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

/*
 * 3d file system library implementation
 *
 * debug trace levels are controlled by /#option/debug
 * debug level n enables tracing for all levels less than or equal to n
 *
 *	level	trace
 *	  0	no trace
 *	  1	basic calls
 *	  2	mounted fs
 *	  3	fs control
 *	  4	pathcanon
 *	  5	pathreal
 *	  6	pathnext
 *	  7	*unused*
 *	  8	*unused*
 *	  9	*unused*
 *	 10	getkey
 */

#include "3d.h"

#include <cs.h>

/*
 * keep 3d tables from buf,siz in state.table.fd
 * if force!=0 then state.table.fd set to TABLE_FD
 * 0 returned if state.table.fd is ok
 */

int
keep(const char* buf, size_t siz, int force)
{
	int	n;

	if (force && state.in_2d || siz > sizeof(state.table.buf))
	{
		if (state.table.fd > 0)
			cancel(&state.table.fd);
		return(-1);
	}
	if (state.table.fd > 0)
	{
		char	tmp[sizeof(state.table.buf)];

		if (peek(state.table.fd, tmp, sizeof(tmp) - 1) == siz && !memcmp(tmp, buf, siz))
			return(0);
		cancel(&state.table.fd);
	}
#if _stream_peek || _socket_peek
	{
		int	fds[2];

		state.in_2d++;
#if _socket_peek
		n = cspipe(&cs, fds);
#else
		n = pipe(fds);
#endif
		state.in_2d--;
		if (!n && fds[1] == TABLE_FD)
		{
#if _pipe_rw || _lib_socketpair
			fds[1] = fds[0];
			fds[0] = TABLE_FD;
#else
			CLOSE(fds[0]);
			state.in_2d++;
			n = pipe(fds);
			state.in_2d--;
			CLOSE(TABLE_FD);
#endif
		}
		if (!n)
		{
			if (fds[0] == TABLE_FD) state.table.fd = fds[0];
			else
			{
				if (force) CLOSE(TABLE_FD);
				state.table.fd = FCNTL(fds[0], F_DUPFD, TABLE_FD);
				CLOSE(fds[0]);
			}
			n = WRITE(fds[1], buf, siz) == siz;
			CLOSE(fds[1]);
			if (n)
			{
				state.table.size = siz;
				reserve(&state.table.fd);
				return(0);
			}
		}
	}
#else
	sfsprintf(state.path.name, sizeof(state.path.name), "/tmp/3D#%d", state.pid);
	if ((n = OPEN(state.path.name, O_CREAT, S_IRUSR|S_IRGRP|S_IROTH)) >= 0)
	{
		UNLINK(state.path.name);
		if (force) CLOSE(TABLE_FD);
		if (n == TABLE_FD) state.table.fd = TABLE_FD;
		else
		{
			state.table.fd = FCNTL(n, F_DUPFD, TABLE_FD);
			CLOSE(n);
		}
		if (WRITE(state.table.fd, buf, siz) == siz && !LSEEK(state.table.fd, 0L, 0))
		{
			state.table.size = siz;
			reserve(&state.table.fd);
			return(0);
		}
	}
#endif
	if (state.table.fd > 0)
		cancel(&state.table.fd);
	return(-1);
}

/*
 * enable/disable/test 3d/anno state
 * allows state transitions without changing the tables
 */

int
fs3d(register int op)
{
	register int	po;
	register int	n;

	if ((n = FS3D_op(op)) == FS3D_OP_INIT || !state.pid)
		init(1, 0, 0);
	if (!state.in_2d)
		po = FS3D_ON;
	else if (state.limit != TABSIZE)
		po = FS3D_LIMIT(state.limit);
	else
		po = FS3D_OFF;
	switch (n)
	{
	case FS3D_OP_INIT:
	case FS3D_OP_TEST:
		break;
	case FS3D_OP_LIMIT:
		if ((n = FS3D_arg(op)) <= 0 || n > TABSIZE)
			n = TABSIZE;
		state.limit = n;
		state.in_2d = 0;
		break;
	case FS3D_OP_OFF:
		if (state.level > 0)
			po = -1;
		else
		{
			state.limit = TABSIZE;
			state.in_2d = 1;
		}
		break;
	case FS3D_OP_ON:
		state.limit = TABSIZE;
		state.in_2d = 0;
		break;
	default:
		po = -1;
		break;
	}
	message((-1, "fs3d(%d)=%d", op, po));
	return po;
}

/*
 * return 6 char lower case hash of key
 * end is end of key string
 * 0 returned at end of keys
 * state.key.value is set to key value
 * if `no<key>' then state.key.invert gets value and state.key.value=""
 * otherwise state.key.invert=0
 * state.key.next is set to next key if any
 */

unsigned long
getkey(register const char* key, register const char* end, int alt)
{
	register const char*	val;
	register unsigned long	x = 0;
	register int		invert;
	register int		c;
#if DEBUG
	const char*		beg;
#endif

	if (key)
	{
#if DEBUG
		beg = key;
#endif
		if (key < end - 1 && *key == 'n' && *(key + 1) == 'o')
		{
			key += 2;
			invert = 1;
		}
		else invert = 0;
		if (key < end)
		{
			val = ((end - key) > HASHKEYMAX) ? key + HASHKEYMAX : end;
			while (key < end && (c = *key) != '=' && c != '/' && c != alt)
			{
				if (key < val)
					x = (c >= '0' && c <= '9') ? HASHKEYPART(x, HASHKEYN(c)) : HASHKEYPART(x, c);
				key++;
			}
			if (key < end && c == '=')
				key++;
		}
		if (key >= end || *key == '/' || *key == alt)
		{
			state.key.value = state.one;
			state.key.valsize = 1;
		}
		else
		{
			state.key.value = (char*)key;
			while (key < end && *key != '/' && *key != alt)
				key++;
			state.key.valsize = (char*)key - state.key.value;
		}
		while (key < end && (*key == '/' || *key == alt))
			key++;
		if (key < end && *key == '#' && *(key - 1) == '/')
			key++;
		if (invert)
		{
			state.key.invert = state.key.value;
			state.key.invertsize = state.key.valsize;
			state.key.value = state.null;
			state.key.valsize = 0;
		}
		else state.key.invert = 0;
		state.key.next = (key < end) ? (char*)key : (char*)0;
		message((-10, "getkey: key=%-*s hash=%x value=%-*s next=%-*s", end - beg, beg, x, state.key.valsize, state.key.value, state.key.next ? end - state.key.next : 6, state.key.next));
	}
	else state.key.next = 0;
	return(x);
}

#if FS

/*
 * return mount pointer for path
 * under is set to the part of the path under the mount
 */

Mount_t*
getmount(register const char* path, const char** under)
{
	register Map_t*	map;
	int		n;
	int		oerrno;
	struct stat	st;

	if (!(map = search(&state.vmount, path, strlen(path), NiL, T_PREFIX)) || ((Mount_t*)map->val)->fs == state.fs)
		return(0);
	if (under)
	{
		if (*(path += map->keysize)) path++;
		else
		{
			oerrno = errno;
			n = !STAT(path - map->keysize, &st) && !S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode);
			errno = oerrno;
			if (n) return(0);
			else path = (const char*)state.dot;
		}
		*under = path;
	}
	return((Mount_t*)map->val);
}

#endif

int
nosys(void)
{
	errno = EINVAL;
	return(-1);
}

/*
 * copy t into s, return a pointer to the end of s ('\0')
 */

char*
strcopy(register char* s, register const char* t)
{
	if (!t) return(s);
	while (*s++ = *t++);
	return(--s);
}

/*
 * return map for <key,keysize> in tab
 * if val!=0 then key=val inserted if not already entered
 * valsize==0 causes string val to be malloc'd on insertion
 * key is malloc'd on insertion if (valsize|T_ALLOCATE) 
 * valsize==T_DELETE for deletion
 * valsize==T_PREFIX for longest path prefix search and val==visit_mask
 */

Map_t*
search(Table_t* tab, const char* key, int keysize, const char* val, int valsize)
{
	register Map_t*		lo;
	register Map_t*		mid;
	register Map_t*		hi;
	register const char*	sp;
	register const char*	se;
	register const char*	tp;
	register const char*	te;
	int			n;
	Map_t*			end;

	se = key + keysize;
	lo = tab->table;
	end = lo + tab->size;
	if (valsize == T_PREFIX)
	{
		mid = 0;
		hi = end;
		while (lo < hi)
		{
			sp = key;
			tp = lo->key;
			te = tp + lo->keysize;
message((-12, "T_PREFIX: %-*s: key=%-*s mid=%-*s", keysize, key, lo->keysize, lo->key, mid ? mid->keysize : 1, mid ? mid->key : "-"));
			for (;;)
			{
				if (tp >= te)
				{
					if ((sp >= se || *sp == '/') && (!val || !(*((long*)val) & (1 << (lo - tab->table)))))
					{
						mid = lo;
						if (sp >= se)
							goto prefix;
					}
					break;
				}
				if (sp >= se || (n = *((unsigned char*)sp++) - *((unsigned char*)tp++)) < 0)
					goto prefix;
				if (n > 0)
				{
					if (mid && mid->keysize >= (sp - key))
						goto prefix;
					break;
				}
			}
			lo++;
		}
	prefix:
		if (mid && val) *((long*)val) |= (1 << (mid - tab->table));
		return(mid);
	}
	if (end > lo)
	{
		hi = end - 1;
		while (lo <= hi)
		{
			mid = lo + (hi - lo) / 2;
			sp = key;
			tp = mid->key;
			te = tp + mid->keysize;
			for (;;)
			{
				if (tp >= te)
				{
					if (sp >= se)
					{
						if (valsize != T_DELETE)
							return(mid);
						if (mid->valsize & T_ALLOCATE)
							free(mid->key);
#if FS
						if ((mid->valsize & T_SIZE) == T_MOUNT)
							((Mount_t*)mid->val)->fs = 0;
						else
#endif
						if (!(mid->valsize & T_SIZE))
							free(mid->val);
						for (; mid < end; mid++)
							*mid = *(mid + 1);
						tab->size--;
						return(0);
					}
					lo = mid + 1;
					break;
				}
				if (sp >= se || (n = *((unsigned char*)sp++) - *((unsigned char*)tp++)) < 0)
				{
					hi = mid - 1;
					break;
				}
				if (n > 0)
				{
					lo = mid + 1;
					break;
				}
			}
		}
	}
	if (!val || valsize == T_DELETE || tab->size >= elementsof(tab->table))
		return(0);
	tab->size++;
	for (hi = end; hi > lo; hi--)
		*hi = *(hi - 1);
	lo->keysize = keysize;
	if (valsize & T_ALLOCATE) lo->key = strcpy(newof(0, char, keysize, 1), key);
	else lo->key = (char*)key;
	if ((lo->valsize = valsize) & T_SIZE) lo->val = (char*)val;
	else lo->val = strcpy(newof(0, char, strlen(val), 1), val);
	return(lo);
}

/*
 * iterate fun over tab
 * terminates on first negative return from fun
 */

int
iterate(register Table_t* tab, int (*fun)(Map_t*, char*, int), register char* buf, int flags)
{
	register Map_t*	cp;
	register Map_t*	ep;
	register int	n;
	register int	sum;

	sum = 0;
	for (ep = (cp = tab->table) + tab->size; cp < ep; cp++)
	{
		if ((n = (*fun)(cp, buf, flags)) < 0)
			return(0);
		if (buf) buf += n;
		sum += n;
	}
	return(sum);
}

#if FS

/*
 * initialize open file info
 */

int
fileinit(int fd, struct stat* st, Mount_t* mp, int force)
{
	register File_t*	f;
	int			ffd;
	int			ffl;
	struct stat		sb;

	f = state.file + fd;
	if (!force && (f->flags & FILE_ERROR) || !st && FSTAT(fd, st = &sb) || (ffd = FCNTL(fd, F_GETFD, NiL)) == -1 || (ffl = FCNTL(fd, F_GETFL, NiL)) == -1)
	{
		f->flags = FILE_ERROR;
		return -1;
	}
	f->oflag = ffl;
	f->open = 0;
	f->flags = FILE_OPEN;
	if (S_ISREG(st->st_mode))
		f->flags |= FILE_REGULAR;
	if (ffd & FD_CLOEXEC)
		f->flags |= FILE_CLOEXEC;
	if ((ffl & O_ACCMODE) != O_RDONLY)
		f->flags |= FILE_WRITE;
	f->id.fid[0] = st->st_ino;
	f->id.fid[1] = st->st_dev;
	if ((f->mount = mp) && fd > state.cache)
		state.cache = fd;
	if (fd > state.open)
		state.open = fd;
	return 0;
}

#endif
