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"));
62 /* Check for messages every 5 seconds */
63 m_timer = new QTimer(this);
64 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
72 void Console::startTimer()
74 m_timer->start(mainWin->m_checkMessagesInterval*1000);
77 void Console::poll_messages()
79 m_messages_pending = true;
80 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
86 /* Terminate any open socket */
87 void Console::terminate()
97 * Connect to Director. If there are more than one, put up
98 * a modal dialog so that the user chooses one.
100 void Console::connect()
106 m_textEdit = textEdit; /* our console screen */
109 mainWin->set_status("No Director found.");
113 mainWin->set_status("Already connected.");
117 memset(&jcr, 0, sizeof(jcr));
119 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
120 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
122 /* Give GUI a chance */
123 app->processEvents();
126 /* If cons==NULL, default console will be used */
127 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
130 /* Initialize Console TLS context once */
131 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
132 /* Generate passphrase prompt */
133 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
136 /* Initialize TLS context:
137 * Args: CA certfile, CA certdir, Certfile, Keyfile,
138 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
140 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
141 cons->tls_ca_certdir, cons->tls_certfile,
142 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
144 if (!cons->tls_ctx) {
145 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
151 /* Initialize Director TLS context once */
152 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
153 /* Generate passphrase prompt */
154 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
157 /* Initialize TLS context:
158 * Args: CA certfile, CA certdir, Certfile, Keyfile,
159 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
160 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
161 m_dir->tls_ca_certdir, m_dir->tls_certfile,
162 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
164 if (!m_dir->tls_ctx) {
165 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
167 mainWin->set_status("Connection failed");
172 if (m_dir->heartbeat_interval) {
173 heart_beat = m_dir->heartbeat_interval;
175 heart_beat = cons->heartbeat_interval;
180 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
181 _("Director daemon"), m_dir->address,
182 NULL, m_dir->DIRport, 0);
183 if (m_sock == NULL) {
184 mainWin->set_status("Connection failed");
187 /* Update page selector to green to indicate that Console is connected */
188 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
189 QBrush greenBrush(Qt::green);
190 QTreeWidgetItem *item = mainWin->getFromHash(this);
191 item->setForeground(0, greenBrush);
194 jcr.dir_bsock = m_sock;
196 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
204 /* Give GUI a chance */
205 app->processEvents();
207 mainWin->set_status(_("Initializing ..."));
209 /* Set up input notifier */
210 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
211 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
217 dir_cmd(".jobs", job_list);
218 dir_cmd(".clients", client_list);
219 dir_cmd(".filesets", fileset_list);
220 dir_cmd(".msgs", messages_list);
221 dir_cmd(".pools", pool_list);
222 dir_cmd(".storage", storage_list);
223 dir_cmd(".types", type_list);
224 dir_cmd(".levels", level_list);
226 mainWin->set_status(_("Connected"));
230 bool Console::dir_cmd(QString &cmd, QStringList &results)
232 return dir_cmd(cmd.toUtf8().data(), results);
236 * Send a command to the Director, and return the
237 * results in a QStringList.
239 bool Console::dir_cmd(const char *cmd, QStringList &results)
245 while ((stat = read()) > 0) {
246 if (mainWin->m_displayAll) display_text(msg());
247 strip_trailing_junk(msg());
252 return true; /* ***FIXME*** return any command error */
255 bool Console::sql_cmd(QString &query, QStringList &results)
257 return sql_cmd(query.toUtf8().data(), results);
261 * Send an sql query to the Director, and return the
262 * results in a QStringList.
264 bool Console::sql_cmd(const char *query, QStringList &results)
266 if (!is_connectedGui())
269 POOL_MEM cmd(PM_MESSAGE);
273 pm_strcpy(cmd, ".sql query=\"");
274 pm_strcat(cmd, query);
275 pm_strcat(cmd, "\"");
277 while ((stat = read()) > 0) {
278 if (mainWin->m_displayAll) display_text(msg());
279 strip_trailing_junk(msg());
284 return true; /* ***FIXME*** return any command error */
289 * Send a job name to the director, and read all the resulting
292 bool Console::get_job_defaults(struct job_defaults &job_defs)
300 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
302 while ((stat = read()) > 0) {
303 if (mainWin->m_displayAll) display_text(msg());
304 def = strchr(msg(), '=');
308 /* Pointer to default value */
310 strip_trailing_junk(def);
312 if (strcmp(msg(), "job") == 0) {
313 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
318 if (strcmp(msg(), "pool") == 0) {
319 job_defs.pool_name = def;
322 if (strcmp(msg(), "messages") == 0) {
323 job_defs.messages_name = def;
326 if (strcmp(msg(), "client") == 0) {
327 job_defs.client_name = def;
330 if (strcmp(msg(), "storage") == 0) {
331 job_defs.store_name = def;
334 if (strcmp(msg(), "where") == 0) {
335 job_defs.where = def;
338 if (strcmp(msg(), "level") == 0) {
339 job_defs.level = def;
342 if (strcmp(msg(), "type") == 0) {
346 if (strcmp(msg(), "fileset") == 0) {
347 job_defs.fileset_name = def;
350 if (strcmp(msg(), "catalog") == 0) {
351 job_defs.catalog_name = def;
354 if (strcmp(msg(), "enabled") == 0) {
355 job_defs.enabled = *def == '1' ? true : false;
361 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
362 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
363 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
364 job_defs.client_name.toUtf8().data(),
365 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
366 job_defs.store_name.toUtf8().data(),
367 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
368 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
369 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
382 * Save user settings associated with this console
384 void Console::writeSettings()
386 QFont font = get_font();
388 QSettings settings(m_dir->name(), "bat");
389 settings.beginGroup("Console");
390 settings.setValue("consoleFont", font.family());
391 settings.setValue("consolePointSize", font.pointSize());
392 settings.setValue("consoleFixedPitch", font.fixedPitch());
397 * Read and restore user settings associated with this console
399 void Console::readSettings()
401 QFont font = get_font();
403 QSettings settings(m_dir->name(), "bat");
404 settings.beginGroup("Console");
405 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
406 font.setPointSize(settings.value("consolePointSize", 10).toInt());
407 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
409 m_textEdit->setFont(font);
413 * Set the console textEdit font
415 void Console::set_font()
418 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
420 m_textEdit->setFont(font);
425 * Get the console text edit font
427 const QFont Console::get_font()
429 return m_textEdit->font();
433 * Slot for responding to status dir button on button bar
435 void Console::status_dir()
437 QString cmd("status dir");
442 * Slot for responding to messages button on button bar
444 void Console::messages()
446 QString cmd(".messages");
451 * Put text into the console window
453 void Console::display_textf(const char *fmt, ...)
458 va_start(arg_ptr, fmt);
459 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
464 void Console::display_text(const QString buf)
466 m_cursor->insertText(buf);
471 void Console::display_text(const char *buf)
473 m_cursor->insertText(buf);
477 void Console::display_html(const QString buf)
479 m_cursor->insertHtml(buf);
483 /* Position cursor to end of screen */
484 void Console::update_cursor()
486 QApplication::restoreOverrideCursor();
487 m_textEdit->moveCursor(QTextCursor::End);
488 m_textEdit->ensureCursorVisible();
492 * This should be moved into a bSocket class
502 /* Send a command to the Director */
503 void Console::write_dir(const char *msg)
506 mainWin->set_status(_("Processing command ..."));
507 QApplication::setOverrideCursor(Qt::WaitCursor);
510 mainWin->set_status(" Director not connected. Click on connect button.");
511 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
512 QBrush redBrush(Qt::red);
513 QTreeWidgetItem *item = mainWin->getFromHash(this);
514 item->setForeground(0, redBrush);
516 m_at_main_prompt = false;
520 int Console::write(const QString msg)
522 return write(msg.toUtf8().data());
525 int Console::write(const char *msg)
527 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
529 m_at_main_prompt = false;
530 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
531 return m_sock->send();
535 * Get to main command prompt -- i.e. abort any subcommand
537 void Console::beginNewCommand()
539 for (int i=0; i < 3; i++) {
542 if (mainWin->m_displayAll) display_text(msg());
544 if (m_at_main_prompt) {
551 void Console::displayToPrompt()
554 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
555 while (!m_at_prompt) {
556 if ((stat=read()) > 0) {
560 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
563 void Console::discardToPrompt()
566 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
567 while (!m_at_prompt) {
568 if ((stat=read()) > 0) {
569 if (mainWin->m_displayAll) display_text(msg());
572 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
577 * Blocking read from director
584 stat = bnet_wait_data_intr(m_sock, 1);
588 app->processEvents();
589 if (m_api_set && m_messages_pending) {
590 write_dir(".messages");
591 m_messages_pending = false;
594 stat = m_sock->recv();
599 m_at_main_prompt = false;
601 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
603 switch (m_sock->msglen) {
604 case BNET_MSGS_PENDING:
605 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
606 write_dir(".messages");
608 m_messages_pending = false;
611 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
613 m_at_main_prompt = false;
616 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
618 m_at_main_prompt = false;
620 case BNET_MAIN_PROMPT:
621 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
623 m_at_main_prompt = true;
624 mainWin->set_status(_("At main prompt waiting for input ..."));
625 QApplication::restoreOverrideCursor();
628 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
630 m_at_main_prompt = false;
631 mainWin->set_status(_("At prompt waiting for input ..."));
632 QApplication::restoreOverrideCursor();
634 case BNET_CMD_FAILED:
635 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
636 mainWin->set_status(_("Command failed."));
637 QApplication::restoreOverrideCursor();
639 /* We should not get this one */
641 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
642 mainWin->set_status_ready();
643 QApplication::restoreOverrideCursor();
648 case BNET_START_SELECT:
649 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
650 new selectDialog(this);
653 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
657 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
658 m_sock->recv(); /* get the message */
660 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
662 case BNET_WARNING_MSG:
663 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
664 m_sock->recv(); /* get the message */
666 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
669 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
670 m_sock->recv(); /* get the message */
672 mainWin->set_status(msg());
675 if (is_bnet_stop(m_sock)) { /* error or term request */
676 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
679 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
680 QBrush redBrush(Qt::red);
681 QTreeWidgetItem *item = mainWin->getFromHash(this);
682 item->setForeground(0, redBrush);
683 m_notifier->setEnabled(false);
686 mainWin->set_status(_("Director disconnected."));
687 QApplication::restoreOverrideCursor();
695 /* Called by signal when the Director has output for us */
696 void Console::read_dir(int /* fd */)
698 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
699 while (read() >= 0) {
705 * When the notifier is enabled, read_dir() will automatically be
706 * called by the Qt event loop when ever there is any output
707 * from the Directory, and read_dir() will then display it on
710 * When we are in a bat dialog, we want to control *all* output
711 * from the Directory, so we set notify to off.
712 * m_console->notifiy(false);
714 void Console::notify(bool enable)
716 m_notifier->setEnabled(enable);
719 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
721 m_directorTreeItem = item;
724 void Console::setDirRes(DIRRES *dir)
730 * To have the ability to get the name of the director resource.
732 void Console::getDirResName(QString &name_returned)
734 name_returned = m_dir->name();
737 bool Console::is_connectedGui()
739 if (is_connected()) {
742 QString message("Director ");
743 message += " is curerntly disconnected\n Please reconnect!!";
744 QMessageBox::warning(this, tr("Bat"),
745 tr(message.toUtf8().data()), QMessageBox::Ok );
751 * A temporary function to prevent connecting to the director if the director
752 * is busy with a restore.
754 bool Console::preventInUseConnect()
756 if (!is_connected()) {
757 QString message("Director ");
758 message += m_dir->name();
759 message += " is curerntly disconnected\n Please reconnect!!";
760 QMessageBox::warning(this, tr("Bat"),
761 tr(message.toUtf8().data()), QMessageBox::Ok );
763 } else if (!m_at_main_prompt){
764 QString message("Director ");
765 message += m_dir->name();
766 message += " is curerntly busy\n Please complete restore or other "
767 " operation !! This is a limitation that will be resolved before a beta"
768 " release. This is currently an alpha release.";
769 QMessageBox::warning(this, tr("Bat"),
770 tr(message.toUtf8().data()), QMessageBox::Ok );
772 } else if (!m_at_prompt){
773 QString message("Director ");
774 message += m_dir->name();
775 message += " is curerntly not at a prompt\n Please try again!!";
776 QMessageBox::warning(this, tr("Bat"),
777 tr(message.toUtf8().data()), QMessageBox::Ok );
785 * Call-back for reading a passphrase for an encrypted PEM file
786 * This function uses getpass(),
787 * which uses a static buffer and is NOT thread-safe.
789 static int tls_pem_callback(char *buf, int size, const void *userdata)
792 const char *prompt = (const char *)userdata;
793 # if defined(HAVE_WIN32)
795 if (win32_cgets(buf, size) == NULL) {
804 passwd = getpass(prompt);
805 bstrncpy(buf, passwd, size);