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)
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);
618 int Console::sock_read()
622 bool isEnabled = m_notifier->isEnabled();
624 m_notifier->setEnabled(false);
626 stat = m_sock->recv();
628 m_notifier->setEnabled(true);
631 stat = m_sock->recv();
637 * Blocking read from director
644 stat = m_sock->wait_data_intr(0, 50000);
648 app->processEvents();
649 if (m_api_set && m_messages_pending && m_notifier->isEnabled()) {
650 write_dir(".messages");
651 m_messages_pending = false;
657 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
661 m_at_main_prompt = false;
664 switch (m_sock->msglen) {
665 case BNET_MSGS_PENDING :
666 if (m_notifier->isEnabled()) {
667 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
668 write_dir(".messages");
670 m_messages_pending = false;
672 m_messages_pending = true;
675 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
677 m_at_main_prompt = false;
678 mainWin->set_status(_("Command completed ..."));
681 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
683 m_at_main_prompt = false;
684 mainWin->set_status(_("Processing command ..."));
686 case BNET_MAIN_PROMPT:
687 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
689 m_at_main_prompt = true;
690 mainWin->set_status(_("At main prompt waiting for input ..."));
691 QApplication::restoreOverrideCursor();
694 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
696 m_at_main_prompt = false;
697 mainWin->set_status(_("At prompt waiting for input ..."));
698 QApplication::restoreOverrideCursor();
700 case BNET_CMD_FAILED:
701 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
702 mainWin->set_status(_("Command failed."));
703 QApplication::restoreOverrideCursor();
705 /* We should not get this one */
707 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
708 mainWin->set_status_ready();
709 QApplication::restoreOverrideCursor();
714 case BNET_START_SELECT:
715 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
716 new selectDialog(this);
719 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
720 new yesnoPopUp(this);
723 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
726 case BNET_START_RTREE:
727 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
731 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
734 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
735 stat = sock_read(); /* get the message */
737 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
739 case BNET_WARNING_MSG:
740 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
741 stat = sock_read(); /* get the message */
743 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
746 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
747 stat = sock_read(); /* get the message */
749 mainWin->set_status(msg());
752 if (is_bnet_stop(m_sock)) { /* error or term request */
753 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
757 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
758 QBrush redBrush(Qt::red);
759 QTreeWidgetItem *item = mainWin->getFromHash(this);
760 item->setForeground(0, redBrush);
761 m_notifier->setEnabled(false);
764 mainWin->set_status(_("Director disconnected."));
765 QApplication::restoreOverrideCursor();
773 /* Called by signal when the Director has output for us */
774 void Console::read_dir(int /* fd */)
776 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
777 while (read() >= 0) {
783 * When the notifier is enabled, read_dir() will automatically be
784 * called by the Qt event loop when ever there is any output
785 * from the Directory, and read_dir() will then display it on
788 * When we are in a bat dialog, we want to control *all* output
789 * from the Directory, so we set notify to off.
790 * m_console->notifiy(false);
792 void Console::notify(bool enable)
794 m_notifier->setEnabled(enable);
797 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
799 m_directorTreeItem = item;
802 void Console::setDirRes(DIRRES *dir)
808 * To have the ability to get the name of the director resource.
810 void Console::getDirResName(QString &name_returned)
812 name_returned = m_dir->name();
815 bool Console::is_connectedGui()
817 if (is_connected()) {
820 QString message = tr("Director is currently disconnected\nPlease reconnect!");
821 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
827 * A temporary function to prevent connecting to the director if the director
828 * is busy with a restore.
830 bool Console::preventInUseConnect()
832 if (!is_connected()) {
833 QString message = tr("Director %1 is currently disconnected\n"
834 "Please reconnect!").arg(m_dir->name());
835 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
837 } else if (!m_at_main_prompt){
838 QString message = tr("Director %1 is currently busy\n Please complete "
839 "restore or other operation! This is a limitation "
840 "that will be resolved before a beta release. "
841 "This is currently an alpha release.").arg(m_dir->name());
842 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
844 } else if (!m_at_prompt){
845 QString message = tr("Director %1 is currently not at a prompt\n"
846 " Please try again!").arg(m_dir->name());
847 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
855 * Call-back for reading a passphrase for an encrypted PEM file
856 * This function uses getpass(),
857 * which uses a static buffer and is NOT thread-safe.
859 static int tls_pem_callback(char *buf, int size, const void *userdata)
864 const char *prompt = (const char *)userdata;
865 # if defined(HAVE_WIN32)
867 if (win32_cgets(buf, size) == NULL) {
876 passwd = getpass(prompt);
877 bstrncpy(buf, passwd, size);
886 /* Slot for responding to page selectors status help command */
887 void Console::consoleHelp()
893 /* Slot for responding to page selectors reload bacula-dir.conf */
894 void Console::consoleReload()
896 QString cmd("reload");
900 /* Function to get a list of volumes */
901 void Console::getVolumeList(QStringList &volumeList)
903 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
904 if (mainWin->m_sqlDebug) {
905 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
908 if (sql_cmd(query, results)) {
910 QStringList fieldlist;
911 /* Iterate through the lines of results. */
912 foreach (QString resultline, results) {
913 fieldlist = resultline.split("\t");
914 volumeList.append(fieldlist[0]);
915 } /* foreach resultline */
916 } /* if results from query */
919 /* Function to get a list of volumes */
920 void Console::getStatusList(QStringList &statusLongList)
922 QString statusQuery("SELECT JobStatusLong FROM Status");
923 if (mainWin->m_sqlDebug) {
924 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
926 QStringList statusResults;
927 if (sql_cmd(statusQuery, statusResults)) {
929 QStringList fieldlist;
930 /* Iterate through the lines of results. */
931 foreach (QString resultline, statusResults) {
932 fieldlist = resultline.split("\t");
933 statusLongList.append(fieldlist[0]);
934 } /* foreach resultline */
935 } /* if results from statusquery */