Subject: bin/1776: /bin/sh prints pathname after 'cd' through symlink.
To: None <gnats-bugs@gnats.netbsd.org>
From: Chris G. Demetriou <cgd@NetBSD.ORG>
List: netbsd-bugs
Date: 11/20/1995 21:39:48
>Number:         1776
>Category:       bin
>Synopsis:       /bin/sh doesn't act like other [k]shs, cd'ing thru symlinks
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people (Utility Bug People)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Mon Nov 20 22:05:03 1995
>Last-Modified:
>Originator:     Chris G. Demetriou
>Organization:
Kernel Hackers 'r' Us
>Release:        NetBSD-current (trunk) as of 11/20/95
>Environment:
System: NetBSD sun-lamp.pc.cs.cmu.edu 1.0A NetBSD 1.0A (SUN_LAMP) #4: Mon Nov 20 19:31:01 EST 1995 cgd@sun-lamp.pc.cs.cmu.edu:/usr/src/sys/arch/i386/compile/SUN_LAMP i386


>Description:
	NetBSD's /bin/sh is a bit more verbose when cd'ing through symlinks
	than on other systems.  In particular, when cd'ing through a symlink,
	NetBSD's /bin/sh:
		(1) prints out the destination of the symlink, and
		(2) sets the internal notion of the pwd to be
			the 'real' destination of the symlink.

	This contrasts with (apparently) standard sh/ksh, which:
		(1) is silent, and
		(2) doesn't ferret out the 'real' destination of
			the symlink.

	The difference in output from the 'norm' is annoying enough, but
	the way it picks the real symlink destination can be problematic:
	it reads each symlink, and applies its own interpretation to
	the contents (via what it thinks are the 'normal' file system
	methods).  If for some reason a file system interprets
	symlinks specially (for instance, so that "cd @foo" doesn't
	work the same as "cd symlink" where symlink contains "@foo"),
	the NetBSD sh will break.  (This could happen because a
	special NFS server is being tricky, or for a variety of
	reasons.)

>How-To-Repeat:
	Do:

	cd /tmp
	ln -s /tmp bar
	cd bar
	pwd
	/bin/pwd

	on NetBSD, and using sh or ksh on other systems.  

	With NetBSD's /bin/sh, the results will end up like:

	$ cd /tmp
	$ ln -s /tmp bar
	$ cd bar
	/tmp
	$ pwd
	/tmp
	$ /bin/pwd
	/tmp

	But, using the SunOS sh or ksh, the results end up like:

	$ cd /tmp
	$ ln -s /tmp bar
	$ cd bar
	$ pwd
	/tmp/bar
	$ /bin/pwd
	/tmp

>Fix:
	Apply the following diff.  If pwd should really report
	the 'correct current directory', it should probably get
	it via getcwd(), not by translating the pathname itself.

Index: cd.c
===================================================================
RCS file: /a/cvsroot/src/bin/sh/cd.c,v
retrieving revision 1.14
diff -c -r1.14 cd.c
*** cd.c	1995/11/19 23:27:37	1.14
--- cd.c	1995/11/21 02:25:07
***************
*** 118,241 ****
  
  
  /*
!  * Actually do the chdir.  If the name refers to symbolic links, we
!  * compute the actual directory name before doing the cd.  In an
!  * interactive shell, print the directory name if "print" is nonzero
!  * or if the name refers to a symbolic link.  We also print the name
!  * if "/u/logname" was expanded in it, since this is similar to a
!  * symbolic link.  (The check for this breaks if the user gives the
!  * cd command some additional, unused arguments.)
   */
  
- #if SYMLINKS == 0
  STATIC int
  docd(dest, print)
  	char *dest;
- 	{
- 	INTOFF;
- 	if (chdir(dest) < 0) {
- 		INTON;
- 		return -1;
- 	}
- 	updatepwd(dest);
- 	INTON;
- 	if (print && iflag)
- 		out1fmt("%s\n", stackblock());
- 	return 0;
- }
- 
- #else
- 
- 
- 
- STATIC int
- docd(dest, print)
- 	char *dest;
- 	int print;
  {
- 	register char *p;
- 	register char *q;
- 	char *symlink;
- 	char *component;
- 	struct stat statb;
- 	int first;
- 	int i;
  
  	TRACE(("docd(\"%s\", %d) called\n", dest, print));
- 
- top:
- 	cdcomppath = dest;
- 	STARTSTACKSTR(p);
- 	if (*dest == '/') {
- 		STPUTC('/', p);
- 		cdcomppath++;
- 	}
- 	first = 1;
- 	while ((q = getcomponent()) != NULL) {
- 		if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
- 			continue;
- 		if (! first)
- 			STPUTC('/', p);
- 		first = 0;
- 		component = q;
- 		while (*q)
- 			STPUTC(*q++, p);
- 		if (equal(component, ".."))
- 			continue;
- 		STACKSTRNUL(p);
- 		if (lstat(stackblock(), &statb) < 0)
- 			error("lstat %s failed", stackblock());
- 		if (!S_ISLNK(statb.st_mode))
- 			continue;
- 
- 		/* Hit a symbolic link.  We have to start all over again. */
- 		print = 1;
- 		STPUTC('\0', p);
- 		symlink = grabstackstr(p);
- 		i = (int)statb.st_size + 2;		/* 2 for '/' and '\0' */
- 		if (cdcomppath != NULL)
- 			i += strlen(cdcomppath);
- 		p = stalloc(i);
- 		if (readlink(symlink, p, (int)statb.st_size) < 0) {
- 			error("readlink %s failed", stackblock());
- 		}
- 		if (cdcomppath != NULL) {
- 			p[(int)statb.st_size] = '/';
- 			scopy(cdcomppath, p + (int)statb.st_size + 1);
- 		} else {
- 			p[(int)statb.st_size] = '\0';
- 		}
- 		if (p[0] != '/') {	/* relative path name */
- 			char *r;
- 			q = r = symlink;
- 			while (*q) {
- 				if (*q++ == '/')
- 					r = q;
- 			}
- 			*r = '\0';
- 			dest = stalloc(strlen(symlink) + strlen(p) + 1);
- 			scopy(symlink, dest);
- 			strcat(dest, p);
- 		} else {
- 			dest = p;
- 		}
- 		goto top;
- 	}
- 	STPUTC('\0', p);
- 	p = grabstackstr(p);
  	INTOFF;
! 	if (chdir(p) < 0) {
  		INTON;
  		return -1;
  	}
! 	updatepwd(p);
  	INTON;
  	if (print && iflag)
! 		out1fmt("%s\n", p);
  	return 0;
  }
- #endif /* SYMLINKS */
- 
  
  
  /*
--- 118,144 ----
  
  
  /*
!  * Actually do the chdir.  In an interactive shell, print the
!  * directory name if "print" is nonzero.
   */
  
  STATIC int
  docd(dest, print)
  	char *dest;
  {
  
  	TRACE(("docd(\"%s\", %d) called\n", dest, print));
  	INTOFF;
! 	if (chdir(dest) < 0) {
  		INTON;
  		return -1;
  	}
! 	updatepwd(dest);
  	INTON;
  	if (print && iflag)
! 		out1fmt("%s\n", stackblock());
  	return 0;
  }
  
  
  /*
Index: shell.h
===================================================================
RCS file: /a/cvsroot/src/bin/sh/shell.h,v
retrieving revision 1.8
diff -c -r1.8 shell.h
*** shell.h	1995/05/11 21:30:22	1.8
--- shell.h	1995/11/21 02:25:08
***************
*** 41,47 ****
  /*
   * The follow should be set to reflect the type of system you have:
   *	JOBS -> 1 if you have Berkeley job control, 0 otherwise.
-  *	SYMLINKS -> 1 if your system includes symbolic links, 0 otherwise.
   *	SHORTNAMES -> 1 if your linker cannot handle long names.
   *	define BSD if you are running 4.2 BSD or later.
   *	define SYSV if you are running under System V.
--- 41,46 ----
***************
*** 54,60 ****
  
  
  #define JOBS 1
- #define SYMLINKS 1
  #ifndef BSD
  #define BSD 1
  #endif
--- 53,58 ----
>Audit-Trail:
>Unformatted: