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::poll_messages()
75 m_messages_pending = true;
76 if (m_at_main_prompt) {
82 /* Terminate any open socket */
83 void Console::terminate()
93 * Connect to Director. If there are more than one, put up
94 * a modal dialog so that the user chooses one.
96 void Console::connect()
102 m_textEdit = textEdit; /* our console screen */
105 mainWin->set_status("No Director found.");
109 mainWin->set_status("Already connected.");
113 memset(&jcr, 0, sizeof(jcr));
115 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
116 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
118 /* Give GUI a chance */
119 app->processEvents();
122 /* If cons==NULL, default console will be used */
123 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
126 /* Initialize Console TLS context */
127 if (cons && (cons->tls_enable || cons->tls_require)) {
128 /* Generate passphrase prompt */
129 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
132 /* Initialize TLS context:
133 * Args: CA certfile, CA certdir, Certfile, Keyfile,
134 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
136 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
137 cons->tls_ca_certdir, cons->tls_certfile,
138 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
140 if (!cons->tls_ctx) {
141 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
147 /* Initialize Director TLS context */
148 if (m_dir->tls_enable || m_dir->tls_require) {
149 /* Generate passphrase prompt */
150 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
153 /* Initialize TLS context:
154 * Args: CA certfile, CA certdir, Certfile, Keyfile,
155 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
156 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
157 m_dir->tls_ca_certdir, m_dir->tls_certfile,
158 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
160 if (!m_dir->tls_ctx) {
161 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
163 mainWin->set_status("Connection failed");
168 if (m_dir->heartbeat_interval) {
169 heart_beat = m_dir->heartbeat_interval;
171 heart_beat = cons->heartbeat_interval;
176 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
177 _("Director daemon"), m_dir->address,
178 NULL, m_dir->DIRport, 0);
179 if (m_sock == NULL) {
180 mainWin->set_status("Connection failed");
183 /* Update page selector to green to indicate that Console is connected */
184 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
185 QBrush greenBrush(Qt::green);
186 QTreeWidgetItem *item = mainWin->getFromHash(this);
187 item->setForeground(0, greenBrush);
190 jcr.dir_bsock = m_sock;
192 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
200 /* Give GUI a chance */
201 app->processEvents();
203 mainWin->set_status(_("Initializing ..."));
205 /* Set up input notifier */
206 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
207 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
213 dir_cmd(".jobs", job_list);
214 dir_cmd(".clients", client_list);
215 dir_cmd(".filesets", fileset_list);
216 dir_cmd(".msgs", messages_list);
217 dir_cmd(".pools", pool_list);
218 dir_cmd(".storage", storage_list);
219 dir_cmd(".types", type_list);
220 dir_cmd(".levels", level_list);
222 mainWin->set_status(_("Connected"));
226 bool Console::dir_cmd(QString &cmd, QStringList &results)
228 return dir_cmd(cmd.toUtf8().data(), results);
232 * Send a command to the Director, and return the
233 * results in a QStringList.
235 bool Console::dir_cmd(const char *cmd, QStringList &results)
241 while ((stat = read()) > 0) {
242 if (mainWin->m_displayAll) display_text(msg());
243 strip_trailing_junk(msg());
248 return true; /* ***FIXME*** return any command error */
251 bool Console::sql_cmd(QString &query, QStringList &results)
253 return sql_cmd(query.toUtf8().data(), results);
257 * Send an sql query to the Director, and return the
258 * results in a QStringList.
260 bool Console::sql_cmd(const char *query, QStringList &results)
262 if (!is_connectedGui())
265 POOL_MEM cmd(PM_MESSAGE);
269 pm_strcpy(cmd, ".sql query=\"");
270 pm_strcat(cmd, query);
271 pm_strcat(cmd, "\"");
273 while ((stat = read()) > 0) {
274 if (mainWin->m_displayAll) display_text(msg());
275 strip_trailing_junk(msg());
280 return true; /* ***FIXME*** return any command error */
285 * Send a job name to the director, and read all the resulting
288 bool Console::get_job_defaults(struct job_defaults &job_defs)
296 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
298 while ((stat = read()) > 0) {
299 if (mainWin->m_displayAll) display_text(msg());
300 def = strchr(msg(), '=');
304 /* Pointer to default value */
306 strip_trailing_junk(def);
308 if (strcmp(msg(), "job") == 0) {
309 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
314 if (strcmp(msg(), "pool") == 0) {
315 job_defs.pool_name = def;
318 if (strcmp(msg(), "messages") == 0) {
319 job_defs.messages_name = def;
322 if (strcmp(msg(), "client") == 0) {
323 job_defs.client_name = def;
326 if (strcmp(msg(), "storage") == 0) {
327 job_defs.store_name = def;
330 if (strcmp(msg(), "where") == 0) {
331 job_defs.where = def;
334 if (strcmp(msg(), "level") == 0) {
335 job_defs.level = def;
338 if (strcmp(msg(), "type") == 0) {
342 if (strcmp(msg(), "fileset") == 0) {
343 job_defs.fileset_name = def;
346 if (strcmp(msg(), "catalog") == 0) {
347 job_defs.catalog_name = def;
350 if (strcmp(msg(), "enabled") == 0) {
351 job_defs.enabled = *def == '1' ? true : false;
357 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
358 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
359 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
360 job_defs.client_name.toUtf8().data(),
361 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
362 job_defs.store_name.toUtf8().data(),
363 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
364 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
365 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
378 * Save user settings associated with this console
380 void Console::writeSettings()
382 QFont font = get_font();
384 QSettings settings(m_dir->name(), "bat");
385 settings.beginGroup("Console");
386 settings.setValue("consoleFont", font.family());
387 settings.setValue("consolePointSize", font.pointSize());
388 settings.setValue("consoleFixedPitch", font.fixedPitch());
393 * Read and restore user settings associated with this console
395 void Console::readSettings()
397 QFont font = get_font();
399 QSettings settings(m_dir->name(), "bat");
400 settings.beginGroup("Console");
401 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
402 font.setPointSize(settings.value("consolePointSize", 10).toInt());
403 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
405 m_textEdit->setFont(font);
409 * Set the console textEdit font
411 void Console::set_font()
414 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
416 m_textEdit->setFont(font);
421 * Get the console text edit font
423 const QFont Console::get_font()
425 return m_textEdit->font();
429 * Slot for responding to status dir button on button bar
431 void Console::status_dir()
433 QString cmd("status dir");
438 * Slot for responding to messages button on button bar
440 void Console::messages()
442 QString cmd(".messages");
447 * Put text into the console window
449 void Console::display_textf(const char *fmt, ...)
454 va_start(arg_ptr, fmt);
455 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
460 void Console::display_text(const QString buf)
462 m_cursor->insertText(buf);
467 void Console::display_text(const char *buf)
469 m_cursor->insertText(buf);
473 void Console::display_html(const QString buf)
475 m_cursor->insertHtml(buf);
479 /* Position cursor to end of screen */
480 void Console::update_cursor()
482 QApplication::restoreOverrideCursor();
483 m_textEdit->moveCursor(QTextCursor::End);
484 m_textEdit->ensureCursorVisible();
488 * This should be moved into a bSocket class
498 /* Send a command to the Director */
499 void Console::write_dir(const char *msg)
502 mainWin->set_status(_("Processing command ..."));
503 QApplication::setOverrideCursor(Qt::WaitCursor);
506 mainWin->set_status(" Director not connected. Click on connect button.");
507 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
508 QBrush redBrush(Qt::red);
509 QTreeWidgetItem *item = mainWin->getFromHash(this);
510 item->setForeground(0, redBrush);
512 m_at_main_prompt = false;
516 int Console::write(const QString msg)
518 return write(msg.toUtf8().data());
521 int Console::write(const char *msg)
523 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
525 m_at_main_prompt = false;
526 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
527 return m_sock->send();
531 * Get to main command prompt -- i.e. abort any subcommand
533 void Console::beginNewCommand()
535 for (int i=0; i < 3; i++) {
538 if (mainWin->m_displayAll) display_text(msg());
540 if (m_at_main_prompt) {
547 void Console::displayToPrompt()
550 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
551 while (!m_at_prompt) {
552 if ((stat=read()) > 0) {
556 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
559 void Console::discardToPrompt()
562 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
563 while (!m_at_prompt) {
564 if ((stat=read()) > 0) {
565 if (mainWin->m_displayAll) display_text(msg());
568 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
573 * Blocking read from director
580 stat = bnet_wait_data_intr(m_sock, 1);
584 app->processEvents();
585 if (m_api_set && m_messages_pending) {
586 write_dir(".messages");
587 m_messages_pending = false;
590 stat = m_sock->recv();
595 m_at_main_prompt = false;
597 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
599 switch (m_sock->msglen) {
600 case BNET_MSGS_PENDING:
601 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
602 write_dir(".messages");
604 m_messages_pending = false;
607 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
609 m_at_main_prompt = false;
612 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
614 m_at_main_prompt = false;
616 case BNET_MAIN_PROMPT:
617 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
619 m_at_main_prompt = true;
620 mainWin->set_status(_("At prompt waiting for input ..."));
621 QApplication::restoreOverrideCursor();
624 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
626 m_at_main_prompt = false;
627 mainWin->set_status(_("At prompt waiting for input ..."));
628 QApplication::restoreOverrideCursor();
630 case BNET_CMD_FAILED:
631 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
632 mainWin->set_status(_("Command failed. At prompt waiting for input ..."));
633 QApplication::restoreOverrideCursor();
635 /* We should not get this one */
637 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
638 mainWin->set_status_ready();
639 QApplication::restoreOverrideCursor();
644 case BNET_START_SELECT:
645 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
646 new selectDialog(this);
649 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
653 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
654 m_sock->recv(); /* get the message */
656 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
658 case BNET_WARNING_MSG:
659 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
660 m_sock->recv(); /* get the message */
662 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
665 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
666 m_sock->recv(); /* get the message */
668 mainWin->set_status(msg());
671 if (is_bnet_stop(m_sock)) { /* error or term request */
672 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
675 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
676 QBrush redBrush(Qt::red);
677 QTreeWidgetItem *item = mainWin->getFromHash(this);
678 item->setForeground(0, redBrush);
679 m_notifier->setEnabled(false);
682 mainWin->set_status(_("Director disconnected."));
683 QApplication::restoreOverrideCursor();
691 /* Called by signal when the Director has output for us */
692 void Console::read_dir(int /* fd */)
694 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
695 while (read() >= 0) {
701 * When the notifier is enabled, read_dir() will automatically be
702 * called by the Qt event loop when ever there is any output
703 * from the Directory, and read_dir() will then display it on
706 * When we are in a bat dialog, we want to control *all* output
707 * from the Directory, so we set notify to off.
708 * m_console->notifiy(false);
710 void Console::notify(bool enable)
712 m_notifier->setEnabled(enable);
715 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
717 m_directorTreeItem = item;
720 void Console::setDirRes(DIRRES *dir)
726 * To have the ability to get the name of the director resource.
728 void Console::getDirResName(QString &name_returned)
730 name_returned = m_dir->name();
733 bool Console::is_connectedGui()
735 if (is_connected()) {
738 QString message("Director ");
739 message += " is curerntly disconnected\n Please reconnect!!";
740 QMessageBox::warning(this, tr("Bat"),
741 tr(message.toUtf8().data()), QMessageBox::Ok );
747 * A temporary function to prevent connecting to the director if the director
748 * is busy with a restore.
750 bool Console::preventInUseConnect()
752 if (!is_connected()) {
753 QString message("Director ");
754 message += m_dir->name();
755 message += " is curerntly disconnected\n Please reconnect!!";
756 QMessageBox::warning(this, tr("Bat"),
757 tr(message.toUtf8().data()), QMessageBox::Ok );
759 } else if (!m_at_main_prompt){
760 QString message("Director ");
761 message += m_dir->name();
762 message += " is curerntly busy\n Please complete restore or other "
763 " operation !! This is a limitation that will be resolved before a beta"
764 " release. This is currently an alpha release.";
765 QMessageBox::warning(this, tr("Bat"),
766 tr(message.toUtf8().data()), QMessageBox::Ok );
768 } else if (!m_at_prompt){
769 QString message("Director ");
770 message += m_dir->name();
771 message += " is curerntly not at a prompt\n Please try again!!";
772 QMessageBox::warning(this, tr("Bat"),
773 tr(message.toUtf8().data()), QMessageBox::Ok );
781 * Call-back for reading a passphrase for an encrypted PEM file
782 * This function uses getpass(),
783 * which uses a static buffer and is NOT thread-safe.
785 static int tls_pem_callback(char *buf, int size, const void *userdata)
788 const char *prompt = (const char *)userdata;
789 # if defined(HAVE_WIN32)
791 if (win32_cgets(buf, size) == NULL) {
800 passwd = getpass(prompt);
801 bstrncpy(buf, passwd, size);