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