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 (m_notifier->isEnabled()) {
634 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
635 write_dir(".messages");
637 m_messages_pending = false;
639 m_messages_pending = true;
642 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
644 m_at_main_prompt = false;
645 mainWin->set_status(_("Command completed ..."));
648 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
650 m_at_main_prompt = false;
651 mainWin->set_status(_("Processing command ..."));
653 case BNET_MAIN_PROMPT:
654 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
656 m_at_main_prompt = true;
657 mainWin->set_status(_("At main prompt waiting for input ..."));
658 QApplication::restoreOverrideCursor();
661 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
663 m_at_main_prompt = false;
664 mainWin->set_status(_("At prompt waiting for input ..."));
665 QApplication::restoreOverrideCursor();
667 case BNET_CMD_FAILED:
668 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
669 mainWin->set_status(_("Command failed."));
670 QApplication::restoreOverrideCursor();
672 /* We should not get this one */
674 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
675 mainWin->set_status_ready();
676 QApplication::restoreOverrideCursor();
681 case BNET_START_SELECT:
682 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
683 new selectDialog(this);
686 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
687 new yesnoPopUp(this);
690 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
694 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
695 m_sock->recv(); /* get the message */
697 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
699 case BNET_WARNING_MSG:
700 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
701 m_sock->recv(); /* get the message */
703 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
706 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
707 m_sock->recv(); /* get the message */
709 mainWin->set_status(msg());
712 if (is_bnet_stop(m_sock)) { /* error or term request */
713 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
717 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
718 QBrush redBrush(Qt::red);
719 QTreeWidgetItem *item = mainWin->getFromHash(this);
720 item->setForeground(0, redBrush);
721 m_notifier->setEnabled(false);
724 mainWin->set_status(_("Director disconnected."));
725 QApplication::restoreOverrideCursor();
733 /* Called by signal when the Director has output for us */
734 void Console::read_dir(int /* fd */)
736 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
737 while (read() >= 0) {
743 * When the notifier is enabled, read_dir() will automatically be
744 * called by the Qt event loop when ever there is any output
745 * from the Directory, and read_dir() will then display it on
748 * When we are in a bat dialog, we want to control *all* output
749 * from the Directory, so we set notify to off.
750 * m_console->notifiy(false);
752 void Console::notify(bool enable)
754 m_notifier->setEnabled(enable);
757 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
759 m_directorTreeItem = item;
762 void Console::setDirRes(DIRRES *dir)
768 * To have the ability to get the name of the director resource.
770 void Console::getDirResName(QString &name_returned)
772 name_returned = m_dir->name();
775 bool Console::is_connectedGui()
777 if (is_connected()) {
780 QString message("Director ");
781 message += " is currently disconnected\n Please reconnect!!";
782 QMessageBox::warning(this, "Bat",
783 tr(message.toUtf8().data()), QMessageBox::Ok );
789 * A temporary function to prevent connecting to the director if the director
790 * is busy with a restore.
792 bool Console::preventInUseConnect()
794 if (!is_connected()) {
795 QString message("Director ");
796 message += m_dir->name();
797 message += " is currently disconnected\n Please reconnect!!";
798 QMessageBox::warning(this, "Bat",
799 tr(message.toUtf8().data()), QMessageBox::Ok );
801 } else if (!m_at_main_prompt){
802 QString message("Director ");
803 message += m_dir->name();
804 message += " is currently busy\n Please complete restore or other "
805 " operation !! This is a limitation that will be resolved before a beta"
806 " release. This is currently an alpha release.";
807 QMessageBox::warning(this, "Bat",
808 tr(message.toUtf8().data()), QMessageBox::Ok );
810 } else if (!m_at_prompt){
811 QString message("Director ");
812 message += m_dir->name();
813 message += " is currently not at a prompt\n Please try again!!";
814 QMessageBox::warning(this, "Bat",
815 tr(message.toUtf8().data()), QMessageBox::Ok );
823 * Call-back for reading a passphrase for an encrypted PEM file
824 * This function uses getpass(),
825 * which uses a static buffer and is NOT thread-safe.
827 static int tls_pem_callback(char *buf, int size, const void *userdata)
832 const char *prompt = (const char *)userdata;
833 # if defined(HAVE_WIN32)
835 if (win32_cgets(buf, size) == NULL) {
844 passwd = getpass(prompt);
845 bstrncpy(buf, passwd, size);
854 /* Slot for responding to page selectors status help command */
855 void Console::consoleHelp()
861 /* Slot for responding to page selectors reload bacula-dir.conf */
862 void Console::consoleReload()
864 QString cmd("reload");
868 /* Function to get a list of volumes */
869 void Console::getVolumeList(QStringList &volumeList)
871 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
872 if (mainWin->m_sqlDebug) {
873 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
876 if (sql_cmd(query, results)) {
878 QStringList fieldlist;
879 /* Iterate through the lines of results. */
880 foreach (QString resultline, results) {
881 fieldlist = resultline.split("\t");
882 volumeList.append(fieldlist[0]);
883 } /* foreach resultline */
884 } /* if results from query */
887 /* Function to get a list of volumes */
888 void Console::getStatusList(QStringList &statusLongList)
890 QString statusQuery("SELECT JobStatusLong FROM Status");
891 if (mainWin->m_sqlDebug) {
892 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
894 QStringList statusResults;
895 if (sql_cmd(statusQuery, statusResults)) {
897 QStringList fieldlist;
898 /* Iterate through the lines of results. */
899 foreach (QString resultline, statusResults) {
900 fieldlist = resultline.split("\t");
901 statusLongList.append(fieldlist[0]);
902 } /* foreach resultline */
903 } /* if results from statusquery */