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 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
43 static int tls_pem_callback(char *buf, int size, const void *userdata);
46 Console::Console(QStackedWidget *parent)
56 m_at_main_prompt = false;
57 m_textEdit = textEdit; /* our console screen */
58 m_cursor = new QTextCursor(m_textEdit->document());
59 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
62 m_contextActions.append(actionStatusDir);
63 m_contextActions.append(actionConsoleHelp);
64 m_contextActions.append(actionRequestMessages);
65 m_contextActions.append(actionConsoleReload);
66 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
67 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
68 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
69 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
76 void Console::startTimer()
78 m_timer = new QTimer(this);
79 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
80 m_timer->start(mainWin->m_checkMessagesInterval*1000);
83 void Console::stopTimer()
86 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
93 void Console::poll_messages()
95 m_messages_pending = true;
96 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
102 /* Terminate any open socket */
103 void Console::terminate()
107 m_notifier->setEnabled(false);
118 * Connect to Director.
120 void Console::connect_dir()
129 m_textEdit = textEdit; /* our console screen */
132 mainWin->set_status( tr("No Director found."));
136 mainWin->set_status( tr("Already connected."));
140 memset(jcr, 0, sizeof(JCR));
142 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
143 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
145 /* Give GUI a chance */
146 app->processEvents();
149 /* If cons==NULL, default console will be used */
150 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
153 /* Initialize Console TLS context once */
154 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
155 /* Generate passphrase prompt */
156 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
159 /* Initialize TLS context:
160 * Args: CA certfile, CA certdir, Certfile, Keyfile,
161 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
163 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
164 cons->tls_ca_certdir, cons->tls_certfile,
165 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
167 if (!cons->tls_ctx) {
168 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
174 /* Initialize Director TLS context once */
175 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
176 /* Generate passphrase prompt */
177 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
180 /* Initialize TLS context:
181 * Args: CA certfile, CA certdir, Certfile, Keyfile,
182 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
183 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
184 m_dir->tls_ca_certdir, m_dir->tls_certfile,
185 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
187 if (!m_dir->tls_ctx) {
188 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
190 mainWin->set_status( tr("Connection failed") );
195 if (m_dir->heartbeat_interval) {
196 heart_beat = m_dir->heartbeat_interval;
198 heart_beat = cons->heartbeat_interval;
203 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
204 _("Director daemon"), m_dir->address,
205 NULL, m_dir->DIRport, 0);
206 if (m_sock == NULL) {
207 mainWin->set_status("Connection failed");
210 /* Update page selector to green to indicate that Console is connected */
211 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
212 QBrush greenBrush(Qt::green);
213 QTreeWidgetItem *item = mainWin->getFromHash(this);
214 item->setForeground(0, greenBrush);
217 jcr->dir_bsock = m_sock;
219 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
228 /* Give GUI a chance */
229 app->processEvents();
231 mainWin->set_status(_("Initializing ..."));
233 /* Set up input notifier */
234 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
235 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
243 fileset_list.clear();
244 fileset_list.clear();
245 messages_list.clear();
247 storage_list.clear();
250 dir_cmd(".jobs", job_list);
251 dir_cmd(".clients", client_list);
252 dir_cmd(".filesets", fileset_list);
253 dir_cmd(".msgs", messages_list);
254 dir_cmd(".pools", pool_list);
255 dir_cmd(".storage", storage_list);
256 dir_cmd(".types", type_list);
257 dir_cmd(".levels", level_list);
259 mainWin->set_status(_("Connected"));
260 startTimer(); /* start message timer */
267 bool Console::dir_cmd(QString &cmd, QStringList &results)
269 return dir_cmd(cmd.toUtf8().data(), results);
273 * Send a command to the Director, and return the
274 * results in a QStringList.
276 bool Console::dir_cmd(const char *cmd, QStringList &results)
282 while ((stat = read()) > 0) {
283 if (mainWin->m_displayAll) display_text(msg());
284 strip_trailing_junk(msg());
289 return true; /* ***FIXME*** return any command error */
292 bool Console::sql_cmd(QString &query, QStringList &results)
294 return sql_cmd(query.toUtf8().data(), results);
298 * Send an sql query to the Director, and return the
299 * results in a QStringList.
301 bool Console::sql_cmd(const char *query, QStringList &results)
304 POOL_MEM cmd(PM_MESSAGE);
306 if (!is_connectedGui()) {
312 pm_strcpy(cmd, ".sql query=\"");
313 pm_strcat(cmd, query);
314 pm_strcat(cmd, "\"");
316 while ((stat = read()) > 0) {
317 if (mainWin->m_displayAll) {
321 strip_trailing_junk(msg());
326 return true; /* ***FIXME*** return any command error */
331 * Send a job name to the director, and read all the resulting
334 bool Console::get_job_defaults(struct job_defaults &job_defs)
342 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
344 while ((stat = read()) > 0) {
345 if (mainWin->m_displayAll) display_text(msg());
346 def = strchr(msg(), '=');
350 /* Pointer to default value */
352 strip_trailing_junk(def);
354 if (strcmp(msg(), "job") == 0) {
355 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
360 if (strcmp(msg(), "pool") == 0) {
361 job_defs.pool_name = def;
364 if (strcmp(msg(), "messages") == 0) {
365 job_defs.messages_name = def;
368 if (strcmp(msg(), "client") == 0) {
369 job_defs.client_name = def;
372 if (strcmp(msg(), "storage") == 0) {
373 job_defs.store_name = def;
376 if (strcmp(msg(), "where") == 0) {
377 job_defs.where = def;
380 if (strcmp(msg(), "level") == 0) {
381 job_defs.level = def;
384 if (strcmp(msg(), "type") == 0) {
388 if (strcmp(msg(), "fileset") == 0) {
389 job_defs.fileset_name = def;
392 if (strcmp(msg(), "catalog") == 0) {
393 job_defs.catalog_name = def;
396 if (strcmp(msg(), "enabled") == 0) {
397 job_defs.enabled = *def == '1' ? true : false;
412 * Save user settings associated with this console
414 void Console::writeSettings()
416 QFont font = get_font();
418 QSettings settings(m_dir->name(), "bat");
419 settings.beginGroup("Console");
420 settings.setValue("consoleFont", font.family());
421 settings.setValue("consolePointSize", font.pointSize());
422 settings.setValue("consoleFixedPitch", font.fixedPitch());
427 * Read and restore user settings associated with this console
429 void Console::readSettings()
431 QFont font = get_font();
433 QSettings settings(m_dir->name(), "bat");
434 settings.beginGroup("Console");
435 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
436 font.setPointSize(settings.value("consolePointSize", 10).toInt());
437 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
439 m_textEdit->setFont(font);
443 * Set the console textEdit font
445 void Console::set_font()
448 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
450 m_textEdit->setFont(font);
455 * Get the console text edit font
457 const QFont Console::get_font()
459 return m_textEdit->font();
463 * Slot for responding to status dir button on button bar
465 void Console::status_dir()
467 QString cmd("status dir");
472 * Slot for responding to messages button on button bar
474 void Console::messages()
476 QString cmd(".messages");
481 * Put text into the console window
483 void Console::display_textf(const char *fmt, ...)
488 va_start(arg_ptr, fmt);
489 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
494 void Console::display_text(const QString buf)
496 m_cursor->insertText(buf);
501 void Console::display_text(const char *buf)
503 m_cursor->insertText(buf);
507 void Console::display_html(const QString buf)
509 m_cursor->insertHtml(buf);
513 /* Position cursor to end of screen */
514 void Console::update_cursor()
516 QApplication::restoreOverrideCursor();
517 m_textEdit->moveCursor(QTextCursor::End);
518 m_textEdit->ensureCursorVisible();
522 * This should be moved into a bSocket class
532 /* Send a command to the Director */
533 void Console::write_dir(const char *msg)
536 mainWin->set_status(_("Processing command ..."));
537 QApplication::setOverrideCursor(Qt::WaitCursor);
540 mainWin->set_status( tr(" Director not connected. Click on connect button."));
541 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
542 QBrush redBrush(Qt::red);
543 QTreeWidgetItem *item = mainWin->getFromHash(this);
544 item->setForeground(0, redBrush);
546 m_at_main_prompt = false;
550 int Console::write(const QString msg)
552 return write(msg.toUtf8().data());
555 int Console::write(const char *msg)
560 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
562 m_at_main_prompt = false;
563 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
564 return m_sock->send();
569 * Get to main command prompt -- i.e. abort any subcommand
571 void Console::beginNewCommand()
573 for (int i=0; i < 3; i++) {
576 if (mainWin->m_displayAll) display_text(msg());
578 if (m_at_main_prompt) {
585 void Console::displayToPrompt()
589 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
590 while (!m_at_prompt) {
591 if ((stat=read()) > 0) {
593 if (buf.size() >= 8196 || m_messages_pending) {
596 m_messages_pending = false;
601 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
604 void Console::discardToPrompt()
607 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
608 if (mainWin->m_displayAll) {
611 while (!m_at_prompt) {
615 if (mainWin->m_commDebug) Pmsg1(000, "endDiscardToPrompt=%d\n", stat);
620 * Blocking read from director
627 stat = bnet_wait_data_intr(m_sock, 1);
631 app->processEvents();
632 if (m_api_set && m_messages_pending && m_notifier->isEnabled()) {
633 write_dir(".messages");
634 m_messages_pending = false;
638 stat = m_sock->recv();
640 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
644 m_at_main_prompt = false;
647 switch (m_sock->msglen) {
648 case BNET_MSGS_PENDING :
649 if (m_notifier->isEnabled()) {
650 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
651 write_dir(".messages");
653 m_messages_pending = false;
655 m_messages_pending = true;
658 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
660 m_at_main_prompt = false;
661 mainWin->set_status(_("Command completed ..."));
664 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
666 m_at_main_prompt = false;
667 mainWin->set_status(_("Processing command ..."));
669 case BNET_MAIN_PROMPT:
670 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
672 m_at_main_prompt = true;
673 mainWin->set_status(_("At main prompt waiting for input ..."));
674 QApplication::restoreOverrideCursor();
677 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
679 m_at_main_prompt = false;
680 mainWin->set_status(_("At prompt waiting for input ..."));
681 QApplication::restoreOverrideCursor();
683 case BNET_CMD_FAILED:
684 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
685 mainWin->set_status(_("Command failed."));
686 QApplication::restoreOverrideCursor();
688 /* We should not get this one */
690 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
691 mainWin->set_status_ready();
692 QApplication::restoreOverrideCursor();
697 case BNET_START_SELECT:
698 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
699 new selectDialog(this);
702 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
703 new yesnoPopUp(this);
706 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
709 case BNET_START_RTREE:
710 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
714 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
717 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
718 m_sock->recv(); /* get the message */
720 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
722 case BNET_WARNING_MSG:
723 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
724 m_sock->recv(); /* get the message */
726 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
729 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
730 m_sock->recv(); /* get the message */
732 mainWin->set_status(msg());
735 if (is_bnet_stop(m_sock)) { /* error or term request */
736 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
740 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
741 QBrush redBrush(Qt::red);
742 QTreeWidgetItem *item = mainWin->getFromHash(this);
743 item->setForeground(0, redBrush);
744 m_notifier->setEnabled(false);
747 mainWin->set_status(_("Director disconnected."));
748 QApplication::restoreOverrideCursor();
756 /* Called by signal when the Director has output for us */
757 void Console::read_dir(int /* fd */)
759 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
760 while (read() >= 0) {
766 * When the notifier is enabled, read_dir() will automatically be
767 * called by the Qt event loop when ever there is any output
768 * from the Directory, and read_dir() will then display it on
771 * When we are in a bat dialog, we want to control *all* output
772 * from the Directory, so we set notify to off.
773 * m_console->notifiy(false);
775 void Console::notify(bool enable)
777 m_notifier->setEnabled(enable);
780 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
782 m_directorTreeItem = item;
785 void Console::setDirRes(DIRRES *dir)
791 * To have the ability to get the name of the director resource.
793 void Console::getDirResName(QString &name_returned)
795 name_returned = m_dir->name();
798 bool Console::is_connectedGui()
800 if (is_connected()) {
803 QString message = tr("Director is currently disconnected\nPlease reconnect!");
804 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
810 * A temporary function to prevent connecting to the director if the director
811 * is busy with a restore.
813 bool Console::preventInUseConnect()
815 if (!is_connected()) {
816 QString message = tr("Director %1 is currently disconnected\n"
817 "Please reconnect!").arg(m_dir->name());
818 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
820 } else if (!m_at_main_prompt){
821 QString message = tr("Director %1 is currently busy\n Please complete "
822 "restore or other operation! This is a limitation "
823 "that will be resolved before a beta release. "
824 "This is currently an alpha release.").arg(m_dir->name());
825 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
827 } else if (!m_at_prompt){
828 QString message = tr("Director %1 is currently not at a prompt\n"
829 " Please try again!").arg(m_dir->name());
830 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
838 * Call-back for reading a passphrase for an encrypted PEM file
839 * This function uses getpass(),
840 * which uses a static buffer and is NOT thread-safe.
842 static int tls_pem_callback(char *buf, int size, const void *userdata)
847 const char *prompt = (const char *)userdata;
848 # if defined(HAVE_WIN32)
850 if (win32_cgets(buf, size) == NULL) {
859 passwd = getpass(prompt);
860 bstrncpy(buf, passwd, size);
869 /* Slot for responding to page selectors status help command */
870 void Console::consoleHelp()
876 /* Slot for responding to page selectors reload bacula-dir.conf */
877 void Console::consoleReload()
879 QString cmd("reload");
883 /* Function to get a list of volumes */
884 void Console::getVolumeList(QStringList &volumeList)
886 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
887 if (mainWin->m_sqlDebug) {
888 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
891 if (sql_cmd(query, results)) {
893 QStringList fieldlist;
894 /* Iterate through the lines of results. */
895 foreach (QString resultline, results) {
896 fieldlist = resultline.split("\t");
897 volumeList.append(fieldlist[0]);
898 } /* foreach resultline */
899 } /* if results from query */
902 /* Function to get a list of volumes */
903 void Console::getStatusList(QStringList &statusLongList)
905 QString statusQuery("SELECT JobStatusLong FROM Status");
906 if (mainWin->m_sqlDebug) {
907 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
909 QStringList statusResults;
910 if (sql_cmd(statusQuery, statusResults)) {
912 QStringList fieldlist;
913 /* Iterate through the lines of results. */
914 foreach (QString resultline, statusResults) {
915 fieldlist = resultline.split("\t");
916 statusLongList.append(fieldlist[0]);
917 } /* foreach resultline */
918 } /* if results from statusquery */