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
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)));
235 fileset_list.clear();
236 fileset_list.clear();
237 messages_list.clear();
239 storage_list.clear();
242 dir_cmd(".jobs", job_list);
243 dir_cmd(".clients", client_list);
244 dir_cmd(".filesets", fileset_list);
245 dir_cmd(".msgs", messages_list);
246 dir_cmd(".pools", pool_list);
247 dir_cmd(".storage", storage_list);
248 dir_cmd(".types", type_list);
249 dir_cmd(".levels", level_list);
251 mainWin->set_status(_("Connected"));
252 startTimer(); /* start message timer */
256 bool Console::dir_cmd(QString &cmd, QStringList &results)
258 return dir_cmd(cmd.toUtf8().data(), results);
262 * Send a command to the Director, and return the
263 * results in a QStringList.
265 bool Console::dir_cmd(const char *cmd, QStringList &results)
271 while ((stat = read()) > 0) {
272 if (mainWin->m_displayAll) display_text(msg());
273 strip_trailing_junk(msg());
278 return true; /* ***FIXME*** return any command error */
281 bool Console::sql_cmd(QString &query, QStringList &results)
283 return sql_cmd(query.toUtf8().data(), results);
287 * Send an sql query to the Director, and return the
288 * results in a QStringList.
290 bool Console::sql_cmd(const char *query, QStringList &results)
293 POOL_MEM cmd(PM_MESSAGE);
295 if (!is_connectedGui()) {
301 pm_strcpy(cmd, ".sql query=\"");
302 pm_strcat(cmd, query);
303 pm_strcat(cmd, "\"");
305 while ((stat = read()) > 0) {
306 if (mainWin->m_displayAll) display_text(msg());
307 strip_trailing_junk(msg());
312 return true; /* ***FIXME*** return any command error */
317 * Send a job name to the director, and read all the resulting
320 bool Console::get_job_defaults(struct job_defaults &job_defs)
328 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
330 while ((stat = read()) > 0) {
331 if (mainWin->m_displayAll) display_text(msg());
332 def = strchr(msg(), '=');
336 /* Pointer to default value */
338 strip_trailing_junk(def);
340 if (strcmp(msg(), "job") == 0) {
341 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
346 if (strcmp(msg(), "pool") == 0) {
347 job_defs.pool_name = def;
350 if (strcmp(msg(), "messages") == 0) {
351 job_defs.messages_name = def;
354 if (strcmp(msg(), "client") == 0) {
355 job_defs.client_name = def;
358 if (strcmp(msg(), "storage") == 0) {
359 job_defs.store_name = def;
362 if (strcmp(msg(), "where") == 0) {
363 job_defs.where = def;
366 if (strcmp(msg(), "level") == 0) {
367 job_defs.level = def;
370 if (strcmp(msg(), "type") == 0) {
374 if (strcmp(msg(), "fileset") == 0) {
375 job_defs.fileset_name = def;
378 if (strcmp(msg(), "catalog") == 0) {
379 job_defs.catalog_name = def;
382 if (strcmp(msg(), "enabled") == 0) {
383 job_defs.enabled = *def == '1' ? true : false;
398 * Save user settings associated with this console
400 void Console::writeSettings()
402 QFont font = get_font();
404 QSettings settings(m_dir->name(), "bat");
405 settings.beginGroup("Console");
406 settings.setValue("consoleFont", font.family());
407 settings.setValue("consolePointSize", font.pointSize());
408 settings.setValue("consoleFixedPitch", font.fixedPitch());
413 * Read and restore user settings associated with this console
415 void Console::readSettings()
417 QFont font = get_font();
419 QSettings settings(m_dir->name(), "bat");
420 settings.beginGroup("Console");
421 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
422 font.setPointSize(settings.value("consolePointSize", 10).toInt());
423 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
425 m_textEdit->setFont(font);
429 * Set the console textEdit font
431 void Console::set_font()
434 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
436 m_textEdit->setFont(font);
441 * Get the console text edit font
443 const QFont Console::get_font()
445 return m_textEdit->font();
449 * Slot for responding to status dir button on button bar
451 void Console::status_dir()
453 QString cmd("status dir");
458 * Slot for responding to messages button on button bar
460 void Console::messages()
462 QString cmd(".messages");
467 * Put text into the console window
469 void Console::display_textf(const char *fmt, ...)
474 va_start(arg_ptr, fmt);
475 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
480 void Console::display_text(const QString buf)
482 m_cursor->insertText(buf);
487 void Console::display_text(const char *buf)
489 m_cursor->insertText(buf);
493 void Console::display_html(const QString buf)
495 m_cursor->insertHtml(buf);
499 /* Position cursor to end of screen */
500 void Console::update_cursor()
502 QApplication::restoreOverrideCursor();
503 m_textEdit->moveCursor(QTextCursor::End);
504 m_textEdit->ensureCursorVisible();
508 * This should be moved into a bSocket class
518 /* Send a command to the Director */
519 void Console::write_dir(const char *msg)
522 mainWin->set_status(_("Processing command ..."));
523 QApplication::setOverrideCursor(Qt::WaitCursor);
526 mainWin->set_status(" Director not connected. Click on connect button.");
527 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
528 QBrush redBrush(Qt::red);
529 QTreeWidgetItem *item = mainWin->getFromHash(this);
530 item->setForeground(0, redBrush);
532 m_at_main_prompt = false;
536 int Console::write(const QString msg)
538 return write(msg.toUtf8().data());
541 int Console::write(const char *msg)
546 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
548 m_at_main_prompt = false;
549 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
550 return m_sock->send();
555 * Get to main command prompt -- i.e. abort any subcommand
557 void Console::beginNewCommand()
559 for (int i=0; i < 3; i++) {
562 if (mainWin->m_displayAll) display_text(msg());
564 if (m_at_main_prompt) {
571 void Console::displayToPrompt()
574 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
575 while (!m_at_prompt) {
576 if ((stat=read()) > 0) {
580 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
583 void Console::discardToPrompt()
586 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
587 while (!m_at_prompt) {
588 if ((stat=read()) > 0) {
589 if (mainWin->m_displayAll) display_text(msg());
592 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
597 * Blocking read from director
604 stat = bnet_wait_data_intr(m_sock, 1);
608 app->processEvents();
609 if (m_api_set && m_messages_pending) {
610 write_dir(".messages");
611 m_messages_pending = false;
614 stat = m_sock->recv();
619 m_at_main_prompt = false;
621 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
623 switch (m_sock->msglen) {
624 case BNET_MSGS_PENDING:
625 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
626 write_dir(".messages");
628 m_messages_pending = false;
631 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
633 m_at_main_prompt = false;
634 mainWin->set_status(_("Command completed ..."));
637 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
639 m_at_main_prompt = false;
640 mainWin->set_status(_("Processing command ..."));
642 case BNET_MAIN_PROMPT:
643 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
645 m_at_main_prompt = true;
646 mainWin->set_status(_("At main prompt waiting for input ..."));
647 QApplication::restoreOverrideCursor();
650 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
652 m_at_main_prompt = false;
653 mainWin->set_status(_("At prompt waiting for input ..."));
654 QApplication::restoreOverrideCursor();
656 case BNET_CMD_FAILED:
657 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
658 mainWin->set_status(_("Command failed."));
659 QApplication::restoreOverrideCursor();
661 /* We should not get this one */
663 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
664 mainWin->set_status_ready();
665 QApplication::restoreOverrideCursor();
670 case BNET_START_SELECT:
671 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
672 new selectDialog(this);
675 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
679 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
680 m_sock->recv(); /* get the message */
682 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
684 case BNET_WARNING_MSG:
685 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
686 m_sock->recv(); /* get the message */
688 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
691 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
692 m_sock->recv(); /* get the message */
694 mainWin->set_status(msg());
697 if (is_bnet_stop(m_sock)) { /* error or term request */
698 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
702 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
703 QBrush redBrush(Qt::red);
704 QTreeWidgetItem *item = mainWin->getFromHash(this);
705 item->setForeground(0, redBrush);
706 m_notifier->setEnabled(false);
709 mainWin->set_status(_("Director disconnected."));
710 QApplication::restoreOverrideCursor();
718 /* Called by signal when the Director has output for us */
719 void Console::read_dir(int /* fd */)
721 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
722 while (read() >= 0) {
728 * When the notifier is enabled, read_dir() will automatically be
729 * called by the Qt event loop when ever there is any output
730 * from the Directory, and read_dir() will then display it on
733 * When we are in a bat dialog, we want to control *all* output
734 * from the Directory, so we set notify to off.
735 * m_console->notifiy(false);
737 void Console::notify(bool enable)
739 m_notifier->setEnabled(enable);
742 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
744 m_directorTreeItem = item;
747 void Console::setDirRes(DIRRES *dir)
753 * To have the ability to get the name of the director resource.
755 void Console::getDirResName(QString &name_returned)
757 name_returned = m_dir->name();
760 bool Console::is_connectedGui()
762 if (is_connected()) {
765 QString message("Director ");
766 message += " is currently disconnected\n Please reconnect!!";
767 QMessageBox::warning(this, "Bat",
768 tr(message.toUtf8().data()), QMessageBox::Ok );
774 * A temporary function to prevent connecting to the director if the director
775 * is busy with a restore.
777 bool Console::preventInUseConnect()
779 if (!is_connected()) {
780 QString message("Director ");
781 message += m_dir->name();
782 message += " is currently disconnected\n Please reconnect!!";
783 QMessageBox::warning(this, "Bat",
784 tr(message.toUtf8().data()), QMessageBox::Ok );
786 } else if (!m_at_main_prompt){
787 QString message("Director ");
788 message += m_dir->name();
789 message += " is currently busy\n Please complete restore or other "
790 " operation !! This is a limitation that will be resolved before a beta"
791 " release. This is currently an alpha release.";
792 QMessageBox::warning(this, "Bat",
793 tr(message.toUtf8().data()), QMessageBox::Ok );
795 } else if (!m_at_prompt){
796 QString message("Director ");
797 message += m_dir->name();
798 message += " is currently not at a prompt\n Please try again!!";
799 QMessageBox::warning(this, "Bat",
800 tr(message.toUtf8().data()), QMessageBox::Ok );
808 * Call-back for reading a passphrase for an encrypted PEM file
809 * This function uses getpass(),
810 * which uses a static buffer and is NOT thread-safe.
812 static int tls_pem_callback(char *buf, int size, const void *userdata)
817 const char *prompt = (const char *)userdata;
818 # if defined(HAVE_WIN32)
820 if (win32_cgets(buf, size) == NULL) {
829 passwd = getpass(prompt);
830 bstrncpy(buf, passwd, size);
839 /* Slot for responding to page selectors status help command */
840 void Console::consoleHelp()
846 /* Slot for responding to page selectors reload bacula-dir.conf */
847 void Console::consoleReload()
849 QString cmd("reload");