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) {
327 if (mainWin->m_displayAll) {
331 strip_trailing_junk(msg());
332 bool doappend = true;
335 if ((dum.left(6) == "*None*")) doappend = false;
343 return true; /* ***FIXME*** return any command error */
348 * Send a job name to the director, and read all the resulting
351 bool Console::get_job_defaults(struct job_defaults &job_defs)
359 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
361 while ((stat = read()) > 0) {
362 if (mainWin->m_displayAll) display_text(msg());
363 def = strchr(msg(), '=');
367 /* Pointer to default value */
369 strip_trailing_junk(def);
371 if (strcmp(msg(), "job") == 0) {
372 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
377 if (strcmp(msg(), "pool") == 0) {
378 job_defs.pool_name = def;
381 if (strcmp(msg(), "messages") == 0) {
382 job_defs.messages_name = def;
385 if (strcmp(msg(), "client") == 0) {
386 job_defs.client_name = def;
389 if (strcmp(msg(), "storage") == 0) {
390 job_defs.store_name = def;
393 if (strcmp(msg(), "where") == 0) {
394 job_defs.where = def;
397 if (strcmp(msg(), "level") == 0) {
398 job_defs.level = def;
401 if (strcmp(msg(), "type") == 0) {
405 if (strcmp(msg(), "fileset") == 0) {
406 job_defs.fileset_name = def;
409 if (strcmp(msg(), "catalog") == 0) {
410 job_defs.catalog_name = def;
413 if (strcmp(msg(), "enabled") == 0) {
414 job_defs.enabled = *def == '1' ? true : false;
429 * Save user settings associated with this console
431 void Console::writeSettings()
433 QFont font = get_font();
435 QSettings settings(m_dir->name(), "bat");
436 settings.beginGroup("Console");
437 settings.setValue("consoleFont", font.family());
438 settings.setValue("consolePointSize", font.pointSize());
439 settings.setValue("consoleFixedPitch", font.fixedPitch());
444 * Read and restore user settings associated with this console
446 void Console::readSettings()
448 QFont font = get_font();
450 QSettings settings(m_dir->name(), "bat");
451 settings.beginGroup("Console");
452 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
453 font.setPointSize(settings.value("consolePointSize", 10).toInt());
454 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
456 m_textEdit->setFont(font);
460 * Set the console textEdit font
462 void Console::set_font()
465 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
467 m_textEdit->setFont(font);
472 * Get the console text edit font
474 const QFont Console::get_font()
476 return m_textEdit->font();
480 * Slot for responding to status dir button on button bar
482 void Console::status_dir()
484 QString cmd("status dir");
489 * Slot for responding to messages button on button bar
490 * Here we want to bring the console to the front so use pages' consoleCommand
492 void Console::messages()
494 QString cmd(".messages");
496 messagesPending(false);
500 * Put text into the console window
502 void Console::display_textf(const char *fmt, ...)
507 va_start(arg_ptr, fmt);
508 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
513 void Console::display_text(const QString buf)
515 m_cursor->insertText(buf);
520 void Console::display_text(const char *buf)
522 m_cursor->insertText(buf);
526 void Console::display_html(const QString buf)
528 m_cursor->insertHtml(buf);
532 /* Position cursor to end of screen */
533 void Console::update_cursor()
535 QApplication::restoreOverrideCursor();
536 m_textEdit->moveCursor(QTextCursor::End);
537 m_textEdit->ensureCursorVisible();
541 * This should be moved into a bSocket class
551 /* Send a command to the Director */
552 void Console::write_dir(const char *msg)
555 mainWin->set_status(_("Processing command ..."));
556 QApplication::setOverrideCursor(Qt::WaitCursor);
559 mainWin->set_status( tr(" Director not connected. Click on connect button."));
560 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
561 QBrush redBrush(Qt::red);
562 QTreeWidgetItem *item = mainWin->getFromHash(this);
563 item->setForeground(0, redBrush);
565 m_at_main_prompt = false;
569 int Console::write(const QString msg)
571 return write(msg.toUtf8().data());
574 int Console::write(const char *msg)
579 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
581 m_at_main_prompt = false;
582 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
583 return m_sock->send();
588 * Get to main command prompt -- i.e. abort any subcommand
590 void Console::beginNewCommand()
592 for (int i=0; i < 3; i++) {
595 if (mainWin->m_displayAll) display_text(msg());
597 if (m_at_main_prompt) {
604 void Console::displayToPrompt()
608 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
609 while (!m_at_prompt) {
610 if ((stat=read()) > 0) {
612 if (buf.size() >= 8196 || m_messages_pending) {
615 messagesPending(false);
620 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
623 void Console::discardToPrompt()
626 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
627 if (mainWin->m_displayAll) {
630 while (!m_at_prompt) {
634 if (mainWin->m_commDebug) Pmsg1(000, "endDiscardToPrompt=%d\n", stat);
637 int Console::sock_read()
641 bool wasEnabled = notify(false);
642 stat = m_sock->recv();
645 stat = m_sock->recv();
651 * Blocking read from director
658 stat = m_sock->wait_data_intr(0, 50000);
662 app->processEvents();
663 if (m_api_set && m_messages_pending && is_notify_enabled() && hasFocus()) {
664 write_dir(".messages");
665 messagesPending(false);
671 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
675 m_at_main_prompt = false;
678 switch (m_sock->msglen) {
679 case BNET_MSGS_PENDING :
680 if (is_notify_enabled() && hasFocus()) {
681 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
682 write_dir(".messages");
684 messagesPending(false);
686 messagesPending(true);
689 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
691 m_at_main_prompt = false;
692 mainWin->set_status(_("Command completed ..."));
695 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
697 m_at_main_prompt = false;
698 mainWin->set_status(_("Processing command ..."));
700 case BNET_MAIN_PROMPT:
701 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
703 m_at_main_prompt = true;
704 mainWin->set_status(_("At main prompt waiting for input ..."));
705 QApplication::restoreOverrideCursor();
708 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
710 m_at_main_prompt = false;
711 mainWin->set_status(_("At prompt waiting for input ..."));
712 QApplication::restoreOverrideCursor();
714 case BNET_CMD_FAILED:
715 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
716 mainWin->set_status(_("Command failed."));
717 QApplication::restoreOverrideCursor();
719 /* We should not get this one */
721 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
722 mainWin->set_status_ready();
723 QApplication::restoreOverrideCursor();
728 case BNET_START_SELECT:
729 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
730 new selectDialog(this);
733 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
734 new yesnoPopUp(this);
737 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
740 case BNET_START_RTREE:
741 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
745 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
748 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
749 stat = sock_read(); /* get the message */
751 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
753 case BNET_WARNING_MSG:
754 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
755 stat = sock_read(); /* get the message */
757 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
760 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
761 stat = sock_read(); /* get the message */
763 mainWin->set_status(msg());
766 if (is_bnet_stop(m_sock)) { /* error or term request */
767 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
771 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
772 QBrush redBrush(Qt::red);
773 QTreeWidgetItem *item = mainWin->getFromHash(this);
774 item->setForeground(0, redBrush);
776 m_notifier->setEnabled(false);
780 mainWin->set_status(_("Director disconnected."));
781 QApplication::restoreOverrideCursor();
789 /* Called by signal when the Director has output for us */
790 void Console::read_dir(int /* fd */)
792 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
793 while (read() >= 0) {
799 * When the notifier is enabled, read_dir() will automatically be
800 * called by the Qt event loop when ever there is any output
801 * from the Directory, and read_dir() will then display it on
804 * When we are in a bat dialog, we want to control *all* output
805 * from the Directory, so we set notify to off.
806 * m_console->notifiy(false);
808 bool Console::notify(bool enable)
810 bool prev_enabled = false;
812 prev_enabled = m_notifier->isEnabled();
813 m_notifier->setEnabled(enable);
818 bool Console::is_notify_enabled() const
820 bool enabled = false;
822 enabled = m_notifier->isEnabled();
826 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
828 m_directorTreeItem = item;
831 void Console::setDirRes(DIRRES *dir)
837 * To have the ability to get the name of the director resource.
839 void Console::getDirResName(QString &name_returned)
841 name_returned = m_dir->name();
844 bool Console::is_connectedGui()
846 if (is_connected()) {
849 QString message = tr("Director is currently disconnected\nPlease reconnect!");
850 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
856 * A temporary function to prevent connecting to the director if the director
857 * is busy with a restore.
859 bool Console::preventInUseConnect()
861 if (!is_connected()) {
862 QString message = tr("Director %1 is currently disconnected\n"
863 "Please reconnect!").arg(m_dir->name());
864 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
866 } else if (!m_at_main_prompt){
867 QString message = tr("Director %1 is currently busy\n Please complete "
868 "restore or other operation! This is a limitation "
869 "that will be resolved before a beta release. "
870 "This is currently an alpha release.").arg(m_dir->name());
871 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
873 } else if (!m_at_prompt){
874 QString message = tr("Director %1 is currently not at a prompt\n"
875 " Please try again!").arg(m_dir->name());
876 QMessageBox::warning(this, "Bat", message, QMessageBox::Ok );
884 * Call-back for reading a passphrase for an encrypted PEM file
885 * This function uses getpass(),
886 * which uses a static buffer and is NOT thread-safe.
888 static int tls_pem_callback(char *buf, int size, const void *userdata)
893 const char *prompt = (const char *)userdata;
894 # if defined(HAVE_WIN32)
896 if (win32_cgets(buf, size) == NULL) {
905 passwd = getpass(prompt);
906 bstrncpy(buf, passwd, size);
915 /* Slot for responding to page selectors status help command */
916 void Console::consoleHelp()
922 /* Slot for responding to page selectors reload bacula-dir.conf */
923 void Console::consoleReload()
925 QString cmd("reload");
929 /* Function to get a list of volumes */
930 void Console::getVolumeList(QStringList &volumeList)
932 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
933 if (mainWin->m_sqlDebug) {
934 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
937 if (sql_cmd(query, results)) {
939 QStringList fieldlist;
940 /* Iterate through the lines of results. */
941 foreach (QString resultline, results) {
942 fieldlist = resultline.split("\t");
943 volumeList.append(fieldlist[0]);
944 } /* foreach resultline */
945 } /* if results from query */
948 /* Function to get a list of volumes */
949 void Console::getStatusList(QStringList &statusLongList)
951 QString statusQuery("SELECT JobStatusLong FROM Status");
952 if (mainWin->m_sqlDebug) {
953 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
955 QStringList statusResults;
956 if (sql_cmd(statusQuery, statusResults)) {
958 QStringList fieldlist;
959 /* Iterate through the lines of results. */
960 foreach (QString resultline, statusResults) {
961 fieldlist = resultline.split("\t");
962 statusLongList.append(fieldlist[0]);
963 } /* foreach resultline */
964 } /* if results from statusquery */
967 /* For suppressing .messages
968 * This may be rendered not needed if the multiple connections feature gets working */
969 bool Console::hasFocus()
971 if (mainWin->stackedWidget->currentIndex() == mainWin->stackedWidget->indexOf(this))
977 /* For adding feature to have the gui's messages button change when
978 * messages are pending */
979 bool Console::messagesPending(bool pend)
981 bool prev = m_messages_pending;
982 m_messages_pending = pend;
983 mainWin->setMessageIcon();