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
43 static int tls_pem_callback(char *buf, int size, const void *userdata);
46 Console::Console(QStackedWidget *parent)
56 m_at_main_prompt = false;
57 m_textEdit = textEdit; /* our console screen */
58 m_cursor = new QTextCursor(m_textEdit->document());
59 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
62 m_contextActions.append(actionStatusDir);
63 m_contextActions.append(actionConsoleHelp);
64 m_contextActions.append(actionRequestMessages);
65 m_contextActions.append(actionConsoleReload);
66 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
67 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
68 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
69 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
76 void Console::startTimer()
78 m_timer = new QTimer(this);
79 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
80 m_timer->start(mainWin->m_checkMessagesInterval*1000);
83 void Console::stopTimer()
86 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
93 void Console::poll_messages()
95 m_messages_pending = true;
96 if ((m_at_main_prompt) && (mainWin->m_checkMessages)){
102 /* Terminate any open socket */
103 void Console::terminate()
116 * Connect to Director.
118 void Console::connect_dir()
127 m_textEdit = textEdit; /* our console screen */
130 mainWin->set_status("No Director found.");
134 mainWin->set_status("Already connected.");
138 memset(jcr, 0, sizeof(JCR));
140 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
141 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
143 /* Give GUI a chance */
144 app->processEvents();
147 /* If cons==NULL, default console will be used */
148 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
151 /* Initialize Console TLS context once */
152 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
153 /* Generate passphrase prompt */
154 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
157 /* Initialize TLS context:
158 * Args: CA certfile, CA certdir, Certfile, Keyfile,
159 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
161 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
162 cons->tls_ca_certdir, cons->tls_certfile,
163 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
165 if (!cons->tls_ctx) {
166 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
172 /* Initialize Director TLS context once */
173 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
174 /* Generate passphrase prompt */
175 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
178 /* Initialize TLS context:
179 * Args: CA certfile, CA certdir, Certfile, Keyfile,
180 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
181 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
182 m_dir->tls_ca_certdir, m_dir->tls_certfile,
183 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
185 if (!m_dir->tls_ctx) {
186 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
188 mainWin->set_status("Connection failed");
193 if (m_dir->heartbeat_interval) {
194 heart_beat = m_dir->heartbeat_interval;
196 heart_beat = cons->heartbeat_interval;
201 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
202 _("Director daemon"), m_dir->address,
203 NULL, m_dir->DIRport, 0);
204 if (m_sock == NULL) {
205 mainWin->set_status("Connection failed");
208 /* Update page selector to green to indicate that Console is connected */
209 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
210 QBrush greenBrush(Qt::green);
211 QTreeWidgetItem *item = mainWin->getFromHash(this);
212 item->setForeground(0, greenBrush);
215 jcr->dir_bsock = m_sock;
217 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
226 /* Give GUI a chance */
227 app->processEvents();
229 mainWin->set_status(_("Initializing ..."));
231 /* Set up input notifier */
232 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
233 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
241 fileset_list.clear();
242 fileset_list.clear();
243 messages_list.clear();
245 storage_list.clear();
248 dir_cmd(".jobs", job_list);
249 dir_cmd(".clients", client_list);
250 dir_cmd(".filesets", fileset_list);
251 dir_cmd(".msgs", messages_list);
252 dir_cmd(".pools", pool_list);
253 dir_cmd(".storage", storage_list);
254 dir_cmd(".types", type_list);
255 dir_cmd(".levels", level_list);
257 mainWin->set_status(_("Connected"));
258 startTimer(); /* start message timer */
265 bool Console::dir_cmd(QString &cmd, QStringList &results)
267 return dir_cmd(cmd.toUtf8().data(), results);
271 * Send a command to the Director, and return the
272 * results in a QStringList.
274 bool Console::dir_cmd(const char *cmd, QStringList &results)
280 while ((stat = read()) > 0) {
281 if (mainWin->m_displayAll) display_text(msg());
282 strip_trailing_junk(msg());
287 return true; /* ***FIXME*** return any command error */
290 bool Console::sql_cmd(QString &query, QStringList &results)
292 return sql_cmd(query.toUtf8().data(), results);
296 * Send an sql query to the Director, and return the
297 * results in a QStringList.
299 bool Console::sql_cmd(const char *query, QStringList &results)
302 POOL_MEM cmd(PM_MESSAGE);
304 if (!is_connectedGui()) {
310 pm_strcpy(cmd, ".sql query=\"");
311 pm_strcat(cmd, query);
312 pm_strcat(cmd, "\"");
314 while ((stat = read()) > 0) {
315 if (mainWin->m_displayAll) display_text(msg());
316 strip_trailing_junk(msg());
321 return true; /* ***FIXME*** return any command error */
326 * Send a job name to the director, and read all the resulting
329 bool Console::get_job_defaults(struct job_defaults &job_defs)
337 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
339 while ((stat = read()) > 0) {
340 if (mainWin->m_displayAll) display_text(msg());
341 def = strchr(msg(), '=');
345 /* Pointer to default value */
347 strip_trailing_junk(def);
349 if (strcmp(msg(), "job") == 0) {
350 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
355 if (strcmp(msg(), "pool") == 0) {
356 job_defs.pool_name = def;
359 if (strcmp(msg(), "messages") == 0) {
360 job_defs.messages_name = def;
363 if (strcmp(msg(), "client") == 0) {
364 job_defs.client_name = def;
367 if (strcmp(msg(), "storage") == 0) {
368 job_defs.store_name = def;
371 if (strcmp(msg(), "where") == 0) {
372 job_defs.where = def;
375 if (strcmp(msg(), "level") == 0) {
376 job_defs.level = def;
379 if (strcmp(msg(), "type") == 0) {
383 if (strcmp(msg(), "fileset") == 0) {
384 job_defs.fileset_name = def;
387 if (strcmp(msg(), "catalog") == 0) {
388 job_defs.catalog_name = def;
391 if (strcmp(msg(), "enabled") == 0) {
392 job_defs.enabled = *def == '1' ? true : false;
407 * Save user settings associated with this console
409 void Console::writeSettings()
411 QFont font = get_font();
413 QSettings settings(m_dir->name(), "bat");
414 settings.beginGroup("Console");
415 settings.setValue("consoleFont", font.family());
416 settings.setValue("consolePointSize", font.pointSize());
417 settings.setValue("consoleFixedPitch", font.fixedPitch());
422 * Read and restore user settings associated with this console
424 void Console::readSettings()
426 QFont font = get_font();
428 QSettings settings(m_dir->name(), "bat");
429 settings.beginGroup("Console");
430 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
431 font.setPointSize(settings.value("consolePointSize", 10).toInt());
432 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
434 m_textEdit->setFont(font);
438 * Set the console textEdit font
440 void Console::set_font()
443 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
445 m_textEdit->setFont(font);
450 * Get the console text edit font
452 const QFont Console::get_font()
454 return m_textEdit->font();
458 * Slot for responding to status dir button on button bar
460 void Console::status_dir()
462 QString cmd("status dir");
467 * Slot for responding to messages button on button bar
469 void Console::messages()
471 QString cmd(".messages");
476 * Put text into the console window
478 void Console::display_textf(const char *fmt, ...)
483 va_start(arg_ptr, fmt);
484 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
489 void Console::display_text(const QString buf)
491 m_cursor->insertText(buf);
496 void Console::display_text(const char *buf)
498 m_cursor->insertText(buf);
502 void Console::display_html(const QString buf)
504 m_cursor->insertHtml(buf);
508 /* Position cursor to end of screen */
509 void Console::update_cursor()
511 QApplication::restoreOverrideCursor();
512 m_textEdit->moveCursor(QTextCursor::End);
513 m_textEdit->ensureCursorVisible();
517 * This should be moved into a bSocket class
527 /* Send a command to the Director */
528 void Console::write_dir(const char *msg)
531 mainWin->set_status(_("Processing command ..."));
532 QApplication::setOverrideCursor(Qt::WaitCursor);
535 mainWin->set_status(" Director not connected. Click on connect button.");
536 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
537 QBrush redBrush(Qt::red);
538 QTreeWidgetItem *item = mainWin->getFromHash(this);
539 item->setForeground(0, redBrush);
541 m_at_main_prompt = false;
545 int Console::write(const QString msg)
547 return write(msg.toUtf8().data());
550 int Console::write(const char *msg)
555 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
557 m_at_main_prompt = false;
558 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
559 return m_sock->send();
564 * Get to main command prompt -- i.e. abort any subcommand
566 void Console::beginNewCommand()
568 for (int i=0; i < 3; i++) {
571 if (mainWin->m_displayAll) display_text(msg());
573 if (m_at_main_prompt) {
580 void Console::displayToPrompt()
583 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
584 while (!m_at_prompt) {
585 if ((stat=read()) > 0) {
589 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
592 void Console::discardToPrompt()
595 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
596 while (!m_at_prompt) {
597 if ((stat=read()) > 0) {
598 if (mainWin->m_displayAll) display_text(msg());
601 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
606 * Blocking read from director
613 stat = bnet_wait_data_intr(m_sock, 1);
617 app->processEvents();
618 if (m_api_set && m_messages_pending) {
619 write_dir(".messages");
620 m_messages_pending = false;
624 stat = m_sock->recv();
626 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
630 m_at_main_prompt = false;
633 switch (m_sock->msglen) {
634 case BNET_MSGS_PENDING :
635 if (m_notifier->isEnabled()) {
636 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
637 write_dir(".messages");
639 m_messages_pending = false;
641 m_messages_pending = true;
644 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
646 m_at_main_prompt = false;
647 mainWin->set_status(_("Command completed ..."));
650 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
652 m_at_main_prompt = false;
653 mainWin->set_status(_("Processing command ..."));
655 case BNET_MAIN_PROMPT:
656 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
658 m_at_main_prompt = true;
659 mainWin->set_status(_("At main prompt waiting for input ..."));
660 QApplication::restoreOverrideCursor();
663 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
665 m_at_main_prompt = false;
666 mainWin->set_status(_("At prompt waiting for input ..."));
667 QApplication::restoreOverrideCursor();
669 case BNET_CMD_FAILED:
670 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
671 mainWin->set_status(_("Command failed."));
672 QApplication::restoreOverrideCursor();
674 /* We should not get this one */
676 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
677 mainWin->set_status_ready();
678 QApplication::restoreOverrideCursor();
683 case BNET_START_SELECT:
684 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
685 new selectDialog(this);
688 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
689 new yesnoPopUp(this);
692 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
695 case BNET_START_RTREE:
696 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
700 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
703 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
704 m_sock->recv(); /* get the message */
706 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
708 case BNET_WARNING_MSG:
709 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
710 m_sock->recv(); /* get the message */
712 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
715 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
716 m_sock->recv(); /* get the message */
718 mainWin->set_status(msg());
721 if (is_bnet_stop(m_sock)) { /* error or term request */
722 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
726 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
727 QBrush redBrush(Qt::red);
728 QTreeWidgetItem *item = mainWin->getFromHash(this);
729 item->setForeground(0, redBrush);
730 m_notifier->setEnabled(false);
733 mainWin->set_status(_("Director disconnected."));
734 QApplication::restoreOverrideCursor();
742 /* Called by signal when the Director has output for us */
743 void Console::read_dir(int /* fd */)
745 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
746 while (read() >= 0) {
752 * When the notifier is enabled, read_dir() will automatically be
753 * called by the Qt event loop when ever there is any output
754 * from the Directory, and read_dir() will then display it on
757 * When we are in a bat dialog, we want to control *all* output
758 * from the Directory, so we set notify to off.
759 * m_console->notifiy(false);
761 void Console::notify(bool enable)
763 m_notifier->setEnabled(enable);
766 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
768 m_directorTreeItem = item;
771 void Console::setDirRes(DIRRES *dir)
777 * To have the ability to get the name of the director resource.
779 void Console::getDirResName(QString &name_returned)
781 name_returned = m_dir->name();
784 bool Console::is_connectedGui()
786 if (is_connected()) {
789 QString message("Director ");
790 message += " is currently disconnected\n Please reconnect!!";
791 QMessageBox::warning(this, "Bat",
792 tr(message.toUtf8().data()), QMessageBox::Ok );
798 * A temporary function to prevent connecting to the director if the director
799 * is busy with a restore.
801 bool Console::preventInUseConnect()
803 if (!is_connected()) {
804 QString message("Director ");
805 message += m_dir->name();
806 message += " is currently disconnected\n Please reconnect!!";
807 QMessageBox::warning(this, "Bat",
808 tr(message.toUtf8().data()), QMessageBox::Ok );
810 } else if (!m_at_main_prompt){
811 QString message("Director ");
812 message += m_dir->name();
813 message += " is currently busy\n Please complete restore or other "
814 " operation !! This is a limitation that will be resolved before a beta"
815 " release. This is currently an alpha release.";
816 QMessageBox::warning(this, "Bat",
817 tr(message.toUtf8().data()), QMessageBox::Ok );
819 } else if (!m_at_prompt){
820 QString message("Director ");
821 message += m_dir->name();
822 message += " is currently not at a prompt\n Please try again!!";
823 QMessageBox::warning(this, "Bat",
824 tr(message.toUtf8().data()), QMessageBox::Ok );
832 * Call-back for reading a passphrase for an encrypted PEM file
833 * This function uses getpass(),
834 * which uses a static buffer and is NOT thread-safe.
836 static int tls_pem_callback(char *buf, int size, const void *userdata)
841 const char *prompt = (const char *)userdata;
842 # if defined(HAVE_WIN32)
844 if (win32_cgets(buf, size) == NULL) {
853 passwd = getpass(prompt);
854 bstrncpy(buf, passwd, size);
863 /* Slot for responding to page selectors status help command */
864 void Console::consoleHelp()
870 /* Slot for responding to page selectors reload bacula-dir.conf */
871 void Console::consoleReload()
873 QString cmd("reload");
877 /* Function to get a list of volumes */
878 void Console::getVolumeList(QStringList &volumeList)
880 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
881 if (mainWin->m_sqlDebug) {
882 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
885 if (sql_cmd(query, results)) {
887 QStringList fieldlist;
888 /* Iterate through the lines of results. */
889 foreach (QString resultline, results) {
890 fieldlist = resultline.split("\t");
891 volumeList.append(fieldlist[0]);
892 } /* foreach resultline */
893 } /* if results from query */
896 /* Function to get a list of volumes */
897 void Console::getStatusList(QStringList &statusLongList)
899 QString statusQuery("SELECT JobStatusLong FROM Status");
900 if (mainWin->m_sqlDebug) {
901 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
903 QStringList statusResults;
904 if (sql_cmd(statusQuery, statusResults)) {
906 QStringList fieldlist;
907 /* Iterate through the lines of results. */
908 foreach (QString resultline, statusResults) {
909 fieldlist = resultline.split("\t");
910 statusLongList.append(fieldlist[0]);
911 } /* foreach resultline */
912 } /* if results from statusquery */