/*
 * getcwd - get current working directory
 */

#ident "$Header: getcwd.c 1.5 89/03/25 $"
/*LINTLIBRARY*/

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*
 * Library routine to get the current working directory.  This will
 * walk back from the current working directory to the root directory
 * building a list of the directory names it finds on the walk back up.
 * When done it will change back to the directory it started from
 * before returning.
 *
 * Usage:
 *
 * char *getcwd(buf, size)
 * char *arg1;
 * int size;
 * 
 * 
 * Pass "buf" as a character buffer to contain the null terminated
 * current directory path.  Pass "size" as the size of this buffer.
 * if "buf" is "(char *) 0" then a malloc of "size" bytes will
 * be done to hold the working directory path.
 */

/*
 * From the System V, Version 3.2 comment:
 *
 * Library routine to GET the Current Working Directory.
 * arg1 is a pointer to a character buffer into which the
 * path name of the current directory is placed by the
 * subroutine.  arg1 may be zero, in which case the 
 * subroutine will call malloc to get the required space.
 * arg2 is the length of the buffer space for the path-name.
 * If the actual path-name is longer than (arg2-2), or if
 * the value of arg2 is not at least 3, the subroutine will
 * return a value of zero, with errno set as appropriate.
 */

/*
 * Much of this code was taken from the 4.3-tahoe distribution:
 *
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/errno.h>
#include <string.h>

char *strcpy();
char *malloc();
static int pathsize;			/* pathname length */
extern int errno;

char *
getcwd(buf, size)
	char *buf;
	int size;
{
	char pathbuf[MAXPATHLEN];		/* temporary pathname buffer */
	char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */
	char curdir[MAXPATHLEN];	/* current directory buffer */
	char *dptr = curdir;		/* directory pointer */
	char *prepend();		/* prepend dirname to pathname */
	dev_t cdev, rdev;		/* current & root device number */
	ino_t cino, rino;		/* current & root inode number */
	DIR *dirp;			/* directory stream */
	struct dirent *dir;		/* directory entry struct */
	struct stat d, dd;		/* file status struct */
	int did_malloc = 0;


	if (size <= 0) {
		errno = EINVAL;
		return((char *) 0);
	}

	if (size == 1) {
		errno = ERANGE;
		return((char *) 0);
	}

	if (buf == (char *) 0) {
		if ((buf = malloc((unsigned) size)) == 0) {
			errno = ENOMEM;
			return((char *) 0);
		}
		did_malloc = 1;
	}

	pathsize = 0;
	*pnptr = '\0';
	if (stat("/", &d) < 0)
		goto fail;
	rdev = d.st_dev;
	rino = d.st_ino;
	strcpy(dptr, "./");
	dptr += 2;
	if (stat(curdir, &d) < 0)
		goto fail;

	for (;;) {
		if (d.st_ino == rino && d.st_dev == rdev)
			break;		/* reached root directory */
		cino = d.st_ino;
		cdev = d.st_dev;
		strcpy(dptr, "../");
		dptr += 3;
		if ((dirp = opendir(curdir)) == NULL)
			goto fail;
		fstat(dirp->dd_fd, &d);
		if (cdev == d.st_dev) {
			if (cino == d.st_ino) {
				/* reached root directory */
				closedir(dirp);
				break;
			}
			do {
				if ((dir = readdir(dirp)) == NULL) {
					closedir(dirp);
					goto fail;
				}
			} while (dir->d_ino != cino);
		} else
			do {
				if ((dir = readdir(dirp)) == NULL) {
					closedir(dirp);
					goto fail;
				}
				/*
				 * Do not need to follow symbolic links.
				 * So use lstat() instead of stat.
				 *
				 * NFS note:
				 * This allows one to hide nfs mounts in
				 * sub-directory from the local filesystem
				 * mount points and use symbolic links to
				 * point at the nfs mounted on directories.
				 * This setup can help prevent users using
				 * local resources from being hung up
				 * stat'ing a remote resource where the server
				 * is down. This happens when the remote
				 * resource directory entry occurs before
				 * the local one. 
				 */
				strcpy(dptr, dir->d_name);
				lstat(curdir, &dd);
			} while(dd.st_ino != cino || dd.st_dev != cdev);
		closedir(dirp);
		pnptr = prepend("/", prepend(dir->d_name, pnptr));
	}
	if (*pnptr == '\0')		/* current dir == root dir */
		strcpy(buf, "/");
	else {
		if (strlen(pnptr) > size - 1) {
			if (did_malloc)
				free(buf);
			errno = ERANGE;
			return((char *) 0);
		}
		strcpy(buf, pnptr);
	}
	return (buf);

fail:
	if (did_malloc)
		free(buf);
	errno = EACCES;
	return((char *) 0);
}

/*
 * prepend() tacks a directory name onto the front of a pathname.
 */
static char *
prepend(dirname, pathname)
	register char *dirname;
	register char *pathname;
{
	register int i;			/* directory name size counter */

	for (i = 0; *dirname != '\0'; i++, dirname++)
		continue;
	if ((pathsize += i) < MAXPATHLEN)
		while (i-- > 0)
			*--pathname = *--dirname;
	return (pathname);
}
