/***********************************************************************
*                                                                      *
*               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

/*
 * pax slt format for ansi and ibm labeled tapes
 */

#include "format.h"

#include <tm.h>

#define ANSI		1
#define IBM		2

#define SLT_ID		"slt"
#define LABEL_MAX	2048
#define HDR_SIZE	80
#define NAME_SIZE	17
#define VARHDR_SIZE	9

typedef struct Slt_s
{
	unsigned char*	mapin;		/* input map			*/
	unsigned char*	mapout;		/* output map			*/
	char		id[NAME_SIZE+1]; /* name id			*/
	char		volume[64];	/* volume id			*/
	char		format[7];	/* format id			*/
	char		implementation[8];/* implementation id		*/
	char		owner[15];	/* owner id			*/
	char		standards[20];	/* standards id			*/
	int		done;		/* label read done		*/
	int		ibm;		/* ibm format			*/
	int		peek;		/* buf (size peek) already read	*/
	int		section;	/* inside section		*/
	int		sequence;	/* sequence number		*/
	int		vol;		/* emit volume prologue		*/
	char		buf[LABEL_MAX]; /* header buffer		*/
	char		last[5];	/* last label			*/
} Slt_t;

/*
 * get label header number
 */

static long
getlabnum(register char* p, int byte, int width, int base)
{
	register char*	e;
	register int	c;
	long		n;

	p += byte - 1;
	c = *(e = p + width);
	*e = 0;
	n = strtol(p, NiL, base);
	*e = c;
	return n;
}

/*
 * get label header string
 */

static char*
getlabstr(register char* p, int byte, int width, register char* s)
{

	register char*	e;
	char*		v;

	v = s;
	p += byte - 1;
	e = p + width;
	while (p < e && (*s = *p++) != ' ')
		s++;
	*s = 0;
	return v;
}

/*
 * return length of next label
 * variable length labels have label number > 3 and Vnnnn at position 5
 * where nnnn is the decimal length of the entire label
 * nnnn may be < HDR_SIZE but label block must be >= HDR_SIZE
 * 0 returned at end of label group
 */

static int
getlabel(Pax_t* pax, register Archive_t* ap, register File_t* f)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	register int	c;
	register int	n;

	if (c = slt->peek)
	{
		slt->peek = 0;
		return c;
	}
	if (slt->done || (c = paxread(pax, ap, slt->buf, (off_t)HDR_SIZE, (off_t)LABEL_MAX, 0)) < HDR_SIZE)
		return *slt->last = slt->done = c = 0;
	if (slt->buf[4] == 'V' && ((n = getlabnum(slt->buf, 4, 1, 10)) < 1 || n > 3) && (n = getlabnum(slt->buf, 6, 4, 10)) != c)
	{
		if ((c = n - c) > 0)
		{
			if (ap->io->blocked || paxread(pax, ap, slt->buf + HDR_SIZE, (off_t)0, (off_t)c, 1) != c)
			{
				c = HDR_SIZE;
				error(2, "%s: %-*.*s: variable length label record too short", f->name, c, c, slt->buf);
			}
			else
				c = n;
		}
		else if (n <= VARHDR_SIZE)
			c = VARHDR_SIZE;
		else
			c = n;
	}
	if (!ap->io->blocked && !*slt->last && slt->buf[3] == '2' && (strneq(slt->buf, "HDR", 3) || strneq(slt->buf, "EOF", 3) || strneq(slt->buf, "EOV", 3)))
		getlabstr(slt->buf, 26, 4, slt->last);
	if (*slt->last && strneq(slt->buf, slt->last, 4))
		slt->done = 1;
	message((-4, "label: %-*.*s", c, c, slt->buf));
	return c;
}

/*
 * output file HDR/EOF/EOV labels
 */

static void
putlabels(Pax_t* pax, register Archive_t* ap, register File_t* f, char* type)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	struct tm*	tm;

	switch (*type)
	{
	case 'E':
		paxwrite(pax, ap, slt->buf, 0);
		break;
	case 'H':
		slt->sequence++;
		break;
	}
	tm = gmtime(&f->st->st_mtime);
	sfsprintf(slt->buf, sizeof(slt->buf), "%s1%-17.17s000001%04d%04d000100 %02d%03d 00000 %06d%-6.6sD%-7.7s       ", type, f->id, slt->section, slt->sequence, tm->tm_year, tm->tm_yday, f->record.blocks, slt->format, slt->implementation);
	paxwrite(pax, ap, slt->buf, HDR_SIZE);
	sfsprintf(slt->buf, sizeof(slt->buf), "%s2%c%05d%05d%010d%s%c                     00                            ", type, state.record.format, state.blocksize, state.record.size, f->st->st_size, type, '2');
	paxwrite(pax, ap, slt->buf, HDR_SIZE);
	paxwrite(pax, ap, slt->buf, 0);
	if (streq(type, "EOV"))
	{
		slt->section++;
		slt->sequence = 0;
	}
	else
		slt->section = 1;
}

static int
slt_getprologue(Pax_t* pax, Format_t* fp, register Archive_t* ap, File_t* f, unsigned char* buf, size_t size)
{
	register Slt_t*		slt;
	char*			s;
	char*			t;
	int			lab;
	long			n;
	off_t			x;
	char			hdr[HDR_SIZE + 1];

	static const char	key[] = "VOL1";

	if (size < HDR_SIZE)
		return 0;
	memcpy(hdr, buf, HDR_SIZE);
	if (fp->flags & CONV)
	{
		ccmapstr(state.map.e2n, hdr, HDR_SIZE);
		if (!strneq(hdr, key, sizeof(key) - 1))
			return 0;
		ccmapstr(state.map.e2n, hdr, HDR_SIZE);
		convert(ap, SECTION_CONTROL, CC_NATIVE, CC_EBCDIC);
		if (!ap->convert[0].on)
			convert(ap, SECTION_DATA, CC_NATIVE, CC_EBCDIC);
	}
	else
	{
		ccmapstr(state.map.a2n, hdr, HDR_SIZE);
		if (!strneq(hdr, key, sizeof(key) - 1))
			return 0;
		convert(ap, SECTION_CONTROL, CC_NATIVE, CC_ASCII);
		if (!ap->convert[0].on)
			convert(ap, SECTION_DATA, CC_NATIVE, CC_ASCII);
	}
	if (!(slt = newof(0, Slt_t, 1, 0)))
	{
		nospace();
		return -1;
	}
	ap->data = slt;
	getlabstr(hdr, 5, 6, state.volume);
	getlabstr(hdr, 25, 6, slt->format);
	getlabstr(hdr, 31, 7, slt->implementation);
	getlabstr(hdr, 38, 14, slt->owner);
	paxget(pax, ap, 0, &x);
	ap->io->blocked = !x;
	if (ap->checkdelta)
	{
		if (!(lab = getlabel(pax, ap, f)))
			return 0;
		if (strneq(slt->buf, "UVL1", 4) && strneq(slt->buf + 5, ID, IDLEN))
		{
			ap->checkdelta = 0;
			s = slt->buf + 10;
			f->st->st_mtime = getlabnum(slt->buf, 14, 10, 10);
			n = getlabnum(slt->buf, 24, 10, 10);
			f->st->st_uid = DELTA_LO(n);
			f->st->st_gid = DELTA_HI(n);
			if (t = strchr(s, ' '))
				*t = 0;
			deltaset(ap, s);
		}
		else
			slt->peek = lab;
	}
	return 1;
}

static int
slt_done(Pax_t* pax, register Archive_t* ap)
{
	if (ap->data)
	{
		free(ap->data);
		ap->data = 0;
	}
	return 0;
}

static int
slt_getheader(Pax_t* pax, register Archive_t* ap, register File_t* f, int wfd)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	register char*	s;
	register int	i;
	register off_t	n;
	int		lab;
	int		type;

 again:
	if (!(lab = getlabel(pax, ap, f)))
		return 0;
	f->st->st_dev = 0;
	f->st->st_ino = 0;
	f->st->st_mode = X_IFREG|X_IRUSR|X_IWUSR|X_IRGRP|X_IROTH;
	f->st->st_uid = state.uid;
	f->st->st_gid = state.gid;
	f->st->st_nlink = 1;
	IDEVICE(f->st, 0);
	f->st->st_size = 0;
	f->linktype = NOLINK;
	f->linkpath = 0;
	f->uidname = 0;
	f->gidname = 0;
	type = 0;
	do
	{
		if (strneq(slt->buf, "HDR", 3))
		{
			if (getlabnum(slt->buf, 4, 1, 10) != ++type)
				error(3, "%s format HDR label out of sequence", ap->format->name);
			if (type == 1)
			{
				s = f->name = paxstash(pax, &ap->stash.head, NiL, NAME_SIZE + 3);
				for (i = 4; i <= NAME_SIZE + 3; i++)
				{
					if (slt->buf[i] == ' ')
					{
						if (i >= NAME_SIZE + 3 || slt->buf[i + 1] == ' ')
							break;
						*s++ = '.';
					}
					else
						*s++ = isupper(slt->buf[i]) ? tolower(slt->buf[i]) : slt->buf[i];
				}
				if ((n = getlabnum(slt->buf, 40, 2, 10)) > 0 && n < 99)
					sfsprintf(s, 3, ".%02d", n);
				else
					*s = 0;
				f->record.section = getlabnum(slt->buf, 28, 4, 10);
				getlabstr(slt->buf, 5, NAME_SIZE, f->id = slt->id);
				getlabstr(slt->buf, 61, 6, slt->format);
				getlabstr(slt->buf, 67, 7, slt->implementation);
#if SAVESET
				if (streq(slt->format, SAVESET_ID) && streq(slt->implementation, SAVESET_IMPL))
					ap->format = SAVESET;
#endif
				f->st->st_mtime = 0;
				if (n = getlabnum(slt->buf, 43, 2, 10))
				{
					if (slt->buf[41] == '0') n += 100;
					if ((i = getlabnum(slt->buf, 45, 3, 10)) >= 0 && i <= 365)
					{
						f->st->st_mtime = i;
						while (n-- > 70) f->st->st_mtime += ((n % 4) || n == 100) ? 365 : 366;
						f->st->st_mtime *= 24L * 60L * 60L;
						f->st->st_mtime += 12L * 60L * 60L;
					}
				}
				if (!f->st->st_mtime)
					f->st->st_mtime = NOW;
			}
			else if (type == 2)
			{
				switch (f->record.format = slt->buf[4])
				{
				case 'D': /* decimal variable	*/
				case 'F': /* fixed length	*/
				case 'S': /* spanned		*/
				case 'U': /* input block size	*/
				case 'V': /* binary variable	*/
					break;
				default:
					error(2, "%s record format %c not supported", ap->format->name, f->record.format);
					f->skip = 1;
				}
				state.blocksize = getlabnum(slt->buf, 6, 5, 10);
				state.record.size = getlabnum(slt->buf, 11, 5, 10);
				if (!ap->io->blocked) f->st->st_size = getlabnum(slt->buf, 16, 10, 10);
				state.record.offset = getlabnum(slt->buf, 51, 2, 10);
			}
		}
		else if (!ap->io->blocked && strneq(slt->buf, "VOL1", 4))
		{
			paxunread(pax, ap, slt->buf, lab);
			if (!(getprologue(ap)))
				return 0;
			goto again;
		}
	} while ((lab = getlabel(pax, ap, f)));
	return 1;
}

static int
slt_getdata(Pax_t* pax, register Archive_t* ap, register File_t* f, int wfd)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	register off_t	n;
	register off_t	size;
	int		c;
	int		i;
	int		j;
	int		k;
	int		nl;
	off_t		m;
	Sfio_t*		wfp;

	if (wfd < 0)
		wfp = 0;
	else if (!(wfp = sfnew(NiL, NiL, SF_UNBOUND, wfd, SF_WRITE)))
	{
		error(2, "%s: cannot write", f->name);
		return -1;
	}
	ap->io->empty = 0;
	nl = state.record.line;
	size = 0;
	for (;;)
	{
		if (ap->io->blocked)
			n = paxread(pax, ap, state.tmp.buffer, (off_t)0, (off_t)state.buffersize, 0);
		else if ((m = f->st->st_size - size) <= 0)
			n = 0;
		else if (wfp) 
		{
			if (m > state.buffersize)
				m = state.buffersize;
			n = paxread(pax, ap, state.tmp.buffer, (off_t)0, m, 1);
		}
		else
			n = paxread(pax, ap, NiL, (off_t)0, m, 1);
		if (n < 0)
			break;
		if (n == 0)
		{
			k = 1;
			ap->sum--;
			while (getlabel(pax, ap, f))
			{
				if (strneq(slt->buf, "EOV1", 4))
					k = 0;
				else if (!strneq(slt->buf, "EOF", 3) && !strneq(slt->buf, "EOV", 3) && !strneq(slt->buf, "UTL", 3) && ++n >= 16 && !state.keepgoing)
					error(3, "%s: %s: %d invalid %s end of file/volume labels detected", ap->name, f->name, n, ap->format->name);
			}
			if (n)
				error(1, "%s: %s: %d invalid %s end of file/volume labels detected", ap->name, f->name, n, ap->format->name);
			if (k)
			{
				ap->sum++;
				break;
			}
			f->record.section++;
			f->id = strcpy(state.tmp.buffer, f->id);
			f->name = strcpy(state.tmp.buffer + NAME_SIZE + 1, f->name);
			for (;;)
			{
				newio(ap, 0, 0);
				if (getprologue(ap))
				{
					File_t		v;
					struct stat	st;

					v.st = &st;
					if (getheader(ap, &v))
					{
						if (streq(f->id, v.id) && streq(f->name, v.name) && f->record.section == v.record.section)
						{
							f->id = v.id;
							f->name = v.name;
							break;
						}
						error(1, "volume containing %s id %s section %d required", f->name, f->id, f->record.section);
					}
					ap->volume--;
				}
				ap->part--;
			}
			ap->sum++;
			continue;
		}
		if (f->record.format == 'V')
		{
			if ((k = ((unsigned char*)state.tmp.buffer)[0] << 8 | ((unsigned char*)state.tmp.buffer)[1]) != n)
				error(3, "%s: invalid %s V format block descriptor [%d!=%d]", f->name, ap->format->name, k, n);
			i = 4;
		}
		else
			i = 0;
		while (i < n)
		{
			i += state.record.offset;
			if (state.tmp.buffer[i] == '^')
				switch (f->record.format)
				{
				case 'F':
					if (slt->ibm)
						break;
					for (j = i; j < n && state.tmp.buffer[j] == '^'; j++);
					if (j < n)
						break;
					/*FALLTHROUGH*/
				case 'D':
				case 'S':
					i = n;
					continue;
				}

			/*
			 * get record size
			 */

			switch (f->record.format)
			{
			case 'D':
				if (sfsscanf(&state.tmp.buffer[i], "%4d", &k) != 1)
					k = -1;
				j = i + 4;
				break;
			case 'F':
				if (i + state.record.size > n)
					k = n - i;
				else if (state.record.line || state.record.offset)
					k = state.record.size;
				else
					k = n;
				j = i;
				break;
			case 'S':
				switch (state.tmp.buffer[i])
				{
				case '0':
				case '3':
					nl = 1;
					break;
				default:
					nl = 0;
					break;
				}
				if (sfsscanf(&state.tmp.buffer[i + 1], "%4d", &k) != 1)
					k = -1;
				j = i + 5;
				break;
			case 'U':
				k = n;
				j = i;
				break;
			case 'V':
				nl = !(state.tmp.buffer[i + 2] & 01);
				k = ((unsigned char*)state.tmp.buffer)[i] << 8 | ((unsigned char*)state.tmp.buffer)[i + 1];
				j = i + 4;
				break;
			}
			if (k < 0)
			{
				error(2, "invalid %s %c record size", ap->format->name, f->record.format);
				break;
			}
			m = i += k;
			if (slt->mapin)
				ccmapstr(slt->mapin, &state.tmp.buffer[j], m - j);
			if (state.record.line)
				switch (f->record.format)
				{
				case 'F':
				case 'U':
					while (--m >= j && state.tmp.buffer[m] == ' ');
					m++;
					break;
				}
			k = m - j + nl;
			size += k;
			if (wfp)
			{
				if (nl)
				{
					c = state.tmp.buffer[m];
					state.tmp.buffer[m] = '\n';
				}
				if (sfwrite(wfp, &state.tmp.buffer[j], k) != k)
				{
					error(ERROR_SYSTEM|1, "%s: write error", f->name);
					break;
				}
				if (nl)
					state.tmp.buffer[m] = c;
			}
		}
	}
	if (f->st->st_size && f->st->st_size != size)
		error(1, "%s: header size %I*d does not match data size %I*d", f->name, sizeof(f->st->st_size), f->st->st_size, sizeof(size), size);
	f->st->st_size = size;
	if (wfp)
	{
		sfclose(wfp);
		setfile(ap, f);
	}
	if (n < 0)
	{
		error(ERROR_SYSTEM|3, "%s: %s: archive read error", ap->name, f->name);
		return -1;
	}
	return 1;
}

static int
slt_getepilogue(Pax_t* pax, Archive_t* ap)
{
	return 1;
}

static int
slt_backup(Pax_t* pax, Archive_t* ap)
{
#ifdef MTIOCTOP
	struct mtop	mt;

	mt.mt_op = MTBSF;
	mt.mt_count = 1;
	if (ioctl(ap->io->fd, MTIOCTOP, &mt))
	{
		error(ERROR_SYSTEM|3, "%s: %s archive seek MTIO error", ap->name, ap->format->name);
		return -1;
	}
	return 0;
#else
	error(3, "%s: %s archive seek requires MTIO", ap->name, ap->format->name);
	return -1;
#endif
}

static int
slt_putprologue(Pax_t* pax, Archive_t* ap)
{
	register Slt_t*	slt = (Slt_t*)ap->data;

	if (!ap->locked && slt->vol)
	{
		slt->vol = 0;
		ap->locked = 1;
		putlabels(pax, ap, state.record.file, "HDR");
		ap->locked = 0;
	}
	if (ap->format->flags & CONV)
	{
		convert(ap, SECTION_CONTROL, CC_NATIVE, CC_EBCDIC);
		if (!ap->convert[0].on)
			convert(ap, SECTION_DATA, CC_NATIVE, CC_EBCDIC);
	}
#if DEBUG
	if (ap->io->blok)
		ap->io->blocked = 1;
	else
#endif
	ap->io->blocked = !ap->io->unblocked;
	if (!slt->owner[0])
	{
		strncpy(slt->owner, fmtuid(getuid()), sizeof(slt->owner) - 1);
		slt->owner[sizeof(slt->owner) - 1] = 0;
	}
	strupper(slt->owner);
	if (!state.volume[0])
	{
		strncpy(state.volume, slt->owner, sizeof(state.volume) - 1);
		state.volume[sizeof(state.volume) - 1] = 0;
	}
	strupper(state.volume);
	strncpy(slt->format, SLT_ID, sizeof(slt->format) - 1);
	strncpy(slt->implementation, IMPLEMENTATION, sizeof(slt->implementation) - 1);
	if (ap->format->flags & CONV)
		sfsprintf(slt->standards, sizeof(slt->standards), "%-5.5s%-5.5s%-5.5s%-4.4s", "ATT", "1", "EBCDIC", "1979");
	else
		sfsprintf(slt->standards, sizeof(slt->standards), "%-5.5s%-5.5s%-5.5s%-4.4s", "ISO", "646", "IRV", "1990");
	sfsprintf(slt->buf, sizeof(slt->buf), "VOL1%-6.6s              %-6.6s%-7.7s%-14.14s                            4", state.volume, slt->format, slt->implementation, slt->owner);
	paxwrite(pax, ap, slt->buf, HDR_SIZE);
	sfsprintf(slt->buf, sizeof(slt->buf), "VOL2%-19.19s                                                         ", slt->standards);
	paxwrite(pax, ap, slt->buf, HDR_SIZE);
	if (ap->delta && !(ap->delta->format->flags & PSEUDO))
	{
		sfsprintf(slt->buf, sizeof(slt->buf), "UVL1 %-6.6s%c%-6.6s%010ld%010ld                                         ", ID, ap->delta->compress ? TYPE_COMPRESS : TYPE_DELTA, ((Compress_format_t*)ap->delta->format)->variant, state.operation == OUT ? (long)ap->size : (long)0, state.operation == OUT ? ap->checksum : 0L);
		paxwrite(pax, ap, slt->buf, HDR_SIZE);
	}
	return 1;
}

static int
slt_putheader(Pax_t* pax, Archive_t* ap, File_t* f)
{
	if (state.complete)
		complete(ap, f, 4 * HDR_SIZE);
	putlabels(pax, ap, f, "HDR");
	return 1;
}

static int
recordout(Pax_t* pax, Archive_t* ap, File_t* f, Sfio_t* fp)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	register int	c;
	register char*	p;
	register char*	recdat;
	register char*	blkdat;
	char*		rec;
	char*		blk;
	int		span;

	int		count = 0;
	int		partial = 0;
	int		truncated = 0;

	static char	span_out[] = "0132";

	if (!fp)
		error(3, "record output from buffer not supported");
	ap->record = f;
	f->record.blocks = 0;
	span = 0;
	blk = state.tmp.buffer;

	/*
	 * file loop
	 */

	for (;;)
	{
		p = blk;
		switch (state.record.format)
		{
		case 'V':
			p += 4;
			break;
		}
		blkdat = p;

		/*
		 * block loop
		 */

		for (;;)
		{
			rec = p;
			switch (state.record.format)
			{
			case 'D':
			case 'V':
				p += 4;
				break;
			case 'S':
				p += 5;
				break;
			}
			recdat = p;

			/*
			 * check for partial record from previous block
			 */

			if (partial)
			{
				memcpy(recdat, f->record.partial, partial);
				p += partial;
				partial = 0;
			}

			/*
			 * record loop
			 */

			span &= 01;
			span <<= 1;
			for (;;)
			{
				if (p >= &rec[state.record.size] && state.record.size)
				{
					if (state.record.line)
					{
						truncated++;
						while ((c = sfgetc(fp)) != EOF && c != '\n');
					}
					break;
				}
				else if (p >= &blk[state.blocksize])
				{
					if (state.record.format == 'S' || state.record.format == 'V')
					{
						if (p > recdat)
						{
							span |= 01;
							break;
						}
					}
					else if (partial = p - recdat)
					{
						/*
						 * save partial record for next block
						 */

						if (!f->record.partial && !(f->record.partial = newof(0, char, state.blocksize, 0)))
							nospace();
						memcpy(f->record.partial, recdat, partial);
					}
					p = rec;
					goto eob;
				}
				else if ((c = sfgetc(fp)) == EOF)
				{
					if (p == recdat)
					{
						if (rec == blkdat) goto eof;
						p = rec;
						goto eob;
					}
					break;
				}
				else if (c == '\n' && state.record.line)
					break;
				else
					*p++ = c;
			}
			switch (state.record.format)
			{
			case 'D':
				c = recdat[0];
				sfsprintf(rec, 4, "%04d", p - rec);
				recdat[0] = c;
				break;
			case 'F':
				if (c != EOF || state.record.pad)
				{
					memset(p, ' ', state.record.size - (p - rec));
					p = rec + state.record.size;
				}
				break;
			case 'S':
				c = recdat[0];
				sfsprintf(rec, 4, "%c%04d", span_out[span], p - rec);
				recdat[0] = c;
				break;
			case 'U':
				if (p == recdat) *p++ = ' ';
				break;
			case 'V':
				rec[0] = ((p - rec) >> 8) & 0xff;
				rec[1] = (p - rec) & 0xff;
				rec[2] = span;
				rec[3] = 0;
				break;
			}
			if (slt->mapout)
				ccmapstr(slt->mapout, recdat, p - recdat);
			count++;
			if (p >= &blk[state.blocksize] || state.record.format == 'U')
				break;
		}
	eob:
		switch (state.record.format)
		{
		case 'D':
		case 'S':
			if (state.record.pad)
			{
				memset(p, '^', state.blocksize - (p - blk));
				p = blk + state.blocksize;
			}
			break;
		case 'V':
			blk[0] = ((p - blk) >> 8) & 0xff;
			blk[1] = (p - blk) & 0xff;
			blk[2] = 0;
			blk[3] = 0;
			break;
		}
		paxwrite(pax, ap, blk, p - blk);
		f->record.blocks++;
	}
 eof:
	ap->record = 0;
	if (truncated)
		error(1, "%s: %d out of %d record%s truncated", f->name, truncated, count, count == 1 ? "" : "s");
	return 0;
}

static int
slt_putdata(Pax_t* pax, Archive_t* ap, File_t* f, int rfd)
{
	register size_t		m;
	register ssize_t	n;
	register off_t		c;
	int			r;
	Buffer_t*		bp;
	Sfio_t*			rfp;

	if (ap->io->blocked)
		return 0;
	r = 1;
	if (f->st->st_size > 0)
	{
		if (state.record.format == 'F' && !state.record.line)
		{
			/*
			 * this is faster than recordout()
			 */

			ap->record = f;
			c = f->st->st_size;
			while (c > 0)
			{
				n = m = c > state.record.size ? state.record.size : c;

				/*
				 * NOTE: we expect that all but the last
				 *	 read returns state.record.size
				 *	 if not the the intermediate short
				 *	 reads are filled with 0's
				 */

				if (!r)
				{
					if (rfd >= 0)
						n = read(rfd, state.tmp.buffer, m);
					else if (bp = getbuffer(rfd))
					{
						memcpy(ap->io->next, bp->next, m);
						bp->next += m;
					}
					else if (paxread(pax, f->ap, state.tmp.buffer, (off_t)0, (off_t)m, 1) <= 0)
						n = -1;
				}
				if (n <= 0)
				{
					if (n)
						error(ERROR_SYSTEM|2, "%s: read error", f->path);
					else
						error(2, "%s: file size changed", f->path);
					memzero(state.tmp.buffer, state.record.size);
					r = -1;
				}
				else
				{
					c -= n;
					if (n < state.record.size && (c > 0 || state.record.pad))
					{
						memzero(state.tmp.buffer + n, state.record.size - n);
						n = state.record.size;
					}
					paxwrite(pax, ap, state.tmp.buffer, n);
				}
			}
			ap->record = 0;
			if (rfd >= 0)
				close(rfd);
		}
		else if (rfd < 0)
			recordout(pax, ap, f, NiL);
		else if (!(rfp = sfnew(NiL, NiL, SF_UNBOUND, rfd, SF_READ)))
		{
			error(1, "%s: cannot read", f->path);
			close(rfd);
		}
		else
		{
			recordout(pax, ap, f, rfp);
			sfclose(rfp);
		}
	}
	return r;
}

static int
slt_puttrailer(Pax_t* pax, Archive_t* ap, File_t* f)
{
	putlabels(pax, ap, f, "EOF");
	return 0;
}

static off_t
slt_putepilogue(Pax_t* pax, Archive_t* ap)
{
	register Slt_t*	slt = (Slt_t*)ap->data;

	if (!ap->locked)
	{
		ap->locked = 1;
		putlabels(pax, ap, state.record.file, "EOV");
		ap->locked = 0;
		slt->vol = 1;
	}
	paxwrite(pax, ap, slt->buf, 0);
	paxwrite(pax, ap, slt->buf, 0);
	return ap->io->count;
}

static int
slt_validate(Pax_t* pax, Archive_t* ap, register File_t* f)
{
	register Slt_t*	slt = (Slt_t*)ap->data;
	register char*	s;

	if (f->type != X_IFREG)
	{
		error(2, "%s: only regular files copied in %s format", f->path, ap->format->name);
		return 0;
	}
	if (s = strrchr(f->name, '/'))
	{
		s++;
		error(1, "%s: file name stripped to %s", f->name, s);
	}
	else
		s = f->name;
	if (strlen(s) > sizeof(slt->id) - 1)
	{
		error(2, "%s: file name too long", f->name);
		return 0;
	}
	f->id = strupper(strcpy(slt->id, s));
	return 1;
}

Format_t	pax_slt_format =
{
	"ansi",
	"slt",
	"ANSI standard label tape; for tape devices only",
	ANSI,
	ARCHIVE|NOHARDLINKS|IN|OUT,
	4,
	4,
	0,
	pax_slt_next,
	0,
	slt_done,
	slt_getprologue,
	0,
	slt_getdata,
	0,
	slt_getepilogue,
	slt_putprologue,
	slt_putheader,
	slt_putdata,
	slt_puttrailer,
	slt_putepilogue,
	0,
	slt_backup,
	0,
	slt_validate
};

static int
ibm_getprologue(Pax_t* pax, Format_t* fp, register Archive_t* ap, File_t* f, unsigned char* buf, size_t size)
{
	register Slt_t*	slt;
	int		n;

	if ((n = slt_getprologue(pax, fp, ap, f, buf, size)) > 0 && state.record.charset)
	{
		slt = (Slt_t*)ap->data;
		slt->mapin = state.map.e2n;
		slt->mapout = state.map.n2e;
	}
	return n;
}

Format_t	pax_ibm_format =
{
	"ibm",
	"elt",
	"EBCDIC standard label tape; for tape devices only",
	IBM,
	ARCHIVE|CONV|NOHARDLINKS|IN|OUT,
	4,
	4,
	0,
	PAXNEXT(pax_ibm_next),
	0,
	slt_done,
	ibm_getprologue,
	0,
	slt_getdata,
	0,
	slt_getepilogue,
	slt_putprologue,
	slt_putheader,
	slt_putdata,
	slt_puttrailer,
	slt_putepilogue,
	0,
	slt_backup,
	0,
	slt_validate
};

PAXLIB(&pax_ibm_format)
