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