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

#include "format.h"

#define BLKHDR_SIZE	256
#define BLKHDR_hdrsiz	0
#define BLKHDR_blksiz	40

#define FILHDR_SIZE	4
#define FILHDR_MAGIC	257
#define FILHDR_namelen	128
#define FILHDR_size	0
#define FILHDR_type	2
#define FILHDR_data	4

#define FILATT_blocks	10
#define FILATT_frag	12
#define FILATT_recatt	1
#define FILATT_recfmt	0
#define FILATT_reclen	2
#define FILATT_recvfc	15

#define RECHDR_SIZE	16
#define RECHDR_size	0
#define RECHDR_type	2

#define REC_file	3
#define REC_vbn		4

typedef struct Saveset_s
{
	char*		block;		/* current block		*/
	long		blocksize;	/* max block size		*/
	char*		bp;		/* block pointer		*/
	int		recatt;		/* record attributes		*/
	int		recfmt;		/* record format		*/
	int		reclen;		/* record length		*/
	int		recvfc;		/* record fixed control length	*/
	int		lastsize;	/* size of last record		*/
	time_t		time;		/* backup time			*/
	char		id[17];		/* name id			*/
} Saveset_t;

/*
 * get next saveset record
 * if header!=0 then all records skipped until REC_file found
 * otherwise REC_vbn cause non-zero return
 * 0 returned for no record match
 */

static int
getsaveset(Pax_t* pax, register Archive_t* ap, register File_t* f, register Saveset_t* ss, int header)
{
	register char*	p;
	register char*	s;
	char*		t;
	int		i;
	long		n;

	for (;;)
	{
		ss->bp += ss->lastsize;
		while (ss->bp >= ss->block + state.blocksize)
		{
			ss->bp = ss->block;
			ss->lastsize = 0;
			if (paxread(pax, ap, ss->bp, (off_t)0, (off_t)state.blocksize, 0) <= 0)
			{
				ap->format = getformat("slt", 1);
				if (header)
					gettrailer(ap, f);
				return 0;
			}
			if (swapget(1, ss->bp + BLKHDR_hdrsiz, 2) != BLKHDR_SIZE)
				error(3, "invalid %s format block header", ap->format->name);
			if (!(n = swapget(3, ss->bp + BLKHDR_blksiz, 4)))
				ss->bp += state.blocksize;
			else if (n != state.blocksize)
				error(3, "invalid %s format blocksize=%ld", ap->format->name, n);
			ss->bp += BLKHDR_SIZE;
		}
		ss->lastsize = swapget(1, ss->bp + RECHDR_size, 2);
		i = swapget(1, ss->bp + RECHDR_type, 2);
		ss->bp += RECHDR_SIZE;
		message((-2, "record: type=%d size=%d", i, ss->lastsize));
		if (i == REC_file)
		{
			if (header)
			{
				p = ss->bp;
				if (swapget(1, p, 2) != FILHDR_MAGIC)
					error(3, "invalid %s format file header", ap->format->name);
				p += 2;
				i = swapget(1, p + FILHDR_size, 2);
				if (p + FILHDR_data + i > ss->block + state.blocksize)
					error(3, "invalid %s format file attribute", ap->format->name);
				t = f->name = paxstash(pax, &ap->stash.head, NiL, i);
				n = 0;
				for (s = p + FILHDR_data + 1; s < p + FILHDR_data + i; s++)
				{
					if (isupper(*s))
						*t++ = tolower(*s);
					else if (n)
					{
						if (*s == ';')
							break;
						*t++ = *s;
					}
					else if (*s == ']')
					{
						n = 1;
						*t++ = '/';
					}
					else if (*s == '.')
						*t++ = '/';
					else
						*t++ = *s;
				}
				*t = 0;
				for (i = 0; i < 5; i++)
				{
					s = p + FILHDR_size;
					if ((p += FILHDR_SIZE + (long)swapget(1, s, 2)) > ss->block + state.blocksize)
						error(3, "invalid %s format file attribute", ap->format->name);
				}
				ss->recatt = *(p + FILHDR_data + FILATT_recfmt);
				ss->recfmt = *(p + FILHDR_data + FILATT_recfmt);
				ss->reclen = swapget(1, p + FILHDR_data + FILATT_reclen, 2);
				ss->recvfc = swapget(1, p + FILHDR_data + FILATT_recvfc, 2);
				f->st->st_size = (long)(swapget(1, p + FILHDR_data + FILATT_blocks, 2) - 1) * BLOCKSIZE + (long)swapget(1, p + FILHDR_data + FILATT_frag, 2);
				for (; i < 15; i++)
				{
					s = p + FILHDR_size;
					if ((p += FILHDR_SIZE + (long)swapget(1, s, 2)) > ss->block + state.blocksize)
						error(3, "invalid %s format file attribute", ap->format->name);
				}

				/*
				 * pure guesswork based on 100 nsec epoch
				 * 17-NOV-1858 00:00:00 GMT
				 */

				if ((n = swapget(3, p + FILHDR_data + 4, 4) - 7355000) < 0)
					n = 1;
				else
					n = (n << 8) + *(p + FILHDR_data + 3);
				f->st->st_mtime = (time_t)n;
				return 1;
			}
			ss->bp -= RECHDR_SIZE;
			ss->lastsize = 0;
			return 0;
		}
		else if (i == REC_vbn && !header)
			return 1;
	}
}

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

static int
vmsbackup_getdata(Pax_t* pax, register Archive_t* ap, register File_t* f, int wfd)
{
	register Saveset_t*	ss = (Saveset_t*)ap->data;
	register off_t		c;
	int			i;
	int			j;
	int			k;
	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;
	}
	j = 0;
	k = 0;
	c = 0;
	while (getsaveset(pax, ap, f, ss, 0))
	{
		/*
		 * this part transcribed from vmsbackup
		 */

		i = 0;
		if (wfp) while ((c + i) < f->st->st_size && i < ss->lastsize) switch (ss->recfmt)
		{
		case 1:	/* fixed length		*/
			if (j <= 0) j = ss->reclen;
			sfputc(wfp, ss->bp[i]);
			i++;
			j--;
			break;
		case 2:	/* variable length	*/
		case 3:	/* with fixed control	*/
			if (j <= 0)
			{
				j = k = swapget(1, &ss->bp[i], 2);
				i += 2;
				if (ss->recfmt == 3)
				{
					i += ss->recvfc;
					j -= ss->recvfc;
				}
			}
			else
			{
				if (j == k && ss->recatt == 1)
				{
					if (ss->bp[i] == '0') ss->bp[i] = '\n';
					else if (ss->bp[i] == '1') ss->bp[i] = '\f';
				}
				sfputc(wfp, ss->bp[i]);
				i++;
				j--;
			}
			if (j <= 0)
			{
				sfputc(wfp, '\n');
				if (i & 1) i++;
			}
			break;
		case 4:	/* seq stream		*/
		case 5:	/* seq LF stream	*/
			if (j <= 0) j = 512;
			if (ss->bp[i] == '\n') j = 0;
			else j--;
			sfputc(wfp, ss->bp[i]);
			i++;
			break;
		case 6:	/* seq CR stream	*/
			if (ss->bp[i] == '\r') ss->bp[i] = '\n';
			sfputc(wfp, ss->bp[i]);
			i++;
			break;
		default:
			error(state.keepgoing ? 1 : 3, "%s: invalid %s format data record format=%d", f->name, ap->format->name, ss->recfmt);
			goto next;
		}
	next:
		c += i;
	}
	if (wfp)
	{
		sfclose(wfp);
		setfile(ap, f);
	}
	return 1;
}

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

static int
vmsbackup_validate(Pax_t* pax, Archive_t* ap, register File_t* f)
{
	register Saveset_t*	ss = (Saveset_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(ss->id) - 1)
	{
		error(2, "%s: file name too long", f->name);
		return 0;
	}
	f->id = strupper(strcpy(ss->id, s));
	return 1;
}

Format_t	pax_vmsbackup_format =
{
	"vmsbackup",
	0,
	"VMS backup savset; for tape devices only",
	0,
	ARCHIVE|NOHARDLINKS|IN,
	0,
	0,
	0,
	PAXNEXT(pax_vmsbackup_next),
	0,
	vmsbackup_done,
	0,
	0,
	vmsbackup_getdata,
	0,
	vmsbackup_getepilogue,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	vmsbackup_validate
};

PAXLIB(&pax_vmsbackup_format)
