2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2007 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
33 * Kern Sibbald, January MMVII
37 //#include <QAbstractEventDispatcher>
44 static int tls_pem_callback(char *buf, int size, const void *userdata);
47 Console::Console(QStackedWidget *parent)
57 m_at_main_prompt = false;
58 m_textEdit = textEdit; /* our console screen */
59 m_cursor = new QTextCursor(m_textEdit->document());
60 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
63 m_contextActions.append(actionStatusDir);
64 m_contextActions.append(actionConsoleHelp);
65 m_contextActions.append(actionRequestMessages);
66 m_contextActions.append(actionConsoleReload);
67 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
68 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
69 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
70 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
77 void Console::startTimer()
79 m_timer = new QTimer(this);
80 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
81 m_timer->start(mainWin->m_checkMessagesInterval*1000);
84 void Console::stopTimer()
87 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
94 void Console::poll_messages()
96 m_messages_pending = true;
97 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
103 /* Terminate any open socket */
104 void Console::terminate()
114 * Connect to Director.
116 void Console::connect_dir()
125 m_textEdit = textEdit; /* our console screen */
128 mainWin->set_status("No Director found.");
132 mainWin->set_status("Already connected.");
136 memset(jcr, 0, sizeof(JCR));
138 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
139 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
141 /* Give GUI a chance */
142 app->processEvents();
145 /* If cons==NULL, default console will be used */
146 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
149 /* Initialize Console TLS context once */
150 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
151 /* Generate passphrase prompt */
152 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
155 /* Initialize TLS context:
156 * Args: CA certfile, CA certdir, Certfile, Keyfile,
157 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
159 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
160 cons->tls_ca_certdir, cons->tls_certfile,
161 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
163 if (!cons->tls_ctx) {
164 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
170 /* Initialize Director TLS context once */
171 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
172 /* Generate passphrase prompt */
173 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
176 /* Initialize TLS context:
177 * Args: CA certfile, CA certdir, Certfile, Keyfile,
178 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
179 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
180 m_dir->tls_ca_certdir, m_dir->tls_certfile,
181 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
183 if (!m_dir->tls_ctx) {
184 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
186 mainWin->set_status("Connection failed");
191 if (m_dir->heartbeat_interval) {
192 heart_beat = m_dir->heartbeat_interval;
194 heart_beat = cons->heartbeat_interval;
199 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
200 _("Director daemon"), m_dir->address,
201 NULL, m_dir->DIRport, 0);
202 if (m_sock == NULL) {
203 mainWin->set_status("Connection failed");
206 /* Update page selector to green to indicate that Console is connected */
207 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
208 QBrush greenBrush(Qt::green);
209 QTreeWidgetItem *item = mainWin->getFromHash(this);
210 item->setForeground(0, greenBrush);
213 jcr->dir_bsock = m_sock;
215 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
224 /* Give GUI a chance */
225 app->processEvents();
227 mainWin->set_status(_("Initializing ..."));
229 /* Set up input notifier */
230 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
231 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
239 fileset_list.clear();
240 fileset_list.clear();
241 messages_list.clear();
243 storage_list.clear();
246 dir_cmd(".jobs", job_list);
247 dir_cmd(".clients", client_list);
248 dir_cmd(".filesets", fileset_list);
249 dir_cmd(".msgs", messages_list);
250 dir_cmd(".pools", pool_list);
251 dir_cmd(".storage", storage_list);
252 dir_cmd(".types", type_list);
253 dir_cmd(".levels", level_list);
255 mainWin->set_status(_("Connected"));
256 startTimer(); /* start message timer */
263 bool Console::dir_cmd(QString &cmd, QStringList &results)
265 return dir_cmd(cmd.toUtf8().data(), results);
269 * Send a command to the Director, and return the
270 * results in a QStringList.
272 bool Console::dir_cmd(const char *cmd, QStringList &results)
278 while ((stat = read()) > 0) {
279 if (mainWin->m_displayAll) display_text(msg());
280 strip_trailing_junk(msg());
285 return true; /* ***FIXME*** return any command error */
288 bool Console::sql_cmd(QString &query, QStringList &results)
290 return sql_cmd(query.toUtf8().data(), results);
294 * Send an sql query to the Director, and return the
295 * results in a QStringList.
297 bool Console::sql_cmd(const char *query, QStringList &results)
300 POOL_MEM cmd(PM_MESSAGE);
302 if (!is_connectedGui()) {
308 pm_strcpy(cmd, ".sql query=\"");
309 pm_strcat(cmd, query);
310 pm_strcat(cmd, "\"");
312 while ((stat = read()) > 0) {
313 if (mainWin->m_displayAll) display_text(msg());
314 strip_trailing_junk(msg());
319 return true; /* ***FIXME*** return any command error */
324 * Send a job name to the director, and read all the resulting
327 bool Console::get_job_defaults(struct job_defaults &job_defs)
335 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
337 while ((stat = read()) > 0) {
338 if (mainWin->m_displayAll) display_text(msg());
339 def = strchr(msg(), '=');
343 /* Pointer to default value */
345 strip_trailing_junk(def);
347 if (strcmp(msg(), "job") == 0) {
348 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
353 if (strcmp(msg(), "pool") == 0) {
354 job_defs.pool_name = def;
357 if (strcmp(msg(), "messages") == 0) {
358 job_defs.messages_name = def;
361 if (strcmp(msg(), "client") == 0) {
362 job_defs.client_name = def;
365 if (strcmp(msg(), "storage") == 0) {
366 job_defs.store_name = def;
369 if (strcmp(msg(), "where") == 0) {
370 job_defs.where = def;
373 if (strcmp(msg(), "level") == 0) {
374 job_defs.level = def;
377 if (strcmp(msg(), "type") == 0) {
381 if (strcmp(msg(), "fileset") == 0) {
382 job_defs.fileset_name = def;
385 if (strcmp(msg(), "catalog") == 0) {
386 job_defs.catalog_name = def;
389 if (strcmp(msg(), "enabled") == 0) {
390 job_defs.enabled = *def == '1' ? true : false;
405 * Save user settings associated with this console
407 void Console::writeSettings()
409 QFont font = get_font();
411 QSettings settings(m_dir->name(), "bat");
412 settings.beginGroup("Console");
413 settings.setValue("consoleFont", font.family());
414 settings.setValue("consolePointSize", font.pointSize());
415 settings.setValue("consoleFixedPitch", font.fixedPitch());
420 * Read and restore user settings associated with this console
422 void Console::readSettings()
424 QFont font = get_font();
426 QSettings settings(m_dir->name(), "bat");
427 settings.beginGroup("Console");
428 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
429 font.setPointSize(settings.value("consolePointSize", 10).toInt());
430 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
432 m_textEdit->setFont(font);
436 * Set the console textEdit font
438 void Console::set_font()
441 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
443 m_textEdit->setFont(font);
448 * Get the console text edit font
450 const QFont Console::get_font()
452 return m_textEdit->font();
456 * Slot for responding to status dir button on button bar
458 void Console::status_dir()
460 QString cmd("status dir");
465 * Slot for responding to messages button on button bar
467 void Console::messages()
469 QString cmd(".messages");
474 * Put text into the console window
476 void Console::display_textf(const char *fmt, ...)
481 va_start(arg_ptr, fmt);
482 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
487 void Console::display_text(const QString buf)
489 m_cursor->insertText(buf);
494 void Console::display_text(const char *buf)
496 m_cursor->insertText(buf);
500 void Console::display_html(const QString buf)
502 m_cursor->insertHtml(buf);
506 /* Position cursor to end of screen */
507 void Console::update_cursor()
509 QApplication::restoreOverrideCursor();
510 m_textEdit->moveCursor(QTextCursor::End);
511 m_textEdit->ensureCursorVisible();
515 * This should be moved into a bSocket class
525 /* Send a command to the Director */
526 void Console::write_dir(const char *msg)
529 mainWin->set_status(_("Processing command ..."));
530 QApplication::setOverrideCursor(Qt::WaitCursor);
533 mainWin->set_status(" Director not connected. Click on connect button.");
534 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
535 QBrush redBrush(Qt::red);
536 QTreeWidgetItem *item = mainWin->getFromHash(this);
537 item->setForeground(0, redBrush);
539 m_at_main_prompt = false;
543 int Console::write(const QString msg)
545 return write(msg.toUtf8().data());
548 int Console::write(const char *msg)
553 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
555 m_at_main_prompt = false;
556 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
557 return m_sock->send();
562 * Get to main command prompt -- i.e. abort any subcommand
564 void Console::beginNewCommand()
566 for (int i=0; i < 3; i++) {
569 if (mainWin->m_displayAll) display_text(msg());
571 if (m_at_main_prompt) {
578 void Console::displayToPrompt()
581 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
582 while (!m_at_prompt) {
583 if ((stat=read()) > 0) {
587 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
590 void Console::discardToPrompt()
593 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
594 while (!m_at_prompt) {
595 if ((stat=read()) > 0) {
596 if (mainWin->m_displayAll) display_text(msg());
599 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
604 * Blocking read from director
611 stat = bnet_wait_data_intr(m_sock, 1);
615 app->processEvents();
616 if (m_api_set && m_messages_pending) {
617 write_dir(".messages");
618 m_messages_pending = false;
622 stat = m_sock->recv();
624 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
628 m_at_main_prompt = false;
631 switch (m_sock->msglen) {
632 case BNET_MSGS_PENDING :
633 if (m_notifier->isEnabled()) {
634 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
635 write_dir(".messages");
637 m_messages_pending = false;
639 m_messages_pending = true;
642 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
644 m_at_main_prompt = false;
645 mainWin->set_status(_("Command completed ..."));
648 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
650 m_at_main_prompt = false;
651 mainWin->set_status(_("Processing command ..."));
653 case BNET_MAIN_PROMPT:
654 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
656 m_at_main_prompt = true;
657 mainWin->set_status(_("At main prompt waiting for input ..."));
658 QApplication::restoreOverrideCursor();
661 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
663 m_at_main_prompt = false;
664 mainWin->set_status(_("At prompt waiting for input ..."));
665 QApplication::restoreOverrideCursor();
667 case BNET_CMD_FAILED:
668 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
669 mainWin->set_status(_("Command failed."));
670 QApplication::restoreOverrideCursor();
672 /* We should not get this one */
674 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
675 mainWin->set_status_ready();
676 QApplication::restoreOverrideCursor();
681 case BNET_START_SELECT:
682 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
683 new selectDialog(this);
686 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
690 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
691 m_sock->recv(); /* get the message */
693 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
695 case BNET_WARNING_MSG:
696 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
697 m_sock->recv(); /* get the message */
699 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
702 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
703 m_sock->recv(); /* get the message */
705 mainWin->set_status(msg());
708 if (is_bnet_stop(m_sock)) { /* error or term request */
709 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
713 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
714 QBrush redBrush(Qt::red);
715 QTreeWidgetItem *item = mainWin->getFromHash(this);
716 item->setForeground(0, redBrush);
717 m_notifier->setEnabled(false);
720 mainWin->set_status(_("Director disconnected."));
721 QApplication::restoreOverrideCursor();
729 /* Called by signal when the Director has output for us */
730 void Console::read_dir(int /* fd */)
732 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
733 while (read() >= 0) {
739 * When the notifier is enabled, read_dir() will automatically be
740 * called by the Qt event loop when ever there is any output
741 * from the Directory, and read_dir() will then display it on
744 * When we are in a bat dialog, we want to control *all* output
745 * from the Directory, so we set notify to off.
746 * m_console->notifiy(false);
748 void Console::notify(bool enable)
750 m_notifier->setEnabled(enable);
753 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
755 m_directorTreeItem = item;
758 void Console::setDirRes(DIRRES *dir)
764 * To have the ability to get the name of the director resource.
766 void Console::getDirResName(QString &name_returned)
768 name_returned = m_dir->name();
771 bool Console::is_connectedGui()
773 if (is_connected()) {
776 QString message("Director ");
777 message += " is currently disconnected\n Please reconnect!!";
778 QMessageBox::warning(this, "Bat",
779 tr(message.toUtf8().data()), QMessageBox::Ok );
785 * A temporary function to prevent connecting to the director if the director
786 * is busy with a restore.
788 bool Console::preventInUseConnect()
790 if (!is_connected()) {
791 QString message("Director ");
792 message += m_dir->name();
793 message += " is currently disconnected\n Please reconnect!!";
794 QMessageBox::warning(this, "Bat",
795 tr(message.toUtf8().data()), QMessageBox::Ok );
797 } else if (!m_at_main_prompt){
798 QString message("Director ");
799 message += m_dir->name();
800 message += " is currently busy\n Please complete restore or other "
801 " operation !! This is a limitation that will be resolved before a beta"
802 " release. This is currently an alpha release.";
803 QMessageBox::warning(this, "Bat",
804 tr(message.toUtf8().data()), QMessageBox::Ok );
806 } else if (!m_at_prompt){
807 QString message("Director ");
808 message += m_dir->name();
809 message += " is currently not at a prompt\n Please try again!!";
810 QMessageBox::warning(this, "Bat",
811 tr(message.toUtf8().data()), QMessageBox::Ok );
819 * Call-back for reading a passphrase for an encrypted PEM file
820 * This function uses getpass(),
821 * which uses a static buffer and is NOT thread-safe.
823 static int tls_pem_callback(char *buf, int size, const void *userdata)
828 const char *prompt = (const char *)userdata;
829 # if defined(HAVE_WIN32)
831 if (win32_cgets(buf, size) == NULL) {
840 passwd = getpass(prompt);
841 bstrncpy(buf, passwd, size);
850 /* Slot for responding to page selectors status help command */
851 void Console::consoleHelp()
857 /* Slot for responding to page selectors reload bacula-dir.conf */
858 void Console::consoleReload()
860 QString cmd("reload");
864 /* Function to get a list of volumes */
865 void Console::getVolumeList(QStringList &volumeList)
867 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
868 if (mainWin->m_sqlDebug) {
869 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
872 if (sql_cmd(query, results)) {
874 QStringList fieldlist;
875 /* Iterate through the lines of results. */
876 foreach (QString resultline, results) {
877 fieldlist = resultline.split("\t");
878 volumeList.append(fieldlist[0]);
879 } /* foreach resultline */
880 } /* if results from query */
883 /* Function to get a list of volumes */
884 void Console::getStatusList(QStringList &statusLongList)
886 QString statusQuery("SELECT JobStatusLong FROM Status");
887 if (mainWin->m_sqlDebug) {
888 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
890 QStringList statusResults;
891 if (sql_cmd(statusQuery, statusResults)) {
893 QStringList fieldlist;
894 /* Iterate through the lines of results. */
895 foreach (QString resultline, statusResults) {
896 fieldlist = resultline.split("\t");
897 statusLongList.append(fieldlist[0]);
898 } /* foreach resultline */
899 } /* if results from statusquery */