2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2008 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 Kern Sibbald.
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
43 static int tls_pem_callback(char *buf, int size, const void *userdata);
46 Console::Console(QStackedWidget *parent):
49 m_messages_pending(false)
59 m_at_main_prompt = false;
60 m_textEdit = textEdit; /* our console screen */
61 m_cursor = new QTextCursor(m_textEdit->document());
62 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
65 m_contextActions.append(actionStatusDir);
66 m_contextActions.append(actionConsoleHelp);
67 m_contextActions.append(actionRequestMessages);
68 m_contextActions.append(actionConsoleReload);
69 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
70 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
71 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
72 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
79 void Console::startTimer()
81 m_timer = new QTimer(this);
82 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
83 m_timer->start(mainWin->m_checkMessagesInterval*1000);
86 void Console::stopTimer()
89 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
96 void Console::poll_messages()
98 m_messages_pending = true;
99 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
105 /* Terminate any open socket */
106 void Console::terminate()
110 m_notifier->setEnabled(false);
121 * Connect to Director.
123 void Console::connect_dir()
132 m_textEdit = textEdit; /* our console screen */
135 mainWin->set_status( tr("No Director found."));
139 mainWin->set_status( tr("Already connected."));
143 memset(jcr, 0, sizeof(JCR));
145 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
146 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
148 /* Give GUI a chance */
149 app->processEvents();
152 /* If cons==NULL, default console will be used */
153 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
156 /* Initialize Console TLS context once */
157 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
158 /* Generate passphrase prompt */
159 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
162 /* Initialize TLS context:
163 * Args: CA certfile, CA certdir, Certfile, Keyfile,
164 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
166 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
167 cons->tls_ca_certdir, cons->tls_certfile,
168 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
170 if (!cons->tls_ctx) {
171 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
177 /* Initialize Director TLS context once */
178 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
179 /* Generate passphrase prompt */
180 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
183 /* Initialize TLS context:
184 * Args: CA certfile, CA certdir, Certfile, Keyfile,
185 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
186 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
187 m_dir->tls_ca_certdir, m_dir->tls_certfile,
188 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
190 if (!m_dir->tls_ctx) {
191 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
193 mainWin->set_status( tr("Connection failed") );
198 if (m_dir->heartbeat_interval) {
199 heart_beat = m_dir->heartbeat_interval;
201 heart_beat = cons->heartbeat_interval;
206 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
207 _("Director daemon"), m_dir->address,
208 NULL, m_dir->DIRport, 0);
209 if (m_sock == NULL) {
210 mainWin->set_status("Connection failed");
213 /* Update page selector to green to indicate that Console is connected */
214 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
215 QBrush greenBrush(Qt::green);
216 QTreeWidgetItem *item = mainWin->getFromHash(this);
217 item->setForeground(0, greenBrush);
220 jcr->dir_bsock = m_sock;
222 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
231 /* Give GUI a chance */
232 app->processEvents();
234 mainWin->set_status(_("Initializing ..."));
237 /* Set up input notifier */
238 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
239 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
249 fileset_list.clear();
250 fileset_list.clear();
251 messages_list.clear();
253 storage_list.clear();
256 dir_cmd(".jobs", job_list);
257 dir_cmd(".clients", client_list);
258 dir_cmd(".filesets", fileset_list);
259 dir_cmd(".msgs", messages_list);
260 dir_cmd(".pools", pool_list);
261 dir_cmd(".storage", storage_list);
262 dir_cmd(".types", type_list);
263 dir_cmd(".levels", level_list);
265 mainWin->set_status(_("Connected"));
266 startTimer(); /* start message timer */
273 bool Console::dir_cmd(QString &cmd, QStringList &results)
275 return dir_cmd(cmd.toUtf8().data(), results);
279 * Send a command to the Director, and return the
280 * results in a QStringList.
282 bool Console::dir_cmd(const char *cmd, QStringList &results)
288 while ((stat = read()) > 0) {
289 if (mainWin->m_displayAll) display_text(msg());
290 strip_trailing_junk(msg());
295 return true; /* ***FIXME*** return any command error */
298 bool Console::sql_cmd(QString &query, QStringList &results)
300 return sql_cmd(query.toUtf8().data(), results);
304 * Send an sql query to the Director, and return the
305 * results in a QStringList.
307 bool Console::sql_cmd(const char *query, QStringList &results)
310 POOL_MEM cmd(PM_MESSAGE);
312 if (!is_connectedGui()) {
318 pm_strcpy(cmd, ".sql query=\"");
319 pm_strcat(cmd, query);
320 pm_strcat(cmd, "\"");
322 while ((stat = read()) > 0) {
323 if (mainWin->m_displayAll) {
327 strip_trailing_junk(msg());
332 return true; /* ***FIXME*** return any command error */
337 * Send a job name to the director, and read all the resulting
340 bool Console::get_job_defaults(struct job_defaults &job_defs)
348 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
350 while ((stat = read()) > 0) {
351 if (mainWin->m_displayAll) display_text(msg());
352 def = strchr(msg(), '=');
356 /* Pointer to default value */
358 strip_trailing_junk(def);
360 if (strcmp(msg(), "job") == 0) {
361 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
366 if (strcmp(msg(), "pool") == 0) {
367 job_defs.pool_name = def;
370 if (strcmp(msg(), "messages") == 0) {
371 job_defs.messages_name = def;
374 if (strcmp(msg(), "client") == 0) {
375 job_defs.client_name = def;
378 if (strcmp(msg(), "storage") == 0) {
379 job_defs.store_name = def;
382 if (strcmp(msg(), "where") == 0) {
383 job_defs.where = def;
386 if (strcmp(msg(), "level") == 0) {
387 job_defs.level = def;
390 if (strcmp(msg(), "type") == 0) {
394 if (strcmp(msg(), "fileset") == 0) {
395 job_defs.fileset_name = def;
398 if (strcmp(msg(), "catalog") == 0) {
399 job_defs.catalog_name = def;
402 if (strcmp(msg(), "enabled") == 0) {
403 job_defs.enabled = *def == '1' ? true : false;
418 * Save user settings associated with this console
420 void Console::writeSettings()
422 QFont font = get_font();
424 QSettings settings(m_dir->name(), "bat");
425 settings.beginGroup("Console");
426 settings.setValue("consoleFont", font.family());
427 settings.setValue("consolePointSize", font.pointSize());
428 settings.setValue("consoleFixedPitch", font.fixedPitch());
433 * Read and restore user settings associated with this console
435 void Console::readSettings()
437 QFont font = get_font();
439 QSettings settings(m_dir->name(), "bat");
440 settings.beginGroup("Console");
441 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
442 font.setPointSize(settings.value("consolePointSize", 10).toInt());
443 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
445 m_textEdit->setFont(font);
449 * Set the console textEdit font
451 void Console::set_font()
454 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
456 m_textEdit->setFont(font);
461 * Get the console text edit font
463 const QFont Console::get_font()
465 return m_textEdit->font();
469 * Slot for responding to status dir button on button bar
471 void Console::status_dir()
473 QString cmd("status dir");
478 * Slot for responding to messages button on button bar
480 void Console::messages()
482 QString cmd(".messages");
487 * Put text into the console window
489 void Console::display_textf(const char *fmt, ...)
494 va_start(arg_ptr, fmt);
495 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
500 void Console::display_text(const QString buf)
502 m_cursor->insertText(buf);
507 void Console::display_text(const char *buf)
509 m_cursor->insertText(buf);
513 void Console::display_html(const QString buf)
515 m_cursor->insertHtml(buf);
519 /* Position cursor to end of screen */
520 void Console::update_cursor()
522 QApplication::restoreOverrideCursor();
523 m_textEdit->moveCursor(QTextCursor::End);
524 m_textEdit->ensureCursorVisible();
528 * This should be moved into a bSocket class
538 /* Send a command to the Director */
539 void Console::write_dir(const char *msg)
542 mainWin->set_status(_("Processing command ..."));
543 QApplication::setOverrideCursor(Qt::WaitCursor);
546 mainWin->set_status( tr(" Director not connected. Click on connect button."));
547 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
548 QBrush redBrush(Qt::red);
549 QTreeWidgetItem *item = mainWin->getFromHash(this);
550 item->setForeground(0, redBrush);
552 m_at_main_prompt = false;
556 int Console::write(const QString msg)
558 return write(msg.toUtf8().data());
561 int Console::write(const char *msg)
566 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
568 m_at_main_prompt = false;
569 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
570 return m_sock->send();
575 * Get to main command prompt -- i.e. abort any subcommand
577 void Console::beginNewCommand()
579 for (int i=0; i < 3; i++) {
582 if (mainWin->m_displayAll) display_text(msg());
584 if (m_at_main_prompt) {
591 void Console::displayToPrompt()
595 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
596 while (!m_at_prompt) {
597 if ((stat=read()) > 0) {
599 if (buf.size() >= 8196 || m_messages_pending) {
602 m_messages_pending = false;
607 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
610 void Console::discardToPrompt()
613 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
614 if (mainWin->m_displayAll) {
617 while (!m_at_prompt) {
621 if (mainWin->m_commDebug) Pmsg1(000, "endDiscardToPrompt=%d\n", stat);
624 int Console::sock_read()
628 bool wasEnabled = notify(false);
629 stat = m_sock->recv();
632 stat = m_sock->recv();
638 * Blocking read from director
645 stat = m_sock->wait_data_intr(0, 50000);
649 app->processEvents();
650 if (m_api_set && m_messages_pending && is_notify_enabled()) {
651 write_dir(".messages");
652 m_messages_pending = false;
658 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
662 m_at_main_prompt = false;
665 switch (m_sock->msglen) {
666 case BNET_MSGS_PENDING :
667 if (is_notify_enabled()) {
668 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
669 write_dir(".messages");
671 m_messages_pending = false;
673 m_messages_pending = true;
676 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
678 m_at_main_prompt = false;
679 mainWin->set_status(_("Command completed ..."));
682 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
684 m_at_main_prompt = false;
685 mainWin->set_status(_("Processing command ..."));
687 case BNET_MAIN_PROMPT:
688 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
690 m_at_main_prompt = true;
691 mainWin->set_status(_("At main prompt waiting for input ..."));
692 QApplication::restoreOverrideCursor();
695 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
697 m_at_main_prompt = false;
698 mainWin->set_status(_("At prompt waiting for input ..."));
699 QApplication::restoreOverrideCursor();
701 case BNET_CMD_FAILED:
702 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
703 mainWin->set_status(_("Command failed."));
704 QApplication::restoreOverrideCursor();
706 /* We should not get this one */
708 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
709 mainWin->set_status_ready();
710 QApplication::restoreOverrideCursor();
715 case BNET_START_SELECT:
716 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
717 new selectDialog(this);
720 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
721 new yesnoPopUp(this);
724 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
727 case BNET_START_RTREE:
728 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
732 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
735 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
736 stat = sock_read(); /* get the message */
738 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
740 case BNET_WARNING_MSG:
741 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
742 stat = sock_read(); /* get the message */
744 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
747 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
748 stat = sock_read(); /* get the message */
750 mainWin->set_status(msg());
753 if (is_bnet_stop(m_sock)) { /* error or term request */
754 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
758 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
759 QBrush redBrush(Qt::red);
760 QTreeWidgetItem *item = mainWin->getFromHash(this);
761 item->setForeground(0, redBrush);
763 m_notifier->setEnabled(false);
767 mainWin->set_status(_("Director disconnected."));
768 QApplication::restoreOverrideCursor();
776 /* Called by signal when the Director has output for us */
777 void Console::read_dir(int /* fd */)
779 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
780 while (read() >= 0) {
786 * When the notifier is enabled, read_dir() will automatically be
787 * called by the Qt event loop when ever there is any output
788 * from the Directory, and read_dir() will then display it on
791 * When we are in a bat dialog, we want to control *all* output
792 * from the Directory, so we set notify to off.
793 * m_console->notifiy(false);
795 bool Console::notify(bool enable)
797 bool prev_enabled = false;
799 prev_enabled = m_notifier->isEnabled();
800 m_notifier->setEnabled(enable);
805 bool Console::is_notify_enabled() const
807 bool enabled = false;
809 enabled = m_notifier->isEnabled();
813 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
815 m_directorTreeItem = item;
818 void Console::setDirRes(DIRRES *dir)
824 * To have the ability to get the name of the director resource.
826 void Console::getDirResName(QString &name_returned)
828 name_returned = m_dir->name();
831 bool Console::is_connectedGui()
833 if (is_connected()) {
836 QString message = tr("Director is currently disconnected\nPlease reconnect!");
837 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
843 * A temporary function to prevent connecting to the director if the director
844 * is busy with a restore.
846 bool Console::preventInUseConnect()
848 if (!is_connected()) {
849 QString message = tr("Director %1 is currently disconnected\n"
850 "Please reconnect!").arg(m_dir->name());
851 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
853 } else if (!m_at_main_prompt){
854 QString message = tr("Director %1 is currently busy\n Please complete "
855 "restore or other operation! This is a limitation "
856 "that will be resolved before a beta release. "
857 "This is currently an alpha release.").arg(m_dir->name());
858 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
860 } else if (!m_at_prompt){
861 QString message = tr("Director %1 is currently not at a prompt\n"
862 " Please try again!").arg(m_dir->name());
863 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
871 * Call-back for reading a passphrase for an encrypted PEM file
872 * This function uses getpass(),
873 * which uses a static buffer and is NOT thread-safe.
875 static int tls_pem_callback(char *buf, int size, const void *userdata)
880 const char *prompt = (const char *)userdata;
881 # if defined(HAVE_WIN32)
883 if (win32_cgets(buf, size) == NULL) {
892 passwd = getpass(prompt);
893 bstrncpy(buf, passwd, size);
902 /* Slot for responding to page selectors status help command */
903 void Console::consoleHelp()
909 /* Slot for responding to page selectors reload bacula-dir.conf */
910 void Console::consoleReload()
912 QString cmd("reload");
916 /* Function to get a list of volumes */
917 void Console::getVolumeList(QStringList &volumeList)
919 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
920 if (mainWin->m_sqlDebug) {
921 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
924 if (sql_cmd(query, results)) {
926 QStringList fieldlist;
927 /* Iterate through the lines of results. */
928 foreach (QString resultline, results) {
929 fieldlist = resultline.split("\t");
930 volumeList.append(fieldlist[0]);
931 } /* foreach resultline */
932 } /* if results from query */
935 /* Function to get a list of volumes */
936 void Console::getStatusList(QStringList &statusLongList)
938 QString statusQuery("SELECT JobStatusLong FROM Status");
939 if (mainWin->m_sqlDebug) {
940 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
942 QStringList statusResults;
943 if (sql_cmd(statusQuery, statusResults)) {
945 QStringList fieldlist;
946 /* Iterate through the lines of results. */
947 foreach (QString resultline, statusResults) {
948 fieldlist = resultline.split("\t");
949 statusLongList.append(fieldlist[0]);
950 } /* foreach resultline */
951 } /* if results from statusquery */