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;
615 stat = m_sock->recv();
617 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
621 m_at_main_prompt = false;
624 switch (m_sock->msglen) {
625 case BNET_MSGS_PENDING:
626 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
627 write_dir(".messages");
629 m_messages_pending = false;
632 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
634 m_at_main_prompt = false;
635 mainWin->set_status(_("Command completed ..."));
638 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
640 m_at_main_prompt = false;
641 mainWin->set_status(_("Processing command ..."));
643 case BNET_MAIN_PROMPT:
644 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
646 m_at_main_prompt = true;
647 mainWin->set_status(_("At main prompt waiting for input ..."));
648 QApplication::restoreOverrideCursor();
651 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
653 m_at_main_prompt = false;
654 mainWin->set_status(_("At prompt waiting for input ..."));
655 QApplication::restoreOverrideCursor();
657 case BNET_CMD_FAILED:
658 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
659 mainWin->set_status(_("Command failed."));
660 QApplication::restoreOverrideCursor();
662 /* We should not get this one */
664 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
665 mainWin->set_status_ready();
666 QApplication::restoreOverrideCursor();
671 case BNET_START_SELECT:
672 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
673 new selectDialog(this);
676 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
680 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
681 m_sock->recv(); /* get the message */
683 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
685 case BNET_WARNING_MSG:
686 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
687 m_sock->recv(); /* get the message */
689 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
692 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
693 m_sock->recv(); /* get the message */
695 mainWin->set_status(msg());
698 if (is_bnet_stop(m_sock)) { /* error or term request */
699 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
703 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
704 QBrush redBrush(Qt::red);
705 QTreeWidgetItem *item = mainWin->getFromHash(this);
706 item->setForeground(0, redBrush);
707 m_notifier->setEnabled(false);
710 mainWin->set_status(_("Director disconnected."));
711 QApplication::restoreOverrideCursor();
719 /* Called by signal when the Director has output for us */
720 void Console::read_dir(int /* fd */)
722 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
723 while (read() >= 0) {
729 * When the notifier is enabled, read_dir() will automatically be
730 * called by the Qt event loop when ever there is any output
731 * from the Directory, and read_dir() will then display it on
734 * When we are in a bat dialog, we want to control *all* output
735 * from the Directory, so we set notify to off.
736 * m_console->notifiy(false);
738 void Console::notify(bool enable)
740 m_notifier->setEnabled(enable);
743 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
745 m_directorTreeItem = item;
748 void Console::setDirRes(DIRRES *dir)
754 * To have the ability to get the name of the director resource.
756 void Console::getDirResName(QString &name_returned)
758 name_returned = m_dir->name();
761 bool Console::is_connectedGui()
763 if (is_connected()) {
766 QString message("Director ");
767 message += " is currently disconnected\n Please reconnect!!";
768 QMessageBox::warning(this, "Bat",
769 tr(message.toUtf8().data()), QMessageBox::Ok );
775 * A temporary function to prevent connecting to the director if the director
776 * is busy with a restore.
778 bool Console::preventInUseConnect()
780 if (!is_connected()) {
781 QString message("Director ");
782 message += m_dir->name();
783 message += " is currently disconnected\n Please reconnect!!";
784 QMessageBox::warning(this, "Bat",
785 tr(message.toUtf8().data()), QMessageBox::Ok );
787 } else if (!m_at_main_prompt){
788 QString message("Director ");
789 message += m_dir->name();
790 message += " is currently busy\n Please complete restore or other "
791 " operation !! This is a limitation that will be resolved before a beta"
792 " release. This is currently an alpha release.";
793 QMessageBox::warning(this, "Bat",
794 tr(message.toUtf8().data()), QMessageBox::Ok );
796 } else if (!m_at_prompt){
797 QString message("Director ");
798 message += m_dir->name();
799 message += " is currently not at a prompt\n Please try again!!";
800 QMessageBox::warning(this, "Bat",
801 tr(message.toUtf8().data()), QMessageBox::Ok );
809 * Call-back for reading a passphrase for an encrypted PEM file
810 * This function uses getpass(),
811 * which uses a static buffer and is NOT thread-safe.
813 static int tls_pem_callback(char *buf, int size, const void *userdata)
818 const char *prompt = (const char *)userdata;
819 # if defined(HAVE_WIN32)
821 if (win32_cgets(buf, size) == NULL) {
830 passwd = getpass(prompt);
831 bstrncpy(buf, passwd, size);
840 /* Slot for responding to page selectors status help command */
841 void Console::consoleHelp()
847 /* Slot for responding to page selectors reload bacula-dir.conf */
848 void Console::consoleReload()
850 QString cmd("reload");