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()
101 m_textEdit = textEdit; /* our console screen */
104 mainWin->set_status("No Director found.");
108 mainWin->set_status("Already connected.");
112 memset(&jcr, 0, sizeof(jcr));
114 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
115 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
117 /* Give GUI a chance */
118 app->processEvents();
121 /* If cons==NULL, default console will be used */
122 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)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: ", cons->hdr.name);
131 /* Initialize TLS context:
132 * Args: CA certfile, CA certdir, Certfile, Keyfile,
133 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
135 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
136 cons->tls_ca_certdir, cons->tls_certfile,
137 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
139 if (!cons->tls_ctx) {
140 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
146 /* Initialize Director TLS context */
147 if (m_dir->tls_enable || m_dir->tls_require) {
148 /* Generate passphrase prompt */
149 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%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 */
155 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
156 m_dir->tls_ca_certdir, m_dir->tls_certfile,
157 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
159 if (!m_dir->tls_ctx) {
160 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
162 mainWin->set_status("Connection failed");
167 if (m_dir->heartbeat_interval) {
168 heart_beat = m_dir->heartbeat_interval;
170 heart_beat = cons->heartbeat_interval;
175 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
176 _("Director daemon"), m_dir->address,
177 NULL, m_dir->DIRport, 0);
178 if (m_sock == NULL) {
179 mainWin->set_status("Connection failed");
182 /* Update page selector to green to indicate that Console is connected */
183 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
184 QBrush greenBrush(Qt::green);
185 QTreeWidgetItem *item = mainWin->getFromHash(this);
186 item->setForeground(0, greenBrush);
189 jcr.dir_bsock = m_sock;
191 if (!authenticate_director(&jcr, m_dir, cons)) {
192 display_text(m_sock->msg);
196 /* Give GUI a chance */
197 app->processEvents();
199 mainWin->set_status(_("Initializing ..."));
201 /* Set up input notifier */
202 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
203 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
209 dir_cmd(".jobs", job_list);
210 dir_cmd(".clients", client_list);
211 dir_cmd(".filesets", fileset_list);
212 dir_cmd(".msgs", messages_list);
213 dir_cmd(".pools", pool_list);
214 dir_cmd(".storage", storage_list);
215 dir_cmd(".types", type_list);
216 dir_cmd(".levels", level_list);
218 mainWin->set_status(_("Connected"));
222 bool Console::dir_cmd(QString &cmd, QStringList &results)
224 return dir_cmd(cmd.toUtf8().data(), results);
228 * Send a command to the Director, and return the
229 * results in a QStringList.
231 bool Console::dir_cmd(const char *cmd, QStringList &results)
237 while ((stat = read()) > 0) {
238 if (mainWin->m_displayAll) display_text(msg());
239 strip_trailing_junk(msg());
244 return true; /* ***FIXME*** return any command error */
247 bool Console::sql_cmd(QString &query, QStringList &results)
249 return sql_cmd(query.toUtf8().data(), results);
253 * Send an sql query to the Director, and return the
254 * results in a QStringList.
256 bool Console::sql_cmd(const char *query, QStringList &results)
258 if (!is_connectedGui())
261 POOL_MEM cmd(PM_MESSAGE);
265 pm_strcpy(cmd, ".sql query=\"");
266 pm_strcat(cmd, query);
267 pm_strcat(cmd, "\"");
269 while ((stat = read()) > 0) {
270 if (mainWin->m_displayAll) display_text(msg());
271 strip_trailing_junk(msg());
276 return true; /* ***FIXME*** return any command error */
281 * Send a job name to the director, and read all the resulting
284 bool Console::get_job_defaults(struct job_defaults &job_defs)
292 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
294 while ((stat = read()) > 0) {
295 if (mainWin->m_displayAll) display_text(msg());
296 def = strchr(msg(), '=');
300 /* Pointer to default value */
302 strip_trailing_junk(def);
304 if (strcmp(msg(), "job") == 0) {
305 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
310 if (strcmp(msg(), "pool") == 0) {
311 job_defs.pool_name = def;
314 if (strcmp(msg(), "messages") == 0) {
315 job_defs.messages_name = def;
318 if (strcmp(msg(), "client") == 0) {
319 job_defs.client_name = def;
322 if (strcmp(msg(), "storage") == 0) {
323 job_defs.store_name = def;
326 if (strcmp(msg(), "where") == 0) {
327 job_defs.where = def;
330 if (strcmp(msg(), "level") == 0) {
331 job_defs.level = def;
334 if (strcmp(msg(), "type") == 0) {
338 if (strcmp(msg(), "fileset") == 0) {
339 job_defs.fileset_name = def;
342 if (strcmp(msg(), "catalog") == 0) {
343 job_defs.catalog_name = def;
346 if (strcmp(msg(), "enabled") == 0) {
347 job_defs.enabled = *def == '1' ? true : false;
353 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
354 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
355 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
356 job_defs.client_name.toUtf8().data(),
357 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
358 job_defs.store_name.toUtf8().data(),
359 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
360 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
361 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
374 * Save user settings associated with this console
376 void Console::writeSettings()
378 QFont font = get_font();
380 QSettings settings(m_dir->name(), "bat");
381 settings.beginGroup("Console");
382 settings.setValue("consoleFont", font.family());
383 settings.setValue("consolePointSize", font.pointSize());
384 settings.setValue("consoleFixedPitch", font.fixedPitch());
389 * Read and restore user settings associated with this console
391 void Console::readSettings()
393 QFont font = get_font();
395 QSettings settings(m_dir->name(), "bat");
396 settings.beginGroup("Console");
397 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
398 font.setPointSize(settings.value("consolePointSize", 10).toInt());
399 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
401 m_textEdit->setFont(font);
405 * Set the console textEdit font
407 void Console::set_font()
410 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
412 m_textEdit->setFont(font);
417 * Get the console text edit font
419 const QFont Console::get_font()
421 return m_textEdit->font();
425 * Slot for responding to status dir button on button bar
427 void Console::status_dir()
429 QString cmd("status dir");
434 * Slot for responding to messages button on button bar
436 void Console::messages()
438 QString cmd(".messages");
443 * Put text into the console window
445 void Console::display_textf(const char *fmt, ...)
450 va_start(arg_ptr, fmt);
451 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
456 void Console::display_text(const QString buf)
458 m_cursor->insertText(buf);
463 void Console::display_text(const char *buf)
465 m_cursor->insertText(buf);
469 void Console::display_html(const QString buf)
471 m_cursor->insertHtml(buf);
475 /* Position cursor to end of screen */
476 void Console::update_cursor()
478 QApplication::restoreOverrideCursor();
479 m_textEdit->moveCursor(QTextCursor::End);
480 m_textEdit->ensureCursorVisible();
484 * This should be moved into a bSocket class
494 /* Send a command to the Director */
495 void Console::write_dir(const char *msg)
498 mainWin->set_status(_("Processing command ..."));
499 QApplication::setOverrideCursor(Qt::WaitCursor);
502 mainWin->set_status(" Director not connected. Click on connect button.");
503 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
504 QBrush redBrush(Qt::red);
505 QTreeWidgetItem *item = mainWin->getFromHash(this);
506 item->setForeground(0, redBrush);
508 m_at_main_prompt = false;
512 int Console::write(const QString msg)
514 return write(msg.toUtf8().data());
517 int Console::write(const char *msg)
519 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
521 m_at_main_prompt = false;
522 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
523 return m_sock->send();
527 * Get to main command prompt -- i.e. abort any subcommand
529 void Console::beginNewCommand()
531 for (int i=0; i < 3; i++) {
534 if (mainWin->m_displayAll) display_text(msg());
536 if (m_at_main_prompt) {
543 void Console::displayToPrompt()
546 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
547 while (!m_at_prompt) {
548 if ((stat=read()) > 0) {
552 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
555 void Console::discardToPrompt()
558 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
559 while (!m_at_prompt) {
560 if ((stat=read()) > 0) {
561 if (mainWin->m_displayAll) display_text(msg());
564 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
569 * Blocking read from director
576 stat = bnet_wait_data_intr(m_sock, 1);
580 app->processEvents();
581 if (m_api_set && m_messages_pending) {
582 write_dir(".messages");
583 m_messages_pending = false;
586 stat = m_sock->recv();
591 m_at_main_prompt = false;
593 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
595 switch (m_sock->msglen) {
596 case BNET_MSGS_PENDING:
597 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
598 write_dir(".messages");
600 m_messages_pending = false;
603 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
605 m_at_main_prompt = false;
608 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
610 m_at_main_prompt = false;
612 case BNET_MAIN_PROMPT:
613 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
615 m_at_main_prompt = true;
616 mainWin->set_status(_("At prompt waiting for input ..."));
617 QApplication::restoreOverrideCursor();
620 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
622 m_at_main_prompt = false;
623 mainWin->set_status(_("At prompt waiting for input ..."));
624 QApplication::restoreOverrideCursor();
626 case BNET_CMD_FAILED:
627 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
628 mainWin->set_status(_("Command failed. At prompt waiting for input ..."));
629 QApplication::restoreOverrideCursor();
631 /* We should not get this one */
633 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
634 mainWin->set_status_ready();
635 QApplication::restoreOverrideCursor();
640 case BNET_START_SELECT:
641 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
642 new selectDialog(this);
645 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
649 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
650 m_sock->recv(); /* get the message */
652 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
654 case BNET_WARNING_MSG:
655 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
656 m_sock->recv(); /* get the message */
658 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
661 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
662 m_sock->recv(); /* get the message */
664 mainWin->set_status(msg());
667 if (is_bnet_stop(m_sock)) { /* error or term request */
668 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
671 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
672 QBrush redBrush(Qt::red);
673 QTreeWidgetItem *item = mainWin->getFromHash(this);
674 item->setForeground(0, redBrush);
675 m_notifier->setEnabled(false);
678 mainWin->set_status(_("Director disconnected."));
679 QApplication::restoreOverrideCursor();
687 /* Called by signal when the Director has output for us */
688 void Console::read_dir(int /* fd */)
690 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
691 while (read() >= 0) {
697 * When the notifier is enabled, read_dir() will automatically be
698 * called by the Qt event loop when ever there is any output
699 * from the Directory, and read_dir() will then display it on
702 * When we are in a bat dialog, we want to control *all* output
703 * from the Directory, so we set notify to off.
704 * m_console->notifiy(false);
706 void Console::notify(bool enable)
708 m_notifier->setEnabled(enable);
711 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
713 m_directorTreeItem = item;
716 void Console::setDirRes(DIRRES *dir)
722 * To have the ability to get the name of the director resource.
724 void Console::getDirResName(QString &name_returned)
726 name_returned = m_dir->name();
729 bool Console::is_connectedGui()
731 if (is_connected()) {
734 QString message("Director ");
735 message += " is curerntly disconnected\n Please reconnect!!";
736 QMessageBox::warning(this, tr("Bat"),
737 tr(message.toUtf8().data()), QMessageBox::Ok );
743 * A temporary function to prevent connecting to the director if the director
744 * is busy with a restore.
746 bool Console::preventInUseConnect()
748 if (!is_connected()) {
749 QString message("Director ");
750 message += m_dir->name();
751 message += " is curerntly disconnected\n Please reconnect!!";
752 QMessageBox::warning(this, tr("Bat"),
753 tr(message.toUtf8().data()), QMessageBox::Ok );
755 } else if (!m_at_main_prompt){
756 QString message("Director ");
757 message += m_dir->name();
758 message += " is curerntly busy\n Please complete restore or other "
759 " operation !! This is a limitation that will be resolved before a beta"
760 " release. This is currently an alpa release.";
761 QMessageBox::warning(this, tr("Bat"),
762 tr(message.toUtf8().data()), QMessageBox::Ok );
764 } else if (!m_at_prompt){
765 QString message("Director ");
766 message += m_dir->name();
767 message += " is curerntly not at a prompt\n Please try again!!";
768 QMessageBox::warning(this, tr("Bat"),
769 tr(message.toUtf8().data()), QMessageBox::Ok );
777 * Call-back for reading a passphrase for an encrypted PEM file
778 * This function uses getpass(),
779 * which uses a static buffer and is NOT thread-safe.
781 static int tls_pem_callback(char *buf, int size, const void *userdata)
784 const char *prompt = (const char *)userdata;
785 # if defined(HAVE_WIN32)
787 if (win32_cgets(buf, size) == NULL) {
796 passwd = getpass(prompt);
797 bstrncpy(buf, passwd, size);