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()
125 m_textEdit = textEdit; /* our console screen */
128 mainWin->set_status("No Director found.");
132 mainWin->set_status("Already connected.");
136 memset(jcr, 0, sizeof(JCR));
138 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
139 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
141 /* Give GUI a chance */
142 app->processEvents();
145 /* If cons==NULL, default console will be used */
146 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
149 /* Initialize Console TLS context once */
150 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
151 /* Generate passphrase prompt */
152 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
155 /* Initialize TLS context:
156 * Args: CA certfile, CA certdir, Certfile, Keyfile,
157 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
159 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
160 cons->tls_ca_certdir, cons->tls_certfile,
161 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
163 if (!cons->tls_ctx) {
164 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
170 /* Initialize Director TLS context once */
171 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
172 /* Generate passphrase prompt */
173 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
176 /* Initialize TLS context:
177 * Args: CA certfile, CA certdir, Certfile, Keyfile,
178 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
179 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
180 m_dir->tls_ca_certdir, m_dir->tls_certfile,
181 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
183 if (!m_dir->tls_ctx) {
184 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
186 mainWin->set_status("Connection failed");
191 if (m_dir->heartbeat_interval) {
192 heart_beat = m_dir->heartbeat_interval;
194 heart_beat = cons->heartbeat_interval;
199 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
200 _("Director daemon"), m_dir->address,
201 NULL, m_dir->DIRport, 0);
202 if (m_sock == NULL) {
203 mainWin->set_status("Connection failed");
206 /* Update page selector to green to indicate that Console is connected */
207 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
208 QBrush greenBrush(Qt::green);
209 QTreeWidgetItem *item = mainWin->getFromHash(this);
210 item->setForeground(0, greenBrush);
213 jcr->dir_bsock = m_sock;
215 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
224 /* Give GUI a chance */
225 app->processEvents();
227 mainWin->set_status(_("Initializing ..."));
229 /* Set up input notifier */
230 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
231 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
239 fileset_list.clear();
240 fileset_list.clear();
241 messages_list.clear();
243 storage_list.clear();
246 dir_cmd(".jobs", job_list);
247 dir_cmd(".clients", client_list);
248 dir_cmd(".filesets", fileset_list);
249 dir_cmd(".msgs", messages_list);
250 dir_cmd(".pools", pool_list);
251 dir_cmd(".storage", storage_list);
252 dir_cmd(".types", type_list);
253 dir_cmd(".levels", level_list);
255 mainWin->set_status(_("Connected"));
256 startTimer(); /* start message timer */
263 bool Console::dir_cmd(QString &cmd, QStringList &results)
265 return dir_cmd(cmd.toUtf8().data(), results);
269 * Send a command to the Director, and return the
270 * results in a QStringList.
272 bool Console::dir_cmd(const char *cmd, QStringList &results)
278 while ((stat = read()) > 0) {
279 if (mainWin->m_displayAll) display_text(msg());
280 strip_trailing_junk(msg());
285 return true; /* ***FIXME*** return any command error */
288 bool Console::sql_cmd(QString &query, QStringList &results)
290 return sql_cmd(query.toUtf8().data(), results);
294 * Send an sql query to the Director, and return the
295 * results in a QStringList.
297 bool Console::sql_cmd(const char *query, QStringList &results)
300 POOL_MEM cmd(PM_MESSAGE);
302 if (!is_connectedGui()) {
308 pm_strcpy(cmd, ".sql query=\"");
309 pm_strcat(cmd, query);
310 pm_strcat(cmd, "\"");
312 while ((stat = read()) > 0) {
313 if (mainWin->m_displayAll) display_text(msg());
314 strip_trailing_junk(msg());
319 return true; /* ***FIXME*** return any command error */
324 * Send a job name to the director, and read all the resulting
327 bool Console::get_job_defaults(struct job_defaults &job_defs)
335 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
337 while ((stat = read()) > 0) {
338 if (mainWin->m_displayAll) display_text(msg());
339 def = strchr(msg(), '=');
343 /* Pointer to default value */
345 strip_trailing_junk(def);
347 if (strcmp(msg(), "job") == 0) {
348 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
353 if (strcmp(msg(), "pool") == 0) {
354 job_defs.pool_name = def;
357 if (strcmp(msg(), "messages") == 0) {
358 job_defs.messages_name = def;
361 if (strcmp(msg(), "client") == 0) {
362 job_defs.client_name = def;
365 if (strcmp(msg(), "storage") == 0) {
366 job_defs.store_name = def;
369 if (strcmp(msg(), "where") == 0) {
370 job_defs.where = def;
373 if (strcmp(msg(), "level") == 0) {
374 job_defs.level = def;
377 if (strcmp(msg(), "type") == 0) {
381 if (strcmp(msg(), "fileset") == 0) {
382 job_defs.fileset_name = def;
385 if (strcmp(msg(), "catalog") == 0) {
386 job_defs.catalog_name = def;
389 if (strcmp(msg(), "enabled") == 0) {
390 job_defs.enabled = *def == '1' ? true : false;
405 * Save user settings associated with this console
407 void Console::writeSettings()
409 QFont font = get_font();
411 QSettings settings(m_dir->name(), "bat");
412 settings.beginGroup("Console");
413 settings.setValue("consoleFont", font.family());
414 settings.setValue("consolePointSize", font.pointSize());
415 settings.setValue("consoleFixedPitch", font.fixedPitch());
420 * Read and restore user settings associated with this console
422 void Console::readSettings()
424 QFont font = get_font();
426 QSettings settings(m_dir->name(), "bat");
427 settings.beginGroup("Console");
428 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
429 font.setPointSize(settings.value("consolePointSize", 10).toInt());
430 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
432 m_textEdit->setFont(font);
436 * Set the console textEdit font
438 void Console::set_font()
441 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
443 m_textEdit->setFont(font);
448 * Get the console text edit font
450 const QFont Console::get_font()
452 return m_textEdit->font();
456 * Slot for responding to status dir button on button bar
458 void Console::status_dir()
460 QString cmd("status dir");
465 * Slot for responding to messages button on button bar
467 void Console::messages()
469 QString cmd(".messages");
474 * Put text into the console window
476 void Console::display_textf(const char *fmt, ...)
481 va_start(arg_ptr, fmt);
482 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
487 void Console::display_text(const QString buf)
489 m_cursor->insertText(buf);
494 void Console::display_text(const char *buf)
496 m_cursor->insertText(buf);
500 void Console::display_html(const QString buf)
502 m_cursor->insertHtml(buf);
506 /* Position cursor to end of screen */
507 void Console::update_cursor()
509 QApplication::restoreOverrideCursor();
510 m_textEdit->moveCursor(QTextCursor::End);
511 m_textEdit->ensureCursorVisible();
515 * This should be moved into a bSocket class
525 /* Send a command to the Director */
526 void Console::write_dir(const char *msg)
529 mainWin->set_status(_("Processing command ..."));
530 QApplication::setOverrideCursor(Qt::WaitCursor);
533 mainWin->set_status(" Director not connected. Click on connect button.");
534 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
535 QBrush redBrush(Qt::red);
536 QTreeWidgetItem *item = mainWin->getFromHash(this);
537 item->setForeground(0, redBrush);
539 m_at_main_prompt = false;
543 int Console::write(const QString msg)
545 return write(msg.toUtf8().data());
548 int Console::write(const char *msg)
553 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
555 m_at_main_prompt = false;
556 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
557 return m_sock->send();
562 * Get to main command prompt -- i.e. abort any subcommand
564 void Console::beginNewCommand()
566 for (int i=0; i < 3; i++) {
569 if (mainWin->m_displayAll) display_text(msg());
571 if (m_at_main_prompt) {
578 void Console::displayToPrompt()
581 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
582 while (!m_at_prompt) {
583 if ((stat=read()) > 0) {
587 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
590 void Console::discardToPrompt()
593 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
594 while (!m_at_prompt) {
595 if ((stat=read()) > 0) {
596 if (mainWin->m_displayAll) display_text(msg());
599 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
604 * Blocking read from director
611 stat = bnet_wait_data_intr(m_sock, 1);
615 app->processEvents();
616 if (m_api_set && m_messages_pending) {
617 write_dir(".messages");
618 m_messages_pending = false;
622 stat = m_sock->recv();
624 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
628 m_at_main_prompt = false;
631 switch (m_sock->msglen) {
632 case BNET_MSGS_PENDING:
633 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
634 write_dir(".messages");
636 m_messages_pending = false;
639 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
641 m_at_main_prompt = false;
642 mainWin->set_status(_("Command completed ..."));
645 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
647 m_at_main_prompt = false;
648 mainWin->set_status(_("Processing command ..."));
650 case BNET_MAIN_PROMPT:
651 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
653 m_at_main_prompt = true;
654 mainWin->set_status(_("At main prompt waiting for input ..."));
655 QApplication::restoreOverrideCursor();
658 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
660 m_at_main_prompt = false;
661 mainWin->set_status(_("At prompt waiting for input ..."));
662 QApplication::restoreOverrideCursor();
664 case BNET_CMD_FAILED:
665 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
666 mainWin->set_status(_("Command failed."));
667 QApplication::restoreOverrideCursor();
669 /* We should not get this one */
671 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
672 mainWin->set_status_ready();
673 QApplication::restoreOverrideCursor();
678 case BNET_START_SELECT:
679 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
680 new selectDialog(this);
683 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
687 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
688 m_sock->recv(); /* get the message */
690 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
692 case BNET_WARNING_MSG:
693 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
694 m_sock->recv(); /* get the message */
696 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
699 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
700 m_sock->recv(); /* get the message */
702 mainWin->set_status(msg());
705 if (is_bnet_stop(m_sock)) { /* error or term request */
706 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
710 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
711 QBrush redBrush(Qt::red);
712 QTreeWidgetItem *item = mainWin->getFromHash(this);
713 item->setForeground(0, redBrush);
714 m_notifier->setEnabled(false);
717 mainWin->set_status(_("Director disconnected."));
718 QApplication::restoreOverrideCursor();
726 /* Called by signal when the Director has output for us */
727 void Console::read_dir(int /* fd */)
729 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
730 while (read() >= 0) {
736 * When the notifier is enabled, read_dir() will automatically be
737 * called by the Qt event loop when ever there is any output
738 * from the Directory, and read_dir() will then display it on
741 * When we are in a bat dialog, we want to control *all* output
742 * from the Directory, so we set notify to off.
743 * m_console->notifiy(false);
745 void Console::notify(bool enable)
747 m_notifier->setEnabled(enable);
750 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
752 m_directorTreeItem = item;
755 void Console::setDirRes(DIRRES *dir)
761 * To have the ability to get the name of the director resource.
763 void Console::getDirResName(QString &name_returned)
765 name_returned = m_dir->name();
768 bool Console::is_connectedGui()
770 if (is_connected()) {
773 QString message("Director ");
774 message += " is currently disconnected\n Please reconnect!!";
775 QMessageBox::warning(this, "Bat",
776 tr(message.toUtf8().data()), QMessageBox::Ok );
782 * A temporary function to prevent connecting to the director if the director
783 * is busy with a restore.
785 bool Console::preventInUseConnect()
787 if (!is_connected()) {
788 QString message("Director ");
789 message += m_dir->name();
790 message += " is currently disconnected\n Please reconnect!!";
791 QMessageBox::warning(this, "Bat",
792 tr(message.toUtf8().data()), QMessageBox::Ok );
794 } else if (!m_at_main_prompt){
795 QString message("Director ");
796 message += m_dir->name();
797 message += " is currently busy\n Please complete restore or other "
798 " operation !! This is a limitation that will be resolved before a beta"
799 " release. This is currently an alpha release.";
800 QMessageBox::warning(this, "Bat",
801 tr(message.toUtf8().data()), QMessageBox::Ok );
803 } else if (!m_at_prompt){
804 QString message("Director ");
805 message += m_dir->name();
806 message += " is currently not at a prompt\n Please try again!!";
807 QMessageBox::warning(this, "Bat",
808 tr(message.toUtf8().data()), QMessageBox::Ok );
816 * Call-back for reading a passphrase for an encrypted PEM file
817 * This function uses getpass(),
818 * which uses a static buffer and is NOT thread-safe.
820 static int tls_pem_callback(char *buf, int size, const void *userdata)
825 const char *prompt = (const char *)userdata;
826 # if defined(HAVE_WIN32)
828 if (win32_cgets(buf, size) == NULL) {
837 passwd = getpass(prompt);
838 bstrncpy(buf, passwd, size);
847 /* Slot for responding to page selectors status help command */
848 void Console::consoleHelp()
854 /* Slot for responding to page selectors reload bacula-dir.conf */
855 void Console::consoleReload()
857 QString cmd("reload");