]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
Fix bug # 746 - Windows FD crashes when job canceled
[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 #ifdef ENABLE_NLS
217    setlocale(LC_ALL, "");
218    bindtextdomain("bacula", LOCALEDIR);
219    textdomain("bacula");
220 #endif
221
222    init_stack_dump();
223    my_name_is(argc, argv, "gnome-console");
224    init_msg(NULL, NULL);
225    working_directory  = "/tmp";
226
227    struct sigaction sigignore;
228    sigignore.sa_flags = 0;
229    sigignore.sa_handler = SIG_IGN;
230    sigfillset(&sigignore.sa_mask);
231    sigaction(SIGPIPE, &sigignore, NULL);
232
233    if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
234       Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
235          strerror(stat));
236    }
237
238    gnome_init("bacula", VERSION, gargc, (char **)&gargv);
239
240    while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
241       switch (ch) {
242       case 'c':                    /* configuration file */
243          if (configfile != NULL)
244             free(configfile);
245          configfile = bstrdup(optarg);
246          break;
247
248       case 'd':
249          debug_level = atoi(optarg);
250          if (debug_level <= 0)
251             debug_level = 1;
252          break;
253
254       case 's':                    /* turn off signals */
255          no_signals = TRUE;
256          break;
257
258       case 't':
259          test_config = TRUE;
260          break;
261
262       case '?':
263       default:
264          usage();
265       }
266    }
267    argc -= optind;
268    argv += optind;
269
270
271    if (!no_signals) {
272       init_signals(terminate_console);
273    }
274
275    if (argc) {
276       usage();
277    }
278
279    if (configfile == NULL) {
280       configfile = bstrdup(CONFIG_FILE);
281    }
282
283    parse_config(configfile);
284
285    if (init_crypto() != 0) {
286       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
287    }
288
289    if (!check_resources()) {
290       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
291    }
292
293    console = create_console();
294    gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
295    run_dialog = create_RunDialog();
296    label_dialog = create_label_dialog();
297    restore_dialog = create_RestoreDialog();
298    about1 = create_about1();
299
300    text1 = lookup_widget(console, "text1");
301    entry1 = lookup_widget(console, "entry1");
302    status1 = lookup_widget(console, "status1");
303    scroll1 = lookup_widget(console, "scroll1");
304
305    select_restore_setup();
306
307    gtk_widget_show(console);
308
309 /*
310  * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
311  */
312
313    LockRes();
314    foreach_res(con_font, R_CONSOLE_FONT) {
315        if (!con_font->fontface) {
316           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
317           continue;
318        }
319        Dmsg1(100, "Now loading: %s\n",con_font->fontface);
320        text_font_desc = pango_font_description_from_string(con_font->fontface);
321        if (text_font_desc == NULL) {
322            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
323                   con_font->hdr.name, con_font->fontface);
324        } else {
325            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
326                   con_font->hdr.name, con_font->fontface);
327            break;
328        }
329    }
330    UnlockRes();
331     
332    font_desc = pango_font_description_from_string("LucidaTypewriter 9");
333    if (!text_font_desc) {
334       text_font_desc = pango_font_description_from_string("Monospace 10");
335    }
336    if (!text_font_desc) {
337       text_font_desc = pango_font_description_from_string("monospace");
338    }
339
340    gtk_widget_modify_font(console, font_desc);
341    gtk_widget_modify_font(entry1, font_desc);
342    gtk_widget_modify_font(status1, font_desc);
343    if (text_font_desc) {
344       gtk_widget_modify_font(text1, text_font_desc);
345       pango_font_description_free(text_font_desc);
346    } else {
347       gtk_widget_modify_font(text1, font_desc);
348    }
349    pango_font_description_free(font_desc);
350
351    if (test_config) {
352       terminate_console(0);
353       exit(0);
354    }
355
356    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
357
358    gtk_main();
359    quit = true;
360    disconnect_from_director((gpointer)NULL);
361    return 0;
362 }
363
364 /*
365  * Every 5 seconds, ask the Director for our
366  *  messages.
367  */
368 static gint message_handler(gpointer data)
369 {
370    if (ready && UA_sock) {
371       bnet_fsend(UA_sock, ".messages");
372    }
373    return TRUE;
374 }
375
376 int disconnect_from_director(gpointer data)
377 {
378    if (!quit) {
379       set_status(_(" Not Connected"));
380    }
381    if (UA_sock) {
382       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
383       bnet_close(UA_sock);
384       UA_sock = NULL;
385    }
386    return 1;
387 }
388
389 /*
390  * Called just after the main loop is started to allow
391  *  us to connect to the Director.
392  */
393 static int initial_connect_to_director(gpointer data)
394 {
395    gtk_timeout_remove(initial);
396    if (connect_to_director(data)) {
397       start_director_reader(data);
398    }
399    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
400    return TRUE;
401 }
402
403 static GList *get_list(char *cmd)
404 {
405    GList *options;
406    char *msg;
407
408    options = NULL;
409    write_director(cmd);
410    while (bnet_recv(UA_sock) > 0) {
411       strip_trailing_junk(UA_sock->msg);
412       msg = (char *)malloc(strlen(UA_sock->msg) + 1);
413       strcpy(msg, UA_sock->msg);
414       options = g_list_append(options, msg);
415    }
416    return options;
417
418 }
419
420 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
421 {
422    GtkWidget *combo;
423    GList *options;
424
425    combo = lookup_widget(dialog, combo_name);
426    options = get_list((char *)dircmd);
427    if (combo && options) {
428       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
429    }
430    return options;
431 }
432
433 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
434 {
435    GtkWidget *combo;
436
437    combo = lookup_widget(dialog, combo_name);
438    if (combo && options) {
439       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
440    }
441    return;
442 }
443
444
445 /*
446  * Connect to Director. If there are more than one, put up
447  * a modal dialog so that the user chooses one.
448  */
449 int connect_to_director(gpointer data)
450 {
451    GList *dirs = NULL;
452    GtkWidget *combo;
453    JCR jcr;
454
455
456    if (UA_sock) {
457       return 0;
458    }
459
460    if (ndir > 1) {
461       LockRes();
462       foreach_res(dir, R_DIRECTOR) {
463          dirs = g_list_append(dirs, dir->hdr.name);
464       }
465       UnlockRes();
466       dir_dialog = create_SelectDirectorDialog();
467       combo = lookup_widget(dir_dialog, "combo1");
468       dir_select = lookup_widget(dir_dialog, "dirselect");
469       if (dirs) {
470          gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
471       }
472       gtk_widget_show(dir_dialog);
473       gtk_main();
474
475       if (reply == OK) {
476          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
477          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
478          if (ecmd) {
479             g_free(ecmd);             /* release director name string */
480          }
481       }
482       if (dirs) {
483          g_free(dirs);
484       }
485       gtk_widget_destroy(dir_dialog);
486       dir_dialog = NULL;
487    } else {
488       /* Just take the first Director */
489       LockRes();
490       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
491       UnlockRes();
492    }
493
494    if (!dir) {
495       return 0;
496    }
497
498    memset(&jcr, 0, sizeof(jcr));
499
500    set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
501    set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
502
503    while (gtk_events_pending()) {     /* fully paint screen */
504       gtk_main_iteration();
505    }
506
507    LockRes();
508    /* If cons==NULL, default console will be used */
509    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
510    UnlockRes();
511
512    char buf[1024];
513    /* Initialize Console TLS context */
514    if (cons && (cons->tls_enable || cons->tls_require)) {
515       /* Generate passphrase prompt */
516       bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
517
518       /* Initialize TLS context:
519        * Args: CA certfile, CA certdir, Certfile, Keyfile,
520        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
521       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
522          cons->tls_ca_certdir, cons->tls_certfile,
523          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
524
525       if (!cons->tls_ctx) {
526          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
527             dir->hdr.name);
528          set_text(buf, strlen(buf));
529          terminate_console(0);
530          return 1;
531       }
532
533    }
534
535    /* Initialize Director TLS context */
536    if (dir->tls_enable || dir->tls_require) {
537       /* Generate passphrase prompt */
538       bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
539
540       /* Initialize TLS context:
541        * Args: CA certfile, CA certdir, Certfile, Keyfile,
542        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
543       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
544          dir->tls_ca_certdir, dir->tls_certfile,
545          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
546
547       if (!dir->tls_ctx) {
548          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
549             dir->hdr.name);
550          set_text(buf, strlen(buf));
551          terminate_console(0);
552          return 1;
553       }
554    }
555
556
557    UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
558                           NULL, dir->DIRport, 0);
559    if (UA_sock == NULL) {
560       return 0;
561    }
562
563    jcr.dir_bsock = UA_sock;
564    if (!authenticate_director(&jcr, dir, cons)) {
565       set_text(UA_sock->msg, UA_sock->msglen);
566       return 0;
567    }
568
569    set_status(_(" Initializing ..."));
570
571    bnet_fsend(UA_sock, "autodisplay on");
572
573    /* Read and display all initial messages */
574    while (bnet_recv(UA_sock) > 0) {
575       set_text(UA_sock->msg, UA_sock->msglen);
576    }
577
578    /* Paint changes */
579    while (gtk_events_pending()) {
580       gtk_main_iteration();
581    }
582
583    /* Fill the run_dialog combo boxes */
584    job_list      = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
585    client_list   = get_and_fill_combo(run_dialog, "combo_client", ".clients");
586    fileset_list  = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
587    messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
588    pool_list     = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
589    storage_list  = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
590    type_list     = get_and_fill_combo(run_dialog, "combo_type", ".types");
591    level_list    = get_and_fill_combo(run_dialog, "combo_level", ".levels");
592
593    /* Fill the label dialog combo boxes */
594    fill_combo(label_dialog, "label_combo_storage", storage_list);
595    fill_combo(label_dialog, "label_combo_pool", pool_list);
596
597
598    /* Fill the restore_dialog combo boxes */
599    fill_combo(restore_dialog, "combo_restore_job", job_list);
600    fill_combo(restore_dialog, "combo_restore_client", client_list);
601    fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
602    fill_combo(restore_dialog, "combo_restore_pool", pool_list);
603    fill_combo(restore_dialog, "combo_restore_storage", storage_list);
604
605    set_status(_(" Connected"));
606    return 1;
607 }
608
609 void write_director(const gchar *msg)
610 {
611    if (UA_sock) {
612       at_prompt = false;
613       set_status(_(" Processing command ..."));
614       UA_sock->msglen = strlen(msg);
615       pm_strcpy(&UA_sock->msg, msg);
616       bnet_send(UA_sock);
617    }
618    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
619       disconnect_from_director((gpointer)NULL);
620       gtk_main_quit();
621    }
622 }
623
624 extern "C"
625 void read_director(gpointer data, gint fd, GdkInputCondition condition)
626 {
627    int stat;
628
629    if (!UA_sock || UA_sock->fd != fd) {
630       return;
631    }
632    stat = bnet_recv(UA_sock);
633    if (stat >= 0) {
634       if (at_prompt) {
635          set_text("\n", 1);
636          at_prompt = false;
637       }
638       set_text(UA_sock->msg, UA_sock->msglen);
639       return;
640    }
641    if (is_bnet_stop(UA_sock)) {         /* error or term request */
642       gtk_main_quit();
643       return;
644    }
645    /* Must be a signal -- either do something or ignore it */
646    if (UA_sock->msglen == BNET_PROMPT) {
647       at_prompt = true;
648       set_status(_(" At prompt waiting for input ..."));
649    }
650    if (UA_sock->msglen == BNET_EOD) {
651       set_status_ready();
652    }
653    return;
654 }
655
656 static gint tag;
657
658 void start_director_reader(gpointer data)
659 {
660
661    if (director_reader_running || !UA_sock) {
662       return;
663    }
664    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
665    director_reader_running = true;
666 }
667
668 void stop_director_reader(gpointer data)
669 {
670    if (!director_reader_running) {
671       return;
672    }
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    gdk_input_remove(tag);
683    gdk_input_remove(tag);
684    director_reader_running = false;
685 }
686
687
688
689 /* Cleanup and then exit */
690 void terminate_console(int sig)
691 {
692    static bool already_here = false;
693
694    if (already_here)                  /* avoid recursive temination problems */
695       exit(1);
696    already_here = true;
697    cleanup_crypto();
698    disconnect_from_director((gpointer)NULL);
699    gtk_main_quit();
700    exit(0);
701 }
702
703
704 /* Buffer approx 2000 lines -- assume 60 chars/line */
705 #define MAX_TEXT_CHARS   (2000 * 60)
706 static int text_chars = 0;
707
708 static void truncate_text_chars()
709 {
710    GtkTextBuffer *textbuf;
711    GtkTextIter iter, iter2;
712    guint len;
713    int del_chars = MAX_TEXT_CHARS / 4;
714
715    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
716    len = gtk_text_buffer_get_char_count(textbuf);
717    gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
718    gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
719    gtk_text_buffer_delete (textbuf, &iter, &iter2);
720    text_chars -= del_chars;
721    len = gtk_text_buffer_get_char_count(textbuf);
722    gtk_text_iter_set_offset(&iter, len);
723 }
724
725 void set_textf(const char *fmt, ...)
726 {
727    va_list arg_ptr;
728    char buf[1000];
729    int len;
730    va_start(arg_ptr, fmt);
731    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
732    va_end(arg_ptr);
733    set_text(buf, len);
734 }
735
736 void set_text(const char *buf, int len)
737 {
738    GtkTextBuffer *textbuf;
739    GtkTextIter iter;
740    guint buf_len;
741
742    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
743    buf_len = gtk_text_buffer_get_char_count(textbuf);
744    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
745    gtk_text_buffer_insert(textbuf, &iter, buf, -1);
746    text_chars += len;
747    if (text_chars > MAX_TEXT_CHARS) {
748       truncate_text_chars();
749    }
750    set_scroll_bar_to_end();
751 }
752
753 void set_statusf(const char *fmt, ...)
754 {
755    va_list arg_ptr;
756    char buf[1000];
757    int len;
758    va_start(arg_ptr, fmt);
759    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
760    va_end(arg_ptr);
761    gtk_label_set_text(GTK_LABEL(status1), buf);
762 // set_scroll_bar_to_end();
763    ready = false;
764 }
765
766 void set_status_ready()
767 {
768    gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
769    ready = true;
770 // set_scroll_bar_to_end();
771 }
772
773 void set_status(const char *buf)
774 {
775    gtk_label_set_text(GTK_LABEL(status1), buf);
776 // set_scroll_bar_to_end();
777    ready = false;
778 }
779
780 static void set_scroll_bar_to_end(void)
781 {
782    GtkTextBuffer* textbuf = NULL;
783    GtkTextIter iter;
784    guint buf_len;
785
786    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
787    buf_len = gtk_text_buffer_get_char_count(textbuf);
788    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
789    gtk_text_iter_set_offset(&iter, buf_len);
790    gtk_text_buffer_place_cursor(textbuf, &iter);
791    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
792               gtk_text_buffer_get_mark(textbuf, "insert"),
793               0, TRUE, 0.0, 1.0);
794 }