#ifndef _BLURB_
#define _BLURB_
/*

            Coda: an Experimental Distributed File System
                             Release 3.1

          Copyright (c) 1987-1995 Carnegie Mellon University
                         All Rights Reserved

Permission  to  use, copy, modify and distribute this software and its
documentation is hereby granted,  provided  that  both  the  copyright
notice  and  this  permission  notice  appear  in  all  copies  of the
software, derivative works or  modified  versions,  and  any  portions
thereof, and that both notices appear in supporting documentation, and
that credit is given to Carnegie Mellon University  in  all  documents
and publicity pertaining to direct or indirect use of this code or its
derivatives.

CODA IS AN EXPERIMENTAL SOFTWARE SYSTEM AND IS  KNOWN  TO  HAVE  BUGS,
SOME  OF  WHICH MAY HAVE SERIOUS CONSEQUENCES.  CARNEGIE MELLON ALLOWS
FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION.   CARNEGIE  MELLON
DISCLAIMS  ANY  LIABILITY  OF  ANY  KIND  FOR  ANY  DAMAGES WHATSOEVER
RESULTING DIRECTLY OR INDIRECTLY FROM THE USE OF THIS SOFTWARE  OR  OF
ANY DERIVATIVE WORK.

Carnegie  Mellon  encourages  users  of  this  software  to return any
improvements or extensions that  they  make,  and  to  grant  Carnegie
Mellon the rights to redistribute these changes without encumbrance.
*/

static char *rcsid = "$Header: vproc_pathname.c,v 3.3.1.1 95/10/11 10:28:15 raiff Exp $";
#endif /*_BLURB_*/






/*
 *
 *    Implementation of the Venus path expansion facility.
 *
 */

#ifdef __cplusplus
extern "C" {
#endif __cplusplus

#include <stdio.h>
#include <strings.h>

#ifdef __cplusplus
}
#endif __cplusplus

/* interfaces */
#include <vice.h>

/* from venus */
#include "fso.h"
#include "venus.private.h"
#include "venusrecov.h"
#include "vproc.h"


inline void GetComponent(char **pptr_addr, int *plen_addr, char *nextcompptr) {
    char c;
    while ((c = **pptr_addr) && c != '/') {
	/* Move on to next character. */
	*nextcompptr++ = c;
	(*pptr_addr)++;
	(*plen_addr)--;
    }
    *nextcompptr = 0;	    /* make comp a real string */
}


inline void SkipSlashes(char **pptr_addr, int *plen_addr) {
    while (**pptr_addr == '/') {
	(*pptr_addr)++;
	(*plen_addr)--;
    }
}


/* Should be possible to inhibit symlink expansion (i.e., consider it an error)! */
/* Returns {0, 1}.  On 0, u.u_error is set to appropriate Unix errno and *vpp is 0. */
/* On 1, u.u_error is 0 and *vpp is a valid vnode pointer. */
/* Caller must set u_cred, u_priority, u.u_cdir and u_nc fields as appropriate. */
int vproc::namev(char *path, int flags, struct vnode **vpp) {
    LOG(1, ("vproc::namev: %s, %d\n", path, flags));

    *vpp = 0;

    /* Initialize some global variables. */
    u.u_error = 0;
    u.u_flags = flags;
    struct vnode *pvp = 0;
    struct vnode *vp = 0;
    char comp[MAXNAMLEN];
    comp[0] = '\0';
    char workingpath[MAXPATHLEN];
    strcpy(workingpath, path);
    char *pptr = workingpath;
    int plen = strlen(pptr);
    int nlinks = 0;

    /* Initialize the parent (i.e., the root of the expansion). */
    {
	struct vfs vfs; bzero(&vfs, sizeof(struct vfs));
	struct cfid fid;
	fid.cfid_len = sizeof(ViceFid);
	fid.cfid_fid = u.u_cdir;
	vget(&vfs, &pvp, (struct fid *)&fid);
	if (u.u_error) goto Exit;

	/* Skip over leading slashes. */
	if (plen != 0) SkipSlashes(&pptr, &plen);

	/* Check for degenerate case of asking for Cdir. */
	if (plen == 0) {
	    *vpp = pvp;
	    goto Exit;
	}
    }

    /* Each loop iteration moves down through one pathname component. */
    for (;;) {
	if (plen <= 0)
	    { print(logFile); Choke("vproc::namev: plen <= 0"); }

	/* Get the next component. */
	GetComponent(&pptr, &plen, comp);

	/* Skip over trailing slashes. */
	SkipSlashes(&pptr, &plen);

	/* Handle ".." out of venus here! */
	if (FID_EQ(VTOC(pvp)->c_fid, rootfid) && STREQ(comp, "..")) {
	    LOG(100, ("vproc::namev: .. out of this venus\n"));

	    u.u_error = ENOENT;
	    goto Exit;
	}

	/* Now lookup the object in the directory. */
	lookup(pvp, comp, &vp);
	if (u.u_error) goto Exit;

	/* We have the new object.  The next action depends on what type of object it is. */
	/* If it is a file, we check that we are at the end of the path; */
	/* If it is a directory, we simply make it the new parent object. */
	/* If it is a symbolic link, we reset the pathname to be it, and continue scanning. */
	switch(vp->v_mode & VFMT) {
	    case VREG:
		{
		if (plen == 0) {
		    DISCARD_VNODE(pvp);
		    pvp = 0;
		    *vpp = vp;
		    goto Exit;
		}

		/* File must be the last comp in the path. */
		u.u_error = ENOTDIR;
		goto Exit;
		}

	    case VDIR:
		{
		if (plen == 0) {
		    DISCARD_VNODE(pvp);
		    pvp = 0;
		    *vpp = vp;
		    goto Exit;
		}

		/* Child becomes the new parent. */
		DISCARD_VNODE(pvp);
		pvp = vp;
		vp = 0;
		comp[0] = '\0';

		break;
		}

	    case VLNK:
		{
		/* Return the link if we are not to "follow" and this is the last component. */
		if (plen == 0 && !(u.u_flags & FOLLOW_SYMLINKS)) {
		    DISCARD_VNODE(pvp);
		    pvp = 0;
		    *vpp = vp;
		    goto Exit;
		}

		/* Guard against looping. */
		if (++nlinks > MAXSYMLINKS) {
		    u.u_error = ELOOP;
		    goto Exit;
		}

		/* Examine the link contents.  We will find either: */
		/*    - an absolute pathname within this venus; */
		/*    - an absolute pathname leaving this venus; */
		/*    - a relative pathname. */
		/* Note that the result of ReadLink is not necessarily a proper string. */

		/* Get the link contents. */
		char linkdata[MAXPATHLEN];
		int linklen = MAXPATHLEN;
		struct iovec aio;
		aio.iov_base = linkdata;
		aio.iov_len = linklen;
		struct uio auio;
		auio.uio_iov = &aio;
		auio.uio_iovcnt = 1;
		auio.uio_resid = linklen;
		readlink(vp, &auio);
		if (u.u_error) goto Exit;
		linklen -= auio.uio_resid;
		if (linklen == 0) {
		    u.u_error = EINVAL;
		    goto Exit;
		}

		/* Append the trailing part of the original pathname. */
		if (linklen + plen > MAXPATHLEN) {
		    u.u_error = ENAMETOOLONG;
		    goto Exit;
		}
		if (plen > 0) {
		    linkdata[linklen] = '/';
		    linkdata[linklen + 1] = '\0';
		    strcat(linkdata, pptr);
		}
		else
		    linkdata[linklen] = '\0';

		/* Figure out the type of the new path and act accordingly. */
		static int venusRootLength = -1;
		if (venusRootLength == -1)
		    venusRootLength = strlen(venusRoot);
		if (linklen >= venusRootLength &&
		    STRNEQ(linkdata, venusRoot, venusRootLength) &&
		    (linklen == venusRootLength || linkdata[venusRootLength] == '/')) {
		    LOG(100, ("vproc::namev: abspath within this venus (%s)\n", linkdata));

		    /* Copy the part after the VenusRoot to workingpath. */
		    strcpy(workingpath, linkdata + venusRootLength);

		    /* Release the child. */
		    DISCARD_VNODE(vp);
		    vp = 0;
		    comp[0] = '\0';

		    /* Release the parent and reset it to the VenusRoot. */
		    DISCARD_VNODE(pvp);
		    pvp = 0;
		    struct vfs vfs; bzero(&vfs, sizeof(struct vfs));
		    struct cfid fid;
		    fid.cfid_len = sizeof(ViceFid);
		    fid.cfid_fid = rootfid;
		    vget(&vfs, &pvp, (struct fid *)&fid);
		    if (u.u_error) goto Exit;
		}
		else if (linkdata[0] == '/') {
		    LOG(100, ("vproc::namev: abspath leaving this venus (%s)\n", linkdata));

		    u.u_error = ENOENT;
		    goto Exit;
		}
		else {
		    LOG(100, ("vproc::namev: relpath (%s)\n", linkdata));

		    /* Copy the whole path to workingpath. */
		    strcpy(workingpath, linkdata);

		    /* Release the child. */
		    DISCARD_VNODE(vp);
		    vp = 0;
		    comp[0] = '\0';

		    /* Parent stays the same. */
		}

		/* Reset the name pointers. */
		pptr = workingpath;
		plen = strlen(pptr);

		/* Skip over leading slashes. */
		SkipSlashes(&pptr, &plen);

		/* Check (again) for degenerate case of asking for root. */
		if (plen == 0) {
		    *vpp = pvp;
		    goto Exit;
		}

		break;
		}

	    default:
		{
		print(logFile);
		Choke("vproc::namev: bogus vnode type (%d)!", vp->v_mode & VFMT);
		}
	}
    }

Exit:
    LOG(1, ("vproc::namev: returns %s\n", VenusRetStr(u.u_error)));
    if (u.u_error) {
	if (pvp != 0) { DISCARD_VNODE(pvp); pvp = 0; }
	if (vp != 0) { DISCARD_VNODE(vp); vp = 0; }
    }
    return(u.u_error == 0);
}


/* Map fid to full or volume-relative pathname.  Kind of like getwd(). */
/* XXX - Need fsobj::IsRealRoot predicate which excludes "fake" roots! -JJK */
void vproc::GetPath(ViceFid *fid, char *out, int *outlen, int fullpath) {
    LOG(1, ("vproc::GetPath: (%x.%x.%x), %d\n",
		fid->Volume, fid->Vnode, fid->Unique, fullpath));

    if (*outlen < MAXPATHLEN)
	{ u.u_error = ENAMETOOLONG; *outlen = 0; goto Exit; }

    /* Handle degenerate case of file system or volume root! */
    if (fid->Vnode == ROOT_VNODE && fid->Unique == ROOT_UNIQUE) {
	if (!fullpath) {
	    strcpy(out, ".");
	    *outlen = 2;
	    goto Exit;
	}

	if (fid->Volume == rootfid.Volume) {
	    strcpy(out, venusRoot);
	    *outlen = strlen(venusRoot) + 1;
	    goto Exit;
	}
    }

    for (;;) {
	fsobj *f = 0;
	ViceFid currFid;
	ViceFid prevFid;
	out[0] = '\0';
	*outlen = 0;

	Begin_VFS(fid->Volume, VFSOP_VGET);
	if (u.u_error) goto Exit;

	/* Initialize the "prev" and "current" fids to the target object and its parent, respectively. */
	u.u_error = FSDB->Get(&f, fid, CRTORUID(u.u_cred), RC_STATUS);
	if (u.u_error) goto FreeLocks;
	if (/*f->IsRealRoot()*/(f->fid.Vnode == ROOT_VNODE && f->fid.Unique == ROOT_UNIQUE)) {
	    fsobj *newf = f->u.mtpoint;
	    FSDB->Put(&f);

	    f = newf;
	    if (f == 0) {
		u.u_error = ENOENT;
		goto Exit;
	    }
	    f->Lock(RD);
	}
	prevFid = f->fid;
	currFid = f->pfid;
	FSDB->Put(&f);

	for (;;) {
	    /* Get the current object. */
	    u.u_error = FSDB->Get(&f, &currFid, CRTORUID(u.u_cred), RC_DATA);
	    if (u.u_error) goto FreeLocks;

	    /* Lookup the name of the previous component. */
	    char comp[MAXNAMLEN];
	    u.u_error = f->dir_LookupByFid(comp, &prevFid);
	    if (u.u_error) goto FreeLocks;

	    /* Prefix the pathname with this latest component. */
	    /* I know this is inefficient; I don't care! -JJK */
	    {
		char tbuf[MAXPATHLEN];
		strcpy(tbuf, out);
		strcpy(out, comp);
		if (tbuf[0] != '\0') {
		    strcat(out, "/");
		    strcat(out, tbuf);
		}
	    }

	    /* Termination condition is when current object is root. */
	    if (/*f->IsRealRoot()*/(f->fid.Vnode == ROOT_VNODE && f->fid.Unique == ROOT_UNIQUE)) {
		if (!fullpath) {
		    char tbuf[MAXPATHLEN];
		    strcpy(tbuf, out);
		    strcpy(out, "./");
		    strcat(out, tbuf);
		    *outlen = strlen(out) + 1;

		    goto FreeLocks;
		}

		if (f->fid.Volume == rootfid.Volume) {
		    char tbuf[MAXPATHLEN];
		    strcpy(tbuf, out);
		    strcpy(out, venusRoot);
		    strcat(out, "/");
		    strcat(out, tbuf);
		    *outlen = strlen(out) + 1;

		    goto FreeLocks;
		}
	    }

	    /* Move on to next object. */
	    if (/*f->IsRealRoot()*/(f->fid.Vnode == ROOT_VNODE && f->fid.Unique == ROOT_UNIQUE)) {
		fsobj *newf = f->u.mtpoint;
		FSDB->Put(&f);

		f = newf;
		if (f == 0) {
		    u.u_error = ENOENT;
		    goto FreeLocks;
		}
		f->Lock(RD);
	    }
	    prevFid = f->fid;
	    currFid = f->pfid;
	    FSDB->Put(&f);
	}

FreeLocks:
	FSDB->Put(&f);
	int retry_call = 0;
	End_VFS(&retry_call);
	if (!retry_call) break;
    }

Exit:
    if (u.u_error != 0) {
	out[0] = '\0';
	*outlen = 0;
    }
    LOG(1, ("vproc::GetPath: returns %s (%s)\n",
	     VenusRetStr(u.u_error), out));
}
