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("No Director found.");
136 mainWin->set_status("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("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(" 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()
588 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
589 while (!m_at_prompt) {
590 if ((stat=read()) > 0) {
594 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
597 void Console::discardToPrompt()
600 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
601 while (!m_at_prompt) {
602 if ((stat=read()) > 0) {
603 if (mainWin->m_displayAll) display_text(msg());
606 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
611 * Blocking read from director
618 stat = bnet_wait_data_intr(m_sock, 1);
622 app->processEvents();
623 if (m_api_set && m_messages_pending) {
624 write_dir(".messages");
625 m_messages_pending = false;
629 stat = m_sock->recv();
631 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
635 m_at_main_prompt = false;
638 switch (m_sock->msglen) {
639 case BNET_MSGS_PENDING :
640 if (m_notifier->isEnabled()) {
641 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
642 write_dir(".messages");
644 m_messages_pending = false;
646 m_messages_pending = true;
649 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
651 m_at_main_prompt = false;
652 mainWin->set_status(_("Command completed ..."));
655 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
657 m_at_main_prompt = false;
658 mainWin->set_status(_("Processing command ..."));
660 case BNET_MAIN_PROMPT:
661 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
663 m_at_main_prompt = true;
664 mainWin->set_status(_("At main prompt waiting for input ..."));
665 QApplication::restoreOverrideCursor();
668 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
670 m_at_main_prompt = false;
671 mainWin->set_status(_("At prompt waiting for input ..."));
672 QApplication::restoreOverrideCursor();
674 case BNET_CMD_FAILED:
675 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
676 mainWin->set_status(_("Command failed."));
677 QApplication::restoreOverrideCursor();
679 /* We should not get this one */
681 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
682 mainWin->set_status_ready();
683 QApplication::restoreOverrideCursor();
688 case BNET_START_SELECT:
689 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
690 new selectDialog(this);
693 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
694 new yesnoPopUp(this);
697 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
700 case BNET_START_RTREE:
701 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
705 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
708 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
709 m_sock->recv(); /* get the message */
711 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
713 case BNET_WARNING_MSG:
714 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
715 m_sock->recv(); /* get the message */
717 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
720 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
721 m_sock->recv(); /* get the message */
723 mainWin->set_status(msg());
726 if (is_bnet_stop(m_sock)) { /* error or term request */
727 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
731 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
732 QBrush redBrush(Qt::red);
733 QTreeWidgetItem *item = mainWin->getFromHash(this);
734 item->setForeground(0, redBrush);
735 m_notifier->setEnabled(false);
738 mainWin->set_status(_("Director disconnected."));
739 QApplication::restoreOverrideCursor();
747 /* Called by signal when the Director has output for us */
748 void Console::read_dir(int /* fd */)
750 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
751 while (read() >= 0) {
757 * When the notifier is enabled, read_dir() will automatically be
758 * called by the Qt event loop when ever there is any output
759 * from the Directory, and read_dir() will then display it on
762 * When we are in a bat dialog, we want to control *all* output
763 * from the Directory, so we set notify to off.
764 * m_console->notifiy(false);
766 void Console::notify(bool enable)
768 m_notifier->setEnabled(enable);
771 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
773 m_directorTreeItem = item;
776 void Console::setDirRes(DIRRES *dir)
782 * To have the ability to get the name of the director resource.
784 void Console::getDirResName(QString &name_returned)
786 name_returned = m_dir->name();
789 bool Console::is_connectedGui()
791 if (is_connected()) {
794 QString message("Director ");
795 message += " is currently disconnected\n Please reconnect!!";
796 QMessageBox::warning(this, "Bat",
797 tr(message.toUtf8().data()), QMessageBox::Ok );
803 * A temporary function to prevent connecting to the director if the director
804 * is busy with a restore.
806 bool Console::preventInUseConnect()
808 if (!is_connected()) {
809 QString message("Director ");
810 message += m_dir->name();
811 message += " is currently disconnected\n Please reconnect!!";
812 QMessageBox::warning(this, "Bat",
813 tr(message.toUtf8().data()), QMessageBox::Ok );
815 } else if (!m_at_main_prompt){
816 QString message("Director ");
817 message += m_dir->name();
818 message += " is currently busy\n Please complete restore or other "
819 " operation !! This is a limitation that will be resolved before a beta"
820 " release. This is currently an alpha release.";
821 QMessageBox::warning(this, "Bat",
822 tr(message.toUtf8().data()), QMessageBox::Ok );
824 } else if (!m_at_prompt){
825 QString message("Director ");
826 message += m_dir->name();
827 message += " is currently not at a prompt\n Please try again!!";
828 QMessageBox::warning(this, "Bat",
829 tr(message.toUtf8().data()), QMessageBox::Ok );
837 * Call-back for reading a passphrase for an encrypted PEM file
838 * This function uses getpass(),
839 * which uses a static buffer and is NOT thread-safe.
841 static int tls_pem_callback(char *buf, int size, const void *userdata)
846 const char *prompt = (const char *)userdata;
847 # if defined(HAVE_WIN32)
849 if (win32_cgets(buf, size) == NULL) {
858 passwd = getpass(prompt);
859 bstrncpy(buf, passwd, size);
868 /* Slot for responding to page selectors status help command */
869 void Console::consoleHelp()
875 /* Slot for responding to page selectors reload bacula-dir.conf */
876 void Console::consoleReload()
878 QString cmd("reload");
882 /* Function to get a list of volumes */
883 void Console::getVolumeList(QStringList &volumeList)
885 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
886 if (mainWin->m_sqlDebug) {
887 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
890 if (sql_cmd(query, results)) {
892 QStringList fieldlist;
893 /* Iterate through the lines of results. */
894 foreach (QString resultline, results) {
895 fieldlist = resultline.split("\t");
896 volumeList.append(fieldlist[0]);
897 } /* foreach resultline */
898 } /* if results from query */
901 /* Function to get a list of volumes */
902 void Console::getStatusList(QStringList &statusLongList)
904 QString statusQuery("SELECT JobStatusLong FROM Status");
905 if (mainWin->m_sqlDebug) {
906 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
908 QStringList statusResults;
909 if (sql_cmd(statusQuery, statusResults)) {
911 QStringList fieldlist;
912 /* Iterate through the lines of results. */
913 foreach (QString resultline, statusResults) {
914 fieldlist = resultline.split("\t");
915 statusLongList.append(fieldlist[0]);
916 } /* foreach resultline */
917 } /* if results from statusquery */