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)
58 m_at_main_prompt = false;
59 m_textEdit = textEdit; /* our console screen */
60 m_cursor = new QTextCursor(m_textEdit->document());
61 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
63 /* Check for messages every 5 seconds */
64 m_timer = new QTimer(this);
65 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
73 void Console::startTimer()
75 m_timer->start(mainWin->m_checkMessagesInterval*1000);
78 void Console::poll_messages()
80 m_messages_pending = true;
81 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
87 /* Terminate any open socket */
88 void Console::terminate()
98 * Connect to Director. If there are more than one, put up
99 * a modal dialog so that the user chooses one.
101 void Console::connect()
107 m_textEdit = textEdit; /* our console screen */
110 mainWin->set_status("No Director found.");
114 mainWin->set_status("Already connected.");
118 memset(&jcr, 0, sizeof(jcr));
120 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
121 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
123 /* Give GUI a chance */
124 app->processEvents();
127 /* If cons==NULL, default console will be used */
128 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
131 /* Initialize Console TLS context */
132 if (cons && (cons->tls_enable || cons->tls_require)) {
133 /* Generate passphrase prompt */
134 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
137 /* Initialize TLS context:
138 * Args: CA certfile, CA certdir, Certfile, Keyfile,
139 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
141 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
142 cons->tls_ca_certdir, cons->tls_certfile,
143 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
145 if (!cons->tls_ctx) {
146 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
152 /* Initialize Director TLS context */
153 if (m_dir->tls_enable || m_dir->tls_require) {
154 /* Generate passphrase prompt */
155 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
158 /* Initialize TLS context:
159 * Args: CA certfile, CA certdir, Certfile, Keyfile,
160 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
161 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
162 m_dir->tls_ca_certdir, m_dir->tls_certfile,
163 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
165 if (!m_dir->tls_ctx) {
166 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
168 mainWin->set_status("Connection failed");
173 if (m_dir->heartbeat_interval) {
174 heart_beat = m_dir->heartbeat_interval;
176 heart_beat = cons->heartbeat_interval;
181 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
182 _("Director daemon"), m_dir->address,
183 NULL, m_dir->DIRport, 0);
184 if (m_sock == NULL) {
185 mainWin->set_status("Connection failed");
188 /* Update page selector to green to indicate that Console is connected */
189 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
190 QBrush greenBrush(Qt::green);
191 QTreeWidgetItem *item = mainWin->getFromHash(this);
192 item->setForeground(0, greenBrush);
195 jcr.dir_bsock = m_sock;
197 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
205 /* Give GUI a chance */
206 app->processEvents();
208 mainWin->set_status(_("Initializing ..."));
210 /* Set up input notifier */
211 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
212 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
218 dir_cmd(".jobs", job_list);
219 dir_cmd(".clients", client_list);
220 dir_cmd(".filesets", fileset_list);
221 dir_cmd(".msgs", messages_list);
222 dir_cmd(".pools", pool_list);
223 dir_cmd(".storage", storage_list);
224 dir_cmd(".types", type_list);
225 dir_cmd(".levels", level_list);
227 mainWin->set_status(_("Connected"));
231 bool Console::dir_cmd(QString &cmd, QStringList &results)
233 return dir_cmd(cmd.toUtf8().data(), results);
237 * Send a command to the Director, and return the
238 * results in a QStringList.
240 bool Console::dir_cmd(const char *cmd, QStringList &results)
246 while ((stat = read()) > 0) {
247 if (mainWin->m_displayAll) display_text(msg());
248 strip_trailing_junk(msg());
253 return true; /* ***FIXME*** return any command error */
256 bool Console::sql_cmd(QString &query, QStringList &results)
258 return sql_cmd(query.toUtf8().data(), results);
262 * Send an sql query to the Director, and return the
263 * results in a QStringList.
265 bool Console::sql_cmd(const char *query, QStringList &results)
267 if (!is_connectedGui())
270 POOL_MEM cmd(PM_MESSAGE);
274 pm_strcpy(cmd, ".sql query=\"");
275 pm_strcat(cmd, query);
276 pm_strcat(cmd, "\"");
278 while ((stat = read()) > 0) {
279 if (mainWin->m_displayAll) display_text(msg());
280 strip_trailing_junk(msg());
285 return true; /* ***FIXME*** return any command error */
290 * Send a job name to the director, and read all the resulting
293 bool Console::get_job_defaults(struct job_defaults &job_defs)
301 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
303 while ((stat = read()) > 0) {
304 if (mainWin->m_displayAll) display_text(msg());
305 def = strchr(msg(), '=');
309 /* Pointer to default value */
311 strip_trailing_junk(def);
313 if (strcmp(msg(), "job") == 0) {
314 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
319 if (strcmp(msg(), "pool") == 0) {
320 job_defs.pool_name = def;
323 if (strcmp(msg(), "messages") == 0) {
324 job_defs.messages_name = def;
327 if (strcmp(msg(), "client") == 0) {
328 job_defs.client_name = def;
331 if (strcmp(msg(), "storage") == 0) {
332 job_defs.store_name = def;
335 if (strcmp(msg(), "where") == 0) {
336 job_defs.where = def;
339 if (strcmp(msg(), "level") == 0) {
340 job_defs.level = def;
343 if (strcmp(msg(), "type") == 0) {
347 if (strcmp(msg(), "fileset") == 0) {
348 job_defs.fileset_name = def;
351 if (strcmp(msg(), "catalog") == 0) {
352 job_defs.catalog_name = def;
355 if (strcmp(msg(), "enabled") == 0) {
356 job_defs.enabled = *def == '1' ? true : false;
362 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
363 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
364 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
365 job_defs.client_name.toUtf8().data(),
366 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
367 job_defs.store_name.toUtf8().data(),
368 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
369 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
370 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
383 * Save user settings associated with this console
385 void Console::writeSettings()
387 QFont font = get_font();
389 QSettings settings(m_dir->name(), "bat");
390 settings.beginGroup("Console");
391 settings.setValue("consoleFont", font.family());
392 settings.setValue("consolePointSize", font.pointSize());
393 settings.setValue("consoleFixedPitch", font.fixedPitch());
398 * Read and restore user settings associated with this console
400 void Console::readSettings()
402 QFont font = get_font();
404 QSettings settings(m_dir->name(), "bat");
405 settings.beginGroup("Console");
406 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
407 font.setPointSize(settings.value("consolePointSize", 10).toInt());
408 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
410 m_textEdit->setFont(font);
414 * Set the console textEdit font
416 void Console::set_font()
419 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
421 m_textEdit->setFont(font);
426 * Get the console text edit font
428 const QFont Console::get_font()
430 return m_textEdit->font();
434 * Slot for responding to status dir button on button bar
436 void Console::status_dir()
438 QString cmd("status dir");
443 * Slot for responding to messages button on button bar
445 void Console::messages()
447 QString cmd(".messages");
452 * Put text into the console window
454 void Console::display_textf(const char *fmt, ...)
459 va_start(arg_ptr, fmt);
460 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
465 void Console::display_text(const QString buf)
467 m_cursor->insertText(buf);
472 void Console::display_text(const char *buf)
474 m_cursor->insertText(buf);
478 void Console::display_html(const QString buf)
480 m_cursor->insertHtml(buf);
484 /* Position cursor to end of screen */
485 void Console::update_cursor()
487 QApplication::restoreOverrideCursor();
488 m_textEdit->moveCursor(QTextCursor::End);
489 m_textEdit->ensureCursorVisible();
493 * This should be moved into a bSocket class
503 /* Send a command to the Director */
504 void Console::write_dir(const char *msg)
507 mainWin->set_status(_("Processing command ..."));
508 QApplication::setOverrideCursor(Qt::WaitCursor);
511 mainWin->set_status(" Director not connected. Click on connect button.");
512 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
513 QBrush redBrush(Qt::red);
514 QTreeWidgetItem *item = mainWin->getFromHash(this);
515 item->setForeground(0, redBrush);
517 m_at_main_prompt = false;
521 int Console::write(const QString msg)
523 return write(msg.toUtf8().data());
526 int Console::write(const char *msg)
528 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
530 m_at_main_prompt = false;
531 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
532 return m_sock->send();
536 * Get to main command prompt -- i.e. abort any subcommand
538 void Console::beginNewCommand()
540 for (int i=0; i < 3; i++) {
543 if (mainWin->m_displayAll) display_text(msg());
545 if (m_at_main_prompt) {
552 void Console::displayToPrompt()
555 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
556 while (!m_at_prompt) {
557 if ((stat=read()) > 0) {
561 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
564 void Console::discardToPrompt()
567 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
568 while (!m_at_prompt) {
569 if ((stat=read()) > 0) {
570 if (mainWin->m_displayAll) display_text(msg());
573 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
578 * Blocking read from director
585 stat = bnet_wait_data_intr(m_sock, 1);
589 app->processEvents();
590 if (m_api_set && m_messages_pending) {
591 write_dir(".messages");
592 m_messages_pending = false;
595 stat = m_sock->recv();
600 m_at_main_prompt = false;
602 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
604 switch (m_sock->msglen) {
605 case BNET_MSGS_PENDING:
606 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
607 write_dir(".messages");
609 m_messages_pending = false;
612 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
614 m_at_main_prompt = false;
617 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
619 m_at_main_prompt = false;
621 case BNET_MAIN_PROMPT:
622 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
624 m_at_main_prompt = true;
625 mainWin->set_status(_("At prompt waiting for input ..."));
626 QApplication::restoreOverrideCursor();
629 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
631 m_at_main_prompt = false;
632 mainWin->set_status(_("At prompt waiting for input ..."));
633 QApplication::restoreOverrideCursor();
635 case BNET_CMD_FAILED:
636 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
637 mainWin->set_status(_("Command failed. At prompt waiting for input ..."));
638 QApplication::restoreOverrideCursor();
640 /* We should not get this one */
642 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
643 mainWin->set_status_ready();
644 QApplication::restoreOverrideCursor();
649 case BNET_START_SELECT:
650 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
651 new selectDialog(this);
654 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
658 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
659 m_sock->recv(); /* get the message */
661 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
663 case BNET_WARNING_MSG:
664 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
665 m_sock->recv(); /* get the message */
667 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
670 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
671 m_sock->recv(); /* get the message */
673 mainWin->set_status(msg());
676 if (is_bnet_stop(m_sock)) { /* error or term request */
677 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
680 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
681 QBrush redBrush(Qt::red);
682 QTreeWidgetItem *item = mainWin->getFromHash(this);
683 item->setForeground(0, redBrush);
684 m_notifier->setEnabled(false);
687 mainWin->set_status(_("Director disconnected."));
688 QApplication::restoreOverrideCursor();
696 /* Called by signal when the Director has output for us */
697 void Console::read_dir(int /* fd */)
699 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
700 while (read() >= 0) {
706 * When the notifier is enabled, read_dir() will automatically be
707 * called by the Qt event loop when ever there is any output
708 * from the Directory, and read_dir() will then display it on
711 * When we are in a bat dialog, we want to control *all* output
712 * from the Directory, so we set notify to off.
713 * m_console->notifiy(false);
715 void Console::notify(bool enable)
717 m_notifier->setEnabled(enable);
720 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
722 m_directorTreeItem = item;
725 void Console::setDirRes(DIRRES *dir)
731 * To have the ability to get the name of the director resource.
733 void Console::getDirResName(QString &name_returned)
735 name_returned = m_dir->name();
738 bool Console::is_connectedGui()
740 if (is_connected()) {
743 QString message("Director ");
744 message += " is curerntly disconnected\n Please reconnect!!";
745 QMessageBox::warning(this, tr("Bat"),
746 tr(message.toUtf8().data()), QMessageBox::Ok );
752 * A temporary function to prevent connecting to the director if the director
753 * is busy with a restore.
755 bool Console::preventInUseConnect()
757 if (!is_connected()) {
758 QString message("Director ");
759 message += m_dir->name();
760 message += " is curerntly disconnected\n Please reconnect!!";
761 QMessageBox::warning(this, tr("Bat"),
762 tr(message.toUtf8().data()), QMessageBox::Ok );
764 } else if (!m_at_main_prompt){
765 QString message("Director ");
766 message += m_dir->name();
767 message += " is curerntly busy\n Please complete restore or other "
768 " operation !! This is a limitation that will be resolved before a beta"
769 " release. This is currently an alpha release.";
770 QMessageBox::warning(this, tr("Bat"),
771 tr(message.toUtf8().data()), QMessageBox::Ok );
773 } else if (!m_at_prompt){
774 QString message("Director ");
775 message += m_dir->name();
776 message += " is curerntly not at a prompt\n Please try again!!";
777 QMessageBox::warning(this, tr("Bat"),
778 tr(message.toUtf8().data()), QMessageBox::Ok );
786 * Call-back for reading a passphrase for an encrypted PEM file
787 * This function uses getpass(),
788 * which uses a static buffer and is NOT thread-safe.
790 static int tls_pem_callback(char *buf, int size, const void *userdata)
793 const char *prompt = (const char *)userdata;
794 # if defined(HAVE_WIN32)
796 if (win32_cgets(buf, size) == NULL) {
805 passwd = getpass(prompt);
806 bstrncpy(buf, passwd, size);