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"));
69 void Console::startTimer()
71 m_timer = new QTimer(this);
72 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
73 m_timer->start(mainWin->m_checkMessagesInterval*1000);
76 void Console::stopTimer()
79 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
86 void Console::poll_messages()
88 m_messages_pending = true;
89 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
95 /* Terminate any open socket */
96 void Console::terminate()
106 * Connect to Director. If there are more than one, put up
107 * a modal dialog so that the user chooses one.
109 void Console::connect()
115 m_textEdit = textEdit; /* our console screen */
118 mainWin->set_status("No Director found.");
122 mainWin->set_status("Already connected.");
126 memset(&jcr, 0, sizeof(jcr));
128 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
129 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
131 /* Give GUI a chance */
132 app->processEvents();
135 /* If cons==NULL, default console will be used */
136 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
139 /* Initialize Console TLS context once */
140 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
141 /* Generate passphrase prompt */
142 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
145 /* Initialize TLS context:
146 * Args: CA certfile, CA certdir, Certfile, Keyfile,
147 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
149 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
150 cons->tls_ca_certdir, cons->tls_certfile,
151 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
153 if (!cons->tls_ctx) {
154 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
160 /* Initialize Director TLS context once */
161 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
162 /* Generate passphrase prompt */
163 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
166 /* Initialize TLS context:
167 * Args: CA certfile, CA certdir, Certfile, Keyfile,
168 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
169 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
170 m_dir->tls_ca_certdir, m_dir->tls_certfile,
171 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
173 if (!m_dir->tls_ctx) {
174 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
176 mainWin->set_status("Connection failed");
181 if (m_dir->heartbeat_interval) {
182 heart_beat = m_dir->heartbeat_interval;
184 heart_beat = cons->heartbeat_interval;
189 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
190 _("Director daemon"), m_dir->address,
191 NULL, m_dir->DIRport, 0);
192 if (m_sock == NULL) {
193 mainWin->set_status("Connection failed");
196 /* Update page selector to green to indicate that Console is connected */
197 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
198 QBrush greenBrush(Qt::green);
199 QTreeWidgetItem *item = mainWin->getFromHash(this);
200 item->setForeground(0, greenBrush);
203 jcr.dir_bsock = m_sock;
205 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
213 /* Give GUI a chance */
214 app->processEvents();
216 mainWin->set_status(_("Initializing ..."));
218 /* Set up input notifier */
219 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
220 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
226 dir_cmd(".jobs", job_list);
227 dir_cmd(".clients", client_list);
228 dir_cmd(".filesets", fileset_list);
229 dir_cmd(".msgs", messages_list);
230 dir_cmd(".pools", pool_list);
231 dir_cmd(".storage", storage_list);
232 dir_cmd(".types", type_list);
233 dir_cmd(".levels", level_list);
235 mainWin->set_status(_("Connected"));
240 bool Console::dir_cmd(QString &cmd, QStringList &results)
242 return dir_cmd(cmd.toUtf8().data(), results);
246 * Send a command to the Director, and return the
247 * results in a QStringList.
249 bool Console::dir_cmd(const char *cmd, QStringList &results)
255 while ((stat = read()) > 0) {
256 if (mainWin->m_displayAll) display_text(msg());
257 strip_trailing_junk(msg());
262 return true; /* ***FIXME*** return any command error */
265 bool Console::sql_cmd(QString &query, QStringList &results)
267 return sql_cmd(query.toUtf8().data(), results);
271 * Send an sql query to the Director, and return the
272 * results in a QStringList.
274 bool Console::sql_cmd(const char *query, QStringList &results)
276 if (!is_connectedGui())
279 POOL_MEM cmd(PM_MESSAGE);
283 pm_strcpy(cmd, ".sql query=\"");
284 pm_strcat(cmd, query);
285 pm_strcat(cmd, "\"");
287 while ((stat = read()) > 0) {
288 if (mainWin->m_displayAll) display_text(msg());
289 strip_trailing_junk(msg());
294 return true; /* ***FIXME*** return any command error */
299 * Send a job name to the director, and read all the resulting
302 bool Console::get_job_defaults(struct job_defaults &job_defs)
310 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
312 while ((stat = read()) > 0) {
313 if (mainWin->m_displayAll) display_text(msg());
314 def = strchr(msg(), '=');
318 /* Pointer to default value */
320 strip_trailing_junk(def);
322 if (strcmp(msg(), "job") == 0) {
323 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
328 if (strcmp(msg(), "pool") == 0) {
329 job_defs.pool_name = def;
332 if (strcmp(msg(), "messages") == 0) {
333 job_defs.messages_name = def;
336 if (strcmp(msg(), "client") == 0) {
337 job_defs.client_name = def;
340 if (strcmp(msg(), "storage") == 0) {
341 job_defs.store_name = def;
344 if (strcmp(msg(), "where") == 0) {
345 job_defs.where = def;
348 if (strcmp(msg(), "level") == 0) {
349 job_defs.level = def;
352 if (strcmp(msg(), "type") == 0) {
356 if (strcmp(msg(), "fileset") == 0) {
357 job_defs.fileset_name = def;
360 if (strcmp(msg(), "catalog") == 0) {
361 job_defs.catalog_name = def;
364 if (strcmp(msg(), "enabled") == 0) {
365 job_defs.enabled = *def == '1' ? true : false;
371 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
372 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
373 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
374 job_defs.client_name.toUtf8().data(),
375 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
376 job_defs.store_name.toUtf8().data(),
377 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
378 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
379 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
392 * Save user settings associated with this console
394 void Console::writeSettings()
396 QFont font = get_font();
398 QSettings settings(m_dir->name(), "bat");
399 settings.beginGroup("Console");
400 settings.setValue("consoleFont", font.family());
401 settings.setValue("consolePointSize", font.pointSize());
402 settings.setValue("consoleFixedPitch", font.fixedPitch());
407 * Read and restore user settings associated with this console
409 void Console::readSettings()
411 QFont font = get_font();
413 QSettings settings(m_dir->name(), "bat");
414 settings.beginGroup("Console");
415 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
416 font.setPointSize(settings.value("consolePointSize", 10).toInt());
417 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
419 m_textEdit->setFont(font);
423 * Set the console textEdit font
425 void Console::set_font()
428 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
430 m_textEdit->setFont(font);
435 * Get the console text edit font
437 const QFont Console::get_font()
439 return m_textEdit->font();
443 * Slot for responding to status dir button on button bar
445 void Console::status_dir()
447 QString cmd("status dir");
452 * Slot for responding to messages button on button bar
454 void Console::messages()
456 QString cmd(".messages");
461 * Put text into the console window
463 void Console::display_textf(const char *fmt, ...)
468 va_start(arg_ptr, fmt);
469 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
474 void Console::display_text(const QString buf)
476 m_cursor->insertText(buf);
481 void Console::display_text(const char *buf)
483 m_cursor->insertText(buf);
487 void Console::display_html(const QString buf)
489 m_cursor->insertHtml(buf);
493 /* Position cursor to end of screen */
494 void Console::update_cursor()
496 QApplication::restoreOverrideCursor();
497 m_textEdit->moveCursor(QTextCursor::End);
498 m_textEdit->ensureCursorVisible();
502 * This should be moved into a bSocket class
512 /* Send a command to the Director */
513 void Console::write_dir(const char *msg)
516 mainWin->set_status(_("Processing command ..."));
517 QApplication::setOverrideCursor(Qt::WaitCursor);
520 mainWin->set_status(" Director not connected. Click on connect button.");
521 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
522 QBrush redBrush(Qt::red);
523 QTreeWidgetItem *item = mainWin->getFromHash(this);
524 item->setForeground(0, redBrush);
526 m_at_main_prompt = false;
530 int Console::write(const QString msg)
532 return write(msg.toUtf8().data());
535 int Console::write(const char *msg)
540 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
542 m_at_main_prompt = false;
543 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
544 return m_sock->send();
549 * Get to main command prompt -- i.e. abort any subcommand
551 void Console::beginNewCommand()
553 for (int i=0; i < 3; i++) {
556 if (mainWin->m_displayAll) display_text(msg());
558 if (m_at_main_prompt) {
565 void Console::displayToPrompt()
568 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
569 while (!m_at_prompt) {
570 if ((stat=read()) > 0) {
574 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
577 void Console::discardToPrompt()
580 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
581 while (!m_at_prompt) {
582 if ((stat=read()) > 0) {
583 if (mainWin->m_displayAll) display_text(msg());
586 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
591 * Blocking read from director
598 stat = bnet_wait_data_intr(m_sock, 1);
602 app->processEvents();
603 if (m_api_set && m_messages_pending) {
604 write_dir(".messages");
605 m_messages_pending = false;
608 stat = m_sock->recv();
613 m_at_main_prompt = false;
615 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
617 switch (m_sock->msglen) {
618 case BNET_MSGS_PENDING:
619 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
620 write_dir(".messages");
622 m_messages_pending = false;
625 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
627 m_at_main_prompt = false;
630 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
632 m_at_main_prompt = false;
634 case BNET_MAIN_PROMPT:
635 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
637 m_at_main_prompt = true;
638 mainWin->set_status(_("At main prompt waiting for input ..."));
639 QApplication::restoreOverrideCursor();
642 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
644 m_at_main_prompt = false;
645 mainWin->set_status(_("At prompt waiting for input ..."));
646 QApplication::restoreOverrideCursor();
648 case BNET_CMD_FAILED:
649 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
650 mainWin->set_status(_("Command failed."));
651 QApplication::restoreOverrideCursor();
653 /* We should not get this one */
655 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
656 mainWin->set_status_ready();
657 QApplication::restoreOverrideCursor();
662 case BNET_START_SELECT:
663 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
664 new selectDialog(this);
667 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
671 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
672 m_sock->recv(); /* get the message */
674 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
676 case BNET_WARNING_MSG:
677 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
678 m_sock->recv(); /* get the message */
680 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
683 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
684 m_sock->recv(); /* get the message */
686 mainWin->set_status(msg());
689 if (is_bnet_stop(m_sock)) { /* error or term request */
690 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
694 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
695 QBrush redBrush(Qt::red);
696 QTreeWidgetItem *item = mainWin->getFromHash(this);
697 item->setForeground(0, redBrush);
698 m_notifier->setEnabled(false);
701 mainWin->set_status(_("Director disconnected."));
702 QApplication::restoreOverrideCursor();
710 /* Called by signal when the Director has output for us */
711 void Console::read_dir(int /* fd */)
713 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
714 while (read() >= 0) {
720 * When the notifier is enabled, read_dir() will automatically be
721 * called by the Qt event loop when ever there is any output
722 * from the Directory, and read_dir() will then display it on
725 * When we are in a bat dialog, we want to control *all* output
726 * from the Directory, so we set notify to off.
727 * m_console->notifiy(false);
729 void Console::notify(bool enable)
731 m_notifier->setEnabled(enable);
734 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
736 m_directorTreeItem = item;
739 void Console::setDirRes(DIRRES *dir)
745 * To have the ability to get the name of the director resource.
747 void Console::getDirResName(QString &name_returned)
749 name_returned = m_dir->name();
752 bool Console::is_connectedGui()
754 if (is_connected()) {
757 QString message("Director ");
758 message += " is curerntly disconnected\n Please reconnect!!";
759 QMessageBox::warning(this, tr("Bat"),
760 tr(message.toUtf8().data()), QMessageBox::Ok );
766 * A temporary function to prevent connecting to the director if the director
767 * is busy with a restore.
769 bool Console::preventInUseConnect()
771 if (!is_connected()) {
772 QString message("Director ");
773 message += m_dir->name();
774 message += " is curerntly disconnected\n Please reconnect!!";
775 QMessageBox::warning(this, tr("Bat"),
776 tr(message.toUtf8().data()), QMessageBox::Ok );
778 } else if (!m_at_main_prompt){
779 QString message("Director ");
780 message += m_dir->name();
781 message += " is curerntly busy\n Please complete restore or other "
782 " operation !! This is a limitation that will be resolved before a beta"
783 " release. This is currently an alpha release.";
784 QMessageBox::warning(this, tr("Bat"),
785 tr(message.toUtf8().data()), QMessageBox::Ok );
787 } else if (!m_at_prompt){
788 QString message("Director ");
789 message += m_dir->name();
790 message += " is curerntly not at a prompt\n Please try again!!";
791 QMessageBox::warning(this, tr("Bat"),
792 tr(message.toUtf8().data()), QMessageBox::Ok );
800 * Call-back for reading a passphrase for an encrypted PEM file
801 * This function uses getpass(),
802 * which uses a static buffer and is NOT thread-safe.
804 static int tls_pem_callback(char *buf, int size, const void *userdata)
807 const char *prompt = (const char *)userdata;
808 # if defined(HAVE_WIN32)
810 if (win32_cgets(buf, size) == NULL) {
819 passwd = getpass(prompt);
820 bstrncpy(buf, passwd, size);