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()
107 m_notifier->setEnabled(false);
118 * Connect to Director.
120 void Console::connect_dir()
129 m_textEdit = textEdit; /* our console screen */
132 mainWin->set_status("No Director found.");
136 mainWin->set_status("Already connected.");
140 memset(jcr, 0, sizeof(JCR));
142 mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
143 display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
145 /* Give GUI a chance */
146 app->processEvents();
149 /* If cons==NULL, default console will be used */
150 cons = (CONRES *)GetNextRes(R_CONSOLE, NULL);
153 /* Initialize Console TLS context once */
154 if (cons && !cons->tls_ctx && (cons->tls_enable || cons->tls_require)) {
155 /* Generate passphrase prompt */
156 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ",
159 /* Initialize TLS context:
160 * Args: CA certfile, CA certdir, Certfile, Keyfile,
161 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
163 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
164 cons->tls_ca_certdir, cons->tls_certfile,
165 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
167 if (!cons->tls_ctx) {
168 display_textf(_("Failed to initialize TLS context for Console \"%s\".\n"),
174 /* Initialize Director TLS context once */
175 if (!m_dir->tls_ctx && (m_dir->tls_enable || m_dir->tls_require)) {
176 /* Generate passphrase prompt */
177 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ",
180 /* Initialize TLS context:
181 * Args: CA certfile, CA certdir, Certfile, Keyfile,
182 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
183 m_dir->tls_ctx = new_tls_context(m_dir->tls_ca_certfile,
184 m_dir->tls_ca_certdir, m_dir->tls_certfile,
185 m_dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
187 if (!m_dir->tls_ctx) {
188 display_textf(_("Failed to initialize TLS context for Director \"%s\".\n"),
190 mainWin->set_status("Connection failed");
195 if (m_dir->heartbeat_interval) {
196 heart_beat = m_dir->heartbeat_interval;
198 heart_beat = cons->heartbeat_interval;
203 m_sock = bnet_connect(NULL, 5, 15, heart_beat,
204 _("Director daemon"), m_dir->address,
205 NULL, m_dir->DIRport, 0);
206 if (m_sock == NULL) {
207 mainWin->set_status("Connection failed");
210 /* Update page selector to green to indicate that Console is connected */
211 mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
212 QBrush greenBrush(Qt::green);
213 QTreeWidgetItem *item = mainWin->getFromHash(this);
214 item->setForeground(0, greenBrush);
217 jcr->dir_bsock = m_sock;
219 if (!authenticate_director(jcr, m_dir, cons, buf, sizeof(buf))) {
228 /* Give GUI a chance */
229 app->processEvents();
231 mainWin->set_status(_("Initializing ..."));
233 /* Set up input notifier */
234 m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
235 QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
243 fileset_list.clear();
244 fileset_list.clear();
245 messages_list.clear();
247 storage_list.clear();
250 dir_cmd(".jobs", job_list);
251 dir_cmd(".clients", client_list);
252 dir_cmd(".filesets", fileset_list);
253 dir_cmd(".msgs", messages_list);
254 dir_cmd(".pools", pool_list);
255 dir_cmd(".storage", storage_list);
256 dir_cmd(".types", type_list);
257 dir_cmd(".levels", level_list);
259 mainWin->set_status(_("Connected"));
260 startTimer(); /* start message timer */
267 bool Console::dir_cmd(QString &cmd, QStringList &results)
269 return dir_cmd(cmd.toUtf8().data(), results);
273 * Send a command to the Director, and return the
274 * results in a QStringList.
276 bool Console::dir_cmd(const char *cmd, QStringList &results)
282 while ((stat = read()) > 0) {
283 if (mainWin->m_displayAll) display_text(msg());
284 strip_trailing_junk(msg());
289 return true; /* ***FIXME*** return any command error */
292 bool Console::sql_cmd(QString &query, QStringList &results)
294 return sql_cmd(query.toUtf8().data(), results);
298 * Send an sql query to the Director, and return the
299 * results in a QStringList.
301 bool Console::sql_cmd(const char *query, QStringList &results)
304 POOL_MEM cmd(PM_MESSAGE);
306 if (!is_connectedGui()) {
312 pm_strcpy(cmd, ".sql query=\"");
313 pm_strcat(cmd, query);
314 pm_strcat(cmd, "\"");
316 while ((stat = read()) > 0) {
317 if (mainWin->m_displayAll) display_text(msg());
318 strip_trailing_junk(msg());
323 return true; /* ***FIXME*** return any command error */
328 * Send a job name to the director, and read all the resulting
331 bool Console::get_job_defaults(struct job_defaults &job_defs)
339 scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
341 while ((stat = read()) > 0) {
342 if (mainWin->m_displayAll) display_text(msg());
343 def = strchr(msg(), '=');
347 /* Pointer to default value */
349 strip_trailing_junk(def);
351 if (strcmp(msg(), "job") == 0) {
352 if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
357 if (strcmp(msg(), "pool") == 0) {
358 job_defs.pool_name = def;
361 if (strcmp(msg(), "messages") == 0) {
362 job_defs.messages_name = def;
365 if (strcmp(msg(), "client") == 0) {
366 job_defs.client_name = def;
369 if (strcmp(msg(), "storage") == 0) {
370 job_defs.store_name = def;
373 if (strcmp(msg(), "where") == 0) {
374 job_defs.where = def;
377 if (strcmp(msg(), "level") == 0) {
378 job_defs.level = def;
381 if (strcmp(msg(), "type") == 0) {
385 if (strcmp(msg(), "fileset") == 0) {
386 job_defs.fileset_name = def;
389 if (strcmp(msg(), "catalog") == 0) {
390 job_defs.catalog_name = def;
393 if (strcmp(msg(), "enabled") == 0) {
394 job_defs.enabled = *def == '1' ? true : false;
409 * Save user settings associated with this console
411 void Console::writeSettings()
413 QFont font = get_font();
415 QSettings settings(m_dir->name(), "bat");
416 settings.beginGroup("Console");
417 settings.setValue("consoleFont", font.family());
418 settings.setValue("consolePointSize", font.pointSize());
419 settings.setValue("consoleFixedPitch", font.fixedPitch());
424 * Read and restore user settings associated with this console
426 void Console::readSettings()
428 QFont font = get_font();
430 QSettings settings(m_dir->name(), "bat");
431 settings.beginGroup("Console");
432 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
433 font.setPointSize(settings.value("consolePointSize", 10).toInt());
434 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
436 m_textEdit->setFont(font);
440 * Set the console textEdit font
442 void Console::set_font()
445 QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
447 m_textEdit->setFont(font);
452 * Get the console text edit font
454 const QFont Console::get_font()
456 return m_textEdit->font();
460 * Slot for responding to status dir button on button bar
462 void Console::status_dir()
464 QString cmd("status dir");
469 * Slot for responding to messages button on button bar
471 void Console::messages()
473 QString cmd(".messages");
478 * Put text into the console window
480 void Console::display_textf(const char *fmt, ...)
485 va_start(arg_ptr, fmt);
486 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
491 void Console::display_text(const QString buf)
493 m_cursor->insertText(buf);
498 void Console::display_text(const char *buf)
500 m_cursor->insertText(buf);
504 void Console::display_html(const QString buf)
506 m_cursor->insertHtml(buf);
510 /* Position cursor to end of screen */
511 void Console::update_cursor()
513 QApplication::restoreOverrideCursor();
514 m_textEdit->moveCursor(QTextCursor::End);
515 m_textEdit->ensureCursorVisible();
519 * This should be moved into a bSocket class
529 /* Send a command to the Director */
530 void Console::write_dir(const char *msg)
533 mainWin->set_status(_("Processing command ..."));
534 QApplication::setOverrideCursor(Qt::WaitCursor);
537 mainWin->set_status(" Director not connected. Click on connect button.");
538 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
539 QBrush redBrush(Qt::red);
540 QTreeWidgetItem *item = mainWin->getFromHash(this);
541 item->setForeground(0, redBrush);
543 m_at_main_prompt = false;
547 int Console::write(const QString msg)
549 return write(msg.toUtf8().data());
552 int Console::write(const char *msg)
557 m_sock->msglen = pm_strcpy(m_sock->msg, msg);
559 m_at_main_prompt = false;
560 if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
561 return m_sock->send();
566 * Get to main command prompt -- i.e. abort any subcommand
568 void Console::beginNewCommand()
570 for (int i=0; i < 3; i++) {
573 if (mainWin->m_displayAll) display_text(msg());
575 if (m_at_main_prompt) {
582 void Console::displayToPrompt()
585 if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
586 while (!m_at_prompt) {
587 if ((stat=read()) > 0) {
591 if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
594 void Console::discardToPrompt()
597 if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
598 while (!m_at_prompt) {
599 if ((stat=read()) > 0) {
600 if (mainWin->m_displayAll) display_text(msg());
603 if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
608 * Blocking read from director
615 stat = bnet_wait_data_intr(m_sock, 1);
619 app->processEvents();
620 if (m_api_set && m_messages_pending) {
621 write_dir(".messages");
622 m_messages_pending = false;
626 stat = m_sock->recv();
628 if (mainWin->m_commDebug) Pmsg1(000, "got: %s\n", m_sock->msg);
632 m_at_main_prompt = false;
635 switch (m_sock->msglen) {
636 case BNET_MSGS_PENDING :
637 if (m_notifier->isEnabled()) {
638 if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
639 write_dir(".messages");
641 m_messages_pending = false;
643 m_messages_pending = true;
646 if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
648 m_at_main_prompt = false;
649 mainWin->set_status(_("Command completed ..."));
652 if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
654 m_at_main_prompt = false;
655 mainWin->set_status(_("Processing command ..."));
657 case BNET_MAIN_PROMPT:
658 if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
660 m_at_main_prompt = true;
661 mainWin->set_status(_("At main prompt waiting for input ..."));
662 QApplication::restoreOverrideCursor();
665 if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
667 m_at_main_prompt = false;
668 mainWin->set_status(_("At prompt waiting for input ..."));
669 QApplication::restoreOverrideCursor();
671 case BNET_CMD_FAILED:
672 if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
673 mainWin->set_status(_("Command failed."));
674 QApplication::restoreOverrideCursor();
676 /* We should not get this one */
678 if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
679 mainWin->set_status_ready();
680 QApplication::restoreOverrideCursor();
685 case BNET_START_SELECT:
686 if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
687 new selectDialog(this);
690 if (mainWin->m_commDebug) Pmsg0(000, "YESNO\n");
691 new yesnoPopUp(this);
694 if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
697 case BNET_START_RTREE:
698 if (mainWin->m_commDebug) Pmsg0(000, "START RTREE CMD\n");
702 if (mainWin->m_commDebug) Pmsg0(000, "END RTREE CMD\n");
705 if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
706 m_sock->recv(); /* get the message */
708 QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
710 case BNET_WARNING_MSG:
711 if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
712 m_sock->recv(); /* get the message */
714 QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
717 if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
718 m_sock->recv(); /* get the message */
720 mainWin->set_status(msg());
723 if (is_bnet_stop(m_sock)) { /* error or term request */
724 if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
728 mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
729 QBrush redBrush(Qt::red);
730 QTreeWidgetItem *item = mainWin->getFromHash(this);
731 item->setForeground(0, redBrush);
732 m_notifier->setEnabled(false);
735 mainWin->set_status(_("Director disconnected."));
736 QApplication::restoreOverrideCursor();
744 /* Called by signal when the Director has output for us */
745 void Console::read_dir(int /* fd */)
747 if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
748 while (read() >= 0) {
754 * When the notifier is enabled, read_dir() will automatically be
755 * called by the Qt event loop when ever there is any output
756 * from the Directory, and read_dir() will then display it on
759 * When we are in a bat dialog, we want to control *all* output
760 * from the Directory, so we set notify to off.
761 * m_console->notifiy(false);
763 void Console::notify(bool enable)
765 m_notifier->setEnabled(enable);
768 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
770 m_directorTreeItem = item;
773 void Console::setDirRes(DIRRES *dir)
779 * To have the ability to get the name of the director resource.
781 void Console::getDirResName(QString &name_returned)
783 name_returned = m_dir->name();
786 bool Console::is_connectedGui()
788 if (is_connected()) {
791 QString message("Director ");
792 message += " is currently disconnected\n Please reconnect!!";
793 QMessageBox::warning(this, "Bat",
794 tr(message.toUtf8().data()), QMessageBox::Ok );
800 * A temporary function to prevent connecting to the director if the director
801 * is busy with a restore.
803 bool Console::preventInUseConnect()
805 if (!is_connected()) {
806 QString message("Director ");
807 message += m_dir->name();
808 message += " is currently disconnected\n Please reconnect!!";
809 QMessageBox::warning(this, "Bat",
810 tr(message.toUtf8().data()), QMessageBox::Ok );
812 } else if (!m_at_main_prompt){
813 QString message("Director ");
814 message += m_dir->name();
815 message += " is currently busy\n Please complete restore or other "
816 " operation !! This is a limitation that will be resolved before a beta"
817 " release. This is currently an alpha release.";
818 QMessageBox::warning(this, "Bat",
819 tr(message.toUtf8().data()), QMessageBox::Ok );
821 } else if (!m_at_prompt){
822 QString message("Director ");
823 message += m_dir->name();
824 message += " is currently not at a prompt\n Please try again!!";
825 QMessageBox::warning(this, "Bat",
826 tr(message.toUtf8().data()), QMessageBox::Ok );
834 * Call-back for reading a passphrase for an encrypted PEM file
835 * This function uses getpass(),
836 * which uses a static buffer and is NOT thread-safe.
838 static int tls_pem_callback(char *buf, int size, const void *userdata)
843 const char *prompt = (const char *)userdata;
844 # if defined(HAVE_WIN32)
846 if (win32_cgets(buf, size) == NULL) {
855 passwd = getpass(prompt);
856 bstrncpy(buf, passwd, size);
865 /* Slot for responding to page selectors status help command */
866 void Console::consoleHelp()
872 /* Slot for responding to page selectors reload bacula-dir.conf */
873 void Console::consoleReload()
875 QString cmd("reload");
879 /* Function to get a list of volumes */
880 void Console::getVolumeList(QStringList &volumeList)
882 QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
883 if (mainWin->m_sqlDebug) {
884 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
887 if (sql_cmd(query, results)) {
889 QStringList fieldlist;
890 /* Iterate through the lines of results. */
891 foreach (QString resultline, results) {
892 fieldlist = resultline.split("\t");
893 volumeList.append(fieldlist[0]);
894 } /* foreach resultline */
895 } /* if results from query */
898 /* Function to get a list of volumes */
899 void Console::getStatusList(QStringList &statusLongList)
901 QString statusQuery("SELECT JobStatusLong FROM Status");
902 if (mainWin->m_sqlDebug) {
903 Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data());
905 QStringList statusResults;
906 if (sql_cmd(statusQuery, statusResults)) {
908 QStringList fieldlist;
909 /* Iterate through the lines of results. */
910 foreach (QString resultline, statusResults) {
911 fieldlist = resultline.split("\t");
912 statusLongList.append(fieldlist[0]);
913 } /* foreach resultline */
914 } /* if results from statusquery */