#include <conf.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#ifdef	HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef	HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef	HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifdef	HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef	HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#include <x_types.h>
#include <utils.h>
#include <fileutil.h>

#include "backup.h"

#ifdef	DEBUG
#define	DB(fp, fmt, arg1, arg2, arg3)	fprintf(fp, fmt, arg1, arg2, arg3)
#else
#define	DB(fp, fmt, arg1, arg2, arg3)	/**/
#endif

#define	GETOUT	{ goto getout; }
#define CLEANUP	{ goto cleanup; }

#define	SERIAL_OPERATION	1
#define	QUEUED_OPERATION	2

#define	MAX_QUEUE_MEMORY	10485760	/* 10 MB */

typedef	struct _queueentry_ {
  UChar		*inmem;
  UChar		*outmem;
  Uns32		num_in;
  Uns32		num_out;
  Uns16		instruction;
  UChar		processed;
} QueueEntry;

#define	NOT_PROCESSED		0
#define	PROCESSED_OK		RESERVED_ERROR_CODE

UChar		comm_mode = QUEUED_OPERATION;
Uns32		queuelen = 512;
QueueEntry	*queue_entries = NULL;
UChar		*outbufmem = NULL;
UChar		*inbufmem = NULL;
Uns32		queueent_done_idx = 0;
Uns32		queueent_requested_idx = 0;
Uns32		queueent_processed_idx = 0;
Uns32		queue_insert_idx = 0;

#define	QUEUEDOP	(comm_mode == QUEUED_OPERATION)

#define	MAX_OUTARGLEN	(COMMBUFSIZ + 4)
#define	MAX_INRESULTLEN	MAX_OUTARGLEN

Uns32		bytes_transferred;
UChar		cycle_started;
Uns32		num_cycles_processing;
Uns32		transfer_per_cycle;
Uns32		transfer_per_cycle_cur;
Uns32		transferred_uninterrupted;

Uns32		bytes_transferred_prev;
Uns32		transfer_per_cycle_prev;

Uns32		queue_clock_usec;

UChar		*servername = NULL;
short unsigned	portnum = DEFAULT_PORT;

UChar		scbuf[COMMBUFSIZ + 4];
UChar		*cbuf = &(scbuf[1]);
Int32		cbufptr = 0;		/* pointer into send/receive-buffer */

#define	reset_data_buffer	{ cbufptr = 0; }

int		commfd = -1;
int		commfd_socktype = -1;
struct timeval	null_timeval;

Int32		cart;
Int32		filenum;
Int32		wrcart;
Int32		wrfilenum;

UChar		*infoheader = NULL;

UChar		filenum_valid = 0;

Int32		endofrec = 0;

AarParams	params = AAR_DEFAULT_PARAMS;

UChar		bu_create = 0;
UChar		bu_extract = 0;
UChar		bu_contents = 0;
UChar		bu_verify = 0;
UChar		bu_index = 0;
UChar		bu_ready = 0;
UChar		allfiles = 0;
UChar		verbose = 0;
UChar		c_f_verbose = 0;
UChar		l_verbose = 0;
UChar		o_verbose = 0;
UChar		bu_request = 0;

UChar		*clientprog = NULL;

UChar		**filelist = NULL;

char		*toextractfilename = NULL;
char		*errorlogfile = NULL;
char		*savefile = NULL;

Int32		num_errors = 0;

UChar		long_errmsgs = 0;
UChar		**gargv;

#define	MAX_NUM_ERRORS		20

#define	ESTIM_PROT_OVERHEAD	50

struct fault_msgs	fault_messages[] = FAULT_MESSAGES;

void	start_queue_timer();
void	queue_processor(int);
Int32	post_process_entry(Int32);
Int32	post_process_rest_of_queue();
Int32	append_to_queue(Uns16, UChar *, Uns32, UChar *, Uns32);
Int32	simple_command(UChar);

#define	set_server_serial	simple_command(SETSERIALOP)
#define	set_server_buffering	simple_command(SETBUFFEREDOP)
#define	set_server_erroneot	simple_command(SETERRORONEOT)
#define	set_server_chcartoneot	simple_command(SETCHCARTONEOT)


static void
errmsg(UChar * msg, ...)
{
  va_list	args;

  va_start(args, msg);

  if(long_errmsgs)
    fprintf(stderr, "%s, ", actimestr());

  vfprintf(stderr, msg, args);

  va_end(args);

  if(msg[strlen(msg) - 1] != '\n')
    fprintf(stderr, ".\n");

  fflush(stderr);
}

static Int32
E__(Int32 e)
{
  if(e)
    errmsg("%sError: %s", (e > 0 ? "Server " : ""),
		(e > 0 ? fault_string(e) : (UChar *) strerror(-e)));

  return(e);
}

#define	OK__(func)	(! E__(func))

void
signal_handler(int s)
{
  if(long_errmsgs || s == SIGTERM || s == SIGINT)
    fprintf(stderr, "%s got signal %d, exiting.\n",
					FN_BASENAME(gargv[0]), s);

  exit(128 | s);
}

UChar
send_cmd(UChar cmd)		/* utility */
{
  if(write_split(commfd, &cmd, 1))
    return(errno ? -errno : EOF);

  return(0);
}

UChar
result()
{
  UChar		c;
  Int32		i;

  if(savefile)
    return(0);

  i = read_split(commfd, &c, 1);

  if(i){
    return(errno ? -errno : EOF);
  }

  if(! c)
    return(0);

  return(c);
}

UChar
send_buffer_to_server()
{
  Int32		i, num, r;
  UChar		*cptr;

  scbuf[0] = WRITETOTAPE;

  cptr = scbuf;
  num = COMMBUFSIZ + 1;

  if(savefile){
    cptr = cbuf;
    num = COMMBUFSIZ;
  }

  filenum_valid = 0;

  if(QUEUEDOP){
    r = append_to_queue(WRITETOTAPE, NULL, 0, cbuf, COMMBUFSIZ);
    return(r);
  }

  if( (i = write_split(commfd, cptr, num)) )
    return(i);

  r = result();

  return(r);
}

Int32
send_pending()
{
  if(cbufptr == 0)
    return(0);

  if(cbufptr < COMMBUFSIZ)
    memset(cbuf + cbufptr, 0, (COMMBUFSIZ - cbufptr) * sizeof(UChar));

  return(send_buffer_to_server());
}

Int32
send_to_server(UChar * buf, Int32 num)
{
  UChar		*cptr;
  Int32		j;

  if(num == 0)
    return(0);

  cptr = buf;

  forever{
    if(cbufptr + num <= COMMBUFSIZ){
      memcpy(cbuf + cbufptr, cptr, num * sizeof(UChar));

      cbufptr += num;

      return(0);
    }

    if(cbufptr < COMMBUFSIZ)
      memcpy(cbuf + cbufptr, cptr, (COMMBUFSIZ - cbufptr)
					* sizeof(UChar));

    j = send_buffer_to_server();
    if(j){
	return(j);
    }

    cptr += (COMMBUFSIZ - cbufptr);

    num -= (COMMBUFSIZ - cbufptr);

    cbufptr = 0;
  }

  return(0);
}

Int32
bu_output(UChar * buf, Int32 num, AarParams * params)
{
  params->vars.bytes_saved += (Real64) num;

  return(send_to_server(buf, num));
}


Int32
receive_buffer_from_server()
{
  Int32		i, j;

  filenum_valid = 0;

  if(QUEUEDOP){
    i = append_to_queue(READFROMTAPE, cbuf, COMMBUFSIZ, NULL, 0);

    j = post_process_rest_of_queue();

    return(i ? i : j);
  }

  if(!savefile){
    if( (i = send_cmd(READFROMTAPE)) )
      return(i);
  }

  if( (i = read_split(commfd, cbuf, COMMBUFSIZ)) )
    return(i);

  return(result());
}

Int32
receive(UChar * buf, Int32 num, Int32 * num_read)
{
  Int32		res, j;
  UChar		*cptr;

  if(num == 0){
    *num_read = 0;
    return(0);
  }

  cptr = buf;

  j = num;

  forever{
    if(cbufptr == 0){
	if(!endofrec)
	  res = receive_buffer_from_server();
	else
	  res = 0;

	if(res){
	  if(res < 0){
	    *num_read = num - j;
	    return(res);
	  }
	  else{
	    if(res == ENDOFFILEREACHED || res == ENDOFTAPEREACHED){
		endofrec = 1;
	    }
	    else{
		*num_read = num - j;
		return(res);
	    }
	  }
	}
    }

    if(COMMBUFSIZ - cbufptr <= j){
	if(cbufptr < COMMBUFSIZ)
	  memcpy(cptr, cbuf + cbufptr, COMMBUFSIZ - cbufptr);

	cptr += (COMMBUFSIZ - cbufptr);
	j -= (COMMBUFSIZ - cbufptr);
	cbufptr = 0;

	if(endofrec){
	  *num_read = num - j;
	  return(ENDOFFILEREACHED);
	}
	if(j == 0){
	  *num_read = num;
	  return(0);
	}
    }
    else{		/* in case of eof this is not really good code */
	if(j > 0)
	  memcpy(cptr, cbuf + cbufptr, j);

	cbufptr += j;
	*num_read = num;
	return(0);
    }
  }

  return(0);	/* NOTREACHED */
}

Int32
bu_input(UChar * buf, Int32 num, AarParams * params)
{
  Int32		i, r;

  i = receive(buf, num, &r);

  if(i && i != ENDOFFILEREACHED){
    fprintf(params->errfp,
	"%sError: unexpected fault receiving from server: %s. Trying to continue ...\n",
		(i > 0 ? "Server " : ""),
		(i > 0 ? fault_string(i) : (UChar *) strerror(-i)));
  }

  return(r);
}

Int32
getcartandfile(AarParams * params)
{
  static UChar	first_call = 1;

  UChar		buf[10];
  Int32		i;

  if(filenum_valid)
    return(0);

  if(QUEUEDOP){
    i = append_to_queue(QUERYPOSITION, NULL, 7, NULL, 0);

    if(first_call){
	i = post_process_rest_of_queue();

	params->vars.firstcart = cart;
	params->vars.firstfile = filenum;

	first_call = 0;
    }

    filenum_valid = 1;

    return(i);
  }

  if( (i = send_cmd(QUERYPOSITION)) )
    return(i);

  if( (i = read_split(commfd, buf, 7)) )
    return(i);

  if( (i = result()) )
    return(i);

  xref_to_UnsN(&cart, buf, 24);
  xref_to_Uns32(&filenum, buf + 3);

  filenum_valid = 1;

  if(first_call){
    params->vars.firstcart = cart;
    params->vars.firstfile = filenum;

    first_call = 0;
  }

  return(0);
}

Int32
getwrcartandfile()
{
  static UChar	first_call = 1;

  UChar		buf[10];
  Int32		i;

  if(QUEUEDOP){
    i = append_to_queue(QUERYWRPOSITION, NULL, 7, NULL, 0);

    if(first_call){
	i = post_process_rest_of_queue();
	first_call = 0;
    }

    return(i);
  }

  if( (i = send_cmd(QUERYWRPOSITION)) )
    return(i);

  if( (i = read_split(commfd, buf, 7)) )
    return(i);

  if( (i = result()) )
    return(i);

  xref_to_UnsN(&wrcart, buf, 24);
  xref_to_Uns32(&wrfilenum, buf + 3);

  return(0);
}

Int32
getnumcarts(Int32 * ncarts)
{
  UChar		buf[10];
  Int32		i;

  if(QUEUEDOP){
    i = append_to_queue(QUERYNUMCARTS, NULL, 3, NULL, 0);

    return(i);
  }

  if( (i = send_cmd(QUERYNUMCARTS)) )
    return(i);

  if( (i = read_split(commfd, buf, 3)) )
    return(i);

  if( (i = result()) )
    return(i);

  xref_to_UnsN(ncarts, buf, 24);

  return(0);
}

Int32
getserverstate(UChar * serverstate)
{
  Int32		i;
  UChar		serverstatebuf[512];

  if(QUEUEDOP){
    i = append_to_queue(QUERYRDYFORSERV, NULL, 512, NULL, 0);

    return(i);
  }

  if( (i = send_cmd(QUERYRDYFORSERV)) )
    return(i);

  if( (i = read_split(commfd, serverstatebuf, 512)) )
    return(i);

  *serverstate = serverstatebuf[0];

  if( (i = result()) )
    return(i);

  return(0);
}

Int32
setcart(Int32 cartnum)
{
  UChar		buf[8];
  Int32		i;

  filenum_valid = 0;

  buf[0] = SETCARTRIDGE;
  UnsN_to_xref(buf + 1, cartnum, 24);

  reset_data_buffer;

  if(QUEUEDOP){
    i = append_to_queue(buf[0], NULL, 0, buf + 1, 3);

    return(i);
  }

  if( (i = write_split(commfd, buf, 4)) )
    return(i);

  return(result());
}

Int32
setcartset(Int32 cartset)
{
  UChar		buf[8];
  Int32		i;

  filenum_valid = 0;

  buf[0] = SETCARTSET;
  UnsN_to_xref(buf + 1, cartset, 24);

  if(QUEUEDOP){
    i = append_to_queue(buf[0], NULL, 0, buf + 1, 3);

    return(i);
  }

  if( (i = write_split(commfd, buf, 4)) )
    return(i);

  return(result());
}

Int32
setfilenum(Int32 filenum)
{
  UChar		buf[8];
  Int32		i;

  filenum_valid = 0;

  buf[0] = SETFILE;
  Uns32_to_xref(buf + 1, filenum & 0x7fffffff);

  reset_data_buffer;

  if(QUEUEDOP){
    i = append_to_queue(SETFILE, NULL, 0, buf + 1, 4);

    return(i);
  }

  if( (i = write_split(commfd, buf, 5)) )
    return(i);

  return(result());
}

Int32
skipfiles(Int32 numfiles)
{
  UChar		buf[8];
  Int32		i;

  filenum_valid = 0;

  buf[0] = SKIPFILES;
  Uns32_to_xref(buf + 1, numfiles & 0x7fffffff);
  
  reset_data_buffer;

  if(QUEUEDOP){
    i = append_to_queue(SKIPFILES, NULL, 0, buf + 1, 4);

    return(i);
  }

  if( (i = write_split(commfd, buf, 5)) )
    return(i);

  return(result());
}

Int32
simple_command(UChar cmd)
{
  Int32		i;

  if(QUEUEDOP){
    i = append_to_queue(cmd, NULL, 0, NULL, 0);

    return(i);
  }

  if( (i = write_split(commfd, &cmd, 1)) )
    return(i);

  return(result());
}

Int32
open_write()
{
  Int32		i;

  filenum_valid = 0;

  if(QUEUEDOP)
    return(append_to_queue(OPENFORWRITE, NULL, 0, NULL, 0));

  if( (i = send_cmd(OPENFORWRITE)) )
    return(i);

  return(result());
}

Int32
open_read()
{
  Int32		i;

  filenum_valid = 0;

  if(QUEUEDOP)
    return(append_to_queue(OPENFORREAD, NULL, 0, NULL, 0));

  if( (i = send_cmd(OPENFORREAD)) )
    return(i);

  return(result());
}

Int32
closetape(Int8 rewind)
{
  Int32		i, j;
  UChar		cmd;

  cmd = (rewind ? CLOSETAPE : CLOSETAPEN);

  filenum_valid = 0;

  if(QUEUEDOP){
    i = append_to_queue(cmd, NULL, 0, NULL, 0);

    j = post_process_rest_of_queue();

    return(i ? i : j);
  }

  if( (i = send_cmd(cmd)) )
    return(i);

  return(result());
}

UChar *
fault_string(Int32 code)
{
  Int32		i, sz;

  if(!code)
    return("");

  sz = sizeof(fault_messages) / sizeof(fault_messages[0]);

  for(i = 0; i < sz; i++){
    if(code == (Int32) fault_messages[i].code)
	return(fault_messages[i].msg);
  }

  return("unknown fault");
}


Int32
close_connection()
{
  Int32		i, j;

  filenum_valid = 0;

  if(QUEUEDOP){
    i = append_to_queue(GOODBYE, NULL, 0, NULL, 0);

    j = post_process_rest_of_queue();

    return(i ? i : j);
  }

  if( (i = send_cmd(GOODBYE)) )
    return(i);

  return(result());
}

Int32
request_client_backup(UChar * cmdname)
{
  Int32		i;
  Uns32		n;
  UChar		*buf, lenstr[5], exitst;

  n = strlen(cmdname);

  buf = strapp("...", cmdname);
  buf[0] = CLIENTBACKUP;
  buf[1] = n % 251;
  buf[2] = 1;		/* suppress output if queued */

  if(QUEUEDOP){
    i = append_to_queue(CLIENTBACKUP, cbuf, 5, buf + 1, n + 2);

    free(buf);

    return(i);
  }

  buf[2] = 0;
  i = write_split(commfd, buf, n + 3);
  free(buf);

  if(i)
    return(i);

  if( (i = read_split(commfd, lenstr, 5)) )
    return(i);

  exitst = lenstr[0];
  xref_to_Uns32(&n, lenstr + 1);

  buf = NEWP(UChar, n);
  if(!buf){
    errmsg("Error: No memory, where absolutely needed");
  }

  if( (i = read_split(commfd, buf, n)) )
    return(i);

  fwrite(buf, 1, n, stdout);
  free(buf);

  if(n < 1 && exitst){
    fprintf(stderr,
	">>> Remote process exited with status %d, but produced no output.\n",
	exitst);
  }

  return(result() | (exitst << 16));
}

UChar **
get_file_list(FILE * fp)
{
  UChar		buf[MAXPATHLEN + 10];
  Int32		i;
  UChar		**filelist = NULL;
  Int32		num_files = 0;

  forever{
    i = fscanwordq_forced(fp, buf);

    if(i == EOF){
	return(filelist);
    }

    if(filelist)
	filelist = RENEWP(filelist, UChar *, num_files + 2);
    else
	filelist = NEWP(UChar *, 2);

    if(!filelist){
	errmsg("Error: memory exhausted by the list of files");
	exit(-2);
    }

    filelist[num_files] = strdup(buf);
    if(!filelist[num_files]){
	for(i = 0; i < num_files; free(filelist[i++]));
	free(filelist);
	return(NULL);
    }

    num_files++;

    filelist[num_files] = NULL;
  }
}

void
pre_extended_verbose(UChar * str, AarParams * params)
{
  E__(getcartandfile(params));

  params->vars.actfile = filenum;
  params->vars.actcart = cart;
}

void
extended_verbose(UChar * str, AarParams * params)
{
  FILE		*fp;

  fp = ((params->mode == MODE_CONTENTS || params->mode == MODE_INDEX) ?
			params->outfp : params->errfp);

  if(verbose & VERBOSE_LOCATION)
    fprintf(fp, "%s%s%d%s", servername, PORTSEP, (int) portnum, LOCSEP);

  if((verbose & VERBOSE_CART_FILE) && ! (verbose & VERBOSE_UID))
    fprintf(fp, "%d.%d: ", (int) params->vars.actcart,
			(int) params->vars.actfile);

  if((verbose & VERBOSE_CART_FILE) && (verbose & VERBOSE_UID))
    fprintf(fp, "%d.%d" UIDSEP "%d: ", (int) params->vars.actcart,
			(int) params->vars.actfile,
			(int) params->vars.uid);

  if(! (verbose & VERBOSE_CART_FILE) && (verbose & VERBOSE_UID))
    fprintf(fp, UIDSEP "%d: ", (int) params->vars.uid);

  fprintf(fp, "%s", str);
}

void
write_reportfile(UChar * filename, AarParams * params, Int32 rc)
{
  FILE		*fp;
  UChar		numbuf[50];

  fp = fopen(filename, "w");
  if(!fp){
    errmsg("Error: Cannot write report file \"%s\"\n", filename);
    return;
  }

  fprintf(fp, "Backup statistics:\n");
  fprintf(fp, "==================\n\n");
  fprintf(fp, "Exit status:                 %ld\n\n", rc);
  fprintf(fp, "Start-Time:                  %s\n", params->vars.startdate);
  fprintf(fp, "End-Time:                    %s\n\n", params->vars.enddate);
  fprintf(fp, "First Cartridge used:        %ld\n", params->vars.firstcart);
  fprintf(fp, "First Tapefile accessed:     %ld\n", params->vars.firstfile);
  fprintf(fp, "Last Cartridge used:         %ld\n", params->vars.lastcart);
  fprintf(fp, "Last Tapefile accessed:      %ld\n\n", params->vars.lastfile);
  fprintf(fp, "Stored filesystem entries:   %ld\n", params->vars.num_fsentries);
  fprintf(fp, "Bytes written to media:      %s\n",
		Real64_to_intstr(params->vars.bytes_saved, numbuf));
  fprintf(fp, "Sum of filesizes:            %s\n",
		Real64_to_intstr(params->vars.sum_filesizes, numbuf));
  fprintf(fp, "Sum of compressed filesizes: %s\n",
		Real64_to_intstr(params->vars.sum_compr_filesizes, numbuf));
  if(params->vars.sum_compr_filesizes > 0)
    fprintf(fp, "Total compression factor:    %.2f\n",
		(float) params->vars.sum_filesizes /
		(float) params->vars.sum_compr_filesizes);
  fprintf(fp, "\n");

  fclose(fp);
}

void
give_help()
{
{
char *l[170];
int i;
l[0]="Description";
l[1]="===========";
l[2]="";
l[3]="This program is used to maintain archives on a backup server";
l[4]="host or in a file. Archives can be created, extracted or their";
l[5]="contents be listed. Almost one of the following flags has to";
l[6]="be supplied:";
l[7]="";
l[8]=" -c  to create an archive";
l[9]="";
l[10]=" -x  to extract from an archive";
l[11]="";
l[12]=" -t  to list the contents of an archive";
l[13]="";
l[14]=" -d  to verify (compare) the contents of an archive";
l[15]="";
l[16]=" -C  to set a certain cartridge on the backup server";
l[17]="       (makes only sense extracting or listing with -x or";
l[18]="        -t, the writing position can't be changed by clients)";
l[19]="";
l[20]=" -F  to set a certain file on the backup server's tape";
l[21]="       (the same applies as for -C)";
l[22]="";
l[23]=" -q  to printout the actual cartridge and tape file number";
l[24]="       on the backup server";
l[25]="";
l[26]=" -Q  to printout the cartridge and tape file number for the";
l[27]="       the next write access on the backup server";
l[28]="";
l[29]=" -X  followed by the full path name of a program to be started on";
l[30]="       the client. This can be used to trigger a backup remotely.";
l[31]="       If the program needs arguments, the command together with";
l[32]="       the arguments has to be enclosed by quotes";
l[33]="";
l[34]=" -I  to printout an index of the backups written to the actual";
l[35]="       cartridge";
l[36]="";
l[37]=" -w  to check the status of the streamer on the server side, e.g.";
l[38]="       whether it is ready and waiting for requests to service";
l[39]="";
l[40]="-c, -x, -t, -d, -X and -I are mutual exclusive. The other options";
l[41]="can be supplied as needed. To set the cartridge and/or the tape";
l[42]="file on the backup server is only making sense when not creating";
l[43]="an archive. The serial order of writing to tape is handled by";
l[44]="the server machine independently of the client.";
l[45]="";
l[46]="";
l[47]="Filenames";
l[48]="";
l[49]="The names of the files and directories, that have to be put";
l[50]="into or extracted from an archive are by default read from the";
l[51]="standard input. If you supply filenames in the command line or";
l[52]="enter the -a flag when extracting, standard input is not read.";
l[53]="The same is valid, if filenames are read from a file with the";
l[54]="-T option. When reading the names from a file or from standard";
l[55]="input, filenames have to be separated by whitespace. If a name";
l[56]="is containing whitespace, it has to be enclosed in double";
l[57]="quotes (\"). If a name contains double quotes or backslashes,";
l[58]="each has to be preceded by a backslash (so backslashes become";
l[59]="double backslashes).";
l[60]="";
l[61]="";
l[62]="More options in alphabetical order:";
l[63]="";
l[64]=" -A <time>    process files (save or extract) modified after";
l[65]="                the given time in seconds since 1.1.1970 00:00";
l[66]="";
l[67]=" -a           in combination with -x: extract all files and";
l[68]="                directories in the archive";
l[69]="";
l[70]=" -B <time>    process files (save or extract) modified before";
l[71]="                the given time in seconds since 1.1.1970 00:00";
l[72]="";
l[73]=" -b           don't enter buffering mode";
l[74]="";
l[75]=" -e <errlog>  Use the file <errlog> to write error messages to";
l[76]="                instead of the standard error output";
l[77]="";
l[78]=" -f <file>    write to or read from a file instead of querying";
l[79]="                the backup server";
l[80]="";
l[81]=" -g           while extracting/reading: ignore leading garbage,";
l[82]="                suppress error messages at the beginning. This";
l[83]="                is useful when extracting from tape files, that";
l[84]="                are not the first ones of a whole archive.";
l[85]="";
l[86]=" -H <header>  put the supplied informational header to the begin";
l[87]="                of the backup";
l[88]="";
l[89]=" -h <host>    use the backup server with the name <host>";
l[90]="                default host is the machine with the name";
l[91]="                backuphost";
l[92]="";
l[93]=" -i           while extracting: ignore the stored ownership and";
l[94]="                do not restore it";
l[95]="";
l[96]=" -k <file>    use the contents of the given file as encryption";
l[97]="                key for authenticating to the server";
l[98]="";
l[99]=" -l           for each packed or unpacked filename, if sending";
l[100]="                to or receiving from a backup server in verbose";
l[101]="                   mode in combination with -n:";
l[102]="                printout server name and port number at the";
l[103]="                beginning of the line, e.g.: orion%2988!";
l[104]="";
l[105]=" -N <file>    while archiving: ignore files with a modification";
l[106]="                time before the one of the given file, only save";
l[107]="                newer files or such with the same age in seconds";
l[108]="";
l[109]=" -n           for each packed or unpacked filename, if sending";
l[110]="                to or receiving from a backup server in verbose";
l[111]="                   mode:";
l[112]="                printout cartridge and tape file number at the";
l[113]="                beginning of the line, e. g.: 7.15: <filename>";
l[114]="";
l[115]=" -O           for each packed file creating a backup in verbose";
l[116]="                mode: printout the user-ID of the file owner at";
l[117]="                the beginning of the line prefixed with a bar |";
l[118]="                eventually behind cartridge and file number";
l[119]="";
l[120]=" -o <uid>     archive or extract only files owned by the user";
l[121]="                with the given user-ID (an integer)";
l[122]="";
l[123]=" -p <portno>  use a different port number for communicating with";
l[124]="                the backup server. Default is TCP-Port 2988";
l[125]="";
l[126]=" -R           pack or extract directories recursively with all";
l[127]="                of their contents";
l[128]="";
l[129]=" -r           use filenames relative to the current directory,";
l[130]="                whether they start with a slash or not";
l[131]="";
l[132]=" -S <cartset> The cartridge set to use, where <cartset> is the";
l[133]="                number of a valid cartridge set on the server";
l[134]="                side. Default is 1. This option makes sense only";
l[135]="                when creating backups with -c";
l[136]="";
l[137]=" -s <filepat> do not attempt compression on files matching the";
l[138]="                given filename pattern. This parameter may";
l[139]="                appear several times";
l[140]="";
l[141]=" -T <file>    read the filenames to process from the <file>.";
l[142]="                The filenames must be separated by whitespace.";
l[143]="                If whitespace is part of a filename, it has to";
l[144]="                be enclosed by double quotes. Double quotes or";
l[145]="                backslashes within the filename have to be";
l[146]="                preceded by a backslash";
l[147]="";
l[148]=" -u           while extracting: remove existing files with the";
l[149]="                same name as found in the archive. Otherwise";
l[150]="                no existing files are overwritten";
l[151]="";
l[152]=" -V <file>    write a report containing statistics at the end of";
l[153]="                a backup to the <file>";
l[154]="";
l[155]=" -v           verbose mode: print the filenames while creating";
l[156]="                or extracting, be a little more verbose while";
l[157]="                listing contents";
l[158]="";
l[159]=" -z <z> <uz>  use <z> as the command, that is used to compress";
l[160]="                files, <uz> for the corresponding uncompress.";
l[161]="                The command has to read from stdin and to write";
l[162]="                to stdout. If arguments have to be supplied to";
l[163]="                <z> and/or <uz>, don't forget to use quotes";
l[164]="";
l[165]=" -Z           while printing out the contents: check those files";
l[166]="                in the archive that are compressed for integrity";
l[167]="";
l[168]="";
l[169]=" -?           to printout this text";
for(i=0;i<170;i++)
fprintf(stdout,"%s\n",l[i]);
}

  exit(0);
}

void
usage(UChar * prnam)
{
  prnam = FN_BASENAME(prnam);

  errmsg("usage: %s -cxtdI [ -RraunlOvgiqQZb ] \\\n", prnam);
  errmsg("               [ -h <backup-server> ] \\\n");
  errmsg("               [ -z <zipcmd> <unzipcmd> ] \\\n");
  errmsg("               [ -T <to-extract-filename> ] \\\n");
  errmsg("               [ -C <cartridge-number> ] \\\n");
  errmsg("               [ -F <filenumber-on-tape> ] \\\n");
  errmsg("               [ -f <archive-filename> ] \\\n");
  errmsg("               [ -e <errorlog-filename> ] \\\n");
  errmsg("               [ -p <server-port-number> ] \\\n");
  errmsg("               [ -N <newer-than-filename> ] \\\n");
  errmsg("               [ -o <user-ID> ] \\\n");
  errmsg("               [ -k <encryption-key-file> ] \\\n");
  errmsg("               [ -s <dont-compress-filepattern> [ -s ... ] ] \\\n");
  errmsg("               [ -V <statistics-report-file> ] \\\n");
  errmsg("               [ -A <after-time-seconds> ] \\\n");
  errmsg("               [ -B <before-time-seconds> ] \\\n");
  errmsg("               [ <files> <directories> ... ]\n");
  errmsg("\n       %s -X <program> \\\n", prnam);
  errmsg("               [ -h <backup-client> ]\n");
  errmsg("\n       %s -\\?  (to get help)\n", prnam);

  exit(1);
}


main(int argc, char ** argv)
{
  Int32		i, j;
  Int32		gexitst = 0;
  struct stat	statb;
  FILE		*fp;
  UChar		c, buf[2000];
  UChar		givehelp = 0;
  UChar		querypos = 0;
  UChar		querywrpos = 0;
  UChar		noqueued = 0;
  Int32		p = -1;
  UChar		**filelist = NULL;
  UChar		**files = NULL;
  UChar		*found_files = NULL;
  Int32		s_cart = -1;
  Int32		s_file = -1;
  Int32		s_cset = 1;
  Uns32		code;
  Int32		num_unused = 0;
  UChar		*newer_than_file = NULL;
  UChar		**unused_args = NULL;
  UChar		*cryptfile = NULL;
  UChar		*cptr = NULL;
  UChar		*reportfile = NULL;
  UChar		*older_than_sec = NULL;
  UChar		*newer_than_sec = NULL;

  gargv = (UChar **) argv;

  memset(&params.vars, 0, sizeof(params.vars));

  strcpy(params.vars.startdate, actimestr());

  if(!params.infp)
    params.infp = stdin;
  if(!params.outfp)
    params.outfp = stdout;
  if(!params.errfp)
    params.errfp = stderr;
  sigemptyset(&params.blocked_signals);
  sigprocmask(SIG_SETMASK, &params.blocked_signals, NULL);

  if(sizeof(UChar) > 4){
    fprintf(stderr, "Error: long int is wider than 4 Bytes. Inform author about machine type.\n");
    exit(1);
  }

  i = goptions(-argc, (UChar **) argv,
	"b:c;b:x;b:t;b:d;b:I;b:w;b:R;b:r;b:v;b:n;b:O;b:l;b:u;b:a;s:h;s2:z;"
	"s:T;i:p;s:e;s:f;i:C;i:F;i:S;b:g;b:i;b:?;b:q;b:Q;*;b:Z;s:X;s:N;i:o;"
	"s:k;b:b;s:s;s:V;s:B;s:A;b:E;s:H",
			&bu_create, &bu_extract, &bu_contents, &bu_verify,
			&bu_index, &bu_ready,
			&params.recursive, &params.relative,
			&verbose, &c_f_verbose, &o_verbose, &l_verbose,
			&params.unlink, &allfiles, &servername,
			&params.zipcmd, &params.unzipcmd,
			&toextractfilename, &p, &errorlogfile,
			&savefile, &s_cart, &s_file, &s_cset,
			&params.skip_garbage, &params.ignoreown,
			&givehelp, &querypos, &querywrpos,
			&num_unused, &unused_args, &params.check,
			&clientprog, &newer_than_file, &params.uid,
			&cryptfile, &noqueued, &cptr, &reportfile,
			&older_than_sec, &newer_than_sec,
			&long_errmsgs, &infoheader);

  if(i)
    usage(argv[0]);

  if(cptr){
    params.dont_compress = NEWP(UChar *, 1);
    params.dont_compress[0] = NULL;

    for(i = 1, j = 0; i < argc; i++){
      if(!strcmp(argv[i], "-s")){
	params.dont_compress = RENEWP(params.dont_compress, UChar *, j + 2);
	params.dont_compress[j] = strdup(argv[i + 1]);
	j++;
	params.dont_compress[j] = NULL;
	i++;
      }
    }
  }

  if(givehelp)
    give_help();

  if(noqueued || !bu_create)
    comm_mode = SERIAL_OPERATION;

  if(clientprog)
    bu_request = 1;

  if(bu_create + bu_extract + bu_contents + bu_request + bu_verify
		+ bu_index > 1){
    errmsg("You may supply only one of the flags: cxtX");
    usage(argv[0]);
  }
  if(bu_create + bu_extract + bu_contents + bu_request + bu_verify
			+ bu_index + bu_ready < 1
		&& s_cart < 0 && s_file < 0 && ! querypos && ! querywrpos){
    errmsg("You have to supply one of the flags: cxtCFqQ");
    usage(argv[0]);
  }
  if(toextractfilename && (! bu_extract && ! bu_create)){
    errmsg("The flag -T may only be given in combination wit -x or -c");
    usage(argv[0]);
  }
  if(toextractfilename && allfiles){
    errmsg("The flags -T and -a don't make sense together. Ignoring -a");
    allfiles = 0;
  }
  if(unused_args && (toextractfilename || allfiles)){
    errmsg("If you name files and/or directories in the commandline,\n -a or -T don't make any sense. Using filenames from the command line");
    allfiles = 0;
    toextractfilename = NULL;
  }
  if(allfiles && ! bu_extract){
    errmsg("Warning: the flag -a makes only sense in combination with -x");
  }
  if(params.unlink && ! bu_extract){
    errmsg("Warning: the flag -u makes only sense in combination with -x");
  }
  if(params.recursive && !(bu_create || bu_extract)){
    errmsg("Warning: the flag -R makes only sense in combination with -x or -c");
  }
  if(params.relative && ! bu_extract){
    errmsg("Warning: the flag -r makes only sense in combination with -x");
  }
  if(params.ignoreown && ! bu_extract){
    errmsg("Warning: the flag -r makes only sense in combination with -x");
  }
  if(newer_than_file && ! bu_create){
    errmsg("Warning: the flag -N only makes sense in combination with -c");
  }
  if(newer_than_file && newer_than_sec){
    errmsg("Warning: -N and -A may not be specified together, -N ignored.");
    ZFREE(newer_than_file);
  }
  if(params.zipcmd && params.unzipcmd && !bu_create){
    errmsg("Warning: to specify (de-)compression is only allowed with -c");
  }
  if(params.check && ! bu_contents){
    errmsg("Warning: the flag -Z makes only sense in combination with -t. Ignoring -Z");
    params.check = 0;
  }
  if(bu_index && savefile){
    errmsg("Error: You cannot get a tape index (-I) of a file (-f)");
    usage(argv[0]);
  }
  if(servername && savefile){
    errmsg("Error: You may not specify -h and -f together");
    usage(argv[0]);
  }
  if((s_cart != -1 || s_file != -1 || querypos || querywrpos) && savefile){
    errmsg("Error: with a file (-f) you cannot set/get cartridge and/or tape position");
    usage(argv[0]);
  }
  if(savefile && c_f_verbose){
    errmsg("Warning: -n makes no sense in combination with -f. -n is ignored");
    c_f_verbose = 0;
  }
  if(savefile && l_verbose){
    errmsg("Warning: -l makes no sense in combination with -f. -l is ignored");
    l_verbose = 0;
  }
  if(!bu_create && o_verbose){
    errmsg("Warning: -O is only supported in combination with -c, -O ignored");
    o_verbose = 0;
  }
  if(savefile && cryptfile){
    errmsg("Warning: -k makes no sense in combination with -f");
  }
  if(s_file >	0x7fffffff){
    errmsg("Error: requested file number is too high - must be <= %ld",
		0x7fffffffL);
    usage(argv[0]);
  }
  if(bu_request){
    if(strlen(clientprog) > 250){
      errmsg("Error: program line supplied with -X is too long (max. 250 chars)");
      exit(1);
    }
  }

  if(set_cryptkey(cryptfile) && cryptfile){
	errmsg("Warning: Cannot read enough characters from encryption key file \"%s\"",
		cryptfile);
	errmsg("         Ignoring file, using compiled-in key");
  }

  if(!servername)
    servername = DEFAULT_SERVER;

  signal(SIGTERM, signal_handler);
  signal(SIGINT, signal_handler);
  signal(SIGHUP, signal_handler);
  signal(SIGSEGV, signal_handler);
  signal(SIGBUS, signal_handler);
  signal(SIGPIPE, signal_handler);

  params.verbose = verbose;

  if(verbose || bu_contents){
    verbose = VERBOSE_NORMAL;

    if(c_f_verbose)
	verbose |= VERBOSE_CART_FILE;
    if(l_verbose)
	verbose |= VERBOSE_LOCATION;
    if(o_verbose)
	verbose |= VERBOSE_UID;
  }

  if(c_f_verbose || o_verbose){
    params.verbosefunc = extended_verbose;
    params.pre_verbosefunc = pre_extended_verbose;
  }

  if(newer_than_file){
    if(stat(newer_than_file, &statb)){
      errmsg("Warning: Cannot stat file \"%s\". Option ignored",
				newer_than_file);
    }
    else{
      params.time_newer = statb.st_mtime;
    }
  }

  if(older_than_sec){
    params.time_older = strint2time(older_than_sec);
    if(params.time_older == (time_t) -1){
	errmsg("Warning: Could not read upper time limit from "
			"\"%s\". Option ignored", older_than_sec);
	params.time_older = 0;
    }
  }
  if(newer_than_sec){
    params.time_newer = strint2time(newer_than_sec);
    if(params.time_newer == (time_t) -1){
	errmsg("Warning: Could not read lower time limit from "
			"\"%s\". Option ignored", newer_than_sec);
	params.time_newer = 0;
    }
  }

  if(unused_args){
    filelist = unused_args;
  }
  else if(toextractfilename || (bu_extract && !allfiles)){
    if(toextractfilename){
	fp = fopen(toextractfilename, "r");
	if(!fp){
	  errmsg("Error: cannot open file \"%s\"", toextractfilename);
	  exit(1);
	}
    }
    else
	fp = stdin;

    filelist = get_file_list(fp);

    if(toextractfilename)
	fclose(fp);

    if(! filelist){
	errmsg("Exiting, because %s does not contain any filename",
			toextractfilename);
	exit(0);
    }
  }
  if(filelist){
    for(files = filelist, i = 0; *files; i++, files++);

    if(i > 0){
	found_files = NEWP(UChar, i);
	memset(found_files, 0, i * sizeof(UChar));

	if(!found_files){
	  errmsg("Error: cannot allocate memory");
	  exit(9);
	}
    }
  }

  if(errorlogfile){
    fp = fopen(errorlogfile, "w");
    if(!fp){
	errmsg("Error: cannot open error log file \"%s\"",
				errorlogfile);
	errmsg("	 Writing errors to stdout");
    }
    else
	params.errfp = fp;
  }

  if(!savefile){
    if(p > 0)
      portnum = p;
    commfd = connect_afbu_server(servername, DEFAULT_SERVICE, portnum);

    if(commfd < 0){
      errmsg("Error: Cannot open communication socket");
      exit(-1);
    }

    memset(buf, 0, 1000 * sizeof(UChar));

    for(i = 0; i < 1000; i++){
      j = read_split(commfd, buf + i, 1);

      if(j){
	errmsg("Error: Cannot get the greeting message from server");
	exit(-1);
      }

      if(strstr(buf, GREETING_MESSAGE))
	break;
    }
    if(i >= 1000){
      errmsg("Error: server didn't send the greeting message");
      exit(-1);
    }

#ifdef	USE_DES_ENCRYPTION

    read_split(commfd, buf, 16);

    encrpt(buf);

    write_split(commfd, buf, 16);

#else	/* defined(USE_DES_ENCRYPTION) */

    read_split(commfd, buf, 4);

    xref_to_Uns32(&code, buf);

    code = encrpt(code);

    Uns32_to_xref(buf, code);

    write_split(commfd, buf, 4);

#endif	/* ifelse defined(USE_DES_ENCRYPTION) */

    i = result();
    if(i){
	errmsg("Error: %s. Server closed connection", fault_string(i));
	exit(1);
    }

    if(QUEUEDOP){
      Int32	needed_entry_mem;
#ifndef	_WIN32
      struct rlimit	rlim;
#endif

      needed_entry_mem = MAX_OUTARGLEN + MAX_INRESULTLEN + sizeof(QueueEntry);

#ifndef	_WIN32
      i = getrlimit(RLIMIT_STACK, &rlim);
      if(i){
	errmsg("Error: cannot get maximum available memory");
      }
      queuelen = rlim.rlim_cur / needed_entry_mem / 4;
#else
      queuelen = 1000000 / needed_entry_mem / 4;
#endif

      if(queuelen < 1){
	comm_mode = SERIAL_OPERATION;
      }
      else{
	if(needed_entry_mem * queuelen > MAX_QUEUE_MEMORY){
	  queuelen = MAX_QUEUE_MEMORY / needed_entry_mem;
	}

	if(MAX_OUTARGLEN * queuelen < 100000){
	  comm_mode = SERIAL_OPERATION;
	}
	else{
	  outbufmem = NEWP(UChar, MAX_OUTARGLEN * queuelen);
	  inbufmem = NEWP(UChar, MAX_INRESULTLEN * queuelen);
	  queue_entries = NEWP(QueueEntry, queuelen);

	  if(!outbufmem || !inbufmem || !queue_entries){
	    errmsg("Error: Strange. Can't get memory. Disabling queued operation");
	    comm_mode = SERIAL_OPERATION;
	    ZFREE(outbufmem);
	    ZFREE(inbufmem);
	    ZFREE(queue_entries);
	  }
	  else{
	    struct sigaction	siga;

	    queue_clock_usec = 10000;

		/* assume initial max transfer rate of 1 MB / sec */
	    transfer_per_cycle = 1000000 / 1000000 * queue_clock_usec;
	    transfer_per_cycle_cur = transfer_per_cycle;

	    transfer_per_cycle_prev = transfer_per_cycle;
	    bytes_transferred_prev = transfer_per_cycle;

	    bytes_transferred = 0;
	    cycle_started = 0;
	    num_cycles_processing = 0;
	    queueent_done_idx = queue_insert_idx = queueent_requested_idx
				= queueent_processed_idx = 0;
	    transferred_uninterrupted = 0;

	    SETZERO(siga);
	    siga.sa_handler = queue_processor;
#ifdef	SA_NOMASK
	    siga.sa_flags |= SA_NOMASK;
#else
#ifdef	SA_NODEFER
	    siga.sa_flags |= SA_NODEFER;
#endif
#endif
#ifdef	SA_RESTART
	    siga.sa_flags |= SA_RESTART;
#endif
	    sigaction(SIGALRM, &siga, NULL);

	    start_queue_timer();
	  }
	}
      }
    }
  }
  else{
    comm_mode = SERIAL_OPERATION;

    if(bu_create){
      if(!strcmp(savefile, "-")){
	commfd = 1;
      }
      else{
	if( (fp = fopen(savefile, "w")) ){
	  fclose(fp);

	  commfd = open(savefile, O_WRONLY | O_CREAT | O_BINARY, 0644);
	}
	else
	  commfd = -1;
      }
    }
    else{
      if(!strcmp(savefile, "-")){
	commfd = 0;
      }
      else{
	commfd = open(savefile, O_RDONLY | O_BINARY);
      }
    }

    if(commfd < 0){
	errmsg("Error: cannot open file \"%s\"", savefile);
	exit(9);
    }
  }

  SETZERO(null_timeval);

  if(QUEUEDOP){
    sigaddset(&params.blocked_signals, SIGALRM);
    sigaddset(&params.blocked_signals, SIGPIPE);
  }

  if(s_cset != 1){
    if(E__(setcartset(s_cset)))
	GETOUT;
  }
  if(s_cart != -1){
    if(E__(setcart(s_cart)))
	GETOUT;
  }
  if(s_file != -1){
    if(E__(setfilenum(s_file)))
	GETOUT;
  }

  if(bu_request){
    E__( (i = request_client_backup(clientprog)) & 0xffff);

    gexitst = (i >> 16) & 0xff;

    if(QUEUEDOP)
	E__(post_process_rest_of_queue());
  }

  if(bu_ready){
    if(OK__(i = getserverstate(&c))){
	j = c & STREAMER_FLAGS_MASK;
	c &= STREAMER_STATE_MASK;
	fprintf(stdout, "Streamer state: %s",
			c == STREAMER_READY ? "READY" : (
			c == STREAMER_BUSY ? "BUSY" : (
			c == STREAMER_UNLOADED ? "UNLOADED" : (
			c == STREAMER_DEVINUSE ? "DEVINUSE" : (
			c == STREAMER_UNAVAIL ? "UNAVAILABLE" : "UNKNOWN")))));
	if(j & STREAMER_CHANGEABLE)
	  fprintf(stdout, "+CHANGEABLE");
	fprintf(stdout, "\n");
    }
    if(i)
	errmsg("Error: Cannot determine streamer state on server");
  }

  if(querypos){
    if(OK__(i = getcartandfile(&params))){
	if(QUEUEDOP)
	  E__(i = post_process_rest_of_queue());

	if(!i){
	  fprintf(stdout, "Actual tape access position\n");
	  fprintf(stdout, "Cartridge: %d\nFile:      %d\n",
				(int) cart, (int) filenum);
	}
    }
    if(i)
	errmsg("Error: Cannot determine current tape access position");
  }

  if(querywrpos){
    if(OK__(i = getwrcartandfile())){
	if(QUEUEDOP)
	  E__(i = post_process_rest_of_queue());

	if(!i){
	  fprintf(stdout, "Next tape writing position\n");
	  fprintf(stdout, "Cartridge: %d\nFile:	   %d\n",
				(int) wrcart, (int) wrfilenum);
	}
    }
    if(i)
	errmsg("Error: Cannot determine next tape writing position");
  }

  if((querypos || querywrpos) && verbose){
    if(OK__(i = getnumcarts(&j))){
	if(QUEUEDOP)
	  E__(i = post_process_rest_of_queue());

	if(!i){
	  fprintf(stdout, "Number of cartridges: %d\n", (int) j);
	}
    }
    if(i)
	errmsg("Error: Cannot determine number of cartridges");
  }

  if(bu_create){
	params.mode = MODE_CREATE;

	params.outputfunc = bu_output;

	if(!savefile){
	  if(E__(open_write()))
	    GETOUT;
	}

	if(infoheader){
	  repl_esc_seq(infoheader, '\\');
	  sprintf(buf, "%d;%d;", INFORMATION, strlen(infoheader));

	  if(E__(bu_output(buf, strlen(buf), &params))
		|| E__(bu_output(infoheader, strlen(infoheader), &params))
		|| E__(bu_output(".", 1, &params)))
	    GETOUT;
	}

	if(filelist){
	  while(*filelist){
	    i = E__(writeout(*filelist, &params, 0));

	    if(i){
		if(i > 0)
		  num_errors++;

		if(num_errors > MAX_NUM_ERRORS){
		  errmsg("Too many errors on server. Exiting");

		  break;
		}
	    }
	    else{
		num_errors = 0;
	    }

	    filelist++;
	  }
	}
	else{
	  while(EOF != fscanwordq_forced(stdin, buf)){
	    i = E__(writeout(buf, &params, 0));

	    if(i){
		if(i > 0)
		  num_errors++;

		if(num_errors > MAX_NUM_ERRORS){
		  errmsg("Too many errors on server. Exiting");

		  break;
		}
	    }
	    else{
		num_errors = 0;
	    }
	  }
	}

	writeout(NULL, &params, ENDOFARCHIVE);

	if(E__(send_pending()))
	  GETOUT;

	if(!savefile){
	  if(E__(getcartandfile(&params)))
	    GETOUT;

	  params.vars.lastcart = cart;
	  params.vars.lastfile = filenum;

	  if(E__(closetape(1)))
	    GETOUT;
	}
  }
  else if(bu_contents){
	params.mode = MODE_CONTENTS;

	params.inputfunc = bu_input;

	if(!savefile){
	  if(E__(open_read()))
	    GETOUT;
	}

	contents(&params);

	if(!savefile){
	  if(E__(closetape(1)))
	    GETOUT;
	}
  }
  else if(bu_extract || bu_verify){
	params.mode = (bu_extract ? MODE_EXTRACT : MODE_VERIFY);

	params.inputfunc = bu_input;

	if(!savefile){
	  if(E__(open_read()))
	    GETOUT;
	}

	if(bu_extract)
	  readin(filelist, &params, found_files);
	else
	  verify(filelist, &params, found_files);

	if(!savefile){
	  if(E__(closetape(1)))
	    GETOUT;
	}

	if(filelist){
	  for(files = filelist, i = 0; *files; files++, i++){
	    if(!found_files[i]){
		errmsg("%s not found in archive", *files);
	    }
	  }
	}
  }
  else while(bu_index){
	Int8	eot = 0;
	Int32	len;
	UChar	htypebuf[20];

	params.mode = MODE_INDEX;

	sprintf(htypebuf, "%d;", INFORMATION);
	j = strlen(htypebuf);

	if(E__(set_server_serial))
	  break;

	if(E__(set_server_erroneot))
	  break;

	params.inputfunc = bu_input;

	while(!eot){
	  if(open_read()){
	    eot = 1;
	    break;
	  }

	  if(params.pre_verbosefunc)
	    params.pre_verbosefunc("", &params);

	  i = params.inputfunc(buf, j, &params);
	  if(i < j){
	    E__(closetape(0));
	    break;
	  }
	  buf[i] = '\0';

	  if(!strcmp(htypebuf, buf)){
	    len = 0;
	    cptr = NULL;

	    forever{
		i = params.inputfunc(&c, 1, &params);
		if(i < 1)
		  break;

		if(isdigit(c))
		  len = len * 10 + (c - '0');
		else if(c == ';'){
		  cptr = NEWP(UChar, len + 1);

		  i = params.inputfunc(cptr, len + 1, &params);
		  if(i < len + 1){
		    break;
		  }
		  if(cptr[len] != '.')
		    break;
		  cptr[len] = '\0';

		  params.verbosefunc(cptr, &params);
		  fflush(params.infp);
		  fflush(params.outfp);

		  break;
		}
		else
		  break;
	    }		/* reading header information */

	    ZFREE(cptr);
	  }		/* if have header */

	  E__(closetape(0));

	  if(skipfiles(1)){
	    eot = 1;
	    break;
	  }
	}

	break;
  }

  if(QUEUEDOP){
     E__(post_process_rest_of_queue());

     queue_clock_usec = 0;
     start_queue_timer();	/* in fact stop it */

     comm_mode = SERIAL_OPERATION;	/* synchronous mode for disconnect */
  }

  if(!savefile)
    close_connection();
  else{
    if(strcmp(savefile, "-"))
      close(commfd);
  }

  if(errorlogfile)
    fclose(params.errfp);

  strcpy(params.vars.enddate, actimestr());

  if(reportfile)
    write_reportfile(reportfile, &params, 0);

  exit(gexitst);


 getout:

  if(!savefile)
    close_connection();
  else{
    if(strcmp(savefile, "-"))
      close(commfd);
  }

  if(QUEUEDOP){
     E__(post_process_rest_of_queue());
  }

  if(errorlogfile)
    fclose(params.errfp);

  strcpy(params.vars.enddate, actimestr());

  if(reportfile)
    write_reportfile(reportfile, &params, 1);

  exit(1);
}

/* queued operation stuff */
void
start_queue_timer()
{
  struct itimerval	queue_clock_itv;

  SETZERO(queue_clock_itv);

  queue_clock_itv.it_value.tv_usec
			= queue_clock_itv.it_interval.tv_usec
			= queue_clock_usec % 1000000;
  queue_clock_itv.it_value.tv_sec
			= queue_clock_itv.it_interval.tv_sec
			= queue_clock_usec / 1000000;

  setitimer(ITIMER_REAL, &queue_clock_itv, NULL);
}

void
reduce_transfer_rate()
{
  Uns32		diff;

  diff = transfer_per_cycle / 100;

  transfer_per_cycle -= diff;
}

void
try_increase_transfer_rate()
{
  Uns32		diff;

  diff = transfer_per_cycle / 100;
  if(diff < 1)
    diff = 1;

  transfer_per_cycle += diff;
}

Int32
process_queue_entry_response()
{
  QueueEntry	*entry;
  UChar		*inbuf;
  Int32		res, num_to_read;
  fd_set	commfdset;

  if(queueent_processed_idx == queueent_requested_idx)
    return(0);

  FD_ZERO(&commfdset);
  FD_SET(commfd, &commfdset);
  if(select(commfd + 1, &commfdset, NULL, NULL, &null_timeval) < 1)
    return(0);

  entry = queue_entries + queueent_processed_idx;

  inbuf = inbufmem + MAX_INRESULTLEN * queueent_processed_idx;
  num_to_read = entry->num_in;

  entry->processed = PROCESSED_OK;

  if(num_to_read > 0){
    res = read_split(commfd, inbuf, num_to_read);
    if(res)
	entry->processed = res;

    bytes_transferred += num_to_read + ESTIM_PROT_OVERHEAD;
  }

  if( (res = result()) )
	entry->processed = res;

  bytes_transferred += ESTIM_PROT_OVERHEAD;

  queueent_processed_idx++;
  if(queueent_processed_idx >= queuelen)
    queueent_processed_idx = 0;

  return(1);
}

Int32
process_queue_entry_request()
{
  QueueEntry	*entry;
  Uns16		instruction;
  UChar		*outbuf;
  UChar		c;
  Int32		res, num_to_write;
  fd_set	commfdset;

  if(queueent_requested_idx == queue_insert_idx)
    return(0);

  FD_ZERO(&commfdset);
  FD_SET(commfd, &commfdset);
  if(select(commfd + 1, NULL, &commfdset, NULL, &null_timeval) < 1)
    return(0);

  entry = queue_entries + queueent_requested_idx;

  outbuf = outbufmem + MAX_OUTARGLEN * queueent_requested_idx;
  num_to_write = entry->num_out;
  instruction = entry->instruction;

  if(num_to_write == 0){
    c = (UChar) instruction;
    res = write_split(commfd, &c, 1);
    if(res){
      entry->processed = res;
    }
    bytes_transferred += ESTIM_PROT_OVERHEAD;
  }

  if(num_to_write > 0){
    outbuf[1] = (UChar) instruction;

    res = write_split(commfd, outbuf + 1, num_to_write + 1);

    if(res)
	entry->processed = res;

    bytes_transferred += num_to_write + 1 + ESTIM_PROT_OVERHEAD;
  }

  bytes_transferred += ESTIM_PROT_OVERHEAD;

  queueent_requested_idx++;
  if(queueent_requested_idx >= queuelen)
    queueent_requested_idx = 0;

  return(1);
}

void
queue_processor(int sig)
{
  Uns32		new_usec_val, i;
  char		increase = 0, cycle_used_up;
#ifndef	SA_NOMASK
#ifndef	SA_NODEFER		/* we have to do this "by hand" */
  sigset_t	sigs, osigs;
#endif
#endif

#if 0	/* now using sigaction */
  signal(SIGALRM, queue_processor);
#endif

#ifndef	SA_NOMASK
#ifndef	SA_NODEFER		/* we have to do this "by hand" */
  sigemptyset(&sigs);
  sigaddset(&sigs, SIGALRM);
  sigprocmask(SIG_UNBLOCK, &sigs, &osigs);
#endif
#endif

  new_usec_val = queue_clock_usec;

  if(queueent_processed_idx == queue_insert_idx)	/* nothing to do */
    GETOUT;

  if(cycle_started){			/* already started */
    if(num_cycles_processing == 0)
	transferred_uninterrupted = bytes_transferred;

    num_cycles_processing++;

#if 0		/* this is not yet sufficiently worked out */
    if(num_cycles_processing < 10){	/* dont waste the rest */
					/* of the time slice */
	transfer_per_cycle_cur = bytes_transferred +
		(bytes_transferred * (10 - num_cycles_processing)
				/ num_cycles_processing);
    }
#endif

    GETOUT;
  }

  cycle_started = 1;

  while(bytes_transferred < transfer_per_cycle_cur &&
			queueent_processed_idx != queue_insert_idx){
    i = process_queue_entry_request();
    i += process_queue_entry_response();

    if(i < 1)
	break;		/* nothing can be done */
  }


  /* OPTIMIZER */
  /* Try to enhance the transfer rate */
  if(bytes_transferred < transfer_per_cycle_cur &&
				num_cycles_processing < 1L)
    cycle_used_up = 0;
  else
    cycle_used_up = 1;

#ifdef OPTTEST
  fprintf(stderr, "cycle used up: %d\n", cycle_used_up);
  fprintf(stderr, "Previously transferred: %lu, now: %lu\n",
	bytes_transferred_prev, bytes_transferred, 0);
  fprintf(stderr, "Previous rate: %lu, now: %lu\n",
	transfer_per_cycle_prev, transfer_per_cycle,0);
  fprintf(stderr, "num_cycles_processing: %d\n", num_cycles_processing);
#endif

  if(cycle_used_up){
    if(num_cycles_processing > 0){
      if(transferred_uninterrupted < transfer_per_cycle_prev)
	increase = 0;
    }
    else if(bytes_transferred_prev <= bytes_transferred){
      if(transfer_per_cycle_prev <= transfer_per_cycle)
	increase = 1;
      else
	increase = 0;
    }
    else{
      if(transfer_per_cycle_prev > transfer_per_cycle)
	increase = 1;
      else
	increase = 0;
    }
  }

  transfer_per_cycle_prev = transfer_per_cycle;

#ifdef OPTTEST
  fprintf(stderr, "increase: %d\n", increase);
  fprintf(stderr, "transf_uninterrpt: %d\n", transferred_uninterrupted);
#endif

  if(cycle_used_up){
    if(increase)
      try_increase_transfer_rate();
    else
      reduce_transfer_rate();
  }

  /* END OPTIMIZER */


  bytes_transferred_prev = bytes_transferred;

  cycle_started = 0;
  num_cycles_processing = 0;
  bytes_transferred = 0;
  transfer_per_cycle_cur = transfer_per_cycle;

 getout:

  if(queue_clock_usec != new_usec_val){
    queue_clock_usec = 0;
    start_queue_timer();
  }

  if(queue_clock_usec != new_usec_val){
    queue_clock_usec = new_usec_val;
    start_queue_timer();
  }

#ifndef	SA_NOMASK
#ifndef	SA_NODEFER		/* we have to do this "by hand" */
  sigprocmask(SIG_SETMASK, &osigs, NULL);
#endif
#endif
}

Int32
post_process_proc(Uns32 entryidx)
{
  QueueEntry	*entry;
  UChar		*inmem;
  UChar		*inbuf;
  Uns32		num_in;
  Int32		res;

  entry = queue_entries + entryidx;

  inmem = entry->inmem;
  inbuf = inbufmem + entryidx * MAX_INRESULTLEN;
  num_in = entry->num_in;

  if(inmem && num_in)
    memcpy(inmem, inbuf, num_in * sizeof(UChar));

  switch(entry->instruction){
    case QUERYPOSITION:
	xref_to_UnsN(&cart, inbuf, 24);
	xref_to_Uns32(&filenum, inbuf + 3);
	break;

    case QUERYWRPOSITION:
	xref_to_UnsN(&wrcart, inbuf, 24);
	xref_to_Uns32(&wrfilenum, inbuf + 3);
	break;
  }

  res = (entry->processed == PROCESSED_OK ? NO_ERROR
			: entry->processed);

  return(res);
}

Int32
post_process_rest_of_queue()
{
  Int32		res = NO_ERROR;

  while(queueent_done_idx != queue_insert_idx){
    if(queue_entries[queueent_done_idx].processed == NOT_PROCESSED){
	pause();

	continue;
    }

    res = post_process_proc(queueent_done_idx);
    if(res)
	return(res);

    queueent_done_idx++;
    if(queueent_done_idx >= queuelen)
	queueent_done_idx = 0;
  }

  return(res);
}

Int32
post_process_pending()
{
  Int32		res = NO_ERROR;

  while(queueent_done_idx != queueent_processed_idx){
    res = post_process_proc(queueent_done_idx);

    if(res)
	return(res);

    queueent_done_idx++;
    if(queueent_done_idx >= queuelen)
	queueent_done_idx = 0;
  }

  return(res);
}

Int32
append_to_queue(
  Uns16		instruction,
  UChar		*inmem,
  Uns32		num_in,
  UChar		*outmem,
  Uns32		num_out)
{
  Int32		newidx, res;
  UChar		*outbuf;

  if( (res = post_process_pending()) )
    return(res);

  while((newidx = (queue_insert_idx + 1) % queuelen) == queueent_processed_idx)
    pause();			/* queue full */

  outbuf = outbufmem + MAX_OUTARGLEN * queue_insert_idx;

  queue_entries[queue_insert_idx].instruction = instruction;
  queue_entries[queue_insert_idx].inmem = inmem;
  queue_entries[queue_insert_idx].num_in = num_in;
  queue_entries[queue_insert_idx].outmem = outmem;
  queue_entries[queue_insert_idx].num_out = num_out;
  queue_entries[queue_insert_idx].processed = NOT_PROCESSED;

  if(outmem && num_out)
    memcpy(outbuf + 2, outmem, num_out * sizeof(UChar));
		/* data is always put into byte 3..., so we */
		/* can put the instruction into byte 2 (and */
		/* probably 1) and send it all together in */
		/* a single packet */

  queue_insert_idx = newidx;

  return(0);
}
