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"));
63 m_contextActions.append(actionStatusDir);
64 m_contextActions.append(actionConsoleHelp);
65 m_contextActions.append(actionRequestMessages);
66 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
67 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
68 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
75 void Console::startTimer()
77 m_timer = new QTimer(this);
78 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
79 m_timer->start(mainWin->m_checkMessagesInterval*1000);
82 void Console::stopTimer()
85 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
92 void Console::poll_messages()
94 m_messages_pending = true;
95 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
101 /* Terminate any open socket */
102 void Console::terminate()
112 * Connect to Director.
114 void Console::connect_dir()
120 m_textEdit = textEdit; /* our console screen */
123 mainWin->set_status("No Director found.");
127 mainWin->set_status("Already connected.");
131 memset(&jcr, 0, sizeof(jcr));
133 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
134 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
136 /* Give GUI a chance */
137 app->processEvents();
140 /* If cons==NULL, default console will be used */
141 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
144 /* Initialize Console TLS context once */
145 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
146 /* Generate passphrase prompt */
147 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
150 /* Initialize TLS context:
151 * Args: CA certfile, CA certdir, Certfile, Keyfile,
152 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
154 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
155 cons->tls_ca_certdir, cons->tls_certfile,
156 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
158 if (!cons->tls_ctx) {
159 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
165 /* Initialize Director TLS context once */
166 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
167 /* Generate passphrase prompt */
168 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
171 /* Initialize TLS context:
172 * Args: CA certfile, CA certdir, Certfile, Keyfile,
173 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
174 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
175 m_dir->tls_ca_certdir, m_dir->tls_certfile,
176 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
178 if (!m_dir->tls_ctx) {
179 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
181 mainWin->set_status("Connection failed");
186 if (m_dir->heartbeat_interval) {
187 heart_beat = m_dir->heartbeat_interval;
189 heart_beat = cons->heartbeat_interval;
194 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
195 _("Director daemon"), m_dir->address,
196 NULL, m_dir->DIRport, 0);
197 if (m_sock == NULL) {
198 mainWin->set_status("Connection failed");
201 /* Update page selector to green to indicate that Console is connected */
202 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
203 QBrush greenBrush(Qt::green);
204 QTreeWidgetItem *item = mainWin->getFromHash(this);
205 item->setForeground(0, greenBrush);
208 jcr.dir_bsock = m_sock;
210 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
218 /* Give GUI a chance */
219 app->processEvents();
221 mainWin->set_status(_("Initializing ..."));
223 /* Set up input notifier */
224 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
225 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
231 dir_cmd(".jobs", job_list);
232 dir_cmd(".clients", client_list);
233 dir_cmd(".filesets", fileset_list);
234 dir_cmd(".msgs", messages_list);
235 dir_cmd(".pools", pool_list);
236 dir_cmd(".storage", storage_list);
237 dir_cmd(".types", type_list);
238 dir_cmd(".levels", level_list);
240 mainWin->set_status(_("Connected"));
241 startTimer(); /* start message timer */
245 bool Console::dir_cmd(QString &cmd, QStringList &results)
247 return dir_cmd(cmd.toUtf8().data(), results);
251 * Send a command to the Director, and return the
252 * results in a QStringList.
254 bool Console::dir_cmd(const char *cmd, QStringList &results)
260 while ((stat = read()) > 0) {
261 if (mainWin->m_displayAll) display_text(msg());
262 strip_trailing_junk(msg());
267 return true; /* ***FIXME*** return any command error */
270 bool Console::sql_cmd(QString &query, QStringList &results)
272 return sql_cmd(query.toUtf8().data(), results);
276 * Send an sql query to the Director, and return the
277 * results in a QStringList.
279 bool Console::sql_cmd(const char *query, QStringList &results)
282 POOL_MEM cmd(PM_MESSAGE);
284 if (!is_connectedGui()) {
290 pm_strcpy(cmd, ".sql query=\"");
291 pm_strcat(cmd, query);
292 pm_strcat(cmd, "\"");
294 while ((stat = read()) > 0) {
295 if (mainWin->m_displayAll) display_text(msg());
296 strip_trailing_junk(msg());
301 return true; /* ***FIXME*** return any command error */
306 * Send a job name to the director, and read all the resulting
309 bool Console::get_job_defaults(struct job_defaults &job_defs)
317 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
319 while ((stat = read()) > 0) {
320 if (mainWin->m_displayAll) display_text(msg());
321 def = strchr(msg(), '=');
325 /* Pointer to default value */
327 strip_trailing_junk(def);
329 if (strcmp(msg(), "job") == 0) {
330 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
335 if (strcmp(msg(), "pool") == 0) {
336 job_defs.pool_name = def;
339 if (strcmp(msg(), "messages") == 0) {
340 job_defs.messages_name = def;
343 if (strcmp(msg(), "client") == 0) {
344 job_defs.client_name = def;
347 if (strcmp(msg(), "storage") == 0) {
348 job_defs.store_name = def;
351 if (strcmp(msg(), "where") == 0) {
352 job_defs.where = def;
355 if (strcmp(msg(), "level") == 0) {
356 job_defs.level = def;
359 if (strcmp(msg(), "type") == 0) {
363 if (strcmp(msg(), "fileset") == 0) {
364 job_defs.fileset_name = def;
367 if (strcmp(msg(), "catalog") == 0) {
368 job_defs.catalog_name = def;
371 if (strcmp(msg(), "enabled") == 0) {
372 job_defs.enabled = *def == '1' ? true : false;
387 * Save user settings associated with this console
389 void Console::writeSettings()
391 QFont font = get_font();
393 QSettings settings(m_dir->name(), "bat");
394 settings.beginGroup("Console");
395 settings.setValue("consoleFont", font.family());
396 settings.setValue("consolePointSize", font.pointSize());
397 settings.setValue("consoleFixedPitch", font.fixedPitch());
402 * Read and restore user settings associated with this console
404 void Console::readSettings()
406 QFont font = get_font();
408 QSettings settings(m_dir->name(), "bat");
409 settings.beginGroup("Console");
410 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
411 font.setPointSize(settings.value("consolePointSize", 10).toInt());
412 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
414 m_textEdit->setFont(font);
418 * Set the console textEdit font
420 void Console::set_font()
423 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
425 m_textEdit->setFont(font);
430 * Get the console text edit font
432 const QFont Console::get_font()
434 return m_textEdit->font();
438 * Slot for responding to status dir button on button bar
440 void Console::status_dir()
442 QString cmd("status dir");
447 * Slot for responding to messages button on button bar
449 void Console::messages()
451 QString cmd(".messages");
456 * Put text into the console window
458 void Console::display_textf(const char *fmt, ...)
463 va_start(arg_ptr, fmt);
464 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
469 void Console::display_text(const QString buf)
471 m_cursor->insertText(buf);
476 void Console::display_text(const char *buf)
478 m_cursor->insertText(buf);
482 void Console::display_html(const QString buf)
484 m_cursor->insertHtml(buf);
488 /* Position cursor to end of screen */
489 void Console::update_cursor()
491 QApplication::restoreOverrideCursor();
492 m_textEdit->moveCursor(QTextCursor::End);
493 m_textEdit->ensureCursorVisible();
497 * This should be moved into a bSocket class
507 /* Send a command to the Director */
508 void Console::write_dir(const char *msg)
511 mainWin->set_status(_("Processing command ..."));
512 QApplication::setOverrideCursor(Qt::WaitCursor);
515 mainWin->set_status(" Director not connected. Click on connect button.");
516 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
517 QBrush redBrush(Qt::red);
518 QTreeWidgetItem *item = mainWin->getFromHash(this);
519 item->setForeground(0, redBrush);
521 m_at_main_prompt = false;
525 int Console::write(const QString msg)
527 return write(msg.toUtf8().data());
530 int Console::write(const char *msg)
535 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
537 m_at_main_prompt = false;
538 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
539 return m_sock->send();
544 * Get to main command prompt -- i.e. abort any subcommand
546 void Console::beginNewCommand()
548 for (int i=0; i < 3; i++) {
551 if (mainWin->m_displayAll) display_text(msg());
553 if (m_at_main_prompt) {
560 void Console::displayToPrompt()
563 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
564 while (!m_at_prompt) {
565 if ((stat=read()) > 0) {
569 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
572 void Console::discardToPrompt()
575 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
576 while (!m_at_prompt) {
577 if ((stat=read()) > 0) {
578 if (mainWin->m_displayAll) display_text(msg());
581 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
586 * Blocking read from director
593 stat = bnet_wait_data_intr(m_sock, 1);
597 app->processEvents();
598 if (m_api_set && m_messages_pending) {
599 write_dir(".messages");
600 m_messages_pending = false;
603 stat = m_sock->recv();
608 m_at_main_prompt = false;
610 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
612 switch (m_sock->msglen) {
613 case BNET_MSGS_PENDING:
614 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
615 write_dir(".messages");
617 m_messages_pending = false;
620 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
622 m_at_main_prompt = false;
625 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
627 m_at_main_prompt = false;
629 case BNET_MAIN_PROMPT:
630 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
632 m_at_main_prompt = true;
633 mainWin->set_status(_("At main prompt waiting for input ..."));
634 QApplication::restoreOverrideCursor();
637 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
639 m_at_main_prompt = false;
640 mainWin->set_status(_("At prompt waiting for input ..."));
641 QApplication::restoreOverrideCursor();
643 case BNET_CMD_FAILED:
644 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
645 mainWin->set_status(_("Command failed."));
646 QApplication::restoreOverrideCursor();
648 /* We should not get this one */
650 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
651 mainWin->set_status_ready();
652 QApplication::restoreOverrideCursor();
657 case BNET_START_SELECT:
658 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
659 new selectDialog(this);
662 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
666 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
667 m_sock->recv(); /* get the message */
669 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
671 case BNET_WARNING_MSG:
672 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
673 m_sock->recv(); /* get the message */
675 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
678 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
679 m_sock->recv(); /* get the message */
681 mainWin->set_status(msg());
684 if (is_bnet_stop(m_sock)) { /* error or term request */
685 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
689 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
690 QBrush redBrush(Qt::red);
691 QTreeWidgetItem *item = mainWin->getFromHash(this);
692 item->setForeground(0, redBrush);
693 m_notifier->setEnabled(false);
696 mainWin->set_status(_("Director disconnected."));
697 QApplication::restoreOverrideCursor();
705 /* Called by signal when the Director has output for us */
706 void Console::read_dir(int /* fd */)
708 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
709 while (read() >= 0) {
715 * When the notifier is enabled, read_dir() will automatically be
716 * called by the Qt event loop when ever there is any output
717 * from the Directory, and read_dir() will then display it on
720 * When we are in a bat dialog, we want to control *all* output
721 * from the Directory, so we set notify to off.
722 * m_console->notifiy(false);
724 void Console::notify(bool enable)
726 m_notifier->setEnabled(enable);
729 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
731 m_directorTreeItem = item;
734 void Console::setDirRes(DIRRES *dir)
740 * To have the ability to get the name of the director resource.
742 void Console::getDirResName(QString &name_returned)
744 name_returned = m_dir->name();
747 bool Console::is_connectedGui()
749 if (is_connected()) {
752 QString message("Director ");
753 message += " is curerntly disconnected\n Please reconnect!!";
754 QMessageBox::warning(this, "Bat",
755 tr(message.toUtf8().data()), QMessageBox::Ok );
761 * A temporary function to prevent connecting to the director if the director
762 * is busy with a restore.
764 bool Console::preventInUseConnect()
766 if (!is_connected()) {
767 QString message("Director ");
768 message += m_dir->name();
769 message += " is curerntly disconnected\n Please reconnect!!";
770 QMessageBox::warning(this, "Bat",
771 tr(message.toUtf8().data()), QMessageBox::Ok );
773 } else if (!m_at_main_prompt){
774 QString message("Director ");
775 message += m_dir->name();
776 message += " is curerntly busy\n Please complete restore or other "
777 " operation !! This is a limitation that will be resolved before a beta"
778 " release. This is currently an alpha release.";
779 QMessageBox::warning(this, "Bat",
780 tr(message.toUtf8().data()), QMessageBox::Ok );
782 } else if (!m_at_prompt){
783 QString message("Director ");
784 message += m_dir->name();
785 message += " is curerntly not at a prompt\n Please try again!!";
786 QMessageBox::warning(this, "Bat",
787 tr(message.toUtf8().data()), QMessageBox::Ok );
795 * Call-back for reading a passphrase for an encrypted PEM file
796 * This function uses getpass(),
797 * which uses a static buffer and is NOT thread-safe.
799 static int tls_pem_callback(char *buf, int size, const void *userdata)
804 const char *prompt = (const char *)userdata;
805 # if defined(HAVE_WIN32)
807 if (win32_cgets(buf, size) == NULL) {
816 passwd = getpass(prompt);
817 bstrncpy(buf, passwd, size);
826 /* Slot for responding to page selectors status help command */
827 void Console::consoleHelp()