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

#include "3d.h"

#ifndef MAXSYMLINKS
#define MAXSYMLINKS	20
#endif

/*
 * if <sp> is a hard link to state.opaque in the same directory then the file
 * is opaque and the errno should be ENOENT
 */

static int
checkopaque(register char* path, struct stat* st)
{
	register char*	basesp;
	int		oerrno = errno;
	struct stat	statb;
	char		savebuf[sizeof(state.opaque)];
	register int	r = 0;

	if (st->st_nlink <= 1)
		return 0;
	if (st->st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))
		return 0;

	/*
	 * change the basename to state.opaque
	 */

	if (basesp = strrchr(path, '/'))
		basesp++;
	else
		basesp = path;
	memcpy(savebuf, basesp, sizeof(state.opaque));
	strcpy(basesp, state.opaque);
	if (LSTAT(path, &statb))
	{
		/*
		 * for backward compatability
		 */

		basesp[3] = 0;
		if (LSTAT(path, &statb))
			goto not_opaque;
	}
	if (statb.st_ino == st->st_ino && statb.st_dev == st->st_dev)
	{
		errno = ENOENT;
		r = statb.st_ino;
	}
	else
		errno = oerrno;
 not_opaque:
	memcpy(basesp, savebuf, sizeof(state.opaque));
	return r;
}

/*
 * return real path name for path
 */

#if DEBUG

static char*	_pathreal(const char*, int, struct stat*);

char*
pathreal(const char* apath, register int type, struct stat* st)
{
	char*	path = (char*)apath;

	initialize();
	message((-5, "pathreal: ++ %s type=|%s%s%s%s%s%s%s%s%s%s%s", path
		, (type & P_ABSOLUTE) ? "ABSOLUTE|" : state.null
		, (type & P_DOTDOT) ? "DOTDOT|" : state.null
		, (type & P_LSTAT) ? "LSTAT|" : state.null
		, st ? "MYSTAT|" : state.null
		, (type & P_NOOPAQUE) ? "NOOPAQUE|" : state.null
		, (type & P_NOSLASH) ? "NOSLASH|" : state.null
		, (type & P_PATHONLY) ? "PATHONLY|" : state.null
		, (type & P_READLINK) ? "READLINK|" : state.null
		, (type & P_SAFE) ? "SAFE|" : state.null
		, (type & P_SLASH) ? "SLASH|" : state.null
		, (type & P_TOP) ? "TOP|" : state.null
		));
	path = _pathreal(path, type, st);
	message((-5, "pathreal: -- %s level=%d links=%d", path, state.path.level, state.path.nlinks));
	return path;
}

#undef	initialize
#define initialize()
#undef	pathreal
#define pathreal _pathreal
static

#endif

char*
pathreal(const char* apath, register int type, struct stat* st)
{
	char*			path = (char*)apath;
	register char*		sp;
	register char*		cp;
	register char*		ip;
	Table_t*		safe;
	int			oerrno = errno;
	int			opaqued = 0;
	int			len;
	int			vir;
	int			safesize;
	int			safe_dir;
	long			visits;
	char			buf[PATH_MAX + 1];

	static struct stat	stbuf;
	static struct stat	tsbuf;

	state.path.level = state.path.synthesize = state.path.nlinks = 0;
	if (!path)
	{
		errno = EFAULT;
		return 0;
	}
	initialize();
	if (state.in_2d)
	{
		if (!st || (!state.level || *path == '/') && !LSTAT(path, st))
			return path;
		if (state.level && streq(path, ".") && !CHDIR(state.pwd))
		{
			state.level = 0;
			return path;
		}
		return 0;
	}
#if FS
	if (mounted() && (sp = fsreal(state.path.monitor, MSG_stat, state.path.mount)))
		apath = (char*)(path = sp);
#endif

	/*
	 * handle null path, . and / separately
	 */

	if (safe = state.safe ? &state.vsafe : (Table_t*)0)
	{
		type |= P_ABSOLUTE;
		if (!(safesize = state.safe->servicesize))
			safesize = strlen(state.safe->service);
	}
	else
		type &= ~P_SAFE;
 again:
	if (!*path)
	{
		errno = ENOENT;
		return 0;
	}
	cp = sp = path;
	state.path.synthesize = state.path.linksize = 0;
	if (!st)
		st = &stbuf;

	/*
	 * check if virtual dir has been created by another process
	 * only P_PATHONLY|P_TOP calls (usually create or modify link) and
	 * references to "." are checked for performance
	 */

	if (state.level > 0 && state.pwd && ((type & (P_PATHONLY|P_TOP)) && *sp != '/' || *sp == '.' && sp[1] == 0))
	{
		if (!CHDIR(state.pwd))
			state.level = 0;
		else if (!(type & (P_PATHONLY|P_TOP)))
		{
			len = 0;
			state.path.level += (state.path.synthesize = state.level);
			sp = strcpy(state.path.name, state.pwd);
			goto skip;
		}
	}
	if (!state.pwd || sp[1] == 0 && (*sp == '.' || *sp == '/' && !safe))
	{
		if (st != &stbuf && LSTAT(sp, st))
			return 0;
		if (*sp == '/' || !state.pwd && (type & P_PATHONLY))
			strncpy(state.path.name, sp, PATH_MAX);
		else if (!state.pwd)
		{
			/*
			 * treat the current directory as if were empty
			 */

			errno = ENOENT;
			return 0;
		}
		else
			strncpy(state.path.name, state.pwd, PATH_MAX);
		errno = oerrno;
		return state.path.name;
	}

	/*
	 * put absolute pathname into state.path
	 */

	safe_dir = 0;
	if (*path != '/')
	{
		strcpy(state.path.name, state.pwd);
		sp = state.path.name + state.pwdsize;
		*sp++ = '/';
		if (safe && state.pwdsize >= safesize && !strncmp(state.pwd, state.safe->service, safesize) && (!state.pwd[safesize] || state.pwd[safesize] == '/'))
			safe_dir = safesize;
	}
	else
		sp = state.path.name;
	ip = state.path.name + elementsof(state.path.name);
	while (sp < ip && (*sp = *cp++))
		sp++;
	if (type & P_DOTDOT)
		strcpy(sp, "/..");
	sp = state.path.name;
	if (!(ip = pathcanon(sp + safe_dir, 0)))
	{
		errno = ENOENT;
		return 0;
	}
	if (type & (P_DOTDOT|P_NOSLASH))
	{
		/*
		 * remove trailing slashes
		 */

		while (*--ip == '/');
		*++ip = 0;
	}
	else if ((type & P_SLASH) && *(ip - 1) != '/')
		*ip++ = '/';
	if (*(ip - 1) == '/' && ip - sp > 1)
	{
		/*
		 * trailing slash is equivalent to trailing slash-dot
		 * this forces the common-sense interpretation
		 */
#if DEBUG
		if (!(state.test & 010))
#endif
		*ip++ = '.';
		*ip = 0;
	}
	len = ip - sp;

	/*
	 * try to use relative path
	 */

	if (!(type & (P_LSTAT|P_READLINK)))
	{
		for (ip = state.pwd; *ip && *ip == *sp++; ip++);
		if (*ip != 0 || *sp && *sp != '/' || state.level < 0)
			sp = state.path.name;
		else
		{
			state.path.level += (state.path.synthesize = state.level);
			if (state.level && !(type & P_PATHONLY) && st == &stbuf)
			{
				sp = state.path.name;
				len -= state.pwdsize;
			}
			else if (type & P_ABSOLUTE)
				sp = state.path.name;
			else if (*sp == '/')
				sp++;
		}
		if (*sp == 0)
			sp = state.dot;
	}
 skip:
	if ((type & P_NOOPAQUE) && !LSTAT(sp, st) && checkopaque(sp, st))
	{
		message((-1, "%s: remove opaque", sp));
		UNLINK(sp);
		opaqued = 1;
	}
	if (safe && *sp == '/')
	{
		state.path.table = safe;
		cp = pathnext(sp, NiL, NiL);
		state.path.table = safe = 0;
		if (cp)
		{
			state.path.level = 0;
			path = strcpy(buf, sp);
			message((-5, "pathreal: == safe map %s", path));
			type &= ~(P_DOTDOT|P_SAFE);
			goto again;
		}
		if (!*(sp + 1))
		{
			strncpy(sp, state.safe->service, safesize);
			sp[safesize] = 0;
		}
		else if (strncmp(sp, state.safe->service, safesize) || sp[safesize] && sp[safesize] != '/')
		{
			if (*path != '/' && safe_dir)
			{
				errno = EPERM;
				return 0;
			}
			if (sp[1])
				strcpy(buf, sp);
			else
				*buf = 0;
			len = sfsprintf(sp, sizeof(state.path.name), "%-*s%s", safesize, state.safe->service, buf);
			message((-5, "pathreal: == safe next %s", sp));
			if (!pathnext(sp, NiL, NiL))
			{
				errno = EPERM;
				return 0;
			}
		}
		else
			type &= ~P_SAFE;
	}
	if ((type & P_SAFE) && state.path.level)
	{
		errno = EPERM;
		return 0;
	}
	if (type & P_PATHONLY)
	{
		errno = oerrno;
		return sp;
	}
	visits = 0;
	vir = 1;
	while (LSTAT(sp, st))
	{
		if (vir)
		{
			if (apath[0] == '.' && apath[1] == '.' && apath[2] == '.' && !apath[3])
			{
				if (state.level > 0)
				{
					message((-1, "pathreal: %s => %s", apath, sp));
					LSTAT(".", st);
					return sp;
				}
				errno = ENOENT;
				return 0;
			}
			vir = 0;
		}
		if (errno == ENOTDIR)
		{
			/*
			 * check for version instance
			 */

			cp = ip = sp + strlen(sp);
			while (ip > sp && *--ip != '/');
			if (ip < sp)
				return 0;
			while (ip > sp && *--ip == '/');
			if (ip < sp)
				return 0;
			while (ip > sp && *--ip != '/');
			if (*ip == '/')
				ip++;
			while (cp >= ip)
			{
				cp[4] = *cp;
				cp--;
			}
			memcpy(ip, state.opaque, 4);
			if (!LSTAT(sp, st))
				break;
			errno = ENOTDIR;
			return 0;
		}

		if (errno != ENOENT || opaqued)
			return 0;
#if FS
		/*
		 * check user mount
		 */

		if (visits)
		{
			Mount_t*	mp;
			const char*	up;

			if ((mp = getmount(sp, &up)) && (mp->fs->flags & FS_NAME) && (sp = fsreal(mp, MSG_open, (char*)up)) && !LSTAT(sp, st))
				break;
		}
#endif

		/*
		 * search down the viewpath
		 */

		if (type & P_SAFE)
		{
			errno = EPERM;
			return 0;
		}
		if (!pathnext(state.path.name, NiL, &visits))
			return 0;
		sp = state.path.name;
		if (!(type & P_ABSOLUTE))
		{
			/*
			 * try to use relative path
			 */

			for (ip = state.pwd; *ip && *ip == *sp++; ip++);
			if (*ip == 0 && *sp == '/')
				sp++;
			else
				sp = state.path.name;
		}
		if (*sp == 0)
			sp = state.dot;
	}
	if (st->st_nlink > 1 && checkopaque(sp, st))
		return 0;
	if ((type & P_TOP) && state.path.level)
	{
		int	rfd;
		int	wfd;

		if ((rfd = OPEN(sp, O_RDONLY, 0)) < 0)
			sp = 0;
		else
		{
			tsbuf = *st;
			wfd = open(apath, O_WRONLY|O_CREAT|O_TRUNC, st->st_mode & S_IPERM);
			*st = tsbuf;
			if (wfd < 0)
				sp = 0;
			else 
			{
				if (fs3d_copy(rfd, wfd, st))
					sp = 0;
				CLOSE(wfd);
			}
			CLOSE(rfd);
		}
		if (!sp)
		{
			errno = EROFS;
			return 0;
		}
		if (st == &stbuf)
			st = 0;
		return pathreal(apath, P_PATHONLY, st);
	}
	IVIEW(st, state.path.level);
	if (state.path.synthesize)
	{
		if (state.path.level < state.level)
		{
			if (len)
			{
				ip  = state.path.name + strlen(state.path.name) - len;
				len = *ip;
				*ip = 0;
			}
			if (!CHDIR(state.path.name))
				state.level = state.path.level;
			message((-1, "chdir=%s level=%d", state.path.name, state.level));
			*ip = len;
		}
		else if (S_ISDIR(st->st_mode))
		{
			int		mask;
			static int	uid = -1;
			static int	gid;

			umask(mask = umask(0));
			st->st_mode = (st->st_mode | (S_IRWXU|S_IRWXG|S_IRWXO)) & ~(mask & (S_IRWXU|S_IRWXG|S_IRWXO));
			if (uid == -1)
			{
				uid = geteuid();
				gid = getegid();
			}
			st->st_uid = uid;
			st->st_gid = gid;
		}
	}
	ip = sp;

	/*
	 * symbolic links handled specially
	 * get filename from pathname
	 */

	if (S_ISLNK(st->st_mode) && (len = checklink(sp, st, type)) > 1 && !(type & (P_LSTAT|P_READLINK)) && state.path.nlinks++ < MAXSYMLINKS)
	{
		path = strcpy(buf, state.path.name);
		message((-1, "pathreal: == again %s", path));
		if (*path != '/')
			state.path.level = 0;
		type &= ~(P_DOTDOT|P_SAFE);
		goto again;
	}
#if VCS && defined(VCS_REAL)
	VCS_REAL(state.path.name, st);
#endif
	errno = oerrno;
	return sp;
}

/*
 * check whether sp points to a version object and find version instance
 * sp is canonicalized and points into state.path
 * when called from unlink, (type & P_PATHONLY) is set
 *     -1 for non-existent link
 *     length of path for a relative link that is not a version object
 *     0  otherwise
 *    state.path.linkname and state.path.linksize are set for version object
 */

int
checklink(const char* asp, struct stat* st, int type)
{
	register char*	sp = (char*)asp;
	register char*	ip;
	register int	len;
	register int	n;
	register char*	bp;

	char		buf[PATH_MAX + 1];

	if (sp < state.path.name || sp >= state.path.name + sizeof(state.path.name))
	{
		message((-1, "AHA#%d checklink bounds sp=%p state.path.name=%p sp=%s", __LINE__, sp, state.path.name, sp));
		sp = strncpy(state.path.name, sp, sizeof(state.path.name) - 1);
	}
	while (S_ISLNK(st->st_mode))
	{
		/*
		 * go to the last component
		 */

		if (ip = strrchr(sp, '/'))
			ip++;
		else
			ip = sp;
		strcpy(buf, ip);
		len = (state.path.name + sizeof(state.path.name) - 1) - ip;
		if ((len = READLINK(sp, ip, len)) < 0)
		{
			message((-1, "%s: cannot readlink", sp));
			return 0;
		}
		state.path.linkname = ip;
		state.path.linksize = len;

		/*
		 * check for relative link
		 */

		if (*ip != '/')
		{
			ip[len] = 0;
			if (*ip == *state.opaque && !memcmp(ip, state.opaque, 4) && !memcmp(ip + 4, buf, n = strlen(buf)))
			{
				/*
				 * version object
				 */

				ip += n + 4;
				if (instance(state.path.name, ip, st, 0))
				{
					state.path.linksize = strlen(state.path.linkname);
					if (type & P_LSTAT)
					{
						st->st_size = state.path.linksize;
						st->st_mode &= S_IPERM;
#ifdef S_IFLNK
						st->st_mode |= S_IFLNK;
#endif
						return 0;
					}
					continue;
				}
				errno = ENOENT;
				return -1;
			}
			else if (!(type & (P_LSTAT|P_PATHONLY|P_READLINK)) && *ip == '.' && *(ip + 1) == '.' && (*(ip + 2) == '/' || *(ip + 2) == 0))
			{
				memcpy(buf, ip, len + 1);
				bp = state.path.name;
				while (ip > bp && *(ip - 1) == '/')
					ip--;
				for (;;)
				{
					*(sp = ip) = 0;
					while (ip > bp && *--ip != '/');
					while (ip > bp && *(ip - 1) == '/')
						ip--;
					if (*ip == '/')
						ip++;
					if ((n = READLINK(state.path.name, ip, PATH_MAX - (ip - state.path.name))) <= 0)
					{
						*sp++ = '/';
						state.path.linkname = (char*)memcpy(sp, buf, len + 1);
						return sp + len - state.path.name;
					}
					if (*ip == '/')
						ip = (char*)memcpy(bp = state.path.name, ip, n);
					else if (ip > bp)
						*(ip - 1) = '/';
					ip += n;
				}
			}
		}

		/*
		 * restore last component
		 */

		if (!(type & P_READLINK))
			strcpy(ip, buf);
		break;
	}
	return 0;
}
