]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
kes More copyright changes.
[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    Bacula® - The Network Backup Solution
11
12    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
13
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.
20
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.
25
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
29    02110-1301, USA.
30
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.
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
47 /* Dummy functions */
48 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
49
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;
68 char cmd[1000];
69 int reply;
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;
74
75 /* Forward referenced functions */
76 void terminate_console(int sig);
77
78 extern "C" {
79     static gint message_handler(gpointer data);
80     static int initial_connect_to_director(gpointer data);
81 }
82
83 static void set_scroll_bar_to_end(void);
84
85 /* Static variables */
86 static char *configfile = NULL;
87 static DIRRES *dir;
88 static int ndir;
89 static bool director_reader_running = false;
90 static bool at_prompt = false;
91 static bool ready = false;
92 static bool quit = false;
93 static guint initial;
94 static int numdir = 0;
95
96 #define CONFIG_FILE "./gnome-console.conf"   /* default configuration file */
97
98 static void usage()
99 {
100    fprintf(stderr, _(
101 PROG_COPYRIGHT
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"
106 "       -s          no signals\n"
107 "       -t          test - read configuration and exit\n"
108 "       -?          print this message.\n"
109 "\n"), 2002, VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
110
111    exit(1);
112 }
113
114 /*
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.
117  */
118 static int tls_pem_callback(char *buf, int size, const void *userdata)
119 {
120 #ifdef HAVE_TLS
121    const char *prompt = (const char *) userdata;
122    char *passwd;
123
124    passwd = getpass(prompt);
125    bstrncpy(buf, passwd, size);
126    return (strlen(buf));
127 #else
128    buf[0] = 0;
129    return 0;
130 #endif
131 }
132
133
134 /*
135  * Make a quick check to see that we have all the
136  * resources needed.
137  */
138 static int check_resources()
139 {
140    bool ok = true;
141    DIRRES *director;
142
143    LockRes();
144
145    numdir = 0;
146    foreach_res(director, R_DIRECTOR) {
147       numdir++;
148       /* tls_require implies tls_enable */
149       if (director->tls_require) {
150          if (have_tls) {
151             director->tls_enable = true;
152          } else {
153             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
154             ok = false;
155             continue;
156          }
157       }
158
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);
164          ok = false;
165       }
166    }
167    
168    if (numdir == 0) {
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);
171       ok = false;
172    }
173
174    CONRES *cons;
175    /* Loop over Consoles */
176    foreach_res(cons, R_CONSOLE) {
177       /* tls_require implies tls_enable */
178       if (cons->tls_require) {
179          if (have_tls) {
180             cons->tls_enable = true;
181          } else {
182             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
183             ok = false;
184             continue;
185          }
186       }
187
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);
192          ok = false;
193       }
194    }
195
196    UnlockRes();
197
198    return ok;
199 }
200
201
202 /*********************************************************************
203  *
204  *         Main Bacula GNOME Console -- User Interface Program
205  *
206  */
207 int main(int argc, char *argv[])
208 {
209    int ch, stat;
210    int no_signals = TRUE;
211    int test_config = FALSE;
212    int gargc = 1;
213    const char *gargv[2] = {"gnome-console", NULL};
214    CONFONTRES *con_font;
215
216    setlocale(LC_ALL, "");
217    bindtextdomain("bacula", LOCALEDIR);
218    textdomain("bacula");
219
220    init_stack_dump();
221    my_name_is(argc, argv, "gnome-console");
222    init_msg(NULL, NULL);
223    working_directory  = "/tmp";
224
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);
230
231    if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
232       Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
233          strerror(stat));
234    }
235
236    gnome_init("bacula", VERSION, gargc, (char **)&gargv);
237
238    while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
239       switch (ch) {
240       case 'c':                    /* configuration file */
241          if (configfile != NULL)
242             free(configfile);
243          configfile = bstrdup(optarg);
244          break;
245
246       case 'd':
247          debug_level = atoi(optarg);
248          if (debug_level <= 0)
249             debug_level = 1;
250          break;
251
252       case 's':                    /* turn off signals */
253          no_signals = TRUE;
254          break;
255
256       case 't':
257          test_config = TRUE;
258          break;
259
260       case '?':
261       default:
262          usage();
263       }
264    }
265    argc -= optind;
266    argv += optind;
267
268
269    if (!no_signals) {
270       init_signals(terminate_console);
271    }
272
273    if (argc) {
274       usage();
275    }
276
277    if (configfile == NULL) {
278       configfile = bstrdup(CONFIG_FILE);
279    }
280
281    parse_config(configfile);
282
283    if (init_crypto() != 0) {
284       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
285    }
286
287    if (!check_resources()) {
288       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
289    }
290
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();
297
298    text1 = lookup_widget(console, "text1");
299    entry1 = lookup_widget(console, "entry1");
300    status1 = lookup_widget(console, "status1");
301    scroll1 = lookup_widget(console, "scroll1");
302
303    select_restore_setup();
304
305    gtk_widget_show(console);
306
307 /*
308  * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
309  */
310
311    LockRes();
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);
315           continue;
316        }
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);
322        } else {
323            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
324                   con_font->hdr.name, con_font->fontface);
325            break;
326        }
327    }
328    UnlockRes();
329     
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");
333    }
334    if (!text_font_desc) {
335       text_font_desc = pango_font_description_from_string("monospace");
336    }
337
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);
344    } else {
345       gtk_widget_modify_font(text1, font_desc);
346    }
347    pango_font_description_free(font_desc);
348
349    if (test_config) {
350       terminate_console(0);
351       exit(0);
352    }
353
354    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
355
356    gtk_main();
357    quit = true;
358    disconnect_from_director((gpointer)NULL);
359    return 0;
360 }
361
362 /*
363  * Every 5 seconds, ask the Director for our
364  *  messages.
365  */
366 static gint message_handler(gpointer data)
367 {
368    if (ready && UA_sock) {
369       bnet_fsend(UA_sock, ".messages");
370    }
371    return TRUE;
372 }
373
374 int disconnect_from_director(gpointer data)
375 {
376    if (!quit) {
377       set_status(_(" Not Connected"));
378    }
379    if (UA_sock) {
380       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
381       bnet_close(UA_sock);
382       UA_sock = NULL;
383    }
384    return 1;
385 }
386
387 /*
388  * Called just after the main loop is started to allow
389  *  us to connect to the Director.
390  */
391 static int initial_connect_to_director(gpointer data)
392 {
393    gtk_timeout_remove(initial);
394    if (connect_to_director(data)) {
395       start_director_reader(data);
396    }
397    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
398    return TRUE;
399 }
400
401 static GList *get_list(char *cmd)
402 {
403    GList *options;
404    char *msg;
405
406    options = NULL;
407    write_director(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);
413    }
414    return options;
415
416 }
417
418 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
419 {
420    GtkWidget *combo;
421    GList *options;
422
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);
427    }
428    return options;
429 }
430
431 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
432 {
433    GtkWidget *combo;
434
435    combo = lookup_widget(dialog, combo_name);
436    if (combo && options) {
437       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
438    }
439    return;
440 }
441
442
443 /*
444  * Connect to Director. If there are more than one, put up
445  * a modal dialog so that the user chooses one.
446  */
447 int connect_to_director(gpointer data)
448 {
449    GList *dirs = NULL;
450    GtkWidget *combo;
451    JCR jcr;
452
453
454    if (UA_sock) {
455       return 0;
456    }
457
458    if (ndir > 1) {
459       LockRes();
460       foreach_res(dir, R_DIRECTOR) {
461          dirs = g_list_append(dirs, dir->hdr.name);
462       }
463       UnlockRes();
464       dir_dialog = create_SelectDirectorDialog();
465       combo = lookup_widget(dir_dialog, "combo1");
466       dir_select = lookup_widget(dir_dialog, "dirselect");
467       if (dirs) {
468          gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
469       }
470       gtk_widget_show(dir_dialog);
471       gtk_main();
472
473       if (reply == OK) {
474          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
475          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
476          if (ecmd) {
477             g_free(ecmd);             /* release director name string */
478          }
479       }
480       if (dirs) {
481          g_free(dirs);
482       }
483       gtk_widget_destroy(dir_dialog);
484       dir_dialog = NULL;
485    } else {
486       /* Just take the first Director */
487       LockRes();
488       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
489       UnlockRes();
490    }
491
492    if (!dir) {
493       return 0;
494    }
495
496    memset(&jcr, 0, sizeof(jcr));
497
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);
500
501    while (gtk_events_pending()) {     /* fully paint screen */
502       gtk_main_iteration();
503    }
504
505    LockRes();
506    /* If cons==NULL, default console will be used */
507    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
508    UnlockRes();
509
510    char buf[1024];
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);
515
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);
522
523       if (!cons->tls_ctx) {
524          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
525             dir->hdr.name);
526          set_text(buf, strlen(buf));
527          terminate_console(0);
528          return 1;
529       }
530
531    }
532
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);
537
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);
544
545       if (!dir->tls_ctx) {
546          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
547             dir->hdr.name);
548          set_text(buf, strlen(buf));
549          terminate_console(0);
550          return 1;
551       }
552    }
553
554
555    UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
556                           NULL, dir->DIRport, 0);
557    if (UA_sock == NULL) {
558       return 0;
559    }
560
561    jcr.dir_bsock = UA_sock;
562    if (!authenticate_director(&jcr, dir, cons)) {
563       set_text(UA_sock->msg, UA_sock->msglen);
564       return 0;
565    }
566
567    set_status(_(" Initializing ..."));
568
569    bnet_fsend(UA_sock, "autodisplay on");
570
571    /* Read and display all initial messages */
572    while (bnet_recv(UA_sock) > 0) {
573       set_text(UA_sock->msg, UA_sock->msglen);
574    }
575
576    /* Paint changes */
577    while (gtk_events_pending()) {
578       gtk_main_iteration();
579    }
580
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");
590
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);
594
595
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);
602
603    set_status(_(" Connected"));
604    return 1;
605 }
606
607 void write_director(const gchar *msg)
608 {
609    if (UA_sock) {
610       at_prompt = false;
611       set_status(_(" Processing command ..."));
612       UA_sock->msglen = strlen(msg);
613       pm_strcpy(&UA_sock->msg, msg);
614       bnet_send(UA_sock);
615    }
616    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
617       disconnect_from_director((gpointer)NULL);
618       gtk_main_quit();
619    }
620 }
621
622 extern "C"
623 void read_director(gpointer data, gint fd, GdkInputCondition condition)
624 {
625    int stat;
626
627    if (!UA_sock || UA_sock->fd != fd) {
628       return;
629    }
630    stat = bnet_recv(UA_sock);
631    if (stat >= 0) {
632       if (at_prompt) {
633          set_text("\n", 1);
634          at_prompt = false;
635       }
636       set_text(UA_sock->msg, UA_sock->msglen);
637       return;
638    }
639    if (is_bnet_stop(UA_sock)) {         /* error or term request */
640       gtk_main_quit();
641       return;
642    }
643    /* Must be a signal -- either do something or ignore it */
644    if (UA_sock->msglen == BNET_PROMPT) {
645       at_prompt = true;
646       set_status(_(" At prompt waiting for input ..."));
647    }
648    if (UA_sock->msglen == BNET_EOD) {
649       set_status_ready();
650    }
651    return;
652 }
653
654 static gint tag;
655
656 void start_director_reader(gpointer data)
657 {
658
659    if (director_reader_running || !UA_sock) {
660       return;
661    }
662    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
663    director_reader_running = true;
664 }
665
666 void stop_director_reader(gpointer data)
667 {
668    if (!director_reader_running) {
669       return;
670    }
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;
683 }
684
685
686
687 /* Cleanup and then exit */
688 void terminate_console(int sig)
689 {
690    static bool already_here = false;
691
692    if (already_here)                  /* avoid recursive temination problems */
693       exit(1);
694    already_here = true;
695    cleanup_crypto();
696    disconnect_from_director((gpointer)NULL);
697    gtk_main_quit();
698    exit(0);
699 }
700
701
702 /* Buffer approx 2000 lines -- assume 60 chars/line */
703 #define MAX_TEXT_CHARS   (2000 * 60)
704 static int text_chars = 0;
705
706 static void truncate_text_chars()
707 {
708    GtkTextBuffer *textbuf;
709    GtkTextIter iter, iter2;
710    guint len;
711    int del_chars = MAX_TEXT_CHARS / 4;
712
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);
721 }
722
723 void set_textf(const char *fmt, ...)
724 {
725    va_list arg_ptr;
726    char buf[1000];
727    int len;
728    va_start(arg_ptr, fmt);
729    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
730    va_end(arg_ptr);
731    set_text(buf, len);
732 }
733
734 void set_text(const char *buf, int len)
735 {
736    GtkTextBuffer *textbuf;
737    GtkTextIter iter;
738    guint buf_len;
739
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);
744    text_chars += len;
745    if (text_chars > MAX_TEXT_CHARS) {
746       truncate_text_chars();
747    }
748    set_scroll_bar_to_end();
749 }
750
751 void set_statusf(const char *fmt, ...)
752 {
753    va_list arg_ptr;
754    char buf[1000];
755    int len;
756    va_start(arg_ptr, fmt);
757    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
758    va_end(arg_ptr);
759    gtk_label_set_text(GTK_LABEL(status1), buf);
760 // set_scroll_bar_to_end();
761    ready = false;
762 }
763
764 void set_status_ready()
765 {
766    gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
767    ready = true;
768 // set_scroll_bar_to_end();
769 }
770
771 void set_status(const char *buf)
772 {
773    gtk_label_set_text(GTK_LABEL(status1), buf);
774 // set_scroll_bar_to_end();
775    ready = false;
776 }
777
778 static void set_scroll_bar_to_end(void)
779 {
780    GtkTextBuffer* textbuf = NULL;
781    GtkTextIter iter;
782    guint buf_len;
783
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"),
791               0, TRUE, 0.0, 1.0);
792 }