#include "common.h"


/* Common window variables */
WINDOW    *win_main, *title, *files, *selection, *bottom;
int       screen_ylen, screen_xlen;		 /* physical dims of screen */
int       screen_ylen_files, screen_ylen_selection;/* length of screens */
int       left_width, right_width;


/* Global options */
char  append=1;
char  hard_links=0;
int   lstrip=99;
char  compression=1;
char  sort_dir=1;
char  archive_title[MAX_ARCHIVETITLE]={0};
char  ovrwrite=1;
char  rel_path[MAXNAMLEN]={0};
char  pr_dir=0;
char  taper_info_files[MAXNAMLEN]={0};
char  dir_selection=1;
char  log_file[MAXNAMLEN]={0};
char  log_level=2;
int   max_tr_size=28*1024;			 /* is used for reading/writing files to/from hard disk */
int   block_size=28*1024;			 /* is used for reading/writing data to/from tape device */
int   update=0;					 
dev_t proc_dev=1;				 /* device on which /proc is mounted */
int   limit_vols=0;				 /* limit restore to volume */

/* Variables common to both backup & restore */
long total_compressed;				 /* # file bytes in volume - compressed */
long total_uncompressed;			 /* # file bytes in volume - uncompressed */
long no_in_archive=0;				 /* # files in archive */
long no_sel=0;					 /* # files selected */
long total_selected=0;				 /* # bytes selected */

int  dv;					 /* handle of backup device */
char tape[MAXNAMLEN];				 /* filename of rewinding tape device */
char ntape[MAXNAMLEN];				 /* filename of non-rewinding tape device */
FILE *lf;					 /* log file */
int log_errors=0;
int log_warnings=0;

char cf[MAXNAMLEN]={0};				 /* common pathname prefix */
char stripped_cf[MAXNAMLEN];			 /* portion of common pathname prefix that is not stripped */

char *tr_buffer;				 /* transfer buffer */
char *info;					 /* pointer to volume directory */
char *cp;					 /* offset used into above */

char *read_buffer;				 /* read buffer */
int read_offset;				 /* position in read buffer */
int read_buffer_count;				 /* how many bytes in read buffer */
char *write_buffer;				 /* write buffer */
int write_buffer_count;				 /* how many bytes in write buffer */
int write_offset;				 /* position in write buffer */
int block_size;					 /* size of buffer */

struct file_entry *vol_files;			 /* files on volume */
struct file_entry *sel_files;
struct volume_tape_info *vt_info;		 /* which tape each volume is on */

struct tape_header tdh;				 /* headers for tape & */
struct info_file_header ifd;			 /* info file */

char cur_dir[MAXNAMLEN];
int vols_passed;				 /* number of volumes passed on this tape */

/* Color defaults */
char color_title_foreground=COLOR_BLUE;
char color_title_background=COLOR_WHITE;
char color_main_foreground=COLOR_WHITE;
char color_main_background=COLOR_BLUE;
char color_dialog_foreground=COLOR_WHITE;
char color_dialog_background=COLOR_RED;
char color_status_foreground=COLOR_WHITE;
char color_status_background=COLOR_RED;
char color_selected_foreground=COLOR_WHITE;
char color_selected_background=COLOR_BLUE;
char color_directory_foreground=COLOR_BLACK;
char color_directory_background=COLOR_GREEN;
char color_onvol_foreground=COLOR_WHITE;
char color_onvol_background=COLOR_MAGENTA;
char color_bottom_foreground=COLOR_BLUE;
char color_bottom_background=COLOR_WHITE;
char color_help_foreground=COLOR_WHITE;
char color_help_background=COLOR_YELLOW;


void read_environment()
{
    strcpy(tape, DEFAULT_TAPE);
    if (getenv(TAPE_ENVIRON))
      strcpy(tape, getenv(TAPE_ENVIRON));

    strcpy(ntape, DEFAULT_NTAPE);
    if (getenv(NTAPE_ENVIRON))
      strcpy(tape, getenv(NTAPE_ENVIRON));
    
    strcpy(log_file, getenv("HOME"));
    strcat(log_file, "/"); 
    strcat(log_file, DEFAULT_LOG_FILE);
    if (getenv(LOGFILE_ENVIRON))
      strcpy(log_file, getenv(LOGFILE_ENVIRON));
    
    if (getenv(LOGLEVEL_ENVIRON))
      log_level = atol(getenv(LOGLEVEL_ENVIRON));
    
    strcpy(taper_info_files, getenv("HOME"));
    strcat(taper_info_files, "/"); 
    strcat(taper_info_files, DEFAULT_TAPER_INFO_FILES);
    if (getenv(TAPER_INFO_FILES_ENVIRON))
      strcpy(taper_info_files, getenv(TAPER_INFO_FILES_ENVIRON));
}


void strip_spaces(char *s)
/* strips spaces from the string in 's'.
 * Spaces are preserved within ' or " only
*/
{
    int in_q = 0;
    int c, c1=0;
    
    while (c1 < strlen(s)) {			 /* remove spaces */
	if (s[c1] == '\'' || s[c1] == '"')
	  in_q = !in_q;
	if ((s[c1] == ' ') && (!in_q))
	  for (c=c1; c<strlen(s); c++) s[c] = s[c+1];
	else
	  c1++;
    }
}


int ev(char *s)
{
/* Returns 1 if *s == 'T'
 * Returns 0 otherwise
*/
    if ((*s == 't') || (*s == 'T'))
      return 1;
    return 0;
}

char evcol(char *s)
{
/* make color string into color integer */    
    if (!strcasecmp(s, "BLACK")) return COLOR_BLACK;
    if (!strcasecmp(s, "RED")) return COLOR_RED;
    if (!strcasecmp(s, "GREEN")) return COLOR_GREEN;
    if (!strcasecmp(s, "YELLOW")) return COLOR_YELLOW;
    if (!strcasecmp(s, "BLUE")) return COLOR_BLUE;
    if (!strcasecmp(s, "MAGENTA")) return COLOR_MAGENTA;
    if (!strcasecmp(s, "CYAN")) return COLOR_CYAN;
    if (!strcasecmp(s, "WHITE")) return COLOR_WHITE;
    return COLOR_BLACK;				 /* unknown = black */
}


void evcol_pair(char *vl, char *fore, char *back)
{
/* extract foreground and background colors */   
    vl = strtok(vl, ",");
    *fore = evcol(vl);
    vl = strtok(NULL, ",");
    *back = evcol(vl);
}

void read_prefs_file(int argc, char *argv[]) 
{
/* Looks for a preferences  file:
 * 
 * First checks to see if there is a -p argument
 * If not, then looks for TAPER_CONFIG environment variable
 * If none, then looks for file .taper_prefs in $HOME
 * If none, then looks for file .taper_prefs in DEFAULT_GLOBAL_PREFS (/usr/local/etc)
 * If not`found, then use internal defaults
 * 
 * Note: Command line options over-ride anything in a file or internal defaults
*/ 
    int c;
    char fname[MAXNAMLEN]={0};
    FILE *fd;
    char s[1000], *val;
    int line=0;

    strcpy(fname, getenv("HOME")); strcat(fname, "/");
    strcat(fname, DEFAULT_PREFS);		 /* default preference filename*/
    *fname = 0;
    c=1;					 /* check if command line name given */
    while (c < argc) {
	if (!strcasecmp(argv[c], "-p") || !strcasecmp(argv[c], "--preference-file")) {
	    strcpy(fname,argv[c+1]);
	    break;
	}
	c++;
    }
    if (!(*fname)) 				 /* no command line specified */
      if (getenv(PREFS_ENVIRON))		 /* see if environment specified */
        strcpy(fname, getenv(PREFS_ENVIRON));
    if (!*fname) {				 /* no environment specified */
	strcpy(fname, getenv("HOME"));
	strcat(fname, "/"); 
	strcat(fname, DEFAULT_PREFS);
	fd = fopen(fname, "r");
	if (fd == NULL)	{			 /* couldn't open this */
	    strcpy(fname, DEFAULT_GLOBAL_PREFS); /* therefore look in global */
	    strcat(fname, "/");
	    strcat(fname, DEFAULT_PREFS);
	}
	else
	  fclose(fd);				 /* close what we just opened */
    }
    
    if (!*fname)				 /* if still no filename */
      return;
    
    if ((fd=fopen(fname, "r")) == NULL)		 /* error opening config file */
      if (errno != ENOENT)			 /* allowed to have file not found */
        do_exit(ERROR_CONFIGURATION);		 /* any other error */
      else
        return;
    while (line++, !feof(fd)) {
	if (fgets(s, sizeof(s), fd) == NULL)
	  break;
	strip_spaces(s);
	if (*s)
	  if (s[strlen(s)-1] == '\n')		 /* remove terminating newline */
            s[strlen(s)-1] = 0;
	if (!(*s)) 				 /* ignore blank lines */
	  continue;
	if (strchr(s, '=') == 0)		 /* no equals */
	  do_exit(ERROR_CONFIGURATION);
	val = strchr(s, '=')+1;
	*strchr(s, '=') = 0;
/* Color options */
	if (!strcasecmp(s, "COLOR_TITLE"))
	  evcol_pair(val, &color_title_foreground, &color_title_background);
	if (!strcasecmp(s, "COLOR_MAIN"))
	  evcol_pair(val, &color_main_foreground, &color_main_background);
	if (!strcasecmp(s, "COLOR_DIALOG"))
	  evcol_pair(val, &color_dialog_foreground, &color_dialog_background);
	if (!strcasecmp(s, "COLOR_STATUS"))
	  evcol_pair(val, &color_status_foreground, &color_status_background);
	if (!strcasecmp(s, "COLOR_DIRECTORY"))
	  evcol_pair(val, &color_directory_foreground, &color_directory_background);
	if (!strcasecmp(s, "COLOR_ON_VOLUME"))
	  evcol_pair(val, &color_onvol_foreground, &color_onvol_background);
	if (!strcasecmp(s, "COLOR_SELECTED"))
	  evcol_pair(val, &color_selected_foreground, &color_selected_background);
	if (!strcasecmp(s, "COLOR_BOTTOM"))
	  evcol_pair(val, &color_bottom_foreground, &color_bottom_background);
	if (!strcasecmp(s, "COLOR_HELP"))
	  evcol_pair(val, &color_help_foreground, &color_help_background);
/* options */
	if (!strcasecmp(s, "HARD_LINKS"))
	  hard_links = ev(val);
	if (!strcasecmp(s, "STRIP_PATHS"))
	  lstrip = atoi(val);
	if (!strcasecmp(s, "APPEND"))
	  append = ev(val);
	if (!strcasecmp(s, "COMPRESS"))
	  compression = ev(val);
	if (!strcasecmp(s, "UPDATE"))
	  update = 1;
	if (!strcasecmp(s, "TAPE"))
	  strcpy(tape, val);
	if (!strcasecmp(s, "NTAPE"))
	  strcpy(ntape, val);
	if (!strcasecmp(s, "BOTH")) {
	  strcpy(ntape, val);
          strcpy(tape, val);
	}
	if (!strcasecmp(s, "ARCHIVE_TITLE"))
	  strcpy(archive_title, val);
	if (!strcasecmp(s, "TAPER_INFO_FILES"))
	  strcpy(taper_info_files, val);
	if (!strcasecmp(s, "CONFIRM_DIRS"))
	  dir_selection = ev(val);
	if (!strcasecmp(s, "BLOCK_SIZE"))
	  block_size = atol(val);
	if (!strcasecmp(s, "PROC_DEVICE"))
	  proc_dev = atol(val);
/* restore options */
	if (!strcasecmp(s, "RESTORE_PATH"))
	  strcpy(rel_path, val);
	if (!strcasecmp(s, "OVERWRITE"))
	  ovrwrite = ev(val);
/* log file options */
	if (!strcasecmp(s, "LOGFILE"))
	  strcpy(log_file, val);
	if (!strcasecmp(s, "LOGLEVEL"))
	  log_level = atol(val);
    }
    fclose(fd);
}


void setup_colors()
{
/* Sets up color */
    
    if (has_colors()) {
	init_pair(COLOR_MAIN, color_main_foreground, color_main_background);
	init_pair(COLOR_DIALOG, color_dialog_foreground, color_dialog_background);
	init_pair(COLOR_STATUS, color_status_foreground, color_status_background);
	init_pair(COLOR_HELP, color_help_foreground, color_help_background);
	init_pair(COLOR_DIRECTORY, color_directory_foreground, color_directory_background);
	init_pair(COLOR_ONVOL, color_onvol_foreground, color_onvol_background);
	init_pair(COLOR_SELECTED, color_selected_foreground, color_selected_background);
	init_pair(COLOR_BOTTOM, color_bottom_foreground, color_bottom_background);
	init_pair(COLOR_TITLE, color_title_foreground, color_title_background);
    }
}

char *convert(char *s, long num)
{
/* prints a long number in the form 123,456,789  */
    
    sprintf(s, "%d,%03d,%03d", (int) (num/1000000L), (int) ((num%1000000L)/1000L),
	    (int) ((num%1000000L)%1000L));
    if (!(num /1000000L)) 
	sprintf(s, "%d,%03d", (int) (num/1000L), (int) (num%1000L));
    if (!(num / 1000L)) 			 /* number is < 1000 */
	sprintf(s, "%d", (int) num);
    return s;
}

	
inline void add_perm_string(char *s, char *a, int perm) {
/* Adds a permission attribute to a string.
 * 
 * Checks to see is perm is allowed. If it is, adds 'a' to string,
 * otherwise adds '-'
*/
    (perm) ? strcat(s, a) : strcat(s, "-");
}

void do_exit(int c) {
    char err[100];
    
    if (win_main) {
	curs_set(1);
	werase(win_main); 
	wrefresh(win_main);
    	endwin();
    }
    strcpy(err, strerror(errno));
    switch(c) {
     case ERROR_NONE:
	strcpy(err, ""); break;
     case ERROR_USAGE: 
	strcpy(err, ""); break;
     case ERROR_ILLEGAL_DIR:
	strcat(err, " while opening directory"); break;
     case ERROR_GETINFO:
        strcat(err, " while doing getinfo call"); break;
     case ERROR_MEMORY:
	strcpy(err, "Memory allocation error"); break;
     case ERROR_OPENING_BACKUP:
	strcat(err, " while opening backup device"); break;
     case ERROR_MAGIC:
	strcpy(err, "Backup device contains unknown data"); break;
     case ERROR_REWIND:
        strcat(err, " while rewinding tape"); break;
     case ERROR_WRITING:
        strcat(err, " while writing to backup device"); break;
     case ERROR_READING:
	strcat(err, " while reading from backup device"); break;
     case ERROR_CONFIGURATION:
	strcat(err, " while opening preferences file"); break;
     case ERROR_NO_INFO:
	fprintf(stderr, "\nNo info file for this archive\n");
	fprintf(stderr, "\nUse mkinfo to recreate info files or specify the");
	fprintf(stderr, "\ncorrect directory where info files can be found.\n");
	write_log("Info file not found");
	*err = 0;
	break;
     case ERROR_CREATING_INFO:
	strcat(err, " while creating info file"); break;
     case ERROR_WRITING_INFO:
	strcat(err, " while writing to info file"); break;
     case ERROR_OPENING_LOG:
	strcat(err, " while opening log file"); break;
     case ERROR_TRSIZE:
	strcpy(err, "Illegal maximum transfer size"); break;
     case ERROR_NEW_TAPE:
	strcat(err, " while trying to get next tape in archive"); break;
     case ERROR_EMPTY_ARCHIVE:
	strcpy(err, "This tape is blank"); break;
     default:
	strcpy(err, "Unknown error"); break;
    }
    if (*err) {
	fprintf(stderr, "\n%s\n", err);
	write_log(err);
    }
    write_log("Exiting with error");
    if (lf) fputs("\n\n", lf);
    my_free_all();
    exit(c);
}

inline int is_regfile(int dv)
{
/* Returns 1 if dv is the file handle of a regular file */
    struct stat sb;
    
    fstat(dv, &sb);
    return S_ISREG(sb.st_mode);
}


void tape_readheader(struct tape_header *t, int allowz)
{
/* Reads the tape header.
 * Leading zeros are ignored if LEADING_ZEROES is defined
 * 
 * If allowz is 1, then if it can't read any bytes from the
 * tape, it returns (setting the tape_header structure to 0)
 * otherwise, this is an error
 */
#ifdef LEADING_ZEROES
    char x=0;
#endif    
    size_t x1;
    char   l[200];
    
    memset(t, 0, sizeof(struct tape_header));	 /* zero structure */
#ifdef LEADING_ZEROES    
    if (log_level == 3) write_log("Reading in tape header ignoring leading zeroes");
    while (!x) {				 /* keep reading until */
	x1 = read(dv, &x, sizeof(x));
	if ((x1 == 0) && allowz)		 /* nothing on tape */
	  return;
	if (x1 < 1) {
	    write_fatal_log("skipping leading zeros");
	    do_exit(ERROR_READING);
	}
    }
    
    if (read(dv, ((char *) t)+1, sizeof(struct tape_header)-1) == -1) {
	write_fatal_log("reading tape header");
	do_exit(ERROR_READING);			 /* re-read header */
    }
    *((char *) t) = x;				 /* put in the byte we read */
    sprintf(l, "Read in tape header %lx %ld %d", t->magic, t->archive_id, t->tape_number);
#else
    x1 = read(dv, t, sizeof(struct tape_header));
    sprintf(l, "Read in tape header %lx %ld %d", t->magic, t->archive_id, t->tape_number);
    if (log_level == 3) write_log(l);
    if (x1 == sizeof(struct tape_header)) return;/* read it all */
    if (x1 < sizeof(struct tape_header) && allowz)/* couldn't read - ?allow a zero header */
      return;					 /* yes - return */
    write_fatal_log("reading tape header");	 /* no - error */
    do_exit(ERROR_READING);			 /* don't allow a zero header */
#endif    
}

						 
inline void paint_main() {
    my_werase(win_main, COLOR_MAIN); wrefresh(win_main);
}


int check_tape(int tape_required)
{
/* Prompts user for tape_required tape on archive in tdh.archive_id */
/* Returns -1 if error or user aborted
   Returns 1 if OK  */

    WINDOW *mes=NULL;
    char   l[200];
    struct tape_header tdh1;

    if (tdh.tape_number == tape_required) return 1; /* correct tape already read */
    sprintf(l, "Please insert tape %d into drive", tape_required);
    message_box(l, MB_OK); paint_main();
    mes = status_box(mes, "", 1, TRUE, 1);
    while (1) {
	mes = status_box(mes, "Identifying tape", 1, FALSE, 1);
	tape_close();
	sprintf(l, "Opening tape to ensure that tape #%d of archive %ld is in drive", 
		tape_required, tdh.archive_id);
	if (log_level == 3) write_log(l);
	tape_rewind();
	dv = tape_open(O_RDWR);
	if (dv == -1) {
	    message_box("Error opening tape", MB_OK); paint_main();
	    write_error_log(l);
	    close_statusbox(mes);
	    return -1;
	}
	vols_passed = 0;			 /* no volumes passed on this tape */
	tdh1.magic = 0;
	tape_readheader(&tdh1, 0);
	if (tdh1.magic != TAPER_MAGIC_NUMBER) {
	    message_box("This tape doesn't contain taper data", MB_OK);
	    paint_main();
	    continue;
	}
	if (tdh1.archive_id != tdh.archive_id) {
	    sprintf(l, "This tape isn't part of archive %s", tdh.archive_title);
	    message_box(l, MB_OK);
	    paint_main();
	    continue;
	}
	if (tdh1.tape_number != tape_required) {
	    sprintf(l, "This is tape %d. I require %d", tdh1.tape_number, tape_required);
	    message_box(l, MB_OK);
	    paint_main();
	    continue;
	}
	break;
    }
    close_statusbox(mes);
    paint_main();
    tdh = tdh1;
    return 1;
}



int tape_fsf(int count)
{
    struct mtop m;
    char   l[200];
    
    m.mt_op = MTFSF;
    m.mt_count = count;
    
    sprintf(l, "Advancing %d volumes", count);
    if (log_level == 3) write_log(l);
    return (is_regfile(dv)) ? lseek(dv, 0L, SEEK_END) : 
                              ioctl(dv, MTIOCTOP, (char *) &m);
}


int tape_rewind()
{
/* Rewinds tape. Tape must be closed prior to calling of rewind.
 * Returns 0 if rewound, -1 if error
*/
    int fd, err=0, myerrno;
    struct mtop m;
    
    m.mt_op = MTREW;
    m.mt_count = 1;
    
    if (log_level==3) write_log("Rewinding tape");
    fd=open(tape, O_RDWR);			 /* try and open tape */
    if (fd==-1) 
      return (errno == ENOENT) ? 0 : -1;	 /* doesn't exist is not an error */
    if (!is_regfile(fd))
      err = ioctl (fd, MTIOCTOP, (char *) &m);	 /* do status if not regular file */
    myerrno = errno;				 /* preserve errno accross close */
    close(fd);					 /* close */
    errno = myerrno;
    return err;
}


int tape_erase()
{
    struct mtop m;
    
    m.mt_op = MTERASE;
    m.mt_count = 1;
    
    if (log_level == 3) write_log("Erasing tape");
    return (is_regfile(dv)) ? 0 : ioctl(dv, MTIOCTOP, (char *) &m);
}

int ntape_open(int mode)
{
    char l[200];
/* Opens non-rewinding device. */
    sprintf(l, "Opening non-rewinding %s", ntape);
    if (log_level == 3) write_log(l);
    read_offset = -1; read_buffer_count = 0;
    write_offset = 0;
    return open(ntape, mode);
}


int tape_create(int mode, int perms)
{
    char l[200];
/* Opens rewinding device. */
/* Allows for a create */
    sprintf(l, "Creating rewinding %s", tape);
    if (log_level == 3) write_log(l);
    write_offset = 0;
    read_offset = -1; write_offset = 0;
    return open(tape, mode, perms);
}


int tape_open(int mode)
{
    char l[200];
/* Opens rewinding device */
    sprintf(l, "Opening rewinding %s", tape);
    if (log_level == 3) write_log(l);
    read_offset = -1; read_buffer_count = 0;
    write_offset = 0;
    return open(tape, mode);
}


int tape_close()
{
    int x;
/* closes tape device */
    flush_buffers();
    if (log_level == 3) write_log("Closing tape");
    x = (dv) ? close(dv) : 0;
    dv = 0;
    return x;
}


int read_from_buf(char *buf, size_t count)
{
    size_t tr_bytes;
    
    if (read_offset == -1) {			 /* nothing in buffer */
	if (log_level == 3) write_log("Reading in block from tape");
	read_buffer_count = read(dv, read_buffer, block_size);   /* read in a block */
	read_offset = 0;			 /* set offset to zero */
    }
    if (read_buffer_count == -1) return -1;	 /* problem reading in buffer */
    tr_bytes = 0;				 /* either now or previously */
    while (tr_bytes < count) {			 /* transfer bytes to user buffer */
	*(buf+tr_bytes) = *(read_buffer+read_offset);   
	tr_bytes++;
	read_offset++;
	if (read_offset == read_buffer_count) {	 /* run out of bytes in buffer */
	    if (read_buffer_count < block_size)	 /* means that last time, didn't get all that */
	      return tr_bytes;			 /* we wanted - ie. end of tape */
	    if (log_level == 3) write_log("Reading in block from tape");
	    read_buffer_count = read(dv, read_buffer, block_size);
	    if (read_buffer_count == -1) return tr_bytes;   /* if couldn't get any more, accept what we got */
	    read_offset = 0;
	}
    }
    return tr_bytes;
}



int flush_buffers()
{
/* Flushes the write buffer
 * 
 * An end of tape is assumed if a write returns a count less than 'count'  -- OR --
 * zero bytes written  -- OR --
 * wrote less bytes than we asked for */

    size_t c, new_write_offset, c1;
    WINDOW  *mes=NULL;
    struct tape_header tdh1;
    int    half_buffer=0;

    if (!dv) return 0;				 /* backup device not open */
    if (write_offset < 1) return 0;		 /* nothing in buffer */
    if (write_offset < block_size) {		 /* pad out rest of buffer */
	memset(write_buffer+write_offset+1, 0,  block_size-write_offset-1);
	half_buffer = 1;
    }
    
    if (log_level == 3) write_log("Writing block to tape device");
    c = write(dv, write_buffer, block_size);	 /* write data */
    if (c == block_size) {
	write_offset = 0;			 /* start at beginning of buf again */
	return c;				 /* OK - written what we asked for */
    }						 
    if (c == -1)				 /* error */
      if (errno != ENOSPC)			 /* make sure not an end of tape error */
        return -1;				 /* no - return error */
      else
        c=0;					 /* none would have been written */
    for (c1=0; c1<c; c1++)			 /* move data that wasn't written to  */
      write_buffer[c1] = write_buffer[c1+c];	 /* beginning of buffer */
    new_write_offset = write_offset - c;	 /* where offset should be */

/* Returned less than the number of bytes we wanted - therefore
 * assume an end of tape situation */
    if (log_level == 3) write_log("End of tape reached while writing. Closing tape device");
    close(dv);					 /* don't use tape_close because it will try to flush */
    dv = 0;
    while (1) {
	message_box("Insert next tape", MB_OK); paint_main();
	mes = status_box(mes, "Identifying tape", 1, TRUE, 1);
	tape_rewind();				 /* rewind new tape */
	dv = tape_open(O_RDWR);		 
	if (dv == -1) {
	    message_box("Error opening tape", MB_OK); paint_main();
	    write_error_log("Error opening new tape");
	    continue;
	}
	tdh1.magic = 0;
	tape_readheader(&tdh1, 1);
	if (tdh1.magic == TAPER_MAGIC_NUMBER)
	  if (tdh1.archive_id != 0) {
	      if (!message_box("This is tape contains taper data. Confirm overwrite", MB_YESNO))
		continue;
	  }
	  else					 /* freshly formatted taper tape */
	    break;
	if (!message_box("Unknown tape data. Confirm overwrite", MB_YESNO))
	  continue;
	break;
    }
    mes = status_box(mes, "Erasing tape", 1, TRUE, 1);   
    tape_erase(dv);				 /* erase tape */
    if (log_level == 3) write_log("Closing tape device");
    status_box(mes, "Rewinding tape & writing tape header", 1, FALSE, 1);   
    close(dv);					 /* auto rewind because opened rewinding */
    dv = 0;

    if (log_level == 3) write_log("Opening new tape for data writing");
    dv = tape_open(O_RDWR);
    if (dv == -1) {
	write_error_log("Error opening new tape");
	return -1;
    }
    tdh.tape_number++;
    ifd.number_tapes++;
    if (log_level ==3) write_log("Writing tape header");
    if (write(dv, &tdh, sizeof(struct tape_header)) != sizeof(struct tape_header)) {
	write_error_log("Error writing tape header");
	return -1;
    }
    close_statusbox(mes);
    paint_main();
    write_offset = new_write_offset;		 /* reset offset count */
    if (half_buffer) {				 /* only half a buffer was originally present */
	memset(write_buffer+write_offset+1, 0,  block_size-write_offset-1);
	write(dv, write_buffer, block_size);	 /* therefore, must be closing tape */
	write_offset = 0;
    }
    return block_size;				 /* return successfully */
}


int tape_write(char *buf, size_t count)
{
    size_t tr_bytes;
    
    tr_bytes = 0;				 /* either now or previously */
    while (tr_bytes < count) {			 /* transfer bytes to user buffer */
	if (write_offset >= block_size) 	 /* buffer is full  */
	    if (flush_buffers() == -1)		 /* flush to tape */
	      return -1;			 /* error flushing */
	*(write_buffer+write_offset) = *(buf+tr_bytes);
	tr_bytes++;
	write_offset++;
    }
    return tr_bytes;
}


int tape_read(char *buf, size_t count)
{
/* An end of tape is assumed if we get an return code=-1 and errno=ENOSPC   --OR--
 * the count returned is less than we asked for 
 * 
 * note that if the return count is 0, then this is assumed to be an
 * end of volume and not and end of tape, so the zero is returned.
*/
    size_t c;
    
    c =  read_from_buf(buf, count);
    if (c == count) return c;			 /* OK - written what we asked for */
    if (c == -1)				 /* error */
      if (errno != ENOSPC)			 /* make sure not an end of tape error */
        return -1;				 /* no - return error */
      else
        c=0;					 /* none would have been written */
    if (c==0) return 0;				 /* assume end of volume */
/* Must be at end of tape */
    if (log_level == 3) write_log("End of tape reached while reading. Closing tape");
    if (check_tape(tdh.tape_number+1) == -1) 	 /* get next tape */
      return -1;
    return read_from_buf(buf+c, count-c)+c;	 /* return data */
}


int make_dirs(char *s)
/* Makes directories for a filename.
 * For example, if s = t/usd/y/f, it will make directories
 * t, then usd then y. Permission are rwr-xr-x and the owner
 * is the owner of the person running the program.
 * 
 * Returns 0 if error occurred, otherwise 1
*/ 
{
    char *s1;
    char s2[MAXNAMLEN], s3[MAXNAMLEN];
    int  c=0;
    DIR *dir;
    
    strcpy(s3, s);
    if(strrchr(s3, '/') == NULL)		 /* filename only */
      return 1;
    if (s3[strlen(s3)-1] == '/')		 /* directory - should be */
      return 1;					 /* created by caller */
    
    *strrchr(s3, '/') = 0;			 /* remove filename */
    
    *s2 = 0;
    s1 = s3;
    if (*s1 == '/') {				 /* we don't try and create */
	strcpy(s2, "/");			 /* the root directory */
	s1++;
	c = 1;
    }
    while (*s1) {
	while (*s1 && (*s1 != '/')) 
	    s2[c++] = *s1++;
	s2[c] = 0;
	if (*s1) s1++;
	if ((dir=opendir(s2)) == NULL) 		 /* check to see if dir exists */
	  if (errno == ENOENT) {		 /* no it doesn't */
	      if (mkdir(s2, 493) == -1) {		 /* try and create */
		  sprintf(s3, "creating directory %s", s2);
		  write_error_log(s3);
		  return 0;
	      }
	  }
	  else {
	      sprintf(s3, "opening directory %s", s2);
	      write_error_log(s3);
	      return 0;
	  }
	
	if (dir)				 /* close dir if it was opened */
	  closedir(dir);
	s2[c++] = '/';
    }
    return 1;
}
    

int open_info_file(int must_exist, long archive_id)
{
    char    info_file[MAXNAMLEN];
    int     fd;
    
    strcpy(info_file, taper_info_files);	 /* look for information file */
    if (info_file[strlen(info_file)-1] != '/')	 /* associated with this archive */
      strcat(info_file, "/");
    sprintf(info_file, "%staper.%ld", info_file, archive_id);

    if (log_level == 3) write_log("Trying to open info file");
    fd = open(info_file, O_RDWR);		 /* open information file */
    if ((fd == -1) && (must_exist))		 /* error if doesn't exist */
      do_exit(ERROR_NO_INFO);			 /* and it must */

    if (!make_dirs(info_file))			 /* Make directories leading up to info */
      do_exit(ERROR_CREATING_INFO);
    if (fd == -1) {
	if (log_level == 3) write_log("Creating info file");
	fd = open(info_file, O_RDWR|O_CREAT, S_IWRITE|S_IREAD);
    }
    if (fd == -1)
      do_exit(ERROR_CREATING_INFO);
    return fd;
}


int cmp_fe(const void *cmp1, const void *cmp2)
{
/* Sort order for files in volume directory:
 * 
 * 1. directories
 * 2. non-regular files
 * 3. files - a. name
 *            b. date of backup
*/
    struct file_entry *fe1= (struct file_entry *) cmp1;
    struct file_entry *fe2= (struct file_entry *) cmp2;

    if ((S_ISDIR(fe1->i.org_mode) &&		 /* 1 is directory, 2 is not */
	 (!S_ISDIR(fe2->i.org_mode))))		 /* 1 comes before 2 */
	   return -1;
    if ((S_ISDIR(fe2->i.org_mode) &&		 /* 2 is a directory, 1 is not */
	 (!S_ISDIR(fe1->i.org_mode))))		 /* 2 before 1 */
	    return 1;
    if ((S_ISDIR(fe1->i.org_mode) &&		 /* both directories */
	 (S_ISDIR(fe2->i.org_mode))))		 /* compare on name */
      goto namecmp;
	 
    if ((S_ISREG(fe1->i.org_mode)) &&		 /* 1 is reg file, 2 is not  */
	(!S_ISREG(fe2->i.org_mode)))		 /* 2 comes before 1 */
      return 1;
    if ((S_ISREG(fe2->i.org_mode)) &&		 /* 2 is reg file, 1 is not  */
	(!S_ISREG(fe1->i.org_mode)))		 /* 1 comes before 2 */
      return -1;
    
    namecmp:					 /* compare by name first */
    if (strcmp(fe1->name, fe2->name))
      return strcmp(fe1->name, fe2->name);
    if (fe1->i.backup_time > fe2->i.backup_time)
      return 1;
    if (fe1->i.backup_time < fe2->i.backup_time)
      return -1;
    return 0;
}


int make_cf(char *c, long no_in_archive)
{
/* Works out common pathname prefix 
 * c points to first file entry in info file 
 
   Returns the maximum path user can strip */
    
    long    cs;
    int     cf1;
    char    *lp, *src;
    int     maxstrip;
    
    c += sizeof(struct file_info);
    src = c;
    strcpy(cf, c);
    c += strlen(c)+1;			 /* move to next entry in info file */
    for (cs=0; cs<no_in_archive; cs++) {
	c += sizeof(struct file_info);	 /* move to filename */
	lp = c; cf1 = 0;
	c += strlen(c)+1;
	while (*lp && cf[cf1]) {
	    if (*lp != cf[cf1]) {		 /* pathnames differ here */
		while (cf1) {
		    if (cf[cf1] == '/') 
		      break;
		    else
		      cf1--;
		}
		cf[cf1] = 0;		 /* terminate common here */
		break;
	    }
	    lp++; cf1++;
	}
	if (!strcmp(cf, src)) {		 /* filenames are the same */
	    if (!*cf) continue;
	    if (cf[strlen(cf)-1] == '/')
	      cf[strlen(cf)-1] = 0;
	    if (strrchr(cf, '/'))
	      *strrchr(cf, '/') = 0;
	    else
	      *cf = 0;
	}
    }
    maxstrip = 1;
    lp = cf;
    while (*lp) {
	if (*lp == '/') maxstrip++;
	lp++;
    }
    return maxstrip;
}


void make_stripped_cf()
{
    char *s;
    int  l;

    *stripped_cf = 0;
    if (!*cf) return;
    if (!lstrip) {
	strcpy(stripped_cf, cf);
	return;
    }

    s = cf;
    l=1;
    while (l<lstrip) {
	s++;
	while (*s && (*s != '/'))  s++;
	l++;
    }
    strcpy(stripped_cf, s+(*s != 0));
}


int do_read_vol_dir(char *tape, int mess, int mode, int rvdir, int must_exist) {
/* Closes the backup device after use */    
    struct file_entry *fe, *fe1;
    char   *cp;
    long    c, c1, sz;
    WINDOW  *mes=NULL;
    int    fd;
    long   new_no;
 
    if (mess)
      mes = status_box(mes, "Rewinding tape", 1, TRUE, 1);
    if (tape_rewind() == -1) 
	do_exit(ERROR_OPENING_BACKUP);
     
    if (mess)
      status_box(mes, "Identifying tape", 1, FALSE, 1);
    dv = tape_open(mode);			 /* open rewinding device */
    if (dv == -1) 				 /* so that after this procedure */
      if (must_exist) 				 /* tape should be at beginning */
	do_exit(ERROR_OPENING_BACKUP);
      else {
	if (mess) close_statusbox(mes);
	return TAPE_NOEXIST;
    }

    tape_readheader(&tdh, 0);
    tape_close();

    if ((tdh.magic != TAPER_MAGIC_NUMBER) || (!rvdir)) {
	if (mess)  close_statusbox(mes);
	return (tdh.magic == TAPER_MAGIC_NUMBER) ? TAPE_EXIST : BAD_MAGIC;
    }
    
    if ((tdh.magic == TAPER_MAGIC_NUMBER) && (tdh.archive_id == 0))
      if (must_exist)
        do_exit(ERROR_EMPTY_ARCHIVE);
      else 
	  return TAPE_EXIST_EMPTY;

    fd = open_info_file(TRUE, tdh.archive_id);
    read(fd, (char *) &ifd, sizeof(struct info_file_header));
    if (ifd.archive_id != tdh.archive_id) {	 /* archive and info files */
	close(fd);				 /* do not match */
	do_exit(INFO_MISMATCH);
    }
    no_in_archive = ifd.no_in_archive;
    info = my_malloc(ifd.info_file_size);	 /* allocate memory to read in whole information */
    if (!info)					 /* can't allocate */
      do_exit(ERROR_MEMORY);
    if (log_level == 3) write_log("Reading in info file");
    read(fd, (char *) (info+sizeof(struct info_file_header)), ifd.info_file_size-sizeof(struct info_file_header));
    memcpy(info, &ifd, sizeof(struct info_file_header));
    vt_info = my_realloc(vt_info, sizeof(struct volume_tape_info)*ifd.number_volumes);
    if (vt_info==NULL)				 /* get block for volume/tape info */
      do_exit(ERROR_MEMORY);
    read(fd, vt_info, sizeof(struct volume_tape_info)*ifd.number_volumes);   /* read in volume/tape info */
    if (log_level == 3) write_log("Closing info file");
    close(fd);					 /* close info file */
    total_compressed = 0; total_uncompressed = 0;/* now extract info from info file */
    cp = info + sizeof(struct info_file_header);
    vol_files = my_malloc(ifd.no_in_archive * sizeof(struct file_entry));
    if (!vol_files)				 /* allocate memory for files */
      do_exit(ERROR_MEMORY);
    fe = vol_files;
    new_no=0;
    
    lstrip = min(make_cf(cp, ifd.no_in_archive), lstrip);/* find common pathname */
    make_stripped_cf();				 /* work out what is the prefix to get right strip level */
    
    for (c=0; c<ifd.no_in_archive; c++) {
	fe->i = *((struct file_info *) cp); cp += sizeof(struct file_info);
	fe->selected = 0;
	total_compressed += fe->i.act_size + sizeof(struct file_info) + strlen(cp)+1;
	total_uncompressed += fe->i.size + sizeof(struct file_info) + strlen(cp)+1;
	strcpy(fe->name, &cp[strlen(cf)+ (cf!=0)]);   /* make stripped filename */
	cp += strlen(cp)+1;
        if ((limit_vols == 0) || (fe->i.volume == limit_vols)) {
	    fe++;
	    new_no++;
	}
    }
    ifd.no_in_archive = new_no;			 
    memcpy(info, &ifd, sizeof(struct info_file_header));
    if (mess)
      status_box(mes, "Calculating directory sizes", 1, FALSE, 1);
    if (sort_dir) 
      qsort(vol_files, ifd.no_in_archive, sizeof(struct file_entry), cmp_fe);
    
    fe = vol_files;
    for (c1=0; c1<ifd.no_in_archive;c1++) {
	if (S_ISDIR(fe->i.mode)) {		 /* not a directory */
	    fe1=vol_files;
	    sz=0;
	    for (c=0; c<ifd.no_in_archive;c++) {
		if (strlen(fe1->name) >= strlen(fe->name))
		  if (!strncmp(fe->name, fe1->name, strlen(fe->name)))
		    if (!S_ISDIR(fe1->i.mode))
		      if (fe->i.volume == fe1->i.volume)   
		        sz += fe1->i.size;
		fe1++;
	    }
	    fe->dirsize = sz;
	}
	fe++;
    }

    if (mess) close_statusbox(mes);
    return TAPE_EXIST;
}

void file_type(char *s, umode_t mode) {

    s[1] = 0;
    *s = ' ';
    if (S_ISDIR(mode)) 
      *s = 'd';
    if (S_ISLNK(mode))
      *s = 'l';
    if (S_ISBLK(mode))
      *s = 'b';
    if (S_ISCHR(mode))
      *s = 'c';
}


char *convtime(time_t t1, time_t t2)
{
    static char   s[50];
    long   secs=t2-t1;
    int    hr,min,sc;
    
    hr=secs/3600;
    min=(secs-hr*3600)/60;
    sc=secs-hr*3600-min*60;
    sprintf(s,"%d:%02d:%02d",hr,min,sc);
    return s;
}

void fixup_device_names()
{
    char s[MAXNAMLEN];
    
/* Converts the device name to a full pathname */
    getcwd(cur_dir, sizeof(cur_dir));
    if (*ntape != '/') {
	sprintf(s, "%s/%s", cur_dir, ntape);
	strcpy(ntape, s);
    }
    if (*tape != '/') {
	sprintf(s, "%s/%s", cur_dir, tape);
	strcpy(tape, s);
    }
}


long calc_checksum(int fd)
{
/* Calculates a file checksum 
   Returns -1 if error */
    long    sz, chk=0, c;
    
    lseek(fd, 0, SEEK_SET);
    while (1) {
	sz = read(fd, (char *) tr_buffer, max_tr_size);/* read in file */
	if (!sz)				 /* ?EOF */
	  break;
	if (sz == -1) return -1;
	for (c=0; c<sz;c++)			 /* add up bytes in */
	  chk += *(tr_buffer+c);		 /* this file */
    }
    lseek(fd, 0, SEEK_SET);			 /* move back to BOF */
    return chk;					 /* return checksum */
}
    
void open_logfile(char *prog)
{
    char s[100];
    
    lf = NULL;
    if (*log_file)
      lf = fopen(log_file, "a");
    if (!lf) do_exit(ERROR_OPENING_LOG);
    strcpy(s, "Starting "); strcat(s, prog);
    write_log(s);
}


void close_logfile(char *prog)
{
    char s[100];
    
    if (lf) {
	sprintf(s, "%s finished.", prog);
	write_log(s);
	fputs("\n\n", lf);
    }
}

    
void write_log(char *s)
{
    time_t  t;
    char    s1[500];
    
    if (!lf) return;				 /* no log file open */
    t = time(NULL);
    strcpy(s1, ctime(&t));
    s1[strlen(s1)-1] = 0;			 /* remove trailing \n */
    fprintf(lf, "%s:  %s\n", s1, s);
}


void write_error_log(char *s)
{
/* Writes an error to the log file. 
 * 
 * Format:   Date:  ERROR: Error while s
*/ 
    char s1[200];
    
    sprintf(s1, "ERROR:  %s while %s", strerror(errno), s);
    log_errors++;
    write_log(s1);
}


void write_warning_log(char *s)
{
/* Writes a warning to the log file. 
 * 
 * Format:   Date:  WARNING: s
*/ 
    char s1[200];
    
    sprintf(s1, "WARNING:  %s", s);
    log_warnings++;
    write_log(s1);
}


void write_fatal_log(char *s)
{
/* Writes a fatal error to the log file. 
 * 
 * Format:   Date:  FATAL ERROR: Error while s
*/ 
    char s1[200];
    
    sprintf(s1, "FATAL ERROR:  %s", s);
    log_warnings++;
    write_log(s1);
}


void read_volheader(long *no_in_vol)
{
/* Reads in volume magic # and no in vol */
    
    if (log_level == 3) write_log("Reading in volume magic number");
    if (tape_read((char *) no_in_vol, sizeof(long)) != sizeof(long)) { /* volume magic number */
	write_fatal_log("reading in volume magic number");
	do_exit(ERROR_READING);
    }
    if (*no_in_vol != VOLUME_MAGIC)		 /* volume magic doesn't agree - warning */
      write_warning_log("Volume magic number incorrect");   /* try to proceed */
    if (log_level == 3) write_log("Reading in number files on volume");
    if (tape_read((char *) no_in_vol, sizeof(long)) != sizeof(long)) { /* reads in number in volume */
	write_fatal_log("reading in number of files on volume");
	do_exit(ERROR_READING);
    }
}


char on_or_off(char *arg)
{
/* returns 1 if the last part of the string arg is '-on' or returns 0
 * if last part is -off. Returns -1 if neither on/off */
  
    int s;

    s = strlen(arg);				 /* find end of string */
    if (!s) return -1;				 /* NULL string==error */
    while (--s) 				 /* look for - */
      if (arg[s] == '-') break;
    if (!s) return -1;				 /* no dash found */
    if (!strcasecmp(&arg[s], "-off")) return 0;	 
    if (!strcasecmp(&arg[s], "-on")) return 1;
    return -1;					 /* neither on/off */
}


char common_parse_args(int argc, char *argv[], int *c)
{
/* Does parsing of args common to all progs 
 * Returns 1 if recognised an arg, 0 if didn't 
 * Returns -1 if usage error */
    if (!strcasecmp(argv[*c], "--prompt-dirs"))
      return (dir_selection=on_or_off(argv[*c])); 
    if (!strcasecmp(argv[*c], "--alpha-sort"))
      return (sort_dir=on_or_off(argv[*c])); 
    if (!strcasecmp(argv[*c], "--strip"))	 /* convert it to short option */
      argv[*c][1] = 's';
    if (!strcasecmp(argv[*c], "--rewinding-device"))
      argv[*c][1] = 'f';
    if (!strcasecmp(argv[*c], "--non-rewinding-device"))
      argv[*c][1] = 'n';
    if (!strcasecmp(argv[*c], "--both-devices"))
      argv[*c][1] = 'b';
    if (!strcasecmp(argv[*c], "--archive-title"))
      argv[*c][1] = 't';
    if (!strcasecmp(argv[*c], "--info-files"))
      argv[*c][1] = 'i';
    if (!strcasecmp(argv[*c], "--log-file"))
      argv[*c][1] = 'l';
    if (!strcasecmp(argv[*c], "--log-level"))
      argv[*c][1] = 'm';
    if (!strcasecmp(argv[*c], "--version"))
      argv[*c][1] = 'v';
    if (!strcasecmp(argv[*c], "--max-transfer-size"))
      argv[*c][1] = 'x';
    if (!strcasecmp(argv[*c], "--block-size"))
      argv[*c][1] = 'x';
    if (!strcasecmp(argv[*c], "--preference-file"))
      argv[*c][1] = 'p';
    switch(argv[*c][1]) {
     case '-':					 /* a long option we didn't recognize */
	return 0;
     case 'S':
     case 's':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;
	lstrip = atoi(argv[*c]);
	break;
     case 'Z':					 /* prompt for directory selection */
     case 'z':
	dir_selection = (argv[*c][0] == '-') ? 0 : 1;
	break;
     case 'y':
     case 'Y':
	sort_dir = (argv[*c][0] == '-') ? 0 : 1;
	break;
     case 'F':					 /* backup device name (rewinding) */
     case 'f':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	strcpy(tape, argv[*c]);
	break;
     case 'N':					 /* backup device name (non-rewinding) */
     case 'n':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	strcpy(ntape, argv[*c]);
	break;
     case 'b':					 /* both rewinding & non-rewinding device */
     case 'B':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	strcpy(ntape, argv[*c]);
	strcpy(tape, argv[*c]);
	break;
     case 'p':
     case 'P':
	(*c)++;					 /* ignore -p option */
	if (*c==argc)				 /* it will be picked up in  */
	  return -1;				 /* read_prefs() */
	break;
     case 'v':
     case 'V':
	fprintf(stderr, "Version is %s\n", CUR_VERSION);
	do_exit(ERROR_NONE);
     case 'i':
     case 'I':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	strcpy(taper_info_files, argv[*c]);
	break;
     case 'l':
     case 'L':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	strcpy(log_file, argv[*c]);
	break;
     case 'm':
     case 'M':
	(*c)++;
	if (*c==argc)				 /* make sure that they've supplied */
	  return -1;				 /* another arg */
	log_level = atoi(argv[*c]);		 
	break;
     case 'x':
     case 'X':
	(*c)++;
	if (*c==argc)
	  return -1;
	block_size = atol(argv[*c]);
	if (block_size < 2048)			 /*  minimum is 2K */
	  do_exit(ERROR_TRSIZE);
	break;
     default:
	return 0;
    }
    return 1;
}


void common_usage()
{
/* Prints usage of common options */   
    fprintf(stderr, "\n\n");
    fprintf(stderr, "-i path (--info-files)            where the archive information files are\n");
    fprintf(stderr, "-p file (--preference-file)       name of preferences file\n");
    fprintf(stderr, "-f file (--rewinding-device)      use file as backup device (rewinding) \n");
    fprintf(stderr, "-n file (--non-rewinding-device)  use file as backup device (non-rewinding) \n");
    fprintf(stderr, "-b file (--both-devices)          use file as rewinding & non-rewinding device\n");
    fprintf(stderr, "-l file (--log-file)              name of log file\n");
    fprintf(stderr, "-m num  (--log-level)             level of logging (0=none..3=verbose)\n");
    fprintf(stderr, "-s num  (--strip)                 level of pathname stripping\n");
    fprintf(stderr, "-v      (--version)               prints current version\n");
    fprintf(stderr, "-x num  (--max-transfer-size)     maximum number bytes to transfer\n");
    fprintf(stderr, "+/- y   (--alpha-sort-[on|off])   turns on or off directory sorting\n");
    fprintf(stderr, "-/+ z   (--prompt-dirs-[on|off])  confirm directory selection\n");
    fprintf(stderr, "-?      (--help)                  help\n");
}


void mktape_usage()
{
/* Prints usage of common options for mktape & testzero */   
    fprintf(stderr, "\n\n");
    fprintf(stderr, "-p file (--preference-file)       name of preferences file\n");
    fprintf(stderr, "-f file (--rewinding-device)      use file as backup device (rewinding) \n");
    fprintf(stderr, "-n file (--non-rewinding-device)  use file as backup device (non-rewinding) \n");
    fprintf(stderr, "-b file (--both-devices)          use file as rewinding & non-rewinding device\n");
    fprintf(stderr, "-v      (--version)               prints current version\n");
    fprintf(stderr, "-?      (--help)                  help\n");
}


void calc_tape_pos(int vol_no, int *tape, int *pos)
{
/* Works out on what tape and at what position on the tape
 * vol_no is.  
*/ 
    int count;
    struct volume_tape_info *vti;
    
    vti = (struct volume_tape_info *) vt_info;
    for (count=0; count < ifd.number_volumes; count++)/* find which tape it's on */
	if (vti->volume == vol_no) {		 /* found this volume */
	    *tape = vti->tape;
	    break;
	}
        else
          vti++;

    vti = vt_info;
    *pos = (*tape == 1) ? 0 : 1;
    for (count=0; count < ifd.number_volumes; count++) {   /* now find out how many volumes are before it */
	if ((vti->tape == *tape) && (vti->volume < vol_no))
	  (*pos)++;
	vti++;
    }
}


int traverse_volume(file_passed_action action, long no_in_vol,
		    time_t t_start, WINDOW *mes_box, int full_traverse,
		    int doing_mkinfo, print_status ps)
/* Traverses a volume.
 * Returns -1 is user aborted, otherwise 0
 * 
 * If full_traverse == 1, then traverse completes the volume
 * otherwise, traverse is allowed to finish mid-volume
 * 
 * Action should return
 *    -2 to stop traverse (file was not read in)
 *    -1 to stop traverse (file was read in)
 *     0 file was not read in
 *     1 file was read in
 */ 
{
    long tr, x, sz, a, total_read=0;
    struct file_info fi;
    char fn[MAXNAMLEN],  scr[100+MAXNAMLEN];
    char l[MAXNAMLEN*2];
    time_t t_current;
    long  cur_in_vol;
    
    cur_in_vol=0;
    while (cur_in_vol < no_in_vol) {
	*scr = wgetch(mes_box);
	if ((*scr == 'q') || (*scr == 'Q'))
	  return -1;
	cur_in_vol++;				
	sprintf(l, "Reading in file info & filename");
	if (tape_read((char *) &fi, sizeof(fi)) != sizeof(fi)) {
	    write_fatal_log(l);
	    do_exit(ERROR_READING);
	}
	if (tape_read(fn, fi.name_len) != fi.name_len) {/* skip past filename */
	    write_fatal_log(l);
	    do_exit(ERROR_READING);
	}
	
	t_current = time(NULL);
	ps(mes_box,  cur_in_vol, fi.act_size, fn,
	   t_start, t_current);			 /* let caller print status box */

	sprintf(scr, "Average restore/mkinfo rate %ld K/sec",
		((t_current-t_start) ? (total_read/1024)/(t_current-t_start) : 0));
	status_box(mes_box, scr, 6, FALSE, 1);

	total_read += fi.act_size + sizeof(struct file_info)+strlen(fn)+1;
	a=action(&fi, fn);
	if ((a < 0) && (!full_traverse)) return 0;/* finish traverse */
	if ((a==1) || (a==-1))  continue;	 /* file was read in */

	sprintf(l, "Passing file %s", fn);
	if (log_level > 1) write_log(l);
	if (S_ISLNK(fi.mode)) {
	    sprintf(l, "Passing link info for %s", fn);
	    if (log_level == 3) write_log(l);
	    if (tape_read((char *) &x, sizeof(x)) != sizeof(x)) {/* a link */
		write_fatal_log(l);
		do_exit(ERROR_READING);
	    }
	    if (log_level == 3) write_log(l);
	    if (tape_read(fn, x) != x) {	 /* who do we point to */
		write_fatal_log(l);
		do_exit(ERROR_READING);
	    }
	    continue;
	}
	
	if (!S_ISREG(fi.mode))			 /* must be directory or device file */
	  continue;
	
	if (fi.checksum == -1) {		 /* checksum -1 - therefore file not backed up */
	    sprintf(l, "There was a problem when backing up file %s, therefore file not restored", fn);
	    write_warning_log(l);
	    continue;				 /* problem backing - no file backed up */
	}

	sz = fi.act_size;
	while (1) {				 /* copy file accross */
	    tr = min(sz, max_tr_size); 
	    x = tape_read(tr_buffer, tr);	 /* to device file   */
	    if (!x) break;
	    if (x == -1) {
		write_fatal_log(l);
		do_exit(ERROR_READING);
	    }
	    sz -= x;
	    if (!sz) break;			 /* read all we need to */
	}
    }
    return 0;					 /* successful return */
}


void common_restore_loop(file_passed_action action, int doing_mkinfo,
			 print_status ps)
{
/* This loops through the archive. It calls action for each file
 * in encounters.
 * 
 * If doing_mkinfo = -1, then it assumed that we don't know how many
 * files are in archive so the search continues until the very end
 * 
 * We use the non-rewinding device so that when the end of tape is
 * reached, the user doesn't have to wait for the device to rewind
 * before s/he can insert the next tape
 * 
 * Note that for regular files, when moving on to a new volume, then
 * the rest of the data from the block currently read must be discarded
 * because there is no easy was to do a tape_fsf(dv,1) on regular files.
 * 
 * Assumes a rewinding tape device is open and tape header has been read in 
 */
    char l[MAXNAMLEN*2];
    WINDOW *mes_box=NULL;
    time_t t_start;
    long  no_in_vol, cur_vol;
    struct volume_tape_info *vi;

    mes_box = status_box(mes_box, "", 2, TRUE, 4);/* create window */
    nodelay(mes_box, TRUE);
    status_box(mes_box, "Identifying tape", 2, FALSE, 1);
    if (check_tape(1) == -1)			 /* make sure first tape in drive */
      do_exit(ERROR_OPENING_BACKUP);
    cur_vol = 0;				 
    t_start = time(NULL);
    read_volheader(&no_in_vol);
    while (1) {				 
	if (traverse_volume(action, no_in_vol, t_start, 
			    mes_box, 1, doing_mkinfo, ps) == -1)/* traverse this volume */
	  break;				 /* user aborted */
	cur_vol++;
	if ((cur_vol == ifd.number_volumes) && (doing_mkinfo != -1))
	  break;
	if (!is_regfile(dv)) {		 /* if a tape device is being */
	    status_box(mes_box, "", 0, FALSE, 1);
	    status_box(mes_box, "", 2, FALSE, 1);
	    status_box(mes_box, "", 5, FALSE, 1);
	    status_box(mes_box, "", 6, FALSE, 1);
	    status_box(mes_box, "Looking for next volume", 3, FALSE, 1);
	    tape_close();			 /* reposition tape */
	    					 /* should rewind since we expect a rewinding to be open */
	    dv = ntape_open(O_RDONLY);		 /* it's a pity that we have to rewind */
	    if (dv == -1) 			 /* but otherwise, ftape doesn't want to work */
	      do_exit(ERROR_OPENING_BACKUP);
	    vols_passed++;			 /* passed another vol on this tape */
	    tape_fsf(vols_passed);	
	    tape_close(dv);		
	    strcpy(l, "Reopening device after repositioning");
	    dv = tape_open(O_RDONLY);		 /* open rewinding */
	    if (dv == -1) {
		write_fatal_log(l);
		do_exit(ERROR_OPENING_BACKUP);
	    }
	}
	else  {
	    if (log_level == 3) write_log("Simulating fsf 1 by discarding rest of block");
	    read_offset = -1;			 /* discard the rest of data from this block */
	}
	
	if (doing_mkinfo == -1) {		 /* don't know how many vols */
	    if (tape_read((char *) &no_in_vol, sizeof(no_in_vol)) != sizeof(no_in_vol))   /* read in magic */
	      break;				 /* couldn't read - assume eot */
	    if (no_in_vol != VOLUME_MAGIC)	 /* not another volume */
	      break;				 /* therefore assume eot */
	    if (tape_read((char *) &no_in_vol, sizeof(no_in_vol)) != sizeof(no_in_vol))
	      break;				 /* couldn't read in no in vol - eot */
	    ifd.number_volumes++;
	    vt_info = my_realloc(vt_info, sizeof(struct volume_tape_info)*ifd.number_volumes);
	    vi = vt_info+ifd.number_volumes-1;	 /* update volume/tape info */
	    vi->volume = ifd.number_volumes;
	    vi->tape = tdh.tape_number;
	}
	else
	  read_volheader(&no_in_vol);
    }
    nodelay(mes_box, FALSE);
    my_free(tr_buffer);
    close_statusbox(mes_box);
}


void change_dollar(char *s)
{
/* Reads a string an replaces $ with \$
 * To avoid shell expansions
 */
    
    char  *s1=s, *s2;
    
    while (*s1) {
	if (*s1 == '$') {
	    for (s2=&s[strlen(s)]; s2>=s1; s2--)
	      *(s2+1) = *s2;
	    *s1 = '\\';
	    s1++;
	}
	s1++;
    }
}


void init_buffers()
{						 
/* Allocates memory for three buffers
 * 
 * 1. Read buffer
 * 2. Write buffer
 * 3. File transfer buffer
 */ 
    read_offset = -1;
    read_buffer_count = 0;
    write_offset = 0;
    write_buffer_count = 0;
    read_buffer = my_malloc(max_tr_size);
    if (read_buffer == NULL) do_exit(ERROR_MEMORY);
    write_buffer = my_malloc(max_tr_size);
    if (write_buffer == NULL) do_exit(ERROR_MEMORY);
    tr_buffer = my_malloc(max_tr_size);
    if (tr_buffer == NULL) do_exit(ERROR_MEMORY);
}


void final_message(char *prog)
{
/* Prints the final message, telling how many warnings & how many
 * errors were encountered
 */
    
    char s[100], s1[100], s2[100];
    
    sprintf(s, "%s completed successfully", prog);
    sprintf(s1, "%d Warnings", log_warnings);
    sprintf(s2, "%d Errors", log_errors);
    three_message_box(s, s1, s2, MB_OK);
}


void print_title_line()
{
/* Prints the top title line with the archive ID & archive title */
    char s[200];

    my_werase(title, COLOR_TITLE);
    sprintf(s, "Archive ID %ld. Title '%s'", tdh.archive_id, tdh.archive_title);
    centre(title, 0, s, COLOR_TITLE);
    wrefresh(title);
}

