#include <conf.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/utsname.h>
#ifdef	HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef	HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <sys/types.h>
#ifdef  HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef	HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#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 <signal.h>
#include <x_types.h>
#include <utils.h>
#include <fileutil.h>

#include "budefs.h"
#include "backup.h"

#ifdef	DEBUG
#define	DB(fmt, arg1, arg2, arg3)	\
			{	FILE *fp; \
				fp = fopen("/dev/console", "w"); \
				fprintf(fp, fmt, arg1, arg2, arg3); \
				fflush(fp); \
				fclose(fp); }
#else
#define	DB(fmt, arg1, arg2, arg3)	/**/
#endif

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

#define	BUFFERED_OPERATION	0x01
#define	CHANGE_CART_ON_EOT	0x02

#define	BUFFEREDOP	(streamermode & BUFFERED_OPERATION)
#define	AUTOCHCART	(streamermode & CHANGE_CART_ON_EOT)

#define	MAX_BUFFER_MEMORY	10485760	/* 10 MB */
#define	MAX_UNINTR_CYCLES	5

#define	PREF_CLIENT_DELAY	5

#define	NOT_OPEN		-10

#define	OPENED_FOR_READ		0x8
#define	OPENED_FOR_WRITE	0x9
#define	OPENED_FOR_RAWREAD	0xa
#define	OPENED_FOR_RAWWRITE	0xb
#define	MODE_MASK		0xff

#define	OPENED_RAW_ACCESS	0x100
#define	MODE_FLAG_MASK		0xff00

#define	BU_NO_LOCK		0
#define	BU_LOCKED_RD		1
#define	BU_LOCKED_WR		2
#define	BU_LOCKED		(BU_LOCKED_RD | BU_LOCKED_WR)
#define	BU_GOT_LOCK_RD		4
#define	BU_GOT_LOCK_WR		8
#define	BU_GOT_LOCK		(BU_GOT_LOCK_RD | BU_GOT_LOCK_WR)
#define	BU_CANT_LOCK		64

#define	LOCK_READ		1
#define	LOCK_WRITE		2

#ifdef	YES
#undef	YES
#endif
#ifdef	NO
#undef	NO
#endif
#define	YES	1
#define	NO	0

UChar		*programdir = "nowhere";

UChar		*default_configfilenames[] = {	\
				DEFAULT_SERVER_CONFIGFILES, NULL, NULL };
UChar		*configfilename = NULL;

Uns32		tapeposfile_changed = 0;
Uns32		devicename_changed = 0;

UChar		*cryptfile = NULL;

UChar		*devicename = (UChar *) DEFAULT_TAPE_DEVICE;
struct stat	devstatb;
Int32		devprobe_interval = DEFAULT_DEVPROBEITV;

UChar		*storefile = NULL;

UChar		streamermode = BUFFERED_OPERATION | CHANGE_CART_ON_EOT;
UChar		*streamerbuffer = NULL;
Uns32		streamerbufferlen = 0;
Uns32		streamerbuf_insert_idx = 0;
Uns32		streamerbuf_processed_idx = 0;
UChar		at_mediaend = 0;
UChar		force_cartchange = 0;
Uns32		*tapefilenumbuf = NULL;

UChar		*init_cmd = NULL;
UChar		*exit_cmd = NULL;

UChar		*setcartcmd = NULL;
UChar		*nextcartcmd = (UChar *) DEFAULT_NEXTCARTCMD;
UChar		*setfilecmd = (UChar *) DEFAULT_SETFILECMD;
UChar		*skipfilescmd = (UChar *) DEFAULT_SKIPFILESCMD;
UChar		*erasetapecmd = NULL;
UChar		*weofcmd = (UChar *) DEFAULT_WEOFCMD;

UChar		*tapeposfile = (UChar *) DEFAULT_TAPEPOSFILE;

UChar		*loggingfile = NULL;
UChar		*lockfile = NULL;
int		lockfd;
UChar		locked = BU_NO_LOCK;
struct flock	lockb;

UChar		*user_to_inform = (UChar *) DEFAULT_USERTOINFORM;
UChar		*mail_program = (UChar *) DEFAULT_MAILPROGRAM;

Int16		cartridge_handler = 0;
Int32		num_cartridges = 1;

Int32		*startcarts = NULL;
Int32		*endcarts = NULL;
Uns32		num_cartsets = 0;
Int32		active_cartset = 0;

Int32		tapeblocksize = DEFAULT_TAPEBLOCKSIZE;
Uns32		maxbytes_per_file = DEFAULT_MAXBYTESPERFILE;

UChar		*pref_client_file = DEFAULT_PREFCLIENTFILE;

int		commfd;
char		*remotehost = "<unknown>";

FILE		*lfp = stderr;
int		lfd = 2;

int		tapefd = NOT_OPEN;

UChar		*tapebuffer;
Int32		outptr = 0;
UChar		*inputbuffer;
Int32		tapeptr = 0;
UChar		endofrec = 0;
Int32		intrpttapeptr = 0;

UChar		*infobuffer;
Int32		infobuffersize;
UChar		have_infobuffer = 0;
UChar		tried_infobuffer = 0;
Int32		tape_label_cartno;

Int32		tapeaccessmode = NOT_OPEN;

Int32		actfilenum = 1;
Int32		actcart = 1;
Int32		*insfilenum = NULL;
Int32		*inscart = NULL;

Uns32		cartins_gracetime = DEFAULT_CARTGRACETIME;
Uns32		devunavail_send_mail = DEFAULT_DEVUNAVSENDMAIL;
Uns32		devunavail_give_up = DEFAULT_DEVUNAVGIVEUP;

Uns32		bytes_written = 0;
struct utsname	unam;

UChar		interrupted = 0;

UChar		tape_moved = 0;
UChar		tape_rewound = 0;

struct stat	gstatb;			/* global multipurpose stat buffer */
struct timeval	null_timeval;

UChar		edebug = 0;

ParamFileEntry	entries[] = {
	{ &nextcartcmd, NULL,
	(UChar *) "^[ \t]*[Cc]hange[-_ \t]*[Cc]artr?i?d?g?e?[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &setcartcmd, NULL,
	(UChar *) "^[ \t]*[Ss]et[-_ \t]*[Cc]artr?i?d?g?e?[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &init_cmd, NULL,
	(UChar *) "^[ \t]*[iI]niti?a?l?i?z?a?t?i?o?n?[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &exit_cmd, NULL,
	(UChar *) "^[ \t]*[eE]xit[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &setfilecmd, NULL,
	(UChar *) "^[ \t]*[Ss]et[-_ \t]*[Ff]ile[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &skipfilescmd, NULL,
	(UChar *) "^[ \t]*[Ss]kip[-_ \t]*[Ff]iles?[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &erasetapecmd, NULL,
	(UChar *) "^[ \t]*[Ee]rase[-_ \t]*[Tt]ape[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &weofcmd, NULL,
	(UChar *) "^[ \t]*[Ww]r?i?t?e?[-_ \t]*[Ee][Oo][Ff][-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &tapeposfile, &tapeposfile_changed,
	(UChar *) "^[ \t]*[Tt]ape[-_ \t]*[Pp]osi?t?i?o?n?[-_ \t]*[Ff]ile:?[ \t]*",
		TypeUCharPTR	},
	{ &devicename, &devicename_changed,
	(UChar *) "^[ \t]*[Bb]ackup[-_ \t]*[Dd]evi?c?e?:?[ \t]*",
		TypeUCharPTR	},
	{ &devprobe_interval, NULL,
	(UChar *) "^[ \t]*[Dd]evi?c?e?[-_ \t]*[Pp]rob\\(e\\|ing\\)[-_ \t]*[Ii]nterval:?",
		TypeInt32	},
	{ &loggingfile, NULL,
	(UChar *) "^[ \t]*[Ll]ogg?i?n?g?[-_ \t]*[Ff]ile:?[ \t]*",
		TypeUCharPTR	},
	{ &lockfile, NULL,
	(UChar *) "^[ \t]*[Ll]ocki?n?g?[-_ \t]*[Ff]ile:?[ \t]*",
		TypeUCharPTR	},
	{ &user_to_inform, NULL,
	(UChar *) "^[ \t]*[Uu]ser[-_ \t]*[Tt]o[-_ \t]*[Ii]nfor?m?:?[ \t]*",
		TypeUCharPTR	},
	{ &mail_program, NULL,
	(UChar *) "^[ \t]*[Mm]ail[-_ \t]*[Pp]rogram:?[ \t]*",
		TypeUCharPTR	},
	{ &cartridge_handler, NULL,
	(UChar *) "^[ \t]*[Cc]artr?i?d?g?e?[-_ \t]*[Hh]andler:?[ \t]*",
		TypeInt16	},
	{ &num_cartridges, NULL,
	(UChar *) "^[ \t]*[Nn]umb?e?r?[-_ \t]*[Oo]f[-_ \t]*[Cc]artr?i?d?g?e?s[-_ \t]*:?",
		TypeInt32	},
	{ &endcarts, &num_cartsets,
	(UChar *) "^[ \t]*[Ll]ast[-_ \t]*[Cc]artr?i?d?g?e?s[-_ \t]*:?",
		TypeInt32PTR	},
	{ &tapeblocksize, NULL,
	(UChar *) "^[ \t]*[Tt]ape[-_ \t]*[Bb]lock[-_ \t]*[Ss]ize:?",
		TypeInt32	},
	{ &maxbytes_per_file, NULL,
	(UChar *) "^[ \t]*[Mm]axi?m?u?m?[-_ \t]*[Bb]ytes[-_ \t]*[Pp]er[-_ \t]*[Ff]ile:?",
		TypeUns32	},
	{ &cartins_gracetime, NULL,
	(UChar *) "^[ \t]*[Cc]artr?i?d?g?e?[-_ \t]*[Ii]nse?r?t?[-_ \t]*[Gg]race[-_ \t]*[Tt]ime:?",
		TypeUns32	},
	{ &devunavail_send_mail, NULL,
	(UChar *) "^[ \t]*[Dd]evi?c?e?[-_ \t]*[Uu]n[Aa]vaila?b?l?e?[-_ \t]*[Ss]end[-_ \t]*[Mm]ail[-_ \t]*\\([Aa]fter[-_ \t]*\\)?[Mm]inu?t?e?s?:?",
		TypeUns32	},
	{ &devunavail_give_up, NULL,
	(UChar *) "^[ \t]*[Dd]evi?c?e?[-_ \t]*[Uu]n[Aa]vaila?b?l?e?[-_ \t]*[Gg]ive[-_ \t]*[Uu]p[-_ \t]*\\([Aa]fter[-_ \t]*\\)?[Mm]inu?t?e?s?:?",
		TypeUns32	},
	{ &programdir, NULL,
	(UChar *) "^[ \t]*[Pp]rogram[-_ \t]*[Dd]ire?c?t?o?r?y?:?[ \t]*",
		TypeUCharPTR	},
	{ &cryptfile, NULL,
	(UChar *) "^[ \t]*\\([Ee]n\\)?[Cc]rypti?o?n?[-_ \t]*[Kk]ey[-_ \t]*[Ff]ile:?[ \t]*",
		TypeUCharPTR	},
	};

UChar	*lockdirs[] = {
		"/var/locks", "/var/lock", "/var/spool/locks",
		"/var/spool/lock", "/var/tmp", "/tmp", NULL,
};

Int32	set_cartridge_cmd(Int32);
Int32	set_cartridge(Int32);
Int32	set_filenum_cmd(Int32);
Int32	set_filenum(Int32, Int8);
Int32	skip_files_cmd(Int32);
Int32	skip_files(Int32);
Int32	set_cartset_cmd(Int32);
Int32	poll_device_cmd(UChar *, Int8);
Int32	closetape(Int8);
Int32	closetape_cmd();
Int32	closetapen_cmd();
Int32	opentapewr_cmd(Uns32);
Int32	opentapewr(Uns32);
Int32	opentaperd_cmd(Uns32);
Int32	opentaperd(Uns32);
Int32	read_from_tape();
Int32	erasetape();
Int32	erasetape_cmd();
Int32	rewindtape(Int8);
Int32	sendtapepos();
Int32	sendtapewrpos();
Int32	osendtapepos();
Int32	osendtapewrpos();
Int32	sendnumcarts();
Int32	sendifbusy();
Int32	flush_buffer_to_tape(Int32 *, Int32 *);
Int32	write_tape_buffer(Int32 *, Int32 *);
Int32	get_tape_buffer(Int32 *, Int32 *);
Int32	write_infobuffer(Int32);
Int32	goodbye(Int32);
Int32	goodbye_cmd();
Int32	write_tapepos_file();
Int32	write_tapepos_file_msg();
Int32	write_to_tape();
Int32	client_backup_cmd(Int8);
Int32	wait_for_service(Int8);
void	sig_handler();
Int32	create_streamer_buffer();
void	dismiss_streamer_buffer();
Int8	check_interrupted();
Int32	set_lock(Int8);
Int32	release_lock();
void	register_pref_serv();
void	release_pref_lock();

void
logmsg(UChar * fmt, ...)
{
  va_list	args;

  va_start(args, fmt);

  if(lfp){
    fprintf(lfp, "%s, ", actimestr());
    vfprintf(lfp, fmt, args);
    fflush(lfp);
  }

  va_end(args);
}

void
do_exit(int exitst)
{
  static char	exiting = 0;

  if(exiting){
    DB("Internal error, do_exit loops.\n", 0, 0, 0);

    return;
  }

  exiting = 1;	/* just in case, if we have produced a loop */

  closetape(1);

  register_pref_serv();

  release_lock();

  ms_sleep(1000 * 1);

  exit(exitst);
}

void
send_status(UChar statusc)	/* utility */
{
  write_split(commfd, &statusc, 1);
}

void
command_failed(UChar cmd, UChar * str)
{
  logmsg("Error: Command %d ('%c') failed (%s)\n", cmd, cmd, str);
}

void
prot_error(UChar * str, Int32 num)
{
  UChar	buf[100];

  sprintf(buf, str, num);
  logmsg("%s, Protocol Error: %s\n", actimestr(), buf);

  send_status(PROTOCOL_ERROR);
}

void
command_error(UChar * str)
{
  logmsg("Command Error: %s.\n", str);
}

void
fatal(UChar * msg)
{
  logmsg("%s", msg);

  do_exit(1);
}

void
nomemfatal()
{
  fatal("Error: Cannot allocate memory.\n");
}

Int32
set_storefile()
{
  UChar		buf[20];

  sprintf(buf, "%d", (int)(actfilenum - 1 + (have_infobuffer ? 1 : 0)));

  ZFREE(storefile);
  storefile = strchain(devicename, "/data.", buf, NULL);

  if(!storefile)
    return(FATAL_ERROR);

  return(0);
}

Int32
change_cartridge(Int8 wait_success)	/* take tape offline, unload */
{
  Int32	i;

  i = poll_device_cmd(nextcartcmd, wait_success);

  have_infobuffer = 0;
  tried_infobuffer = 0;

  tape_rewound = 0;
  tape_moved = 1;

  return(i);
}

Int32
wait_for_device(Int8 lock_mode)
{
  int		fd, i, st;
  time_t	t, t_mail, t_giveup;
  FILE		*pp;

  i = wait_for_service(lock_mode);
  if(i)
    return(i);

  fd = -1;
  st = stat(devicename, &devstatb);
  if(!st && !IS_DIRECTORY(devstatb)){
    fd = open(devicename, O_RDONLY | O_BINARY);
    if(fd < 0)
	st = -1;
  }

  if(st){
    t = time(NULL);
    t_mail = t + devunavail_send_mail * 60;
    t_giveup = t + devunavail_give_up * 60;

    do{
      if(check_interrupted())
	do_exit(1);

      if(t_mail > 0 && t > t_mail){
	pp = popen(mail_program, "w");
	if(!pp){
	  logmsg("Error: Unable to ask user %s to check device availability.\n",
			user_to_inform);
	}
	else{
	  fprintf(pp, "The device %s on host %s is not ready for use.\n",
			devicename, unam.nodename);
	  fprintf(pp, "You are requested to check the device for possible\n");
	  fprintf(pp, "errors and to correct them.\n\n");
	  fprintf(pp, "Best regards from your backup service.\n");
	  pclose(pp);
	}
	t_mail = 0;
      }

      if(t_giveup > 0 && t > t_giveup){
	logmsg("Warning: Device access timed out.\n");
	return((Int32) DEVNOTREADY);
      }
      ms_sleep(1000 * 10);
      t += 10;

      st = stat(devicename, &devstatb);
      if(!st && !IS_DIRECTORY(devstatb)){
	fd = open(devicename, O_RDONLY | O_BINARY);
	if(fd < 0)
	  st = -1;
      }
    } while(st);
  }

  if(fd >= 0)
    close(fd);

  return(0);
}

Int32
poll_device_cmd(UChar * cmd, Int8 wait_success)
{
  int		res;
  time_t	t, t_mail, t_giveup;
  FILE		*pp;

  res = system(cmd);

  if(res && wait_success){
    t = time(NULL);
    t_mail = t + devunavail_send_mail * 60;
    t_giveup = t + devunavail_give_up * 60;

    do{
      if(check_interrupted())
	do_exit(1);

      if(t_mail > 0 && t > t_mail){
	pp = popen(mail_program, "w");
	if(!pp){
	  logmsg("Error: Unable to ask user %s to check device availability.\n",
			user_to_inform);
	}
	else{
	  fprintf(pp, "The device %s on host %s is not ready for use.\n",
			devicename, unam.nodename);
	  fprintf(pp, "You are requested to check the device for possible\n");
	  fprintf(pp, "errors and to correct them.\n\n");
	  fprintf(pp, "Best regards from your backup service.\n");
	  pclose(pp);
	}
	t_mail = 0;
      }

      if(t_giveup > 0 && t > t_giveup){
	logmsg("Warning: Device command timed out.\n");
	return((Int32) res);
      }
      ms_sleep(1000 * 30);
      t += 30;
    } while( (res = system(cmd)) );
  }

  return(res);
}

Int32
set_lock(Int8 lock_mode)
{
  struct stat	statb;
  int		i, fd, lock_state = BU_NO_LOCK;
  char		buf[30], *cptr;

  if(locked & BU_GOT_LOCK){
    if(lock_mode == LOCK_WRITE && locked != BU_GOT_LOCK_WR){
	lseek(lockfd, 0, SEEK_SET);
	sprintf(buf, "%d %c\n", (int) getpid(), 'w');
	write_unintr(lockfd, buf, strlen(buf) + 1);

	locked = BU_GOT_LOCK_WR;
    }
    return((Int32) locked);
  }

  fd = open(lockfile, O_RDONLY);
  if(fd >= 0){
    i = read(fd, buf, 29);
    close(fd);
    lock_state = BU_LOCKED_WR;
    if(i > 0){
	buf[i] = '\0';
	for(cptr = buf + strlen(buf) - 1; cptr >= buf; cptr--){
	  if(*cptr == 'r')
	    lock_state = BU_LOCKED_RD;
	  if(isdigit(*cptr))
	    break;
	}
    }
  }

  i = lstat(lockfile, &statb);
  if(!i && !IS_REGFILE(statb)){
    if(unlink(lockfile)){
	logmsg("Error: Cannot remove lock file entry \"%s\", that is not a file.\n", lockfile);
	return( (Int32) (locked = BU_CANT_LOCK) );
    }

    i = 1;
  }

  if(! i){
    lockfd = open(lockfile, O_WRONLY | O_SYNC);
    if(lockfd < 0){
      logmsg("Warning: Lock file \"%s\" exists, but can't open it.\n",
			lockfile);
      return( (Int32) (locked = lock_state) );
    }
  }
  else{
    lockfd = open(lockfile, O_WRONLY | O_CREAT | O_SYNC, 0644);
    if(lockfd < 0){
	logmsg("Error: Cannot create lock file \"%s\".\n", lockfile);
	return( (Int32) (locked = BU_CANT_LOCK) );
    }
  }

  lockb.l_type = F_WRLCK;
  if(fcntl(lockfd, F_SETLK, &lockb)){
    close(lockfd);
    return( (Int32) (locked = lock_state) );
  }

  sprintf(buf, "%d %c\n", (int) getpid(), lock_mode == LOCK_WRITE ? 'w' : 'r');
  write_unintr(lockfd, buf, strlen(buf) + 1);

  locked = (lock_mode == LOCK_WRITE ? BU_GOT_LOCK_WR : BU_GOT_LOCK_RD);

  return((Int32) locked);
}

void
register_pref_serv()
{
  struct flock	plockb;
  time_t	locked_until, curtime;
  UChar		locking_remhost[100], buf[300], *cptr;
  Int32		lock_failed, file_read, i;
  int		fd;
  struct stat	statb;

  fd = 0;
  i = lstat(pref_client_file, &statb);
  if(!i && !IS_REGFILE(statb)){
    if(unlink(pref_client_file)){
	logmsg("Warning: Cannot remove filesystem entry to check preferred service for client, that is not a regular file !!!\n");

	fd = -1;
    }
  }
		/* check, if the same client did already connect recently */
  if(!fd)
    fd = open(pref_client_file, O_RDWR | O_CREAT, 0644);

  if(fd < 0){
    logmsg("Warning: Cannot open file to check preferred service for client.\n");
  }
  else{
    do{
      SETZERO(plockb);
      plockb.l_type = F_WRLCK;

      lock_failed = fcntl(fd, F_SETLK, &plockb);
			/* if we cannot get the lock, we will surely be able */
      if(lock_failed)		/* to read the file after a short period */
	ms_sleep(100 + (Int32) (drandom() * 500.0));

      file_read = 0;

      lseek(fd, 0, SEEK_SET);

      i = read(fd, buf, 99);

      if(i > 0){
	buf[i] = '\0';
	if(word_count(buf) == 2){
	  file_read = 1;

	  cptr = first_nospace(buf);
	  for(i = 0; *cptr && !isspace(*cptr); cptr++, i++)
	    locking_remhost[i] = *cptr;
	  locking_remhost[i] = '\0';

	  cptr = first_nospace(cptr);

	  locked_until = 0;
	  while(*cptr && isdigit(*cptr))
	    locked_until = locked_until * 10 + (*(cptr++) - '0');

	  if(strcmp(locking_remhost, remotehost)){
	    curtime = time(NULL);
	    if(locked_until >= curtime){
		ms_sleep(1000 * (locked_until + 1 - curtime));
	    }
	  }
	}
      }
    } while(lock_failed);

    lseek(fd, 0, SEEK_SET);

    cptr = time_t_to_intstr(time(NULL) + PREF_CLIENT_DELAY, NULL);
    if(!cptr)
	nomemfatal();

    sprintf(buf, "%s %s\n", remotehost, cptr);
    free(cptr);

    write(fd, buf, strlen(buf) + 1);

    close(fd);
  }
}

Int32
release_lock()
{
  if(locked & BU_GOT_LOCK){
#if	0	/* unnecessary: close removes any lock */
    lockb.l_type = F_UNLCK;
    fcntl(lockfd, F_SETLK, &lockb);
#endif

    close(lockfd);

    unlink(lockfile);

    locked = BU_NO_LOCK;
  }

  return(locked);
}

Int32
wait_for_service(Int8 lock_mode)
{
  time_t	t, t_mail, t_giveup;
  FILE		*pp;
  Int32		lck;
  int		sleeptime;

  lck = set_lock(lock_mode);
  if(lck == BU_CANT_LOCK)
    return((Int32) FATAL_ERROR);

  if(lck & BU_LOCKED){
    t = time(NULL);
    t_mail = t + devunavail_send_mail * 60;
    t_giveup = t + devunavail_give_up * 60;
    sleeptime = 1;

    do{
      if(check_interrupted())
	do_exit(1);

      if(t_mail > 0 && t > t_mail){
	pp = popen(mail_program, "w");
	if(!pp){
	  logmsg("Error: Unable to ask user %s to check service availability.\n",
			user_to_inform);
	}
	else{
	  fprintf(pp, "The backup service on host %s is in use.\n", unam.nodename);
	  fprintf(pp, "You are requested to check the service for possible\n");
	  fprintf(pp, "errors and to correct them.\n\n");
	  fprintf(pp, "Best regards from your backup service.\n");
	  pclose(pp);
	}
	t_mail = 0;
      }

      if(t_giveup > 0 && t > t_giveup){
	logmsg("Warning: Service access timed out.\n");
	return((Int32) SERVICEINUSE);
      }
      ms_sleep(1000 * sleeptime);
      t += sleeptime;

      sleeptime = (sleeptime < 30 ? sleeptime + 1 : 30);

      lck = set_lock(lock_mode);
      if(lck == BU_CANT_LOCK)
	return((Int32) FATAL_ERROR);

    } while(! (lck & BU_GOT_LOCK));
  }

  return(0);
}

Int32		/* reopen the stream for writing, this is not a cmd */
reopentapewr()
{
  Int32	i;
  int		filemode;
  UChar		*filename;

  if(tapeaccessmode != NOT_OPEN){
    i = close(tapefd);

    if(i){
	return(-errno);
    }

    tapeaccessmode = NOT_OPEN;
  }

  if(interrupted)
    do_exit(1);

  bytes_written = 0;

  endofrec = 0;

  actfilenum++;
  insfilenum[active_cartset]++;

  if( (i = write_tapepos_file_msg()) )
    return(i);

  if(stat(devicename, &devstatb)){
    return(-errno);
  }

  if(IS_REGFILE(devstatb) || IS_DIRECTORY(devstatb)){
    if( (i = set_filenum(insfilenum[active_cartset], YES)) ){
      logmsg("Error: Cannot change store file.\n");
      return(i);
    }
  }

  tape_moved = 1;
  tape_rewound = 0;

  filename = devicename;
  filemode = O_WRONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    filename = storefile;
    filemode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
  }

  tapefd = open(filename, filemode, 0600);
  if(tapefd < 0){
    logmsg("Error: Cannot open tape once again for writing.\n");
    return(-errno);
  }

  tapeaccessmode = OPENED_FOR_WRITE;

  return(0);
}

Int32
write_tape_buffer(Int32 * bufferpos, Int32 * bytes_flushed)
{
  Int32	nextidx;

  if(streamerbuffer){
    nextidx = streamerbuf_insert_idx + 1;
    if(nextidx >= streamerbufferlen)
	nextidx = 0;

    while(nextidx == streamerbuf_processed_idx)
	pause();

    memcpy(streamerbuffer + streamerbuf_insert_idx * tapeblocksize,
			tapebuffer, sizeof(UChar) * tapeblocksize);
    streamerbuf_insert_idx = nextidx;
    *bytes_flushed = tapeblocksize;

    *bufferpos = 0;

    return(0);
  }
  else{
    return(flush_buffer_to_tape(bufferpos, bytes_flushed));
  }
}

/* write tapebuffer to tape, evtl. start a new file or change cartridge */
Int32
flush_buffer_to_tape(Int32 * bufferpos, Int32 * flushed_bytes)
{
  static UChar	write_error = 0;
  Int32		i, newcart, written, w;
  UChar		*cptr, eom, *filename;
  int		filemode;

  if(maxbytes_per_file && !(tapeaccessmode & OPENED_RAW_ACCESS)
		&& bytes_written + tapeblocksize > maxbytes_per_file){
    if(reopentapewr()){
	logmsg("Error: Cannot reopen device for writing.\n");
	write_error = 1;
	return(REOPEN_FAILED);
    }
  }

  written = write_ext(tapefd, tapebuffer, tapeblocksize,
					WRITE_UNINTR | WRITE_NOSELECT);
  w = (written > 0 ? written : 0);

  bytes_written += w;

  *flushed_bytes = w;

  if(written == tapeblocksize){		/* the normal case */
    write_error = 0;
    *bufferpos = 0;
    return(0);
  }

  if(written > 0 && written < tapeblocksize){	/* block not fully written */
    for(i = 0, cptr = tapebuffer + *bufferpos; i < written; i++, cptr++)
	tapebuffer[i] = *cptr;
    *bufferpos = written;			/* we try not to be afraid */

    write_error = 0;
    return(0);
  }

  if(tapeaccessmode & OPENED_RAW_ACCESS){	/* In raw access mode */
    if(tapefd >= 0)				/* it is always an error, */
	close(tapefd);			/* if nothing could be written */

    tapefd = tapeaccessmode = NOT_OPEN;
    *bufferpos = 0;

    return(ENDOFTAPEREACHED);
  }

  eom = 0;

  if(written < 0){				/* tape operation failed */
    i = errno;
    if(END_OF_TAPE){
	eom = 1;
	errno = 0;
    }
    else if(errno ==
#ifdef	_AIX
			EMEDIA || errno == EIO
#else
			EIO
#endif
				){
      if(write_error){
	logmsg("Error: Repeated faults writing to tape, now: %s.\n",
				strerror(errno));
	return(FATAL_ERROR);
      }

      logmsg("Warning: A media error occured (%d), changing cartridge. Proceeding with fingers crossed.\n",
				errno);

      write_error = 1;
      eom = 1;
    }
    else{
	logmsg("Error: %s.\n", strerror(errno));
	write_error = 1;
	return(FATAL_ERROR);
    }
  }

  if(!eom){
    i = reopentapewr();		/* try again into the next tape file */
    if(!i){
      written = write_ext(tapefd, tapebuffer, tapeblocksize,
					WRITE_UNINTR | WRITE_NOSELECT);

      w = (written > 0 ? written : 0);

      bytes_written += w;

      *flushed_bytes = w;

      if(written == tapeblocksize){		/* that worked */
	write_error = 0;
	*bufferpos = 0;
	return(0);
      }

      if(written > 0 && written < tapeblocksize){
	for(i = 0, cptr = tapebuffer + *bufferpos; i < written; i++, cptr++)
	  tapebuffer[i] = *cptr;
	*bufferpos = written;		/* keep fingers crossed */

	write_error = 0;
	return(0);
      }

      eom = 1;		/* some other problems with the tape, assume EOM */

      logmsg("Warning: An unexpected error occured, changing cartridge. Proceeding with fingers crossed.\n");
      write_error = 1;
    }
  }

  if(eom){
	close(tapefd);	/* we change the cartridge and try again */

	tapeaccessmode = tapefd = NOT_OPEN;

	if(interrupted)
	  do_exit(1);

	bytes_written = 0;

	endofrec = 0;

	if(!AUTOCHCART)
	  return(ENDOFTAPEREACHED);

	newcart = (inscart[active_cartset] + 1 > endcarts[active_cartset] ?
				startcarts[active_cartset]
				: (inscart[active_cartset] + 1));
	i = set_cartridge(newcart);
	if(i){
	  logmsg("Error: Cannot change to cartridge %d for writing.\n",
			newcart);
	  return(REOPEN_FAILED);
	}

	inscart[active_cartset] = actcart;

	i = rewindtape(YES);
	if(i){
	  logmsg("Error: Cannot change to file 1 for writing.\n");

	  return(REOPEN_FAILED);
	}

	insfilenum[active_cartset] = actfilenum;

	if(write_tapepos_file_msg())
	  return(REOPEN_FAILED);

	if(interrupted)
	  do_exit(1);

	i = write_infobuffer(inscart[active_cartset]);
	if(i){
	  logmsg("Error: Cannot write label to tape.\n");

	  return(REOPEN_FAILED);
	}

	if(IS_REGFILE(devstatb) || IS_DIRECTORY(devstatb)){
	  if( (i = set_filenum(insfilenum[active_cartset], YES)) ){
	    logmsg("Error: Cannot change to file %d for writing.\n",
				insfilenum[active_cartset]);

	    return(REOPEN_FAILED);
	  }
	}

	filename = devicename;
	filemode = O_WRONLY | O_BINARY;

	if(IS_DIRECTORY(devstatb)){
	  filename = storefile;
	  filemode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
	}

	tapefd = open(filename, filemode, 0600);
	if(tapefd < 0){
	  return(REOPEN_FAILED);
	}

	tapeaccessmode = OPENED_FOR_WRITE;

	tape_moved = 1;
	tape_rewound = 0;
  }		/* eom */

  written = write_ext(tapefd, tapebuffer, tapeblocksize,
					WRITE_UNINTR | WRITE_NOSELECT);

  w = (written > 0 ? written : 0);

  bytes_written += w;

  *flushed_bytes = w;

  if(written == tapeblocksize){		/* now it worked. Poooh ! */
    *bufferpos = 0;
    write_error = 0;
    return(0);
  }

  if(written > 0 && written < tapeblocksize){	/* block not fully written */
    for(i = 0, cptr = tapebuffer + *bufferpos; i < written; i++, cptr++)
	tapebuffer[i] = *cptr;
    *bufferpos = written;			/* we try not to be afraid */

    write_error = 0;
    return(0);
  }

  write_error = 1;
  return(FATAL_ERROR);		/* finally we failed writing to tape */
}

Int32
read_tape_buffer(Int32 * bufferpos, Int32 * num_bytes)
{
  if(streamerbuffer){
    while(streamerbuf_insert_idx == streamerbuf_processed_idx){
	if(at_mediaend){	/* we are at end of media */
	  if((tapeaccessmode & OPENED_RAW_ACCESS) || tapeaccessmode == NOT_OPEN)
	    return(ENDOFFILEREACHED);

	  force_cartchange = 1;	/* need to read on, set flag */
	}			/* to force cartridge change */

	pause();
    }

    memcpy(tapebuffer,
	streamerbuffer + tapeblocksize * streamerbuf_processed_idx,
	tapeblocksize * sizeof(UChar));

    streamerbuf_processed_idx++;
    if(streamerbuf_processed_idx >= streamerbufferlen)
	streamerbuf_processed_idx = 0;

    *num_bytes = tapeblocksize;

    *bufferpos = 0;

    return(0);
  }
  else{
    return(get_tape_buffer(bufferpos, num_bytes));
  }
}

/* get a buffer from tape, evtl. change the cartridge */
Int32
get_tape_buffer(Int32 * bufferpos, Int32 * num_bytes)
{
  Int32		newcart, i, rawaccess;
  int		filemode;
  UChar		*filename;

  i = read_ext(tapefd, tapebuffer, tapeblocksize,
				READ_UNINTR | READ_NOSELECT);

  *num_bytes = i;

  if(i < tapeblocksize){
    if(errno){
	logmsg("Error: %s, trying to continue.\n", strerror(errno));
	errno = 0;
    }

    if(i < 1){		/* End Of File */
	if(tapefd >= 0)
	  close(tapefd);

	rawaccess = tapeaccessmode & OPENED_RAW_ACCESS;

	tapeaccessmode = tapefd = NOT_OPEN;

	*bufferpos = 0;
	endofrec = 0;

	actfilenum++;

	if(write_tapepos_file_msg())
	  return(REOPEN_FAILED);

	if(IS_REGFILE(devstatb) || IS_DIRECTORY(devstatb)){
	  if( (i = set_filenum(actfilenum, YES)) ){
	    logmsg("Error: Cannot change store file.\n");

	    return(i);
	  }
	}

	filename = devicename;
	filemode = O_RDONLY | O_BINARY;

	if(IS_DIRECTORY(devstatb)){
	  filename = storefile;
	}

	if(rawaccess){
	  at_mediaend = 1;
	  return(ENDOFFILEREACHED);
	}

	tapefd = i = open(filename, filemode);
	if(tapefd < 0 && !IS_REGFILE(devstatb) && !IS_DIRECTORY(devstatb)){
	  return(REOPEN_FAILED);
	}

	if(tapefd >= 0)
	  i = read_ext(tapefd, tapebuffer, tapeblocksize,
					READ_UNINTR | READ_NOSELECT);

	tapeaccessmode = OPENED_FOR_READ;

	if(i < 1){			/* at end of medium */
	  if(streamerbuffer){
	    if(!force_cartchange){	/* in read-ahead mode do */
		at_mediaend = 1;	/* not change tape until */
		return(0);		/* forced explicitely */
	    }
	    force_cartchange = at_mediaend = 0;	/* reset flags */
	  }

	  if(tapefd >= 0)
	    close(tapefd);
	  tapefd = tapeaccessmode = NOT_OPEN;

	  *bufferpos = 0;
	  endofrec = 0;

	  if(!AUTOCHCART)
	    return(ENDOFTAPEREACHED);

	  newcart = (actcart + 1 > endcarts[active_cartset] ? 
			startcarts[active_cartset] : (actcart + 1));
	  i = set_cartridge(newcart);
	  if(i){
	    logmsg("Error: Cannot change to cartridge %d for reading.\n",
				newcart);
	    return(REOPEN_FAILED);
	  }

	  i = set_filenum(1, YES);
	  if(i){
	    logmsg("Error: Cannot change to file 1 for reading.\n");

	    return(REOPEN_FAILED);
	  }

	  if(write_tapepos_file_msg())
	    return(REOPEN_FAILED);

	  filename = devicename;
	  filemode = O_RDONLY | O_BINARY;

	  if(IS_DIRECTORY(devstatb)){
	    filename = storefile;
	  }

	  tapefd = open(filename, filemode);
	  if(tapefd < 0){
	    return(REOPEN_FAILED);
	  }

	  tapeaccessmode = OPENED_FOR_READ;

	  tape_moved = 1;
	  tape_rewound = 0;

	  /*if(*num_bytes == 0)*/{	/* always read the block again */
	    i = read_ext(tapefd, tapebuffer, tapeblocksize,
					READ_UNINTR | READ_NOSELECT);
	  }
	}

	*num_bytes = i;
    }
  }

  return(0);
}

Int32
write_tapepos_file()	/* write the file to store cartridge and file */
{
  FILE		*fp;
  Int32		i;

  fp = fopen(tapeposfile, "w");

  if(!fp)
    return(-errno);

  fprintf(fp, "%ld %ld", actcart, actfilenum);

  for(i = 0; i < num_cartsets; i++)
    fprintf(fp, " %ld %ld", inscart[i], insfilenum[i]);

  fprintf(fp, "\n");

  fclose(fp);

  return(0);
}

Int32
write_tapepos_file_msg()
{
  Int32		i;

  if( (i = write_tapepos_file()) )
	logmsg("%s, Error: Cannot write file %s to save the tape state.\n",
			actimestr(), tapeposfile);

  return(i);
}

Int32
read_tapepos_file()	/* read the file to get cartridge and filenum */
{
  FILE		*fp;
  long int	i, j, n;
  long int	acart, afile, icart, ifile;
  Int32		num_leftover, r = 0;
  Int32		*leftovercart = NULL, *leftoverfilenum = NULL;

  fp = fopen(tapeposfile, "r");

  if(!fp)
    return(-errno);

  n = fscanf(fp, "%ld%ld", &acart, &afile);

  if(n < 2){
    r = CONFIG_ERROR;
    GETOUT;
  }

  actcart = acart;
  actfilenum = afile;

  leftovercart = NEWP(Int32, num_cartsets);
  leftoverfilenum = NEWP(Int32, num_cartsets);
  if(!leftovercart || !leftoverfilenum){
    r = FATAL_ERROR;
    GETOUT;
  }
  num_leftover = 0;
  memset(inscart, 0, num_cartsets * sizeof(Int32));
  memset(insfilenum, 0, num_cartsets * sizeof(Int32));

  for(i = 0; i < num_cartsets; i++){
    n = fscanf(fp, "%ld%ld", &icart, &ifile);

    if(n < 2)
	break;

    inscart[i] = icart;
    insfilenum[i] = ifile;
  }
  n = i;

  for(i = 0; i < num_cartsets; i++){
    if(inscart[i] < startcarts[i] || inscart[i] > endcarts[i]){
	logmsg("Warning: insert cartridge %d for set %d out of bounds. Trying to reorganize.\n",
						inscart[i], i + 1);

	for(j = i + 1; j < num_cartsets; j++){
	  if(inscart[j] >= startcarts[i] && inscart[j] <= endcarts[i]){
	    memswap(inscart + i, inscart + j, sizeof(Int32));
	    memswap(insfilenum + i, insfilenum + j, sizeof(Int32));
	    break;
	  }
	}
	if(j >= num_cartsets){
	  for(j = 0; j < num_leftover; j++){
	    if(leftovercart[j] >= startcarts[i]
				&& leftovercart[j] <= endcarts[i]){
		memswap(inscart + i, leftovercart + j, sizeof(Int32));
		memswap(insfilenum + i, &leftoverfilenum + j, sizeof(Int32));
		break;
	    }
	  }
	  if(j >= num_leftover){
	    leftovercart[num_leftover] = inscart[i];
	    leftoverfilenum[num_leftover] = insfilenum[i];
	    num_leftover++;

	    inscart[i] = startcarts[i];
	    insfilenum[i] = 1;

	    logmsg("Warning: Could not find start cartridge for set %d. Starting with cartridge %d, file 1.\n",
			i + 1, startcarts[i]);
	  }
	}
    }
  }

 getout:
  fclose(fp);

  ZFREE(leftovercart);
  ZFREE(leftoverfilenum);

  return(r);
}

Int32
put_in_sh(UChar ** str)
{
  char	*cptr;

  return(0);

/* NOTREACHED */

  cptr = strchain("sh -c \"", *str, " 1>",
		(loggingfile ? loggingfile : (UChar *) "/dev/null"),
		"0>&1 2>&1\"", NULL);
  if(!cptr)
    return(ENOMEM);
  free(*str);
  *str = cptr;

  return(0);
}

void
usage(UChar * prnam)
{
  logmsg("Usage: %s [ <configuration-file> ] [ -l <logging-file> ]\n", prnam);
  do_exit(1);
}

#define	cfr	cmd

main(int argc, char ** argv)
{
  Int32		i, j, errc, cmdres, in_trouble;
  UChar		*cptr, buf[100], *backuphome;
  Uns32		code, crpt, cmd, u;
  UChar		**cpptr, unsecure = 0, nobuffering = 0;
  struct stat	statb;
  int		sock_optval, sock_type;
  struct linger	linger;
  Int32		sock_optlen[2];
  struct sockaddr_in	peeraddr;
  struct hostent	*hp;

  errc = 0;
  SETZERO(lockb);

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

  commfd = dup(0);
  close(0);

  if(goptions(argc, (UChar **) argv, "s-1:;s:l;s:x;b:b;b:D;b:s", &i,
		&configfilename, &loggingfile, &programdir,
		&nobuffering, &edebug, &unsecure))
    usage(argv[0]);

  signal(SIGUSR1, sig_handler);
  while(edebug);	/* For debugging the caught running daemon */

#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
  sock_optval = IPTOS_THROUGHPUT;
  if( (i = setsockopt(commfd, IPPROTO_IP, IP_TOS,
				(char *) &sock_optval, sizeof(int))) < 0)
    fprintf(stderr, "Warning: setsockopt (IP_TOS): %d\n", errno);
#endif

		/* I use int for sock_optlen. See below for a *comment* */
  *((int *) &(sock_optlen[0])) = sizeof(int);
  i = getsockopt(commfd, SOL_SOCKET, SO_TYPE,
			(char *) &sock_type, (int *) &(sock_optlen[0]));
  if(i){
    fprintf(stderr, "Warning: getsockopt (SO_TYPE): %d\n", errno);
    sock_type = -1;
  }

#ifdef	TCP_NODELAY
  sock_optval = 1;
  if(sock_type == SOCK_STREAM){
    if( (i = setsockopt(commfd, IPPROTO_TCP, TCP_NODELAY,
				(char *) &sock_optval, sizeof(int))) < 0)
      fprintf(stderr, "Warning: setsockopt (TCP_NODELAY): %d\n", errno);
  }
#endif

#ifdef	SO_KEEPALIVE
  sock_optval = 1;
  if( (i = setsockopt(commfd, SOL_SOCKET, SO_KEEPALIVE,
				(char *) &sock_optval, sizeof(int))) < 0)
    fprintf(stderr, "Warning: setsockopt (SO_KEEPALIVE): %d\n", errno);
#endif

  linger.l_onoff = 1;
  linger.l_linger = 60;
  if ( (i = setsockopt(commfd, SOL_SOCKET, SO_LINGER, (char *) &linger,
						sizeof (linger))) < 0)
    fprintf(stderr, "Warning: setsockopt (SO_LINGER): %d", errno);

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

  i = uname(&unam);
  if(i < 0)
    strcpy(unam.nodename, "???");

  if(!configfilename){
    for(cpptr = default_configfilenames; *cpptr; cpptr++);

    backuphome = find_program(argv[0]);
    if(!backuphome){
	fprintf(stderr, "Warning: Could not find program file of \"%s\"\n",
				argv[0]);
    }
    else{
      cptr = mkabspath(backuphome, NULL);
      free(backuphome);
      backuphome = resolvepath__(cptr, NULL);
      free(cptr);
      if(backuphome){
	cptr = FN_LASTDIRDELIM(backuphome);
	if(cptr){
	  *cptr = '\0';
	  cptr = FN_LASTDIRDELIM(backuphome);
	  if(cptr){
	    cptr[1] = '\0';
	    *cpptr = strapp(backuphome, "lib" FN_DIRSEPSTR DEFSERVERCONF);
	  }
	}
      }
    }

    for(cpptr = default_configfilenames; *cpptr; cpptr++){
      configfilename = *cpptr;
      if(!stat(*cpptr, &statb) && access(*cpptr, R_OK)){
	cfr = 1;
	while(*cpptr) cpptr++;
	break;
      }
      if(!stat(*cpptr, &statb))
	break;
    }

    configfilename = *cpptr;
  }

  cfr = read_param_file(configfilename, entries,
			sizeof(entries) / sizeof(entries[0]), NULL, NULL);

  if(num_cartsets < 1){
    endcarts = NEWP(Int32, 1);
    if(!endcarts)
      nomemfatal();

    num_cartsets = 1;
    endcarts[0] = num_cartridges;
  }
  insfilenum = NEWP(Int32, num_cartsets);
  inscart = NEWP(Int32, num_cartsets);
  startcarts = NEWP(Int32, num_cartsets);
  if(!insfilenum || !inscart || !startcarts)
    nomemfatal();

  for(i = 0; i < num_cartsets; i++){
    insfilenum[i] = 1;

    startcarts[i] = 1;
    for(j = 0; j < num_cartsets; j++){
	if(endcarts[j] < endcarts[i] && startcarts[i] < endcarts[j] + 1)
	  startcarts[i] = endcarts[j] + 1;
    }

    inscart[i] = startcarts[i];
  }

  if(init_cmd)
    if(empty_string(init_cmd))
      init_cmd = NULL;
  if(exit_cmd)
   if(empty_string(exit_cmd))
      exit_cmd = NULL;
  if(setcartcmd)
    if(empty_string(setcartcmd))
      setcartcmd = NULL;
  if(erasetapecmd)
    if(empty_string(erasetapecmd))
      erasetapecmd = NULL;
  if(loggingfile)
    if(empty_string(loggingfile))
      loggingfile = NULL;
  if(lockfile)
    if(empty_string(lockfile))
      lockfile = NULL;
  if(cryptfile)
    if(empty_string(cryptfile))
      cryptfile = NULL;

  if(loggingfile)
    massage_string(loggingfile);
  if(lockfile)
    massage_string(lockfile);
  if(cryptfile)
    massage_string(cryptfile);
  if(tapeposfile_changed)
    massage_string(tapeposfile);
  if(devicename_changed)
    massage_string(devicename);

  setfilecmd = repl_substring(setfilecmd, "%d", devicename);
  if(put_in_sh(&setfilecmd))
	nomemfatal();
  skipfilescmd = repl_substring(skipfilescmd, "%d", devicename);
  if(put_in_sh(&skipfilescmd))
	nomemfatal();
  if(erasetapecmd){
    erasetapecmd = repl_substring(erasetapecmd, "%d", devicename);
    if(!erasetapecmd){
	nomemfatal();
    }
    if(put_in_sh(&erasetapecmd))
	nomemfatal();
  }
  nextcartcmd = repl_substring(nextcartcmd, "%d", devicename);
  if(put_in_sh(&nextcartcmd))
	nomemfatal();

  weofcmd = repl_substring(weofcmd, "%d", devicename);
  if(put_in_sh(&weofcmd))
	nomemfatal();

  if(setcartcmd){
    setcartcmd = repl_substring(setcartcmd, "%d", devicename);
    if(!setcartcmd){
	nomemfatal();
    }
    if(put_in_sh(&setcartcmd))
	nomemfatal();
  }

  mail_program = repl_substring(mail_program, "%u", user_to_inform);
  if(put_in_sh(&mail_program))
	nomemfatal();

  if(!setfilecmd || !nextcartcmd || !mail_program || !weofcmd){
    nomemfatal();
  }

  if(nobuffering)
    streamermode &= ~BUFFERED_OPERATION;

  tapebuffer = (UChar *) malloc((tapeblocksize + 2) * sizeof(UChar));
  if(!tapebuffer){
    nomemfatal();
  }

  i = COMMBUFSIZ + 8;
  inputbuffer = (UChar *) malloc(i * sizeof(UChar));
  if(!inputbuffer){
    nomemfatal();
  }

  infobuffersize = ((INFOBLOCKSIZE - 1) / tapeblocksize + 1)
					* tapeblocksize;
  infobuffer = (UChar *) malloc(infobuffersize * sizeof(UChar));
  if(!infobuffer){
    nomemfatal();
  }
  memset(infobuffer, 0, sizeof(UChar) * infobuffersize);

  if(loggingfile){
    lfd = open(loggingfile, O_APPEND | O_CREAT | O_WRONLY, 0644);
    if(lfd < 0)
	lfd = 2;
    else{
	lfp = fdopen(lfd, "a");
	if(! lfp){
	  lfp = stderr;
	}
	else{
	  dup2(lfd, 1);
	  dup2(lfd, 2);
	}
    }
  }

  if(cfr)
    logmsg("Warning: Could not read configuration file \"%s\".\n",
		configfilename ? configfilename : (UChar *) "<none found>");

  if(maxbytes_per_file < tapeblocksize){
    logmsg("Warning: Maximum filesize must be at least one tape block. "
			"Reset to %d.\n", tapeblocksize);
    maxbytes_per_file = tapeblocksize;
  }

  /* read encryption key */
  if(cryptfile){
    if(!stat(cryptfile, &statb)){
      if(statb.st_mode & 0044){
	logmsg("Error: Encryption key file \"%s\" is readable by unprivileged users.\n",
		cryptfile);
	do_exit(11);
      }
    }
  }

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

  if(!lockfile){
    for(cpptr = lockdirs; *cpptr; cpptr++){
      i = stat(*cpptr, &statb);
      if(!i){
	if(! access(*cpptr, W_OK)){
	  lockfile = strchain(*cpptr, FN_DIRSEPSTR,
					DEFAULT_SERVERLOCKFILE, NULL);
	  break;
	}
      }
    }
  }

  /* read the file containing the current position: # of cartridge and file */
  if(read_tapepos_file()){
    logmsg("Warning: No tape-state file found. Assuming initial startup with first cartridge in set.\n");

    write_tapepos_file_msg();
  }

			/* get name (or IP-address) of the connected host */
  *((int *) &(sock_optlen[0])) = sizeof(struct sockaddr_in);
  i = getpeername(commfd, (struct sockaddr *) &peeraddr,
						(int *) &(sock_optlen[0]));
  if(i){
    logmsg("Warning: Could not get name of connected peer: %s\n",
						strerror(errno));
  }
  else{
    hp = gethostbyaddr((char *) & peeraddr.sin_addr,
					sizeof(struct in_addr), AF_INET);
    if(hp)
	remotehost = strdup(hp->h_name);
    else

#ifdef	HAVE_INET_NTOA

	remotehost = strdup(inet_ntoa(peeraddr.sin_addr));

#else
	{
	  char		naddr[30];	/* manually: assuming IP-V4-address */
	  unsigned long	laddr;

	  laddr = ntohl(peeraddr.sin_addr.s_addr);

	  sprintf(naddr, "%u.%u.%u.%u", laddr >> 24, (laddr >> 16) & 0xff,
				(laddr >> 8) & 0xff, laddr & 0xff);
	  remotehost = strdup(naddr);
	}	  
#endif

    if(!remotehost)
	nomemfatal();
  }

  register_pref_serv();	/* serve same client immediately, others with delay */

  if(init_cmd){				/* run the init command, if supplied */
    cptr = repl_substring(init_cmd, "%p", remotehost);
    if(!cptr){
	logmsg("Warning: Could not replace %p in init command, leaving it unmodified.\n");
    }
    else{
	free(init_cmd);
	init_cmd = cptr;
    }

    i = system(init_cmd);
    if(i){
	logmsg("Warning: Executing init command %s returned exit status %d.\n",
				init_cmd, (int) i);
    }
  }

  cptr = GREETING_MESSAGE;
  i = strlen(cptr);

  write_split(commfd, cptr, i);

#ifdef	USE_DES_ENCRYPTION

  for(i = 0; i < 4; i++){
    code = (Uns32) (drandom() * 4294967296.0);
    memcpy(buf + 4 * i, &code, sizeof(Uns32));
  }
  write_split(commfd, buf, 16);

  encrpt(buf);

  if(interrupted)
	do_exit(1);

  read_split(commfd, buf + 16, 16);

  if(memcmp(buf, buf + 16, 16)

#else	/* defined(USE_DES_ENCRYPTION) */

  code = encrpt(time(NULL) * getpid());

  Uns32_to_xref(buf, code);

  write_split(commfd, buf, 4);

  crpt = encrpt(code);

  if(interrupted)
	do_exit(1);

  read_split(commfd, buf, 4);

  xref_to_Uns32(&code, buf);

  if(crpt != code

#endif	/* ifelse defined(USE_DES_ENCRYPTION) */

			 && !unsecure){
    logmsg("Error: Authentification failed for host %s. Exiting.\n",
						remotehost);
    errc = (Int32) AUTHENTICATION;
  }

  if(errc){
    send_status((UChar) errc);

    do_exit(1);
  }

  send_status(COMMAND_OK);

  in_trouble = 0;

  while(0 < read_unintr(commfd, buf, 1)){	/* command processing */
    cmd = buf[0];

    if(interrupted){
	goodbye(1);
	do_exit(1);
    }

    cmdres = 0;

    if(in_trouble){
      switch(cmd){
	case OPENFORWRITE:
	case OPENFORRAWWRITE:
	case ERASETAPE:
	case WRITETOTAPE:
	case CLIENTBACKUP:
	case OCLIENTBACKUP:
	  command_failed(cmd,
		"skipping possibly destructive commands while in trouble");
	  cmd = NOOPERATION;
	  cmdres = PROTOCOL_ERROR;
	  break;
      }
    }

    switch(cmd){
      case WRITETOTAPE:
	cmdres = write_to_tape();
	if(cmdres)
	  command_failed(cmd, "writing to tape failed.");
	break;

      case READFROMTAPE:
	cmdres = read_from_tape();
	if(cmdres)
	  command_failed(cmd, "reading from tape failed.");
	break;

      case QUERYPOSITION:
	cmdres = sendtapepos();
	if(cmdres)
	  command_failed(cmd, "getting the tape position failed.");
	break;

      case QUERYWRPOSITION:
	cmdres = sendtapewrpos();
	if(cmdres)
	  command_failed(cmd, "getting the tape write position failed.");
	break;

      case QUERYNUMCARTS:
	cmdres = sendnumcarts();
	if(cmdres)
	  command_failed(cmd, "determining the number of cartridges failed.");
	break;

      case QUERYRDYFORSERV:
	cmdres = sendifbusy();
	if(cmdres)
	  command_failed(cmd, "checking for service ability failed.");
	break;

      case SETCARTRIDGE:
	cmdres = read_split(commfd, buf, 3);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	cmdres = set_cartridge_cmd(((Uns32) buf[0] << 16) +
				((Uns32) buf[1] << 8) + buf[2]);
	if(cmdres)
	  command_failed(cmd, "cannot set cartridge");
	break;

      case SETFILE:
	cmdres = read_split(commfd, buf, 4);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	xref_to_Uns32(&u, buf);
	cmdres = set_filenum_cmd(u);
	if(cmdres)
	  command_failed(cmd, "cannot set file");
	break;

      case SKIPFILES:
	cmdres = read_split(commfd, buf, 4);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	xref_to_Uns32(&u, buf);
	cmdres = skip_files_cmd(u);
	if(cmdres)
	  command_failed(cmd, "cannot skip over files");
	break;

      case SETCARTSET:
	cmdres = read_split(commfd, buf, 3);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	cmdres = set_cartset_cmd(((Uns32) buf[0] << 16) +
				((Uns32) buf[1] << 8) + buf[2]);
	if(cmdres)
	  command_failed(cmd, "cannot set cartridge set");
	break;

      case SETBUFFEREDOP:
	if(tapeaccessmode != NOT_OPEN){
	  cmdres = DEVINUSE;
	  command_failed(cmd, "not allowed during device access");
	}
	else
	  streamermode |= BUFFERED_OPERATION;

	send_status(cmdres);

	break;

      case SETSERIALOP:
	if(tapeaccessmode != NOT_OPEN){
	  cmdres = DEVINUSE;
	  command_failed(cmd, "not allowed during device access");
	}
	else
	  streamermode &= ~BUFFERED_OPERATION;

	send_status(cmdres);

	break;

      case SETCHCARTONEOT:
	if(tapeaccessmode != NOT_OPEN){
	  cmdres = DEVINUSE;
	  command_failed(cmd, "not allowed during device access");
	}
	else
	  streamermode |= CHANGE_CART_ON_EOT;

	send_status(cmdres);

	break;

      case SETERRORONEOT:
	if(tapeaccessmode != NOT_OPEN){
	  cmdres = DEVINUSE;
	  command_failed(cmd, "not allowed during device access");
	}
	else
	  streamermode &= ~CHANGE_CART_ON_EOT;

	send_status(cmdres);

	break;

      case OPENFORWRITE:
	cmdres = opentapewr_cmd(0);
	if(cmdres)
	  command_failed(cmd, "cannot open device for writing");
	break;

      case OPENFORREAD:
	cmdres = opentaperd_cmd(0);
	if(cmdres)
	  command_failed(cmd, "cannot open device for reading");
	break;

      case OPENFORRAWWRITE:
	cmdres = opentapewr_cmd(OPENED_RAW_ACCESS);
	if(cmdres)
	  command_failed(cmd, "cannot open device for writing");
	break;

      case OPENFORRAWREAD:
	cmdres = opentaperd_cmd(OPENED_RAW_ACCESS);
	if(cmdres)
	  command_failed(cmd, "cannot open device for reading");
	break;

      case CLOSETAPE:
	cmdres = closetape_cmd();
	if(cmdres)
	  command_failed(cmd, "cannot close device");
	break;

      case CLOSETAPEN:
	cmdres = closetapen_cmd();
	if(cmdres)
	  command_failed(cmd, "cannot close device");
	break;

      case ERASETAPE:
	cmdres = erasetape_cmd();
	if(cmdres)
	  command_failed(cmd, "cannot erase tape");
	break;

      case GOODBYE:
	cmdres = goodbye_cmd();
	break;

      case CLIENTBACKUP:
	cmdres = client_backup_cmd(0);
	if(cmdres)
	  command_failed(cmd, "running a backup as client failed.");
	break;

      case NOOPERATION:
	send_status(cmdres);
	break;

	/* older versions for backward compatibility */
      case OSETCARTRIDGE:
	cmdres = read_split(commfd, buf, 1);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	cmdres = set_cartridge_cmd(buf[0]);
	if(cmdres)
	  command_failed(cmd, "cannot set cartridge");
	break;

      case OSETFILE:
	cmdres = read_split(commfd, buf, 3);
	if(cmdres){
	  prot_error("no argument to command %d", cmd);
	  break;
	}
	cmdres = set_filenum_cmd(((Uns32) buf[0] << 16) +
				((Uns32) buf[1] << 8) + buf[2]);
	if(cmdres)
	  command_failed(cmd, "cannot set file");
	break;

      case OQUERYPOSITION:
	cmdres = osendtapepos();
	if(cmdres)
	  command_failed(cmd, "getting the tape position failed.");
	break;

      case OQUERYWRPOSITION:
	cmdres = osendtapewrpos();
	if(cmdres)
	  command_failed(cmd, "getting the tape write position failed.");
	break;

      case OCLIENTBACKUP:
	cmdres = client_backup_cmd(1);
	if(cmdres)
	  command_failed(cmd, "running a backup as client failed.");
	break;

      default:
	if(isspace(cmd))
	  break;

	prot_error("unknown command: %d", cmd);
	cmdres = 1;
	in_trouble = 10;
    }

    if(!cmdres && in_trouble > 0)
	in_trouble--;
  }

  goodbye(0);
  do_exit(0);

  /* NOTREACHED */
}

Int32
write_infobuffer(Int32 cartno)
{
  UChar		*filename;
  int		i, filemode;

  have_infobuffer = 0;

  sprintf(infobuffer, "\n%s\n\n%s%d\n\n%s%s\n\n%s\n",
			INFOHEADER, CARTNOTEXT, (int) cartno,
			DATETEXT, actimestr(), VERSIONTEXT);

  filename = devicename;
  filemode = O_WRONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) )
	return(i);

    filename = storefile;
    filemode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
  }

  tapefd = open(filename, filemode, 0600);
  if(tapefd < 0){
    tapefd = NOT_OPEN;
    return(-errno);
  }

  if(write_ext(tapefd, infobuffer, infobuffersize,
			WRITE_UNINTR | WRITE_NOSELECT) < infobuffersize){
    close(tapefd);
    tapefd = NOT_OPEN;
    return(-errno);
  }

  close(tapefd);

  tapefd = NOT_OPEN;

  have_infobuffer = 1;
  tape_label_cartno = cartno;

  tape_moved = 1;
  tape_rewound = 0;

  return(0);
}

UChar
read_infobuffer(Int32 * cartno)
{
  Int32		i, fd;
  UChar		*cptr, *filename;
  int		i1, n, filemode;

  if(tried_infobuffer){
    if(have_infobuffer)
      *cartno = tape_label_cartno;
    else
      *cartno = actcart;

    return(have_infobuffer);
  }

  have_infobuffer = 0;
  memset(infobuffer, 0, sizeof(UChar) * infobuffersize);

  if( (i = rewindtape(YES)) )
    return(i);

  if(wait_for_device(LOCK_READ))
    return(have_infobuffer);

  tape_moved = 1;
  tape_rewound = 0;
  tried_infobuffer = 1;

  filename = devicename;
  filemode = O_RDONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    filename = storefile;
  }

  fd = open(filename, filemode);
  if(fd < 0)
    return(have_infobuffer);

  i = read_ext(fd, infobuffer, infobuffersize,
				READ_UNINTR | READ_NOSELECT);
  close(fd);

  if(i < 1){
    return(have_infobuffer);
  }

  if(!strstr(infobuffer, INFOHEADER))
    return(have_infobuffer);

  cptr = strstr(infobuffer, CARTNOTEXT);
  if(!cptr)
    return(have_infobuffer);

  cptr += strlen(CARTNOTEXT);
  i = sscanf(cptr, "%d%n", &i1, &n);
  if(i < 1)
    return(have_infobuffer);

  *cartno = tape_label_cartno = (Int32) i1;
  have_infobuffer = 1;

  return(have_infobuffer);
}

Int32
try_read_from_tape()
{
  int		fd, i, filemode;
  UChar		*filename;

  if( (i = stat(devicename, &devstatb)) )
     return(i);

  filename = devicename;
  filemode = O_RDONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) ){
	return(i);
    }

    filename = storefile;
  }

  if((fd = open(filename, filemode)) < 0)
    return(-1);

  i = read_unintr(fd, infobuffer, tapeblocksize);
  close(fd);

  tape_rewound = 0;
  tape_moved = 1;

  return(i == tapeblocksize ? 0 : -1);
}

Int32
closetape_cmd()
{
  Int32	i;

  i = closetape(1);

  if(i){
    send_status(i);
    return(i);
  }

  send_status(0);

  return(0);
}

Int32
closetapen_cmd()
{
  Int32	i;

  i = closetape(0);

  if(i){
    send_status(i);
    return(i);
  }

  send_status(0);

  return(0);
}

Int32
closetape(Int8 rewind)		/* command: close the stream */
{
  Int32	i, j, ret;

  ret = 0;

  if((tapeaccessmode & MODE_MASK) == OPENED_FOR_WRITE){
    if(tapeptr > 0 || bytes_written == 0){
      if(tapeptr < tapeblocksize)
	memset(tapebuffer + tapeptr,
			0, sizeof(UChar) * (tapeblocksize - tapeptr));

      write_tape_buffer(&tapeptr, &i);

      memset(tapebuffer, 0, tapeblocksize * sizeof(UChar));
      for(j = 0; j < COMMBUFSIZ; j += tapeblocksize)
	write_tape_buffer(&tapeptr, &i);
    }

    actfilenum++;
    insfilenum[active_cartset]++;
    if(actfilenum != insfilenum[active_cartset]){
	logmsg("Error: File numbers for writing inconsistent.\n");
    }

    write_tapepos_file_msg();

    bytes_written = 0;
  }

  if(tapeaccessmode != NOT_OPEN){
    if(streamerbuffer){
	dismiss_streamer_buffer();
    }

    if(tapefd >= 0){
      i = close(tapefd);
      if(i)
	ret = CLOSE_FAILED;
    }
    else{
      if(!streamerbuffer)
	logmsg("Internal error: Device should be open, fd = %d.\n",
			tapefd);
    }
  }

  tapefd = tapeaccessmode = NOT_OPEN;

  if(tape_moved && rewind){
    if( (i = rewindtape(YES)) ){
      logmsg("Error: Cannot rewind tape.\n");

      ret = CLOSE_FAILED;
    }
  }

  tapeptr = outptr = 0;
  endofrec = 0;

  return(ret);
}

Int32
opentapewr_cmd(Uns32 flags)	/* command: open tape for writing */
{
  Int32		i;

  i = opentapewr(flags);

  send_status(i ? (UChar) i : COMMAND_OK);

  return(i);
}

Int32
opentapewr(Uns32 flags)
{
  Int32		i, rawaccess;
  int		filemode;
  UChar		*filename;

  rawaccess = flags & OPENED_RAW_ACCESS;

  if(tapefd != NOT_OPEN)
    closetape(1);

  tapefd = tapeaccessmode = NOT_OPEN;

  if(stat(devicename, &devstatb)){
    return(OPENWR_FAILED);
  }

  i = wait_for_service(LOCK_WRITE);
  if(i)
    return(i);

  if(!rawaccess){
    i = set_cartridge(inscart[active_cartset]);
    if(i)
	return(i);

    if(insfilenum[active_cartset] == 1){
	i = erasetape();
	if(i)
	  return(i);

	i = rewindtape(YES);
	if(i)
	  return(i);

	if( (i = write_infobuffer(inscart[active_cartset])) )
	  return(i);

	if(IS_REGFILE(devstatb)){
	  if( (i = set_filenum(insfilenum[active_cartset], YES)) )
	    return(i);
	}
    }
    else{
	i = set_filenum(insfilenum[active_cartset], YES);
	if(i)
	  return(i);
    }

    tape_rewound = 0;
    tape_moved = 1;
  }

  tapeptr = 0;

  filename = devicename;
  filemode = O_WRONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) )
	return(i);

    filename = storefile;
    filemode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
  }

  tapefd = open(filename, filemode, 0600);
  if(tapefd < 0)
    return(OPENWR_FAILED);

  tapeaccessmode = OPENED_FOR_WRITE | (rawaccess ? OPENED_RAW_ACCESS : 0);

  if(BUFFEREDOP){
    if( (i = create_streamer_buffer()) )
	return(i);
  }

  return(0);
}

Int32
opentaperd_cmd(Uns32 flags)		/* command: open tape for reading */
{
  Int32		i;

  i = opentaperd(flags);

  send_status(i ? (UChar) i : COMMAND_OK);

  return(i);
}

Int32
opentaperd(Uns32 flags)
{
  Int32		i, real_cartno;
  UChar		have_infobuf, *filename;
  int		filemode;

  if(tapefd != NOT_OPEN)
    closetape(1);

  have_infobuf = read_infobuffer(&real_cartno);
  if(have_infobuf && real_cartno != actcart){
    logmsg("Warning: Expected cartridge %d in drive, but have %d.\n",
					actcart, real_cartno);
    i = actcart;
    actcart = real_cartno;
    if( (i = set_cartridge(i)) )
	return(i);
  }

  tape_moved = 1;
  tape_rewound = 0;

  if(stat(devicename, &devstatb))
    return(OPENRD_FAILED);

  filename = devicename;
  filemode = O_RDONLY | O_BINARY;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) )
	return(i);

    filename = storefile;
  }

  tapefd = open(filename, filemode);
  if(tapefd < 0)
    return(OPENRD_FAILED);

  tapeaccessmode = OPENED_FOR_READ |
		((flags & OPENED_RAW_ACCESS) ? OPENED_RAW_ACCESS : 0);

  if(BUFFEREDOP){
    if( (i = create_streamer_buffer()) )
	return(i);
  }

  for(i = 0; i < num_cartsets; i++)
    if(startcarts[i] <= actcart && endcarts[i] >= actcart)
	active_cartset = i;

  return(0);
}

Int32
set_cartridge_cmd(Int32 cartnum)	/* command: set cartridge */
{
  Int32		r;

  r = set_cartridge(cartnum);

  send_status(r ? (UChar) r : COMMAND_OK);

  return(r);
}

static Int32	manual_insert_error = 0;

Int32
set_cartridge(Int32 cartnum)	/* set cartridge */
{
  FILE		*pp;
  Int32		i, real_cartno, could_read = 0, could_unload;
  UChar		buf[100], *cptr, *cptr2, have_infobuf, tapeindrive;
  struct stat	statb;

  if(cartnum == PREVIOUS_CARTRIDGE){
    cartnum = actcart - 1;
    if(cartnum < 1){
	cartnum = num_cartridges;
    }
  }

  tape_rewound = 0;			/* force a rewind to test, */
  tapeindrive = ! rewindtape(NO);	/* whether cartridge is in drive */
  if(tapeindrive){
    have_infobuf = read_infobuffer(&real_cartno);	/* try read label */
    if(have_infobuf && real_cartno != actcart){
	logmsg("Warning: Expected cartridge %d in drive, have %d instead.\n",
					actcart, real_cartno);
	actcart = real_cartno;
    }
  }
  else
    actcart = -1;

  if(actcart == cartnum){		/* assumed: have correct tape */
    tape_rewound = 0;		/* minimum action: force a rewind try */
  }
  else{
    if(cartnum > num_cartridges || cartnum < 1){
      command_error("no valid cartridge number");
      return((Int32) NO_VALID_CARTRIDGE);
    }

    if(tapeaccessmode != NOT_OPEN){
      command_error("device is in use");
      return((Int32) DEVINUSE);
    }

    if(cartridge_handler && !setcartcmd){	/* in sequential mode */
      if( (i = wait_for_device(LOCK_READ)) )	/* tape must be there */
	return(i);

      tried_infobuffer = 0;
      if( (i = rewindtape(YES)) ){
	logmsg("Error: Cannot wind tape back to file 1.\n");
	return((Int32) CHANGECART_FAILED);
      }
    }

    if(cartridge_handler){
      if(setcartcmd){
	sprintf(buf, "%d", (int) cartnum);

	cptr = repl_substring(setcartcmd, "%n", buf);
	if(!cptr){
	  command_error("cannot allocate memory");
	  return((Int32) FATAL_ERROR);
	}

	sprintf(buf, "%d", (int) (cartnum - 1));
	cptr2 = repl_substring(cptr, "%m", buf);
	if(!cptr2){
	  free(cptr);
	  command_error("cannot allocate memory");
	  return((Int32) FATAL_ERROR);
	}

	i = poll_device_cmd(cptr2, YES);

	if(i){
	  logmsg("Warning: Setting the cartridge number %d failed.\n",
						(int) cartnum);

	  free(cptr);
	  free(cptr2);
	  return((Int32) CHANGECART_FAILED);
	}

	for(i = 0; i < cartins_gracetime; i++){
	  if(check_interrupted())
	    do_exit(1);

	  ms_sleep(1000 * 1);
	}

	free(cptr);
	free(cptr2);
      }
      else{
	while(actcart != cartnum){
	  actcart++;
	  if(actcart > num_cartridges)
	    actcart = 1;

	  i = change_cartridge(YES);
	  if(i){
	    logmsg("Error: Cannot run command to change to the next cartridge.\n");
	    return((Int32) CONFIG_ERROR);
	  }

	  if(write_tapepos_file_msg())
	    return((Int32) CHANGECART_FAILED);

	  for(i = 0; i < cartins_gracetime; i++){
	    if(check_interrupted())
	      do_exit(1);

	    ms_sleep(1000 * 1);
	  }
	}
      }
    }
    else{
	i = open(CARTREADY_FILE, O_CREAT | O_WRONLY, 0600);
	if(i >= 0)
	  close(i);
	else
	  logmsg("Error: Cannot create file \"%s\" to indicate"
			" waiting for tape change.\n", CARTREADY_FILE);

	if(tapeindrive)
	  could_unload = ! change_cartridge(NO);
	else
	  could_unload = 1;

	if(! could_unload)
	  logmsg("Warning: Cannot run command to unload the cartridge.\n");

	pp = popen(mail_program, "w");
	if(!pp){
	  logmsg("Error: Unable to ask user %s to change the cartridge.\n",
			user_to_inform);
	  return((Int32) CHANGECART_FAILED);
	}
	else{
	  fprintf(pp, "You are requested to change the cartridge in your\n");
	  fprintf(pp, " backup tape streamer.\n\n");
	  if(manual_insert_error){
	    fprintf(pp, "Your previous try failed. According to the data\n");
	    fprintf(pp, "on tape you inserted cartridge number %d.\n",
					(int) manual_insert_error);
	  }
 	  fprintf(pp, "Please insert cartridge number %d\n", (int) cartnum);
 	  fprintf(pp, "and enter the command:\n\n cartready\n");
	  fprintf(pp, "\non host %s.\n", unam.nodename);
	  fprintf(pp, "\nBest regards from your backup service.\n");
	  pclose(pp);

	  could_read = i = 0;
	  while(!stat(CARTREADY_FILE, &statb)){
	    ms_sleep(1000 * (devprobe_interval > 0 ? devprobe_interval : 2));

	    if(check_interrupted())
		do_exit(1);

	    if(could_unload && devprobe_interval > 0){
	      could_read = ! try_read_from_tape();
	      if(could_read)
		break;
	    }
	  }

	  if(could_read)
	    i = unlink(CARTREADY_FILE);
	  if(i){
	    logmsg("Error: Cannot remove file %s.\n", CARTREADY_FILE);

	    pp = popen(mail_program, "w");
	    fprintf(pp, "I cannot remove the file %s.\n", CARTREADY_FILE);
 	    fprintf(pp, "Please do it by hand and check owner and\n");
 	    fprintf(pp, "permissions of the program cartready. It MUST\n");
	    fprintf(pp, "be setuid and owned by the user, under whose\n");
	    fprintf(pp, "ID the backup-daemon is running.\n");
	    fprintf(pp, "(look it up in: /etc/inetd.conf)\n");
	    fprintf(pp, "\nBest regards from your backup service.\n");
	    pclose(pp);
	  }
	}

	if(! could_read){
	  for(i = 0; i < cartins_gracetime; i++){
	    if(check_interrupted())
	      do_exit(1);

	    ms_sleep(1000 * 1);
	  }
	}
    }

    tape_moved = 1;
  }

  have_infobuf = read_infobuffer(&real_cartno);

  if( (i = rewindtape(YES)) ){
    logmsg("Error: Cannot wind tape back to file 1.\n");
    return((Int32) CHANGECART_FAILED);
  }

  if(have_infobuf && real_cartno != cartnum){
	logmsg("Warning: Expected cartridge %d in drive, but have %d.\n",
					cartnum, real_cartno);

	actcart = real_cartno;

	if(cartridge_handler && setcartcmd){
	  logmsg("Error: Could not set cartridge no. %d\n", cartnum);

	  return((Int32) FATAL_ERROR);
	}

	if(cartridge_handler && !setcartcmd){
	  Int32	rec_trials;

	  for(rec_trials = 0; rec_trials < num_cartridges; rec_trials++){
	    i = change_cartridge(YES);
	    if(i){
	      logmsg("Error: Cannot run command to change to the next cartridge.\n");
	      return((Int32) CONFIG_ERROR);
	    }

	    have_infobuf = read_infobuffer(&real_cartno);

	    if( (i = rewindtape(YES)) ){
	      logmsg("Error: Cannot wind tape back to file 1.\n");
	      return((Int32) CHANGECART_FAILED);
	    }

	    if(!have_infobuf)
		continue;

	    if(real_cartno == cartnum)
		break;
	  }

	  if(rec_trials >= num_cartridges){
	    logmsg("Error: Did not find cartridge no %d\n", cartnum);

	    return((Int32) FATAL_ERROR);
	  }
	  else{
	    logmsg("Warning: Found correct cartridge, but administrator should do a check.\n");
	  }
	}

	if(!cartridge_handler){
	  manual_insert_error = real_cartno;
	  i = set_cartridge(cartnum);
	  manual_insert_error = 0;
	  return(i);
	}

	tape_moved = 1;
  }

  actcart = cartnum;

  if(write_tapepos_file_msg())
    return((Int32) CHANGECART_FAILED);

  return(0);
}

Int32
set_filenum_cmd(Int32 filenum)		/* command: set file */
{
  Int32	r;

  r = set_filenum(filenum, YES);

  send_status(r ? (UChar) r : COMMAND_OK);

  return(r);
}

Int32		/* set file, negative: ignore leading tape label file */
set_filenum(Int32 filenum, Int8 wait_success)
{
  UChar		*cptr2, *cptr;
  UChar		buf[20], have_infobuf, absolute;
  Int32		i;
  Int32		tapefilenum, real_cartno;

  absolute = (filenum >= 0 ? 0 : 1);
  filenum = (filenum >= 0 ? filenum : - filenum);

  if(filenum > 0x7fffffff || filenum < 1){
    command_error("no valid file number");
    return(NO_VALID_FILENUM);
  }

  if(tapeaccessmode != NOT_OPEN){
    command_error("device is in use");
    return(DEVINUSE);
  }

  if( (i = wait_for_device(LOCK_READ)) )
    return(i);

  if(! absolute){
    have_infobuf = read_infobuffer(&real_cartno);
    if(have_infobuf && real_cartno != actcart){
      logmsg("Warning: Expected cartridge %d in drive, but have %d.\n",
					actcart, real_cartno);
#if 0
      i = actcart;
#endif
      actcart = real_cartno;
#if 0
      if(i = set_cartridge(i)){
	return(i);
      }
#endif
    }
  }

  tapefilenum = filenum + ((have_infobuffer && !absolute) ? 1 : 0);

  sprintf(buf, "%d", (int)(tapefilenum - 1));

  cptr = repl_substring(setfilecmd, "%m", buf);
  if(!cptr){
    command_error("cannot allocate memory");
    return(FATAL_ERROR);
  }

  sprintf(buf, "%d", (int) tapefilenum);

  cptr2 = repl_substring(cptr, "%n", buf);
  if(!cptr2){
    free(cptr);
    command_error("cannot allocate memory");
    return(FATAL_ERROR);
  }

  tape_moved = 1;
  tape_rewound = (tapefilenum <= 1 ? 1 : 0);

  i = poll_device_cmd(cptr2, wait_success);
  if(i){
    logmsg("Warning: Setting the file number %d failed.\n", (int) tapefilenum);

    free(cptr);
    free(cptr2);

    return((Int32) SETFILE_FAILED);
  }

  actfilenum = filenum;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) )
	return(i);
  }

  if(write_tapepos_file_msg()){
    free(cptr);
    free(cptr2);

    return((Int32) SETFILE_FAILED);
  }

  free(cptr);
  free(cptr2);

  return(0);
}

Int32
skip_files_cmd(Int32 numfiles)
{
  Int32		r;

  r = skip_files(numfiles);

  send_status(r ? (UChar) r : COMMAND_OK);

  return(r);
}

Int32		/* set file, negative: ignore leading tape label file */
skip_files(Int32 numfiles)
{
  UChar		*cptr, *cptr2;
  UChar		buf[20];
  Int32		i;

  if(numfiles > 0x7fffffff || numfiles < 1){
    command_error("no valid file number");
    return(NO_VALID_FILENUM);
  }

  if(tapeaccessmode != NOT_OPEN){
    command_error("device is in use");
    return(DEVINUSE);
  }

  if( (i = wait_for_device(LOCK_READ)) )
    return(i);

  sprintf(buf, "%d", (int) numfiles);

  cptr = repl_substring(skipfilescmd, "%n", buf);
  if(!cptr){
    command_error("cannot allocate memory");
    return(FATAL_ERROR);
  }

  sprintf(buf, "%d", (int) numfiles - 1);

  cptr2 = repl_substring(cptr, "%m", buf);
  free(cptr);

  if(!cptr2){
    command_error("cannot allocate memory");
    return(FATAL_ERROR);
  }

  tape_moved = 1;
  tape_rewound = 0;

  i = system(cptr2);
  if(i){
    command_error("command to skip over files failed");

    free(cptr2);

    return(CONFIG_ERROR);
  }

  actfilenum += numfiles;

  if(IS_DIRECTORY(devstatb)){
    if( (i = set_storefile()) )
	return(i);
  }

  if(write_tapepos_file_msg()){
    free(cptr2);

    return((Int32) CHANGECART_FAILED);
  }

  free(cptr2);

  return(0);
}

Int32
set_cartset_cmd(Int32 cartsetno)
{
  Int32	r;

  if(cartsetno < 1 || cartsetno > num_cartsets){
    r = NO_VALID_CARTSET;
    send_status(r);
    return(r);
  }

  active_cartset = cartsetno - 1;

  send_status(COMMAND_OK);
  return(0);
}

Int32
rewindtape(Int8 wait_success)
{
  Int32	i;

  if(tape_rewound)
    return(0);

  tape_rewound = 0;

  i = set_filenum(-1, wait_success);
  if(! i)
    tape_rewound = 1;

  return(i);
}

void			/* send the buffer, that is filled from tape */
send_output_buffer(UChar * buffer, UChar statusb)
{
  if(outptr < COMMBUFSIZ)
    memset(buffer + outptr, 0, sizeof(UChar) * (COMMBUFSIZ - outptr));

  buffer[COMMBUFSIZ] = statusb;		/* last CMD is the result */

  write_split(commfd, buffer, COMMBUFSIZ + 1);

  outptr = 0;
}

Int32	/* command: read from tape until the send buffer is full, send it */
read_from_tape()
{
  Int32		i, j;
  UChar		*cptr, *cptr2;
  UChar		*outputbuffer = inputbuffer;

  if((tapeaccessmode & MODE_MASK) != OPENED_FOR_READ){
    command_error("device is not open for reading");

    memset(outputbuffer, 0, COMMBUFSIZ * sizeof(UChar));

    send_output_buffer(outputbuffer, DEVNOTOPENRD);

    return(DEVNOTOPENRD);
  }

  forever{
    if(interrupted){
	goodbye(1);
	do_exit(1);
    }

    if(endofrec && tapeptr == 0){
	send_output_buffer(outputbuffer, ENDOFFILEREACHED);

	return(0);
    }

    if(tapeptr == 0){
	j = read_tape_buffer(&tapeptr, &i);

	if(j){
	  send_output_buffer(outputbuffer, j);

	  return(j);
	}

	if(i == 0){
	  endofrec = 1;

	  send_output_buffer(outputbuffer, ENDOFFILEREACHED);

	  return(0);
	}

	if(i < tapeblocksize){		/* This shouldn't happen, but may */
	  cptr = tapebuffer + tapeblocksize - 1;
	  cptr2 = tapebuffer + i - 1;
	  for(;cptr2 >= tapebuffer; cptr2--, cptr--)
	    *cptr = *cptr2;

	  tapeptr = tapeblocksize - i;

	  /*memset(tapebuffer + i, 0,
			sizeof(UChar) * (tapeblocksize - i));
	  endofrec = 1;*/

	  logmsg("Warning: Incomplete block read from tape (%d bytes).\n", i);
	}
	else
	  endofrec = 0;
    }

    if(outptr + tapeblocksize - tapeptr <= COMMBUFSIZ){
	if(tapeptr < tapeblocksize)
	  memcpy(outputbuffer + outptr, tapebuffer + tapeptr,
				sizeof(UChar) * (tapeblocksize - tapeptr));
	outptr += tapeblocksize - tapeptr;
	tapeptr = 0;

	if(endofrec){
	  send_output_buffer(outputbuffer, ENDOFFILEREACHED);

	  return(0);
	}
	else if(outptr == COMMBUFSIZ){
	  send_output_buffer(outputbuffer, COMMAND_OK);

	  return(0);
	}
    }
    else{
	if(outptr < COMMBUFSIZ)
	  memcpy(outputbuffer + outptr, tapebuffer + tapeptr,
				sizeof(UChar) * (COMMBUFSIZ - outptr));
	tapeptr += (COMMBUFSIZ - outptr);
	outptr = COMMBUFSIZ;

	send_output_buffer(outputbuffer, COMMAND_OK);

	return(0);
    }
  }

  return(0);	/* NOTREACHED */
}

Int32	/* command: fill the buffer to tape from sent bytes, evtl. write */
write_to_tape()
{
  Int32	i, j, num, num_written;
  UChar		*cptr;

  if((tapeaccessmode & MODE_MASK) != OPENED_FOR_WRITE){
    command_error("device is not open for writing");
    send_status(DEVNOTOPENWR);
    return(DEVNOTOPENWR);
  }

  if( (i = read_split(commfd, inputbuffer, COMMBUFSIZ)) )
    return(FATAL_ERROR);

  if(interrupted){
    goodbye(1);
    do_exit(1);
  }

  i = COMMBUFSIZ;
  cptr = inputbuffer;

  forever{
    if(interrupted){
	goodbye(1);
	do_exit(1);
    }

    if(i + tapeptr <= tapeblocksize){
      if(i > 0)
	memcpy(tapebuffer + tapeptr, cptr, i * sizeof(UChar));

      tapeptr += i;

      send_status(COMMAND_OK);

      return(0);
    }

    if(tapeptr < tapeblocksize)
      memcpy(tapebuffer + tapeptr, cptr, (tapeblocksize - tapeptr)
					* sizeof(UChar));

    num = tapeblocksize - tapeptr;

    j = write_tape_buffer(&tapeptr, &num_written);
    if(j){
	send_status((UChar) j);

	return(j);
    }

    cptr += num;
    i -= num;
  }

  send_status(COMMAND_OK);

  return(0);
}

Int32				/* command: erase tape */
erasetape_cmd()
{
  Int32	i;

  i = erasetape();

  send_status(i ? (UChar) i : COMMAND_OK);

  return(0);
}

Int32
erasetape()
{
  Int32		i;

  if(!erasetapecmd)
    return(0);

  i = poll_device_cmd(erasetapecmd, YES);

  tape_moved = 1;
  tape_rewound = 0;

  if(i){
    logmsg("Error: Command to erase tape returned a bad status.\n");
    return(ERASETAPE_FAILED);
  }

  return(0);
}

Int32
sendpos(Int32 cart, Int32 file, UChar result)
{
  UChar		buf[10];

  UnsN_to_xref(buf, cart, 24);
  Uns32_to_xref(buf + 3, file);
  buf[7] = result;

  write_split(commfd, buf, 8);

  return(0);
}

Int32
sendtapepos()		/* command: query tape position */
{
  if((tapeaccessmode & MODE_MASK) == OPENED_FOR_READ && tapefilenumbuf &&
			streamerbuf_insert_idx != streamerbuf_processed_idx)
    return(sendpos(actcart, tapefilenumbuf[streamerbuf_processed_idx],
								COMMAND_OK));

  return(sendpos(actcart, actfilenum, COMMAND_OK));
}

Int32
sendtapewrpos()		/* command: query tape writing position */
{
  UChar		result;
  Int32		lck;

  lck = set_lock(LOCK_WRITE);
  result = ((lck & BU_LOCKED_WR) ? DEVINUSE : COMMAND_OK);

  return(sendpos(inscart[active_cartset], insfilenum[active_cartset], result));
}

/* The older version for backward compatibility */
Int32
osendpos(Int32 cart, Int32 file, UChar result)
{
  UChar		buf[8];

  buf[0] = cart;
  UnsN_to_xref(buf + 1, file, 24);
  buf[4] = result;

  write_split(commfd, buf, 5);

  return(0);
}

Int32
osendtapepos()		/* command: query tape position */
{
  return(osendpos(actcart, actfilenum, COMMAND_OK));
}

Int32
sendnumcarts()
{
  UChar		buf[8];

  UnsN_to_xref(buf, num_cartridges, 24);
  buf[3] = COMMAND_OK;

  write_split(commfd, buf, 4);

  return(0);
}

Int32
sendifbusy()
{
  UChar		busy[512];
  Int32		r = 0;
  int		fd;

  memset(busy, 0, sizeof(UChar) * 512);

  if(set_lock(LOCK_WRITE) & BU_GOT_LOCK){
    fd = open(devicename, O_RDONLY);
    if(fd < 0){
	if(errno == EBUSY)
	  busy[0] = STREAMER_DEVINUSE;
	else
	  busy[0] = STREAMER_UNAVAIL;
    }
    else{
	close(fd);
	if(rewindtape(NO)){
	  busy[0] = (cartridge_handler && setcartcmd) ?
				(STREAMER_CHANGEABLE | STREAMER_UNLOADED)
				: STREAMER_UNLOADED;
	}
	else{
	  busy[0] = STREAMER_READY;

	  if(cartridge_handler)
	    busy[0] |= STREAMER_CHANGEABLE;
	}
    }
  }
  else
    busy[0] = STREAMER_BUSY;

  release_lock();

  r = write_split(commfd, busy, 512);

  send_status(r ? r : COMMAND_OK);

  return(r);
}

Int32
osendtapewrpos()		/* command: query tape writing position */
{
  UChar		result;
  Int32		lck;

  lck = set_lock(LOCK_WRITE);
  result = ((lck & BU_LOCKED_WR) ? DEVINUSE : COMMAND_OK);

  return(osendpos(inscart[active_cartset], insfilenum[active_cartset],
					result));
}

Int32
goodbye(Int32 exitst)		/* command: goodbye */
{
  Int32		i;
  UChar		*cptr;

  tried_infobuffer = 1;
  have_infobuffer = 0;

  closetape(1);

  if(exit_cmd && ! exitst){
    cptr = repl_substring(exit_cmd, "%p", remotehost);
    if(!cptr){
	logmsg("Warning: Could not replace %p in exit command, leaving it unmodified.\n");
    }
    else{
	free(exit_cmd);
	exit_cmd = cptr;
    }

    i = system(exit_cmd);
    if(i){
      logmsg("Warning: Executing exit command returned %d.\n", i);
    }
  }

  if(loggingfile)
    fclose(lfp);

  return(0);
}

Int32
goodbye_cmd()
{
  Int32		i;

  i = goodbye(0);

  send_status(i ? (UChar) i : COMMAND_OK);

  do_exit(0);

  /* NOTREACHED */
}

Int32
client_backup_cmd(Int8 old_version)
{
  UChar		n, *buf = NULL, *cptr, r, *eocmd, echr, suppressoutput;
  Int32		i;
  int		fd, pid, pst;
  UChar		*output = NULL, lenstr[5];
  Uns32		output_len = 0;

  memset(lenstr, 0, 5 * sizeof(UChar));

  r = COMMAND_OK;

  i = read_split(commfd, &n, 1);
  if(i){
    r = PROTOCOL_ERROR;
    goto retlabel;
  }

  if(!old_version){
    i = read_split(commfd, &suppressoutput, 1);
    if(i){
	r = PROTOCOL_ERROR;
	goto retlabel;
    }
  }

  buf = NEWP(UChar, n + 1);
  if(!buf)
    return(FATAL_ERROR);

  i = read_split(commfd, buf, n);
  if(i){
    r = PROTOCOL_ERROR;
    goto retlabel;
  }
  buf[n] = '\0';

  for(eocmd = buf; !isspace(*eocmd) && *eocmd; eocmd++);
  echr = *eocmd;
  *eocmd = '\0';

  if(! FN_ISPATH(buf)){
    *eocmd = echr;
    cptr = strchain(programdir, FN_DIRSEPSTR, buf, NULL);
    free(buf);
    buf = cptr;
    for(eocmd = buf; !isspace(*eocmd) && *eocmd; eocmd++);
    echr = *eocmd;
    *eocmd = '\0';
  }
  cptr = FN_LASTDIRDELIM(buf);
  *cptr = '\0';

  if(strncmp(programdir, buf, strlen(programdir))){
    r = AUTHENTICATION;
    goto retlabel;
  }
  *cptr = FN_DIRSEPCHR;

  if(access(buf, X_OK)){
    r = SUBPROCESS_FAILED;
    goto retlabel;
  }

  *eocmd = echr;

  if(old_version){
    if( (i = system(buf)) )
	r = SUBPROCESS_FAILED;
  }
  else{
    fd = open_from_pipe(buf, NULL, 1 + 2, &pid);
    if(fd >= 0){
	forever{
	  output = ZRENEWP(output, UChar, output_len + 1000);
	  if(!output){
	    r = FATAL_ERROR;
	    output_len = 0;
	    break;
	  }

	  n = read_unintr(fd, output + output_len, 1000);
	  if(n < 1)
	    break;

	  output_len += n;
	}

	waitpid_forced(pid, &pst, 0);
	close(fd);

	lenstr[0] = WEXITSTATUS(pst);
    }
    else{
	output = strapp("Error: Could not start subprocess ", buf);
	output_len = strlen(output);

	lenstr[0] = 127;
    }

    if(suppressoutput)
	output_len = 0;

    Uns32_to_xref(lenstr + 1, output_len);

    write_split(commfd, lenstr, 5);

    if(output){
	write_split(commfd, output, output_len);

	free(output);
    }
  }

 retlabel:
  ZFREE(buf);

  if(!old_version && r != COMMAND_OK)
    write_split(commfd, lenstr, 5);	/* to fulfil protocol in error case */

  send_status(r);

  return(r);
}

void
sig_handler(int sig)
{
  signal(sig, sig_handler);

  switch(sig){
    case SIGPIPE:
	logmsg("Error: Connection to client lost. Exiting.\n");
	do_exit(1);
    case SIGTERM:
	logmsg("Got signal TERM. Exiting.\n");
	interrupted = 1;
	return;
    case SIGINT:
	logmsg("Got signal INTR. Exiting.\n");
	interrupted = 1;
	return;
    case SIGHUP:
	logmsg("Got signal HUP. Exiting.\n");
	interrupted = 1;
	return;
    case SIGSEGV:
	logmsg("Error: Segmentation fault. Exiting.\n");
	do_exit(1);
    case SIGBUS:
	logmsg("Error: Bus error. Exiting.\n");
	do_exit(1);
    case SIGUSR1:
	edebug = 0;
	break;
  }
}

Int8
check_interrupted()
{
  int		i;
  struct sockaddr_in	peeraddr;
  Int32		len[2];

  if(interrupted)
    return(1);

		/* I use 32 Bit int for len. See below for a *comment* */
  *((int *) &(len[0])) = sizeof(int);
  i = getpeername(commfd, (struct sockaddr *) &peeraddr, (int *) &(len[0]));
  if(i && errno == ENOTCONN){
    logmsg("Error: Client disconnected. Exiting.\n");
    kill(getpid(), SIGPIPE);
  }

  return(interrupted);
}

/* buffered operation stuff */
void
start_buffer_timer(Uns32 buffer_clock_usec)
{
  struct itimerval	buffer_clock_itv;

  SETZERO(buffer_clock_itv);

  buffer_clock_itv.it_value.tv_usec
			= buffer_clock_itv.it_interval.tv_usec
			= buffer_clock_usec % 1000000;
  buffer_clock_itv.it_value.tv_sec
			= buffer_clock_itv.it_interval.tv_sec
			= buffer_clock_usec / 1000000;

  setitimer(ITIMER_REAL, &buffer_clock_itv, NULL);
}

void
intrpt_routine(int sig)
{
  static int	cycle_started = 0, cycle_interrupted = 0;

#ifndef	SA_NOMASK
#ifndef	SA_NODEFER		/* we have to do this "by hand" */
  sigset_t	sigs, osigs;	/* for safety block the signal explicitely */
#endif
#endif
  fd_set	fds;
  Int32		i, j, next_idx;
  UChar		*tmp_tapebuf;

#if 0	/* now using sigaction */
  signal(SIGALRM, intrpt_routine);
#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

  if(cycle_started){
    cycle_interrupted++;

    return;
  }

  cycle_started = 1;
  cycle_interrupted = 0;

  if((tapeaccessmode & MODE_MASK) == OPENED_FOR_WRITE){
    if(streamerbuf_insert_idx == streamerbuf_processed_idx)
	CLEANUP;

    while(streamerbuf_insert_idx != streamerbuf_processed_idx){
	FD_ZERO(&fds);
	FD_SET(tapefd, &fds);
	if(select(tapefd + 1, NULL, &fds, NULL, &null_timeval) < 1)
	  CLEANUP;

	tmp_tapebuf = tapebuffer;
	tapebuffer = streamerbuffer + tapeblocksize * streamerbuf_processed_idx;
	i = flush_buffer_to_tape(&intrpttapeptr, &j);
	tapebuffer = tmp_tapebuf;

	if(i){
	  logmsg("Error: Writing buffer to tape failed in interrupt routine.\n");
	  logmsg("       Return code is %d, written: %d.\n", (int) i, (int) j);
	}

	streamerbuf_processed_idx++;
	if(streamerbuf_processed_idx >= streamerbufferlen)
	  streamerbuf_processed_idx = 0;
    }
  }

  if((tapeaccessmode & MODE_MASK) == OPENED_FOR_READ){
    forever{
	if(at_mediaend && !force_cartchange)	/* at end of media */
	  CLEANUP;		/* do not read until forced-flag set */

	if(cycle_interrupted > MAX_UNINTR_CYCLES)    /* esp. while starting */
	  CLEANUP;	     /* give some time to serve the client */

	next_idx = streamerbuf_insert_idx + 1;	/* next index in buffer */
	if(next_idx >= streamerbufferlen)
	  next_idx = 0;

	if(next_idx == streamerbuf_processed_idx)	/* buffer full */
	  CLEANUP;

	FD_ZERO(&fds);			/* can we read sth. from tape ? */
	FD_SET(tapefd, &fds);
	if(select(tapefd + 1, &fds, NULL, NULL, &null_timeval) < 1)
	  CLEANUP;					/* nope. */

	tmp_tapebuf = tapebuffer;	/* read from tape to buffer */
	tapebuffer = streamerbuffer + tapeblocksize * streamerbuf_insert_idx;
	i = get_tape_buffer(&intrpttapeptr, &j);
	tapebuffer = tmp_tapebuf;

	tapefilenumbuf[streamerbuf_insert_idx] = actfilenum;

	if(i == ENDOFFILEREACHED && (tapeaccessmode & OPENED_RAW_ACCESS))
	  CLEANUP;			/* now also at_mediaend is set */

	if(i){
	  logmsg("Error: Reading buffer from tape failed in interrupt routine.\n");
	  logmsg("       Return code is %d, written: %d.\n", (int) i, (int) j);
	}

	if(at_mediaend && !force_cartchange)	/* now at end of media */
	  CLEANUP;		/* do nothing until forced-flag set */

	streamerbuf_insert_idx = next_idx;	/* at last incr. index */
    }
  }

 cleanup:
  cycle_started = cycle_interrupted = 0;

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

Int32
create_streamer_buffer()
{
  struct sigaction	siga;
  Int32		i;

  SETZERO(null_timeval);

  dismiss_streamer_buffer();

#ifndef	_WIN32
 {
  struct rlimit		rlim;

  i = getrlimit(RLIMIT_STACK, &rlim);
  if(i){
    logmsg("Error: cannot get maximum available memory\n");
    streamerbufferlen = 1000000 / tapeblocksize / 3;
  }
  else{
    streamerbufferlen = rlim.rlim_cur / tapeblocksize / 3;
  }
 }
#else
  streamerbufferlen = 1000000 / tapeblocksize / 3;
#endif

  if(streamerbufferlen < 2){
    streamerbufferlen = 0;
    ZFREE(streamerbuffer);

    logmsg("Warning: Low on memory. No tape buffer created.\n");
    return(0);
  }

  if(streamerbufferlen * tapeblocksize > maxbytes_per_file / 5)
    streamerbufferlen = maxbytes_per_file / 5 / tapeblocksize;

  streamerbuffer = NEWP(UChar, streamerbufferlen * tapeblocksize);
  tapefilenumbuf = NEWP(Uns32, streamerbufferlen);

  if(!streamerbuffer || !tapefilenumbuf){
    streamerbufferlen = 0;

    ZFREE(streamerbuffer);
    ZFREE(tapefilenumbuf);

    logmsg("Warning: Low on memory. No tape buffer created.\n");
    return(0);
  }

  streamerbuf_insert_idx = streamerbuf_processed_idx = 0;
  intrpttapeptr = 0;

  SETZERO(siga);
  siga.sa_handler = intrpt_routine;
#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_buffer_timer(10000);

  return(0);
}

void
dismiss_streamer_buffer()
{
  if(streamerbuf_insert_idx != streamerbuf_processed_idx
			&& (tapeaccessmode & MODE_MASK) == OPENED_FOR_WRITE){
    start_buffer_timer(10000);

    while(streamerbuf_insert_idx != streamerbuf_processed_idx)
	pause();
  }

  start_buffer_timer(0);		/* in fact stop it */

  ZFREE(streamerbuffer);
  ZFREE(tapefilenumbuf);

  streamerbufferlen = 0;
}

/* *comment* on getsockopt argument 5:
 * The IBM had the *great* idea to change the type of argument 5 of
 * getsockopt (and BTW argument 3 of getpeername) to size_t *. On the
 * Digital Unix size_t is an 8 Byte unsigned integer. Therefore the
 * safety belt 
 *  int sock_optlen[2].
 * I tried to find a workaround using autoconfig. Parsing the Headers
 * using cpp and perl or awk does not lead to satisfying results. Using
 * the TRY_COMPILE macro of autoconfig fails sometimes. E.G. the DEC
 * C-compiler does not complain on wrong re-declarations (neither via
 * error-message nor by the exit status). Try yourself, if you don't
 * believe. Furthermore there are 4 ways of declaration i found on
 * several systems. 4th argument as char * or void *, 5th arg as
 * explained of type int * or size_t *. Testing all these is annoying.
 * Maybe some other vendors have other ideas, how getsockopt should
 * be declared (sigh). Therefore i use two 32-Bit integers. Even if
 * a system call thinks, it must put there a 64-Bit int, the program
 * does not fail.
 */
