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.
108 void Console::connect_dir()
114 m_textEdit = textEdit; /* our console screen */
117 mainWin->set_status("No Director found.");
121 mainWin->set_status("Already connected.");
125 memset(&jcr, 0, sizeof(jcr));
127 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
128 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
130 /* Give GUI a chance */
131 app->processEvents();
134 /* If cons==NULL, default console will be used */
135 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
138 /* Initialize Console TLS context once */
139 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
140 /* Generate passphrase prompt */
141 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
144 /* Initialize TLS context:
145 * Args: CA certfile, CA certdir, Certfile, Keyfile,
146 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
148 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
149 cons->tls_ca_certdir, cons->tls_certfile,
150 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
152 if (!cons->tls_ctx) {
153 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
159 /* Initialize Director TLS context once */
160 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
161 /* Generate passphrase prompt */
162 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
165 /* Initialize TLS context:
166 * Args: CA certfile, CA certdir, Certfile, Keyfile,
167 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
168 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
169 m_dir->tls_ca_certdir, m_dir->tls_certfile,
170 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
172 if (!m_dir->tls_ctx) {
173 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
175 mainWin->set_status("Connection failed");
180 if (m_dir->heartbeat_interval) {
181 heart_beat = m_dir->heartbeat_interval;
183 heart_beat = cons->heartbeat_interval;
188 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
189 _("Director daemon"), m_dir->address,
190 NULL, m_dir->DIRport, 0);
191 if (m_sock == NULL) {
192 mainWin->set_status("Connection failed");
195 /* Update page selector to green to indicate that Console is connected */
196 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
197 QBrush greenBrush(Qt::green);
198 QTreeWidgetItem *item = mainWin->getFromHash(this);
199 item->setForeground(0, greenBrush);
202 jcr.dir_bsock = m_sock;
204 if (!authenticate_director(&jcr, m_dir, cons, buf, sizeof(buf))) {
212 /* Give GUI a chance */
213 app->processEvents();
215 mainWin->set_status(_("Initializing ..."));
217 /* Set up input notifier */
218 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
219 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
225 dir_cmd(".jobs", job_list);
226 dir_cmd(".clients", client_list);
227 dir_cmd(".filesets", fileset_list);
228 dir_cmd(".msgs", messages_list);
229 dir_cmd(".pools", pool_list);
230 dir_cmd(".storage", storage_list);
231 dir_cmd(".types", type_list);
232 dir_cmd(".levels", level_list);
234 mainWin->set_status(_("Connected"));
235 startTimer(); /* start message timer */
239 bool Console::dir_cmd(QString &cmd, QStringList &results)
241 return dir_cmd(cmd.toUtf8().data(), results);
245 * Send a command to the Director, and return the
246 * results in a QStringList.
248 bool Console::dir_cmd(const char *cmd, QStringList &results)
254 while ((stat = read()) > 0) {
255 if (mainWin->m_displayAll) display_text(msg());
256 strip_trailing_junk(msg());
261 return true; /* ***FIXME*** return any command error */
264 bool Console::sql_cmd(QString &query, QStringList &results)
266 return sql_cmd(query.toUtf8().data(), results);
270 * Send an sql query to the Director, and return the
271 * results in a QStringList.
273 bool Console::sql_cmd(const char *query, QStringList &results)
275 if (!is_connectedGui())
278 POOL_MEM cmd(PM_MESSAGE);
282 pm_strcpy(cmd, ".sql query=\"");
283 pm_strcat(cmd, query);
284 pm_strcat(cmd, "\"");
286 while ((stat = read()) > 0) {
287 if (mainWin->m_displayAll) display_text(msg());
288 strip_trailing_junk(msg());
293 return true; /* ***FIXME*** return any command error */
298 * Send a job name to the director, and read all the resulting
301 bool Console::get_job_defaults(struct job_defaults &job_defs)
309 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
311 while ((stat = read()) > 0) {
312 if (mainWin->m_displayAll) display_text(msg());
313 def = strchr(msg(), '=');
317 /* Pointer to default value */
319 strip_trailing_junk(def);
321 if (strcmp(msg(), "job") == 0) {
322 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
327 if (strcmp(msg(), "pool") == 0) {
328 job_defs.pool_name = def;
331 if (strcmp(msg(), "messages") == 0) {
332 job_defs.messages_name = def;
335 if (strcmp(msg(), "client") == 0) {
336 job_defs.client_name = def;
339 if (strcmp(msg(), "storage") == 0) {
340 job_defs.store_name = def;
343 if (strcmp(msg(), "where") == 0) {
344 job_defs.where = def;
347 if (strcmp(msg(), "level") == 0) {
348 job_defs.level = def;
351 if (strcmp(msg(), "type") == 0) {
355 if (strcmp(msg(), "fileset") == 0) {
356 job_defs.fileset_name = def;
359 if (strcmp(msg(), "catalog") == 0) {
360 job_defs.catalog_name = def;
363 if (strcmp(msg(), "enabled") == 0) {
364 job_defs.enabled = *def == '1' ? true : false;
370 bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
371 "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
372 job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(),
373 job_defs.client_name.toUtf8().data(),
374 job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(),
375 job_defs.store_name.toUtf8().data(),
376 job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(),
377 job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
378 job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
391 * Save user settings associated with this console
393 void Console::writeSettings()
395 QFont font = get_font();
397 QSettings settings(m_dir->name(), "bat");
398 settings.beginGroup("Console");
399 settings.setValue("consoleFont", font.family());
400 settings.setValue("consolePointSize", font.pointSize());
401 settings.setValue("consoleFixedPitch", font.fixedPitch());
406 * Read and restore user settings associated with this console
408 void Console::readSettings()
410 QFont font = get_font();
412 QSettings settings(m_dir->name(), "bat");
413 settings.beginGroup("Console");
414 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
415 font.setPointSize(settings.value("consolePointSize", 10).toInt());
416 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
418 m_textEdit->setFont(font);
422 * Set the console textEdit font
424 void Console::set_font()
427 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
429 m_textEdit->setFont(font);
434 * Get the console text edit font
436 const QFont Console::get_font()
438 return m_textEdit->font();
442 * Slot for responding to status dir button on button bar
444 void Console::status_dir()
446 QString cmd("status dir");
451 * Slot for responding to messages button on button bar
453 void Console::messages()
455 QString cmd(".messages");
460 * Put text into the console window
462 void Console::display_textf(const char *fmt, ...)
467 va_start(arg_ptr, fmt);
468 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
473 void Console::display_text(const QString buf)
475 m_cursor->insertText(buf);
480 void Console::display_text(const char *buf)
482 m_cursor->insertText(buf);
486 void Console::display_html(const QString buf)
488 m_cursor->insertHtml(buf);
492 /* Position cursor to end of screen */
493 void Console::update_cursor()
495 QApplication::restoreOverrideCursor();
496 m_textEdit->moveCursor(QTextCursor::End);
497 m_textEdit->ensureCursorVisible();
501 * This should be moved into a bSocket class
511 /* Send a command to the Director */
512 void Console::write_dir(const char *msg)
515 mainWin->set_status(_("Processing command ..."));
516 QApplication::setOverrideCursor(Qt::WaitCursor);
519 mainWin->set_status(" Director not connected. Click on connect button.");
520 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
521 QBrush redBrush(Qt::red);
522 QTreeWidgetItem *item = mainWin->getFromHash(this);
523 item->setForeground(0, redBrush);
525 m_at_main_prompt = false;
529 int Console::write(const QString msg)
531 return write(msg.toUtf8().data());
534 int Console::write(const char *msg)
539 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
541 m_at_main_prompt = false;
542 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
543 return m_sock->send();
548 * Get to main command prompt -- i.e. abort any subcommand
550 void Console::beginNewCommand()
552 for (int i=0; i < 3; i++) {
555 if (mainWin->m_displayAll) display_text(msg());
557 if (m_at_main_prompt) {
564 void Console::displayToPrompt()
567 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
568 while (!m_at_prompt) {
569 if ((stat=read()) > 0) {
573 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
576 void Console::discardToPrompt()
579 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
580 while (!m_at_prompt) {
581 if ((stat=read()) > 0) {
582 if (mainWin->m_displayAll) display_text(msg());
585 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
590 * Blocking read from director
597 stat = bnet_wait_data_intr(m_sock, 1);
601 app->processEvents();
602 if (m_api_set && m_messages_pending) {
603 write_dir(".messages");
604 m_messages_pending = false;
607 stat = m_sock->recv();
612 m_at_main_prompt = false;
614 if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
616 switch (m_sock->msglen) {
617 case BNET_MSGS_PENDING:
618 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
619 write_dir(".messages");
621 m_messages_pending = false;
624 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
626 m_at_main_prompt = false;
629 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
631 m_at_main_prompt = false;
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 curerntly disconnected\n Please reconnect!!";
758 QMessageBox::warning(this, tr("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 curerntly disconnected\n Please reconnect!!";
774 QMessageBox::warning(this, tr("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 curerntly 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, tr("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 curerntly not at a prompt\n Please try again!!";
790 QMessageBox::warning(this, tr("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);