1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * A copy of the CDDL is also available via the Internet at
11  * http://www.opensource.org/licenses/cddl1.txt
12  * See the License for the specific language governing permissions
13  * and limitations under the License.
14  *
15  * When distributing Covered Code, include this CDDL HEADER in each
16  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17  * If applicable, add the following below this CDDL HEADER, with the
18  * fields enclosed by brackets "[]" replaced with your own identifying
19  * information: Portions Copyright [yyyy] [name of copyright owner]
20  *
21  * CDDL HEADER END
22  */
23 
24 /*
25  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
30 /*	  All Rights Reserved  	*/
31 
32 
33 #if defined(sun)
34 #pragma ident	"@(#)pwd.c	1.12	06/06/16 SMI"
35 #endif
36 
37 #include "defs.h"
38 
39 /*
40  * Copyright 2008-2020 J. Schilling
41  *
42  * @(#)pwd.c	1.40 20/05/17 2008-2020 J. Schilling
43  */
44 #ifndef lint
45 static	UConst char sccsid[] =
46 	"@(#)pwd.c	1.40 20/05/17 2008-2020 J. Schilling";
47 #endif
48 
49 /*
50  *	UNIX shell
51  */
52 #ifdef	SCHILY_INCLUDES
53 #include	<schily/errno.h>
54 #include	<schily/types.h>
55 #include	<schily/fcntl.h>	/* For O_XXX and AT_FDCWD */
56 #include	<schily/stat.h>
57 #include	<schily/getcwd.h>
58 #else
59 #include	"mac.h"
60 #include	<errno.h>
61 #include	<sys/types.h>
62 #include	<fcntl.h>
63 #include	<sys/stat.h>
64 #include	<limits.h>
65 #endif
66 
67 #define	DOT	'.'
68 #ifndef	NULL
69 #define	NULL	0
70 #endif
71 #define	SLASH	'/'
72 #define	PARTLY	2		/* ncwdname may contain symlinks */
73 
74 	int	lchdir		__PR((char *path));
75 #if	defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
76 static	void	sunos5_cwdfix	__PR((void));
77 #endif
78 static	Uchar	*lgetcwd	__PR((void));
79 	int	lstatat		__PR((char *name, struct stat *buf, int flag));
80 	void	cwd		__PR((unsigned char *dir,
81 					unsigned char *cwdbase));
82 static	void	cwd2		__PR((int cdflg));
83 static	int	cwd3		__PR((struct stat *statp));
84 #ifdef	DO_POSIX_CD
85 	int	cwdrel2abs	__PR((void));
86 #endif
87 	unsigned char *cwdget	__PR((int cdflg));
88 	void	cwdprint	__PR((int cdflg));
89 static void	rmslash		__PR((unsigned char *string));
90 	void	ocwdnod		__PR((void));
91 static void	cwdnod		__PR((unsigned char *wdname));
92 
93 #ifdef __STDC__
94 extern const char	longpwd[];
95 #else
96 extern char	longpwd[];
97 #endif
98 
99 unsigned char *ocwdname;
100 unsigned char cwdname[2];		/* Just a place holder for a Nul byte */
101 unsigned char *ncwdname = cwdname;	/* The official handle for $PWD	*/
102 
103 static int	didpwd = FALSE;
104 
105 /*
106  * A chdir() implementation that is able to deal with ENAMETOOLONG.
107  */
108 int
lchdir(path)109 lchdir(path)
110 	char	*path;
111 {
112 	int	ret = chdir(path);
113 #if	defined(ENAMETOOLONG) && defined(DO_CHDIR_LONG)
114 	char	*p;
115 	char	*p2;
116 
117 	if (ret >= 0 || errno != ENAMETOOLONG)
118 		return (ret);
119 
120 	p = path;
121 	if (*p == SLASH) {
122 		if ((ret = chdir("/")) < 0)
123 			return (ret);
124 		while (*p == SLASH)
125 			p++;
126 	}
127 	while (*p) {
128 		if ((p2 =  strchr(p, SLASH)) != NULL)
129 			*p2 = '\0';
130 		if ((ret = chdir(p)) < 0) {
131 			if (p2)
132 				*p2 = SLASH;
133 			break;
134 		}
135 		if (p2 == NULL)
136 			break;
137 		*p2++ = SLASH;
138 		while (*p2 == SLASH)
139 			p2++;
140 		p = p2;
141 	}
142 #endif	/* defined(ENAMETOOLONG) && defined(DO_CHDIR_LONG) */
143 	return (ret);
144 }
145 
146 #if	defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
147 /*
148  * Workaround a Solaris getcwd() bug that hits on newer Solaris releases with
149  * getcwd() being a system call. Once a successful getcwd() call that returns
150  * more than PATH_MAX did succeed, future calls to getcwd() always fail with
151  * ERANGE until a successfull new chdir() call has been issued.
152  */
153 static void
sunos5_cwdfix()154 sunos5_cwdfix()
155 {
156 	int	f = open(".", O_SEARCH|O_DIRECTORY|O_NDELAY);
157 
158 	if (f >= 0) {
159 		chdir("/");
160 		fchdir(f);
161 		close(f);
162 	}
163 }
164 #endif
165 
166 /*
167  * A getcwd() implementation that is able to deal with more than PATH_MAX.
168  *
169  * Since lgetcwd() overwrites possible data at "staktop", it cannot be called
170  * while ephemeral string processing on the string stack is under way.
171  */
172 static unsigned char *
lgetcwd()173 lgetcwd()
174 {
175 	unsigned char	*dir = staktop;
176 		char	*r;
177 	size_t		len = PATH_MAX;
178 #if	defined(DO_CHDIR_LONG)
179 #define	LWD_RETRY_MAX	8			/* stops at 128 * PATH_MAX */
180 	int		i = 0;
181 #endif
182 
183 #if	defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
184 retry:
185 #endif
186 	do {
187 		GROWSTAKL(dir, len);
188 		*dir = '\0';
189 
190 		r = getcwd((char *)dir, (brkend - dir)-1);
191 #if	defined(DO_CHDIR_LONG)
192 		if (++i >= LWD_RETRY_MAX)	/* stops at 128 * PATH_MAX */
193 			break;
194 		len *= 2;
195 	} while (r == NULL && errno == ERANGE);
196 #else
197 	} while (0);
198 #endif
199 
200 #if	defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
201 	if (r == NULL && errno == ERANGE && i == LWD_RETRY_MAX) {
202 		/*
203 		 * Work around a Solaris kernel bug.
204 		 */
205 		sunos5_cwdfix();
206 		goto retry;
207 	}
208 #endif
209 	return ((unsigned char *)r);
210 }
211 
212 #if	defined(HAVE_FCHDIR) && \
213 	(defined(DO_EXPAND_LONG) || defined(DO_CHDIR_LONG))
214 int
sh_hop_dirs(name,np)215 sh_hop_dirs(name, np)
216 	char	*name;
217 	char	**np;
218 {
219 	char	*p;
220 	char	*p2;
221 	int	fd;
222 	int	dfd;
223 	int	err;
224 
225 	p = name;
226 	fd = AT_FDCWD;
227 	if (*p == '/') {
228 		fd = openat(fd, "/", O_SEARCH|O_DIRECTORY|O_NDELAY);
229 		while (*p == '/')
230 			p++;
231 	}
232 	while (*p) {
233 		if ((p2 = strchr(p, '/')) != NULL) {
234 			if (p2[1] == '\0')
235 				break;
236 			*p2 = '\0';
237 		} else {
238 			break;	/* No slash remains, return the prefix fd */
239 		}
240 		if ((dfd = openat(fd, p, O_SEARCH|O_DIRECTORY|O_NDELAY)) < 0) {
241 			err = errno;
242 
243 			close(fd);
244 			if (err == EMFILE)
245 				errno = err;
246 			else
247 				errno = ENAMETOOLONG;
248 			*p2 = '/';
249 			return (dfd);
250 		}
251 		close(fd);	/* Don't care about AT_FDCWD, it is negative */
252 		fd = dfd;
253 		*p2++ = '/';	/* p2 is always != NULL here */
254 		while (*p2 == '/')
255 			p2++;
256 		p = p2;
257 	}
258 	*np = p;
259 	return (fd);
260 }
261 #endif
262 
263 /*
264  * A stat() implementation that is able to deal with ENAMETOOLONG.
265  */
266 int
lstatat(name,buf,flag)267 lstatat(name, buf, flag)
268 	char		*name;
269 	struct stat	*buf;
270 	int		flag;
271 {
272 #if	defined(HAVE_FCHDIR) && defined(ENAMETOOLONG) && defined(DO_CHDIR_LONG)
273 	char	*p;
274 	int	fd;
275 	int	err;
276 #endif
277 	int	ret;
278 
279 	if ((ret = fstatat(AT_FDCWD, name, buf, flag)) < 0 &&
280 	    errno != ENAMETOOLONG) {
281 		return (ret);
282 	}
283 
284 #if	defined(HAVE_FCHDIR) && defined(ENAMETOOLONG) && defined(DO_CHDIR_LONG)
285 	if (ret >= 0)
286 		return (ret);
287 
288 	fd = sh_hop_dirs(name, &p);
289 	ret = fstatat(fd, p, buf, flag);
290 	err = errno;
291 	close(fd);		/* Don't care about AT_FDCWD, it is negative */
292 	errno = err;
293 #endif	/* HAVE_FCHDIR && ENAMETOOLONG && DO_CHDIR_LONG */
294 	return (ret);
295 }
296 
297 
298 /*
299  * Try to update ncwdname.
300  * Called by the "cd" builtin, in this case with "cwdbase" == NULL.
301  *
302  * Incorrect values will be detected by cwd2().
303  *
304  * If "cwdbase" == NULL, we replace $PWD.
305  *
306  * Otherwise we assume "dir" == "cwdbase" and normalize in place without
307  * updating $PWD. This is used by cwdrel2abs().
308  *
309  * This function is based on string manipulation only.
310  */
311 void
cwd(dir,cwdbase)312 cwd(dir, cwdbase)
313 	unsigned char	*dir;		/* The new relative directory	   */
314 	unsigned char	*cwdbase;	/* Absolute directory base or NULL */
315 {
316 	unsigned char *pcwd;
317 	unsigned char *pdir;
318 	unsigned char *base = cwdbase;
319 
320 	if (base == NULL) {		/* Compute new value on string stack */
321 		int	len;		/* and later install result as $PWD  */
322 
323 		/*
324 		 * Create space for $PWD + "/" + length(dir)
325 		 * First step over curstak().
326 		 */
327 		len = length(curstak());
328 		pcwd = curstak() + len;
329 
330 		len = length(ncwdname) + length(dir);
331 		pcwd += len;
332 		GROWSTAK(pcwd);
333 		pcwd -= len;
334 		cwdbase = pcwd;
335 		movstr(ncwdname, cwdbase);	/* Copy old $PWD to base */
336 
337 	} else if (dir == cwdbase && *dir != '/') {
338 		/*
339 		 * In place operation only for absolute path names.
340 		 * In case of a relative path name, we need to call getcwd().
341 		 */
342 		didpwd = FALSE;
343 		return;
344 	}
345 
346 	/*
347 	 * First remove extra /'s by squeezing the string.
348 	 */
349 	rmslash(dir);
350 
351 	/*
352 	 * Now remove any .'s
353 	 */
354 	pdir = dir;
355 	if (*dir == SLASH)		/* skip initial slash */
356 		pdir++;
357 	while (*pdir) {			/* remove /./ by itself */
358 		if ((*pdir == DOT) && (*(pdir+1) == SLASH)) {
359 			movstr(pdir+2, pdir);
360 			continue;
361 		}
362 		pdir++;
363 		while ((*pdir) && (*pdir != SLASH))
364 			pdir++;
365 		if (*pdir)		/* skip next slash */
366 			pdir++;
367 	}
368 	/*
369 	 * Take care of trailing /.
370 	 */
371 	if (*(--pdir) == DOT && pdir > dir && *(--pdir) == SLASH) {
372 		if (pdir > dir) {
373 			*pdir = '\0';
374 		} else {
375 			*(pdir+1) = '\0';
376 		}
377 
378 	}
379 
380 	/*
381 	 * Remove extra /'s by squeezing the string.
382 	 */
383 	rmslash(dir);
384 
385 	/*
386 	 * Now that the dir is canonicalized, process it
387 	 */
388 	if (*dir == DOT && *(dir+1) == '\0') {
389 		/*
390 		 * This was a chdir("."), so we do not need to change
391 		 * the $PWD variable.
392 		 */
393 		return;
394 	}
395 
396 
397 	if (*dir == SLASH) {
398 		/*
399 		 * Absolute path, overwrite cwdbase.
400 		 *
401 		 * The resulting string length will be less or equal to the
402 		 * string length of the "dir" argument.
403 		 */
404 		pcwd = cwdbase;
405 		*pcwd++ = *dir++;	/* Copy over initial '/' */
406 		didpwd = PARTLY;
407 	} else {
408 		/*
409 		 * Relative path, append to cwdbase (usually on string stack).
410 		 *
411 		 * This may cause an overflow in case there is not enough space
412 		 * but the resulting string length will be less or equal to the
413 		 * string length in "cwdbase" and "dir" plus 1 ('/').
414 		 */
415 		if (didpwd == FALSE)
416 			return;
417 		didpwd = PARTLY;
418 		pcwd = cwdbase + length(cwdbase) - 1;
419 		if (pcwd != cwdbase+1)
420 			*pcwd++ = SLASH;
421 	}
422 	while (*dir) {
423 		if (*dir == DOT &&
424 		    *(dir+1) == DOT &&
425 		    (*(dir+2) == SLASH || *(dir+2) == '\0')) {
426 			/* Parent directory, so backup one */
427 
428 			if (pcwd > cwdbase+2)
429 				--pcwd;
430 			while (*(--pcwd) != SLASH)
431 				/* LINTED */
432 				;
433 			pcwd++;
434 			dir += 2;
435 			if (*dir == SLASH) {
436 				dir++;
437 			}
438 			continue;
439 		}
440 		*pcwd++ = *dir++;
441 		while ((*dir) && (*dir != SLASH)) {
442 			*pcwd++ = *dir++;
443 		}
444 		if (*dir) {
445 			*pcwd++ = *dir++;
446 		}
447 	}
448 	*pcwd = '\0';
449 
450 	--pcwd;
451 	if (pcwd > cwdbase && *pcwd == SLASH) {
452 		/* Remove trailing / */
453 
454 		*pcwd = '\0';
455 	}
456 	if (base == NULL)
457 		cwdnod(cwdbase);	/* Update $PWD */
458 }
459 
460 /*
461  * Verify that "ncwdname" is identical to "."
462  * Set didpwd = FALSE in case of problems.
463  * If cdflg is not CHDIR_L, check for symlinks in "ncwdname"
464  * and set didpwd = FALSE in case a symlink was seen.
465  */
466 static void
cwd2(cdflg)467 cwd2(cdflg)
468 	int	cdflg;
469 {
470 	struct stat stat1, stat2;
471 	/* check if there are any symbolic links in pathname */
472 
473 	if (didpwd == FALSE)
474 		return;
475 
476 	if (didpwd == PARTLY) {
477 		if (!(cdflg & CHDIR_L)) {
478 			if (!cwd3(&stat1))
479 				return;
480 		} else if (lstatat((char *)ncwdname, &stat1, 0) == -1) {
481 			didpwd = FALSE;
482 			return;
483 		}
484 	} else if (lstatat((char *)ncwdname, &stat1, 0) == -1) {
485 			didpwd = FALSE;
486 			return;
487 	}
488 
489 	/*
490 	 * check if ino's and dev's match; pathname could
491 	 * consist of symbolic links with ".."
492 	 */
493 	if (stat(".", &stat2) == -1 ||
494 	    stat1.st_dev != stat2.st_dev ||
495 	    stat1.st_ino != stat2.st_ino)
496 		didpwd = FALSE;
497 }
498 
499 /*
500  * Check all pathname components for symlinks.
501  * Set didpwd = FALSE in case a symlink was seen.
502  */
503 static int
cwd3(statp)504 cwd3(statp)
505 	struct stat	*statp;
506 {
507 	unsigned char *pcwd = ncwdname + 1;
508 
509 	while (*pcwd) {
510 		char c;
511 		while ((c = *pcwd++) != SLASH && c != '\0')
512 			/* LINTED */
513 			;
514 		*--pcwd = '\0';
515 		if (lstatat((char *)ncwdname, statp,
516 			    AT_SYMLINK_NOFOLLOW) == -1 ||
517 		    S_ISLNK(statp->st_mode)) {
518 			didpwd = FALSE;
519 			*pcwd = c;
520 			return (FALSE);
521 		}
522 		*pcwd = c;
523 		if (c)
524 			pcwd++;
525 	}
526 	didpwd = TRUE;
527 	return (TRUE);
528 }
529 
530 #ifdef	DO_POSIX_CD
531 /*
532  * Convert a relative pathname into an absolute patname when in -L mode.
533  * The relative pathname is expected in curstak() and the result is
534  * written to the same location.
535  *
536  * We are _always_ called with relative path names in curstak().
537  *
538  * This function is based only on string manipulation.
539  */
540 int
cwdrel2abs()541 cwdrel2abs()
542 {
543 	unsigned char	*p;
544 	unsigned char	*p2;
545 	int		odidpwd = didpwd;
546 	int		ret;
547 
548 	/*
549 	 * Check whether there is no way to make it "right".
550 	 */
551 	if (didpwd == FALSE || ncwdname[0] != '/')
552 		return (FALSE);
553 
554 	/*
555 	 * Create space for $PWD + "/" + curstak()
556 	 * Since we first step over curstak(), we need 2*ret.
557 	 */
558 	ret = length(curstak());
559 	p = curstak() + length(ncwdname) + 2*ret;
560 	GROWSTAK(p);
561 
562 	/*
563 	 * Now concat $PWD + "/" + curstak() above current curstak().
564 	 */
565 	p = curstak() + ret;	/* Step over old curstak() content  */
566 	p2 = movstr(ncwdname, p); /* Copy current $PWD		    */
567 	*p2++ = '/';		/* Add '/'			    */
568 	movstr(curstak(), p2);	/* Add relative path from curstak() */
569 
570 	/*
571 	 * Canonicalize new absolute path in place.
572 	 * The input value at curstak() is still valid.
573 	 */
574 	cwd(p, p);
575 	if (didpwd == FALSE) {
576 		/*
577 		 * TODO: Check whether a long path could be converted into
578 		 * an absolute path that is shorter.
579 		 */
580 		ret = FALSE;
581 		goto out;
582 	}
583 	ret = TRUE;
584 	movstr(p, curstak());			/* move result to curstak() */
585 out:
586 	didpwd = odidpwd;
587 	return (ret);
588 }
589 #endif
590 
591 /*
592  * Get the current working directory.
593  * Mark didpwd that we have a real value.
594  *
595  * Externally used by the "cd" builtin to update PWD= and by xec.c when
596  * setting up a new job slot.
597  * Internally used by cwdset() below.
598  */
599 unsigned char *
cwdget(cdflg)600 cwdget(cdflg)
601 	int	cdflg;
602 {
603 	unsigned char	*cwdptr = NULL;
604 
605 	cwd2(cdflg);
606 	if (didpwd == FALSE) {
607 		if ((cwdptr = lgetcwd()) == NULL) {
608 			/*
609 			 * HP-UX-10.20 returns ENAMETOOLONG even though POSIX
610 			 * requires it to work with long path names.
611 			 * We check whether the value in "ncwdname" refers
612 			 * to "." and contains no symlinks. Since we normalize
613 			 * the path name before, this is as secure as a
614 			 * successful getcwd() call.
615 			 */
616 			didpwd = PARTLY;
617 			cwd2(CHDIR_P);
618 			if (didpwd == FALSE)
619 				*ncwdname = 0;
620 		} else {
621 			didpwd = TRUE;
622 		}
623 	}
624 	if (didpwd && cwdptr)
625 		cwdnod(cwdptr);
626 	else
627 		cwdnod(ncwdname);
628 	return (ncwdname);
629 }
630 
631 /*
632  * Set ncwdname (call cwdget()) if needed.
633  *
634  * Externally used by the "cd" builtin to update PWD=
635  *
636  * We use cwdget(CHDIR_L) as this does not modify PWD
637  * unless it is definitely invalid.
638  */
639 unsigned char *
cwdset()640 cwdset()
641 {
642 	if (ncwdname[0] == '\0')
643 		cwdget(CHDIR_L);
644 	if (didpwd == FALSE)
645 		return (UC "");
646 	return (ncwdname);
647 }
648 
649 /*
650  *	Print the current working directory.
651  *	Used by the "pwd" builtin.
652  */
653 void
cwdprint(cdflg)654 cwdprint(cdflg)
655 	int	cdflg;
656 {
657 	unsigned char	*cwdptr = ncwdname;
658 #ifdef	DO_POSIX_CD
659 
660 	/*
661 	 * As cwdprint() is only used to print the current working directory,
662 	 * it should only update ncwdname[] in case it is definitely invalid.
663 	 * For this reason, we call cwd2(CHDIR_L) to just check whether the
664 	 * content does not point to the current working directory.
665 	 */
666 	cwd2(CHDIR_L);
667 	if (didpwd == FALSE || (didpwd == PARTLY && !(cdflg & CHDIR_L))) {
668 
669 		if ((cwdptr = lgetcwd()) == NULL) {
670 			if (errno && errno != ERANGE)
671 				Error(badpwd);
672 			else
673 				Error(longpwd);
674 			return;
675 		}
676 		didpwd = TRUE;
677 	}
678 #else
679 	cwd2(cdflg);
680 	if (didpwd == FALSE) {
681 		if ((cwdptr = lgetcwd()) == NULL) {
682 			if (errno && errno != ERANGE)
683 				Error(badpwd);
684 			else
685 				Error(longpwd);
686 			return;
687 		}
688 		didpwd = TRUE;
689 	}
690 #endif
691 
692 	prs_buff(cwdptr);
693 	prc_buff(NL);
694 	cwdnod(cwdptr);
695 }
696 
697 /*
698  *	This routine will remove repeated slashes from string.
699  */
700 static void
rmslash(string)701 rmslash(string)
702 	unsigned char *string;
703 {
704 	unsigned char *pstring;
705 
706 	pstring = string;
707 	while (*pstring) {
708 		if (*pstring == SLASH && *(pstring+1) == SLASH) {
709 			/* Remove repeated SLASH's */
710 
711 			movstr(pstring+1, pstring);
712 			continue;
713 		}
714 		pstring++;
715 	}
716 
717 	--pstring;
718 	if (pstring > string && *pstring == SLASH) {
719 		/* Remove trailing / */
720 
721 		*pstring = '\0';
722 	}
723 }
724 
725 /*
726  * Update OLDPWD= node
727  */
728 void
ocwdnod()729 ocwdnod()
730 {
731 #ifdef	DO_POSIX_CD
732 	extern struct namnod opwdnod;
733 
734 	if (opwdnod.namval != ocwdname)
735 		free(opwdnod.namval);
736 	if (opwdnod.namval != opwdnod.namenv && opwdnod.namenv != ocwdname)
737 		free(opwdnod.namenv);
738 	free(ocwdname);
739 	if (ncwdname[0] != '\0')
740 		ocwdname = make(ncwdname);
741 	else
742 		ocwdname = NULL;		/* Makes OLDPWD= disappear */
743 	opwdnod.namval = opwdnod.namenv = ocwdname;
744 
745 	attrib(&opwdnod, N_ENVCHG);		/* Mark as changed */
746 #endif
747 }
748 
749 /*
750  * Update PWD= node
751  */
752 static void
cwdnod(wdname)753 cwdnod(wdname)
754 	unsigned char	*wdname;
755 {
756 	extern struct namnod pwdnod;
757 
758 	if (wdname == pwdnod.namval)		/* $PWD still intact */
759 		return;
760 
761 	if (pwdnod.namval != ncwdname)
762 		free(pwdnod.namval);
763 	if (pwdnod.namval != pwdnod.namenv && pwdnod.namenv != ncwdname)
764 		free(pwdnod.namenv);
765 
766 	if (wdname == ncwdname) {
767 		pwdnod.namval = pwdnod.namenv = ncwdname;
768 	} else {
769 		free(ncwdname);
770 		ncwdname = make(wdname);	/* Never returns NULL */
771 		pwdnod.namval = pwdnod.namenv = ncwdname;
772 	}
773 	attrib(&pwdnod, N_ENVCHG);		/* Mark as changed */
774 }
775 
776 #ifdef	DO_SYSPUSHD
777 static	struct argnod *dirs;
778 
779 struct argnod *
push_dir(name)780 push_dir(name)
781 	unsigned char	*name;
782 {
783 	struct argnod	*ret;
784 
785 	ret = (struct argnod *)alloc(length(name) + BYTESPERWORD);
786 	movstr(name, ret->argval);
787 	ret->argnxt = dirs;
788 	dirs = ret;
789 	return (ret);
790 }
791 
792 struct argnod *
pop_dir(offset)793 pop_dir(offset)
794 	int	offset;
795 {
796 	int		i = 0;
797 	struct argnod	*d = dirs;
798 	struct argnod	*prev = dirs;
799 
800 	while (d && i++ != offset) {
801 		prev = d;
802 		d = d->argnxt;
803 	}
804 	if (!d)
805 		return (NULL);
806 	if (prev == d)		/* d == dirs */
807 		dirs = d->argnxt;
808 	else
809 		prev->argnxt = d->argnxt;
810 	return (d);
811 }
812 
813 void
init_dirs()814 init_dirs()
815 {
816 	if (dirs)
817 		return;
818 	cwdset();
819 	push_dir(ncwdname);
820 }
821 
822 int
pr_dirs(minlen,cdflg)823 pr_dirs(minlen, cdflg)
824 	int	minlen;
825 	int	cdflg;
826 {
827 	struct argnod	*d;
828 
829 	if (!dirs)
830 		init_dirs();
831 
832 	d = dirs;
833 	if (d->argnxt == NULL) {
834 		if (minlen == 0)
835 			cwdprint(cdflg);
836 		return (minlen == 0);
837 	}
838 	while (d) {
839 		prs_buff(d->argval);
840 		d = d->argnxt;
841 		if (d)
842 			prc_buff(' ');
843 	}
844 	prc_buff(NL);
845 	return (dirs->argnxt != NULL);
846 }
847 #endif	/* DO_SYSPUSHD */
848