Blob Blame History Raw
/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*          http://www.eclipse.org/org/documents/epl-v10.html           *
*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * cd [-LP]  [dirname]
 * cd [-LP]  [old] [new]
 * pwd [-LP]
 *
 *   David Korn
 *   AT&T Labs
 *   research!dgk
 *
 */

#include	"defs.h"
#include	<stak.h>
#include	<error.h>
#include	"variables.h"
#include	"path.h"
#include	"name.h"
#include	"builtins.h"
#include	<ls.h>

#ifndef EINTR_REPEAT
#   define EINTR_REPEAT(expr) while((expr) && (errno == EINTR)) errno=0;
#endif

/*
 * Invalidate path name bindings to relative paths
 */
static void rehash(register Namval_t *np,void *data)
{
	Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp;
	NOT_USED(data);
	if(pp && *pp->name!='/')
		_nv_unset(np,0);
}

/*
 * Obtain a file handle to the directory "path" relative to directory
 * "dir", or open a NFSv4 xattr directory handle for file dir/path.
 */
int sh_diropenat(Shell_t *shp, int dir, const char *path, bool xattr)
{
	int fd,shfd;
	int savederrno=errno;
	struct stat fs;
#ifndef AT_FDCWD
	NOT_USED(dir);
#endif
#ifndef O_XATTR
	NOT_USED(xattr);
#endif

#ifdef O_XATTR
	if(xattr)
	{
		int apfd; /* attribute parent fd */
		/* open parent node... */
		EINTR_REPEAT((apfd = openat(dir, path, O_RDONLY|O_NONBLOCK|O_cloexec)) < 0);
		if(apfd < 0)
			return -1;

		/* ... and then open a fd to the attribute directory */
		 EINTR_REPEAT((fd = openat(apfd, e_dot, O_XATTR|O_cloexec)) < 0);

		savederrno = errno;
		EINTR_REPEAT(close(apfd) < 0);
		errno = savederrno;
	}
	else
#endif
	{
#ifdef AT_FDCWD
		/*
		 * Open directory. First we try without |O_SEARCH| and
		 * if this fails with EACCESS we try with |O_SEARCH|
		 * again.
		 * This is required ...
		 * - ... because some platforms may require that it can
		 * only be used for directories while some filesystems
		 * (e.g. Reiser4 or HSM systems) allow a |fchdir()| into
		 * files, too)
		 * - ... to preserve the semantics of "cd", e.g.
		 * otherwise "cd" would return [No access] instead of
		 * [Not a directory] for files on filesystems which do
		 * not allow a "cd" into files.
		 * - ... to allow that a
		 * $ redirect {n}</etc ; cd /dev/fd/$n # works on most
		 * platforms.
		 */
		EINTR_REPEAT((fd = openat(dir, path, O_RDONLY|O_NONBLOCK|O_cloexec)) < 0);
#   ifdef O_SEARCH
		if((fd < 0) && (errno == EACCES))
		{
			EINTR_REPEAT((fd = openat(dir, path, O_SEARCH|O_cloexec)) < 0)
		}
#   endif
#else
		/*
		 * Version of openat() call above for systems without
		 * openat API. This only works because we basically
		 * gurantee that |dir| is always the same place as
		 * |cwd| on such machines (but this won't be the case
		 * in the future).
		 */
		/*
		 * This |fchdir()| call is not needed (yet) since
		 * all consumers do not use |dir| when |AT_FDCWD|
		 * is not available.
		 *
		 * fchdir(dir);
		 */
		EINTR_REPEAT((fd = open(path, O_cloexec)) < 0);
#endif
	}

	if(fd < 0)
		return fd;
	
	if (!fstat(fd, &fs) && !S_ISDIR(fs.st_mode))
	{
	  close(fd);
	  errno = ENOTDIR;
	  return -1;
	}

	/* Move fd to a number > 10 and *register* the fd number with the shell */
	shfd = fcntl(fd, F_dupfd_cloexec, 10);
	savederrno=errno;
	close(fd);
	errno=savederrno;
	return(shfd);
}

int	b_cd(int argc, char *argv[],Shbltin_t *context)
{
	register char *dir;
	Pathcomp_t *cdpath = 0;
	register const char *dp;
	register Shell_t *shp = context->shp;
	int saverrno=0;
	int rval;
	bool flag=false,xattr=false;
	char *oldpwd;
	int newdirfd;
	Namval_t *opwdnod, *pwdnod;
	if(sh_isoption(SH_RESTRICTED))
		errormsg(SH_DICT,ERROR_exit(1),e_restricted+4);
	while((rval = optget(argv,sh_optcd))) switch(rval)
	{
		case 'L':
			flag = false;
			break;
		case 'P':
			flag = true;
			break;
		case ':':
			errormsg(SH_DICT,2, "%s", opt_info.arg);
			break;
		case '?':
			errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg);
			break;
	}
	argv += opt_info.index;
	argc -= opt_info.index;
	dir =  argv[0];
	if(error_info.errors>0 || argc >2)
		errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0));
	oldpwd = (char*)shp->pwd;
	opwdnod = (shp->subshell?sh_assignok(OLDPWDNOD,1):OLDPWDNOD); 
	pwdnod = (shp->subshell?sh_assignok(PWDNOD,1):PWDNOD); 
	if(argc==2)
		dir = sh_substitute(oldpwd,dir,argv[1]);
	else if(!dir)
		dir = nv_getval(HOME);
	else if(*dir == '-' && dir[1]==0)
		dir = nv_getval(opwdnod);
	if(!dir || *dir==0)
		errormsg(SH_DICT,ERROR_exit(1),argc==2?e_subst+4:e_direct);
#if _WINIX
	if(*dir != '/' && (dir[1]!=':'))
#else
	if(*dir != '/')
#endif /* _WINIX */
	{
		if(!(cdpath = (Pathcomp_t*)shp->cdpathlist) && (dp=sh_scoped(shp,CDPNOD)->nvalue.cp))
		{
			if(cdpath=path_addpath(shp,(Pathcomp_t*)0,dp,PATH_CDPATH))
			{
				shp->cdpathlist = (void*)cdpath;
				cdpath->shp = shp;
			}
		}
		if(!oldpwd)
			oldpwd = path_pwd(shp,1);
	}
	if(*dir=='.')
	{
		/* test for pathname . ./ .. or ../ */
		int	n=0;
		char	*sp;
		for(dp=dir; *dp=='.'; dp++)
		{
			if(*++dp=='.' && (*++dp=='/' || *dp==0))
				n++;
			else if(*dp && *dp!='/')
			{
				dp--;
				break;
			}
			if(*dp==0)
				break;
		}
		if(n)	
		{
			cdpath = 0;
			sp = oldpwd + strlen(oldpwd);
			while(n--)
			{
				while(--sp > oldpwd && *sp!='/');
				if(sp==oldpwd)
					break;
				
			}
			sfwrite(shp->strbuf,oldpwd,sp+1-oldpwd);
			sfputr(shp->strbuf,dp,0);
			dir = sfstruse(shp->strbuf);
		}
	}
	rval = -1;
	do
	{
		dp = cdpath?cdpath->name:"";
		cdpath = path_nextcomp(shp,cdpath,dir,0);
#if _WINIX
                if(*stakptr(PATH_OFFSET+1)==':' && isalpha(*stakptr(PATH_OFFSET)))
		{
			*stakptr(PATH_OFFSET+1) = *stakptr(PATH_OFFSET);
			*stakptr(PATH_OFFSET)='/';
		}
#endif /* _WINIX */
                if(*stakptr(PATH_OFFSET)!='/')

		{
			char *last=(char*)stakfreeze(1);
			stakseek(PATH_OFFSET);
			stakputs(oldpwd);
			/* don't add '/' of oldpwd is / itself */
			if(*oldpwd!='/' || oldpwd[1])
				stakputc('/');
			stakputs(last+PATH_OFFSET);
			stakputc(0);
		}
		if(!flag)
		{
			register char *cp;
			stakseek(PATH_MAX+PATH_OFFSET);
#if SHOPT_FS_3D
			if(!(cp = pathcanon(stakptr(PATH_OFFSET),PATH_DOTDOT)))
				continue;
			/* eliminate trailing '/' */
			while(*--cp == '/' && cp>stakptr(PATH_OFFSET))
				*cp = 0;
#else
			if(*(cp=stakptr(PATH_OFFSET))=='/')
				if(!pathcanon(cp,PATH_DOTDOT))
					continue;
#endif /* SHOPT_FS_3D */
		}
		rval = newdirfd = sh_diropenat(shp, shp->pwdfd,
			path_relative(shp,stakptr(PATH_OFFSET)), xattr);
		if(newdirfd >=0)
		{
			/* chdir for directories on HSM/tapeworms may take minutes */
			if((rval=fchdir(newdirfd)) >= 0)
			{
				if(shp->pwdfd >= 0)
					sh_close(shp->pwdfd);
				shp->pwdfd=newdirfd;
				goto success;
			}
		}
#ifndef O_SEARCH
		else
		{
		if((rval=chdir(path_relative(shp,stakptr(PATH_OFFSET)))) >= 0)
			{
				if(shp->pwdfd >= 0)
				{
					sh_close(shp->pwdfd);
#ifdef AT_FDCWD
					shp->pwdfd = AT_FDCWD;
#else
					shp->pwdfd = -1;
#endif
				}
			}
		}
#endif
		if(saverrno==0)
			saverrno=errno;
		if(newdirfd >=0)
			sh_close(newdirfd);
	}
	while(cdpath);
	if(rval<0 && *dir=='/' && *(path_relative(shp,stakptr(PATH_OFFSET)))!='/')
	{
		rval = newdirfd = sh_diropenat(shp,
			shp->pwdfd,
			dir, xattr);
		if(newdirfd >=0)
		{
			/* chdir for directories on HSM/tapeworms may take minutes */
			if(fchdir(newdirfd) >= 0)
			{
				if(shp->pwdfd >= 0)
					sh_close(shp->pwdfd);
				shp->pwdfd=newdirfd;
				goto success;
			}
		}
#ifndef O_SEARCH
		else
		{
			if(chdir(dir) >=0)
			{
				if(shp->pwdfd >= 0)
				{
					sh_close(shp->pwdfd);
					shp->pwdfd=-1;
				}
			}
		}
#endif
	}
	/* use absolute chdir() if relative chdir() fails */
	if(rval<0)
	{
		if(saverrno)
			errno = saverrno;
		errormsg(SH_DICT,ERROR_system(1),"%s:",dir);
	}
success:
	if(dir == nv_getval(opwdnod) || argc==2)
		dp = dir;	/* print out directory for cd - */
	if(flag)
	{
		dir = stakptr(PATH_OFFSET);
		if (!(dir=pathcanon(dir,PATH_PHYSICAL)))
		{
			dir = stakptr(PATH_OFFSET);
			errormsg(SH_DICT,ERROR_system(1),"%s:",dir);
		}
		stakseek(dir-stakptr(0));
	}
	dir = (char*)stakfreeze(1)+PATH_OFFSET;
	if(*dp && (*dp!='.'||dp[1]) && strchr(dir,'/'))
		sfputr(sfstdout,dir,'\n');
	if(*dir != '/')
		return(0);
	nv_putval(opwdnod,oldpwd,NV_RDONLY);
	flag = (strlen(dir)>0)?true:false;
	/* delete trailing '/' */
	while(--flag>0 && dir[flag]=='/')
		dir[flag] = 0;
	nv_putval(pwdnod,dir,NV_RDONLY);
	nv_onattr(pwdnod,NV_NOFREE|NV_EXPORT);
	shp->pwd = pwdnod->nvalue.cp;
	nv_scan(shp->track_tree,rehash,(void*)0,NV_TAGGED,NV_TAGGED);
	path_newdir(shp,shp->pathlist);
	path_newdir(shp,shp->cdpathlist);
	if(oldpwd)
		free(oldpwd);
	return(0);
}

int	b_pwd(int argc, char *argv[],Shbltin_t *context)
{
	register int n, flag = 0;
	register char *cp;
	register Shell_t *shp = context->shp;
	NOT_USED(argc);
	while((n = optget(argv,sh_optpwd))) switch(n)
	{
		case 'L':
			flag = 0;
			break;
		case 'P':
			flag = 1;
			break;
		case ':':
			errormsg(SH_DICT,2, "%s", opt_info.arg);
			break;
		case '?':
			errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg);
			break;
	}
	if(error_info.errors)
		errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0));
	if(*(cp = path_pwd(shp,0)) != '/')
		errormsg(SH_DICT,ERROR_system(1), e_pwd);
	if(flag)
	{
#if SHOPT_FS_3D
		if(shp->gd->lim.fs3d && (flag = mount(e_dot,NIL(char*),FS3D_GET|FS3D_VIEW,0))>=0)
		{
			cp = (char*)stakseek(++flag+PATH_MAX);
			mount(e_dot,cp,FS3D_GET|FS3D_VIEW|FS3D_SIZE(flag),0);
		}
		else
#endif /* SHOPT_FS_3D */
			cp = strcpy(stakseek(strlen(cp)+PATH_MAX),cp);
		pathcanon(cp,PATH_PHYSICAL);
	}
	sfputr(sfstdout,cp,'\n');
	return(0);
}