2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2008 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 Kern Sibbald.
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):
51 m_messages_pending = false;
59 m_at_main_prompt = false;
60 m_textEdit = textEdit; /* our console screen */
61 m_cursor = new QTextCursor(m_textEdit->document());
62 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
65 m_contextActions.append(actionStatusDir);
66 m_contextActions.append(actionConsoleHelp);
67 m_contextActions.append(actionRequestMessages);
68 m_contextActions.append(actionConsoleReload);
69 connect(actionStatusDir, SIGNAL(triggered()), this, SLOT(status_dir()));
70 connect(actionConsoleHelp, SIGNAL(triggered()), this, SLOT(consoleHelp()));
71 connect(actionConsoleReload, SIGNAL(triggered()), this, SLOT(consoleReload()));
72 connect(actionRequestMessages, SIGNAL(triggered()), this, SLOT(messages()));
79 void Console::startTimer()
81 m_timer = new QTimer(this);
82 QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
83 m_timer->start(mainWin->m_checkMessagesInterval*1000);
86 void Console::stopTimer()
89 QWidget::disconnect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
96 /* slot connected to the timer
97 * requires preferences of check messages and operates at interval */
98 void Console::poll_messages()
100 if ( mainWin->m_checkMessages && m_at_main_prompt && hasFocus()){
101 messagesPending(true);
104 messagesPending(false);
108 /* Terminate any open socket */
109 void Console::terminate()
113 m_notifier->setEnabled(false);
124 * Connect to Director.
126 void Console::connect_dir()
135 m_textEdit = textEdit; /* our console screen */
138 mainWin->set_status( tr("No Director found."));
142 mainWin->set_status( tr("Already connected."));
146 memset(jcr, 0, sizeof(JCR));
148 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
149 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
151 /* Give GUI a chance */
152 app->processEvents();
155 /* If cons==NULL, default console will be used */
156 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
159 /* Initialize Console TLS context once */
160 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
161 /* Generate passphrase prompt */
162 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%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
169 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
170 cons->tls_ca_certdir, cons->tls_certfile,
171 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
173 if (!cons->tls_ctx) {
174 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
180 /* Initialize Director TLS context once */
181 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
182 /* Generate passphrase prompt */
183 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
186 /* Initialize TLS context:
187 * Args: CA certfile, CA certdir, Certfile, Keyfile,
188 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
189 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
190 m_dir->tls_ca_certdir, m_dir->tls_certfile,
191 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
193 if (!m_dir->tls_ctx) {
194 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
196 mainWin->set_status( tr("Connection failed") );
201 if (m_dir->heartbeat_interval) {
202 heart_beat = m_dir->heartbeat_interval;
204 heart_beat = cons->heartbeat_interval;
209 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
210 _("Director daemon"), m_dir->address,
211 NULL, m_dir->DIRport, 0);
212 if (m_sock == NULL) {
213 mainWin->set_status("Connection failed");
216 /* Update page selector to green to indicate that Console is connected */
217 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
218 QBrush greenBrush(Qt::green);
219 QTreeWidgetItem *item = mainWin->getFromHash(this);
220 item->setForeground(0, greenBrush);
223 jcr->dir_bsock = m_sock;
225 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
234 /* Give GUI a chance */
235 app->processEvents();
237 mainWin->set_status(_("Initializing ..."));
240 /* Set up input notifier */
241 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
242 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
252 fileset_list.clear();
253 fileset_list.clear();
254 messages_list.clear();
256 storage_list.clear();
259 dir_cmd(".jobs", job_list);
260 dir_cmd(".clients", client_list);
261 dir_cmd(".filesets", fileset_list);
262 dir_cmd(".msgs", messages_list);
263 dir_cmd(".pools", pool_list);
264 dir_cmd(".storage", storage_list);
265 dir_cmd(".types", type_list);
266 dir_cmd(".levels", level_list);
268 mainWin->set_status(_("Connected"));
269 startTimer(); /* start message timer */
276 bool Console::dir_cmd(QString &cmd, QStringList &results)
278 return dir_cmd(cmd.toUtf8().data(), results);
282 * Send a command to the Director, and return the
283 * results in a QStringList.
285 bool Console::dir_cmd(const char *cmd, QStringList &results)
291 while ((stat = read()) > 0) {
292 if (mainWin->m_displayAll) display_text(msg());
293 strip_trailing_junk(msg());
298 return true; /* ***FIXME*** return any command error */
301 bool Console::sql_cmd(QString &query, QStringList &results)
303 return sql_cmd(query.toUtf8().data(), results);
307 * Send an sql query to the Director, and return the
308 * results in a QStringList.
310 bool Console::sql_cmd(const char *query, QStringList &results)
313 POOL_MEM cmd(PM_MESSAGE);
315 if (!is_connectedGui()) {
321 pm_strcpy(cmd, ".sql query=\"");
322 pm_strcat(cmd, query);
323 pm_strcat(cmd, "\"");
325 while ((stat = read()) > 0) {
326 if (mainWin->m_displayAll) {
330 strip_trailing_junk(msg());
335 return true; /* ***FIXME*** return any command error */
340 * Send a job name to the director, and read all the resulting
343 bool Console::get_job_defaults(struct job_defaults &job_defs)
351 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
353 while ((stat = read()) > 0) {
354 if (mainWin->m_displayAll) display_text(msg());
355 def = strchr(msg(), '=');
359 /* Pointer to default value */
361 strip_trailing_junk(def);
363 if (strcmp(msg(), "job") == 0) {
364 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
369 if (strcmp(msg(), "pool") == 0) {
370 job_defs.pool_name = def;
373 if (strcmp(msg(), "messages") == 0) {
374 job_defs.messages_name = def;
377 if (strcmp(msg(), "client") == 0) {
378 job_defs.client_name = def;
381 if (strcmp(msg(), "storage") == 0) {
382 job_defs.store_name = def;
385 if (strcmp(msg(), "where") == 0) {
386 job_defs.where = def;
389 if (strcmp(msg(), "level") == 0) {
390 job_defs.level = def;
393 if (strcmp(msg(), "type") == 0) {
397 if (strcmp(msg(), "fileset") == 0) {
398 job_defs.fileset_name = def;
401 if (strcmp(msg(), "catalog") == 0) {
402 job_defs.catalog_name = def;
405 if (strcmp(msg(), "enabled") == 0) {
406 job_defs.enabled = *def == '1' ? true : false;
421 * Save user settings associated with this console
423 void Console::writeSettings()
425 QFont font = get_font();
427 QSettings settings(m_dir->name(), "bat");
428 settings.beginGroup("Console");
429 settings.setValue("consoleFont", font.family());
430 settings.setValue("consolePointSize", font.pointSize());
431 settings.setValue("consoleFixedPitch", font.fixedPitch());
436 * Read and restore user settings associated with this console
438 void Console::readSettings()
440 QFont font = get_font();
442 QSettings settings(m_dir->name(), "bat");
443 settings.beginGroup("Console");
444 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
445 font.setPointSize(settings.value("consolePointSize", 10).toInt());
446 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
448 m_textEdit->setFont(font);
452 * Set the console textEdit font
454 void Console::set_font()
457 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
459 m_textEdit->setFont(font);
464 * Get the console text edit font
466 const QFont Console::get_font()
468 return m_textEdit->font();
472 * Slot for responding to status dir button on button bar
474 void Console::status_dir()
476 QString cmd("status dir");
481 * Slot for responding to messages button on button bar
482 * Here we want to bring the console to the front so use pages' consoleCommand
484 void Console::messages()
486 QString cmd(".messages");
488 messagesPending(false);
492 * Put text into the console window
494 void Console::display_textf(const char *fmt, ...)
499 va_start(arg_ptr, fmt);
500 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
505 void Console::display_text(const QString buf)
507 m_cursor->insertText(buf);
512 void Console::display_text(const char *buf)
514 m_cursor->insertText(buf);
518 void Console::display_html(const QString buf)
520 m_cursor->insertHtml(buf);
524 /* Position cursor to end of screen */
525 void Console::update_cursor()
527 QApplication::restoreOverrideCursor();
528 m_textEdit->moveCursor(QTextCursor::End);
529 m_textEdit->ensureCursorVisible();
533 * This should be moved into a bSocket class
543 /* Send a command to the Director */
544 void Console::write_dir(const char *msg)
547 mainWin->set_status(_("Processing command ..."));
548 QApplication::setOverrideCursor(Qt::WaitCursor);
551 mainWin->set_status( tr(" Director not connected. Click on connect button."));
552 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
553 QBrush redBrush(Qt::red);
554 QTreeWidgetItem *item = mainWin->getFromHash(this);
555 item->setForeground(0, redBrush);
557 m_at_main_prompt = false;
561 int Console::write(const QString msg)
563 return write(msg.toUtf8().data());
566 int Console::write(const char *msg)
571 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
573 m_at_main_prompt = false;
574 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
575 return m_sock->send();
580 * Get to main command prompt -- i.e. abort any subcommand
582 void Console::beginNewCommand()
584 for (int i=0; i < 3; i++) {
587 if (mainWin->m_displayAll) display_text(msg());
589 if (m_at_main_prompt) {
596 void Console::displayToPrompt()
600 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
601 while (!m_at_prompt) {
602 if ((stat=read()) > 0) {
604 if (buf.size() >= 8196 || m_messages_pending) {
607 messagesPending(false);
612 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
615 void Console::discardToPrompt()
618 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
619 if (mainWin->m_displayAll) {
622 while (!m_at_prompt) {
626 if (mainWin->m_commDebug) Pmsg1(000, "endDiscardToPrompt=%d\n", stat);
629 int Console::sock_read()
633 bool wasEnabled = notify(false);
634 stat = m_sock->recv();
637 stat = m_sock->recv();
643 * Blocking read from director
650 stat = m_sock->wait_data_intr(0, 50000);
654 app->processEvents();
655 if (m_api_set && m_messages_pending && is_notify_enabled() && hasFocus()) {
656 write_dir(".messages");
657 messagesPending(false);
663 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
667 m_at_main_prompt = false;
670 switch (m_sock->msglen) {
671 case BNET_MSGS_PENDING :
672 if (is_notify_enabled() && hasFocus()) {
673 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
674 write_dir(".messages");
676 messagesPending(false);
678 messagesPending(true);
681 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
683 m_at_main_prompt = false;
684 mainWin->set_status(_("Command completed ..."));
687 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
689 m_at_main_prompt = false;
690 mainWin->set_status(_("Processing command ..."));
692 case BNET_MAIN_PROMPT:
693 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
695 m_at_main_prompt = true;
696 mainWin->set_status(_("At main prompt waiting for input ..."));
697 QApplication::restoreOverrideCursor();
700 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
702 m_at_main_prompt = false;
703 mainWin->set_status(_("At prompt waiting for input ..."));
704 QApplication::restoreOverrideCursor();
706 case BNET_CMD_FAILED:
707 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
708 mainWin->set_status(_("Command failed."));
709 QApplication::restoreOverrideCursor();
711 /* We should not get this one */
713 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
714 mainWin->set_status_ready();
715 QApplication::restoreOverrideCursor();
720 case BNET_START_SELECT:
721 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
722 new selectDialog(this);
725 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
726 new yesnoPopUp(this);
729 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
732 case BNET_START_RTREE:
733 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
737 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
740 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
741 stat = sock_read(); /* get the message */
743 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
745 case BNET_WARNING_MSG:
746 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
747 stat = sock_read(); /* get the message */
749 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
752 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
753 stat = sock_read(); /* get the message */
755 mainWin->set_status(msg());
758 if (is_bnet_stop(m_sock)) { /* error or term request */
759 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
763 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
764 QBrush redBrush(Qt::red);
765 QTreeWidgetItem *item = mainWin->getFromHash(this);
766 item->setForeground(0, redBrush);
768 m_notifier->setEnabled(false);
772 mainWin->set_status(_("Director disconnected."));
773 QApplication::restoreOverrideCursor();
781 /* Called by signal when the Director has output for us */
782 void Console::read_dir(int /* fd */)
784 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
785 while (read() >= 0) {
791 * When the notifier is enabled, read_dir() will automatically be
792 * called by the Qt event loop when ever there is any output
793 * from the Directory, and read_dir() will then display it on
796 * When we are in a bat dialog, we want to control *all* output
797 * from the Directory, so we set notify to off.
798 * m_console->notifiy(false);
800 bool Console::notify(bool enable)
802 bool prev_enabled = false;
804 prev_enabled = m_notifier->isEnabled();
805 m_notifier->setEnabled(enable);
810 bool Console::is_notify_enabled() const
812 bool enabled = false;
814 enabled = m_notifier->isEnabled();
818 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
820 m_directorTreeItem = item;
823 void Console::setDirRes(DIRRES *dir)
829 * To have the ability to get the name of the director resource.
831 void Console::getDirResName(QString &name_returned)
833 name_returned = m_dir->name();
836 bool Console::is_connectedGui()
838 if (is_connected()) {
841 QString message = tr("Director is currently disconnected\nPlease reconnect!");
842 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
848 * A temporary function to prevent connecting to the director if the director
849 * is busy with a restore.
851 bool Console::preventInUseConnect()
853 if (!is_connected()) {
854 QString message = tr("Director %1 is currently disconnected\n"
855 "Please reconnect!").arg(m_dir->name());
856 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
858 } else if (!m_at_main_prompt){
859 QString message = tr("Director %1 is currently busy\n Please complete "
860 "restore or other operation! This is a limitation "
861 "that will be resolved before a beta release. "
862 "This is currently an alpha release.").arg(m_dir->name());
863 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
865 } else if (!m_at_prompt){
866 QString message = tr("Director %1 is currently not at a prompt\n"
867 " Please try again!").arg(m_dir->name());
868 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
876 * Call-back for reading a passphrase for an encrypted PEM file
877 * This function uses getpass(),
878 * which uses a static buffer and is NOT thread-safe.
880 static int tls_pem_callback(char *buf, int size, const void *userdata)
885 const char *prompt = (const char *)userdata;
886 # if defined(HAVE_WIN32)
888 if (win32_cgets(buf, size) == NULL) {
897 passwd = getpass(prompt);
898 bstrncpy(buf, passwd, size);
907 /* Slot for responding to page selectors status help command */
908 void Console::consoleHelp()
914 /* Slot for responding to page selectors reload bacula-dir.conf */
915 void Console::consoleReload()
917 QString cmd("reload");
921 /* Function to get a list of volumes */
922 void Console::getVolumeList(QStringList &volumeList)
924 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
925 if (mainWin->m_sqlDebug) {
926 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
929 if (sql_cmd(query, results)) {
931 QStringList fieldlist;
932 /* Iterate through the lines of results. */
933 foreach (QString resultline, results) {
934 fieldlist = resultline.split("\t");
935 volumeList.append(fieldlist[0]);
936 } /* foreach resultline */
937 } /* if results from query */
940 /* Function to get a list of volumes */
941 void Console::getStatusList(QStringList &statusLongList)
943 QString statusQuery("SELECT JobStatusLong FROM Status");
944 if (mainWin->m_sqlDebug) {
945 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
947 QStringList statusResults;
948 if (sql_cmd(statusQuery, statusResults)) {
950 QStringList fieldlist;
951 /* Iterate through the lines of results. */
952 foreach (QString resultline, statusResults) {
953 fieldlist = resultline.split("\t");
954 statusLongList.append(fieldlist[0]);
955 } /* foreach resultline */
956 } /* if results from statusquery */
959 /* For suppressing .messages
960 * This may be rendered not needed if the multiple connections feature gets working */
961 bool Console::hasFocus()
963 if (mainWin->stackedWidget->currentIndex() == mainWin->stackedWidget->indexOf(this))
969 /* For adding feature to have the gui's messages button change when
970 * messages are pending */
971 bool Console::messagesPending(bool pend)
973 bool prev = m_messages_pending;
974 m_messages_pending = pend;
975 mainWin->setMessageIcon();