/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1989-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>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Eduardo Krell <ekrell@adexus.cl>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped 
/*
 * Herman Rao
 * AT&T Research
 * 3d version control system interface
 */

#include "3d.h"

#if VCS

#define MAXVID		256

#define MAGIC_STRING	"\026\003\023\006"
#define MAGIC_LEN	4

#define DELTA		(1 << 0)
#define COMPRESS	(1 << 1)
#define CPIO		(1 << 2)

typedef struct			/* unique id for a sfile in WAN */
{
	unsigned long	host;
	dev_t		dev;
	ino_t		ino;
} Rno_t;

/*
 *	structure of sfile 
 *
 *	attr	tags_region	delta_region	log_region
 *	__________________________________________________________
 *	|      |T|T|T|T| ......|D  |D  |.....   |T|D    |T|D     |
 *	----------------------------------------------------------
 *             
 */

typedef struct
{
	char		magic[4];
	int		type;		/* type of file, i.e., DELTA */
	int		version;	/* RSCS version number */
	Rno_t		rno;		/* rnode */
	int		tag_reg;	/* tags region		*/
	int		del_reg;	/* delta region		*/
	int		log_reg;	/* log region		*/
	int		basetag;	/* base tag 		*/
	int		base;		/* base delta 		*/
	int		basesize;	/* base delta size	*/
} Attr_t;

#define LOG		(1<<0)
#define HASH		(1<<1)
#define VCSLINK		(1<<2)
#define VOBJ		(1<<3)
#define BASE		(1<<4)
#define MARKER		(1<<5)

typedef struct
{
	int		length;			/* length of tag */
	int		type;			/* type of data */
	int		del;			/* addr of data */
	int		dsize;			/* size of data */
	struct stat 	stat;			/* stat info    */
	int		domain;			/* domain of the creator */
	char 		version[MAXVID];	/* version id */
} Tag_t;

#define ISBASE(tp)	(((tp)->type) & BASE)
#define ISROOT(me)	(me == (uid_t)(0))
#define R_ISLINK(tp)	(((tp)->type) & VCSLINK)
#define R_ISMARKER(tp)	(((tp)->type) & MARKER)

#define CHRLINK		'>'
#define CHRMARKER	'<'
#define MAXLINK		10

/*
 * used by lookup_tag()
 */

#define L_LOG		(1<<0)
#define L_HASH		(1<<1)
#define G_LINK		(1<<2)

typedef struct Rdirent
{
	Tag_t*	 		tag;
	int			oldaddr;	/* for reconf used */
	char*			link;		/* used by the link */
	struct Rdirent*		next;
} Rdirent_t;

#define ISRSCS(ap)	(!strncmp(ap->magic, MAGIC_STRING, MAGIC_LEN))
#define KEYEQ(t,v,r)	(streq(t->version, v) && (!r || t->domain == r))

/* 
 *	list of error codes
 */

#define ERRARG		1
#define NOVFILE		2
#define NOTRSCS		3
#define NOVERSION	4
#define NOBASE		5
#define ERRSTRUCT	6
#define ERRBASE		7
#define ERRDELTA	8
#define NOMEM 		9
#define ERRUPDATE	10
#define ERRACCESS	11
#define ERRWRITE	12
#define NOENTIES	13

#define DEFAULT		"default"
#define LATEST		"latest"

#define VCS_RDIR	"VCS"
#define VCS_MDIR	"..."
#define VCS_MDIRLEN	(sizeof(VCS_MDIR)-1)

int
vcs_checkout(const char* path, struct stat* st)
{
	char*		b;
	char*		e;
	int 		fd;
	int		n;
	char		buf[3 * PATH_MAX];
	int		synfd;
	char		synpath[PATH_MAX];

	NoP(st);
	message((-2, "vcs: checkout: %s", path));
	if ((fd = fsfd(state.vcs.fs)) < 0)
	{
		message((-2, "vcs: connect error"));
		return(-1);
	}
	e = (b = buf) + elementsof(buf) - 1;
	bprintf(&b, e, "out %s %s %s\n", state.path.vcs.rfile, state.path.vcs.version, state.path.vcs.rfile);
	n = b - buf;
	message((-2, "vsc: sendout msg %s", buf));
	cancel(&state.vcs.fd);
	if (WRITE(fd, buf, n) != n)
	{
		message((-2, "vcs: checkout: can't send the message %s", buf));
		return(-1);
	}
	e = (b = synpath) + elementsof(synpath) - 1;
	bprintf(&b, e, "%s__%d", state.path.vcs.rfile, state.pid);
	if ((synfd = OPEN(synpath, O_CREAT | O_WRONLY, 0666)) < 0)
		return(-1);
	CLOSE(synfd);
	if ((n = READ(fd, buf, sizeof(buf) - 1)) > 0)
	{
		buf[n - 1] = 0;
		message((-2, "vcs: checkout return msg %s", buf));
		UNLINK(synpath);
		return(0);
	}
	return(-1);
}

/*
 *	path:		prefix/.../file_name/version_name
 *	rfile:		prefix/.../file_name/default
 *	version:	version_name
 */

static int
vcs_map(const char* path, char* rfile, char* version)
{
	register const char*	s;
	const char*		p[3];
	int			i;

	for (s = path; *s; s++);
	for (i = 0; s != path && i < 3; s--)
		if (*s == '/')
			p[i++] = s + 1;
	s++;
	if (i < 3 || s == path || !strneq(p[2], VCS_MDIR, VCS_MDIRLEN))
		return(-1);
	strcpy(version, p[0]);
	strcpy(rfile, path);
	message((-2, "vcs: map path=%s rfile=%s version=%s", path, rfile, version));
	return(0);
}

static Rdirent_t* 
add_entry(Rdirent_t* head, Tag_t* tp)
{
	register Rdirent_t*	ndp;
	register Rdirent_t*	dp;
	register Rdirent_t**	prev_posn;
	int			result;
	Tag_t*			ntp;
	char*			link = 0;
	int			marker;
	

	if (R_ISLINK(tp) && (link = strrchr(tp->version, CHRLINK)))
		*link++ = 0;		
	marker = R_ISMARKER(tp) ? 1 : 0;
	dp = head;
	prev_posn = &head;
	while (dp)
	{
		/*
		 *	no marker as this point
		 */

#if 0
		if (!marker && R_ISMARKER(dp->tag))
		{
			if ((dp->tag->stat.st_ctime < tp->stat.st_ctime) && markermatch(dp->tag->version, tp->version))
			{
				*prev_posn = dp->next;
				ndp = dp->next;
				free(dp->tag);
				free(dp);
				dp = ndp;
				continue;
			}
		}
#endif
		if ((result = strcmp(dp->tag->version, tp->version)) == 0)
		{
			/*
			 * check if the minor key (domain) is the same
			 */

			if (dp->tag->domain == tp->domain)
			{
				if (dp->tag->stat.st_ctime > tp->stat.st_ctime)
					return(head);
				ntp = newof(0, Tag_t, 0, tp->length);
				memcpy((char*)ntp, (char*)tp, tp->length);
				free(dp->tag);
				dp->tag = ntp;
				if (R_ISLINK(tp) && link)
					dp->link = strdup(link);
				return(head);
			}
		}
		else if (result > 0)
		{
			ndp = newof(0, Rdirent_t, 1, 0);
			ntp = newof(0, Tag_t, 0, tp->length);
			memcpy((char*)ntp, (char*)tp, tp->length);
			ndp->tag = ntp;
			ndp->next = dp;
			if (R_ISLINK(tp) && link)
				ndp->link = strdup(link);
			*prev_posn = ndp;
			return(head);
		}
		prev_posn = &(dp->next);
		dp = dp->next;
	}
	ndp = newof(0, Rdirent_t, 1, 0);
	ntp = newof(0, Tag_t, 0, tp->length);
	memcpy((char*)ntp, (char*)tp, tp->length);
	ndp->tag = ntp;
	ndp->next = 0;
	if (R_ISLINK(tp) && link)
		ndp->link = strdup(link);
	*prev_posn = ndp;
	return(head);
}

static Tag_t*	
get_tag(int f, register Tag_t* tp)
{
	register char*	s;
	int		len;

	s = (char *)tp + sizeof(int);
	memset((char *)tp, 0, sizeof(Tag_t));
	if (!READ(f, (char *)&(tp->length), sizeof(int)) || (READ(f, (char *)s, (len = tp->length - sizeof(int))) != len))
		return(0);
	if (tp->type & LOG)
	{
		tp->del = lseek(f, 0, SEEK_CUR);
		lseek(f, tp->dsize, SEEK_CUR);
	}
	return(tp);
}

static Rdirent_t* 
rs_dir(register int fd, register Attr_t* ap)
{
	Tag_t			tag;
	register Tag_t*		tp;
	Rdirent_t*		head;
	register Rdirent_t*	dp;
	register Rdirent_t*	ndp;
	register Rdirent_t**	prev_posn;

	state.vcs.now = time(NiL);
	
	tp = &tag;
	head = 0;
	lseek(fd, ap->log_reg, SEEK_SET);
	while(get_tag(fd, tp))
		head = add_entry(head, tp);

	lseek(fd, ap->tag_reg, SEEK_SET);
	while((lseek(fd, 0, SEEK_CUR)<ap->del_reg) && get_tag(fd, tp))
		head = add_entry(head, tp);

	/*
	 * remove expired marker 
	 */

	dp = head;
	prev_posn = &head;
	while (dp)
	{
		if (R_ISMARKER(dp->tag) && dp->tag->stat.st_mtime < state.vcs.now)
		{
			*prev_posn = dp->next;
			ndp = dp->next;
			free(dp->tag);
			free(dp);
			dp = ndp;
		}
		else
		{
			prev_posn = &(dp->next);
			dp = dp->next;
		}

	}
	
	return(head);
}

static Tag_t* 
locate(register Rdirent_t* rdir, register char* version, int level)
{

	register Rdirent_t* rd;
	register time_t	    mtime;
	register Rdirent_t* p;

	if (level > MAXLINK)
		return(0);

	if (streq(version, DEFAULT) || streq(version, LATEST))
	{
		for (rd = rdir, mtime = 0L; rd; rd = rd->next)
		{
			if (rd->tag->stat.st_mtime > mtime)
			{
				p = rd;
				mtime = rd->tag->stat.st_mtime;
			}
		}
		if (mtime)
			return(p->tag);
	}

	rd = rdir;
	for (rd = rdir; rd; rd = rd->next)
	{
		if (streq(rd->tag->version, version))
		{
			if (R_ISLINK(rd->tag))
				return(locate(rdir, rd->link, level +1));
			return(rd->tag);
		}
	}
	return(0);
}

static void 
free_dir(register Rdirent_t* rdir)
{
	Rdirent_t* rnext;

	while(rdir)
	{
		rnext = rdir->next;
		free(rdir->tag);
		if (rdir->link)
			free(rdir->link);
		free(rdir);
		rdir = rnext;
	}
	return;
}

/*
 *	return 	 0 if found it;
 *		-1 if error
 *		 1 if not found.
 */

static int
vcs_stat(int fd, char* version, struct stat* st, off_t size)
{
	static Attr_t*		ap = 0;
	static Attr_t		apbuf;
	static Rdirent_t*	rdir = 0;
	register Tag_t*		tag;

	NoP(size);
	if (fd != state.vcs.fd || !ap)
	{
		ap = &apbuf;
		if (READ(fd, (char *)ap, sizeof(Attr_t)) != sizeof(Attr_t))
			return(-1);
		if (!rdir)	
			free_dir(rdir);
		if (!(rdir = rs_dir(fd, ap)))
			return(1);
	}
	if (tag = locate(rdir, version, 0))
	{
		*st = tag->stat;
		return(0);
	}
	return(1);

}

int
vcs_real(const char* path, struct stat* st)
{
	int	n;
	int	fd;

	message((-2, "vcs: path=%s", path));
	if (st->st_nlink <= 1 || vcs_map(path, state.path.vcs.rfile, state.path.vcs.version))
		return(-1);

	message((-2, "vcs: real  path=%s rfile=%s version=%s", path, state.path.vcs.rfile, state.path.vcs.version));

	if (state.vcs.fd && state.vcs.dev == st->st_dev && state.vcs.ino == st->st_ino)
		fd = state.vcs.fd;
	else if ((fd = OPEN(path, O_RDONLY, 0)) < 0)
		return(-1);
	if (n = vcs_stat(fd, state.path.vcs.version, st, st->st_size))
	{
		if (fd != state.vcs.fd) CLOSE(fd);
		else if (n == -1) cancel(&state.vcs.fd);
		return(-1);
	}
	if (fd != state.vcs.fd)
	{
		cancel(&state.vcs.fd);
		state.vcs.fd = fd;
		reserve(&state.vcs.fd);
		state.vcs.dev = st->st_dev;
		state.vcs.ino = st->st_ino;
		FCNTL(fd, F_SETFD, FD_CLOEXEC);
	}
	return(0);
}

int
vcs_set(Fs_t* fs, const char* arg, int argsize, const char* op, int opsize)
{
	NoP(arg);
	NoP(argsize);
	NoP(op);
	NoP(opsize);
	state.vcs.fs = fs;
	return(0);
}


#else

NoN(vcs)
	
#endif
