/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1987-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Bell Laboratories
 *
 * pax archive format support
 */

#include "format.h"

Format_t*		formats = pax_first_format;

/*
 * read archive prologue before files are copied
 * compression and input archive formats are identified here
 */

int
getprologue(register Archive_t* ap)
{
	register Format_t*	fp;
	register File_t*	f = &ap->file;
	off_t			skipped;
	int			n;
	unsigned char		buf[MAXUNREAD];
	unsigned char		cvt[MAXUNREAD];

	message((-6, "getprologue() volume=%d eof=%d", ap->volume, ap->io->eof));
	if (ap->io->eof || state.volume && (ap->io->mode & O_ACCMODE) != O_RDONLY)
		return 0;
	state.volume[0] = 0;
	ap->entry = 0;
	ap->format = 0;
	ap->swapio = 0;
	ap->io->offset += ap->io->count;
	ap->io->count = 0;
	ap->section = SECTION_CONTROL;
	convert(ap, SECTION_CONTROL, CC_NATIVE, CC_NATIVE);
	ap->sum++;
	f->name = 0;
	f->record.format = 0;
	f->skip = 0;
	ap->checkdelta = !ap->delta || !(ap->delta->format->flags & PSEUDO);
	f->delta.base = 0;
	f->uncompressed = 0;
	f->delta.checksum = 0;
	f->delta.index = 0;
	if (ap->io->mode != O_RDONLY)
		bsave(ap);

	/*
	 * first check if input is compressed
	 */

	if ((n = bread(ap, (char*)buf, (off_t)0, (off_t)sizeof(buf), 0)) <= MINID && !bcount(ap))
	{
		if (n && ap->volume <= 0)
			goto unknown;
		return 0;
	}
	bunread(ap, buf, n);
	if (CC_NATIVE != CC_ASCII)
		ccmapm(cvt, buf, sizeof(cvt), CC_ASCII, CC_NATIVE);
	message((-2, "identify format"));
	if (ap->volume <= 0 && !ap->compress)
	{
		fp = 0;
		while (fp = nextformat(fp))
			if ((fp->flags & COMPRESS) && (*fp->getprologue)(&state, fp, ap, f, buf, sizeof(buf)) > 0)
			{
				Compress_format_t*	cp;
				Proc_t*			proc;
				long			ops[3];
				char*			cmd[3];

				message((-1, "%s compression", fp->name));
				ap->compress = fp;
				cp = (Compress_format_t*)fp->data;
				if (bseek(ap, (off_t)0, SEEK_SET, 1))
					error(3, "%s: %s input must be seekable", ap->name, ap->compress->name);
				undoable(ap, ap->compress);
				if (state.meter.on && ap == state.in && ap->uncompressed)
					state.meter.size = ap->uncompressed;
				cmd[0] = cp->undo[0];
				cmd[1] = cp->undo[1];
				cmd[2] = 0;
				ops[0] = PROC_FD_DUP(ap->io->fd, 0, PROC_FD_PARENT|PROC_FD_CHILD);
				if (ap->parent && !state.ordered)
				{
					if ((n = open(state.tmp.file, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, S_IRUSR)) < 0)
						error(ERROR_SYSTEM|3, "%s: cannot create %s base temporary file %s", ap->name, cp->undo[0], state.tmp.file);
					ops[1] = PROC_FD_DUP(n, 1, PROC_FD_PARENT|PROC_FD_CHILD);
					ops[2] = 0;
					proc = procopen(*cmd, cmd, NiL, ops, 0);
				}
				else
				{
					ops[1] = 0;
					proc = procopen(*cmd, cmd, NiL, ops, PROC_READ);
				}
				if (!proc)
					error(3, "%s: cannot execute %s filter", ap->name, cp->undo[0]);
				if (ap->parent && !state.ordered)
				{
					if (n = procclose(proc))
						error(3, "%s: %s filter exit code %d", ap->name, cp->undo[0], n);
					if ((ap->io->fd = open(state.tmp.file, O_RDONLY|O_BINARY)) < 0)
						error(ERROR_SYSTEM|3, "%s: cannot read %s base temporary file %s", ap->name, cp->undo[0], state.tmp.file);
					if (remove(state.tmp.file))
						error(ERROR_SYSTEM|1, "%s: cannot remove %s base temporary file %s", ap->name, cp->undo[0], state.tmp.file);
				}
				else
				{
					List_t*	p;

					ap->io->fd = proc->rfd;
					if (!(p = newof(0, List_t, 1, 0)))
						nospace();
					p->item = (void*)proc;
					p->next = state.proc;
					state.proc = p;
				}
				if ((n = bread(ap, (char*)buf, (off_t)0, (off_t)sizeof(buf), 0)) <= MINID && !bcount(ap))
					return 0;
				bunread(ap, buf, n);
				break;
			}
	}

	/*
	 * now identify the format
	 */

	if (!(fp = ap->expected))
	{
		skipped = 0;
		ap->entry = 1;
		for (;;)
		{
			fp = 0;
			while (fp = nextformat(fp))
				if ((fp->flags & ARCHIVE) && fp->getprologue)
				{
					message((-2, "check %s", fp->name));
					convert(ap, SECTION_CONTROL, (fp->flags & CONV) ? CC_NATIVE : CC_ASCII, CC_NATIVE);
					if ((n = (*fp->getprologue)(&state, fp, ap, f, ((fp->flags & CONV) || CC_NATIVE == CC_ASCII) ? buf : cvt, sizeof(buf))) < 0)
						return 0;
					if (n > 0)
						break;
					if (ap->data)
					{
						error(ERROR_PANIC|4, "%s: data!=0 on failed getprologue()", fp->name);
						ap->data = 0;
					}
					ap->checksum = ap->old.checksum = ap->memsum = ap->old.memsum = 0;
				}
			if (fp)
				break;
			convert(ap, SECTION_CONTROL, CC_NATIVE, CC_NATIVE);
			if (!state.keepgoing || ap->io->eof || bread(ap, buf, (off_t)0, (off_t)1, 0) < 1)
			{
			unknown:
				if (ap->expected)
					error(3, "%s: unknown input format -- %s expected", ap->name, ap->expected->name);
				if (ap->volume)
				{
					error(1, "%s: junk data after volume %d", ap->name, ap->volume);
					return 0;
				}
				error(3, "%s: unknown input format", ap->name);
			}
			skipped++;
		}
		ap->entry = 0;
		if (skipped)
			error(1, "%s: %I*d byte%s skipped before identifying %s format archive", ap->name, sizeof(skipped), skipped, skipped == 1 ? "" : "s", fp->name);
	}
	else if (!(fp->flags & ARCHIVE) || !fp->getprologue  || (*fp->getprologue)(&state, fp, ap, f, buf, sizeof(buf)) < 0)
		error(3, "%s: %s: archive format mismatch", ap->name, fp->name);
	if (!ap->format)
		ap->format = fp;
	if (!ap->type)
		ap->type = ap->format->name;
	message((-1, "%s format", ap->type));
	if ((state.operation & IN) && !state.list && !(ap->format->flags & IN))
		error(3, "%s: %s read not implemented", ap->name, ap->type);
	if ((state.operation & OUT) && !(ap->format->flags & OUT))
		error(3, "%s: %s write not implemented", ap->name, ap->type);
	ap->flags = state.operation | (ap->format->flags & ~(IN|OUT));
	ap->sum--;
	ap->volume++;
	if (ap->peek && (ap->checkdelta || ap->delta) && deltacheck(ap, f))
		ap->peek = 0;
	if (state.summary && state.verbose && ap->volume > 0)
	{
		if (ap->io->blok)
			sfprintf(sfstderr, "BLOK ");
		if (ap->parent)
			sfprintf(sfstderr, "%s base %s", ap->parent->name, ap->name);
		else
		{
			sfprintf(sfstderr, "%s", ap->name);
			if (ap->volume > 1)
				sfprintf(sfstderr, " volume %d", ap->volume);
		}
		if (state.volume[0])
			sfprintf(sfstderr, " label %s", state.volume);
		sfprintf(sfstderr, " in");
		if (ap->package)
			sfprintf(sfstderr, " %s", ap->package);
		if (ap->compress)
			sfprintf(sfstderr, " %s", ap->compress->name);
		if (ap->delta && ap->delta->format)
			sfprintf(sfstderr, " %s", ap->delta->format->name);
		sfprintf(sfstderr, " %s format", ap->type);
		if (ap->swapio)
			sfprintf(sfstderr, " %s swapped", "unix\0nuxi\0ixun\0xinu" + 5 * ap->swapio);
		sfprintf(sfstderr, "\n");
	}
	if (ap->volume > 1)
	{
		if (ap->delta)
		{
			if (state.operation == (IN|OUT) || !(ap->delta->format->flags & DELTA))
				error(3, "%s: %s archive cannot be multi-volume", ap->name, ap->parent ? "base" : "delta");
			ap->delta = 0;
		}

		/*
		 * no hard links between volumes
		 */

		hashfree(state.linktab);
		if (!(state.linktab = hashalloc(NiL, HASH_set, HASH_ALLOCATE, HASH_namesize, sizeof(Fileid_t), HASH_name, "links", 0)))
			error(3, "cannot re-allocate hard link table");
	}
	return 1;
}

/*
 * set pseudo file header+trailer info
 */

void
setinfo(register Archive_t* ap, register File_t* f)
{
	long	n;

	if (ap->delta)
	{
		if (ap->delta->format->variant != DELTA_IGNORE && ap->entry > 1 && f->st->st_mtime)
		{
			if ((n = f->st->st_mtime - ap->delta->index) < 0)
				error(3, "%s: corrupt archive: %d extra file%s", ap->name, -n, n == -1 ? "" : "s");
			else if (n > 0)
				error(3, "%s: corrupt archive: %d missing file%s", ap->name, n, n == 1 ? "" : "s");
		}
		ap->delta->epilogue = 1;
	}
}

/*
 * output pseudo file header+trailer
 */

void
putinfo(register Archive_t* ap, char* file, unsigned long mtime, unsigned long checksum)
{
	register File_t*	f = &ap->file;
	Sfio_t*			np = 0;
	Delta_format_t*		dp;

	if (!file)
	{
		np = sfstropen();
		if (!ap->delta || ap->delta->format->variant == DELTA_88)
			sfprintf(np, "DELTA");
		else
		{
			sfprintf(np, "%c%s%c%c%c%s", INFO_SEP, ID, INFO_SEP, ap->delta->compress ? TYPE_COMPRESS : TYPE_DELTA, INFO_SEP, (dp = (Delta_format_t*)ap->delta->format->data) ? dp->variant : "");
			if (state.ordered)
				sfprintf(np, "%c%c", INFO_SEP, INFO_ORDERED);
		}
		sfprintf(np, "%c%c%c", INFO_SEP, INFO_SEP, INFO_SEP);
		file = sfstruse(np);
	}
	initfile(ap, f, f->st, file, X_IFREG);
	f->skip = 1;
	f->st->st_mtime = mtime;
	f->st->st_uid = DELTA_LO(checksum);
	f->st->st_gid = DELTA_HI(checksum);
	putheader(ap, f);
	puttrailer(ap, f);
	if (np)
		sfstrclose(np);
}

/*
 * write archive prologue before files are copied
 */

void
putprologue(register Archive_t* ap)
{
	message((-6, "putprologue()"));
	ap->section = SECTION_CONTROL;
	if (ap->delta && ap->delta->format->variant == DELTA_88)
		ap->checksum = ap->old.checksum;
	if (!(ap->format->flags & CONV))
	{
		convert(ap, SECTION_CONTROL, CC_NATIVE, CC_ASCII);
		if (!ap->convert[0].on)
			convert(ap, SECTION_DATA, CC_NATIVE, CC_NATIVE);
	}
	if ((!ap->format->putprologue || (*ap->format->putprologue)(&state, ap) >= 0) && !(ap->format->flags & DELTAINFO) && ap->delta && !(ap->delta->format->flags & PSEUDO))
	{
		if (ap->delta->base)
			putinfo(ap, NiL, ap->delta->base->size, ap->delta->base->checksum);
		else
			putinfo(ap, NiL, 0, 0);
	}
}

/*
 * read archive epilogue after all files have been copied
 */

void
getepilogue(register Archive_t* ap)
{
	register char*	s;
	register off_t	n;
	register int	i;
	unsigned int	z;
	int		x;
	char		buf[BLOCKSIZE];

	message((-6, "getepilogue()"));
	ap->section = SECTION_CONTROL;
	if (ap->delta && ap->delta->epilogue < 0)
		error(3, "%s: corrupt archive: missing epilogue", ap->name);
	if (ap->io->mode != O_RDONLY)
		backup(ap);
	else
	{
		if (!ap->format->getepilogue || !(*ap->format->getepilogue)(&state, ap))
		{
			/*
			 * check for more volumes
			 * volumes begin on BLOCKSIZE boundaries
			 * separated by null byte filler
			 */

			if (ap->io->keep)
			{
				bskip(ap);
				if (ap->io->eof)
					ap->io->keep = 0;
				else if (ap->io->keep > 0)
					ap->io->keep--;
				goto done;
			}
			x = 0;
			z = 0;
			i = 0;
			if (!(n = roundof(ap->io->count, BLOCKSIZE) - ap->io->count) || bread(ap, buf, (off_t)0, (off_t)n, 0) > 0)
				do
				{
					for (s = buf; s < buf + n && !*s; s++);
					z += s - buf;
					if (z >= BLOCKSIZE)
						x = 1;
					if (s < buf + n)
					{
						if (n == BLOCKSIZE)
						{
							if (!x && ap->format->event && (ap->format->events & PAX_EVENT_SKIP_JUNK))
							{
								if ((*ap->format->event)(&state, ap, NiL, buf, PAX_EVENT_SKIP_JUNK) > 0)
									continue;
								if (i)
									error(1, "%s: %d junk block%s after volume %d", ap->name, i, i == 1 ? "" : "s", ap->volume);
							}
							bunread(ap, buf, BLOCKSIZE);
							goto done;
						}
						if (ap->volume > 1)
							error(1, "junk data after volume %d", ap->volume);
						break;
					}
					n = BLOCKSIZE;
					i++;
				} while (bread(ap, buf, (off_t)0, n, 0) > 0);
			bflushin(ap, 0);
		}
	done:
		if (ap->format->done)
			(*ap->format->done)(&state, ap);
		ap->swapio = 0;
	}
}

/*
 * write archive epilogue after files have been copied
 */

void
putepilogue(register Archive_t* ap)
{
	register ssize_t	n;
	register off_t		boundary;

	message((-6, "putepilogue()"));
	if (state.install.path)
	{
		if (sfclose(state.install.sp))
			error(ERROR_SYSTEM|2, "%s: install temporary write error", state.install.path);
		state.filter.line = 2;
		state.filter.name = state.install.name;
		state.filter.command = "";
		ftwalk(state.install.path, copyout, state.ftwflags, NiL);
		state.filter.line = 0;
	}
	if (state.checksum.path)
	{
		if (sfclose(state.checksum.sp))
			error(ERROR_SYSTEM|2, "%s: checksum temporary write error", state.checksum.path);
		sumclose(state.checksum.sum);
		state.checksum.sum = 0;
		state.filter.line = 2;
		state.filter.name = state.checksum.name;
		state.filter.command = "";
		ftwalk(state.checksum.path, copyout, state.ftwflags, NiL);
		state.filter.line = 0;
	}
	ap->section = SECTION_CONTROL;
	if (ap->selected > state.selected)
	{
		state.selected = ap->selected;
		if (ap->delta && (ap->delta->format->flags & DELTA))
		{
			if (ap->format->event && (ap->format->events & PAX_EVENT_DELTA_EXTEND))
				(*ap->format->event)(&state, ap, NiL, NiL, PAX_EVENT_DELTA_EXTEND);
			else
				putinfo(ap, NiL, ap->delta->index + 1, 0);
		}
		if (!ap->format->putepilogue || (boundary = (*ap->format->putepilogue)(&state, ap)) <= 0)
			boundary = ap->io->count;
		if (n = ((ap->io->count > boundary) ? roundof(ap->io->count, boundary) : boundary) - ap->io->count)
		{
			memzero(state.tmp.buffer, n);
			bwrite(ap, state.tmp.buffer, n);
		}
		bflushout(ap);
		ap->volume++;
	}
	else
	{
		ap->io->count = ap->io->offset = 0;
		ap->io->next = ap->io->buffer;
	}
	if (ap->format->done)
		(*ap->format->done)(&state, ap);
}

/*
 * get key [ug]id value
 */

static void
getkeyid(Archive_t* ap, File_t* f, int index, uid_t* ip, int d)
{
	register Option_t*	op;

	op = &options[index];
	if (op->level < 7)
	{
		if (op->entry == ap->entry)
			*ip = op->temp.number;
		else if (op->level > 0 && op->perm.string)
			*ip = op->perm.number;
	}
	else if (op->level >= 8)
		*ip = d;
}

/*
 * get key name value
 */

static void
getkeyname(Archive_t* ap, File_t* f, int index, char** sp, uid_t* ip, int d)
{
	register Option_t*	op;

	op = &options[index];
	if (op->level < 7)
	{
		if (op->entry == ap->entry)
			*sp = op->temp.string;
		else if (op->level > 0 && op->perm.string)
			*sp = op->perm.string;
	}
	else if (ip && op->level >= 8)
	{
		*sp = 0;
		*ip = d;
	}
}

/*
 * get key size value
 */

static void
getkeysize(Archive_t* ap, File_t* f, int index, off_t* zp)
{
	register Option_t*	op;

	NoP(f);
	op = &options[index];
	if (op->level < 7)
	{
		if (op->entry == ap->entry)
			*zp = strtoll(op->temp.string, NiL, 10);
		else if (op->level > 0)
			*zp = strtoll(op->perm.string, NiL, 10);
	}
}

/*
 * get key time value
 */

static void
getkeytime(Archive_t* ap, File_t* f, int index)
{
	register Option_t*	op;
	register Value_t*	vp;
	Tv_t			tv;

	NoP(f);
	op = &options[index];
	if (op->level < 7)
	{
		if (op->entry == ap->entry)
			vp = &op->temp;
		else if (op->level > 0)
			vp = &op->perm;
		else
			return;
		tv.tv_sec = vp->number;
		tv.tv_nsec = vp->fraction;
		switch (index)
		{
		case OPT_atime:
			tvsetatime(&tv, f->st);
			break;
		case OPT_mtime:
			tvsetmtime(&tv, f->st);
			break;
		case OPT_ctime:
			tvsetctime(&tv, f->st);
			break;
		}
	}
	else if (op->level >= 8)
	{
		tvgettime(&tv);
		vp = &op->perm;
		vp->number = tv.tv_sec;
		vp->fraction = tv.tv_nsec;
	}
}

/*
 * read next archive entry header
 */

int
getheader(register Archive_t* ap, register File_t* f)
{
	register char*	s;
	long		i;

	message((-6, "getheader()"));
	ap->section = SECTION_CONTROL;
	ap->sum++;
	ap->entry++;
	if (ap->io->mode != O_RDONLY)
		bsave(ap);
	if (!ap->peek)
	{
		f->delta.base = 0;
		f->delta.checksum = 0;
		f->delta.index = 0;
		f->uncompressed = 0;
	}
 again:
	do
	{
		if (ap->peek)
			ap->peek = 0;
		else
		{
			f->name = 0;
			f->record.format = 0;
			f->skip = 0;
			i = f->st->st_ino;
			memset(f->st, 0, sizeof(*f->st));
			f->st->st_ino = i;
			f->st->st_nlink = 1;
			if ((*ap->format->getheader)(&state, ap, f) <= 0)
			{
				ap->entry--;
				return 0;
			}
		}
		if (!f->name)
			error(3, "%s: %s format entry %d.%d name not set", ap->name, ap->type, ap->volume, ap->entry);
	} while ((ap->checkdelta || ap->delta) && deltacheck(ap, f));
	ap->entries++;
	getkeysize(ap, f, OPT_size, &f->st->st_size);
	getkeytime(ap, f, OPT_atime);
	getkeytime(ap, f, OPT_ctime);
	getkeytime(ap, f, OPT_mtime);
	getkeyid(ap, f, OPT_gid, &f->st->st_gid, state.gid);
	getkeyname(ap, f, OPT_gname, &f->gidname, &f->st->st_gid, state.gid);
	getkeyname(ap, f, OPT_path, &f->name, NiL, 0);
	getkeyname(ap, f, OPT_linkpath, &f->linkpath, NiL, 0);
	getkeyid(ap, f, OPT_uid, &f->st->st_uid, state.uid);
	getkeyname(ap, f, OPT_uname, &f->uidname, &f->st->st_uid, state.uid);
	if (!state.list)
		setidnames(f);
	if (f->name != ap->stash.head.string)
		f->name = stash(&ap->stash.head, f->name, 0);
	if (ap->flags & DOS)
		undos(f);
	f->type = X_ITYPE(f->st->st_mode);
	s = pathcanon(f->name, 0);
	if (s > f->name + 1 && *--s == '/')
	{
		*s = 0;
		if ((ap->format->flags & SLASHDIR) && f->type == X_IFREG)
		{
			f->st->st_mode &= ~X_IFREG;
			f->st->st_mode |= (f->type = X_IFDIR);
			f->datasize = f->st->st_size;
		}
	}
	f->path = stash(&ap->path.name, f->name, 0);
	f->name = map(f->name);
	f->namesize = strlen(f->name) + 1;
	if (f->linkpath)
	{
		pathcanon(f->linkpath, 0);
		if (!(state.ftwflags & FTW_PHYSICAL))
			f->linkpath = map(f->linkpath);
		f->linkpathsize = strlen(f->linkpath) + 1;
	}
	else
		f->linkpathsize = 0;
	f->perm = modei(f->st->st_mode);
	f->ro = ropath(f->name);
	getdeltaheader(ap, f);
#if DEBUG
	if (error_info.trace)
	{
		s = &state.tmp.buffer[0];
		if (f->record.format)
			sfsprintf(s, state.tmp.buffersize, " [%c,%d,%d]", f->record.format, state.blocksize, state.record.size);
		else
			*s = 0;
		message((-1, "archive=%s path=%s name=%s entry=%d.%d size=%I*u uncompressed=%I*u delta=%c%s", ap->name, f->path, f->name, ap->volume, ap->entry, sizeof(f->st->st_size), f->st->st_size, sizeof(f->uncompressed), f->uncompressed, f->delta.op ? f->delta.op : DELTA_nop, s));
	}
#endif
	if (ap->sum > 0)
	{
		if (ap->format->flags & SUM)
			ap->memsum = FNV_INIT;
		else if (!ap->delta || !ap->delta->trailer)
			ap->memsum = 0;
		ap->old.memsum = 0;
	}
	ap->section = SECTION_DATA;
	ap->convert[ap->section].on = ap->convert[ap->section].f2t != 0;
	return 1;
}

/*
 * write next archive entry header
 */

int
putheader(register Archive_t* ap, register File_t* f)
{
	register int	n;

	message((-6, "putheader()"));
	if (!f->extended)
	{
		setdeltaheader(ap, f);
		ap->entry++;
	}
	ap->section = SECTION_CONTROL;
	if ((n = (*ap->format->putheader)(&state, ap, f)) < 0)
		return -1;
	if (!n)
	{
		if (!ap->incomplete)
			return 0;
		ap->incomplete = 0;
		if ((ap->io->count + f->st->st_size) > state.maxout)
		{
			error(2, "%s: too large to fit in one volume", f->name);
			return -1;
		}
		state.complete = 0;
		putepilogue(ap);
		newio(ap, 0, 0);
		putprologue(ap);
		state.complete = 1;
		if ((n = (*ap->format->putheader)(&state, ap, f)) <= 0)
			return n;
	}
	putdeltaheader(ap, f);
	if (state.checksum.sum)
		suminit(state.checksum.sum);
	ap->section = SECTION_DATA;
	ap->convert[ap->section].on = ap->convert[ap->section].f2t != 0;
	if (state.install.sp && !f->extended)
	{
		n = 0;
		if (f->st->st_gid != state.gid && ((f->st->st_mode & S_ISGID) || (f->st->st_mode & S_IRGRP) && !(f->st->st_mode & S_IROTH) || (f->st->st_mode & S_IXGRP) && !(f->st->st_mode & S_IXOTH)))
		{
			sfprintf(state.install.sp, "chgrp %s %s\n", fmtgid(f->st->st_gid), f->name);
			n = 1;
		}
		if (f->st->st_uid != state.uid && ((f->st->st_mode & S_ISUID) || (f->st->st_mode & S_IRUSR) && !(f->st->st_mode & (S_IRGRP|S_IROTH)) || (f->st->st_mode & S_IXUSR) && !(f->st->st_mode & (S_IXGRP|S_IXOTH))))
		{
			sfprintf(state.install.sp, "chown %s %s\n", fmtuid(f->st->st_uid), f->name);
			n = 1;
		}
		if (n || (f->st->st_mode & (S_ISUID|S_ISGID)))
			sfprintf(state.install.sp, "chmod %04o %s\n", modex(f->st->st_mode & S_IPERM), f->name);
	}
	return 1;
}

/*
 * read entry trailer
 */

void
gettrailer(register Archive_t* ap, File_t* f)
{
	register off_t	n;

	message((-6, "gettrailer()"));
	NoP(f);
	ap->section = SECTION_CONTROL;
	if (ap->sum-- > 0)
	{
		ap->checksum ^= ap->memsum;
		ap->old.checksum ^= ap->old.memsum;
	}
	if (ap->format->gettrailer)
		(*ap->format->gettrailer)(&state, ap, f);
	getdeltatrailer(ap, f);
	if ((n = ap->format->align) && (n = roundof(ap->io->count, n) - ap->io->count))
		bread(ap, state.tmp.buffer, (off_t)0, n, 1);
	if (!(ap->format->flags & SUM) && ap->sum >= 0)
	{
		ap->memsum = 0;
		ap->old.memsum = 0;
	}
}

/*
 * write entry trailer
 */

void
puttrailer(register Archive_t* ap, register File_t* f)
{
	register int	n;

	message((-6, "puttrailer()"));
	if (state.checksum.sum && !f->extended)
	{
		sumdone(state.checksum.sum);
		if (f->link)
		{
			if (!f->link->checksum)
			{
				sumprint(state.checksum.sum, state.tmp.str, 0);
				if (!(f->link->checksum = strdup(sfstruse(state.tmp.str))))
					nospace();
			}
			sfputr(state.checksum.sp, f->link->checksum, -1);
		}
		else
			sumprint(state.checksum.sum, state.checksum.sp, 0);
		sfprintf(state.checksum.sp, " %04o %s %s %s\n"
			, modex(f->st->st_mode & S_IPERM)
			, (f->st->st_uid != state.uid && ((f->st->st_mode & S_ISUID) || (f->st->st_mode & S_IRUSR) && !(f->st->st_mode & (S_IRGRP|S_IROTH)) || (f->st->st_mode & S_IXUSR) && !(f->st->st_mode & (S_IXGRP|S_IXOTH)))) ? fmtuid(f->st->st_uid) : "-"
			, (f->st->st_gid != state.gid && ((f->st->st_mode & S_ISGID) || (f->st->st_mode & S_IRGRP) && !(f->st->st_mode & S_IROTH) || (f->st->st_mode & S_IXGRP) && !(f->st->st_mode & S_IXOTH))) ? fmtgid(f->st->st_gid) : "-"
			, f->name
			);
	}
	ap->section = SECTION_CONTROL;
	putdeltatrailer(ap, f);
	if (ap->format->puttrailer)
		(*ap->format->puttrailer)(&state, ap, f);
	if ((n = ap->format->align) && (n = roundof(ap->io->count, n) - ap->io->count))
	{
		memzero(state.tmp.buffer, n);
		bwrite(ap, state.tmp.buffer, n);
	}
	listentry(f);
}
