/*
	getcwd -- get pathname of current working directory

	public-domain implementation

	last edit:	03-Nov-1990	Gwyn@BRL.MIL

	complies with the following standards:
		IEEE Std 1003.1-1988
		SVID Issue 3
		X/Open Portability Guide Issue 2 (when "XPG2" is defined)
		X/Open Portability Guide Issue 3

	This implementation of getcwd() can be used to replace the UNIX
	System V library routine (which uses popen() to capture the output of
	the "pwd" command).  Once that is done, "pwd" can be reimplemented as
	just puts(getcwd((char*)0,0)), assuming "XPG2" is defined below.

	This implementation depends on every directory having entries for
	"." and "..".  It also depends on the internals of the <dirent.h>
	data structures to some degree.

	I considered using chdir() to ascend the hierarchy, followed by a
	final chdir() to the path being returned by getcwd() to restore the
	location, but decided that error recovery was too difficult that way.
	The algorithm I settled on was inspired by my rewrite of the "pwd"
	utility, combined with the dotdots[] array trick from the SVR2 shell.
*/
#define XPG2	/* define to support obsolete XPG2-mandated feature */


#include	<sys/types.h>
#include	<sys/stat.h>

#ifdef M_XENIX
# include        <sys/ndir.h>
# define dirent direct
#else
# include        <dirent.h>
#endif

#include	<errno.h>
#include	<string.h>

typedef char	*pointer;		/* (void *) if you have it */

extern void	free();
extern pointer	malloc();
extern int	fstat(), stat();

extern int	errno;			/* normally done by <errno.h> */

#ifndef NULL
#define	NULL	0			/* amorphous null pointer constant */
#endif

#ifndef NAME_MAX
#define	NAME_MAX	255		/* maximum directory entry size */
#endif


char	*
getcwd( buf, size )			/* returns pointer to CWD pathname */
	char		*buf;		/* where to put name (NULL to malloc) */
	int		size;		/* size of buf[] or malloc()ed memory */
	{
	static char	dotdots[] =
"../../../../../../../../../../../../../../../../../../../../../../../../../..";
	char		*dotdot;	/* -> dotdots[.], right to left */
	DIR		*dirp;		/* -> parent directory stream */
	struct dirent	*dir;		/* -> directory entry */
	struct stat	stat1,
			stat2;		/* info from stat() */
	struct stat	*d = &stat1;	/* -> info about "." */
	struct stat	*dd = &stat2;	/* -> info about ".." */
	register char	*buffer;	/* local copy of buf, or malloc()ed */
	char		*bufend;	/* -> buffer[size] */
	register char	*endp;		/* -> end of reversed string */
	register char	*dname;		/* entry name ("" for root) */
	int		serrno = errno;	/* save entry errno */

	if ( buf != NULL && size <= 0
#ifndef XPG2
	  || buf == NULL
#endif
	   )	{
		errno = EINVAL;		/* invalid argument */
		return NULL;
		}

	buffer = buf;
#ifdef XPG2
	if ( buf == NULL		/* wants us to malloc() the string */
	  && (buffer = (char *) malloc( (unsigned) size )) == NULL
	/* XXX -- actually should probably not pay attention to "size" arg */
	   )	{
		errno = ENOMEM;		/* cannot malloc() specified size */
		return NULL;
		}
#endif

	if ( stat( ".", dd ) != 0 )	/* prime the pump */
		goto error;		/* errno already set */

	endp = buffer;			/* initially, empty string */
	bufend = &buffer[size];

	for ( dotdot = &dotdots[sizeof dotdots]; dotdot != dotdots; )
		{
		dotdot -= 3;		/* include one more "/.." section */
					/* (first time is actually "..") */

		/* swap stat() info buffers */
		{
		register struct stat	*temp = d;

		d = dd;			/* new current dir is old parent dir */
		dd = temp;
		}

		if ( (dirp = opendir( dotdot )) == NULL )	/* new parent */
			goto error;	/* errno already set */

		if ( fstat( dirp->dd_fd, dd ) != 0 )
			{
			serrno = errno;	/* set by fstat() */
			(void)closedir( dirp );
			errno = serrno;	/* in case closedir() clobbered it */
			goto error;
			}

		if ( d->st_dev == dd->st_dev )
			{		/* not crossing a mount point */
			if ( d->st_ino == dd->st_ino )
				{	/* root directory */
				dname = "";
				goto append;
				}

			do
				if ( (dir = readdir( dirp )) == NULL )
					{
					(void)closedir( dirp );
					errno = ENOENT;	/* missing entry */
					goto error;
					}
			while ( dir->d_ino != d->st_ino );
			}
		else	{		/* crossing a mount point */
			struct stat	t;	/* info re. test entry */
			char		name[sizeof dotdots + 1 + NAME_MAX];

			(void)strcpy( name, dotdot );
			dname = &name[strlen( name )];
			*dname++ = '/';

			do	{
				if ( (dir = readdir( dirp )) == NULL )
					{
					(void)closedir( dirp );
					errno = ENOENT;	/* missing entry */
					goto error;
					}

				(void)strcpy( dname, dir->d_name );
				/* must fit if NAME_MAX is not a lie */
				}
			while ( stat( name, &t ) != 0
			     || t.st_ino != d->st_ino
			     || t.st_dev != d->st_dev
			      );
			}

		dname = dir->d_name;

		/* append "/" and reversed dname string onto buffer */
    append:
		if ( endp != buffer	/* avoid trailing / in final name */
		  || dname[0] == '\0'	/* but allow "/" when CWD is root */
		   )
			*endp++ = '/';

		{
		register char	*app;	/* traverses dname string */

		for ( app = dname; *app != '\0'; ++app )
			;

		if ( app - dname >= bufend - endp )
			{
			(void)closedir( dirp );
			errno = ERANGE;	/* won't fit allotted space */
			goto error;
			}

		while ( app != dname )
			*endp++ = *--app;
		}

		(void)closedir( dirp );

		if ( dname[0] == '\0' )	/* reached root; wrap it up */
			{
			register char	*startp;	/* -> buffer[.] */

			*endp = '\0';	/* plant null terminator */

			/* straighten out reversed pathname string */
			for ( startp = buffer; --endp > startp; ++startp )
				{
				char	temp = *endp;

				*endp = *startp;
				*startp = temp;
				}

			errno = serrno;	/* restore entry errno */
			/* XXX -- if buf==NULL, realloc here? */
			return buffer;
			}
		}

	errno = ENOMEM;			/* actually, algorithm failure */

    error:
	if ( buf == NULL )
		free( (pointer)buffer );

	return NULL;
	}

