NYLXS - Do'ers
Mon May 22 23:17:37 2017 e.s.t.
JOURNAL
HOME

CURRENT
ISSUE
HOME

TABLE
OF
CONTENTS

PREVIOUS
ARTICLE

NEXT
ARTICLE


New York Linux Scene Journal

NYLXS - Writing a backup program in GTK and C

by Ruben I. Safir

A client recently asked me to implement a tape backup program which is easier to user you by office staff. As a result of this request, I chose to implement a solution with the existing free software tool available, and use this as an excuse for finally learning a little GTK and brushing off my C programming skills.

The reason for writing this program in C and GTK is two fold. GTK is the most common free X based widget set available today. Over the past few years, I've written a lot of web based applications, but I wanted this program to run only from the server, and not depend on TCP/IP for it's implementation. In addition to this, backups can include thousands of files, and trying to get them process and display that many rows of information can be very difficult.

The current backup is being done with tar CRON to backup the files every day at 1AM. The program mails a list of backuped files to the the administrator and makes a list on the hard drive of all the files.

The old system will continue, but the new one will allow for simple click and go restoration from the tape.

The first thing I did when tackling this project is to construct a decent make file. GTK has standard configuration scripts which usually make compiling easy, but I'm opposed to using them for a number of reasons. First, the configuration scripts often are wrong. And secondly, there good reasons to create a decent make file in terms of interaction with the editor and compiling speeds through dependency control.

My make file for this project looks like this:

CC=gcc

LDLIBS= -L/usr/local/lib -lglib -L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXi -lXext -lX11 -lm

CFLAGS= -Wall -g -O3 -I/usr/include/glib-1.2 -I/usr/lib/glib/include -I/usr/X11R6/include -I/usr/include -I/usr/include/gtk-1.2

INCLUDE= .

rmbackup: rmbackup.o backprog.o tarlib.o

rmback.o: rmbackup.c

backprog: backprog.o

backprog: backprog.o

backprog.o:backprog.c

tarlib: tarlib.o

tarlib.o:tarlib.c

tarlib.c:

$(CC) -I$(INCLUDE) $(LDLIBS) $(CFLAGS) -c tree.c

backprog.c:

$(CC) -I$(INCLUDE) $(LDLIBS) $(CFLAGS) -c backprog.c

rmbackup.c:

$(CC) -I$(INCLUDE) $(LDLIBS) $(CFLAGS) -c backprog.c

From here you can see I broke my program into three main sections:

  • tarlib - This is mostly a tar library section of code dealing with the ins and outs of the actual backups.

  • backprog - This mostly deals with display of the program in X

  • rmback - This runs main and launches the application.

Proper header have been created for this program, and I found that I didn't make a clear a distinction as I wanted to between display and function. But the program is generally designed cleanly.

When using GTK, I found that some of the documentation and the actual resulting outcomes was not as advertized. Most annoying was problems with the GtkList * widget. In theory, the widget creates a clickable list of files, similarly to mail client. Glib has a linked list structure called a Glist which has a series of functions to handle a linked list. You should be able to append a GList directly into a GtkList, but this fails. As a result, this functionality has to be done by hand.

In Gtk, the use of C structs are used extensively to give the feel of object orientation to a procedural language. Everything is constructed as boxes in boxes in boxes. First you make a GtkWidget *. Then you tern the widget into a specific kind of widget through a Gtk library function for that widget. Then you add widgets into your widget as needed. And finally you show all the widgets. For example, this program might create a top level window, and then a packing box in the window, then 3 buttons in the packing box. It is then up to you to create callback functions for the mouse and keyboard signals of each event.

Signal callbacks run your program. The are attached to specific events, and then call programs. In writing this program, I learned a number of lessons. First, I learned that if I create a data object in a function, even if I pass it as a pointer to the signal callback, the data structure does not follow into the stack of the main Gtk loop. I had to globalize through static some of my data type declarations. Secondly, I learned you can't fork once a X widget is running in your program, because it throws your program into a threading condition and X complains, even though the code compiles.

Let's look at the basic construction of the program.

#include <stdio.h>
#include "tarlib.h"
#include "backprog.h"

int main(int argc, char **argv){
        struct s1 window1;
        gtk_init(&argc, &argv);
        window1 = screen1();
        gtk_main();
        exit(0);
}

backup.h

#ifndef BACKPROG_H
#define BACKPROG_H


struct s1{
     GtkWidget *box;
     GtkWidget *lab;
     GtkWidget *win;
 };

int makescreenone();
struct s1 maketoggles();
void startbackup(GtkWidget *widget, GtkWidget *data);
void listfiles(GtkWidget *widget, gchar *data);
gint closeapp(GtkWidget *widget, gchar *data);
struct s1 screen1();

#endif

backup.c

#include <stdio.h>
#include <gtk/gtk.h>
#include "backprog.h"
#include "tarlib.h"


struct s1 maketoggles(){
        static struct s1 handle;
        GtkWidget *button;
        GtkWidget *vbox;
        GtkWidget *label;

        vbox = gtk_vbox_new(TRUE, 0);

        label = gtk_label_new("Choose an Option");

        gtk_widget_show(label);

        gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE,3);

        button = gtk_toggle_button_new_with_label("Create Backup");
        gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, FALSE, 0);
        gtk_widget_show(button);
        gtk_signal_connect(GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(startbackup),
                        label );

        button = gtk_toggle_button_new_with_label("List Files to Restore");
        gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, FALSE, 0);
        gtk_widget_show(button);
        gtk_signal_connect(GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(listfiles),
                        "looking up files to resotre");

        button = gtk_toggle_button_new_with_label("Exit");
        gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, FALSE, 0);
        gtk_widget_show(button);
        gtk_signal_connect(GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(closeapp), NULL);

        handle.box = vbox;
        handle.lab = label;
        handle.win = NULL;

        return handle;
}

void startbackup( GtkWidget *widget, GtkWidget *lab){
        gint ret;
/*      gtk_label_set_text(GTK_LABEL(lab), "CREATING BACKUP"); */
        ret = createbackup();
        g_print("%d",ret);
        g_print("\n");
/*      gtk_label_set_text(GTK_LABEL(lab), "Choose an Option");*/

}

void listfiles( GtkWidget *widget, gchar *data){
        createlist();
        g_print("%s\n", data);

}

gint closeapp( GtkWidget *widget, gchar *data){
                gtk_main_quit();
                return(FALSE);

}

struct s1 screen1(){
        struct s1 handle;
        GtkWidget * win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_window_set_title(GTK_WINDOW(win), "Rosenzweig and Maffia Backup Program");
        gtk_container_border_width(GTK_CONTAINER(win), 25);
        handle = maketoggles();

        gtk_container_add(GTK_CONTAINER(win), handle.box);
        gtk_widget_show(handle.box);
        gtk_widget_show(win);
        handle.win = win;
        return(handle);
}
#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include "tarlib.h"

gint createbackup(){
        gint ret;
//      gint pid;
        gchar buffer[512] = {'\0'};
        FILE *tarcommand;
        GtkWidget *window_proc;
        GtkWidget *text;
        GtkWidget *box;
        GtkWidget *vscroll;
/*      const char *command = "/bin/tar -cvpPf /tmp/apache.tar /home/ruben/ 2>/dev/null"; */
        const char *command = "nice -10 /bin/tar -cvpP --exclude-from /root/noback  --file /dev/st0 /  2>/dev/null";
        const char *command2 = "nice -10 /bin/tar -tf /dev/st0 1> /home/jam/backup_files.txt";
        GdkColormap *cmap;
        GdkColor color;

/*      pid = fork(); 
        if(pid != 0){  
            perror("We are in the child");   */
            window_proc = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            gtk_widget_set_usize(window_proc, 600, 400);
            gtk_signal_connect(GTK_OBJECT(window_proc), "delete_event",
                            GTK_SIGNAL_FUNC(delete_win),NULL);

            text = gtk_text_new(NULL,NULL);
            gtk_text_set_editable(GTK_TEXT(text), FALSE);
            box = gtk_hbox_new(FALSE,0);
            vscroll=gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
            gtk_container_add(GTK_CONTAINER(window_proc), box);
            gtk_box_pack_start(GTK_BOX(box), text, TRUE, TRUE,0);
            gtk_box_pack_start(GTK_BOX(box), vscroll, FALSE, FALSE,0);
            gtk_widget_show(vscroll);
            gtk_widget_show(text);
            gtk_widget_show(box);
            gtk_widget_show(window_proc);
            cmap = gdk_colormap_get_system();
            color.red = 0xffff;
            color.green = 0;
            color.blue = 0;
            if (!gdk_color_alloc(cmap, &color)) {
                 g_error("couldn't allocate color");
           }




           system("mt -f /dev/st0 rewind");
           system("mt -f /dev/st0 retension");
           tarcommand  = popen(command, "r");
           while(fgets(buffer, 512, tarcommand)){
//                 gtk_text_freeze(GTK_TEXT(text));
                   gtk_text_insert(GTK_TEXT(text),NULL,NULL,NULL, buffer, strlen(buffer));
//                 gtk_text_thaw(GTK_TEXT(text));
                   }
                   gtk_text_insert(GTK_TEXT(text),NULL,&color,NULL,
                                   "DONE", -1);


           ret = pclose(tarcommand);
           ret = system(command2);


            if( ret  == 127 ){
                         g_print("Can not open shell\n");
                         exit(0);

             }
            return ret;

/*          
        }else{ */
         /*ret = fgetc(tar);*/
/*          ret = 0;    
            return(ret);
        } */
}

gint delete_win(GtkWidget *widget, gpointer *data){
        return FALSE;
}

GtkWidget *addrow(char *entry){
        GtkWidget *item;
        item = gtk_list_item_new_with_label(entry);
        gtk_widget_show(item);
        return(item);
}

int createlist(void){
        GtkWidget *item;
        GtkWidget *listbox;
        GtkWidget *sw;
        GtkWidget * window_select;
        GtkWidget * vbox;
        GtkWidget * button;
        GtkAdjustment * vert;
        GtkAdjustment * horz;
        FILE *fp;
        char file[512] = {'\0'};
        if(NULL == (fp = fopen("/home/jam/backup_files.txt", "r"))){
                g_error("Can't Open Files");
                exit(1);
        }
        vert = GTK_ADJUSTMENT( gtk_adjustment_new(0,0,0,0,0,0));
        horz = GTK_ADJUSTMENT( gtk_adjustment_new(0,0,0,0,0,0));

        window_select = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_container_border_width(GTK_CONTAINER(window_select), 10);
        gtk_window_set_title(GTK_WINDOW(window_select), "Files to Restore");
        gtk_window_set_default_size(GTK_WINDOW(window_select), 600,400);

        vbox = gtk_vbox_new(FALSE,5);

        sw = gtk_scrolled_window_new(horz, vert);

        listbox = gtk_list_new();
        gtk_list_set_selection_mode(GTK_LIST(listbox), GTK_SELECTION_EXTENDED);

        gtk_container_add(GTK_CONTAINER(window_select), vbox);

        gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE,5);

        button = gtk_button_new_with_label("Restore Selected Files");
        gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE,5);

        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw),listbox);

        while(fgets(file, 512, fp)){
                 /* g_print("%s",file);
                g_print("%d\n", strlen(file));
                g_print("LAST CHAR %c \n", file[strlen(file) -1]);*/
                file[strlen(file) -1] = '\0';
                item = gtk_list_item_new_with_label(file);
                gtk_container_add(GTK_CONTAINER(listbox), item);
                gtk_widget_show(item);
        }
        fclose(fp);
        gtk_signal_connect(GTK_OBJECT(listbox), "select_child", GTK_SIGNAL_FUNC(file_selected), NULL);
        gtk_signal_connect(GTK_OBJECT(listbox), "unselect_child", GTK_SIGNAL_FUNC(file_unselected), NULL);
        gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(restore), NULL);

        gtk_widget_show(vbox);
        gtk_widget_show(listbox);
        gtk_widget_show(button);
        gtk_widget_show(sw);
        gtk_widget_show(window_select);

        return(1);
}

//Signal Handles for list selection
void file_selected(GtkList * listbox, GtkWidget *widget, gpointer data){
        links = g_list_append(links, widget);
}


void file_unselected(GtkList * listbox, GtkWidget *widget, gpointer data){
        links = g_list_remove(links, widget);
}

//Signal Handle for button

int restore(GtkButton * button, gpointer data){
        GtkWidget *window_proc;
        GtkWidget *text;
        GtkWidget *box;
        GtkWidget *vscroll;
        GdkColormap *cmap;
        GdkColor color;
            window_proc = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            gtk_widget_set_usize(window_proc, 600, 400);
            gtk_signal_connect(GTK_OBJECT(window_proc), "delete_event",
                            GTK_SIGNAL_FUNC(delete_win),NULL);

            text = gtk_text_new(NULL,NULL);
            gtk_text_set_editable(GTK_TEXT(text), FALSE);
            box = gtk_hbox_new(FALSE,0);
            vscroll=gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
            gtk_container_add(GTK_CONTAINER(window_proc), box);
            gtk_box_pack_start(GTK_BOX(box), text, TRUE, TRUE,0);
            gtk_box_pack_start(GTK_BOX(box), vscroll, FALSE, FALSE,0);
            gtk_widget_show(vscroll);
            gtk_widget_show(text);
            gtk_widget_show(box);
            gtk_widget_show(window_proc);
            cmap = gdk_colormap_get_system();
            color.red = 0xffff;
            color.green = 0;
            color.blue = 0;
            if (!gdk_color_alloc(cmap, &color)) {
                 g_error("couldn't allocate color");
            }


        if(links == NULL)
                return(0);
        g_list_foreach(links, untar_file, text);
        gtk_text_insert(GTK_TEXT(text),NULL,&color,NULL,
                                   "DONE", -1);
        return(1);
}

void untar_file( gpointer data, gpointer text){
        gchar * filename;
        gchar buffer[512] = {'\0'};
        FILE *untar;
        const char *command = "nice -10 /bin/tar -xvpPf /dev/st0 ";
        char command2[512] = {'\0'};
        gtk_label_get(GTK_LABEL(GTK_BIN(data)->child), &filename);
//      g_print("FILES %s\n", filename);
        strcat( command2, command );
        strcat( command2, filename );

        strcat( command2, " 2>/dev/null" );
//      g_print("COMMAND %s\n", command2);
        untar  = popen(command2, "r");
//      g_print("RESOTRING FILES");
        system("mt -f /dev/st0 rewind");
        system("mt -f /dev/st0 retension");
        while(fgets(buffer, 512, untar)){
           gtk_text_insert(GTK_TEXT(text),NULL,NULL,NULL, buffer, strlen(buffer));
                //g_print("RESTORED %s\n", buffer);
        }
        pclose(untar);


}

tarlib.h

#ifndef TABLIB_H
#define TABLIB_H
#include<gtk/gtk.h>
#include<unistd.h>
#include<string.h>

gint createbackup(void);
gint delete_win(GtkWidget * ,gpointer *);
int createlist(void);
GtkWidget *addrow(char *);
void file_selected(GtkList *, GtkWidget *, gpointer);
void file_unselected(GtkList *, GtkWidget *, gpointer);
int restore(GtkButton *, gpointer);
void untar_file( gpointer data, gpointer dat);
GList *links;
#endif

tarlib.c

#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include "tarlib.h"

gint createbackup(){
        gint ret;
//      gint pid;
        gchar buffer[512] = {'\0'};
        FILE *tarcommand;
        GtkWidget *window_proc;
        GtkWidget *text;
        GtkWidget *box;
        GtkWidget *vscroll;
/*      const char *command = "/bin/tar -cvpPf /tmp/apache.tar /home/ruben/ 2>/dev/null"; */
        const char *command = "nice -10 /bin/tar -cvpP --exclude-from /root/noback  --file /dev/st0 /  2>/dev/null";
        const char *command2 = "nice -10 /bin/tar -tf /dev/st0 1> /home/jam/backup_files.txt";
        GdkColormap *cmap;
        GdkColor color;

/*      pid = fork(); 
        if(pid != 0){  
            perror("We are in the child");   */
            window_proc = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            gtk_widget_set_usize(window_proc, 600, 400);
            gtk_signal_connect(GTK_OBJECT(window_proc), "delete_event",
                            GTK_SIGNAL_FUNC(delete_win),NULL);

            text = gtk_text_new(NULL,NULL);
            gtk_text_set_editable(GTK_TEXT(text), FALSE);
            box = gtk_hbox_new(FALSE,0);
            vscroll=gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
            gtk_container_add(GTK_CONTAINER(window_proc), box);
            gtk_box_pack_start(GTK_BOX(box), text, TRUE, TRUE,0);
            gtk_box_pack_start(GTK_BOX(box), vscroll, FALSE, FALSE,0);
            gtk_widget_show(vscroll);
            gtk_widget_show(text);
            gtk_widget_show(box);
            gtk_widget_show(window_proc);
            cmap = gdk_colormap_get_system();
            color.red = 0xffff;
            color.green = 0;
            color.blue = 0;
            if (!gdk_color_alloc(cmap, &color)) {
                 g_error("couldn't allocate color");
           }




           system("mt -f /dev/st0 rewind");
           system("mt -f /dev/st0 retension");
           tarcommand  = popen(command, "r");
           while(fgets(buffer, 512, tarcommand)){
//                 gtk_text_freeze(GTK_TEXT(text));
                   gtk_text_insert(GTK_TEXT(text),NULL,NULL,NULL, buffer, strlen(buffer));
//                 gtk_text_thaw(GTK_TEXT(text));
                   }
                   gtk_text_insert(GTK_TEXT(text),NULL,&color,NULL,
                                   "DONE", -1);


           ret = pclose(tarcommand);
           ret = system(command2);


            if( ret  == 127 ){
                         g_print("Can not open shell\n");
                         exit(0);

             }
            return ret;

/*          
        }else{ */
         /*ret = fgetc(tar);*/
/*          ret = 0;    
            return(ret);
        } */
}

gint delete_win(GtkWidget *widget, gpointer *data){
        return FALSE;
}

GtkWidget *addrow(char *entry){
        GtkWidget *item;
        item = gtk_list_item_new_with_label(entry);
        gtk_widget_show(item);
        return(item);
}

int createlist(void){
        GtkWidget *item;
        GtkWidget *listbox;
        GtkWidget *sw;
        GtkWidget * window_select;
        GtkWidget * vbox;
        GtkWidget * button;
        GtkAdjustment * vert;
        GtkAdjustment * horz;
        FILE *fp;
        char file[512] = {'\0'};
        if(NULL == (fp = fopen("/home/jam/backup_files.txt", "r"))){
                g_error("Can't Open Files");
                exit(1);
        }
        vert = GTK_ADJUSTMENT( gtk_adjustment_new(0,0,0,0,0,0));
        horz = GTK_ADJUSTMENT( gtk_adjustment_new(0,0,0,0,0,0));

        window_select = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_container_border_width(GTK_CONTAINER(window_select), 10);
        gtk_window_set_title(GTK_WINDOW(window_select), "Files to Restore");
        gtk_window_set_default_size(GTK_WINDOW(window_select), 600,400);

        vbox = gtk_vbox_new(FALSE,5);

        sw = gtk_scrolled_window_new(horz, vert);

        listbox = gtk_list_new();
        gtk_list_set_selection_mode(GTK_LIST(listbox), GTK_SELECTION_EXTENDED);

        gtk_container_add(GTK_CONTAINER(window_select), vbox);

        gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE,5);

        button = gtk_button_new_with_label("Restore Selected Files");
        gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE,5);

        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw),listbox);

        while(fgets(file, 512, fp)){
                 /* g_print("%s",file);
                g_print("%d\n", strlen(file));
                g_print("LAST CHAR %c \n", file[strlen(file) -1]);*/
                file[strlen(file) -1] = '\0';
                item = gtk_list_item_new_with_label(file);
                gtk_container_add(GTK_CONTAINER(listbox), item);
                gtk_widget_show(item);
        }
        fclose(fp);
        gtk_signal_connect(GTK_OBJECT(listbox), "select_child", GTK_SIGNAL_FUNC(file_selected), NULL);
        gtk_signal_connect(GTK_OBJECT(listbox), "unselect_child", GTK_SIGNAL_FUNC(file_unselected), NULL);
        gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(restore), NULL);

        gtk_widget_show(vbox);
        gtk_widget_show(listbox);
        gtk_widget_show(button);
        gtk_widget_show(sw);
        gtk_widget_show(window_select);

        return(1);
}

//Signal Handles for list selection
void file_selected(GtkList * listbox, GtkWidget *widget, gpointer data){
        links = g_list_append(links, widget);
}


void file_unselected(GtkList * listbox, GtkWidget *widget, gpointer data){
        links = g_list_remove(links, widget);
}

//Signal Handle for button

int restore(GtkButton * button, gpointer data){
        GtkWidget *window_proc;
        GtkWidget *text;
        GtkWidget *box;
        GtkWidget *vscroll;
        GdkColormap *cmap;
        GdkColor color;
            window_proc = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            gtk_widget_set_usize(window_proc, 600, 400);
            gtk_signal_connect(GTK_OBJECT(window_proc), "delete_event",
                            GTK_SIGNAL_FUNC(delete_win),NULL);

            text = gtk_text_new(NULL,NULL);
            gtk_text_set_editable(GTK_TEXT(text), FALSE);
            box = gtk_hbox_new(FALSE,0);
            vscroll=gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
            gtk_container_add(GTK_CONTAINER(window_proc), box);
            gtk_box_pack_start(GTK_BOX(box), text, TRUE, TRUE,0);
            gtk_box_pack_start(GTK_BOX(box), vscroll, FALSE, FALSE,0);
            gtk_widget_show(vscroll);
            gtk_widget_show(text);
            gtk_widget_show(box);
            gtk_widget_show(window_proc);
            cmap = gdk_colormap_get_system();
            color.red = 0xffff;
            color.green = 0;
            color.blue = 0;
            if (!gdk_color_alloc(cmap, &color)) {
                 g_error("couldn't allocate color");
            }


        if(links == NULL)
                return(0);
        g_list_foreach(links, untar_file, text);
        gtk_text_insert(GTK_TEXT(text),NULL,&color,NULL,
                                   "DONE", -1);
        return(1);
}

void untar_file( gpointer data, gpointer text){
        gchar * filename;
        gchar buffer[512] = {'\0'};
        FILE *untar;
        const char *command = "nice -10 /bin/tar -xvpPf /dev/st0 ";
        char command2[512] = {'\0'};
        gtk_label_get(GTK_LABEL(GTK_BIN(data)->child), &filename);
//      g_print("FILES %s\n", filename);
        strcat( command2, command );
        strcat( command2, filename );

        strcat( command2, " 2>/dev/null" );
//      g_print("COMMAND %s\n", command2);
        untar  = popen(command2, "r");
//      g_print("RESOTRING FILES");
        system("mt -f /dev/st0 rewind");
        system("mt -f /dev/st0 retension");
        while(fgets(buffer, 512, untar)){
           gtk_text_insert(GTK_TEXT(text),NULL,NULL,NULL, buffer, strlen(buffer));
                //g_print("RESTORED %s\n", buffer);
        }
        pclose(untar);



}

Some of the interesting features of this program is the use of a global struct defined in backup, which is being used to pass screens from function to function. This was needed to keep the struct in the memory stack.

The program uses popen to talk to tar, which performs the actual backups, and creates a list of backuped files. popen forks and opens a command to a shell for reading or writing (not both). It gets a file handle to process from the command line of the shell.

The clickable list is generated from a text file which is read in. This file is produced by the tar backup process. In addition, some minor string handling needed to be done. Ideally, a fork of the tar could be done so that control of the panel is not lost while the tape is accessed, and this is an imporvement to be built in on the next version. A straight for is not possible because it causes a threading condition for X, as mentioned before. Finally, the resulting backups generate a list of over 170,000 files. This breaks the scrollable window as currently written. Another method will be needed to deal with this problem. On solution might be to read in 1,000 entries at a time and display them only with a new forward and back button added. This can be done either by reading the entry list file into an array of gchar pointers, or using fseek and tell to do random reads and writes to the file. But then we will need to standardize the size of the line inputs. A search window also needs to be added.