]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
Replace explicit checks for "/" with calls to IsPathSeparator, strchr with first_path...
[bacula/bacula] / bacula / src / gnome2-console / console.c
1 /*
2  *
3  *   Bacula GNOME Console interface to the Director
4  *
5  *     Kern Sibbald, March MMII
6  *
7  *     Version $Id$
8  */
9 /*
10    Copyright (C) 2002-2006 Kern Sibbald
11
12    This program is free software; you can redistribute it and/or
13    modify it under the terms of the GNU General Public License
14    version 2 as amended with additional clauses defined in the
15    file LICENSE in the main source directory.
16
17    This program is distributed in the hope that it will be useful,
18    but WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
20    the file LICENSE for additional details.
21
22  */
23
24 #include "bacula.h"
25 #include "console.h"
26
27 #include "interface.h"
28 #include "support.h"
29
30 /* Imported functions */
31 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
32 void select_restore_setup();
33
34 /* Dummy functions */
35 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
36
37 /* Exported variables */
38 GtkWidget *console;   /* application window */
39 GtkWidget *text1;            /* text window */
40 GtkWidget *entry1;           /* entry box */
41 GtkWidget *status1;          /* status bar */
42 GtkWidget *combo1;           /* director combo */
43 GtkWidget *scroll1;          /* main scroll bar */
44 GtkWidget *run_dialog;       /* run dialog */
45 GtkWidget *dir_dialog;       /* director selection dialog */
46 GtkWidget *restore_dialog;   /* restore dialog */
47 GtkWidget *restore_file_selection;
48 GtkWidget *dir_select;
49 GtkWidget *about1;           /* about box */
50 GtkWidget *label_dialog;
51 PangoFontDescription *font_desc = NULL;
52 PangoFontDescription *text_font_desc = NULL;
53 pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
54 pthread_cond_t  cmd_wait;
55 char cmd[1000];
56 int reply;
57 BSOCK *UA_sock = NULL;
58 GList *job_list, *client_list, *fileset_list;
59 GList *messages_list, *pool_list, *storage_list;
60 GList *type_list, *level_list;
61
62 /* Forward referenced functions */
63 void terminate_console(int sig);
64
65 extern "C" {
66     static gint message_handler(gpointer data);
67     static int initial_connect_to_director(gpointer data);
68 }
69
70 static void set_scroll_bar_to_end(void);
71
72 /* Static variables */
73 static char *configfile = NULL;
74 static DIRRES *dir;
75 static int ndir;
76 static bool director_reader_running = false;
77 static bool at_prompt = false;
78 static bool ready = false;
79 static bool quit = false;
80 static guint initial;
81 static int numdir = 0;
82
83 #define CONFIG_FILE "./gnome-console.conf"   /* default configuration file */
84
85 static void usage()
86 {
87    fprintf(stderr, _(
88 "Copyright (C) 2002-2006 Kern Sibbald\n"
89 "\nVersion: %s (%s) %s %s %s\n\n"
90 "Usage: gnome-console [-s] [-c config_file] [-d debug_level] [config_file]\n"
91 "       -c <file>   set configuration file to file\n"
92 "       -dnn        set debug level to nn\n"
93 "       -s          no signals\n"
94 "       -t          test - read configuration and exit\n"
95 "       -?          print this message.\n"
96 "\n"), VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
97
98    exit(1);
99 }
100
101 /*
102  * Call-back for reading a passphrase for an encrypted PEM file
103  * This function uses getpass(), which uses a static buffer and is NOT thread-safe.
104  */
105 static int tls_pem_callback(char *buf, int size, const void *userdata)
106 {
107 #ifdef HAVE_TLS
108    const char *prompt = (const char *) userdata;
109    char *passwd;
110
111    passwd = getpass(prompt);
112    bstrncpy(buf, passwd, size);
113    return (strlen(buf));
114 #else
115    buf[0] = 0;
116    return 0;
117 #endif
118 }
119
120
121 /*
122  * Make a quick check to see that we have all the
123  * resources needed.
124  */
125 static int check_resources()
126 {
127    bool ok = true;
128    DIRRES *director;
129
130    LockRes();
131
132    numdir = 0;
133    foreach_res(director, R_DIRECTOR) {
134       numdir++;
135       /* tls_require implies tls_enable */
136       if (director->tls_require) {
137          if (have_tls) {
138             director->tls_enable = true;
139          } else {
140             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
141             ok = false;
142             continue;
143          }
144       }
145
146       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && director->tls_enable) {
147          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
148                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
149                              " At least one CA certificate store is required.\n"),
150                              director->hdr.name, configfile);
151          ok = false;
152       }
153    }
154    
155    if (numdir == 0) {
156       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
157                           "Without that I don't how to speak to the Director :-(\n"), configfile);
158       ok = false;
159    }
160
161    CONRES *cons;
162    /* Loop over Consoles */
163    foreach_res(cons, R_CONSOLE) {
164       /* tls_require implies tls_enable */
165       if (cons->tls_require) {
166          if (have_tls) {
167             cons->tls_enable = true;
168          } else {
169             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
170             ok = false;
171             continue;
172          }
173       }
174
175       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && cons->tls_enable) {
176          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
177                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
178                              cons->hdr.name, configfile);
179          ok = false;
180       }
181    }
182
183    UnlockRes();
184
185    return ok;
186 }
187
188
189 /*********************************************************************
190  *
191  *         Main Bacula GNOME Console -- User Interface Program
192  *
193  */
194 int main(int argc, char *argv[])
195 {
196    int ch, stat;
197    int no_signals = TRUE;
198    int test_config = FALSE;
199    int gargc = 1;
200    const char *gargv[2] = {"gnome-console", NULL};
201    CONFONTRES *con_font;
202
203    setlocale(LC_ALL, "");
204    bindtextdomain("bacula", LOCALEDIR);
205    textdomain("bacula");
206
207    init_stack_dump();
208    my_name_is(argc, argv, "gnome-console");
209    init_msg(NULL, NULL);
210    working_directory  = "/tmp";
211
212    struct sigaction sigignore;
213    sigignore.sa_flags = 0;
214    sigignore.sa_handler = SIG_IGN;
215    sigfillset(&sigignore.sa_mask);
216    sigaction(SIGPIPE, &sigignore, NULL);
217
218    if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
219       Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
220          strerror(stat));
221    }
222
223    gnome_init("bacula", VERSION, gargc, (char **)&gargv);
224
225    while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
226       switch (ch) {
227       case 'c':                    /* configuration file */
228          if (configfile != NULL)
229             free(configfile);
230          configfile = bstrdup(optarg);
231          break;
232
233       case 'd':
234          debug_level = atoi(optarg);
235          if (debug_level <= 0)
236             debug_level = 1;
237          break;
238
239       case 's':                    /* turn off signals */
240          no_signals = TRUE;
241          break;
242
243       case 't':
244          test_config = TRUE;
245          break;
246
247       case '?':
248       default:
249          usage();
250       }
251    }
252    argc -= optind;
253    argv += optind;
254
255
256    if (!no_signals) {
257       init_signals(terminate_console);
258    }
259
260    if (argc) {
261       usage();
262    }
263
264    if (configfile == NULL) {
265       configfile = bstrdup(CONFIG_FILE);
266    }
267
268    parse_config(configfile);
269
270    if (init_crypto() != 0) {
271       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
272    }
273
274    if (!check_resources()) {
275       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
276    }
277
278    console = create_console();
279    gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
280    run_dialog = create_RunDialog();
281    label_dialog = create_label_dialog();
282    restore_dialog = create_RestoreDialog();
283    about1 = create_about1();
284
285    text1 = lookup_widget(console, "text1");
286    entry1 = lookup_widget(console, "entry1");
287    status1 = lookup_widget(console, "status1");
288    scroll1 = lookup_widget(console, "scroll1");
289
290    select_restore_setup();
291
292    gtk_widget_show(console);
293
294 /*
295  * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
296  */
297
298    LockRes();
299    foreach_res(con_font, R_CONSOLE_FONT) {
300        if (!con_font->fontface) {
301           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
302           continue;
303        }
304        Dmsg1(100, "Now loading: %s\n",con_font->fontface);
305        text_font_desc = pango_font_description_from_string(con_font->fontface);
306        if (text_font_desc == NULL) {
307            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
308                   con_font->hdr.name, con_font->fontface);
309        } else {
310            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
311                   con_font->hdr.name, con_font->fontface);
312            break;
313        }
314    }
315    UnlockRes();
316     
317    font_desc = pango_font_description_from_string("LucidaTypewriter 9");
318    if (!text_font_desc) {
319       text_font_desc = pango_font_description_from_string("Monospace 10");
320    }
321    if (!text_font_desc) {
322       text_font_desc = pango_font_description_from_string("monospace");
323    }
324
325    gtk_widget_modify_font(console, font_desc);
326    gtk_widget_modify_font(entry1, font_desc);
327    gtk_widget_modify_font(status1, font_desc);
328    if (text_font_desc) {
329       gtk_widget_modify_font(text1, text_font_desc);
330       pango_font_description_free(text_font_desc);
331    } else {
332       gtk_widget_modify_font(text1, font_desc);
333    }
334    pango_font_description_free(font_desc);
335
336    if (test_config) {
337       terminate_console(0);
338       exit(0);
339    }
340
341    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
342
343    gtk_main();
344    quit = true;
345    disconnect_from_director((gpointer)NULL);
346    return 0;
347 }
348
349 /*
350  * Every 5 seconds, ask the Director for our
351  *  messages.
352  */
353 static gint message_handler(gpointer data)
354 {
355    if (ready && UA_sock) {
356       bnet_fsend(UA_sock, ".messages");
357    }
358    return TRUE;
359 }
360
361 int disconnect_from_director(gpointer data)
362 {
363    if (!quit) {
364       set_status(_(" Not Connected"));
365    }
366    if (UA_sock) {
367       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
368       bnet_close(UA_sock);
369       UA_sock = NULL;
370    }
371    return 1;
372 }
373
374 /*
375  * Called just after the main loop is started to allow
376  *  us to connect to the Director.
377  */
378 static int initial_connect_to_director(gpointer data)
379 {
380    gtk_timeout_remove(initial);
381    if (connect_to_director(data)) {
382       start_director_reader(data);
383    }
384    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
385    return TRUE;
386 }
387
388 static GList *get_list(char *cmd)
389 {
390    GList *options;
391    char *msg;
392
393    options = NULL;
394    write_director(cmd);
395    while (bnet_recv(UA_sock) > 0) {
396       strip_trailing_junk(UA_sock->msg);
397       msg = (char *)malloc(strlen(UA_sock->msg) + 1);
398       strcpy(msg, UA_sock->msg);
399       options = g_list_append(options, msg);
400    }
401    return options;
402
403 }
404
405 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
406 {
407    GtkWidget *combo;
408    GList *options;
409
410    combo = lookup_widget(dialog, combo_name);
411    options = get_list((char *)dircmd);
412    if (combo && options) {
413       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
414    }
415    return options;
416 }
417
418 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
419 {
420    GtkWidget *combo;
421
422    combo = lookup_widget(dialog, combo_name);
423    if (combo && options) {
424       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
425    }
426    return;
427 }
428
429
430 /*
431  * Connect to Director. If there are more than one, put up
432  * a modal dialog so that the user chooses one.
433  */
434 int connect_to_director(gpointer data)
435 {
436    GList *dirs = NULL;
437    GtkWidget *combo;
438    JCR jcr;
439
440
441    if (UA_sock) {
442       return 0;
443    }
444
445    if (ndir > 1) {
446       LockRes();
447       foreach_res(dir, R_DIRECTOR) {
448          dirs = g_list_append(dirs, dir->hdr.name);
449       }
450       UnlockRes();
451       dir_dialog = create_SelectDirectorDialog();
452       combo = lookup_widget(dir_dialog, "combo1");
453       dir_select = lookup_widget(dir_dialog, "dirselect");
454       if (dirs) {
455          gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
456       }
457       gtk_widget_show(dir_dialog);
458       gtk_main();
459
460       if (reply == OK) {
461          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
462          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
463          if (ecmd) {
464             g_free(ecmd);             /* release director name string */
465          }
466       }
467       if (dirs) {
468          g_free(dirs);
469       }
470       gtk_widget_destroy(dir_dialog);
471       dir_dialog = NULL;
472    } else {
473       /* Just take the first Director */
474       LockRes();
475       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
476       UnlockRes();
477    }
478
479    if (!dir) {
480       return 0;
481    }
482
483    memset(&jcr, 0, sizeof(jcr));
484
485    set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
486    set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
487
488    while (gtk_events_pending()) {     /* fully paint screen */
489       gtk_main_iteration();
490    }
491
492    LockRes();
493    /* If cons==NULL, default console will be used */
494    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
495    UnlockRes();
496
497    char buf[1024];
498    /* Initialize Console TLS context */
499    if (cons && (cons->tls_enable || cons->tls_require)) {
500       /* Generate passphrase prompt */
501       bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
502
503       /* Initialize TLS context:
504        * Args: CA certfile, CA certdir, Certfile, Keyfile,
505        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
506       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
507          cons->tls_ca_certdir, cons->tls_certfile,
508          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
509
510       if (!cons->tls_ctx) {
511          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
512             dir->hdr.name);
513          set_text(buf, strlen(buf));
514          terminate_console(0);
515          return 1;
516       }
517
518    }
519
520    /* Initialize Director TLS context */
521    if (dir->tls_enable || dir->tls_require) {
522       /* Generate passphrase prompt */
523       bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
524
525       /* Initialize TLS context:
526        * Args: CA certfile, CA certdir, Certfile, Keyfile,
527        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
528       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
529          dir->tls_ca_certdir, dir->tls_certfile,
530          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
531
532       if (!dir->tls_ctx) {
533          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
534             dir->hdr.name);
535          set_text(buf, strlen(buf));
536          terminate_console(0);
537          return 1;
538       }
539    }
540
541
542    UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
543                           NULL, dir->DIRport, 0);
544    if (UA_sock == NULL) {
545       return 0;
546    }
547
548    jcr.dir_bsock = UA_sock;
549    if (!authenticate_director(&jcr, dir, cons)) {
550       set_text(UA_sock->msg, UA_sock->msglen);
551       return 0;
552    }
553
554    set_status(_(" Initializing ..."));
555
556    bnet_fsend(UA_sock, "autodisplay on");
557
558    /* Read and display all initial messages */
559    while (bnet_recv(UA_sock) > 0) {
560       set_text(UA_sock->msg, UA_sock->msglen);
561    }
562
563    /* Paint changes */
564    while (gtk_events_pending()) {
565       gtk_main_iteration();
566    }
567
568    /* Fill the run_dialog combo boxes */
569    job_list      = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
570    client_list   = get_and_fill_combo(run_dialog, "combo_client", ".clients");
571    fileset_list  = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
572    messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
573    pool_list     = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
574    storage_list  = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
575    type_list     = get_and_fill_combo(run_dialog, "combo_type", ".types");
576    level_list    = get_and_fill_combo(run_dialog, "combo_level", ".levels");
577
578    /* Fill the label dialog combo boxes */
579    fill_combo(label_dialog, "label_combo_storage", storage_list);
580    fill_combo(label_dialog, "label_combo_pool", pool_list);
581
582
583    /* Fill the restore_dialog combo boxes */
584    fill_combo(restore_dialog, "combo_restore_job", job_list);
585    fill_combo(restore_dialog, "combo_restore_client", client_list);
586    fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
587    fill_combo(restore_dialog, "combo_restore_pool", pool_list);
588    fill_combo(restore_dialog, "combo_restore_storage", storage_list);
589
590    set_status(_(" Connected"));
591    return 1;
592 }
593
594 void write_director(const gchar *msg)
595 {
596    if (UA_sock) {
597       at_prompt = false;
598       set_status(_(" Processing command ..."));
599       UA_sock->msglen = strlen(msg);
600       pm_strcpy(&UA_sock->msg, msg);
601       bnet_send(UA_sock);
602    }
603    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
604       disconnect_from_director((gpointer)NULL);
605       gtk_main_quit();
606    }
607 }
608
609 extern "C"
610 void read_director(gpointer data, gint fd, GdkInputCondition condition)
611 {
612    int stat;
613
614    if (!UA_sock || UA_sock->fd != fd) {
615       return;
616    }
617    stat = bnet_recv(UA_sock);
618    if (stat >= 0) {
619       if (at_prompt) {
620          set_text("\n", 1);
621          at_prompt = false;
622       }
623       set_text(UA_sock->msg, UA_sock->msglen);
624       return;
625    }
626    if (is_bnet_stop(UA_sock)) {         /* error or term request */
627       gtk_main_quit();
628       return;
629    }
630    /* Must be a signal -- either do something or ignore it */
631    if (UA_sock->msglen == BNET_PROMPT) {
632       at_prompt = true;
633       set_status(_(" At prompt waiting for input ..."));
634    }
635    if (UA_sock->msglen == BNET_EOD) {
636       set_status_ready();
637    }
638    return;
639 }
640
641 static gint tag;
642
643 void start_director_reader(gpointer data)
644 {
645
646    if (director_reader_running || !UA_sock) {
647       return;
648    }
649    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
650    director_reader_running = true;
651 }
652
653 void stop_director_reader(gpointer data)
654 {
655    if (!director_reader_running) {
656       return;
657    }
658    gdk_input_remove(tag);
659    gdk_input_remove(tag);
660    gdk_input_remove(tag);
661    gdk_input_remove(tag);
662    gdk_input_remove(tag);
663    gdk_input_remove(tag);
664    gdk_input_remove(tag);
665    gdk_input_remove(tag);
666    gdk_input_remove(tag);
667    gdk_input_remove(tag);
668    gdk_input_remove(tag);
669    director_reader_running = false;
670 }
671
672
673
674 /* Cleanup and then exit */
675 void terminate_console(int sig)
676 {
677    static bool already_here = false;
678
679    if (already_here)                  /* avoid recursive temination problems */
680       exit(1);
681    already_here = true;
682    cleanup_crypto();
683    disconnect_from_director((gpointer)NULL);
684    gtk_main_quit();
685    exit(0);
686 }
687
688
689 /* Buffer approx 2000 lines -- assume 60 chars/line */
690 #define MAX_TEXT_CHARS   (2000 * 60)
691 static int text_chars = 0;
692
693 static void truncate_text_chars()
694 {
695    GtkTextBuffer *textbuf;
696    GtkTextIter iter, iter2;
697    guint len;
698    int del_chars = MAX_TEXT_CHARS / 4;
699
700    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
701    len = gtk_text_buffer_get_char_count(textbuf);
702    gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
703    gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
704    gtk_text_buffer_delete (textbuf, &iter, &iter2);
705    text_chars -= del_chars;
706    len = gtk_text_buffer_get_char_count(textbuf);
707    gtk_text_iter_set_offset(&iter, len);
708 }
709
710 void set_textf(const char *fmt, ...)
711 {
712    va_list arg_ptr;
713    char buf[1000];
714    int len;
715    va_start(arg_ptr, fmt);
716    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
717    va_end(arg_ptr);
718    set_text(buf, len);
719 }
720
721 void set_text(const char *buf, int len)
722 {
723    GtkTextBuffer *textbuf;
724    GtkTextIter iter;
725    guint buf_len;
726
727    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
728    buf_len = gtk_text_buffer_get_char_count(textbuf);
729    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
730    gtk_text_buffer_insert(textbuf, &iter, buf, -1);
731    text_chars += len;
732    if (text_chars > MAX_TEXT_CHARS) {
733       truncate_text_chars();
734    }
735    set_scroll_bar_to_end();
736 }
737
738 void set_statusf(const char *fmt, ...)
739 {
740    va_list arg_ptr;
741    char buf[1000];
742    int len;
743    va_start(arg_ptr, fmt);
744    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
745    va_end(arg_ptr);
746    gtk_label_set_text(GTK_LABEL(status1), buf);
747 // set_scroll_bar_to_end();
748    ready = false;
749 }
750
751 void set_status_ready()
752 {
753    gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
754    ready = true;
755 // set_scroll_bar_to_end();
756 }
757
758 void set_status(const char *buf)
759 {
760    gtk_label_set_text(GTK_LABEL(status1), buf);
761 // set_scroll_bar_to_end();
762    ready = false;
763 }
764
765 static void set_scroll_bar_to_end(void)
766 {
767    GtkTextBuffer* textbuf = NULL;
768    GtkTextIter iter;
769    guint buf_len;
770
771    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
772    buf_len = gtk_text_buffer_get_char_count(textbuf);
773    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
774    gtk_text_iter_set_offset(&iter, buf_len);
775    gtk_text_buffer_place_cursor(textbuf, &iter);
776    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
777               gtk_text_buffer_get_mark(textbuf, "insert"),
778               0, TRUE, 0.0, 1.0);
779 }