#include <conf.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.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 <utime.h>
#ifdef	HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif
#ifdef	HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#endif
#ifdef	HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#ifdef	HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <signal.h>
#include <unistd.h>
#include <utils.h>
#include <fileutil.h>
#include "backup.h"

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

static Int32	write_filecont(
			UChar *, struct stat *, AarParams *, UChar);

struct hardlink {
  ino_t 	ino;
  dev_t		dev;
  UChar		*name;
};

static struct hardlink	*hardlinks = NULL;
static Int32		num_hardlinks = 0;

static Int32
add_link(UChar * name, ino_t ino, dev_t dev)
{
  struct hardlink	*newlinks;

  newlinks = (struct hardlink *) malloc((num_hardlinks + 1) *
				sizeof(struct hardlink));
  if(! newlinks)
	return(-ENOMEM);

  if(num_hardlinks){
    memcpy(newlinks, hardlinks, num_hardlinks * sizeof(struct hardlink));
    free(hardlinks);
  }
  hardlinks = newlinks;

  hardlinks[num_hardlinks].ino = ino;
  hardlinks[num_hardlinks].dev = dev;
  hardlinks[num_hardlinks].name = strdup(name);
  if(!hardlinks[num_hardlinks].name)
    return(-ENOMEM);

  num_hardlinks++;

  return(0);
}

static Int32
link_in_list(ino_t ino, dev_t dev, UChar ** real_name)
{
  Int32	i;

  for(i = 0; i < num_hardlinks; i++){
    if(hardlinks[i].ino == ino && hardlinks[i].dev == dev){
	*real_name = hardlinks[i].name;
	return(1);
    }
  }

  return(0);
}

static void
write_dummy_bytes(
  size_t	num,
  AarParams	*params)
{
  Int32	i;
  UChar		buf[COMMBUFSIZ];

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

  do{
    i = (num < COMMBUFSIZ ? num : COMMBUFSIZ);

    params->outputfunc(buf, i, params);

    num -= i;
  } while(num > 0);
}

static Int32
open_for_compress(
  int		*fd,
  UChar		*name,
  UChar		*zipcmd,
  int		*pid,
  AarParams	*params)
{
  int		i, pp[2], lpid;
  sigset_t	osigs;

  *pid = -1;
  *fd = -1;

  i = pipe(pp);
  if(i){
    return(1);
  }

  sigprocmask(SIG_BLOCK, &params->blocked_signals, &osigs);

  lpid = fork_forced();
  if(lpid != 0)
    sigprocmask(SIG_SETMASK, &osigs, NULL);

  if(lpid < 0){
    close(pp[0]);
    close(pp[1]);
    return(2);
  }

  if(lpid){	/* parent */
    close(pp[1]);

    *fd = pp[0];
    *pid = lpid;

    return(0);
  }
  else{		/* child */
    int		ifd;
    char	**zipargv;

    clr_timer();

    close(pp[0]);

    if(cmd2argvq(&zipargv, zipcmd)){
	close(pp[1]);
	exit(1);
    }

    ifd = open(name, O_RDONLY | O_BINARY);
    if(ifd < 0){
	exit(2);
    }

    dup2(ifd, 0);
    dup2(pp[1], 1);

    execvp(zipargv[0], zipargv + 1);

    close(pp[1]);
    exit(3);
  }
}

static Int32
compress_to_mem(
  UChar		**mem,
  UChar		*name,
  size_t	*size,
  UChar		*zipcmd,
  AarParams	*params)
{
  int		i, pp[2], pid, pst, j;
  sigset_t	osigs;

  *mem = NULL;

  i = pipe(pp);
  if(i){
    return(1);
  }

  sigprocmask(SIG_BLOCK, &params->blocked_signals, &osigs);

  pid = fork_forced();
  if(pid != 0)
    sigprocmask(SIG_SETMASK, &osigs, NULL);

  if(pid < 0){
    close(pp[0]);
    close(pp[1]);
    return(1);
  }

  if(pid){	/* parent */
    close(pp[1]);

    *mem = NEWP(UChar, *size);

    if(! *mem){
	kill(pid, SIGTERM);
	close(pp[0]);
	waitpid_forced(pid, &pst, 0);
	return(4);
    }

    i = read_forced(pp[0], *mem, *size);

    close(pp[0]);

    j = waitpid_forced(pid, &pst, 0);

    if(j != pid || WEXITSTATUS(pst)){
	free(*mem);
	*mem = NULL;

	return(5);
    }

    if(i == *size || i == 0){
	free(*mem);
	*mem = NULL;
	return(-1);
    }

    *size = i;

    return(0);
  }
  else{		/* child */
    char	**zipargv;
    int		fd, j;

    clr_timer();

    close(pp[0]);

    if(cmd2argvq(&zipargv, zipcmd)){
	close(pp[1]);
	exit(1);
    }

    fd = open(name, O_RDONLY | O_BINARY);
    if(fd < 0){
	close(pp[1]);
	exit(2);
    }

    i = dup2(fd, 0);
    j = dup2(pp[1], 1);

    execvp(zipargv[0], zipargv + 1);

    close(pp[1]);
    exit(3);
  }
}

static Int32
compress_to_tmp(
  UChar		*tname,
  UChar		*sname,
  UChar		*zipcmd,
  AarParams	*params)
{
  int		ifd, ofd, pid, pst;
  sigset_t	osigs;

  sigprocmask(SIG_BLOCK, &params->blocked_signals, &osigs);

  pid = fork_forced();
  if(pid != 0)
    sigprocmask(SIG_SETMASK, &osigs, NULL);

  if(pid < 0){
    return(1);
  }

  if(pid){	/* parent */
    waitpid_forced(pid, &pst, 0);

    if(WEXITSTATUS(pst)){
	unlink(tname);

	return(2);
    }

    return(0);
  }
  else{		/* child */
    char	**zipargv;

    clr_timer();

    if(cmd2argvq(&zipargv, zipcmd)){
	exit(1);
    }

    ifd = open(sname, O_RDONLY | O_BINARY);
    if(ifd < 0){
	exit(2);
    }

    unlink(tname);
    ofd = open(tname, O_WRONLY | O_CREAT | O_BINARY, 0600);
    if(ofd < 0){
	exit(3);
    }

    dup2(ifd, 0);
    dup2(ofd, 1);

    execvp(zipargv[0], zipargv + 1);
    exit(4);
  }
}

#define	time_cond(statb, params)	\
	((! params->time_newer || params->time_newer <= statb.st_mtime)	\
	&& (! params->time_older || params->time_older > statb.st_mtime))
#define	uid_cond(statb, params)		\
	(! params->uid || (statb.st_uid == params->uid))

static Int32
write_file_uncompressed(
  UChar		*name,
  struct stat	*statb,
  AarParams	*params)
{
  int		fd;
  Int32		i;
  size_t	bytes, rd_bytes;
  UChar		sbuf[1000], mtimestr[50], sizestr[50];
  UChar		buf[BUFFERSIZ];
  FILE		*errfp;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  sigset_t	sigs, osigs;

  ofunc = params->outputfunc;
  errfp = params->errfp;

  fd = open(name, O_RDONLY | O_BINARY);
  if(fd < 0){
    fprintf(errfp, "Error: cannot open file \"%s\".\n", name);
    return(-ENOENT);
  }

  sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s;%s;", REGFILE,
		(unsigned long) statb->st_mode,
		(char *) time_t_to_intstr(statb->st_mtime, mtimestr),
		(int) statb->st_uid, (int) statb->st_gid,
		strlen(name), name,
		0, "",
		(char *) size_t_to_intstr(statb->st_size, sizestr));
  if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
    close(fd);
    return(i);
  }

  rd_bytes = 0;

  if(statb->st_size > 0){
    sigemptyset(&sigs);
    sigaddset(&sigs, SIGALRM);

    do{
      sigprocmask(SIG_BLOCK, &sigs, &osigs);
      bytes = read(fd, buf, COMMBUFSIZ);
      sigprocmask(SIG_SETMASK, &osigs, NULL);

      if(bytes < 0)
	bytes = 0;

      if(rd_bytes + bytes > statb->st_size){
	fprintf(errfp,
		"Error: File \"%s\" changed size while reading, trailing bytes not saved.\n",
		name);
	bytes = statb->st_size - rd_bytes;
      }

      rd_bytes += bytes;

      if( (i = ofunc(buf, bytes, params)) ){
	close(fd);
	return(i);
      }
    } while(rd_bytes < statb->st_size && bytes > 0);
  }

  close(fd);

  if(rd_bytes < statb->st_size){
    fprintf(errfp, "Error: Unexpected fault reading \"%s\".\n", name);

    write_dummy_bytes(statb->st_size - rd_bytes, params);
  }

  return(0);
}

static Int32
l_writeout(UChar * name, AarParams * params, UChar type_hardlink)
{
  struct stat	statb, statb2;
  Int32		i, k;
  UChar		buf[BUFFERSIZ], buf2[BUFFERSIZ], sbuf[1000];
  UChar		verbosestr[MAXPATHLEN * 2 + 100], mtimestr[50], sizestr[50];
  size_t	bytes, rd_bytes, size;
  int		fd, pid, pst;
  UChar		*hlinkname;
  DIR		*dir;
  struct dirent	*entry;
  UChar		*cptr;
  FILE		*errfp;
  UChar		*unzipcmd;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  Uns32		maxmem;
  UChar		filecontents;
  struct utimbuf utim;
  sigset_t	sigs, osigs;
#ifndef	_WIN32
  struct statfs	statfsb;
  struct rlimit	rlim;
#endif

  sigemptyset(&sigs);
  sigaddset(&sigs, SIGALRM);

  ofunc = params->outputfunc;

  unzipcmd = "";
  if(params->zipcmd && params->unzipcmd)
    if(*params->zipcmd && *params->unzipcmd)
      unzipcmd = params->unzipcmd;

  errfp = params->errfp;

  filecontents = 0;
  if(! strncmp(name, FILECONTPREFIX, i = strlen(FILECONTPREFIX))){
    filecontents = 1;
    name += i;
  }
  if(! strncmp(name, FILECONTZPREFIX, i = strlen(FILECONTZPREFIX))){
    filecontents = 1 | (unzipcmd[0] ? (1 << 1) : 0);
    name += i;
  }

  i = lstat(name, &statb);

  if(i == -1){
    fprintf(errfp, "Error: cannot stat \"%s\".\n", name);

    return(-ENOENT);
  }

  if((! IS_DIRECTORY(statb) || ! params->recursive)
			&& (! time_cond(statb, params)
				|| ! uid_cond(statb, params)))
    return(0);

  params->vars.num_fsentries++;

  utim.modtime = statb.st_mtime;
  utim.actime = statb.st_atime;

  params->vars.uid = statb.st_uid;

  if(params->pre_verbosefunc)
    (*params->pre_verbosefunc)(NULL, params);

  if(IS_SYMLINK(statb)){
    i = readlink(name, buf, BUFFERSIZ);

    if(i < 0){
	fprintf(errfp, "Error: cannot read symlink \"%s\".\n", name);
	return(-errno);
    }

    buf[i] = '\0';

    sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s.", SYMLINK,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name, strlen(buf), buf);
    if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }

    return(0);
  }

  i = stat(name, &statb);

  if(i == -1){
    fprintf(errfp, "Error: cannot stat \"%s\".\n", name);

    return(-ENOENT);
  }

  utim.modtime = statb.st_mtime;
  utim.actime = statb.st_atime;

  if(filecontents){
    i = write_filecont(name, &statb, params, filecontents & (1 << 1));
    if(i)
	return(i);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  } else if(IS_DIRECTORY(statb)){
    if(time_cond(statb, params)){	/* only, if time condition satisf */
      sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s.", DIRECTORY,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name);
      if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

      if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
      }
    }

    if(params->recursive){
      dir = opendir(name);

      if(!dir){
	fprintf(errfp, "Error: cannot read directory \"%s\".\n", name);
	return(-ENOENT);
      }

      forever{
	entry = readdir(dir);
	if(! entry)
	  break;

	cptr = (UChar *) &(entry->d_name[0]);

	if(!strcmp(cptr, ".") || !strcmp(cptr, ".."))
	  continue;

	sprintf(buf, "%s" FN_DIRSEPSTR "%s", name, cptr);

	l_writeout(buf, params, 0);
      }

      closedir(dir);
    }

    utime(name, &utim);
  }
  else if(IS_HARDLINK(statb) && ! type_hardlink){
    if(link_in_list(statb.st_ino, statb.st_dev, &hlinkname)){
      sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s.", HARDLINK,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name, strlen(hlinkname), hlinkname);
      if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

      if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
      }
    }
    else{
	l_writeout(name, params, 1);

	add_link(name, statb.st_ino, statb.st_dev);
    }

    utime(name, &utim);
  }
  else if(IS_FIFO(statb)){
    sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s.", FIFO,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name);
    if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  }
  else if(IS_SOCKET(statb)){
    sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s.", SOCKET,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name);
    if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  }
  else if(IS_BLOCKDEV(statb)){
    sprintf(sbuf, "%d;%lu;%s;%d;%d;%lu;%d;%s.", BLOCKDEVICE,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			(unsigned long) statb.st_rdev,
			strlen(name), name);
    if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  }
  else if(IS_CHARDEV(statb)){
    sprintf(sbuf, "%d;%lu;%s;%d;%d;%lu;%d;%s.", CHARDEVICE,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			(unsigned long) statb.st_rdev,
			strlen(name), name);
    if( (i = ofunc(sbuf, strlen(sbuf), params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  }
  else if(IS_REGFILE(statb)){
    UChar	compression_done, dont_compress;

    dont_compress = 0;
    if(params->dont_compress){
      UChar	**cpptr;

      for(cpptr = params->dont_compress; *cpptr; cpptr++){
	if(!fnmatch(*cpptr, FN_BASENAME(name), 0)){
	  dont_compress = 1;
	  break;
	}
      }
    }

    params->vars.sum_filesizes += (Real64) statb.st_size;

    if(!unzipcmd[0] || statb.st_size == 0 || dont_compress){
	i = write_file_uncompressed(name, &statb, params);

	if(i)
	  return(i);

	params->vars.sum_compr_filesizes += (Real64) statb.st_size;
    }
    else{		/* are we able to compress into memory ? */
      compression_done = 0;
#ifndef	_WIN32
      i = getrlimit(RLIMIT_STACK, &rlim);
#else
      i = 0;
#endif
      if(i){
	fprintf(errfp, "Error: cannot get maximum available memory.\n");
      }
      cptr = NULL;

#ifndef	_WIN32
      if(statb.st_size <= (rlim.rlim_cur >> 1) && ! i){		/* yes */
#else
      {
#endif
	bytes = statb.st_size;

	k = compress_to_mem(&cptr, name, &bytes, params->zipcmd, params);
	if(k < 0){			/* compression makes no sense */
	  i = write_file_uncompressed(name, &statb, params);

	  if(i)
	    return(i);

	  compression_done = 1;

	  params->vars.sum_compr_filesizes += (Real64) statb.st_size;
	}
	if(k == 0){			/* compression succeeded */
	  sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s;%s;", REGFILE,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name,
			strlen(unzipcmd), unzipcmd,
			(char *) size_t_to_intstr(bytes, sizestr));
	  if( (i = ofunc(sbuf, strlen(sbuf), params)) )
		return(i);

	  if( (i = ofunc(cptr, bytes, params)) )
		return(i);

	  free(cptr);

	  compression_done = 1;

	  params->vars.sum_compr_filesizes += (Real64) bytes;
	}
	if(k > 0){			/* compression failed */
	  cptr = NULL;
	}
      }
      if(!compression_done){	/* we have to compress into the tmp - */
	if(params->maxmem == 0){	/* filesystem, find available space */
	  UChar		*cp;

	  maxmem = 1000000;	/* assumption, if space can't be determined */

#ifndef _WIN32	/* here we believe in the maximum thinkable BS occuring */
	  tmpnam(buf2);
	  cp = FN_LASTDIRDELIM(buf2);
	  if(cp)
	    *cp = '\0';
#if defined(_AIX) || defined(linux) || defined(hpux) || defined(__FreeBSD__)
	  if(statfs(buf2, &statfsb) == -1)
#else
#if defined(sun)
	  if(statfs(buf2, &statfsb, sizeof(statfsb), 0) == -1)
#else
#if defined(sgi)
	  if(statfs(buf2, &statfsb, sizeof(statfsb), 0) == -1)
#else
#if defined(__osf__)
	  if(statfs(buf2, &statfsb, sizeof(statfsb)) == -1)
#else
#error	undefined architecture
#endif
#endif
#endif
#endif
	  {
	    fprintf(params->errfp,
		"Error: cannot get filesystem size, assuming 1MB.\n");
	  }
	  else{
	    maxmem = statfsb.f_bsize * statfsb.f_bfree / 2;
	  }
#endif	/* !defined(_WIN32) */

        }
        else
	  maxmem = params->maxmem;
   
        if(statb.st_size < maxmem){		/* we have space */
	  tmpnam(buf2);

	  i = compress_to_tmp(buf2, name, params->zipcmd, params);
	  if(i){
	    unlink(buf2);
	  }
	  else{
	    i = stat(buf2, &statb2);
	    if(i < 0){
		unlink(buf2);
	    }
	    else{
	      if(statb2.st_size < statb.st_size){ /* compression succeeded */
		fd = open(buf2, O_RDONLY | O_BINARY);
		if(fd < 0){
		  unlink(buf2);
		}
		else{
		  sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s;%s;", REGFILE,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name,
			strlen(unzipcmd), unzipcmd,
			(char *) size_t_to_intstr(statb2.st_size, sizestr));
      		  if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
		    close(fd);
		    unlink(buf2);
		    return(i);
		  }

		  rd_bytes = 0;

		  do{
		    sigprocmask(SIG_BLOCK, &sigs, &osigs);
	 	    bytes = read(fd, buf, COMMBUFSIZ);
		    sigprocmask(SIG_SETMASK, &osigs, NULL);

	 	    if(bytes < 0)
			bytes = 0;

		    rd_bytes += bytes;

		    if( (i = ofunc(buf, bytes, params)) ){
			close(fd);
			unlink(buf2);
			return(i);
	  	    }
		  } while(bytes > 0);

		  close(fd);
		  unlink(buf2);

		  if(rd_bytes < statb2.st_size){
		    fprintf(errfp,
			"Error: Unexpected fault reading \"%s\" == compressed \"%s\".\n",
				buf2, name);

		    write_dummy_bytes(statb2.st_size - rd_bytes, params);
		  }

		  compression_done = 1;

		  params->vars.sum_compr_filesizes += (Real64) statb2.st_size;
		}
	      }
	      else{	/* compression makes no sense. save file unchanged */
		unlink(buf2);

		i = write_file_uncompressed(name, &statb, params);

		if(i)
		  return(i);

		compression_done = 1;

		params->vars.sum_compr_filesizes += (Real64) statb.st_size;
	      }
	    }
	  }
	}
      }
      if(!compression_done){
			/* no space. compression must be done twice (shit) */
	i = open_for_compress(&fd, name, params->zipcmd, &pid, params);
	if(!i){
	    size = 0;
	    do{
		sigprocmask(SIG_BLOCK, &sigs, &osigs);
		i = read(fd, buf2, COMMBUFSIZ);
		sigprocmask(SIG_SETMASK, &osigs, NULL);

		if(i > 0)
		  size += i;
	    } while(i > 0);

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

	    if(!WEXITSTATUS(pst)){
		if(size < statb.st_size){	/* compression successful */
		  i = open_for_compress(&fd, name, params->zipcmd, &pid, params);

		  if(!i){
	            sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s;%s;", REGFILE,
			(unsigned long) statb.st_mode,
			(char *) time_t_to_intstr(statb.st_mtime, mtimestr),
			(int) statb.st_uid, (int) statb.st_gid,
			strlen(name), name,
			strlen(unzipcmd), unzipcmd,
			(char *) size_t_to_intstr(size, sizestr));
		    if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
			close(fd);
			return(i);
		    }

		    rd_bytes = 0;

		    do{
		      sigprocmask(SIG_BLOCK, &sigs, &osigs);
		      bytes = read(fd, buf, COMMBUFSIZ);
		      sigprocmask(SIG_SETMASK, &osigs, NULL);

		      if(bytes < 0)
			bytes = 0;

		      if(bytes + rd_bytes > size){
			fprintf(errfp,
				"Error: File \"%s\" changed size while reading, trailing bytes not stored.\n",
				name);
			bytes = size - rd_bytes;
		      }
		      rd_bytes += bytes;

		      if( (i = ofunc(buf, bytes, params)) ){
			close(fd);
			return(i);
		      }
		    } while(rd_bytes < size && bytes > 0);

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

		    if(!WEXITSTATUS(pst)){
			if(rd_bytes < size){
			    fprintf(errfp,
				"Error: Unexpected fault compressing \"%s\".\n",
				name);

			    write_dummy_bytes(size - rd_bytes, params);
			}

			compression_done = 1;

			params->vars.sum_compr_filesizes += (Real64) size;
		    }
		  }
		  else if(i > 0){
		    if(fd >= 0)
			close(fd);
		    if(pid > 0){
			kill(pid, SIGTERM);
			waitpid_forced(pid, &pst, 0);
		    }
		  }
		}
		else{			/* compression not successful */
		  i = write_file_uncompressed(name, &statb, params);

		  if(i)
		    return(i);

		  compression_done = 1;

		  params->vars.sum_compr_filesizes += (Real64) statb.st_size;
		}
	    }
	}
	else if(i > 0){
	  if(fd >= 0)
	    close(fd);
	  if(pid > 0){
	    kill(pid, SIGTERM);
	    waitpid_forced(pid, &pst, 0);
	  }
	}
      }
      if(!compression_done){
	i = write_file_uncompressed(name, &statb, params);

	if(i)
	  return(i);

	params->vars.sum_compr_filesizes += (Real64) statb.st_size;
      }
    }

    buf[0] = '.';

    if( (i = ofunc(buf, 1, params)) )
	return(i);

    utime(name, &utim);

    if(params->verbose){
	sprintf(verbosestr, "%s\n", name);
	params->verbosefunc(verbosestr, params);
    }
  }
  else{
    fprintf(errfp, "Error: \"%s\" is an illegal type of file.\n", name);
    return(WARNING);
  }

  return(0);
}

Int32
writeout(UChar * name, AarParams * params, Int16 endofrecord)
{
  Int32		i;
  UChar		buf[30];

  i = 0;

  if(name)
    i = l_writeout(name, params, 0);

  if(endofrecord){
    sprintf(buf, "%d;", ENDOFARCHIVE);
    i = params->outputfunc(buf, strlen(buf), params);
  }

  return(i);
}

Int32
default_output(UChar * buffer, Int32 num, AarParams * params)
{
  Int32	i;

  i = write(params->outfd, buffer, num);

  return(i);
}

static Int32
write_filecont(
  UChar		*name,
  struct stat	*statb,
  AarParams	*params,
  UChar		compress)
{
  int		fd, pp[2], pid, pst;
  Int32		i, num_read, ret = 0;
  UChar		sbuf[1000], mtimestr[50];
  UChar		buf[256];
  UChar		*unzipcmd;
  FILE		*errfp;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  int		num_written = 0;
  sigset_t	sigs, osigs;

  unzipcmd = "";
  ofunc = params->outputfunc;
  errfp = params->errfp;
  fd = -1;
  pid = -1;

  fd = open(name, O_RDONLY | O_BINARY);
  if(fd < 0){
    fprintf(errfp, "Error: cannot open file \"%s\".\n", name);
    return(-ENOENT);
  }

  if(compress){
    i = pipe(pp);
    if(i){
      ret = 1;
      GETOUT;
    }

    sigprocmask(SIG_BLOCK, &params->blocked_signals, &osigs);

    pid = fork_forced();
    if(pid != 0)
      sigprocmask(SIG_SETMASK, &osigs, NULL);

    if(pid < 0){
      close(pp[0]);
      close(pp[1]);
      ret = 1;
      GETOUT;
    }

    if(!pid){	/* child */
      char	**zipargv;

      clr_timer();

      close(pp[0]);
      if(cmd2argvq(&zipargv, params->zipcmd))
	exit(1);

      dup2(fd, 0);
      dup2(pp[1], 1);

      execvp(zipargv[0], zipargv + 1);

      close(fd);
      close(pp[1]);
      exit(3);
    }

    close(pp[1]);
    close(fd);
    fd = pp[0];

    unzipcmd = params->unzipcmd;
  }

  sprintf(sbuf, "%d;%s;%d;%s;%d;%s;", FILECONTENTS,
			(char *) time_t_to_intstr(statb->st_mtime, mtimestr),
			strlen(name), name, strlen(unzipcmd), unzipcmd);
  if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
    ret = i;
    GETOUT;
  }

  sigemptyset(&sigs);
  sigaddset(&sigs, SIGALRM);

  do{
    num_read = 0;
    forever{
      sigprocmask(SIG_BLOCK, &sigs, &osigs);
      i = read(fd, buf + 1 + num_read, 255 - num_read);
      sigprocmask(SIG_SETMASK, &osigs, NULL);

      if(i > 0)
	num_read += i;
      else
	break;
    }

    buf[0] = (UChar) num_read;

    /*if(num_read < 255)
	memset(buf + 1 + num_read, 0, sizeof(UChar) * (255 - num_read));*/

    if( (i = ofunc(buf, num_read + 1, params)) ){
      ret = i;
      GETOUT;
    }

    num_written += num_read;
  } while(num_read == 255);

  buf[0] = '.';
  if( (i = ofunc(buf, 1, params)) ){
    ret = i;
    GETOUT;
  }

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

  if(compress && pid >= 0){
    waitpid_forced(pid, &pst, 0);
    ret = WEXITSTATUS(pst);
  }

  return(ret);

 getout:
  if(pid >= 0)
    kill(pid, SIGTERM);

  CLEANUP;
}

