3 * Bacula GNOME Console interface to the Director
5 * Kern Sibbald, March MMII
10 Bacula® - The Network Backup Solution
12 Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
14 The main author of Bacula is Kern Sibbald, with contributions from
15 many others, a complete list can be found in the file AUTHORS.
16 This program is Free Software; you can redistribute it and/or
17 modify it under the terms of version two of the GNU General Public
18 License as published by the Free Software Foundation plus additions
19 that are listed in the file LICENSE.
21 This program is distributed in the hope that it will be useful, but
22 WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31 Bacula® is a registered trademark of John Walker.
32 The licensor of Bacula is the Free Software Foundation Europe
33 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
34 Switzerland, email:ftf@fsfeurope.org.
40 #include "interface.h"
43 /* Imported functions */
44 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
45 void select_restore_setup();
48 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
50 /* Exported variables */
51 GtkWidget *console; /* application window */
52 GtkWidget *text1; /* text window */
53 GtkWidget *entry1; /* entry box */
54 GtkWidget *status1; /* status bar */
55 GtkWidget *combo1; /* director combo */
56 GtkWidget *scroll1; /* main scroll bar */
57 GtkWidget *run_dialog; /* run dialog */
58 GtkWidget *dir_dialog; /* director selection dialog */
59 GtkWidget *restore_dialog; /* restore dialog */
60 GtkWidget *restore_file_selection;
61 GtkWidget *dir_select;
62 GtkWidget *about1; /* about box */
63 GtkWidget *label_dialog;
64 PangoFontDescription *font_desc = NULL;
65 PangoFontDescription *text_font_desc = NULL;
66 pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
67 pthread_cond_t cmd_wait;
70 BSOCK *UA_sock = NULL;
71 GList *job_list, *client_list, *fileset_list;
72 GList *messages_list, *pool_list, *storage_list;
73 GList *type_list, *level_list;
75 /* Forward referenced functions */
76 void terminate_console(int sig);
79 static gint message_handler(gpointer data);
80 static int initial_connect_to_director(gpointer data);
83 static void set_scroll_bar_to_end(void);
85 /* Static variables */
86 static char *configfile = NULL;
89 static bool director_reader_running = false;
90 static bool at_prompt = false;
91 static bool ready = false;
92 static bool quit = false;
94 static int numdir = 0;
96 #define CONFIG_FILE "./gnome-console.conf" /* default configuration file */
102 "\nVersion: %s (%s) %s %s %s\n\n"
103 "Usage: gnome-console [-s] [-c config_file] [-d debug_level] [config_file]\n"
104 " -c <file> set configuration file to file\n"
105 " -dnn set debug level to nn\n"
107 " -t test - read configuration and exit\n"
108 " -? print this message.\n"
109 "\n"), 2002, VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
115 * Call-back for reading a passphrase for an encrypted PEM file
116 * This function uses getpass(), which uses a static buffer and is NOT thread-safe.
118 static int tls_pem_callback(char *buf, int size, const void *userdata)
121 const char *prompt = (const char *) userdata;
124 passwd = getpass(prompt);
125 bstrncpy(buf, passwd, size);
126 return (strlen(buf));
135 * Make a quick check to see that we have all the
138 static int check_resources()
146 foreach_res(director, R_DIRECTOR) {
148 /* tls_require implies tls_enable */
149 if (director->tls_require) {
151 director->tls_enable = true;
153 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
159 if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && director->tls_enable) {
160 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
161 " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
162 " At least one CA certificate store is required.\n"),
163 director->hdr.name, configfile);
169 Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
170 "Without that I don't how to speak to the Director :-(\n"), configfile);
175 /* Loop over Consoles */
176 foreach_res(cons, R_CONSOLE) {
177 /* tls_require implies tls_enable */
178 if (cons->tls_require) {
180 cons->tls_enable = true;
182 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
188 if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && cons->tls_enable) {
189 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
190 " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
191 cons->hdr.name, configfile);
202 /*********************************************************************
204 * Main Bacula GNOME Console -- User Interface Program
207 int main(int argc, char *argv[])
210 int no_signals = TRUE;
211 int test_config = FALSE;
213 const char *gargv[2] = {"gnome-console", NULL};
214 CONFONTRES *con_font;
216 setlocale(LC_ALL, "");
217 bindtextdomain("bacula", LOCALEDIR);
218 textdomain("bacula");
221 my_name_is(argc, argv, "gnome-console");
222 init_msg(NULL, NULL);
223 working_directory = "/tmp";
225 struct sigaction sigignore;
226 sigignore.sa_flags = 0;
227 sigignore.sa_handler = SIG_IGN;
228 sigfillset(&sigignore.sa_mask);
229 sigaction(SIGPIPE, &sigignore, NULL);
231 if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
232 Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
236 gnome_init("bacula", VERSION, gargc, (char **)&gargv);
238 while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
240 case 'c': /* configuration file */
241 if (configfile != NULL)
243 configfile = bstrdup(optarg);
247 debug_level = atoi(optarg);
248 if (debug_level <= 0)
252 case 's': /* turn off signals */
270 init_signals(terminate_console);
277 if (configfile == NULL) {
278 configfile = bstrdup(CONFIG_FILE);
281 parse_config(configfile);
283 if (init_crypto() != 0) {
284 Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
287 if (!check_resources()) {
288 Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
291 console = create_console();
292 gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
293 run_dialog = create_RunDialog();
294 label_dialog = create_label_dialog();
295 restore_dialog = create_RestoreDialog();
296 about1 = create_about1();
298 text1 = lookup_widget(console, "text1");
299 entry1 = lookup_widget(console, "entry1");
300 status1 = lookup_widget(console, "status1");
301 scroll1 = lookup_widget(console, "scroll1");
303 select_restore_setup();
305 gtk_widget_show(console);
308 * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
312 foreach_res(con_font, R_CONSOLE_FONT) {
313 if (!con_font->fontface) {
314 Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
317 Dmsg1(100, "Now loading: %s\n",con_font->fontface);
318 text_font_desc = pango_font_description_from_string(con_font->fontface);
319 if (text_font_desc == NULL) {
320 Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
321 con_font->hdr.name, con_font->fontface);
323 Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
324 con_font->hdr.name, con_font->fontface);
330 font_desc = pango_font_description_from_string("LucidaTypewriter 9");
331 if (!text_font_desc) {
332 text_font_desc = pango_font_description_from_string("Monospace 10");
334 if (!text_font_desc) {
335 text_font_desc = pango_font_description_from_string("monospace");
338 gtk_widget_modify_font(console, font_desc);
339 gtk_widget_modify_font(entry1, font_desc);
340 gtk_widget_modify_font(status1, font_desc);
341 if (text_font_desc) {
342 gtk_widget_modify_font(text1, text_font_desc);
343 pango_font_description_free(text_font_desc);
345 gtk_widget_modify_font(text1, font_desc);
347 pango_font_description_free(font_desc);
350 terminate_console(0);
354 initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
358 disconnect_from_director((gpointer)NULL);
363 * Every 5 seconds, ask the Director for our
366 static gint message_handler(gpointer data)
368 if (ready && UA_sock) {
369 bnet_fsend(UA_sock, ".messages");
374 int disconnect_from_director(gpointer data)
377 set_status(_(" Not Connected"));
380 bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
388 * Called just after the main loop is started to allow
389 * us to connect to the Director.
391 static int initial_connect_to_director(gpointer data)
393 gtk_timeout_remove(initial);
394 if (connect_to_director(data)) {
395 start_director_reader(data);
397 gtk_timeout_add(5000, message_handler, (gpointer)NULL);
401 static GList *get_list(char *cmd)
408 while (bnet_recv(UA_sock) > 0) {
409 strip_trailing_junk(UA_sock->msg);
410 msg = (char *)malloc(strlen(UA_sock->msg) + 1);
411 strcpy(msg, UA_sock->msg);
412 options = g_list_append(options, msg);
418 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
423 combo = lookup_widget(dialog, combo_name);
424 options = get_list((char *)dircmd);
425 if (combo && options) {
426 gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
431 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
435 combo = lookup_widget(dialog, combo_name);
436 if (combo && options) {
437 gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
444 * Connect to Director. If there are more than one, put up
445 * a modal dialog so that the user chooses one.
447 int connect_to_director(gpointer data)
460 foreach_res(dir, R_DIRECTOR) {
461 dirs = g_list_append(dirs, dir->hdr.name);
464 dir_dialog = create_SelectDirectorDialog();
465 combo = lookup_widget(dir_dialog, "combo1");
466 dir_select = lookup_widget(dir_dialog, "dirselect");
468 gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
470 gtk_widget_show(dir_dialog);
474 gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
475 dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
477 g_free(ecmd); /* release director name string */
483 gtk_widget_destroy(dir_dialog);
486 /* Just take the first Director */
488 dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
496 memset(&jcr, 0, sizeof(jcr));
498 set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
499 set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
501 while (gtk_events_pending()) { /* fully paint screen */
502 gtk_main_iteration();
506 /* If cons==NULL, default console will be used */
507 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
511 /* Initialize Console TLS context */
512 if (cons && (cons->tls_enable || cons->tls_require)) {
513 /* Generate passphrase prompt */
514 bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
516 /* Initialize TLS context:
517 * Args: CA certfile, CA certdir, Certfile, Keyfile,
518 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
519 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
520 cons->tls_ca_certdir, cons->tls_certfile,
521 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
523 if (!cons->tls_ctx) {
524 bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
526 set_text(buf, strlen(buf));
527 terminate_console(0);
533 /* Initialize Director TLS context */
534 if (dir->tls_enable || dir->tls_require) {
535 /* Generate passphrase prompt */
536 bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
538 /* Initialize TLS context:
539 * Args: CA certfile, CA certdir, Certfile, Keyfile,
540 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
541 dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
542 dir->tls_ca_certdir, dir->tls_certfile,
543 dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
546 bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
548 set_text(buf, strlen(buf));
549 terminate_console(0);
555 UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
556 NULL, dir->DIRport, 0);
557 if (UA_sock == NULL) {
561 jcr.dir_bsock = UA_sock;
562 if (!authenticate_director(&jcr, dir, cons)) {
563 set_text(UA_sock->msg, UA_sock->msglen);
567 set_status(_(" Initializing ..."));
569 bnet_fsend(UA_sock, "autodisplay on");
571 /* Read and display all initial messages */
572 while (bnet_recv(UA_sock) > 0) {
573 set_text(UA_sock->msg, UA_sock->msglen);
577 while (gtk_events_pending()) {
578 gtk_main_iteration();
581 /* Fill the run_dialog combo boxes */
582 job_list = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
583 client_list = get_and_fill_combo(run_dialog, "combo_client", ".clients");
584 fileset_list = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
585 messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
586 pool_list = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
587 storage_list = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
588 type_list = get_and_fill_combo(run_dialog, "combo_type", ".types");
589 level_list = get_and_fill_combo(run_dialog, "combo_level", ".levels");
591 /* Fill the label dialog combo boxes */
592 fill_combo(label_dialog, "label_combo_storage", storage_list);
593 fill_combo(label_dialog, "label_combo_pool", pool_list);
596 /* Fill the restore_dialog combo boxes */
597 fill_combo(restore_dialog, "combo_restore_job", job_list);
598 fill_combo(restore_dialog, "combo_restore_client", client_list);
599 fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
600 fill_combo(restore_dialog, "combo_restore_pool", pool_list);
601 fill_combo(restore_dialog, "combo_restore_storage", storage_list);
603 set_status(_(" Connected"));
607 void write_director(const gchar *msg)
611 set_status(_(" Processing command ..."));
612 UA_sock->msglen = strlen(msg);
613 pm_strcpy(&UA_sock->msg, msg);
616 if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
617 disconnect_from_director((gpointer)NULL);
623 void read_director(gpointer data, gint fd, GdkInputCondition condition)
627 if (!UA_sock || UA_sock->fd != fd) {
630 stat = bnet_recv(UA_sock);
636 set_text(UA_sock->msg, UA_sock->msglen);
639 if (is_bnet_stop(UA_sock)) { /* error or term request */
643 /* Must be a signal -- either do something or ignore it */
644 if (UA_sock->msglen == BNET_PROMPT) {
646 set_status(_(" At prompt waiting for input ..."));
648 if (UA_sock->msglen == BNET_EOD) {
656 void start_director_reader(gpointer data)
659 if (director_reader_running || !UA_sock) {
662 tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
663 director_reader_running = true;
666 void stop_director_reader(gpointer data)
668 if (!director_reader_running) {
671 gdk_input_remove(tag);
672 gdk_input_remove(tag);
673 gdk_input_remove(tag);
674 gdk_input_remove(tag);
675 gdk_input_remove(tag);
676 gdk_input_remove(tag);
677 gdk_input_remove(tag);
678 gdk_input_remove(tag);
679 gdk_input_remove(tag);
680 gdk_input_remove(tag);
681 gdk_input_remove(tag);
682 director_reader_running = false;
687 /* Cleanup and then exit */
688 void terminate_console(int sig)
690 static bool already_here = false;
692 if (already_here) /* avoid recursive temination problems */
696 disconnect_from_director((gpointer)NULL);
702 /* Buffer approx 2000 lines -- assume 60 chars/line */
703 #define MAX_TEXT_CHARS (2000 * 60)
704 static int text_chars = 0;
706 static void truncate_text_chars()
708 GtkTextBuffer *textbuf;
709 GtkTextIter iter, iter2;
711 int del_chars = MAX_TEXT_CHARS / 4;
713 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
714 len = gtk_text_buffer_get_char_count(textbuf);
715 gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
716 gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
717 gtk_text_buffer_delete (textbuf, &iter, &iter2);
718 text_chars -= del_chars;
719 len = gtk_text_buffer_get_char_count(textbuf);
720 gtk_text_iter_set_offset(&iter, len);
723 void set_textf(const char *fmt, ...)
728 va_start(arg_ptr, fmt);
729 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
734 void set_text(const char *buf, int len)
736 GtkTextBuffer *textbuf;
740 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
741 buf_len = gtk_text_buffer_get_char_count(textbuf);
742 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
743 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
745 if (text_chars > MAX_TEXT_CHARS) {
746 truncate_text_chars();
748 set_scroll_bar_to_end();
751 void set_statusf(const char *fmt, ...)
756 va_start(arg_ptr, fmt);
757 len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
759 gtk_label_set_text(GTK_LABEL(status1), buf);
760 // set_scroll_bar_to_end();
764 void set_status_ready()
766 gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
768 // set_scroll_bar_to_end();
771 void set_status(const char *buf)
773 gtk_label_set_text(GTK_LABEL(status1), buf);
774 // set_scroll_bar_to_end();
778 static void set_scroll_bar_to_end(void)
780 GtkTextBuffer* textbuf = NULL;
784 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
785 buf_len = gtk_text_buffer_get_char_count(textbuf);
786 gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
787 gtk_text_iter_set_offset(&iter, buf_len);
788 gtk_text_buffer_place_cursor(textbuf, &iter);
789 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
790 gtk_text_buffer_get_mark(textbuf, "insert"),