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()
589 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
590 while (!m_at_prompt) {
591 if ((stat=read()) > 0) {
593 if (buf.size() >= 8196) {
600 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
603 void Console::discardToPrompt()
606 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
607 if (mainWin->m_displayAll) {
610 while (!m_at_prompt) {
614 if (mainWin->m_commDebug) Pmsg1(000, "endDiscardToPrompt=%d\n", stat);
619 * Blocking read from director
626 stat = bnet_wait_data_intr(m_sock, 1);
630 app->processEvents();
631 if (m_api_set && m_messages_pending) {
632 write_dir(".messages");
633 m_messages_pending = false;
637 stat = m_sock->recv();
639 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
643 m_at_main_prompt = false;
646 switch (m_sock->msglen) {
647 case BNET_MSGS_PENDING :
648 if (m_notifier->isEnabled()) {
649 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
650 write_dir(".messages");
652 m_messages_pending = false;
654 m_messages_pending = true;
657 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
659 m_at_main_prompt = false;
660 mainWin->set_status(_("Command completed ..."));
663 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
665 m_at_main_prompt = false;
666 mainWin->set_status(_("Processing command ..."));
668 case BNET_MAIN_PROMPT:
669 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
671 m_at_main_prompt = true;
672 mainWin->set_status(_("At main prompt waiting for input ..."));
673 QApplication::restoreOverrideCursor();
676 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
678 m_at_main_prompt = false;
679 mainWin->set_status(_("At prompt waiting for input ..."));
680 QApplication::restoreOverrideCursor();
682 case BNET_CMD_FAILED:
683 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
684 mainWin->set_status(_("Command failed."));
685 QApplication::restoreOverrideCursor();
687 /* We should not get this one */
689 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
690 mainWin->set_status_ready();
691 QApplication::restoreOverrideCursor();
696 case BNET_START_SELECT:
697 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
698 new selectDialog(this);
701 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
702 new yesnoPopUp(this);
705 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
708 case BNET_START_RTREE:
709 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
713 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
716 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
717 m_sock->recv(); /* get the message */
719 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
721 case BNET_WARNING_MSG:
722 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
723 m_sock->recv(); /* get the message */
725 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
728 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
729 m_sock->recv(); /* get the message */
731 mainWin->set_status(msg());
734 if (is_bnet_stop(m_sock)) { /* error or term request */
735 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
739 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
740 QBrush redBrush(Qt::red);
741 QTreeWidgetItem *item = mainWin->getFromHash(this);
742 item->setForeground(0, redBrush);
743 m_notifier->setEnabled(false);
746 mainWin->set_status(_("Director disconnected."));
747 QApplication::restoreOverrideCursor();
755 /* Called by signal when the Director has output for us */
756 void Console::read_dir(int /* fd */)
758 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
759 while (read() >= 0) {
765 * When the notifier is enabled, read_dir() will automatically be
766 * called by the Qt event loop when ever there is any output
767 * from the Directory, and read_dir() will then display it on
770 * When we are in a bat dialog, we want to control *all* output
771 * from the Directory, so we set notify to off.
772 * m_console->notifiy(false);
774 void Console::notify(bool enable)
776 m_notifier->setEnabled(enable);
779 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
781 m_directorTreeItem = item;
784 void Console::setDirRes(DIRRES *dir)
790 * To have the ability to get the name of the director resource.
792 void Console::getDirResName(QString &name_returned)
794 name_returned = m_dir->name();
797 bool Console::is_connectedGui()
799 if (is_connected()) {
802 QString message("Director ");
803 message += " is currently disconnected\n Please reconnect!!";
804 QMessageBox::warning(this, "Bat",
805 tr(message.toUtf8().data()), QMessageBox::Ok );
811 * A temporary function to prevent connecting to the director if the director
812 * is busy with a restore.
814 bool Console::preventInUseConnect()
816 if (!is_connected()) {
817 QString message("Director ");
818 message += m_dir->name();
819 message += " is currently disconnected\n Please reconnect!!";
820 QMessageBox::warning(this, "Bat",
821 tr(message.toUtf8().data()), QMessageBox::Ok );
823 } else if (!m_at_main_prompt){
824 QString message("Director ");
825 message += m_dir->name();
826 message += " is currently busy\n Please complete restore or other "
827 " operation !! This is a limitation that will be resolved before a beta"
828 " release. This is currently an alpha release.";
829 QMessageBox::warning(this, "Bat",
830 tr(message.toUtf8().data()), QMessageBox::Ok );
832 } else if (!m_at_prompt){
833 QString message("Director ");
834 message += m_dir->name();
835 message += " is currently not at a prompt\n Please try again!!";
836 QMessageBox::warning(this, "Bat",
837 tr(message.toUtf8().data()), QMessageBox::Ok );
845 * Call-back for reading a passphrase for an encrypted PEM file
846 * This function uses getpass(),
847 * which uses a static buffer and is NOT thread-safe.
849 static int tls_pem_callback(char *buf, int size, const void *userdata)
854 const char *prompt = (const char *)userdata;
855 # if defined(HAVE_WIN32)
857 if (win32_cgets(buf, size) == NULL) {
866 passwd = getpass(prompt);
867 bstrncpy(buf, passwd, size);
876 /* Slot for responding to page selectors status help command */
877 void Console::consoleHelp()
883 /* Slot for responding to page selectors reload bacula-dir.conf */
884 void Console::consoleReload()
886 QString cmd("reload");
890 /* Function to get a list of volumes */
891 void Console::getVolumeList(QStringList &volumeList)
893 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
894 if (mainWin->m_sqlDebug) {
895 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
898 if (sql_cmd(query, results)) {
900 QStringList fieldlist;
901 /* Iterate through the lines of results. */
902 foreach (QString resultline, results) {
903 fieldlist = resultline.split("\t");
904 volumeList.append(fieldlist[0]);
905 } /* foreach resultline */
906 } /* if results from query */
909 /* Function to get a list of volumes */
910 void Console::getStatusList(QStringList &statusLongList)
912 QString statusQuery("SELECT JobStatusLong FROM Status");
913 if (mainWin->m_sqlDebug) {
914 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
916 QStringList statusResults;
917 if (sql_cmd(statusQuery, statusResults)) {
919 QStringList fieldlist;
920 /* Iterate through the lines of results. */
921 foreach (QString resultline, statusResults) {
922 fieldlist = resultline.split("\t");
923 statusLongList.append(fieldlist[0]);
924 } /* foreach resultline */
925 } /* if results from statusquery */