/***********************************************************************
*                                                                      *
*               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 block io support
 */

#include "pax.h"

#define show(s,n)		fmtquote(s,NiL,NiL,(n)>32?32:(n),0)

#if _sys_mtio
#include <ast_tty.h>
#include <sys/mtio.h>
#if _sys_ioctl
#include <sys/ioctl.h>
#endif
#endif
#ifdef MTIOCTOP
#if defined(MTIOBSF) && !defined(MTBSF)
#define MTBSF	MTIOBSF
#endif
#if defined(MTIOBSR) && !defined(MTBSR)
#define MTBSR	MTIOBSR
#endif
#if defined(MTIOEOM) && !defined(MTEOM)
#define MTEOM	MTIOEOM
#endif
#if defined(MTIOFSF) && !defined(MTFSF)
#define MTFSF	MTIOFSF
#endif
#if defined(MTIOWEOF) && !defined(MTWEOF)
#define MTWEOF	MTIOWEOF
#endif
#if !defined(MTBSF) || !defined(MTBSR) || !defined(MTWEOF) || defined(__hppa)/*hppa-compiler-signal-10*/
#undef	MTIOCTOP
#endif
#endif

#define CVT(a,b,c,m,t) \
	do \
	{ \
		if (c) \
			switch ((a)->convert[SECTION(a)].on) \
			{ \
			case 1: \
				if (state.forceconvert || SECTION(a) != SECTION_DATA || TEXT(b,c,t)) \
					(a)->convert[SECTION(a)].on = 2; \
				else \
				{ \
					(a)->convert[SECTION(a)].on = 0; \
					break; \
				} \
			case 2: \
				ccmapstr(m, b, c); \
				break; \
			} \
		if ((a)->swapio) \
			swapmem((a)->swapio, b, b, c); \
	} while (0)

#define TEXT(b,c,t)	text(t,(unsigned char*)b,c)

#define CONVERT(a,b,c)	CVT(a,b,c,(a)->convert[SECTION(a)].f2t,(a)->convert[SECTION(a)].f2a)
#define REVERT(a,b,c)	CVT(a,b,c,(a)->convert[SECTION(a)].t2f,(a)->convert[SECTION(a)].t2a)

/*
 * return 1 if b is text data in f charset
 */

static const unsigned char	ascii_text[] =
{
	0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};

static int
text(unsigned char* map, register unsigned char* b, ssize_t n)
{
	register unsigned char*	e;
	register int		c;

	if (n > 256)
		n = 256;
	e = b + n;
	while (b < e)
	{
		c = *b++;
		c = ccmapchr(map, c);
		if (!ascii_text[c])
			return 0;
	}
	return 1;
}

#if 0 && DEBUG

/*
 * --blok=i	input is BLOK file
 * --blok=o	output file is BLOK file
 */

static int
blokread(register Archive_t* ap, char* buf, int n)
{
	register int		i;
	register int		j;
	char			c;

	if (!ap->io->blokflag)
	{
		ap->io->blokeof = 0;
		ap->io->blokflag = 1;
		if ((i = read(ap->io->fd, buf, ap->io->blok ? 4 : n)) < 4 || !strneq(buf, "\002\014\017\013", 4))
		{
			if (ap->io->blok)
				error(3, "%s: input archive is not a BLOK file", ap->name);
			return i;
		}
		if (i > 4 && lseek(ap->io->fd, (off_t)4, SEEK_SET) != 4)
			error(3, "%s: cannot seek on input archive BLOK file -- use --blok=i", ap->name);
		ap->io->blok = 1;
	}
	if (ap->io->blok)
	{
		j = 0;
		do
		{
			if ((i = read(ap->io->fd, &c, 1)) < 1)
			{
				if (!i)
					return 0;
				if (!ap->io->blokeof)
				{
					ap->io->blokeof = 1;
					return 0;
				}
				return -1;
			}
			j <<= 7;
			j |= c & 0177;
		} while (c & 0200);
		if (j > 0)
		{
			if (j > n)
				error(2, "%s: blokread buffer overflow (%d>%d)", ap->name, j, n);
			if ((i = read(ap->io->fd, buf, j)) != j)
				error(2, "%s: blokread blocking error", ap->name);
		}
		else
			i = 0;
	}
	else
		i = read(ap->io->fd, buf, n);
	return i;
}

static int
blokwrite(register Archive_t* ap, char* buf, int n)
{
	register char*	s;
	register int	i;
	register int	j;
	char		blk[9];

	if (ap->io->blok)
	{
		s = blk;
		if (!ap->io->blokflag)
		{
			ap->io->blokflag = 1;
			*s++ = 002;
			*s++ = 014;
			*s++ = 017;
			*s++ = 013;
		}
		i = 0;
		if (j = (n >> 21) & 0177)
		{
			*s++ = j | 0200;
			i = 1;
		}
		if ((j = (n >> 14) & 0177) || i)
		{
			*s++ = j | 0200;
			i = 1;
		}
		if ((j = (n >> 7) & 0177) || i)
		{
			*s++ = j | 0200;
			i = 1;
		}
		*s++ = n & 0177;
		j = s - blk;
		if ((i = write(ap->io->fd, blk, j)) != j)
			error(ERROR_SYSTEM|3, "%s: blokwrite count write error (%d!=%d)", ap->name, i, j);
		if (n <= 0)
			i = n;
		else if ((i = write(ap->io->fd, buf, n)) != n)
			error(ERROR_SYSTEM|3, "%s: blokwrite data write error (%d!=%d", ap->name, i, n);
	}
	else
		i = write(ap->io->fd, buf, n);
	return i;
}

#define read(f,b,n)	blokread(f,b,n)
#define write(f,b,n)	blokwrite(f,b,n)

#endif

#if 0

static ssize_t
ewrite(int f, void* b, size_t n)
{
	static int	count = 1;

	if (!count)
	{
		sfprintf(sfstderr, "AHA ENOSPC\n");
		errno = ENOSPC;
		return -1;
	}
	count--;
	return write(f, b, n);
}

#undef	write
#define write(f,b,n)	ewrite(f,b,n)

#endif

/*
 * initialize buffered io
 */

void
binit(register Archive_t* ap)
{
	unsigned long	n;
	unsigned long	u;

	if (ap->delta)
		ap->delta->hdr = ap->delta->hdrbuf;
	ap->io->buffersize = state.buffersize;
	n = 2 * state.buffersize;
	if (ap->io->mode == O_RDONLY)
		u = MAXUNREAD;
	else if (!(ap->format->flags & OUT))
		error(3, "%s: archive format not supported on output" , ap->format->name);
	else
		u = 0;
	if (!(ap->io->buffer = newof(0, char, n, u)))
		error(3, "%s: cannot allocate buffer", ap->name);
	ap->io->next = ap->io->last = ap->io->buffer + u;
}

/*
 * skip files on tape fd
 */

int
bskip(register Archive_t* ap)
{
	long		c;
	int		skip = ap->io->skip;
#ifdef MTIOCTOP
	struct mtop	mt;
#ifdef MTEOM
	int		mteom = 1;
#endif
#ifdef MTFSF
	int		mtfsf = 1;
#endif
#endif

	if (ap->io->mode != O_RDONLY)
	{
		ap->io->next = ap->io->last = ap->io->buffer + MAXUNREAD;
		ap->io->eof = 0;
	}
	while (skip)
	{
#ifdef MTIOCTOP
#ifdef MTEOM
		if (skip < 0 && mteom)
		{
			mt.mt_op = MTEOM;
			mt.mt_count = 1;
			if (ioctl(ap->io->fd, MTIOCTOP, &mt) >= 0)
			{
				if (ap->io->mode != O_RDONLY)
					ap->io->eof = 1;
				break;
			}
			mteom = 0;
		}
#endif
#ifdef MTFSF
		if (mtfsf)
		{
			mt.mt_op = MTFSF;
			mt.mt_count = 1;
			if (ioctl(ap->io->fd, MTIOCTOP, &mt) >= 0)
			{
				skip--;
				continue;
			}
			if (errno != ENOTTY)
			{
				if (ap->io->mode != O_RDONLY)
					ap->io->eof = 1;
				break;
			}
			mtfsf = 0;
		}
#endif
#endif
		while ((c = read(ap->io->fd, state.tmp.buffer, state.tmp.buffersize)) > 0);
		if (c < 0)
		{
			if (ap->io->mode != O_RDONLY)
				ap->io->eof = 1;
			break;
		}
		skip--;
	}
	return 0;
}

/*
 * fill input buffer at ap->io->last
 * if must!=0 then EOF causes query for next input volume file
 */

static int
bfill(register Archive_t* ap, int must)
{
	register int	c;

	if (ap->io->eof)
		return -1;
	if (ap->io->skip)
		ap->io->skip = bskip(ap);
	while ((c = read(ap->io->fd, ap->io->last, ap->io->buffersize)) <= 0)
	{
		if (must)
			newio(ap, c, 0);
		else
		{
			ap->io->eof = 1;
			return -1;
		}
	}
	message((-8, "read(%s,%d): %s", ap->name, c, show(ap->io->last, c)));
	ap->io->eof = 0;
	ap->io->last += c;
	return 0;
}

/*
 * sum and convert a bread()/bget() chunk
 */

static void
chunk(register Archive_t* ap, char* t, char* f, register size_t n, char* o)
{
	if (ap->sum > 0)
	{
		if (ap->flags & SUM)
			FNVSUM(ap->memsum, f, n);
		else
		{
			ap->memsum = memsum(f, n, ap->memsum);
			ap->old.memsum = omemsum(f, n, ap->old.memsum);
		}
	}
	if (o)
	{
		if (t != f)
			memcpy(t, f, n);
		CONVERT(ap, t, n);
	}
}

/*
 * buffered char input
 * at least n chars required, m chars max
 * if b is 0 then n chars are skipped
 * if must!=0 then EOF causes query for next input volume file
 */

off_t
bread(register Archive_t* ap, void* ob, off_t n, off_t m, int must)
{
	register char*		s = (char*)ob;
	register ssize_t	c;
	char*			b;
	register off_t		r;
	register off_t		z;

	if (ap->io->eof)
		return -1;
	if (m <= 0)
		m = n;
	b = s;
	r = m;
	if (ap->io->blocked)
	{
		if (!s)
			b = s = state.tmp.buffer;
		while ((c = read(ap->io->fd, s, r > ap->io->buffersize ? ap->io->buffersize : r)) <= 0)
		{
			if (must)
				newio(ap, c, 0);
			else if (ap->io->empty)
			{
				ap->io->eof = 1;
				return -1;
			}
			else
			{
				if (c < 0)
					ap->io->eof = 1;
				else
					ap->io->empty = 1;
				break;
			}
		}
		ap->io->empty = 0;
		chunk(ap, s, s, c, ob);
		s += c;
		r -= c;
	}
	else
		for (;;)
		{
			if ((c = ap->io->last - ap->io->next) < r)
			{
				if (c > 0)
				{
					if (ob)
						memcpy(s, ap->io->next, c);
					chunk(ap, s, ap->io->next, c, ob);
					s += c;
					r -= c;
				}
				ap->io->next = ap->io->last = ap->io->buffer + MAXUNREAD;
				if (!ob && ap->sum <= 0 && ap->io->seekable && (z = r / BUFFERSIZE) && lseek(ap->io->fd, z *= BUFFERSIZE, SEEK_CUR) >= 0)
				{
					s += z;
					r -= z;
				}
				if (bfill(ap, must) < 0)
					break;
			}
			else
			{
				chunk(ap, s, ap->io->next, r, ob);
				ap->io->next += r;
				s += r;
				r = 0;
				break;
			}
		}
	ap->io->count += (r = m - r);
	if (r < n)
	{
		if (ob && r)
		{
			bunread(ap, b, r);
			return 0;
		}
		return -1;
	}
#if DEBUG
	if (ob)
		message((-7, "bread(%s,%I*d@%I*d): %s", ap->name, sizeof(r), r, sizeof(ap->io->count), ap->io->count, show(b, r)));
	else
		message((-7, "bread(%s) skip(%I*d@%I*d)", ap->name, sizeof(r), r, sizeof(ap->io->count), ap->io->count));
#endif
	return r;
}

/*
 * pushback for next bread()
 */

void
bunread(register Archive_t* ap, void* b, register int n)
{
	ap->io->eof = 0;
	ap->io->count -= n;
	if ((ap->io->next -= n) < ap->io->buffer + MAXUNREAD)
	{
		if (ap->io->next < ap->io->buffer)
			error(PANIC, "bunread(%s,%d): too much pushback", ap->name, n);
		if (b)
			memcpy(ap->io->next, b, n);
		REVERT(ap, ap->io->next, n);
	}
	message((-7, "bunread(%s,%d@%I*d): %s", ap->name, n, sizeof(ap->io->count), ap->io->count, show(ap->io->next, n)));
}

/*
 * O_RDONLY bread() n chars and return a pointer to the char buffer
 * O_WRONLY return output buffer pointer and available size
 */

char*
bget(register Archive_t* ap, register off_t n, off_t* p)
{
	register char*	b;
	char*		t;
	size_t		i;
	size_t		j;
	size_t		m;
	int		must;

	if (ap->io->mode != O_RDONLY)
	{
		if (p)
			*p = ap->io->last - ap->io->next;
		return ap->io->next;
	}
	if (n < 0)
	{
		n = -n;
		must = 0;
	}
	else if (n > 0)
		must = 1;
	else
	{
		if (!ap->io->eof && ap->io->seekable)
		{
			if (ap->io->last > ap->io->next)
				n = ap->io->last - ap->io->next;
			else if ((n = ap->io->size - (ap->io->offset + ap->io->count)) < 0)
				n = 0;
			else if (n > ap->io->buffersize)
				n = ap->io->buffersize;
		}
		if (p)
			*p = n;
		return n ? ap->io->next : (char*)0;
	}
	if (n > ap->io->buffersize)
	{
		i = ap->io->next - ap->io->buffer;
		j = ap->io->last - ap->io->buffer;
		m = roundof(n, PAX_DEFBUFFER * PAX_BLOCK);
		message((-8, "bget(%s,%I*d,%d): reallocate %u=>%d", ap->name, sizeof(n), n, must, ap->io->buffersize, m));
		if (!(b = newof(ap->io->buffer, char, 2 * m, MAXUNREAD)))
			error(3, "%s: cannot reallocate buffer", ap->name);
		ap->io->buffersize = m;
		if (b != ap->io->buffer)
		{
			ap->io->buffer = b;
			ap->io->next = b + i;
			ap->io->last = b + j;
		}
	}
	if (p)
		*p = n;
	b = ap->io->next;
	ap->io->next += n;
	while (ap->io->next > ap->io->last)
	{
		if (ap->io->last > ap->io->buffer + MAXUNREAD + ap->io->buffersize)
		{
                        i = ap->io->last - b;
			j = roundof(i, IOALIGN) - i;
                        t = ap->io->next = ap->io->buffer + MAXUNREAD + j;
                        ap->io->last = t + i;
                        if (m = b - t)
			{
                        	while (i > m)
                        	{
                                	message((-8, "bget(%s,%I*d,%d) overlapping memcpy n=%I*d i=%d m=%d next=%p last=%p", ap->name, sizeof(n), n, must, sizeof(n), n, i, m, ap->io->next + n, ap->io->last));
                                	memcpy(t, b, m);
                                	t += m;
                                	b += m;
                                	i -= m;
                        	}
				message((-8, "bget(%s,%I*d,%d): slide %u align %u", ap->name, sizeof(n), n, must, i, j));
                        	memcpy(t, b, i);
			}
			b = ap->io->next;
			ap->io->next += n;
		}
		if (bfill(ap, must) < 0)
			return 0;
	}
	chunk(ap, b, b, n, b);
	ap->io->count += n;
	message((-7, "bget(%s,%I*d@%I*d,%d): %s", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count, must, show(b, n)));
	return b;
}

/*
 * back up input to bsave()'d position and prime output buffer
 */

void
backup(register Archive_t* ap)
{
	register off_t	n;
	register off_t	m;
#ifdef MTIOCTOP
	struct mtop	mt;
#endif

	if (ap->format->backup)
		(*ap->format->backup)(&state, ap);
	else
	{
		m = ap->io->next - (ap->io->buffer + MAXUNREAD);
		if ((n = ap->io->count - m) > state.backup.count)
		{
			message((-1, "backup(%s): reread %I*d", ap->name, sizeof(n), n + m));
			m = state.backup.last - (state.backup.buffer + MAXUNREAD);
			if (lseek(ap->io->fd, (off_t)(-(n + m)), SEEK_CUR) == -1)
			{
#ifdef MTIOCTOP
				mt.mt_op = MTBSR;
				mt.mt_count = 2;
				if (ioctl(ap->io->fd, MTIOCTOP, &mt))
					goto bad;
#else
				goto bad;
#endif
			}
			if (read(ap->io->fd, ap->io->buffer + MAXUNREAD, m) != m)
				goto bad;
		}
		else
			m = ap->io->last - (ap->io->buffer + MAXUNREAD);
		message((-1, "backup(%s): %I*d", ap->name, sizeof(m), m));
		if ((m = lseek(ap->io->fd, -m, SEEK_CUR)) == -1)
		{
#ifdef MTIOCTOP
			mt.mt_op = MTBSR;
			mt.mt_count = 1;
			if (ioctl(ap->io->fd, MTIOCTOP, &mt))
				goto bad;
#else
			goto bad;
#endif
		}
		if (state.backup.next < state.backup.last)
			bwrite(ap, ap->io->buffer + MAXUNREAD, state.backup.next - (state.backup.buffer + MAXUNREAD));
	}
	return;
 bad:
	error(3, "%s: cannot position %s archive for append", ap->name, ap->format->name);
}

/*
 * flush buffered input
 */

void
bflushin(register Archive_t* ap, int hard)
{
	ap->io->count += ap->io->last - ap->io->next;
	ap->io->next = ap->io->last = ap->io->buffer + MAXUNREAD;
	if (hard && !ap->io->eof)
	{
		while (read(ap->io->fd, ap->io->next, ap->io->buffersize) > 0);
		ap->io->eof = 1;
	}
}

/*
 * buffered seek
 */

off_t
bseek(register Archive_t* ap, off_t pos, int op, int hard)
{
	off_t	l;
	off_t	u;

	message((-8, "bseek(%s,%I*d,%d,%d)", ap->name, sizeof(pos), pos, op, hard));
	if (hard)
	{
		if (op == SEEK_CUR)
			return -1;
	}
	else
	{
		l = ap->io->next - (ap->io->buffer + MAXUNREAD);
		u = ap->io->last - ap->io->next;
		if (op == SEEK_CUR)
		{
			if ((pos + ap->io->count) < 0)
				return -1;
			if (-pos <= l && pos <= u)
			{
				ap->io->next += pos;
				return ap->io->count += pos;
			}
			pos += ap->io->count;
			op = SEEK_SET;
		}
		else if (op != SEEK_SET || pos < 0)
			return -1;
		else if (-pos <= (l + ap->io->count) && pos <= (u + ap->io->count))
		{
			ap->io->next += (pos - ap->io->count);
			return ap->io->count = pos;
		}
	}
	ap->io->next = ap->io->last = ap->io->buffer + MAXUNREAD;
	message((-8, "lseek(%s,%I*d,%d)", ap->name, sizeof(pos), pos + ap->io->offset, op));
	if ((u = lseek(ap->io->fd, ap->io->offset + pos, op)) < 0 && (op != SEEK_SET || (u = pos - ap->io->count) < 0 || u > 0 && bread(ap, NiL, u, u, 1) != u || !(u += ap->io->count + ap->io->offset)))
		return -1;
	ap->io->empty = 0;
	ap->io->eof = 0;
	return ap->io->count = u - ap->io->offset;
}

/*
 * flush buffered output
 */

void
bflushout(register Archive_t* ap)
{
	register int	n;
	register int	c;

	if (n = ap->io->next - ap->io->buffer)
	{
		ap->io->next = ap->io->buffer;
		while ((c = write(ap->io->fd, ap->io->next, n)) != n)
		{
			if (c <= 0)
				newio(ap, c, n);
			else
			{
				ap->io->next += c;
				n -= c;
			}
		}
		ap->io->next = ap->io->buffer;
	}
}

/*
 * buffered output
 */

void
bwrite(register Archive_t* ap, void* ab, register off_t n)
{
	register char*	b = (char*)ab;
	register long	c;
	long		an;

	if (!ap->raw)
	{
		CONVERT(ap, b, n);
		an = ap->convert[SECTION(ap)].on ? n : 0;
		if (ap->sum > 0)
			ap->memsum = memsum(b, n, ap->memsum);
		if (state.checksum.sum && SECTION(ap) == SECTION_DATA)
			sumblock(state.checksum.sum, b, n);
	}
	if (ap->io->skip)
		ap->io->skip = bskip(ap);
	if (state.maxout && ap->io->count >= state.maxout)
	{
		bflushout(ap);
		newio(ap, 0, 0);
	}
	ap->io->count += n;
	if (ap->io->blocked)
	{
#if DEBUG
		if (n > 0)
			message((-7, "bwrite(%s,%I*d@%I*d): %s", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count + n, show(b, n)));
		else
			message((-7, "bwrite(%s,%I*d@%I*d):", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count + n));
#endif
		while ((c = write(ap->io->fd, b, n)) != n)
		{
			if (n <= 0)
			{
#ifdef MTIOCTOP
				{
					struct mtop	mt;

					mt.mt_op = MTWEOF;
					mt.mt_count = 1;
					if (ioctl(ap->io->fd, MTIOCTOP, &mt) >= 0)
						break;
				}
#endif
				error(3, "%s: cannot write tape EOF marks", ap->name);
			}
			if (c <= 0)
				newio(ap, c, n);
			else if ((n -= c) > 0)
				b += c;
			else
				break;
		}
	}
	else
	{
#if DEBUG
		if (n > 0)
			message((-7, "bwrite(%s,%I*d@%I*d): %s", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count + n, show(b, n)));
		else
			message((-7, "bwrite(%s,%I*d@%I*d):", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count + n));
#endif
		for (;;)
		{
			if ((c = ap->io->buffer + state.blocksize - ap->io->next) <= n)
			{
				if (c)
				{
					memcpy(ap->io->next, b, c);
					n -= c;
					b += c;
				}
				ap->io->next = ap->io->buffer;
				while ((c = write(ap->io->fd, ap->io->next, state.blocksize)) != state.blocksize)
				{
					if (c <= 0)
						newio(ap, c, n);
					else
					{
						memcpy(state.tmp.buffer, ap->io->buffer + c, state.blocksize - c);
						memcpy(ap->io->buffer, state.tmp.buffer, state.blocksize - c);
						ap->io->next = ap->io->buffer + state.blocksize - c;
						break;
					}
				}
				message((-8, "write(%s,%ld): %s", ap->name, c, show(ap->io->buffer, c)));
			}
			else
			{
				memcpy(ap->io->next, b, n);
				ap->io->next += n;
				break;
			}
		}
	}
	if (!ap->raw)
		REVERT(ap, ab, an);
}

/*
 * bwrite() n chars that have been placed in the
 * buffer returned by a previous bget()
 */

void
bput(register Archive_t* ap, register off_t n)
{
	ap->io->count += n;
	message((-7, "bput(%s,%I*d@%I*d): %s", ap->name, sizeof(n), n, sizeof(ap->io->count), ap->io->count, show(ap->io->next, n)));
	CONVERT(ap, ap->io->next, n);
	if (ap->sum > 0)
		ap->memsum = memsum(ap->io->next, n, ap->memsum);
	if (state.checksum.sum && SECTION(ap) == SECTION_DATA)
		sumblock(state.checksum.sum, ap->io->next, n);
	if ((ap->io->next += n) > ap->io->buffer + state.blocksize)
	{
		n = (ap->io->next - ap->io->buffer) - state.blocksize;
		ap->io->count -= n;

		/*
		 * flush out the buffer and slide over the remains
		 */

		ap->raw++;
		bwrite(ap, ap->io->next = ap->io->buffer + state.blocksize, n);
		ap->raw--;
	}
}

/*
 * return recommended io block size for fd
 * 0 returned for no recommendation
 */

long
bblock(int fd)
{
#ifdef MTIOCGETBLKINFO
	struct mtblkinfo	mt;

	return ioctl(fd, MTIOCGETBLKINFO, &mt) < 0 ? 0 : mt.recblksz;
#else
	return 0;
#endif
}

static struct
{
	char*		path;
	struct stat*	st;
} dev;

/*
 * find path name in /dev for <dev.st->st_dev,dev.st->st_ino>
 * called by ftwalk()
 */

static int
devpath(register Ftw_t* ftw)
{
	if (ftw->info == FTW_F && ftw->statb.st_rdev == dev.st->st_rdev && S_ISCHR(ftw->statb.st_mode) == S_ISCHR(dev.st->st_mode))
	{
		message((-1, "device name is %s", ftw->path));
		dev.path = strdup(ftw->path);
		return 1;
	}
	return 0;
}

/*
 * initilize tty file pointers for interactive prompting
 */

void
interactive(void)
{
	int	fd;

	if (!state.rtty)
	{
		fd = dup(2);
		if (!(state.rtty = sfopen(NiL, "/dev/tty", "r")) || !(state.wtty = sfopen(NiL, "/dev/tty", "w")))
			error(ERROR_SYSTEM|3, "cannot prompt for interactive input");
		sfsetbuf(state.rtty, NiL, 0);
		sfsetbuf(state.wtty, NiL, 0);
		if (fd >= 0)
			close(fd);
	}
}

/*
 * check for new input or output stream
 * c is the io count causing the newio()
 * n is the pending buffered io count
 */

void
newio(register Archive_t* ap, int c, int n)
{
	register char*	s;
	register char*	rw;
	char*		file;
	char*		io;
	char*		t;
	off_t		z;
	int		i;
	struct stat	st;

	Sfio_t*		cp = 0;
	Sfio_t*		pp = 0;
	int		oerrno = errno;

	if (!ap->part)
		ap->part++;
	if (ap->io->mode != O_RDONLY)
	{
		rw = "write";
		io = "output";
		ap->io->offset += ap->io->count - n;
		ap->io->count = n;
		z = ap->io->offset + ap->io->count;
		if (ap->format->putepilogue && (*ap->format->putepilogue)(&state, ap) < 0)
			return;
	}
	else
	{
		rw = "read";
		io = "input";
		z = ap->io->offset + ap->io->count;
	}
	if (fstat(ap->io->fd, &st) < 0)
		error(ERROR_SYSTEM|3, "%s: cannot stat %s", ap->name, io);
	errno = oerrno;
	switch (X_ITYPE(modex(st.st_mode)))
	{
	case X_IFBLK:
	case X_IFCHR:
		file = 0;
		break;
	default:
		if (ap->io->mode != O_RDONLY)
			switch (c < 0 ? errno : 0)
			{
			case 0:
#ifdef EFBIG
			case EFBIG:
#endif
#ifdef EDQUOT
			case EDQUOT:
#endif
				file = "file";
				break;
			default:
				error(ERROR_SYSTEM|3, "%s: %s %s error -- cannot recover", ap->name, io, rw);
				break;
			}
		else
			file = "file";
		break;
	}
	i = (eomprompt && *eomprompt == '!' && !*(eomprompt + 1)) ? 3 : 1;
	switch (c < 0 ? errno : 0)
	{
	case 0:
	case ENOSPC:
	case ENXIO:
		error(i, "%s: end of %s medium", ap->name, io);
		break;
	default:
		error(ERROR_SYSTEM|i, "%s: %s %s error", ap->name, io, rw);
		break;
	}
	if (ap->total == z)
		error(1, "%s: no %s on part %d", ap->name, io, ap->part--);
	else
		ap->total = z;
	if (!file && (ap->name == definput || ap->name == defoutput))
	{
		dev.path = 0;
		dev.st = &st;
		ftwalk("/dev", devpath, 0, NiL);
		ap->name = dev.path;
	}
	close(ap->io->fd);
	if (eomprompt && *eomprompt == '!')
		file = 0;
	if (file && ap->name != definput && ap->name != defoutput && strmatch(ap->name, "*.+([0-9])") && (s = strrchr(ap->name, '.')) && (int)strtol(++s, NiL, 10) == ap->part)
	{
		/*
		 * the parts will be ap->name in sequence
		 * the first part realloc the name with
		 * enough sequence space
		 */

		if (ap->part == 1)
		{
			c = s - ap->name;
			if (!(t = newof(0, char, s - ap->name, 16)))
				nospace();
			strcpy(t, ap->name);
			s = (ap->name = t) + c;
		}
		sfsprintf(s, 16, "%d", ap->part + 1);
		if ((ap->io->fd = open(ap->name, ap->io->mode|O_BINARY, st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))) >= 0)
			goto nextpart;
		error(ERROR_SYSTEM|1, "%s: cannot %s", ap->name, rw);
	}
	if (state.test & 0000040)
		file = 0;
	else if (file || ap->name == definput || ap->name == defoutput)
	{
		for (;;)
		{
			interactive();
			sfputc(state.wtty, CC_bel);
			sfprintf(state.wtty, "Enter part %d %s %s name: ", ap->part + 1, io, file ? file : "device");
			if (!(s = sfgetr(state.rtty, '\n', 1)))
			{
				sfputc(state.wtty, '\n');
				finish(2);
			}
			if (*s)
			{
				if (!file)
					break;
				if ((ap->io->fd = open(s, ap->io->mode|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) >= 0)
					break;
				error(ERROR_SYSTEM|1, "%s: cannot open", s);
			}
		}
		ap->name = strdup(s);
	}
	if (!file)
	{
		for (;;)
		{
			if (eomprompt && *eomprompt == '!')
			{

				if (!cp && !(cp = sfstropen()))
					nospace();
				sfprintf(cp, "%s %s %d", eomprompt + 1, rw, ap->part + 1);
				if (ap->name)
					sfprintf(cp, " %s", ap->name);
				s = (pp = sfpopen(pp, sfstruse(cp), "r")) ? sfgetr(pp, '\n', 1) : (char*)0;
			}
			else
			{
				interactive();
				sfputc(state.wtty, CC_bel);
				if (eomprompt)
					sfprintf(state.wtty, eomprompt, ap->part + 1);
				if (!(s = sfgetr(state.rtty, '\n', 1)))
					sfputc(state.wtty, '\n');
			}
			if (!s)
				finish(2);
			if (*s == '!')
			{
				static char*	last;

				if (*++s)
				{
					if (last)
						free(last);
					last = strdup(s);
				}
				else
					s = last;
				if (!s)
					error(1, "no previous command");
				else if (n = system(s))
					error(1, "exit status %d", n);
			}
			else if (file = *s ? s : ap->name)
			{
				if ((ap->io->fd = open(file, ap->io->mode|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) >= 0)
					break;
				if (!strchr(file, '/'))
				{
					oerrno = errno;
					file = strtape(file, &t);
					if (!*t && (ap->io->fd = open(file, ap->io->mode|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) >= 0)
						break;
					errno = oerrno;
				}
				error(ERROR_SYSTEM|1, "cannot %s %s", rw, *s ? s : ap->name);
			}
			else
				error(1, "pathname required");
		}
		if (ap->name != file)
			ap->name = strdup(file);
		if (cp)
			sfclose(cp);
		if (pp)
			sfclose(pp);
	nextpart:
		ap->part++;
		error(1, "continuing %s %d %s on %s", ap->part == ap->volume + 1 ? "volume" : "part", ap->part, io, ap->name);
	}
	else
		ap->part++;
	if (ap->format->putprologue)
		(*ap->format->putprologue)(&state, ap);
}
