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)));
233 dir_cmd(".jobs", job_list);
234 dir_cmd(".clients", client_list);
235 dir_cmd(".filesets", fileset_list);
236 dir_cmd(".msgs", messages_list);
237 dir_cmd(".pools", pool_list);
238 dir_cmd(".storage", storage_list);
239 dir_cmd(".types", type_list);
240 dir_cmd(".levels", level_list);
242 mainWin->set_status(_("Connected"));
243 startTimer(); /* start message timer */
247 bool Console::dir_cmd(QString &cmd, QStringList &results)
249 return dir_cmd(cmd.toUtf8().data(), results);
253 * Send a command to the Director, and return the
254 * results in a QStringList.
256 bool Console::dir_cmd(const char *cmd, QStringList &results)
262 while ((stat = read()) > 0) {
263 if (mainWin->m_displayAll) display_text(msg());
264 strip_trailing_junk(msg());
269 return true; /* ***FIXME*** return any command error */
272 bool Console::sql_cmd(QString &query, QStringList &results)
274 return sql_cmd(query.toUtf8().data(), results);
278 * Send an sql query to the Director, and return the
279 * results in a QStringList.
281 bool Console::sql_cmd(const char *query, QStringList &results)
284 POOL_MEM cmd(PM_MESSAGE);
286 if (!is_connectedGui()) {
292 pm_strcpy(cmd, ".sql query=\"");
293 pm_strcat(cmd, query);
294 pm_strcat(cmd, "\"");
296 while ((stat = read()) > 0) {
297 if (mainWin->m_displayAll) display_text(msg());
298 strip_trailing_junk(msg());
303 return true; /* ***FIXME*** return any command error */
308 * Send a job name to the director, and read all the resulting
311 bool Console::get_job_defaults(struct job_defaults &job_defs)
319 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
321 while ((stat = read()) > 0) {
322 if (mainWin->m_displayAll) display_text(msg());
323 def = strchr(msg(), '=');
327 /* Pointer to default value */
329 strip_trailing_junk(def);
331 if (strcmp(msg(), "job") == 0) {
332 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
337 if (strcmp(msg(), "pool") == 0) {
338 job_defs.pool_name = def;
341 if (strcmp(msg(), "messages") == 0) {
342 job_defs.messages_name = def;
345 if (strcmp(msg(), "client") == 0) {
346 job_defs.client_name = def;
349 if (strcmp(msg(), "storage") == 0) {
350 job_defs.store_name = def;
353 if (strcmp(msg(), "where") == 0) {
354 job_defs.where = def;
357 if (strcmp(msg(), "level") == 0) {
358 job_defs.level = def;
361 if (strcmp(msg(), "type") == 0) {
365 if (strcmp(msg(), "fileset") == 0) {
366 job_defs.fileset_name = def;
369 if (strcmp(msg(), "catalog") == 0) {
370 job_defs.catalog_name = def;
373 if (strcmp(msg(), "enabled") == 0) {
374 job_defs.enabled = *def == '1' ? true : false;
389 * Save user settings associated with this console
391 void Console::writeSettings()
393 QFont font = get_font();
395 QSettings settings(m_dir->name(), "bat");
396 settings.beginGroup("Console");
397 settings.setValue("consoleFont", font.family());
398 settings.setValue("consolePointSize", font.pointSize());
399 settings.setValue("consoleFixedPitch", font.fixedPitch());
404 * Read and restore user settings associated with this console
406 void Console::readSettings()
408 QFont font = get_font();
410 QSettings settings(m_dir->name(), "bat");
411 settings.beginGroup("Console");
412 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
413 font.setPointSize(settings.value("consolePointSize", 10).toInt());
414 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
416 m_textEdit->setFont(font);
420 * Set the console textEdit font
422 void Console::set_font()
425 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
427 m_textEdit->setFont(font);
432 * Get the console text edit font
434 const QFont Console::get_font()
436 return m_textEdit->font();
440 * Slot for responding to status dir button on button bar
442 void Console::status_dir()
444 QString cmd("status dir");
449 * Slot for responding to messages button on button bar
451 void Console::messages()
453 QString cmd(".messages");
458 * Put text into the console window
460 void Console::display_textf(const char *fmt, ...)
465 va_start(arg_ptr, fmt);
466 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
471 void Console::display_text(const QString buf)
473 m_cursor->insertText(buf);
478 void Console::display_text(const char *buf)
480 m_cursor->insertText(buf);
484 void Console::display_html(const QString buf)
486 m_cursor->insertHtml(buf);
490 /* Position cursor to end of screen */
491 void Console::update_cursor()
493 QApplication::restoreOverrideCursor();
494 m_textEdit->moveCursor(QTextCursor::End);
495 m_textEdit->ensureCursorVisible();
499 * This should be moved into a bSocket class
509 /* Send a command to the Director */
510 void Console::write_dir(const char *msg)
513 mainWin->set_status(_("Processing command ..."));
514 QApplication::setOverrideCursor(Qt::WaitCursor);
517 mainWin->set_status(" Director not connected. Click on connect button.");
518 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
519 QBrush redBrush(Qt::red);
520 QTreeWidgetItem *item = mainWin->getFromHash(this);
521 item->setForeground(0, redBrush);
523 m_at_main_prompt = false;
527 int Console::write(const QString msg)
529 return write(msg.toUtf8().data());
532 int Console::write(const char *msg)
537 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
539 m_at_main_prompt = false;
540 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
541 return m_sock->send();
546 * Get to main command prompt -- i.e. abort any subcommand
548 void Console::beginNewCommand()
550 for (int i=0; i < 3; i++) {
553 if (mainWin->m_displayAll) display_text(msg());
555 if (m_at_main_prompt) {
562 void Console::displayToPrompt()
565 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
566 while (!m_at_prompt) {
567 if ((stat=read()) > 0) {
571 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
574 void Console::discardToPrompt()
577 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
578 while (!m_at_prompt) {
579 if ((stat=read()) > 0) {
580 if (mainWin->m_displayAll) display_text(msg());
583 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
588 * Blocking read from director
595 stat = bnet_wait_data_intr(m_sock, 1);
599 app->processEvents();
600 if (m_api_set && m_messages_pending) {
601 write_dir(".messages");
602 m_messages_pending = false;
605 stat = m_sock->recv();
610 m_at_main_prompt = false;
612 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
614 switch (m_sock->msglen) {
615 case BNET_MSGS_PENDING:
616 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
617 write_dir(".messages");
619 m_messages_pending = false;
622 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
624 m_at_main_prompt = false;
625 mainWin->set_status(_("Command completed ..."));
628 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
630 m_at_main_prompt = false;
631 mainWin->set_status(_("Processing command ..."));
633 case BNET_MAIN_PROMPT:
634 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
636 m_at_main_prompt = true;
637 mainWin->set_status(_("At main prompt waiting for input ..."));
638 QApplication::restoreOverrideCursor();
641 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
643 m_at_main_prompt = false;
644 mainWin->set_status(_("At prompt waiting for input ..."));
645 QApplication::restoreOverrideCursor();
647 case BNET_CMD_FAILED:
648 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
649 mainWin->set_status(_("Command failed."));
650 QApplication::restoreOverrideCursor();
652 /* We should not get this one */
654 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
655 mainWin->set_status_ready();
656 QApplication::restoreOverrideCursor();
661 case BNET_START_SELECT:
662 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
663 new selectDialog(this);
666 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
670 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
671 m_sock->recv(); /* get the message */
673 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
675 case BNET_WARNING_MSG:
676 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
677 m_sock->recv(); /* get the message */
679 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
682 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
683 m_sock->recv(); /* get the message */
685 mainWin->set_status(msg());
688 if (is_bnet_stop(m_sock)) { /* error or term request */
689 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
693 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
694 QBrush redBrush(Qt::red);
695 QTreeWidgetItem *item = mainWin->getFromHash(this);
696 item->setForeground(0, redBrush);
697 m_notifier->setEnabled(false);
700 mainWin->set_status(_("Director disconnected."));
701 QApplication::restoreOverrideCursor();
709 /* Called by signal when the Director has output for us */
710 void Console::read_dir(int /* fd */)
712 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
713 while (read() >= 0) {
719 * When the notifier is enabled, read_dir() will automatically be
720 * called by the Qt event loop when ever there is any output
721 * from the Directory, and read_dir() will then display it on
724 * When we are in a bat dialog, we want to control *all* output
725 * from the Directory, so we set notify to off.
726 * m_console->notifiy(false);
728 void Console::notify(bool enable)
730 m_notifier->setEnabled(enable);
733 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
735 m_directorTreeItem = item;
738 void Console::setDirRes(DIRRES *dir)
744 * To have the ability to get the name of the director resource.
746 void Console::getDirResName(QString &name_returned)
748 name_returned = m_dir->name();
751 bool Console::is_connectedGui()
753 if (is_connected()) {
756 QString message("Director ");
757 message += " is currently disconnected\n Please reconnect!!";
758 QMessageBox::warning(this, "Bat",
759 tr(message.toUtf8().data()), QMessageBox::Ok );
765 * A temporary function to prevent connecting to the director if the director
766 * is busy with a restore.
768 bool Console::preventInUseConnect()
770 if (!is_connected()) {
771 QString message("Director ");
772 message += m_dir->name();
773 message += " is currently disconnected\n Please reconnect!!";
774 QMessageBox::warning(this, "Bat",
775 tr(message.toUtf8().data()), QMessageBox::Ok );
777 } else if (!m_at_main_prompt){
778 QString message("Director ");
779 message += m_dir->name();
780 message += " is currently busy\n Please complete restore or other "
781 " operation !! This is a limitation that will be resolved before a beta"
782 " release. This is currently an alpha release.";
783 QMessageBox::warning(this, "Bat",
784 tr(message.toUtf8().data()), QMessageBox::Ok );
786 } else if (!m_at_prompt){
787 QString message("Director ");
788 message += m_dir->name();
789 message += " is currently not at a prompt\n Please try again!!";
790 QMessageBox::warning(this, "Bat",
791 tr(message.toUtf8().data()), QMessageBox::Ok );
799 * Call-back for reading a passphrase for an encrypted PEM file
800 * This function uses getpass(),
801 * which uses a static buffer and is NOT thread-safe.
803 static int tls_pem_callback(char *buf, int size, const void *userdata)
808 const char *prompt = (const char *)userdata;
809 # if defined(HAVE_WIN32)
811 if (win32_cgets(buf, size) == NULL) {
820 passwd = getpass(prompt);
821 bstrncpy(buf, passwd, size);
830 /* Slot for responding to page selectors status help command */
831 void Console::consoleHelp()
837 /* Slot for responding to page selectors reload bacula-dir.conf */
838 void Console::consoleReload()
840 QString cmd("reload");