#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_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 <sysutils.h>
#include <fileutil.h>
#include <mvals.h>
#include <packer.h>
#if defined(USE_SOLARIS2_ACLS) || defined(USE_HPUX10_ACLS) || defined(USE_POSIX_ACLS)
#include <sys/acl.h>
#endif
#ifdef	USE_ZLIB
#include <zutils.h>
#endif

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

#define	ER__(func, i)	{ if( (i = func) ) return(i); }

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

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

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

#ifdef	USE_ZLIB

#define	read_file(fd, cp, n, z, f)	read_file_zip(z, cp, n, f)

#else	/* defined(USE_ZLIB) */

#define	read_file(fd, cp, n, z, f) (*f)(fd, cp, n)

#endif	/* defined(USE_ZLIB) */


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

  newlinks = (struct hl_spec *) realloc_forced(hardlinks,
			(num_hardlinks + 1) * sizeof(struct hl_spec));
  if(! newlinks)
	return(-ENOMEM);

  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(
  off_t		num,
  AarParams	*params)
{
  Int32	i;
  UChar		buf[BUFFERSIZ];

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

  do{
    i = (Int32) (num < BUFFERSIZ ? num : BUFFERSIZ);

    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;

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

#ifdef	USE_ZLIB

  SETZERO(params->vars.zfile);

  if(!zipcmd){
    *fd = open(name, O_RDONLY | O_BINARY);

    if(params->bi_compr_level){
	i = open_file_zip(&params->vars.zfile, *fd, params->bi_compr_level,
					params->bi_compr_maxblock);
	if(i)
	  return(i);
    }

    return(!(*fd >= 0));
  }

#endif    

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

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

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

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

#ifdef	USE_ZLIB

    i = open_file_zip(&params->vars.zfile, *fd, params->bi_compr_level,
					params->bi_compr_maxblock);
    if(i)
	return(i);

#endif

    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
write_cmdio(UChar * cmd, AarParams * params)
{
  Int32		r = NO_ERROR, i, n;
  FILE		*errfp;
  int		pfd, pid, pst, ftype;
  UChar		buf[8196], *cmd1, *cmd2, c, *cptr;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  Int32		(*readfunc)(int, UChar *, Int32);
  Flag		cks;
  Uns32		crc32sum;	/* uninitialized OK */

  cks = NO;
  ftype = COMMANDINOUT;

#ifdef	USE_ZLIB

  cks = params->check;
  if(cks){
    ftype = COMMANDINOUTCKS;
    crc32sum = crc32(0L, Z_NULL, 0);
  }

#endif  

  cmd1 = cmd + strlen(CMDINOUTPREFIX);
  cmd2 = strstr(cmd1, CMDINOUTSEP);
  c = *cmd2;
  *cmd2 = '\0';

  errfp = params->errfp;
  readfunc = params->readfunc;

  strcpy(buf, cmd1);
  repl_esc_seq(buf, ESCAPE_CHARACTER);

  pid = -1;
  pfd = fdpopen(buf, O_RDONLY, &pid);
  if(pfd < 0){
    fprintf(errfp, T_("Error: Cannot run command `%s' to read output.\n"), cmd1);
    params->vars.errnum |= EPACKREADCMD;
    return(-EINVAL);
  }

  *cmd2 = c;
  ofunc = params->outputfunc;

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

  sprintf(buf, "%d;%d;%s;", ftype, strlen(cmd), cmd);
  if( (r = ofunc(buf, strlen(buf), params)) )
    CLEANUP;

  do{
    for(n = 8192, cptr = buf + 2; n >= 0; cptr += i, n -= i){
	i = readfunc(pfd, cptr, n);
	if(i < 1)
	  break;

#ifdef	USE_ZLIB

	if(cks)
	  crc32sum = crc32(crc32sum, cptr, i);

#endif

    }
    n = 8192 - n;
    UnsN_to_xref(buf, n, 16);

    if( (r = ofunc(buf, n + 2, params)) )
	CLEANUP;
  } while(n == 8192);

  if(cks)
    Uns32_to_xref(buf, crc32sum);

  buf[4] = '.';
  i = (cks ? 0 : 4);
  if( (i = ofunc(buf + i, 1 + 4 - i, params)) )
    if(!r)
	r = i;

  if(params->verbose && params->verbosefunc){
    sprintf(buf, "%s\n", cmd);
    params->verbosefunc(buf, params);
  }

 cleanup:
  if(pfd >= 0){
    close(pfd);

    if(pid > 0){
	waitpid_forced(pid, &pst, 0);
	pst = WEXITSTATUS(pst);
	if(pst)
	  r = - pst;
    }
  }

  return(r);
}

#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, ftype;
  Int32		i;
  off_t		rd_bytes;
  off_t		bytes;
  UChar		sbuf[1000], mtimestr[50], sizestr[50];
  UChar		buf[BUFFERSIZ];
  FILE		*errfp;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  Int32		(*readfunc)(int, UChar *, Int32);
  Flag		cks;
  Uns32		crc32sum;	/* uninitialized OK */

  cks = NO;
  ftype = REGFILE;

#ifdef	USE_ZLIB

  cks = params->check;
  if(cks){
    ftype = REGFILECKS;
    crc32sum = crc32(0L, Z_NULL, 0);
  }

#endif  

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

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

  sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s;%s;", ftype,
		(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 *) off_t_to_intstr(statb->st_size, sizestr));
  if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
    close(fd);
    params->vars.errnum |= EPACKWRITE;
    return(i);
  }

  rd_bytes = 0;

  if(statb->st_size > 0){
    do{
      i = readfunc(fd, buf, BUFFERSIZ);
      if(i < 0)
	i = 0;

#ifdef	USE_ZLIB

      if(cks && i > 0)
	crc32sum = crc32(crc32sum, buf, i);

#endif

      bytes = i;

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

      rd_bytes += bytes;

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

  close(fd);

  if(rd_bytes < statb->st_size){
    fprintf(errfp, T_("Error: Unexpected fault reading \"%s\".\n"), name);
    params->vars.errnum |= EPACKREAD;

    write_dummy_bytes(statb->st_size - rd_bytes, params);
  }
  else{
    if(cks){
	Uns32_to_xref(sbuf, crc32sum);
	if( (i = ofunc(sbuf, 4, params)) ){
	  params->vars.errnum |= EPACKWRITE;
	  return(i);
	}
    }
  }

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

  return(0);
}

#ifdef	USE_POSIX_ACLS
#ifdef	ACL_TYPE_DEFAULT_DIR
#define	PACKER_ACL_NUM_TYPES	3
#else
#define	PACKER_ACL_NUM_TYPES	2
#endif

#ifdef	NACLBASE
#define	PACKER_MIN_ACL_ENTRIES	NACLBASE
#else
#ifdef	ACL_MIN_ENTRIES
#define	PACKER_MIN_ACL_ENTRIES	ACL_MIN_ENTRIES
#else
#define	PACKER_MIN_ACL_ENTRIES	4
#endif
#endif

acl_type_t packer_posix_acl_types_arr[3] = {
		ACL_TYPE_ACCESS,
		ACL_TYPE_DEFAULT,
#if PACKER_ACL_NUM_TYPES >= 3
		ACL_TYPE_DEFAULT_DIR,
#endif
};

Int32
packer_posix_acl_types(Int32 idx)
{
  return(packer_posix_acl_types_arr[idx]);
}
#endif

static Int32
write_acls(UChar * name, struct stat * statb, AarParams * params)
{
  Int32		i, k, n;
  UChar		sbuf[128];	/* space for > 3 x 64 Bit unsigned */

#ifdef	USE_POSIX_ACLS
{
  Uns32		acl_types;
  acl_t		acl;
  acl_entry_t	acl_entry;
  UChar		*acl_strs[3];
  ssize_t	aclstrlens[3];

  memset(acl_strs, 0, 3 * sizeof(acl_strs[0]));
  acl_types = 0;

  n = IS_DIRECTORY((*statb)) ? PACKER_ACL_NUM_TYPES : 1;
  for(i = 0; i < n; i++){
    acl = acl_get_file(name, packer_posix_acl_types(i));
    if(acl){
#ifndef	sgi
#ifndef	__osf__

	for(k = 0; acl_get_entry(acl, k ? ACL_NEXT_ENTRY : ACL_FIRST_ENTRY, &acl_entry); k++);

#else
	acl_first_entry(acl);			/* this is for OSF1 */
	for(k = 0; acl_get_entry(acl); k++);
	acl_first_entry(acl);
#endif
#else
	k = acl->acl_cnt;			/* and this is for IRIX */
#endif

	if(k > PACKER_MIN_ACL_ENTRIES){
	  acl_strs[i] =
#ifdef	HAVE_ACL_TO_SHORT_TEXT
			acl_to_short_text
#else
			acl_to_text
#endif
					(acl, &(aclstrlens[i]));
	  if(!acl_strs[i])
	    return(-ENOMEM);

	  acl_types |= (1 << i);
	}
        acl_free(acl);
    }
  }

  if(acl_types){
    sprintf(sbuf, "%d;%d", (int) POSIX_ACL, (int) acl_types);
    if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) )
	return(k);

    for(i = 0; i < n; i++){
      if(acl_strs[i]){
	sprintf(sbuf, ";%d;", (int) aclstrlens[i]);
	if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) )
	  return(k);
	if( (k = params->outputfunc(acl_strs[i], aclstrlens[i], params)) )
	  return(k);
#ifdef	HAVE_ACL_FREE_TEXT
	acl_free_text(acl_strs[i]);
#else
	acl_free(acl_strs[i]);
#endif
      }
    }
    if( (k = params->outputfunc(";.", 2, params)) )
	return(k);
  }
}
#else	/* defined(USE_POSIX_ACLS) */

#ifdef	USE_SOLARIS2_ACLS
{
  aclent_t	*acls;
  int		nacls;

  nacls = acl(name, GETACLCNT, 0, NULL);
  if(nacls > MIN_ACL_ENTRIES){
    acls = NEWP(aclent_t, nacls);
    if(!acls)
	return(-ENOMEM);

    nacls = acl(name, GETACL, nacls, acls);

    sprintf(sbuf, "%d;%d;", (int) SOLARIS2_ACL, (int) nacls);
    if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) )
	return(k);

    for(i = 0; i < nacls; i++){
	sprintf(sbuf, "%d;%d;%d;", (int) acls[i].a_type,
			(int) acls[i].a_id, (int) acls[i].a_perm);
	if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) ){
	  free(acls);
	  return(k);
	}
    }

    free(acls);
    if( (k = params->outputfunc(".", 1, params)) )
	return(k);
  }
}
#endif

#ifdef	USE_HPUX10_ACLS
{
  struct acl_entry	*acls;
  int			nacls;

  nacls = getacl(name, 0, NULL);
  if(nacls > NBASEENTRIES){
    acls = NEWP(struct acl_entry, nacls);
    if(!acls)
	return(-ENOMEM);

    nacls = getacl(name, nacls, acls);

    sprintf(sbuf, "%d;%d;", (int) HPUX10_ACL, (int) nacls);
    if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) )
	return(k);

    for(i = 0; i < nacls; i++){
	sprintf(sbuf, "%d;%d;%d;", (int) acls[i].uid,
			(int) acls[i].gid, (int) acls[i].mode);
	if( (k = params->outputfunc(sbuf, strlen(sbuf), params)) ){
	  free(acls);
	  return(k);
	}
    }

    free(acls);
    if( (k = params->outputfunc(".", 1, params)) )
	return(k);
  }
}
#endif
#endif /* defined(USE_POSIX_ACLS) */

  return(0);
}

static Int32
l_writeout(UChar * name, AarParams * params, Flag dont_save_as_hardlink)
{
  struct stat	statb, pdirstatb;
  Int32		i, n;
  Flag		compress;
  UChar		buf[BUFFERSIZ], sbuf[1000];
  UChar		verbosestr[MAXPATHLEN * 4 + 100], mtimestr[50];
  UChar		esc_name[MAXPATHLEN * 4 + 100];
  int		fd, pid, pst, ftype;
  UChar		*hlinkname;
  DIR		*dir;
  struct dirent	*entry;
  UChar		*cptr;
  FILE		*errfp;
  UChar		*unzipcmd;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  Int32		(*readfunc)(int, UChar *, Int32);
  UChar		filecontents = 0;
  struct utimbuf utim;
  Flag		cks;
  Uns32		crc32sum;	/* uninitialized OK */
  Flag		no_hardlink;

  cks = NO;

#ifdef	USE_ZLIB

  cks = params->check;

#else

  if(params->check){
    fprintf(stderr, T_("Warning: CRC32 checksumming not available (requires zlib).\n"));
    params->check = NO;
  }

#endif  

  if(! strncmp(name, CMDINOUTPREFIX, i = strlen(CMDINOUTPREFIX)))
    if(strstr(name + i, CMDINOUTSEP))
	return(write_cmdio(name, params));

  ofunc = params->outputfunc;
  readfunc = params->readfunc;

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

  errfp = params->errfp;

  if(! strncmp(name, FILECONTPREFIX, i = strlen(FILECONTPREFIX))){
    filecontents = 1;
    name += i;
  }
  if(! strncmp(name, FILECONTZPREFIX, i = strlen(FILECONTZPREFIX))){
    filecontents = 1 | ((unzipcmd[0]
#ifdef	USE_ZLIB
			|| params->bi_compr_level > 0
#endif
				) ? (1 << 1) : 0);
    name += i;
  }

  strcpy(esc_name, name);

  repl_esc_seq(name, ESCAPE_CHARACTER);

  i = lstat(name, &statb);

  if(i < 0){
    fprintf(errfp, T_("Error: cannot lstat \"%s\".\n"), name);
    params->vars.errnum |= EPACKNOENT;
    return(-ENOENT);
  }

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

  params->vars.num_fsentries++;

  SETZERO(utim);	/* on some architectures utim is more complex */
  utim.modtime = statb.st_mtime;
  utim.actime = statb.st_atime;

  params->vars.uid = statb.st_uid;
  params->vars.mtime = statb.st_mtime;

#ifndef	HAVE_LCHOWN

  if(IS_SYMLINK(statb)){		/* if the symlink is owned by root, */
    if(statb.st_uid == 0 && statb.st_gid == 0){		/* print UID of the */
	strcpy(verbosestr, name);			/* parent directory */
	cleanpath(verbosestr);
	cptr = FN_LASTDIRDELIM(verbosestr);
	if(cptr){
	  *(cptr + 1) = '\0';
	  if(!FN_ISROOTDIR(verbosestr))
	    *cptr = '\0';
	  i = stat(verbosestr, &pdirstatb);
	}
	else{
	  i = stat(".", &pdirstatb);
	}

	if(!i)
	  params->vars.uid = pdirstatb.st_uid;
    }
  }

#endif

  if(params->pre_verbosefunc && ! IS_DIRECTORY(statb))
    (*params->pre_verbosefunc)(NULL, params);

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

    if(i < 0){
	fprintf(errfp, T_("Error: cannot read symlink \"%s\".\n"), name);
	params->vars.errnum |= EPACKSYMLINK;
	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)) ){
	params->vars.errnum |= EPACKWRITE;
	return(i);
    }

    /* utime(name, &utim); */ /* Yes, this was a bug */

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

    return(0);
  }

  i = stat(name, &statb);

  if(i == -1){
    fprintf(errfp, T_("Error: cannot stat \"%s\".\n"), name);
    params->vars.errnum |= EPACKNOENT;

    return(-ENOENT);
  }

  no_hardlink = (!IS_HARDLINK(statb) || dont_save_as_hardlink);

  if(!IS_DIRECTORY(statb) && no_hardlink){
    i = write_acls(name, &statb, params);
    if(i){
	params->vars.errnum |= EPACKACLS;
	return(i);
    }
  }

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

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

    if(params->verbose && params->verbosefunc){
	sprintf(verbosestr, "%s\n", esc_name);
	params->verbosefunc(verbosestr, params);
    }
  } else if(IS_DIRECTORY(statb)){
    if(params->recursive){
      dir = opendir(name);

      if(!dir){
	fprintf(errfp, T_("Error: cannot read directory \"%s\".\n"), name);
	params->vars.errnum |= EPACKNOENT;

	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, esc_name);
	mk_esc_seq(cptr, ESCAPE_CHARACTER, buf + strlen(buf));

	l_writeout(buf, params, NO);
      }

      closedir(dir);
    }

    if(time_cond(statb, params)){	/* only, if time condition satisf */
      if(params->pre_verbosefunc)
	(*params->pre_verbosefunc)(NULL, params);

      i = write_acls(name, &statb, params);
      if(i){
	params->vars.errnum |= EPACKACLS;
	return(i);
      }

      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);
      ER__(ofunc(sbuf, strlen(sbuf), params), i);

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

    utime(name, &utim);
  }
  else if(! no_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);
      ER__(ofunc(sbuf, strlen(sbuf), params), i);

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

	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);
    ER__(ofunc(sbuf, strlen(sbuf), params), i)

    utime(name, &utim);

    if(params->verbose && params->verbosefunc){
	sprintf(verbosestr, "%s\n", esc_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);
    ER__(ofunc(sbuf, strlen(sbuf), params), i);

    utime(name, &utim);

    if(params->verbose && params->verbosefunc){
	sprintf(verbosestr, "%s\n", esc_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);
    ER__(ofunc(sbuf, strlen(sbuf), params), i);

    utime(name, &utim);

    if(params->verbose && params->verbosefunc){
	sprintf(verbosestr, "%s\n", esc_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);
    ER__(ofunc(sbuf, strlen(sbuf), params), i);

    utime(name, &utim);

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

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

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

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

    compress = ((unzipcmd[0]
#ifdef	USE_ZLIB
			|| params->bi_compr_level > 0
#endif
			) && statb.st_size > 0 && (!dont_compress));

    if(! compress){
	ER__(write_file_uncompressed(name, &statb, params), i);
    }
    else{
      UChar	compressbuf[0x1000 + 2];

      compression_done = NO;

      i = open_for_compress(&fd, name, params->zipcmd, &pid, params);
      if(!i){
	n = strlen(unzipcmd);

	cptr = "";

	if(params->bi_compr_level > 0){
	  if(n > 0){
	    n++;
	    cptr = ". ";
	  }
	  else{
	    cptr = ".";
	  }
	  n++;

	  cks = NO;	/* built-in compression already does checksumming */
	}
	
	ftype = REGFILE;

#ifdef	USE_ZLIB

	if(cks){
	  ftype = REGFILECKS;
	  crc32sum = crc32(0L, Z_NULL, 0);
	}

#endif

	sprintf(sbuf, "%d;%lu;%s;%d;%d;%d;%s;%d;%s%s;%d;", ftype,
			(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,
			(int) n, cptr, unzipcmd, 0);
	ER__(ofunc(sbuf, strlen(sbuf), params), i);

	compression_done = YES;

	do{
	  n = read_file(fd, compressbuf + 2, 0xfff, &params->vars.zfile, readfunc);

	  if(n < 0){
	    fprintf(params->errfp,
			T_("Error: Fault during read of compressed file `%s'.\n"), name);
	    params->vars.errnum |= EPACKCOMPR;
	    n = 0;
	    compression_done = NO;
	  }

#ifdef	USE_ZLIB

	  if(cks && n > 0)
	    crc32sum = crc32(crc32sum, compressbuf + 2, n);

#endif

	  UnsN_to_xref(compressbuf, n, 16);
	  ER__(ofunc(compressbuf, n + 2, params), i);

	  params->vars.sum_compr_filesizes += (Real64) (n + 2);
	} while(n == 0xfff);

	pst = 0;
	if(pid > 0){
	  waitpid_forced(pid, &pst, 0);
	  if( (i = WEXITSTATUS(pst)) ){
	    fprintf(params->errfp,
		T_("Error: Compression program returned bad status %d for `%s'.\n"),
				(int) i, name);

	    params->vars.errnum |= EPACKCOMPR;

	    compression_done = NO;
	  }
	}

	close(fd);

#ifdef	USE_ZLIB

	close_file_zip(&params->vars.zfile);

	if(cks){
	  Uns32_to_xref(compressbuf, crc32sum);
	  ER__(ofunc(compressbuf, 4, params), i);
	}

#endif

      }
      else{
	if(fd >= 0){
	  close(fd);
	}
	if(pid > 0){
	  kill(pid, SIGTERM);
	  waitpid_forced(pid, &pst, 0);
	}
      }

      if(!compression_done){
	ER__(write_file_uncompressed(name, &statb, params), i);
      }
    }

    buf[0] = '.';

    ER__(ofunc(buf, 1, params), i);

    utime(name, &utim);

    if(params->verbose && params->verbosefunc){
	sprintf(verbosestr, "%s\n", esc_name);
	params->verbosefunc(verbosestr, params);
    }
  }
#ifdef IS_DOOR
  else if(IS_DOOR(statb)) {
    /* 
     * This is a file special to the OS which will be created on demand.
     * Don't try to archive it, and don't throw any errors/warnings.
     */
    ;
  }  
#endif
  else{
    fprintf(errfp, T_("Error: \"%s\" is an illegal type of file.\n"), name);
    params->vars.errnum |= EPACKTYPE;

    return(0 /* WARNING */);
  }

  return(0);
}

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

  i = 0;

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

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

  return(i);
}

Int32
pack_default_output(UChar * buffer, Int32 num, AarParams * params)
{
  return(write_forced(params->outfd, buffer, num));
}

static Int32
write_filecont(
  UChar		*name,
  struct stat	*statb,
  AarParams	*params,
  Flag		compress)
{
  int		fd, pid, pst;
  Int32		i, num_read, n, ret = 0;
  UChar		sbuf[1000], mtimestr[50];
  UChar		buf[256];
  UChar		*unzipcmd, *cptr;
  FILE		*errfp;
  Uns32		readerrno;
  Int32		(*ofunc)(UChar *, Int32, AarParams *);
  Int32		(*readfunc)(int, UChar *, Int32);
  int		ftype;
  Flag		cks;
  Uns32		crc32sum;	/* uninitialized OK */

  cks = NO;
  ftype = FILECONTENTS;

#ifdef	USE_ZLIB

  cks = params->check;
  if(params->bi_compr_level > 0)
    cks = NO;		/* built-in compression already does checksumming */

  if(cks){
    ftype = FILECONTENTSCKS;
    crc32sum = crc32(0L, Z_NULL, 0);
  }

#endif  

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

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

  compress = (compress && (unzipcmd[0]
#ifdef	USE_ZLIB
			|| params->bi_compr_level > 0
#endif
			));

  if(compress){
    i = open_for_compress(&fd, name, params->zipcmd, &pid, params);

    readerrno |= EPACKCOMPR;
  }
  else{
    fd = open(name, O_RDONLY | O_BINARY);
    i = (fd < 0 ? -1 : 0);
    unzipcmd = "";
  }

#ifdef	USE_ZLIB

  params->vars.zfile.fd = fd;

#endif

  if(i){
    fprintf(errfp, T_("Error: cannot open file \"%s\".\n"), name);
    params->vars.errnum |= readerrno;

    return(-ENOENT);
  }

  n = strlen(unzipcmd);

  cptr = "";
  if(compress && params->bi_compr_level > 0){
    if(n > 0){
	n++;
	cptr = ". ";
    }
    else{
      cptr = ".";
    }
    n++;
  }

  sprintf(sbuf, "%d;%s;%d;%s;%d;%s%s;", ftype,
			(char *) time_t_to_intstr(statb->st_mtime, mtimestr),
			strlen(name), name, (int) n, cptr, unzipcmd);
  if( (i = ofunc(sbuf, strlen(sbuf), params)) ){
    ret = i;
    params->vars.errnum |= EPACKWRITE;
    GETOUT;
  }

  do{
    num_read = 0;
    forever{
      i = read_file(fd, buf + 1 + num_read, 255 - num_read, &params->vars.zfile, readfunc);

      if(i > 0){

#ifdef	USE_ZLIB

	if(cks)
	  crc32sum = crc32(crc32sum, buf + 1 + num_read, i);

#endif

	num_read += i;
      }

      if(i < 0){
	fprintf(params->errfp, T_("Error: Unexpected error reading contents of `%s'.\n"), name);
	params->vars.errnum |= readerrno;
      }

      if(i <= 0)
	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;
      params->vars.errnum |= EPACKWRITE;
      GETOUT;
    }
  } while(num_read == 255);

  if(cks)
    Uns32_to_xref(buf, crc32sum);

  buf[4] = '.';
  i = (cks ? 0 : 4);
  if( (i = ofunc(buf + i, 1 + 4 - i, params)) ){
    ret = i;
    params->vars.errnum |= EPACKWRITE;
    GETOUT;
  }

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

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

    if(ret)
	params->vars.errnum |= EPACKCOMPR;
  }

#ifdef	USE_ZLIB

  close_file_zip(&params->vars.zfile);

#endif

  return(ret);

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

  CLEANUP;
}

static UChar	*packer_errstrs[] = {
		TN_ ("General error"),
		TN_ ("Error writing packed data stream"),
		TN_ ("Error reading data while packing"),
		TN_ ("Error reading symbolic link while packing"),
		TN_ ("Error compressing data while packing"),
		TN_ ("Unknown filesystem entry type found while packing"),
		TN_ ("Size of file changed while packing it"),
		TN_ ("Error reading command output while packing"),
		TN_ ("Error while packing ACLs"),
		TN_ ("Given filesystem entry not found while packing"),
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		TN_ ("Required built-in uncompress not available"),
		TN_ ("Error uncompressing data during extract"),
		TN_ ("Error reading packed data stream during extract"),
		TN_ ("Format error in packed data stream"),
		TN_ ("Error creating base directory during extract"),
		TN_ ("Error setting filesystem entry properties"),
		TN_ ("Error creating filesystem entry during extract"),
		TN_ ("Error setting ACLs during extract"),
		TN_ ("Filesystem entry already exists, must not remove"),
		TN_ ("Error writing data to file during extract"),
		TN_ ("Error writing data to command input"),
		TN_ ("File to write contents to does not exist"),
		TN_ ("Wrong checksum reading packed data stream"),
		TN_ ("No writing permission"),
		NULL,
		};

UChar **
packer_strerrors(UChar ** estrs, Uns32 errnum)
{
  Int32		i, j, n;

  n = sizeof(packer_errstrs) / sizeof(packer_errstrs[0]);

  if(!estrs){
    estrs = NEWP(UChar *, n + 1);
    if(!estrs)
	return(NULL);
  }

  for(i = j = 0; i < n; i++){
    if(errnum & (1 << i)){
	if( (estrs[j] = T_(packer_errstrs[i])) )
	  j++;
    }
  }

  estrs[j] = NULL;

  return(estrs);
}
