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
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()
113 * Connect to Director.
115 void Console::connect_dir()
124 m_textEdit = textEdit; /* our console screen */
127 mainWin->set_status("No Director found.");
131 mainWin->set_status("Already connected.");
135 memset(jcr, 0, sizeof(JCR));
137 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
138 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
140 /* Give GUI a chance */
141 app->processEvents();
144 /* If cons==NULL, default console will be used */
145 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
148 /* Initialize Console TLS context once */
149 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
150 /* Generate passphrase prompt */
151 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
154 /* Initialize TLS context:
155 * Args: CA certfile, CA certdir, Certfile, Keyfile,
156 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
158 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
159 cons->tls_ca_certdir, cons->tls_certfile,
160 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
162 if (!cons->tls_ctx) {
163 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
169 /* Initialize Director TLS context once */
170 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
171 /* Generate passphrase prompt */
172 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
175 /* Initialize TLS context:
176 * Args: CA certfile, CA certdir, Certfile, Keyfile,
177 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
178 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
179 m_dir->tls_ca_certdir, m_dir->tls_certfile,
180 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
182 if (!m_dir->tls_ctx) {
183 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
185 mainWin->set_status("Connection failed");
190 if (m_dir->heartbeat_interval) {
191 heart_beat = m_dir->heartbeat_interval;
193 heart_beat = cons->heartbeat_interval;
198 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
199 _("Director daemon"), m_dir->address,
200 NULL, m_dir->DIRport, 0);
201 if (m_sock == NULL) {
202 mainWin->set_status("Connection failed");
205 /* Update page selector to green to indicate that Console is connected */
206 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
207 QBrush greenBrush(Qt::green);
208 QTreeWidgetItem *item = mainWin->getFromHash(this);
209 item->setForeground(0, greenBrush);
212 jcr->dir_bsock = m_sock;
214 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
223 /* Give GUI a chance */
224 app->processEvents();
226 mainWin->set_status(_("Initializing ..."));
228 /* Set up input notifier */
229 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
230 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
238 fileset_list.clear();
239 fileset_list.clear();
240 messages_list.clear();
242 storage_list.clear();
245 dir_cmd(".jobs", job_list);
246 dir_cmd(".clients", client_list);
247 dir_cmd(".filesets", fileset_list);
248 dir_cmd(".msgs", messages_list);
249 dir_cmd(".pools", pool_list);
250 dir_cmd(".storage", storage_list);
251 dir_cmd(".types", type_list);
252 dir_cmd(".levels", level_list);
254 mainWin->set_status(_("Connected"));
255 startTimer(); /* start message timer */
262 bool Console::dir_cmd(QString &cmd, QStringList &results)
264 return dir_cmd(cmd.toUtf8().data(), results);
268 * Send a command to the Director, and return the
269 * results in a QStringList.
271 bool Console::dir_cmd(const char *cmd, QStringList &results)
277 while ((stat = read()) > 0) {
278 if (mainWin->m_displayAll) display_text(msg());
279 strip_trailing_junk(msg());
284 return true; /* ***FIXME*** return any command error */
287 bool Console::sql_cmd(QString &query, QStringList &results)
289 return sql_cmd(query.toUtf8().data(), results);
293 * Send an sql query to the Director, and return the
294 * results in a QStringList.
296 bool Console::sql_cmd(const char *query, QStringList &results)
299 POOL_MEM cmd(PM_MESSAGE);
301 if (!is_connectedGui()) {
307 pm_strcpy(cmd, ".sql query=\"");
308 pm_strcat(cmd, query);
309 pm_strcat(cmd, "\"");
311 while ((stat = read()) > 0) {
312 if (mainWin->m_displayAll) display_text(msg());
313 strip_trailing_junk(msg());
318 return true; /* ***FIXME*** return any command error */
323 * Send a job name to the director, and read all the resulting
326 bool Console::get_job_defaults(struct job_defaults &job_defs)
334 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
336 while ((stat = read()) > 0) {
337 if (mainWin->m_displayAll) display_text(msg());
338 def = strchr(msg(), '=');
342 /* Pointer to default value */
344 strip_trailing_junk(def);
346 if (strcmp(msg(), "job") == 0) {
347 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
352 if (strcmp(msg(), "pool") == 0) {
353 job_defs.pool_name = def;
356 if (strcmp(msg(), "messages") == 0) {
357 job_defs.messages_name = def;
360 if (strcmp(msg(), "client") == 0) {
361 job_defs.client_name = def;
364 if (strcmp(msg(), "storage") == 0) {
365 job_defs.store_name = def;
368 if (strcmp(msg(), "where") == 0) {
369 job_defs.where = def;
372 if (strcmp(msg(), "level") == 0) {
373 job_defs.level = def;
376 if (strcmp(msg(), "type") == 0) {
380 if (strcmp(msg(), "fileset") == 0) {
381 job_defs.fileset_name = def;
384 if (strcmp(msg(), "catalog") == 0) {
385 job_defs.catalog_name = def;
388 if (strcmp(msg(), "enabled") == 0) {
389 job_defs.enabled = *def == '1' ? true : false;
404 * Save user settings associated with this console
406 void Console::writeSettings()
408 QFont font = get_font();
410 QSettings settings(m_dir->name(), "bat");
411 settings.beginGroup("Console");
412 settings.setValue("consoleFont", font.family());
413 settings.setValue("consolePointSize", font.pointSize());
414 settings.setValue("consoleFixedPitch", font.fixedPitch());
419 * Read and restore user settings associated with this console
421 void Console::readSettings()
423 QFont font = get_font();
425 QSettings settings(m_dir->name(), "bat");
426 settings.beginGroup("Console");
427 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
428 font.setPointSize(settings.value("consolePointSize", 10).toInt());
429 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
431 m_textEdit->setFont(font);
435 * Set the console textEdit font
437 void Console::set_font()
440 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
442 m_textEdit->setFont(font);
447 * Get the console text edit font
449 const QFont Console::get_font()
451 return m_textEdit->font();
455 * Slot for responding to status dir button on button bar
457 void Console::status_dir()
459 QString cmd("status dir");
464 * Slot for responding to messages button on button bar
466 void Console::messages()
468 QString cmd(".messages");
473 * Put text into the console window
475 void Console::display_textf(const char *fmt, ...)
480 va_start(arg_ptr, fmt);
481 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
486 void Console::display_text(const QString buf)
488 m_cursor->insertText(buf);
493 void Console::display_text(const char *buf)
495 m_cursor->insertText(buf);
499 void Console::display_html(const QString buf)
501 m_cursor->insertHtml(buf);
505 /* Position cursor to end of screen */
506 void Console::update_cursor()
508 QApplication::restoreOverrideCursor();
509 m_textEdit->moveCursor(QTextCursor::End);
510 m_textEdit->ensureCursorVisible();
514 * This should be moved into a bSocket class
524 /* Send a command to the Director */
525 void Console::write_dir(const char *msg)
528 mainWin->set_status(_("Processing command ..."));
529 QApplication::setOverrideCursor(Qt::WaitCursor);
532 mainWin->set_status(" Director not connected. Click on connect button.");
533 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
534 QBrush redBrush(Qt::red);
535 QTreeWidgetItem *item = mainWin->getFromHash(this);
536 item->setForeground(0, redBrush);
538 m_at_main_prompt = false;
542 int Console::write(const QString msg)
544 return write(msg.toUtf8().data());
547 int Console::write(const char *msg)
552 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
554 m_at_main_prompt = false;
555 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
556 return m_sock->send();
561 * Get to main command prompt -- i.e. abort any subcommand
563 void Console::beginNewCommand()
565 for (int i=0; i < 3; i++) {
568 if (mainWin->m_displayAll) display_text(msg());
570 if (m_at_main_prompt) {
577 void Console::displayToPrompt()
580 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
581 while (!m_at_prompt) {
582 if ((stat=read()) > 0) {
586 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
589 void Console::discardToPrompt()
592 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
593 while (!m_at_prompt) {
594 if ((stat=read()) > 0) {
595 if (mainWin->m_displayAll) display_text(msg());
598 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
603 * Blocking read from director
610 stat = bnet_wait_data_intr(m_sock, 1);
614 app->processEvents();
615 if (m_api_set && m_messages_pending) {
616 write_dir(".messages");
617 m_messages_pending = false;
621 stat = m_sock->recv();
623 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
627 m_at_main_prompt = false;
630 switch (m_sock->msglen) {
631 case BNET_MSGS_PENDING :
632 if (m_notifier->isEnabled()) {
633 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
634 write_dir(".messages");
636 m_messages_pending = false;
638 m_messages_pending = true;
641 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
643 m_at_main_prompt = false;
644 mainWin->set_status(_("Command completed ..."));
647 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
649 m_at_main_prompt = false;
650 mainWin->set_status(_("Processing command ..."));
652 case BNET_MAIN_PROMPT:
653 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
655 m_at_main_prompt = true;
656 mainWin->set_status(_("At main prompt waiting for input ..."));
657 QApplication::restoreOverrideCursor();
660 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
662 m_at_main_prompt = false;
663 mainWin->set_status(_("At prompt waiting for input ..."));
664 QApplication::restoreOverrideCursor();
666 case BNET_CMD_FAILED:
667 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
668 mainWin->set_status(_("Command failed."));
669 QApplication::restoreOverrideCursor();
671 /* We should not get this one */
673 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
674 mainWin->set_status_ready();
675 QApplication::restoreOverrideCursor();
680 case BNET_START_SELECT:
681 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
682 new selectDialog(this);
685 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
686 new yesnoPopUp(this);
689 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
692 case BNET_START_RTREE:
693 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
697 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
700 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
701 m_sock->recv(); /* get the message */
703 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
705 case BNET_WARNING_MSG:
706 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
707 m_sock->recv(); /* get the message */
709 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
712 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
713 m_sock->recv(); /* get the message */
715 mainWin->set_status(msg());
718 if (is_bnet_stop(m_sock)) { /* error or term request */
719 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
723 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
724 QBrush redBrush(Qt::red);
725 QTreeWidgetItem *item = mainWin->getFromHash(this);
726 item->setForeground(0, redBrush);
727 m_notifier->setEnabled(false);
730 mainWin->set_status(_("Director disconnected."));
731 QApplication::restoreOverrideCursor();
739 /* Called by signal when the Director has output for us */
740 void Console::read_dir(int /* fd */)
742 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
743 while (read() >= 0) {
749 * When the notifier is enabled, read_dir() will automatically be
750 * called by the Qt event loop when ever there is any output
751 * from the Directory, and read_dir() will then display it on
754 * When we are in a bat dialog, we want to control *all* output
755 * from the Directory, so we set notify to off.
756 * m_console->notifiy(false);
758 void Console::notify(bool enable)
760 m_notifier->setEnabled(enable);
763 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
765 m_directorTreeItem = item;
768 void Console::setDirRes(DIRRES *dir)
774 * To have the ability to get the name of the director resource.
776 void Console::getDirResName(QString &name_returned)
778 name_returned = m_dir->name();
781 bool Console::is_connectedGui()
783 if (is_connected()) {
786 QString message("Director ");
787 message += " is currently disconnected\n Please reconnect!!";
788 QMessageBox::warning(this, "Bat",
789 tr(message.toUtf8().data()), QMessageBox::Ok );
795 * A temporary function to prevent connecting to the director if the director
796 * is busy with a restore.
798 bool Console::preventInUseConnect()
800 if (!is_connected()) {
801 QString message("Director ");
802 message += m_dir->name();
803 message += " is currently disconnected\n Please reconnect!!";
804 QMessageBox::warning(this, "Bat",
805 tr(message.toUtf8().data()), QMessageBox::Ok );
807 } else if (!m_at_main_prompt){
808 QString message("Director ");
809 message += m_dir->name();
810 message += " is currently busy\n Please complete restore or other "
811 " operation !! This is a limitation that will be resolved before a beta"
812 " release. This is currently an alpha release.";
813 QMessageBox::warning(this, "Bat",
814 tr(message.toUtf8().data()), QMessageBox::Ok );
816 } else if (!m_at_prompt){
817 QString message("Director ");
818 message += m_dir->name();
819 message += " is currently not at a prompt\n Please try again!!";
820 QMessageBox::warning(this, "Bat",
821 tr(message.toUtf8().data()), QMessageBox::Ok );
829 * Call-back for reading a passphrase for an encrypted PEM file
830 * This function uses getpass(),
831 * which uses a static buffer and is NOT thread-safe.
833 static int tls_pem_callback(char *buf, int size, const void *userdata)
838 const char *prompt = (const char *)userdata;
839 # if defined(HAVE_WIN32)
841 if (win32_cgets(buf, size) == NULL) {
850 passwd = getpass(prompt);
851 bstrncpy(buf, passwd, size);
860 /* Slot for responding to page selectors status help command */
861 void Console::consoleHelp()
867 /* Slot for responding to page selectors reload bacula-dir.conf */
868 void Console::consoleReload()
870 QString cmd("reload");
874 /* Function to get a list of volumes */
875 void Console::getVolumeList(QStringList &volumeList)
877 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
878 if (mainWin->m_sqlDebug) {
879 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
882 if (sql_cmd(query, results)) {
884 QStringList fieldlist;
885 /* Iterate through the lines of results. */
886 foreach (QString resultline, results) {
887 fieldlist = resultline.split("\t");
888 volumeList.append(fieldlist[0]);
889 } /* foreach resultline */
890 } /* if results from query */
893 /* Function to get a list of volumes */
894 void Console::getStatusList(QStringList &statusLongList)
896 QString statusQuery("SELECT JobStatusLong FROM Status");
897 if (mainWin->m_sqlDebug) {
898 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
900 QStringList statusResults;
901 if (sql_cmd(statusQuery, statusResults)) {
903 QStringList fieldlist;
904 /* Iterate through the lines of results. */
905 foreach (QString resultline, statusResults) {
906 fieldlist = resultline.split("\t");
907 statusLongList.append(fieldlist[0]);
908 } /* foreach resultline */
909 } /* if results from statusquery */