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()
123 m_textEdit = textEdit; /* our console screen */
126 mainWin->set_status("No Director found.");
130 mainWin->set_status("Already connected.");
134 memset(jcr, 0, sizeof(JCR));
136 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
137 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
139 /* Give GUI a chance */
140 app->processEvents();
143 /* If cons==NULL, default console will be used */
144 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
147 /* Initialize Console TLS context once */
148 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
149 /* Generate passphrase prompt */
150 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%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
157 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
158 cons->tls_ca_certdir, cons->tls_certfile,
159 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
161 if (!cons->tls_ctx) {
162 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
168 /* Initialize Director TLS context once */
169 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
170 /* Generate passphrase prompt */
171 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
174 /* Initialize TLS context:
175 * Args: CA certfile, CA certdir, Certfile, Keyfile,
176 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
177 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
178 m_dir->tls_ca_certdir, m_dir->tls_certfile,
179 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
181 if (!m_dir->tls_ctx) {
182 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
184 mainWin->set_status("Connection failed");
189 if (m_dir->heartbeat_interval) {
190 heart_beat = m_dir->heartbeat_interval;
192 heart_beat = cons->heartbeat_interval;
197 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
198 _("Director daemon"), m_dir->address,
199 NULL, m_dir->DIRport, 0);
200 if (m_sock == NULL) {
201 mainWin->set_status("Connection failed");
204 /* Update page selector to green to indicate that Console is connected */
205 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
206 QBrush greenBrush(Qt::green);
207 QTreeWidgetItem *item = mainWin->getFromHash(this);
208 item->setForeground(0, greenBrush);
211 jcr->dir_bsock = m_sock;
213 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
221 /* Give GUI a chance */
222 app->processEvents();
224 mainWin->set_status(_("Initializing ..."));
226 /* Set up input notifier */
227 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
228 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
236 fileset_list.clear();
237 fileset_list.clear();
238 messages_list.clear();
240 storage_list.clear();
243 dir_cmd(".jobs", job_list);
244 dir_cmd(".clients", client_list);
245 dir_cmd(".filesets", fileset_list);
246 dir_cmd(".msgs", messages_list);
247 dir_cmd(".pools", pool_list);
248 dir_cmd(".storage", storage_list);
249 dir_cmd(".types", type_list);
250 dir_cmd(".levels", level_list);
252 mainWin->set_status(_("Connected"));
253 startTimer(); /* start message timer */
260 bool Console::dir_cmd(QString &cmd, QStringList &results)
262 return dir_cmd(cmd.toUtf8().data(), results);
266 * Send a command to the Director, and return the
267 * results in a QStringList.
269 bool Console::dir_cmd(const char *cmd, QStringList &results)
275 while ((stat = read()) > 0) {
276 if (mainWin->m_displayAll) display_text(msg());
277 strip_trailing_junk(msg());
282 return true; /* ***FIXME*** return any command error */
285 bool Console::sql_cmd(QString &query, QStringList &results)
287 return sql_cmd(query.toUtf8().data(), results);
291 * Send an sql query to the Director, and return the
292 * results in a QStringList.
294 bool Console::sql_cmd(const char *query, QStringList &results)
297 POOL_MEM cmd(PM_MESSAGE);
299 if (!is_connectedGui()) {
305 pm_strcpy(cmd, ".sql query=\"");
306 pm_strcat(cmd, query);
307 pm_strcat(cmd, "\"");
309 while ((stat = read()) > 0) {
310 if (mainWin->m_displayAll) display_text(msg());
311 strip_trailing_junk(msg());
316 return true; /* ***FIXME*** return any command error */
321 * Send a job name to the director, and read all the resulting
324 bool Console::get_job_defaults(struct job_defaults &job_defs)
332 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
334 while ((stat = read()) > 0) {
335 if (mainWin->m_displayAll) display_text(msg());
336 def = strchr(msg(), '=');
340 /* Pointer to default value */
342 strip_trailing_junk(def);
344 if (strcmp(msg(), "job") == 0) {
345 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
350 if (strcmp(msg(), "pool") == 0) {
351 job_defs.pool_name = def;
354 if (strcmp(msg(), "messages") == 0) {
355 job_defs.messages_name = def;
358 if (strcmp(msg(), "client") == 0) {
359 job_defs.client_name = def;
362 if (strcmp(msg(), "storage") == 0) {
363 job_defs.store_name = def;
366 if (strcmp(msg(), "where") == 0) {
367 job_defs.where = def;
370 if (strcmp(msg(), "level") == 0) {
371 job_defs.level = def;
374 if (strcmp(msg(), "type") == 0) {
378 if (strcmp(msg(), "fileset") == 0) {
379 job_defs.fileset_name = def;
382 if (strcmp(msg(), "catalog") == 0) {
383 job_defs.catalog_name = def;
386 if (strcmp(msg(), "enabled") == 0) {
387 job_defs.enabled = *def == '1' ? true : false;
402 * Save user settings associated with this console
404 void Console::writeSettings()
406 QFont font = get_font();
408 QSettings settings(m_dir->name(), "bat");
409 settings.beginGroup("Console");
410 settings.setValue("consoleFont", font.family());
411 settings.setValue("consolePointSize", font.pointSize());
412 settings.setValue("consoleFixedPitch", font.fixedPitch());
417 * Read and restore user settings associated with this console
419 void Console::readSettings()
421 QFont font = get_font();
423 QSettings settings(m_dir->name(), "bat");
424 settings.beginGroup("Console");
425 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
426 font.setPointSize(settings.value("consolePointSize", 10).toInt());
427 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
429 m_textEdit->setFont(font);
433 * Set the console textEdit font
435 void Console::set_font()
438 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
440 m_textEdit->setFont(font);
445 * Get the console text edit font
447 const QFont Console::get_font()
449 return m_textEdit->font();
453 * Slot for responding to status dir button on button bar
455 void Console::status_dir()
457 QString cmd("status dir");
462 * Slot for responding to messages button on button bar
464 void Console::messages()
466 QString cmd(".messages");
471 * Put text into the console window
473 void Console::display_textf(const char *fmt, ...)
478 va_start(arg_ptr, fmt);
479 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
484 void Console::display_text(const QString buf)
486 m_cursor->insertText(buf);
491 void Console::display_text(const char *buf)
493 m_cursor->insertText(buf);
497 void Console::display_html(const QString buf)
499 m_cursor->insertHtml(buf);
503 /* Position cursor to end of screen */
504 void Console::update_cursor()
506 QApplication::restoreOverrideCursor();
507 m_textEdit->moveCursor(QTextCursor::End);
508 m_textEdit->ensureCursorVisible();
512 * This should be moved into a bSocket class
522 /* Send a command to the Director */
523 void Console::write_dir(const char *msg)
526 mainWin->set_status(_("Processing command ..."));
527 QApplication::setOverrideCursor(Qt::WaitCursor);
530 mainWin->set_status(" Director not connected. Click on connect button.");
531 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
532 QBrush redBrush(Qt::red);
533 QTreeWidgetItem *item = mainWin->getFromHash(this);
534 item->setForeground(0, redBrush);
536 m_at_main_prompt = false;
540 int Console::write(const QString msg)
542 return write(msg.toUtf8().data());
545 int Console::write(const char *msg)
550 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
552 m_at_main_prompt = false;
553 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
554 return m_sock->send();
559 * Get to main command prompt -- i.e. abort any subcommand
561 void Console::beginNewCommand()
563 for (int i=0; i < 3; i++) {
566 if (mainWin->m_displayAll) display_text(msg());
568 if (m_at_main_prompt) {
575 void Console::displayToPrompt()
578 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
579 while (!m_at_prompt) {
580 if ((stat=read()) > 0) {
584 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
587 void Console::discardToPrompt()
590 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
591 while (!m_at_prompt) {
592 if ((stat=read()) > 0) {
593 if (mainWin->m_displayAll) display_text(msg());
596 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
601 * Blocking read from director
608 stat = bnet_wait_data_intr(m_sock, 1);
612 app->processEvents();
613 if (m_api_set && m_messages_pending) {
614 write_dir(".messages");
615 m_messages_pending = false;
619 stat = m_sock->recv();
621 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
625 m_at_main_prompt = false;
628 switch (m_sock->msglen) {
629 case BNET_MSGS_PENDING:
630 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
631 write_dir(".messages");
633 m_messages_pending = false;
636 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
638 m_at_main_prompt = false;
639 mainWin->set_status(_("Command completed ..."));
642 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
644 m_at_main_prompt = false;
645 mainWin->set_status(_("Processing command ..."));
647 case BNET_MAIN_PROMPT:
648 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
650 m_at_main_prompt = true;
651 mainWin->set_status(_("At main prompt waiting for input ..."));
652 QApplication::restoreOverrideCursor();
655 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
657 m_at_main_prompt = false;
658 mainWin->set_status(_("At prompt waiting for input ..."));
659 QApplication::restoreOverrideCursor();
661 case BNET_CMD_FAILED:
662 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
663 mainWin->set_status(_("Command failed."));
664 QApplication::restoreOverrideCursor();
666 /* We should not get this one */
668 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
669 mainWin->set_status_ready();
670 QApplication::restoreOverrideCursor();
675 case BNET_START_SELECT:
676 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
677 new selectDialog(this);
680 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
684 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
685 m_sock->recv(); /* get the message */
687 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
689 case BNET_WARNING_MSG:
690 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
691 m_sock->recv(); /* get the message */
693 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
696 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
697 m_sock->recv(); /* get the message */
699 mainWin->set_status(msg());
702 if (is_bnet_stop(m_sock)) { /* error or term request */
703 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
707 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
708 QBrush redBrush(Qt::red);
709 QTreeWidgetItem *item = mainWin->getFromHash(this);
710 item->setForeground(0, redBrush);
711 m_notifier->setEnabled(false);
714 mainWin->set_status(_("Director disconnected."));
715 QApplication::restoreOverrideCursor();
723 /* Called by signal when the Director has output for us */
724 void Console::read_dir(int /* fd */)
726 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
727 while (read() >= 0) {
733 * When the notifier is enabled, read_dir() will automatically be
734 * called by the Qt event loop when ever there is any output
735 * from the Directory, and read_dir() will then display it on
738 * When we are in a bat dialog, we want to control *all* output
739 * from the Directory, so we set notify to off.
740 * m_console->notifiy(false);
742 void Console::notify(bool enable)
744 m_notifier->setEnabled(enable);
747 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
749 m_directorTreeItem = item;
752 void Console::setDirRes(DIRRES *dir)
758 * To have the ability to get the name of the director resource.
760 void Console::getDirResName(QString &name_returned)
762 name_returned = m_dir->name();
765 bool Console::is_connectedGui()
767 if (is_connected()) {
770 QString message("Director ");
771 message += " is currently disconnected\n Please reconnect!!";
772 QMessageBox::warning(this, "Bat",
773 tr(message.toUtf8().data()), QMessageBox::Ok );
779 * A temporary function to prevent connecting to the director if the director
780 * is busy with a restore.
782 bool Console::preventInUseConnect()
784 if (!is_connected()) {
785 QString message("Director ");
786 message += m_dir->name();
787 message += " is currently disconnected\n Please reconnect!!";
788 QMessageBox::warning(this, "Bat",
789 tr(message.toUtf8().data()), QMessageBox::Ok );
791 } else if (!m_at_main_prompt){
792 QString message("Director ");
793 message += m_dir->name();
794 message += " is currently busy\n Please complete restore or other "
795 " operation !! This is a limitation that will be resolved before a beta"
796 " release. This is currently an alpha release.";
797 QMessageBox::warning(this, "Bat",
798 tr(message.toUtf8().data()), QMessageBox::Ok );
800 } else if (!m_at_prompt){
801 QString message("Director ");
802 message += m_dir->name();
803 message += " is currently not at a prompt\n Please try again!!";
804 QMessageBox::warning(this, "Bat",
805 tr(message.toUtf8().data()), QMessageBox::Ok );
813 * Call-back for reading a passphrase for an encrypted PEM file
814 * This function uses getpass(),
815 * which uses a static buffer and is NOT thread-safe.
817 static int tls_pem_callback(char *buf, int size, const void *userdata)
822 const char *prompt = (const char *)userdata;
823 # if defined(HAVE_WIN32)
825 if (win32_cgets(buf, size) == NULL) {
834 passwd = getpass(prompt);
835 bstrncpy(buf, passwd, size);
844 /* Slot for responding to page selectors status help command */
845 void Console::consoleHelp()
851 /* Slot for responding to page selectors reload bacula-dir.conf */
852 void Console::consoleReload()
854 QString cmd("reload");