From swp@swp.bspu.secna.ru  Wed Mar 31 04:46:37 2004
Return-Path: <swp@swp.bspu.secna.ru>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id C083E16A4CE
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 31 Mar 2004 04:46:37 -0800 (PST)
Received: from swp.bspu.secna.ru (swp.bspu.secna.ru [212.192.2.73])
	by mx1.FreeBSD.org (Postfix) with ESMTP id B0E5A43D39
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 31 Mar 2004 04:46:23 -0800 (PST)
	(envelope-from swp@swp.bspu.secna.ru)
Received: from swp.bspu.secna.ru (localhost [127.0.0.1])
	by swp.bspu.secna.ru (8.12.11/8.12.11) with ESMTP id i2VCkCop037170
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 31 Mar 2004 19:46:12 +0700 (OMSST)
	(envelope-from swp@swp.bspu.secna.ru)
Received: (from root@localhost)
	by swp.bspu.secna.ru (8.12.11/8.12.11/Submit) id i2VCkC0i037169;
	Wed, 31 Mar 2004 19:46:12 +0700 (OMSST)
	(envelope-from swp)
Message-Id: <200403311246.i2VCkC0i037169@swp.bspu.secna.ru>
Date: Wed, 31 Mar 2004 19:46:12 +0700 (OMSST)
From: swp@uni-altai.ru
Reply-To: "mitrohin a.s." <swp@uni-altai.ru>
To: FreeBSD-gnats-submit@freebsd.org
Cc:
Subject: /bin/sh unable to change directory but current dir grow anyway 
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         64990
>Category:       bin
>Synopsis:       [patch] /bin/sh unable to change directory but current dir grow anyway
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    stefanf
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Mar 31 04:50:00 PST 2004
>Closed-Date:    Sun Apr 20 18:09:43 UTC 2008
>Last-Modified:  Sun Apr 20 18:09:43 UTC 2008
>Originator:     mitrohin a.s.
>Release:        FreeBSD 5.2-CURRENT i386
>Organization:
Barnaul State Pedagogical University
>Environment:
System: FreeBSD swp.bspu.secna.ru 5.2-CURRENT FreeBSD 5.2-CURRENT #1: Fri Mar 26 17:56:09 OMST 2004 swp@swp.bspu.secna.ru:/usr/obj/usr/src/sys/ag_kernel i386


	
>Description:
builtin cd unable to change work directory but curdir grow anyway.

	
>How-To-Repeat:
# mkdir -p /tmp/1/2
# chmod 770 /tmp/1/2
# cd /tmp/1
# touch 1.txt
# pwd
/tmp/1
# ls -l
total 1
drwxrwx---  2 root  wheel  512 22  11:22 2
-rw-r--r--  1 root  wheel    0 22  11:22 1.txt
# su -m cyrus
$ pwd
/tmp/1
$ cd 2
cd: can't cd to 2
$ pwd
/tmp/1/2
$ ls -l
total 1
drwxrwx---  2 root  wheel  512 22  11:22 2
-rw-r--r--  1 root  wheel    0 22  11:22 1.txt
$ cd 2
cd: can't cd to 2
$ pwd
/tmp/1/2/2
$ ls -l
total 1
drwxrwx---  2 root  wheel  512 22  11:22 2
-rw-r--r--  1 root  wheel    0 22  11:22 1.txt
$ cd 2
cd: can't cd to 2
$ pwd
/tmp/1/2/2/2
$ ls -l
total 1
drwxrwx---  2 root  wheel  512 22  11:22 2
-rw-r--r--  1 root  wheel    0 22  11:22 1.txt
$ cd .
$ pwd
/tmp/1
$ ^D

	
>Fix:
Index: bin/sh/cd.c
===================================================================
RCS file: /usr/cvs/freebsd/ncvs/src/bin/sh/cd.c,v
retrieving revision 1.33
diff -u -r1.33 cd.c
--- bin/sh/cd.c	5 Jul 2003 15:18:44 -0000	1.33
+++ bin/sh/cd.c	31 Mar 2004 12:35:06 -0000
@@ -42,13 +42,18 @@
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD: src/bin/sh/cd.c,v 1.33 2003/07/05 15:18:44 dds Exp $");
 
+#include <stddef.h>
+#include <stdio.h>
+#include <assert.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/param.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
 #include <limits.h>
+#include <fcntl.h>
 
 /*
  * The cd and pwd commands.
@@ -68,15 +73,305 @@
 #include "show.h"
 #include "cd.h"
 
-STATIC int cdlogical(char *);
-STATIC int cdphysical(char *);
-STATIC int docd(char *, int, int);
-STATIC char *getcomponent(void);
-STATIC int updatepwd(char *);
-
-STATIC char *curdir = NULL;	/* current working directory */
-STATIC char *prevdir;		/* previous working directory */
-STATIC char *cdcomppath;
+/*
+ * Get the next component of the path name pointed to by path.
+ * This routine overwrites the string pointed to by path.
+ */
+static inline
+char *
+getcomponent(char **path)
+{
+	char *p;
+
+	while ((p = strsep(path, "/")) != 0 && !*p)
+		continue;
+	return p;
+}
+
+
+struct mblkcore {
+	int	clnk;
+	int	realsz;
+	int	n;
+	char 	d[1];
+};
+typedef	struct mblkcore *mblk_t;
+
+#define	BLKSZ			0x1000
+#define MBLK_GETMEMSIZE(n) \
+	    ((n)?(((n)-1)/BLKSZ+1)*BLKSZ + offsetof(struct mblkcore, d) : 0)
+
+static inline int mblk_init(mblk_t *);
+static inline int mblk_uninit(mblk_t *);
+static inline int mblk_clear(mblk_t *);
+static inline int mblk_attach(mblk_t *, mblk_t *);
+static inline int mblk_getn(mblk_t *);
+static inline char *mblk_getp(mblk_t *);
+static inline int mblk_realloc(mblk_t *, int);
+static inline int mblk_write(mblk_t *, int, void *, int);
+
+#define	mblk_resize	mblk_realloc
+
+static inline
+int
+mblk_init(mblk_t *b)
+{
+	*b = 0;
+	return 0;
+}
+
+static inline
+int
+mblk_uninit(mblk_t *b)
+{
+	return mblk_clear(b);
+}
+
+static inline
+int
+mblk_clear(mblk_t *b)
+{
+	if (*b) {
+		if (!--(*b)->clnk)
+			free(*b);
+		*b = 0;
+	}
+	return 0;
+}
+
+static inline
+int
+mblk_attach(mblk_t *a, mblk_t *b)
+{
+	mblk_uninit(a);
+	if (*b) {
+		(*b)->clnk++;
+		*a = *b;
+	}
+	return 0;
+}
+
+static inline
+int
+mblk_getn(mblk_t *b)
+{
+	return (*b) ? (*b)->n : 0;
+}
+static inline
+char *
+mblk_getp(mblk_t *b)
+{
+	return (*b) ? (*b)->d : 0;
+}
+
+static
+int
+mblk_realloc(mblk_t *b, int n)
+{
+	int clnk, realsz, new_realsz;
+	struct mblkcore *q;
+
+	if (n == -1)
+		n = mblk_getn(b);
+
+	if (!n)
+		return mblk_clear(b);
+
+	if (!*b) {
+		clnk = 1;
+		realsz = 0;
+	} else {
+		clnk = (*b)->clnk;
+		realsz = (*b)->realsz;
+		assert(realsz > 0);
+	}
+
+	new_realsz = MBLK_GETMEMSIZE(n);
+	if (clnk > 1) {
+		if (!(q = malloc(new_realsz)))
+			return -1;
+		memcpy(q->d, (*b)->d, (*b)->n < n ? (*b)->n : n);
+		(*b)->clnk--;
+		*b = q;
+	} else if (new_realsz != realsz) {
+		if (!(q = realloc(*b, new_realsz)))
+			return -1;
+		*b = q;
+	}
+	(*b)->clnk = 1;
+	(*b)->realsz = new_realsz;
+	(*b)->n = n;
+
+	return 0;
+}
+
+static inline
+int
+mblk_write(mblk_t *b, int off, void *buf, int bufn)
+{
+	int clnk, realsz, bn, n;
+
+	if (!buf || !bufn)
+		return 0;
+	
+	if (!*b) {
+		clnk = 1;
+		realsz = 0;
+		bn = 0;
+	} else {
+		clnk = (*b)->clnk;
+		realsz = (*b)->realsz;
+		bn = (*b)->n;
+	}
+
+	if (off <= realsz) {
+		if (off == -1)
+			off = bn;
+		n = off + bufn;
+		if (n < bn)
+			n = bn;
+		if (mblk_realloc(b, n))
+			return -1;
+		memcpy((*b)->d + off, buf, bufn);
+	}
+	return 0;
+}
+
+
+
+static
+void
+mblk_dump(mblk_t *b)
+{
+	int i;
+
+	printf("blk = %p", *b);
+	if (*b) {
+		printf("clnk: %d, realsz: %d, n: %d,\n  d: ", 
+			(*b)->clnk, (*b)->realsz, (*b)->n);
+		for (i = 0; i < (*b)->n; i++)
+			printf("%d:%c ", i, (*b)->d[i]);
+	}
+	printf("\n");
+}
+
+
+static
+int
+mblk_mkcdpath(mblk_t *b, char *dir, int phys)
+{
+	int rc, off, n;
+	char *p, *d, cdir[MAXPATHLEN];
+	mblk_t a[1];
+
+	if (phys) {
+		if (dir && *dir)
+			if (chdir(dir))
+				return -1;
+		if (!getcwd(cdir, sizeof cdir))
+			return -1;
+		n = strlen(cdir) + 1;
+		if (mblk_write(b, 0, cdir, n) || mblk_resize(b, n))
+			return -1;
+		return 0;
+	}
+
+	if (!*b) {
+		if (!getcwd(cdir, sizeof cdir)) {
+			cdir[0] = '/'; cdir[1] = 0;
+			chdir(cdir);
+		}
+		n = strlen(cdir) + 1;
+		if (mblk_write(b, 0, cdir, n) || mblk_resize(b, n))
+			return -1;
+	}
+
+	if (!dir || !*dir)
+		return 0;
+
+	assert(*b);
+	assert((*b)->n == 2 || ((*b)->n > 2 && (*b)->d[(*b)->n - 2] != '/'));
+	assert(*(*b)->d == '/');
+	assert(!(*b)->d[(*b)->n - 1]);
+
+	if (*dir && *dir == '/') {
+		if (mblk_write(b, 0, "/", 2) || mblk_resize(b, 2))
+			return -1;
+		dir++;
+	}
+
+	mblk_init(a);
+	mblk_attach(a, b);
+
+	rc = 0;
+	while ((p = getcomponent(&dir)) != 0) {
+		if (*p == '.') {
+			if (!p[1])
+				continue;
+			if (p[1] == '.' && !p[2]) {
+				for (p = (*b)->d + (*b)->n - 1;;)
+					if (p-1 == (*b)->d || *--p == '/')
+						break;
+				if ((rc = mblk_resize(b, p-(*b)->d+1)) != 0)
+					break;
+				if ((rc = mblk_write(b,(*b)->n-1,"",1)) != 0)
+					break;
+				continue;
+			}
+		}
+
+		off = 1;
+		if ((*b)->n != 2) {
+			off = (*b)->n;
+			if ((rc = mblk_write(b, off-1, "/", 1)) != 0)
+				break;
+		}
+		if ((rc = mblk_write(b, off, p, strlen(p)+1)) != 0)
+			break;
+	}
+
+	if (rc)
+		mblk_attach(b, a);
+	mblk_uninit(a);
+
+	return rc;
+}
+
+/*
+ * Actually change the directory.  In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+static 
+int
+docd(mblk_t *curdir, mblk_t *prevdir, char *dest, int print, int phys)
+{
+	int rc;
+	mblk_t old_curdir[1];
+
+	TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
+
+	rc = -1;
+	mblk_init(old_curdir);
+	mblk_attach(old_curdir, curdir);
+	if (!mblk_mkcdpath(curdir, dest, phys)) {
+		INTOFF;
+		if (chdir((*curdir)->d) < 0)
+			mblk_attach(curdir, old_curdir);
+		else {
+			mblk_attach(prevdir, old_curdir);
+			if (print && iflag)
+				out1fmt("%s\n", (*curdir)->d);
+			rc = 0;
+		}
+		INTON;
+	}
+	mblk_uninit(old_curdir);
+		
+	return rc;
+}
+
+static mblk_t cdir[1] = { 0 };
+static mblk_t pdir[1] = { 0 };
 
 int
 cdcmd(int argc, char **argv)
@@ -113,7 +408,7 @@
 	if (*dest == '\0')
 		dest = ".";
 	if (dest[0] == '-' && dest[1] == '\0') {
-		dest = prevdir ? prevdir : curdir;
+		dest = *pdir ? (*pdir)->d : (*cdir)->d;
 		if (dest)
 			print = 1;
 		else
@@ -131,7 +426,7 @@
 					p += 2;
 				print = strcmp(p, dest);
 			}
-			if (docd(p, print, phys) >= 0)
+			if (!docd(cdir, pdir, p, print, phys))
 				return 0;
 		}
 	}
@@ -141,189 +436,13 @@
 }
 
 
-/*
- * Actually change the directory.  In an interactive shell, print the
- * directory name if "print" is nonzero.
- */
-STATIC int
-docd(char *dest, int print, int phys)
-{
-
-	TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
-
-	/* If logical cd fails, fall back to physical. */
-	if ((phys || cdlogical(dest) < 0) && cdphysical(dest) < 0)
-		return (-1);
-
-	if (print && iflag && curdir)
-		out1fmt("%s\n", curdir);
-
-	return 0;
-}
-
-STATIC int
-cdlogical(char *dest)
-{
-	char *p;
-	char *q;
-	char *component;
-	struct stat statb;
-	int first;
-	int badstat;
-
-	/*
-	 *  Check each component of the path. If we find a symlink or
-	 *  something we can't stat, clear curdir to force a getcwd()
-	 *  next time we get the value of the current directory.
-	 */
-	badstat = 0;
-	cdcomppath = stalloc(strlen(dest) + 1);
-	scopy(dest, cdcomppath);
-	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) {
-			badstat = 1;
-			break;
-		}
-	}
-
-	INTOFF;
-	if (updatepwd(badstat ? NULL : dest) < 0 || chdir(curdir) < 0) {
-		INTON;
-		return (-1);
-	}
-	INTON;
-	return (0);
-}
-
-STATIC int
-cdphysical(char *dest)
-{
-
-	INTOFF;
-	if (chdir(dest) < 0 || updatepwd(NULL) < 0) {
-		INTON;
-		return (-1);
-	}
-	INTON;
-	return (0);
-}
-
-/*
- * Get the next component of the path name pointed to by cdcomppath.
- * This routine overwrites the string pointed to by cdcomppath.
- */
-STATIC char *
-getcomponent(void)
-{
-	char *p;
-	char *start;
-
-	if ((p = cdcomppath) == NULL)
-		return NULL;
-	start = cdcomppath;
-	while (*p != '/' && *p != '\0')
-		p++;
-	if (*p == '\0') {
-		cdcomppath = NULL;
-	} else {
-		*p++ = '\0';
-		cdcomppath = p;
-	}
-	return start;
-}
-
-
-/*
- * Update curdir (the name of the current directory) in response to a
- * cd command.  We also call hashcd to let the routines in exec.c know
- * that the current directory has changed.
- */
-STATIC int
-updatepwd(char *dir)
-{
-	char *new;
-	char *p;
-
-	hashcd();				/* update command hash table */
-
-	/*
-	 * If our argument is NULL, we don't know the current directory
-	 * any more because we traversed a symbolic link or something
-	 * we couldn't stat().
-	 */
-	if (dir == NULL || curdir == NULL)  {
-		if (prevdir)
-			ckfree(prevdir);
-		INTOFF;
-		prevdir = curdir;
-		curdir = NULL;
-		if (getpwd() == NULL) {
-			INTON;
-			return (-1);
-		}
-		setvar("PWD", curdir, VEXPORT);
-		setvar("OLDPWD", prevdir, VEXPORT);
-		INTON;
-		return (0);
-	}
-	cdcomppath = stalloc(strlen(dir) + 1);
-	scopy(dir, cdcomppath);
-	STARTSTACKSTR(new);
-	if (*dir != '/') {
-		p = curdir;
-		while (*p)
-			STPUTC(*p++, new);
-		if (p[-1] == '/')
-			STUNPUTC(new);
-	}
-	while ((p = getcomponent()) != NULL) {
-		if (equal(p, "..")) {
-			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
-		} else if (*p != '\0' && ! equal(p, ".")) {
-			STPUTC('/', new);
-			while (*p)
-				STPUTC(*p++, new);
-		}
-	}
-	if (new == stackblock())
-		STPUTC('/', new);
-	STACKSTRNUL(new);
-	INTOFF;
-	if (prevdir)
-		ckfree(prevdir);
-	prevdir = curdir;
-	curdir = savestr(stackblock());
-	setvar("PWD", curdir, VEXPORT);
-	setvar("OLDPWD", prevdir, VEXPORT);
-	INTON;
-
-	return (0);
-}
-
 int
 pwdcmd(int argc, char **argv)
 {
 	char buf[PATH_MAX];
 	int ch, phys;
 
-	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
+	optreset = optind = 1; opterr = 0; /* initialize getopt */
 	phys = Pflag;
 	while ((ch = getopt(argc, argv, "LP")) != -1) {
 		switch (ch) {
@@ -344,15 +463,12 @@
 	if (argc != 0)
 		error("too many arguments");
 
-	if (!phys && getpwd()) {
-		out1str(curdir);
-		out1c('\n');
-	} else {
-		if (getcwd(buf, sizeof(buf)) == NULL)
-			error(".: %s", strerror(errno));
-		out1str(buf);
-		out1c('\n');
-	}
+	if (phys)
+		if (mblk_mkcdpath(cdir, 0, phys))
+			return -1;
+
+	out1str((*cdir)->d);
+	out1c('\n');
 
 	return 0;
 }
@@ -364,24 +480,8 @@
 char *
 getpwd(void)
 {
-	char buf[PATH_MAX];
-
-	if (curdir)
-		return curdir;
-	if (getcwd(buf, sizeof(buf)) == NULL) {
-		char *pwd = getenv("PWD");
-		struct stat stdot, stpwd;
-
-		if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
-		    stat(pwd, &stpwd) != -1 &&
-		    stdot.st_dev == stpwd.st_dev &&
-		    stdot.st_ino == stpwd.st_ino) {
-			curdir = savestr(pwd);
-			return curdir;
-		}
-		return NULL;
-	}
-	curdir = savestr(buf);
-
-	return curdir;
+	if (!*cdir)
+		if (mblk_mkcdpath(cdir, 0, 1))
+			return 0;
+	return (*cdir)->d;
 }
	


>Release-Note:
>Audit-Trail:

From: "a.s. mitrohin" <swp@uni-altai.ru>
To: freebsd-gnats-submit@FreeBSD.org, swp@uni-altai.ru
Cc:  
Subject: Re: bin/64990: /bin/sh unable to change directory but current dir
 grow anyway
Date: Sat, 03 Apr 2004 21:18:42 +0700

 This is a multi-part message in MIME format.
 --------------050700010807040401070403
 Content-Type: text/plain; charset=us-ascii; format=flowed
 Content-Transfer-Encoding: 7bit
 
 sorry... patch broken ;(
 please review new version
 
 this patch fixed another bug yet. sh change directory incorrect  (for 
 logical paths)
 if current dir was maked from symlink. for example:
 
 # ln -fs /usr/ports/distfiles /home/ftp/distfiles
 # cd /usr/ports/distfiles
 # pwd
 /usr/ports/distfiles
 # cd ../x11
 cd: can't cd to ../x11
 
 /swp
 
 --------------050700010807040401070403
 Content-Type: text/plain;
  name="patch-bin::sh::cd.c"
 Content-Transfer-Encoding: 7bit
 Content-Disposition: inline;
  filename="patch-bin::sh::cd.c"
 
 Index: bin/sh/cd.c
 ===================================================================
 RCS file: /usr/cvs/freebsd/ncvs/src/bin/sh/cd.c,v
 retrieving revision 1.33
 diff -u -r1.33 cd.c
 --- bin/sh/cd.c	5 Jul 2003 15:18:44 -0000	1.33
 +++ bin/sh/cd.c	3 Apr 2004 11:30:51 -0000
 @@ -42,13 +42,19 @@
  #include <sys/cdefs.h>
  __FBSDID("$FreeBSD: src/bin/sh/cd.c,v 1.33 2003/07/05 15:18:44 dds Exp $");
  
 +#include <stddef.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <assert.h>
  #include <sys/types.h>
  #include <sys/stat.h>
 +#include <sys/param.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <errno.h>
  #include <limits.h>
 +#include <fcntl.h>
  
  /*
   * The cd and pwd commands.
 @@ -68,262 +74,381 @@
  #include "show.h"
  #include "cd.h"
  
 -STATIC int cdlogical(char *);
 -STATIC int cdphysical(char *);
 -STATIC int docd(char *, int, int);
 -STATIC char *getcomponent(void);
 -STATIC int updatepwd(char *);
 -
 -STATIC char *curdir = NULL;	/* current working directory */
 -STATIC char *prevdir;		/* previous working directory */
 -STATIC char *cdcomppath;
 +#define	malloc	ckmalloc
 +#define	realloc	ckrealloc
  
 -int
 -cdcmd(int argc, char **argv)
 +/*
 + * Get the next component of the path name pointed to by path.
 + * This routine overwrites the string pointed to by path.
 + */
 +static inline
 +char *
 +getcomponent(char **path)
  {
 -	char *dest;
 -	char *path;
  	char *p;
 -	struct stat statb;
 -	int ch, phys, print = 0;
  
 -	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
 -	phys = Pflag;
 -	while ((ch = getopt(argc, argv, "LP")) != -1) {
 -		switch (ch) {
 -		case 'L':
 -			phys = 0;
 -			break;
 -		case 'P':
 -			phys = 1;
 -			break;
 -		default:
 -			error("unknown option: -%c", optopt);
 -			break;
 -		}
 -	}
 -	argc -= optind;
 -	argv += optind;
 +	while ((p = strsep(path, "/")) != 0 && !*p)
 +		continue;
 +	return p;
 +}
  
 -	if (argc > 1)
 -		error("too many arguments");
  
 -	if ((dest = *argv) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
 -		error("HOME not set");
 -	if (*dest == '\0')
 -		dest = ".";
 -	if (dest[0] == '-' && dest[1] == '\0') {
 -		dest = prevdir ? prevdir : curdir;
 -		if (dest)
 -			print = 1;
 -		else
 -			dest = ".";
 +struct mblkcore {
 +	int	clnk;
 +	int	realsz;
 +	int	n;
 +	char 	d[1];
 +};
 +typedef	struct mblkcore *mblk_t;
 +
 +#define	BLKSZ			0x1000
 +#define MBLK_GETMEMSIZE(n) \
 +	    ((n)?(((n)-1)/BLKSZ+1)*BLKSZ + offsetof(struct mblkcore, d) : 0)
 +
 +static inline int mblk_init(mblk_t *);
 +static inline int mblk_uninit(mblk_t *);
 +static inline int mblk_clear(mblk_t *);
 +static inline int mblk_attach(mblk_t *, mblk_t *);
 +static inline int mblk_getn(mblk_t *);
 +static inline char *mblk_getp(mblk_t *);
 +static inline int mblk_realloc(mblk_t *, int);
 +static inline int mblk_write(mblk_t *, int, void *, int);
 +
 +#define	mblk_resize	mblk_realloc
 +
 +static inline
 +int
 +mblk_init(mblk_t *b)
 +{
 +	*b = 0;
 +	return 0;
 +}
 +
 +static inline
 +int
 +mblk_uninit(mblk_t *b)
 +{
 +	return mblk_clear(b);
 +}
 +
 +static inline
 +int
 +mblk_clear(mblk_t *b)
 +{
 +	if (*b) {
 +		if (!--(*b)->clnk)
 +			free(*b);
 +		*b = 0;
  	}
 -	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
 -		path = nullstr;
 -	while ((p = padvance(&path, dest)) != NULL) {
 -		if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
 -			if (!print) {
 -				/*
 -				 * XXX - rethink
 -				 */
 -				if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
 -					p += 2;
 -				print = strcmp(p, dest);
 -			}
 -			if (docd(p, print, phys) >= 0)
 -				return 0;
 -		}
 +	return 0;
 +}
 +
 +static inline
 +int
 +mblk_attach(mblk_t *a, mblk_t *b)
 +{
 +	mblk_uninit(a);
 +	if (*b) {
 +		(*b)->clnk++;
 +		*a = *b;
  	}
 -	error("can't cd to %s", dest);
 -	/*NOTREACHED*/
  	return 0;
  }
  
 +static inline
 +int
 +mblk_getn(mblk_t *b)
 +{
 +	return (*b) ? (*b)->n : 0;
 +}
 +static inline
 +char *
 +mblk_getp(mblk_t *b)
 +{
 +	return (*b) ? (*b)->d : 0;
 +}
  
 -/*
 - * Actually change the directory.  In an interactive shell, print the
 - * directory name if "print" is nonzero.
 - */
 -STATIC int
 -docd(char *dest, int print, int phys)
 +static
 +int
 +mblk_realloc(mblk_t *b, int n)
  {
 +	int clnk, realsz, new_realsz;
 +	struct mblkcore *q;
  
 -	TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
 +	if (n == -1)
 +		n = mblk_getn(b);
  
 -	/* If logical cd fails, fall back to physical. */
 -	if ((phys || cdlogical(dest) < 0) && cdphysical(dest) < 0)
 -		return (-1);
 +	if (!n)
 +		return mblk_clear(b);
  
 -	if (print && iflag && curdir)
 -		out1fmt("%s\n", curdir);
 +	if (!*b) {
 +		clnk = 1;
 +		realsz = 0;
 +	} else {
 +		clnk = (*b)->clnk;
 +		realsz = (*b)->realsz;
 +		assert(realsz > 0);
 +	}
 +
 +	new_realsz = MBLK_GETMEMSIZE(n);
 +	if (clnk > 1) {
 +		if (!(q = malloc(new_realsz)))
 +			return -1;
 +		memcpy(q->d, (*b)->d, (*b)->n < n ? (*b)->n : n);
 +		(*b)->clnk--;
 +		*b = q;
 +	} else if (new_realsz != realsz) {
 +		if (!(q = realloc(*b, new_realsz)))
 +			return -1;
 +		*b = q;
 +	}
 +	(*b)->clnk = 1;
 +	(*b)->realsz = new_realsz;
 +	(*b)->n = n;
  
  	return 0;
  }
  
 -STATIC int
 -cdlogical(char *dest)
 +static
 +int
 +mblk_write(mblk_t *b, int off, void *buf, int bufn)
  {
 -	char *p;
 -	char *q;
 -	char *component;
 -	struct stat statb;
 -	int first;
 -	int badstat;
 +	int clnk, realsz, bn, n;
  
 -	/*
 -	 *  Check each component of the path. If we find a symlink or
 -	 *  something we can't stat, clear curdir to force a getcwd()
 -	 *  next time we get the value of the current directory.
 -	 */
 -	badstat = 0;
 -	cdcomppath = stalloc(strlen(dest) + 1);
 -	scopy(dest, cdcomppath);
 -	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) {
 -			badstat = 1;
 -			break;
 -		}
 +	if (!buf || !bufn)
 +		return 0;
 +	
 +	if (!*b) {
 +		clnk = 1;
 +		realsz = 0;
 +		bn = 0;
 +	} else {
 +		clnk = (*b)->clnk;
 +		realsz = (*b)->realsz;
 +		bn = (*b)->n;
  	}
  
 -	INTOFF;
 -	if (updatepwd(badstat ? NULL : dest) < 0 || chdir(curdir) < 0) {
 -		INTON;
 -		return (-1);
 +	if (off <= realsz) {
 +		if (off == -1)
 +			off = bn;
 +		n = off + bufn;
 +		if (n < bn)
 +			n = bn;
 +		if (mblk_realloc(b, n))
 +			return -1;
 +		memcpy((*b)->d + off, buf, bufn);
  	}
 -	INTON;
 -	return (0);
 +	return 0;
  }
  
 -STATIC int
 -cdphysical(char *dest)
 +
 +static
 +void
 +mblk_dump(mblk_t *b)
  {
 +	int i;
  
 -	INTOFF;
 -	if (chdir(dest) < 0 || updatepwd(NULL) < 0) {
 -		INTON;
 -		return (-1);
 +	printf("blk = %p", *b);
 +	if (*b) {
 +		printf("clnk: %d, realsz: %d, n: %d,\n  d: ", 
 +			(*b)->clnk, (*b)->realsz, (*b)->n);
 +		for (i = 0; i < (*b)->n; i++)
 +			printf("%d:%c ", i, (*b)->d[i]);
  	}
 -	INTON;
 -	return (0);
 +	printf("\n");
  }
  
 -/*
 - * Get the next component of the path name pointed to by cdcomppath.
 - * This routine overwrites the string pointed to by cdcomppath.
 - */
 -STATIC char *
 -getcomponent(void)
 +
 +
 +static
 +int
 +mkcdpath(mblk_t *b, char *dir, int phys)
  {
 -	char *p;
 -	char *start;
 +	int rc, off, n;
 +	char *p, cdir[MAXPATHLEN < PATH_MAX ? PATH_MAX : MAXPATHLEN];
 +	mblk_t a[1];
 +
 +	if (!*b) {
 +		if (!getcwd(cdir, sizeof cdir))
 +			memcpy(cdir, "/", 2);
 +		n = strlen(cdir) + 1;
 +		if (mblk_write(b, 0, cdir, n) || mblk_resize(b, n))
 +			return -1;
 +	}
 +
 +	assert(*b);
 +	assert((*b)->n == 2 || ((*b)->n > 2 && (*b)->d[(*b)->n - 2] != '/'));
 +	assert(*(*b)->d == '/');
 +	assert(!(*b)->d[(*b)->n - 1]);
 +
 +	if (phys) {
 +		char *pathname;
 +
 +		if (dir && *dir) {
 +			if (*dir == '/')
 +				pathname = dir;
 +			else {
 +				off = 1;
 +				if ((*b)->n != 2) {
 +					off = (*b)->n;
 +					if (mblk_write(b, off-1, "/", 1))
 +						return -1;
 +				}
 +				if (mblk_write(b, off, dir, strlen(dir)+1))
 +					return -1;
 +				pathname = (*b)->d;
 +			}
 +		} else
 +			pathname = (*b)->d;
  
 -	if ((p = cdcomppath) == NULL)
 -		return NULL;
 -	start = cdcomppath;
 -	while (*p != '/' && *p != '\0')
 -		p++;
 -	if (*p == '\0') {
 -		cdcomppath = NULL;
 -	} else {
 -		*p++ = '\0';
 -		cdcomppath = p;
 +		if (!realpath(pathname, cdir))
 +			return -1;
 +		if (mblk_write(b, 0, cdir, n = strlen(cdir) + 1) ||
 +				mblk_resize(b, n))
 +			return -1;
 +		return 0;
 +	}
 +
 +	if (!dir || !*dir)
 +		return 0;
 +
 +	if (*dir == '/') {
 +		if (mblk_write(b, 0, "/", 2) || mblk_resize(b, 2))
 +			return -1;
 +		dir++;
 +	}
 +
 +	mblk_init(a);
 +	mblk_attach(a, b);
 +
 +	rc = 0;
 +	while ((p = getcomponent(&dir)) != 0) {
 +		if (*p == '.') {
 +			if (!p[1])
 +				continue;
 +			if (p[1] == '.' && !p[2]) {
 +				for (p = (*b)->d + (*b)->n - 1;;)
 +					if (p-1 == (*b)->d || *--p == '/')
 +						break;
 +				if ((rc = mblk_resize(b, p-(*b)->d+1)) != 0)
 +					break;
 +				if ((rc = mblk_write(b,(*b)->n-1,"",1)) != 0)
 +					break;
 +				continue;
 +			}
 +		}
 +
 +		off = 1;
 +		if ((*b)->n != 2) {
 +			off = (*b)->n;
 +			if ((rc = mblk_write(b, off-1, "/", 1)) != 0)
 +				break;
 +		}
 +		if ((rc = mblk_write(b, off, p, strlen(p)+1)) != 0)
 +			break;
  	}
 -	return start;
 -}
  
 +	if (rc)
 +		mblk_attach(b, a);
 +	mblk_uninit(a);
 +
 +	return rc;
 +}
  
  /*
 - * Update curdir (the name of the current directory) in response to a
 - * cd command.  We also call hashcd to let the routines in exec.c know
 - * that the current directory has changed.
 + * Actually change the directory.  In an interactive shell, print the
 + * directory name if "print" is nonzero.
   */
 -STATIC int
 -updatepwd(char *dir)
 +static 
 +int
 +docd(mblk_t *curdir, mblk_t *prevdir, char *dest, int phys)
  {
 -	char *new;
 -	char *p;
 +	int rc;
 +	mblk_t old_curdir[1];
  
 -	hashcd();				/* update command hash table */
 +	TRACE(("docd(\"%s\", %d) called\n", dest, phys));
  
 -	/*
 -	 * If our argument is NULL, we don't know the current directory
 -	 * any more because we traversed a symbolic link or something
 -	 * we couldn't stat().
 -	 */
 -	if (dir == NULL || curdir == NULL)  {
 -		if (prevdir)
 -			ckfree(prevdir);
 +	rc = -1;
 +	mblk_init(old_curdir);
 +	mblk_attach(old_curdir, curdir);
 +	if (!mkcdpath(curdir, dest, phys)) {
  		INTOFF;
 -		prevdir = curdir;
 -		curdir = NULL;
 -		if (getpwd() == NULL) {
 -			INTON;
 -			return (-1);
 +		if (chdir((*curdir)->d) < 0)
 +			mblk_attach(curdir, old_curdir);
 +		else {
 +			mblk_attach(prevdir, old_curdir);
 +			if (iflag)
 +				out1fmt("%s\n", (*curdir)->d);
 +			rc = 0;
  		}
 -		setvar("PWD", curdir, VEXPORT);
 -		setvar("OLDPWD", prevdir, VEXPORT);
  		INTON;
 -		return (0);
  	}
 -	cdcomppath = stalloc(strlen(dir) + 1);
 -	scopy(dir, cdcomppath);
 -	STARTSTACKSTR(new);
 -	if (*dir != '/') {
 -		p = curdir;
 -		while (*p)
 -			STPUTC(*p++, new);
 -		if (p[-1] == '/')
 -			STUNPUTC(new);
 -	}
 -	while ((p = getcomponent()) != NULL) {
 -		if (equal(p, "..")) {
 -			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
 -		} else if (*p != '\0' && ! equal(p, ".")) {
 -			STPUTC('/', new);
 -			while (*p)
 -				STPUTC(*p++, new);
 +	mblk_uninit(old_curdir);
 +		
 +	return rc;
 +}
 +
 +static mblk_t cdir[1] = { 0 };
 +static mblk_t pdir[1] = { 0 };
 +
 +int
 +cdcmd(int argc, char **argv)
 +{
 +	char *dest;
 +	char *path;
 +	char *p;
 +	struct stat statb;
 +	int ch, phys;
 +
 +	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
 +	phys = Pflag;
 +	while ((ch = getopt(argc, argv, "LP")) != -1) {
 +		switch (ch) {
 +		case 'L':
 +			phys = 0;
 +			break;
 +		case 'P':
 +			phys = 1;
 +			break;
 +		default:
 +			error("unknown option: -%c", optopt);
 +			break;
  		}
  	}
 -	if (new == stackblock())
 -		STPUTC('/', new);
 -	STACKSTRNUL(new);
 -	INTOFF;
 -	if (prevdir)
 -		ckfree(prevdir);
 -	prevdir = curdir;
 -	curdir = savestr(stackblock());
 -	setvar("PWD", curdir, VEXPORT);
 -	setvar("OLDPWD", prevdir, VEXPORT);
 -	INTON;
 +	argc -= optind;
 +	argv += optind;
  
 -	return (0);
 +	if (argc > 1)
 +		error("too many arguments");
 +
 +	if ((dest = *argv) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
 +		error("HOME not set");
 +	if (*dest == '\0')
 +		dest = ".";
 +	else if (dest[0] == '-' && dest[1] == '\0') {
 +		dest = *pdir ? (*pdir)->d : (*cdir)->d;
 +		if (!dest)
 +			dest = ".";
 +	}
 +	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
 +		path = nullstr;
 +	if ((p = padvance(&path, dest)) != NULL) {
 +		if (!docd(cdir, pdir, p, phys))
 +			return 0;
 +	}
 +	error("can't cd to %s", dest);
 +	/*NOTREACHED*/
 +	return 0;
  }
  
 +
  int
  pwdcmd(int argc, char **argv)
  {
  	char buf[PATH_MAX];
  	int ch, phys;
  
 -	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
 +	optreset = optind = 1; opterr = 0; /* initialize getopt */
  	phys = Pflag;
  	while ((ch = getopt(argc, argv, "LP")) != -1) {
  		switch (ch) {
 @@ -344,15 +469,11 @@
  	if (argc != 0)
  		error("too many arguments");
  
 -	if (!phys && getpwd()) {
 -		out1str(curdir);
 -		out1c('\n');
 -	} else {
 -		if (getcwd(buf, sizeof(buf)) == NULL)
 -			error(".: %s", strerror(errno));
 -		out1str(buf);
 -		out1c('\n');
 -	}
 +	if (mkcdpath(cdir, 0, phys))
 +		return -1;
 +
 +	out1str((*cdir)->d);
 +	out1c('\n');
  
  	return 0;
  }
 @@ -364,24 +485,8 @@
  char *
  getpwd(void)
  {
 -	char buf[PATH_MAX];
 -
 -	if (curdir)
 -		return curdir;
 -	if (getcwd(buf, sizeof(buf)) == NULL) {
 -		char *pwd = getenv("PWD");
 -		struct stat stdot, stpwd;
 -
 -		if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
 -		    stat(pwd, &stpwd) != -1 &&
 -		    stdot.st_dev == stpwd.st_dev &&
 -		    stdot.st_ino == stpwd.st_ino) {
 -			curdir = savestr(pwd);
 -			return curdir;
 -		}
 -		return NULL;
 -	}
 -	curdir = savestr(buf);
 -
 -	return curdir;
 +	if (!*cdir)
 +		if (mkcdpath(cdir, 0, 1))
 +			return 0;
 +	return (*cdir)->d;
  }
 
 --------------050700010807040401070403--

From: Gavin Atkinson <gavin@FreeBSD.org>
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/64990: [patch] /bin/sh unable to change directory but
	current dir grow anyway
Date: Tue, 12 Jun 2007 13:57:05 +0100

 See also bin/101316 fr a different (much simpler) patch for the same
 problem.  I don't know which is the correct fix, though.
Responsible-Changed-From-To: freebsd-bugs->stefanf 
Responsible-Changed-By: gavin 
Responsible-Changed-When: Sat Jun 23 16:13:24 UTC 2007 
Responsible-Changed-Why:  

Over to sh(1) maintainer.  stefanf: This is the same issue as 
bin/101316, but with a very differnt approach to fixing it. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=64990 
State-Changed-From-To: open->patched 
State-Changed-By: stefanf 
State-Changed-When: Sun Feb 24 16:52:35 UTC 2008 
State-Changed-Why:  
Fixed in HEAD. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=64990 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/64990: commit references a PR
Date: Sun, 24 Feb 2008 16:51:01 +0000 (UTC)

 stefanf     2008-02-24 16:50:55 UTC
 
   FreeBSD src repository
 
   Modified files:
     bin/sh               cd.c 
   Log:
   Split updatepwd() into two smaller functions.  The first one, findpwd(),
   computes the new path and the second one, updatepwd(), updates the variables
   PWD, OLDPWD and the path used for the pwd builtin according to the new
   directory.  For a logical directory change, chdir() is now called between
   those two functions, no longer causing wrong values to be stored in PWD etc. if
   it fails.
   
   PR:     64990, 101316, 120571
   
   Revision  Changes    Path
   1.36      +45 -38    src/bin/sh/cd.c
 _______________________________________________
 cvs-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/cvs-all
 To unsubscribe, send any mail to "cvs-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: patched->closed 
State-Changed-By: stefanf 
State-Changed-When: Sun Apr 20 18:05:47 UTC 2008 
State-Changed-Why:  
Merged to RELENG_6 and RELENG_7.


http://www.freebsd.org/cgi/query-pr.cgi?pr=64990 
>Unformatted:
