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 plus additions
11 that are listed in the file LICENSE.
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
37 //#include <QAbstractEventDispatcher>
44 static int tls_pem_callback(char *buf, int size, const void *userdata);
47 Console::Console(QStackedWidget *parent)
57 m_at_main_prompt = false;
58 m_textEdit = textEdit; /* our console screen */
59 m_cursor = new QTextCursor(m_textEdit->document());
60 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
63 m_contextActions.append(actionStatusDir);
64 m_contextActions.append(actionConsoleHelp);
65 m_contextActions.append(actionRequestMessages);
66 m_contextActions.append(actionConsoleReload);
67 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
68 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
69 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
70 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
77 void Console::startTimer()
79 m_timer = new QTimer(this);
80 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
81 m_timer->start(mainWin->m_checkMessagesInterval*1000);
84 void Console::stopTimer()
87 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
94 void Console::poll_messages()
96 m_messages_pending = true;
97 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
103 /* Terminate any open socket */
104 void Console::terminate()
114 * Connect to Director.
116 void Console::connect_dir()
122 m_textEdit = textEdit; /* our console screen */
125 mainWin->set_status("No Director found.");
129 mainWin->set_status("Already connected.");
133 memset(&jcr, 0, sizeof(jcr));
135 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
136 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
138 /* Give GUI a chance */
139 app->processEvents();
142 /* If cons==NULL, default console will be used */
143 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
146 /* Initialize Console TLS context once */
147 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
148 /* Generate passphrase prompt */
149 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
152 /* Initialize TLS context:
153 * Args: CA certfile, CA certdir, Certfile, Keyfile,
154 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
156 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
157 cons->tls_ca_certdir, cons->tls_certfile,
158 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
160 if (!cons->tls_ctx) {
161 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
167 /* Initialize Director TLS context once */
168 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
169 /* Generate passphrase prompt */
170 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
173 /* Initialize TLS context:
174 * Args: CA certfile, CA certdir, Certfile, Keyfile,
175 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
176 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
177 m_dir->tls_ca_certdir, m_dir->tls_certfile,
178 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
180 if (!m_dir->tls_ctx) {
181 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
183 mainWin->set_status("Connection failed");
188 if (m_dir->heartbeat_interval) {
189 heart_beat = m_dir->heartbeat_interval;
191 heart_beat = cons->heartbeat_interval;
196 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
197 _("Director daemon"), m_dir->address,
198 NULL, m_dir->DIRport, 0);
199 if (m_sock == NULL) {
200 mainWin->set_status("Connection failed");
203 /* Update page selector to green to indicate that Console is connected */
204 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
205 QBrush greenBrush(Qt::green);
206 QTreeWidgetItem *item = mainWin->getFromHash(this);
207 item->setForeground(0, greenBrush);
210 jcr.dir_bsock = m_sock;
212 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
220 /* Give GUI a chance */
221 app->processEvents();
223 mainWin->set_status(_("Initializing ..."));
225 /* Set up input notifier */
226 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
227 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
233 dir_cmd(".jobs", job_list);
234 dir_cmd(".clients", client_list);
235 dir_cmd(".filesets", fileset_list);
236 dir_cmd(".msgs", messages_list);
237 dir_cmd(".pools", pool_list);
238 dir_cmd(".storage", storage_list);
239 dir_cmd(".types", type_list);
240 dir_cmd(".levels", level_list);
242 mainWin->set_status(_("Connected"));
243 startTimer(); /* start message timer */
247 bool Console::dir_cmd(QString &cmd, QStringList &results)
249 return dir_cmd(cmd.toUtf8().data(), results);
253 * Send a command to the Director, and return the
254 * results in a QStringList.
256 bool Console::dir_cmd(const char *cmd, QStringList &results)
262 while ((stat = read()) > 0) {
263 if (mainWin->m_displayAll) display_text(msg());
264 strip_trailing_junk(msg());
269 return true; /* ***FIXME*** return any command error */
272 bool Console::sql_cmd(QString &query, QStringList &results)
274 return sql_cmd(query.toUtf8().data(), results);
278 * Send an sql query to the Director, and return the
279 * results in a QStringList.
281 bool Console::sql_cmd(const char *query, QStringList &results)
284 POOL_MEM cmd(PM_MESSAGE);
286 if (!is_connectedGui()) {
292 pm_strcpy(cmd, ".sql query=\"");
293 pm_strcat(cmd, query);
294 pm_strcat(cmd, "\"");
296 while ((stat = read()) > 0) {
297 if (mainWin->m_displayAll) display_text(msg());
298 strip_trailing_junk(msg());
303 return true; /* ***FIXME*** return any command error */
308 * Send a job name to the director, and read all the resulting
311 bool Console::get_job_defaults(struct job_defaults &job_defs)
319 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
321 while ((stat = read()) > 0) {
322 if (mainWin->m_displayAll) display_text(msg());
323 def = strchr(msg(), '=');
327 /* Pointer to default value */
329 strip_trailing_junk(def);
331 if (strcmp(msg(), "job") == 0) {
332 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
337 if (strcmp(msg(), "pool") == 0) {
338 job_defs.pool_name = def;
341 if (strcmp(msg(), "messages") == 0) {
342 job_defs.messages_name = def;
345 if (strcmp(msg(), "client") == 0) {
346 job_defs.client_name = def;
349 if (strcmp(msg(), "storage") == 0) {
350 job_defs.store_name = def;
353 if (strcmp(msg(), "where") == 0) {
354 job_defs.where = def;
357 if (strcmp(msg(), "level") == 0) {
358 job_defs.level = def;
361 if (strcmp(msg(), "type") == 0) {
365 if (strcmp(msg(), "fileset") == 0) {
366 job_defs.fileset_name = def;
369 if (strcmp(msg(), "catalog") == 0) {
370 job_defs.catalog_name = def;
373 if (strcmp(msg(), "enabled") == 0) {
374 job_defs.enabled = *def == '1' ? true : false;
389 * Save user settings associated with this console
391 void Console::writeSettings()
393 QFont font = get_font();
395 QSettings settings(m_dir->name(), "bat");
396 settings.beginGroup("Console");
397 settings.setValue("consoleFont", font.family());
398 settings.setValue("consolePointSize", font.pointSize());
399 settings.setValue("consoleFixedPitch", font.fixedPitch());
404 * Read and restore user settings associated with this console
406 void Console::readSettings()
408 QFont font = get_font();
410 QSettings settings(m_dir->name(), "bat");
411 settings.beginGroup("Console");
412 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
413 font.setPointSize(settings.value("consolePointSize", 10).toInt());
414 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
416 m_textEdit->setFont(font);
420 * Set the console textEdit font
422 void Console::set_font()
425 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
427 m_textEdit->setFont(font);
432 * Get the console text edit font
434 const QFont Console::get_font()
436 return m_textEdit->font();
440 * Slot for responding to status dir button on button bar
442 void Console::status_dir()
444 QString cmd("status dir");
449 * Slot for responding to messages button on button bar
451 void Console::messages()
453 QString cmd(".messages");
458 * Put text into the console window
460 void Console::display_textf(const char *fmt, ...)
465 va_start(arg_ptr, fmt);
466 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
471 void Console::display_text(const QString buf)
473 m_cursor->insertText(buf);
478 void Console::display_text(const char *buf)
480 m_cursor->insertText(buf);
484 void Console::display_html(const QString buf)
486 m_cursor->insertHtml(buf);
490 /* Position cursor to end of screen */
491 void Console::update_cursor()
493 QApplication::restoreOverrideCursor();
494 m_textEdit->moveCursor(QTextCursor::End);
495 m_textEdit->ensureCursorVisible();
499 * This should be moved into a bSocket class
509 /* Send a command to the Director */
510 void Console::write_dir(const char *msg)
513 mainWin->set_status(_("Processing command ..."));
514 QApplication::setOverrideCursor(Qt::WaitCursor);
517 mainWin->set_status(" Director not connected. Click on connect button.");
518 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
519 QBrush redBrush(Qt::red);
520 QTreeWidgetItem *item = mainWin->getFromHash(this);
521 item->setForeground(0, redBrush);
523 m_at_main_prompt = false;
527 int Console::write(const QString msg)
529 return write(msg.toUtf8().data());
532 int Console::write(const char *msg)
537 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
539 m_at_main_prompt = false;
540 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
541 return m_sock->send();
546 * Get to main command prompt -- i.e. abort any subcommand
548 void Console::beginNewCommand()
550 for (int i=0; i < 3; i++) {
553 if (mainWin->m_displayAll) display_text(msg());
555 if (m_at_main_prompt) {
562 void Console::displayToPrompt()
565 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
566 while (!m_at_prompt) {
567 if ((stat=read()) > 0) {
571 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
574 void Console::discardToPrompt()
577 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
578 while (!m_at_prompt) {
579 if ((stat=read()) > 0) {
580 if (mainWin->m_displayAll) display_text(msg());
583 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
588 * Blocking read from director
595 stat = bnet_wait_data_intr(m_sock, 1);
599 app->processEvents();
600 if (m_api_set && m_messages_pending) {
601 write_dir(".messages");
602 m_messages_pending = false;
605 stat = m_sock->recv();
610 m_at_main_prompt = false;
612 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
614 switch (m_sock->msglen) {
615 case BNET_MSGS_PENDING:
616 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
617 write_dir(".messages");
619 m_messages_pending = false;
622 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
624 m_at_main_prompt = false;
627 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
629 m_at_main_prompt = false;
631 case BNET_MAIN_PROMPT:
632 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
634 m_at_main_prompt = true;
635 mainWin->set_status(_("At main prompt waiting for input ..."));
636 QApplication::restoreOverrideCursor();
639 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
641 m_at_main_prompt = false;
642 mainWin->set_status(_("At prompt waiting for input ..."));
643 QApplication::restoreOverrideCursor();
645 case BNET_CMD_FAILED:
646 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
647 mainWin->set_status(_("Command failed."));
648 QApplication::restoreOverrideCursor();
650 /* We should not get this one */
652 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
653 mainWin->set_status_ready();
654 QApplication::restoreOverrideCursor();
659 case BNET_START_SELECT:
660 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
661 new selectDialog(this);
664 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
668 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
669 m_sock->recv(); /* get the message */
671 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
673 case BNET_WARNING_MSG:
674 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
675 m_sock->recv(); /* get the message */
677 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
680 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
681 m_sock->recv(); /* get the message */
683 mainWin->set_status(msg());
686 if (is_bnet_stop(m_sock)) { /* error or term request */
687 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
691 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
692 QBrush redBrush(Qt::red);
693 QTreeWidgetItem *item = mainWin->getFromHash(this);
694 item->setForeground(0, redBrush);
695 m_notifier->setEnabled(false);
698 mainWin->set_status(_("Director disconnected."));
699 QApplication::restoreOverrideCursor();
707 /* Called by signal when the Director has output for us */
708 void Console::read_dir(int /* fd */)
710 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
711 while (read() >= 0) {
717 * When the notifier is enabled, read_dir() will automatically be
718 * called by the Qt event loop when ever there is any output
719 * from the Directory, and read_dir() will then display it on
722 * When we are in a bat dialog, we want to control *all* output
723 * from the Directory, so we set notify to off.
724 * m_console->notifiy(false);
726 void Console::notify(bool enable)
728 m_notifier->setEnabled(enable);
731 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
733 m_directorTreeItem = item;
736 void Console::setDirRes(DIRRES *dir)
742 * To have the ability to get the name of the director resource.
744 void Console::getDirResName(QString &name_returned)
746 name_returned = m_dir->name();
749 bool Console::is_connectedGui()
751 if (is_connected()) {
754 QString message("Director ");
755 message += " is curerntly disconnected\n Please reconnect!!";
756 QMessageBox::warning(this, "Bat",
757 tr(message.toUtf8().data()), QMessageBox::Ok );
763 * A temporary function to prevent connecting to the director if the director
764 * is busy with a restore.
766 bool Console::preventInUseConnect()
768 if (!is_connected()) {
769 QString message("Director ");
770 message += m_dir->name();
771 message += " is curerntly disconnected\n Please reconnect!!";
772 QMessageBox::warning(this, "Bat",
773 tr(message.toUtf8().data()), QMessageBox::Ok );
775 } else if (!m_at_main_prompt){
776 QString message("Director ");
777 message += m_dir->name();
778 message += " is curerntly busy\n Please complete restore or other "
779 " operation !! This is a limitation that will be resolved before a beta"
780 " release. This is currently an alpha release.";
781 QMessageBox::warning(this, "Bat",
782 tr(message.toUtf8().data()), QMessageBox::Ok );
784 } else if (!m_at_prompt){
785 QString message("Director ");
786 message += m_dir->name();
787 message += " is curerntly not at a prompt\n Please try again!!";
788 QMessageBox::warning(this, "Bat",
789 tr(message.toUtf8().data()), QMessageBox::Ok );
797 * Call-back for reading a passphrase for an encrypted PEM file
798 * This function uses getpass(),
799 * which uses a static buffer and is NOT thread-safe.
801 static int tls_pem_callback(char *buf, int size, const void *userdata)
806 const char *prompt = (const char *)userdata;
807 # if defined(HAVE_WIN32)
809 if (win32_cgets(buf, size) == NULL) {
818 passwd = getpass(prompt);
819 bstrncpy(buf, passwd, size);
828 /* Slot for responding to page selectors status help command */
829 void Console::consoleHelp()
835 /* Slot for responding to page selectors reload bacula-dir.conf */
836 void Console::consoleReload()
838 QString cmd("reload");