xref: /freebsd/contrib/tcsh/sh.dir.c (revision 9a14aa017b21c292740c00ee098195cd46642730)
1 /* $Header: /p/tcsh/cvsroot/tcsh/sh.dir.c,v 3.80 2007/05/08 21:05:34 christos Exp $ */
2 /*
3  * sh.dir.c: Directory manipulation functions
4  */
5 /*-
6  * Copyright (c) 1980, 1991 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 #include "sh.h"
34 #include "ed.h"
35 
36 RCSID("$tcsh: sh.dir.c,v 3.80 2007/05/08 21:05:34 christos Exp $")
37 
38 /*
39  * C Shell - directory management
40  */
41 
42 static	Char			*agetcwd	(void);
43 static	void			 dstart		(const char *);
44 static	struct directory	*dfind		(Char *);
45 static	Char 			*dfollow	(Char *, int);
46 static	void 	 	 	 printdirs	(int);
47 static	Char 			*dgoto		(Char *);
48 static	void 	 	 	 dnewcwd	(struct directory *, int);
49 static	void 	 	 	 dset		(Char *);
50 static  void 			 dextract	(struct directory *);
51 static  int 			 skipargs	(Char ***, const char *,
52 						 const char *);
53 static	void			 dgetstack	(void);
54 
55 static struct directory dhead INIT_ZERO_STRUCT;		/* "head" of loop */
56 static int    printd;			/* force name to be printed */
57 
58 int     bequiet = 0;		/* do not print dir stack -strike */
59 
60 static Char *
61 agetcwd(void)
62 {
63     char *buf;
64     Char *cwd;
65     size_t len;
66 
67     len = MAXPATHLEN;
68     buf = xmalloc(len);
69     while (getcwd(buf, len) == NULL) {
70 	int err;
71 
72 	err = errno;
73 	if (err != ERANGE) {
74 	    xfree(buf);
75 	    errno = err;
76 	    return NULL;
77 	}
78 	len *= 2;
79 	buf = xrealloc(buf, len);
80     }
81     if (*buf == '\0') {
82 	xfree(buf);
83 	return NULL;
84     }
85     cwd = SAVE(buf);
86     xfree(buf);
87     return cwd;
88 }
89 
90 static void
91 dstart(const char *from)
92 {
93     xprintf(CGETS(12, 1, "%s: Trying to start from \"%s\"\n"), progname, from);
94 }
95 
96 /*
97  * dinit - initialize current working directory
98  */
99 void
100 dinit(Char *hp)
101 {
102     Char *cp, *tcp;
103     struct directory *dp;
104 
105     /* Don't believe the login shell home, because it may be a symlink */
106     tcp = agetcwd();
107     if (tcp == NULL) {
108 	xprintf("%s: %s\n", progname, strerror(errno));
109 	if (hp && *hp) {
110 	    char *xcp = short2str(hp);
111 	    dstart(xcp);
112 	    if (chdir(xcp) == -1)
113 		cp = NULL;
114 	    else
115 		cp = Strsave(hp);
116 	}
117 	else
118 	    cp = NULL;
119 	if (cp == NULL) {
120 	    dstart("/");
121 	    if (chdir("/") == -1)
122 		/* I am not even try to print an error message! */
123 		xexit(1);
124 	    cp = SAVE("/");
125 	}
126     }
127     else {
128 #ifdef S_IFLNK
129 	struct stat swd, shp;
130 	int swd_ok;
131 
132 	swd_ok = stat(short2str(tcp), &swd) == 0;
133 	/*
134 	 * See if $HOME is the working directory we got and use that
135 	 */
136 	if (swd_ok && hp && *hp && stat(short2str(hp), &shp) != -1 &&
137 	    DEV_DEV_COMPARE(swd.st_dev, shp.st_dev)  &&
138 		swd.st_ino == shp.st_ino)
139 	    cp = Strsave(hp);
140 	else {
141 	    char   *cwd;
142 
143 	    /*
144 	     * use PWD if we have it (for subshells)
145 	     */
146 	    if (swd_ok && (cwd = getenv("PWD")) != NULL) {
147 		if (stat(cwd, &shp) != -1 &&
148 			DEV_DEV_COMPARE(swd.st_dev, shp.st_dev) &&
149 		        swd.st_ino == shp.st_ino) {
150 		    tcp = SAVE(cwd);
151 		    cleanup_push(tcp, xfree);
152 		}
153 	    }
154 	    cleanup_push(tcp, xfree);
155 	    cp = dcanon(tcp, STRNULL);
156 	    cleanup_ignore(tcp);
157 	    cleanup_until(tcp);
158 	}
159 #else /* S_IFLNK */
160 	cleanup_push(tcp, xfree);
161 	cp = dcanon(tcp, STRNULL);
162 	cleanup_ignore(tcp);
163 	cleanup_until(tcp);
164 #endif /* S_IFLNK */
165     }
166 
167     dp = xcalloc(sizeof(struct directory), 1);
168     dp->di_name = cp;
169     dp->di_count = 0;
170     dhead.di_next = dhead.di_prev = dp;
171     dp->di_next = dp->di_prev = &dhead;
172     printd = 0;
173     dnewcwd(dp, 0);
174     setcopy(STRdirstack, dp->di_name, VAR_READWRITE|VAR_NOGLOB);
175 }
176 
177 static void
178 dset(Char *dp)
179 {
180     /*
181      * Don't call set() directly cause if the directory contains ` or
182      * other junk characters glob will fail.
183      */
184     setcopy(STRowd, varval(STRcwd), VAR_READWRITE|VAR_NOGLOB);
185     setcopy(STRcwd, dp, VAR_READWRITE|VAR_NOGLOB);
186     tsetenv(STRPWD, dp);
187 }
188 
189 #define DIR_PRINT	0x01	/* -p */
190 #define DIR_LONG  	0x02	/* -l */
191 #define DIR_VERT  	0x04	/* -v */
192 #define DIR_LINE  	0x08	/* -n */
193 #define DIR_SAVE 	0x10	/* -S */
194 #define DIR_LOAD	0x20	/* -L */
195 #define DIR_CLEAR	0x40	/* -c */
196 #define DIR_OLD	  	0x80	/* - */
197 
198 static int
199 skipargs(Char ***v, const char *dstr, const char *str)
200 {
201     Char  **n = *v, *s;
202 
203     int dflag = 0, loop = 1;
204     for (n++; loop && *n != NULL && (*n)[0] == '-'; n++)
205 	if (*(s = &((*n)[1])) == '\0')	/* test for bare "-" argument */
206 	    dflag |= DIR_OLD;
207 	else {
208 	    char *p;
209 	    while (*s != '\0')	/* examine flags */
210 	    {
211 		if ((p = strchr(dstr, *s++)) != NULL)
212 		    dflag |= (1 << (p - dstr));
213 	        else
214 		    stderror(ERR_DIRUS, short2str(**v), dstr, str);
215 	    }
216 	}
217     if (*n && (dflag & DIR_OLD))
218 	stderror(ERR_DIRUS, short2str(**v), dstr, str);
219     *v = n;
220     /* make -l, -v, and -n imply -p */
221     if (dflag & (DIR_LONG|DIR_VERT|DIR_LINE))
222 	dflag |= DIR_PRINT;
223     return dflag;
224 }
225 
226 /*
227  * dodirs - list all directories in directory loop
228  */
229 /*ARGSUSED*/
230 void
231 dodirs(Char **v, struct command *c)
232 {
233     static const char flags[] = "plvnSLc";
234     int dflag = skipargs(&v, flags, "");
235 
236     USE(c);
237     if ((dflag & DIR_CLEAR) != 0) {
238 	struct directory *dp, *fdp;
239 	for (dp = dcwd->di_next; dp != dcwd; ) {
240 	    fdp = dp;
241 	    dp = dp->di_next;
242 	    if (fdp != &dhead)
243 		dfree(fdp);
244 	}
245 	dhead.di_next = dhead.di_prev = dp;
246 	dp->di_next = dp->di_prev = &dhead;
247     }
248     if ((dflag & DIR_LOAD) != 0)
249 	loaddirs(*v);
250     else if ((dflag & DIR_SAVE) != 0)
251 	recdirs(*v, 1);
252 
253     if (*v && (dflag & (DIR_SAVE|DIR_LOAD)))
254 	v++;
255 
256     if (*v != NULL || (dflag & DIR_OLD))
257 	stderror(ERR_DIRUS, "dirs", flags, "");
258     if ((dflag & (DIR_CLEAR|DIR_LOAD|DIR_SAVE)) == 0 || (dflag & DIR_PRINT))
259 	printdirs(dflag);
260 }
261 
262 static void
263 printdirs(int dflag)
264 {
265     struct directory *dp;
266     Char   *s, *user;
267     int     idx, len, cur;
268 
269     dp = dcwd;
270     idx = 0;
271     cur = 0;
272     do {
273 	if (dp == &dhead)
274 	    continue;
275 	if (dflag & DIR_VERT) {
276 	    xprintf("%d\t", idx++);
277 	    cur = 0;
278 	}
279 	s = dp->di_name;
280 	user = NULL;
281 	if (!(dflag & DIR_LONG) && (user = getusername(&s)) != NULL)
282 	    len = (int) (Strlen(user) + Strlen(s) + 2);
283 	else
284 	    len = (int) (Strlen(s) + 1);
285 
286 	cur += len;
287 	if ((dflag & DIR_LINE) && cur >= TermH - 1 && len < TermH) {
288 	    xputchar('\n');
289 	    cur = len;
290 	}
291 	if (user)
292 	    xprintf("~%S", user);
293 	xprintf("%-S%c", s, (dflag & DIR_VERT) ? '\n' : ' ');
294     } while ((dp = dp->di_prev) != dcwd);
295     if (!(dflag & DIR_VERT))
296 	xputchar('\n');
297 }
298 
299 void
300 dtildepr(Char *dir)
301 {
302     Char* user;
303     if ((user = getusername(&dir)) != NULL)
304 	xprintf("~%-S%S", user, dir);
305     else
306 	xprintf("%S", dir);
307 }
308 
309 void
310 dtilde(void)
311 {
312     struct directory *d = dcwd;
313 
314     do {
315 	if (d == &dhead)
316 	    continue;
317 	d->di_name = dcanon(d->di_name, STRNULL);
318     } while ((d = d->di_prev) != dcwd);
319 
320     dset(dcwd->di_name);
321 }
322 
323 
324 /* dnormalize():
325  *	The path will be normalized if it
326  *	1) is "..",
327  *	2) or starts with "../",
328  *	3) or ends with "/..",
329  *	4) or contains the string "/../",
330  *	then it will be normalized, unless those strings are quoted.
331  *	Otherwise, a copy is made and sent back.
332  */
333 Char   *
334 dnormalize(const Char *cp, int expnd)
335 {
336 
337 /* return true if dp is of the form "../xxx" or "/../xxx" */
338 #define IS_DOTDOT(sp, p) (ISDOTDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
339 #define IS_DOT(sp, p) (ISDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
340 
341 #ifdef S_IFLNK
342     if (expnd) {
343 	struct Strbuf buf = Strbuf_INIT;
344  	int     dotdot = 0;
345 	Char   *dp, *cwd;
346 	const Char *start = cp;
347 # ifdef HAVE_SLASHSLASH
348 	int slashslash;
349 # endif /* HAVE_SLASHSLASH */
350 
351 	/*
352 	 * count the number of "../xxx" or "xxx/../xxx" in the path
353 	 */
354 	for ( ; *cp && *(cp + 1); cp++)
355 	    if (IS_DOTDOT(start, cp))
356 	        dotdot++;
357 
358 	/*
359 	 * if none, we are done.
360 	 */
361         if (dotdot == 0)
362 	    return (Strsave(start));
363 
364 # ifdef notdef
365 	struct stat sb;
366 	/*
367 	 * We disable this test because:
368 	 * cd /tmp; mkdir dir1 dir2; cd dir2; ln -s /tmp/dir1; cd dir1;
369 	 * echo ../../dir1 does not expand. We had enabled this before
370 	 * because it was bothering people with expansions in compilation
371 	 * lines like -I../../foo. Maybe we need some kind of finer grain
372 	 * control?
373 	 *
374 	 * If the path doesn't exist, we are done too.
375 	 */
376 	if (lstat(short2str(start), &sb) != 0 && errno == ENOENT)
377 	    return (Strsave(start));
378 # endif
379 
380 	cwd = xmalloc((Strlen(dcwd->di_name) + 3) * sizeof(Char));
381 	(void) Strcpy(cwd, dcwd->di_name);
382 
383 	/*
384 	 * If the path starts with a slash, we are not relative to
385 	 * the current working directory.
386 	 */
387 	if (ABSOLUTEP(start))
388 	    *cwd = '\0';
389 # ifdef HAVE_SLASHSLASH
390 	slashslash = cwd[0] == '/' && cwd[1] == '/';
391 # endif /* HAVE_SLASHSLASH */
392 
393 	/*
394 	 * Ignore . and count ..'s
395 	 */
396 	cp = start;
397 	do {
398 	    dotdot = 0;
399 	    buf.len = 0;
400 	    while (*cp)
401 	        if (IS_DOT(start, cp)) {
402 	            if (*++cp)
403 	                cp++;
404 	        }
405 	        else if (IS_DOTDOT(start, cp)) {
406 		    if (buf.len != 0)
407 		        break; /* finish analyzing .././../xxx/[..] */
408 		    dotdot++;
409 		    cp += 2;
410 		    if (*cp)
411 		        cp++;
412 	        }
413 	        else
414 		    Strbuf_append1(&buf, *cp++);
415 
416 	    Strbuf_terminate(&buf);
417 	    while (dotdot > 0)
418 	        if ((dp = Strrchr(cwd, '/')) != NULL) {
419 # ifdef HAVE_SLASHSLASH
420 		    if (dp == &cwd[1])
421 		        slashslash = 1;
422 # endif /* HAVE_SLASHSLASH */
423 		        *dp = '\0';
424 		        dotdot--;
425 	        }
426 	        else
427 		    break;
428 
429 	    if (!*cwd) {	/* too many ..'s, starts with "/" */
430 	        cwd[0] = '/';
431 # ifdef HAVE_SLASHSLASH
432 		/*
433 		 * Only append another slash, if already the former cwd
434 		 * was in a double-slash path.
435 		 */
436 		cwd[1] = slashslash ? '/' : '\0';
437 		cwd[2] = '\0';
438 # else /* !HAVE_SLASHSLASH */
439 		cwd[1] = '\0';
440 # endif /* HAVE_SLASHSLASH */
441 	    }
442 # ifdef HAVE_SLASHSLASH
443 	    else if (slashslash && cwd[1] == '\0') {
444 		cwd[1] = '/';
445 		cwd[2] = '\0';
446 	    }
447 # endif /* HAVE_SLASHSLASH */
448 
449 	    if (buf.len != 0) {
450 		size_t i;
451 
452 		i = Strlen(cwd);
453 		if (TRM(cwd[i - 1]) != '/') {
454 		    cwd[i++] = '/';
455 		    cwd[i] = '\0';
456 		}
457 	        dp = Strspl(cwd, TRM(buf.s[0]) == '/' ? &buf.s[1] : buf.s);
458 	        xfree(cwd);
459 	        cwd = dp;
460 		i = Strlen(cwd) - 1;
461 	        if (TRM(cwd[i]) == '/')
462 		    cwd[i] = '\0';
463 	    }
464 	    /* Reduction of ".." following the stuff we collected in buf
465 	     * only makes sense if the directory item in buf really exists.
466 	     * Avoid reduction of "-I../.." (typical compiler call) to ""
467 	     * or "/usr/nonexistant/../bin" to "/usr/bin":
468 	     */
469 	    if (cwd[0]) {
470 	        struct stat exists;
471 		if (0 != stat(short2str(cwd), &exists)) {
472 		    xfree(buf.s);
473 		    xfree(cwd);
474 		    return Strsave(start);
475 		}
476 	    }
477 	} while (*cp != '\0');
478 	xfree(buf.s);
479 	return cwd;
480     }
481 #endif /* S_IFLNK */
482     return Strsave(cp);
483 }
484 
485 
486 /*
487  * dochngd - implement chdir command.
488  */
489 /*ARGSUSED*/
490 void
491 dochngd(Char **v, struct command *c)
492 {
493     Char *cp;
494     struct directory *dp;
495     int dflag = skipargs(&v, "plvn", "[-|<dir>]");
496 
497     USE(c);
498     printd = 0;
499     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
500 
501     if (cp == NULL) {
502 	if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
503 	    stderror(ERR_NAME | ERR_NOHOMEDIR);
504 	if (chdir(short2str(cp)) < 0)
505 	    stderror(ERR_NAME | ERR_CANTCHANGE);
506 	cp = Strsave(cp);
507     }
508     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
509 	stderror(ERR_NAME | ERR_TOOMANY);
510 	/* NOTREACHED */
511 	return;
512     }
513     else if ((dp = dfind(cp)) != 0) {
514 	char   *tmp;
515 
516 	printd = 1;
517 	if (chdir(tmp = short2str(dp->di_name)) < 0)
518 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
519 	dcwd->di_prev->di_next = dcwd->di_next;
520 	dcwd->di_next->di_prev = dcwd->di_prev;
521 	dfree(dcwd);
522 	dnewcwd(dp, dflag);
523 	return;
524     }
525     else
526 	if ((cp = dfollow(cp, dflag & DIR_OLD)) == NULL)
527 	    return;
528     dp = xcalloc(sizeof(struct directory), 1);
529     dp->di_name = cp;
530     dp->di_count = 0;
531     dp->di_next = dcwd->di_next;
532     dp->di_prev = dcwd->di_prev;
533     dp->di_prev->di_next = dp;
534     dp->di_next->di_prev = dp;
535     dfree(dcwd);
536     dnewcwd(dp, dflag);
537 }
538 
539 static Char *
540 dgoto(Char *cp)
541 {
542     Char *dp, *ret;
543 
544     if (!ABSOLUTEP(cp))
545     {
546 	Char *p, *q;
547 	size_t cwdlen;
548 
549 	cwdlen = Strlen(dcwd->di_name);
550 	if (cwdlen == 1)	/* root */
551 	    cwdlen = 0;
552 	dp = xmalloc((cwdlen + Strlen(cp) + 2) * sizeof(Char));
553 	for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
554 	    continue;
555 	if (cwdlen)
556 	    p[-1] = '/';
557 	else
558 	    p--;		/* don't add a / after root */
559 	Strcpy(p, cp);
560 	xfree(cp);
561 	cp = dp;
562 	dp += cwdlen;
563     }
564     else
565 	dp = cp;
566 
567 #if defined(WINNT_NATIVE)
568     return agetcwd();
569 #elif defined(__CYGWIN__)
570     if (ABSOLUTEP(cp) && cp[1] == ':') { /* Only DOS paths are treated that way */
571 	return agetcwd();
572     } else {
573 	cleanup_push(cp, xfree);
574     	ret = dcanon(cp, dp);
575 	cleanup_ignore(cp);
576 	cleanup_until(cp);
577     }
578 #else /* !WINNT_NATIVE */
579     cleanup_push(cp, xfree);
580     ret = dcanon(cp, dp);
581     cleanup_ignore(cp);
582     cleanup_until(cp);
583 #endif /* WINNT_NATIVE */
584     return ret;
585 }
586 
587 /*
588  * dfollow - change to arg directory; fall back on cdpath if not valid
589  */
590 static Char *
591 dfollow(Char *cp, int old)
592 {
593     Char *dp;
594     struct varent *c;
595     int serrno;
596 
597     cp = old ? Strsave(cp) : globone(cp, G_ERROR);
598     cleanup_push(cp, xfree);
599 #ifdef apollo
600     if (Strchr(cp, '`')) {
601 	char *dptr;
602 	if (chdir(dptr = short2str(cp)) < 0)
603 	    stderror(ERR_SYSTEM, dptr, strerror(errno));
604 	dp = agetcwd();
605 	cleanup_push(dp, xfree);
606 	if (dp != NULL) {
607 	    cleanup_until(cp);
608 	    return dgoto(dp);
609 	}
610 	else
611 	    stderror(ERR_SYSTEM, dptr, strerror(errno));
612     }
613 #endif /* apollo */
614 
615     /*
616      * if we are ignoring symlinks, try to fix relatives now.
617      * if we are expading symlinks, it should be done by now.
618      */
619     dp = dnormalize(cp, symlinks == SYM_IGNORE);
620     if (chdir(short2str(dp)) >= 0) {
621         cleanup_until(cp);
622         return dgoto(dp);
623     }
624     else {
625         xfree(dp);
626         if (chdir(short2str(cp)) >= 0) {
627 	    cleanup_ignore(cp);
628 	    cleanup_until(cp);
629 	    return dgoto(cp);
630 	}
631 	else if (errno != ENOENT && errno != ENOTDIR) {
632 	    int err;
633 
634 	    err = errno;
635 	    stderror(ERR_SYSTEM, short2str(cp), strerror(err));
636 	}
637 	serrno = errno;
638     }
639 
640     if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
641 	&& (c = adrof(STRcdpath)) && c->vec != NULL) {
642 	struct Strbuf buf = Strbuf_INIT;
643 	Char  **cdp;
644 
645 	for (cdp = c->vec; *cdp; cdp++) {
646 	    buf.len = 0;
647 	    Strbuf_append(&buf, *cdp);
648 	    Strbuf_append1(&buf, '/');
649 	    Strbuf_append(&buf, cp);
650 	    Strbuf_terminate(&buf);
651 	    /*
652 	     * We always want to fix the directory here
653 	     * If we are normalizing symlinks
654 	     */
655 	    dp = dnormalize(buf.s, symlinks == SYM_IGNORE ||
656 				   symlinks == SYM_EXPAND);
657 	    if (chdir(short2str(dp)) >= 0) {
658 		printd = 1;
659 		xfree(buf.s);
660 		cleanup_until(cp);
661 		return dgoto(dp);
662 	    }
663 	    else if (chdir(short2str(cp)) >= 0) {
664 		printd = 1;
665 		xfree(dp);
666 		xfree(buf.s);
667 		cleanup_ignore(cp);
668 		cleanup_until(cp);
669 		return dgoto(cp);
670 	    }
671 	}
672 	xfree(buf.s);
673     }
674     dp = varval(cp);
675     if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
676 	cleanup_until(cp);
677 	cp = Strsave(dp);
678 	printd = 1;
679 	return dgoto(cp);
680     }
681     /*
682      * on login source of ~/.cshdirs, errors are eaten. the dir stack is all
683      * directories we could get to.
684      */
685     if (!bequiet)
686 	stderror(ERR_SYSTEM, short2str(cp), strerror(serrno));
687     cleanup_until(cp);
688     return (NULL);
689 }
690 
691 
692 /*
693  * dopushd - push new directory onto directory stack.
694  *	with no arguments exchange top and second.
695  *	with numeric argument (+n) bring it to top.
696  */
697 /*ARGSUSED*/
698 void
699 dopushd(Char **v, struct command *c)
700 {
701     struct directory *dp;
702     Char *cp;
703     int dflag = skipargs(&v, "plvn", " [-|<dir>|+<n>]");
704 
705     USE(c);
706     printd = 1;
707     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
708 
709     if (cp == NULL) {
710 	if (adrof(STRpushdtohome)) {
711 	    if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
712 		stderror(ERR_NAME | ERR_NOHOMEDIR);
713 	    if (chdir(short2str(cp)) < 0)
714 		stderror(ERR_NAME | ERR_CANTCHANGE);
715 	    if ((cp = dfollow(cp, dflag & DIR_OLD)) == NULL)
716 		return;
717 	    dp = xcalloc(sizeof(struct directory), 1);
718 	    dp->di_name = cp;
719 	    dp->di_count = 0;
720 	    dp->di_prev = dcwd;
721 	    dp->di_next = dcwd->di_next;
722 	    dcwd->di_next = dp;
723 	    dp->di_next->di_prev = dp;
724 	}
725 	else {
726 	    char   *tmp;
727 
728 	    if ((dp = dcwd->di_prev) == &dhead)
729 		dp = dhead.di_prev;
730 	    if (dp == dcwd)
731 		stderror(ERR_NAME | ERR_NODIR);
732 	    if (chdir(tmp = short2str(dp->di_name)) < 0)
733 		stderror(ERR_SYSTEM, tmp, strerror(errno));
734 	    dp->di_prev->di_next = dp->di_next;
735 	    dp->di_next->di_prev = dp->di_prev;
736 	    dp->di_next = dcwd->di_next;
737 	    dp->di_prev = dcwd;
738 	    dcwd->di_next->di_prev = dp;
739 	    dcwd->di_next = dp;
740 	}
741     }
742     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
743 	stderror(ERR_NAME | ERR_TOOMANY);
744 	/* NOTREACHED */
745 	return;
746     }
747     else if ((dp = dfind(cp)) != NULL) {
748 	char   *tmp;
749 
750 	if (chdir(tmp = short2str(dp->di_name)) < 0)
751 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
752 	/*
753 	 * kfk - 10 Feb 1984 - added new "extraction style" pushd +n
754 	 */
755 	if (adrof(STRdextract))
756 	    dextract(dp);
757     }
758     else {
759 	Char *ccp;
760 
761 	if ((ccp = dfollow(cp, dflag & DIR_OLD)) == NULL)
762 	    return;
763 	dp = xcalloc(sizeof(struct directory), 1);
764 	dp->di_name = ccp;
765 	dp->di_count = 0;
766 	dp->di_prev = dcwd;
767 	dp->di_next = dcwd->di_next;
768 	dcwd->di_next = dp;
769 	dp->di_next->di_prev = dp;
770     }
771     dnewcwd(dp, dflag);
772 }
773 
774 /*
775  * dfind - find a directory if specified by numeric (+n) argument
776  */
777 static struct directory *
778 dfind(Char *cp)
779 {
780     struct directory *dp;
781     int i;
782     Char *ep;
783 
784     if (*cp++ != '+')
785 	return (0);
786     for (ep = cp; Isdigit(*ep); ep++)
787 	continue;
788     if (*ep)
789 	return (0);
790     i = getn(cp);
791     if (i <= 0)
792 	return (0);
793     for (dp = dcwd; i != 0; i--) {
794 	if ((dp = dp->di_prev) == &dhead)
795 	    dp = dp->di_prev;
796 	if (dp == dcwd)
797 	    stderror(ERR_NAME | ERR_DEEP);
798     }
799     return (dp);
800 }
801 
802 /*
803  * dopopd - pop a directory out of the directory stack
804  *	with a numeric argument just discard it.
805  */
806 /*ARGSUSED*/
807 void
808 dopopd(Char **v, struct command *c)
809 {
810     Char *cp;
811     struct directory *dp, *p = NULL;
812     int dflag = skipargs(&v, "plvn", " [-|+<n>]");
813 
814     USE(c);
815     printd = 1;
816     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
817 
818     if (cp == NULL)
819 	dp = dcwd;
820     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
821 	stderror(ERR_NAME | ERR_TOOMANY);
822 	/* NOTREACHED */
823 	return;
824     }
825     else if ((dp = dfind(cp)) == 0)
826 	stderror(ERR_NAME | ERR_BADDIR);
827     if (dp->di_prev == &dhead && dp->di_next == &dhead)
828 	stderror(ERR_NAME | ERR_EMPTY);
829     if (dp == dcwd) {
830 	char   *tmp;
831 
832 	if ((p = dp->di_prev) == &dhead)
833 	    p = dhead.di_prev;
834 	if (chdir(tmp = short2str(p->di_name)) < 0)
835 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
836     }
837     dp->di_prev->di_next = dp->di_next;
838     dp->di_next->di_prev = dp->di_prev;
839     dfree(dp);
840     if (dp == dcwd) {
841         dnewcwd(p, dflag);
842     }
843     else {
844 	printdirs(dflag);
845     }
846 }
847 
848 /*
849  * dfree - free the directory (or keep it if it still has ref count)
850  */
851 void
852 dfree(struct directory *dp)
853 {
854 
855     if (dp->di_count != 0) {
856 	dp->di_next = dp->di_prev = 0;
857     }
858     else {
859 	xfree(dp->di_name);
860 	xfree(dp);
861     }
862 }
863 
864 /*
865  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
866  *	we are of course assuming that the file system is standardly
867  *	constructed (always have ..'s, directories have links)
868  */
869 Char   *
870 dcanon(Char *cp, Char *p)
871 {
872     Char *sp;
873     Char *p1, *p2;	/* general purpose */
874     int    slash;
875 #ifdef HAVE_SLASHSLASH
876     int    slashslash;
877 #endif /* HAVE_SLASHSLASH */
878     size_t  clen;
879 
880 #ifdef S_IFLNK			/* if we have symlinks */
881     Char *mlink, *newcp;
882     char *tlink;
883     size_t cc;
884 #endif /* S_IFLNK */
885 
886     clen = Strlen(cp);
887 
888     /*
889      * christos: if the path given does not start with a slash prepend cwd. If
890      * cwd does not start with a slash or the result would be too long try to
891      * correct it.
892      */
893     if (!ABSOLUTEP(cp)) {
894 	Char *tmpdir;
895 	size_t	len;
896 
897 	p1 = varval(STRcwd);
898 	if (p1 == STRNULL || !ABSOLUTEP(p1)) {
899 	    Char *new_cwd = agetcwd();
900 
901 	    if (new_cwd == NULL) {
902 		xprintf("%s: %s\n", progname, strerror(errno));
903 		setcopy(STRcwd, str2short("/"), VAR_READWRITE|VAR_NOGLOB);
904 	    }
905 	    else
906 		setv(STRcwd, new_cwd, VAR_READWRITE|VAR_NOGLOB);
907 	    p1 = varval(STRcwd);
908 	}
909 	len = Strlen(p1);
910 	tmpdir = xmalloc((len + clen + 2) * sizeof (*tmpdir));
911 	(void) Strcpy(tmpdir, p1);
912 	(void) Strcat(tmpdir, STRslash);
913 	(void) Strcat(tmpdir, cp);
914 	xfree(cp);
915 	cp = p = tmpdir;
916     }
917 
918 #ifdef HAVE_SLASHSLASH
919     slashslash = (cp[0] == '/' && cp[1] == '/');
920 #endif /* HAVE_SLASHSLASH */
921 
922     while (*p) {		/* for each component */
923 	sp = p;			/* save slash address */
924 	while (*++p == '/')	/* flush extra slashes */
925 	    continue;
926 	if (p != ++sp)
927 	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
928 		continue;
929 	p = sp;			/* save start of component */
930 	slash = 0;
931 	if (*p)
932 	    while (*++p)	/* find next slash or end of path */
933 		if (*p == '/') {
934 		    slash = 1;
935 		    *p = 0;
936 		    break;
937 		}
938 
939 #ifdef HAVE_SLASHSLASH
940 	if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0')
941 	    slashslash = 1;
942 #endif /* HAVE_SLASHSLASH */
943 	if (*sp == '\0') {	/* if component is null */
944 	    if (--sp == cp)	/* if path is one char (i.e. /) */
945 		break;
946 	    else
947 		*sp = '\0';
948 	}
949 	else if (sp[0] == '.' && sp[1] == 0) {
950 	    if (slash) {
951 		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
952 		    continue;
953 		p = --sp;
954 	    }
955 	    else if (--sp != cp)
956 		*sp = '\0';
957 	    else
958 		sp[1] = '\0';
959 	}
960 	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
961 	    /*
962 	     * We have something like "yyy/xxx/..", where "yyy" can be null or
963 	     * a path starting at /, and "xxx" is a single component. Before
964 	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
965 	     * symbolic link.
966 	     */
967 	    *--sp = 0;		/* form the pathname for readlink */
968 #ifdef S_IFLNK			/* if we have symlinks */
969 	    if (sp != cp && /* symlinks != SYM_IGNORE && */
970 		(tlink = areadlink(short2str(cp))) != NULL) {
971 		mlink = str2short(tlink);
972 		xfree(tlink);
973 
974 		if (slash)
975 		    *p = '/';
976 		/*
977 		 * Point p to the '/' in "/..", and restore the '/'.
978 		 */
979 		*(p = sp) = '/';
980 		if (*mlink != '/') {
981 		    /*
982 		     * Relative path, expand it between the "yyy/" and the
983 		     * "/..". First, back sp up to the character past "yyy/".
984 		     */
985 		    while (*--sp != '/')
986 			continue;
987 		    sp++;
988 		    *sp = 0;
989 		    /*
990 		     * New length is "yyy/" + mlink + "/.." and rest
991 		     */
992 		    p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
993 					  Strlen(p) + 1) * sizeof(Char));
994 		    /*
995 		     * Copy new path into newcp
996 		     */
997 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
998 			continue;
999 		    for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
1000 			continue;
1001 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1002 			continue;
1003 		    /*
1004 		     * Restart canonicalization at expanded "/xxx".
1005 		     */
1006 		    p = sp - cp - 1 + newcp;
1007 		}
1008 		else {
1009 		    newcp = Strspl(mlink, p);
1010 		    /*
1011 		     * Restart canonicalization at beginning
1012 		     */
1013 		    p = newcp;
1014 		}
1015 		xfree(cp);
1016 		cp = newcp;
1017 #ifdef HAVE_SLASHSLASH
1018                 slashslash = (cp[0] == '/' && cp[1] == '/');
1019 #endif /* HAVE_SLASHSLASH */
1020 		continue;	/* canonicalize the link */
1021 	    }
1022 #endif /* S_IFLNK */
1023 	    *sp = '/';
1024 	    if (sp != cp)
1025 		while (*--sp != '/')
1026 		    continue;
1027 	    if (slash) {
1028 		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
1029 		    continue;
1030 		p = sp;
1031 	    }
1032 	    else if (cp == sp)
1033 		*++sp = '\0';
1034 	    else
1035 		*sp = '\0';
1036 	}
1037 	else {			/* normal dir name (not . or .. or nothing) */
1038 
1039 #ifdef S_IFLNK			/* if we have symlinks */
1040 	    if (sp != cp && symlinks == SYM_CHASE &&
1041 		(tlink = areadlink(short2str(cp))) != NULL) {
1042 		mlink = str2short(tlink);
1043 		xfree(tlink);
1044 
1045 		/*
1046 		 * restore the '/'.
1047 		 */
1048 		if (slash)
1049 		    *p = '/';
1050 
1051 		/*
1052 		 * point sp to p (rather than backing up).
1053 		 */
1054 		sp = p;
1055 
1056 		if (*mlink != '/') {
1057 		    /*
1058 		     * Relative path, expand it between the "yyy/" and the
1059 		     * remainder. First, back sp up to the character past
1060 		     * "yyy/".
1061 		     */
1062 		    while (*--sp != '/')
1063 			continue;
1064 		    sp++;
1065 		    *sp = 0;
1066 		    /*
1067 		     * New length is "yyy/" + mlink + "/.." and rest
1068 		     */
1069 		    p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
1070 					  Strlen(p) + 1) * sizeof(Char));
1071 		    /*
1072 		     * Copy new path into newcp
1073 		     */
1074 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
1075 			continue;
1076 		    for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
1077 			continue;
1078 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1079 			continue;
1080 		    /*
1081 		     * Restart canonicalization at expanded "/xxx".
1082 		     */
1083 		    p = sp - cp - 1 + newcp;
1084 		}
1085 		else {
1086 		    newcp = Strspl(mlink, p);
1087 		    /*
1088 		     * Restart canonicalization at beginning
1089 		     */
1090 		    p = newcp;
1091 		}
1092 		xfree(cp);
1093 		cp = newcp;
1094 #ifdef HAVE_SLASHSLASH
1095                 slashslash = (cp[0] == '/' && cp[1] == '/');
1096 #endif /* HAVE_SLASHSLASH */
1097 		continue;	/* canonicalize the mlink */
1098 	    }
1099 #endif /* S_IFLNK */
1100 	    if (slash)
1101 		*p = '/';
1102 	}
1103     }
1104 
1105     /*
1106      * fix home...
1107      */
1108 #ifdef S_IFLNK
1109     p1 = varval(STRhome);
1110     cc = Strlen(p1);
1111     /*
1112      * See if we're not in a subdir of STRhome
1113      */
1114     if (p1 && *p1 == '/' && (Strncmp(p1, cp, cc) != 0 ||
1115 	(cp[cc] != '/' && cp[cc] != '\0'))) {
1116 	static ino_t home_ino = (ino_t) -1;
1117 	static dev_t home_dev = (dev_t) -1;
1118 	static Char *home_ptr = NULL;
1119 	struct stat statbuf;
1120 	int found;
1121 	Char *copy;
1122 
1123 	/*
1124 	 * Get dev and ino of STRhome
1125 	 */
1126 	if (home_ptr != p1 &&
1127 	    stat(short2str(p1), &statbuf) != -1) {
1128 	    home_dev = statbuf.st_dev;
1129 	    home_ino = statbuf.st_ino;
1130 	    home_ptr = p1;
1131 	}
1132 	/*
1133 	 * Start comparing dev & ino backwards
1134 	 */
1135 	p2 = copy = Strsave(cp);
1136 	found = 0;
1137 	while (*p2 && stat(short2str(p2), &statbuf) != -1) {
1138 	    if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) &&
1139 			statbuf.st_ino == home_ino) {
1140 			found = 1;
1141 			break;
1142 	    }
1143 	    if ((sp = Strrchr(p2, '/')) != NULL)
1144 		*sp = '\0';
1145 	}
1146 	/*
1147 	 * See if we found it
1148 	 */
1149 	if (*p2 && found) {
1150 	    /*
1151 	     * Use STRhome to make '~' work
1152 	     */
1153 	    newcp = Strspl(p1, cp + Strlen(p2));
1154 	    xfree(cp);
1155 	    cp = newcp;
1156 	}
1157 	xfree(copy);
1158     }
1159 #endif /* S_IFLNK */
1160 
1161 #ifdef HAVE_SLASHSLASH
1162     if (slashslash) {
1163 	if (cp[1] != '/') {
1164 	    p = xmalloc((Strlen(cp) + 2) * sizeof(Char));
1165 	    *p = '/';
1166 	    (void) Strcpy(&p[1], cp);
1167 	    xfree(cp);
1168 	    cp = p;
1169 	}
1170     }
1171     if (cp[1] == '/' && cp[2] == '/') {
1172 	for (p1 = &cp[1], p2 = &cp[2]; (*p1++ = *p2++) != '\0';)
1173 	    continue;
1174     }
1175 #endif /* HAVE_SLASHSLASH */
1176     return cp;
1177 }
1178 
1179 
1180 /*
1181  * dnewcwd - make a new directory in the loop the current one
1182  */
1183 static void
1184 dnewcwd(struct directory *dp, int dflag)
1185 {
1186     int print;
1187 
1188     if (adrof(STRdunique)) {
1189 	struct directory *dn;
1190 
1191 	for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev)
1192 	    if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) {
1193 		dn->di_next->di_prev = dn->di_prev;
1194 		dn->di_prev->di_next = dn->di_next;
1195 		dfree(dn);
1196 		break;
1197 	    }
1198     }
1199     dcwd = dp;
1200     dset(dcwd->di_name);
1201     dgetstack();
1202     print = printd;		/* if printd is set, print dirstack... */
1203     if (adrof(STRpushdsilent))	/* but pushdsilent overrides printd... */
1204 	print = 0;
1205     if (dflag & DIR_PRINT)	/* but DIR_PRINT overrides pushdsilent... */
1206 	print = 1;
1207     if (bequiet)		/* and bequiet overrides everything */
1208 	print = 0;
1209     if (print)
1210 	printdirs(dflag);
1211     cwd_cmd();			/* PWP: run the defined cwd command */
1212 }
1213 
1214 void
1215 dsetstack(void)
1216 {
1217     Char **cp;
1218     struct varent *vp;
1219     struct directory *dn, *dp;
1220 
1221     if ((vp = adrof(STRdirstack)) == NULL || vp->vec == NULL)
1222 	return;
1223 
1224     /* Free the whole stack */
1225     while ((dn = dhead.di_prev) != &dhead) {
1226 	dn->di_next->di_prev = dn->di_prev;
1227 	dn->di_prev->di_next = dn->di_next;
1228 	if (dn != dcwd)
1229 	    dfree(dn);
1230     }
1231 
1232     /* thread the current working directory */
1233     dhead.di_prev = dhead.di_next = dcwd;
1234     dcwd->di_next = dcwd->di_prev = &dhead;
1235 
1236     /* put back the stack */
1237     for (cp = vp->vec; cp && *cp && **cp; cp++) {
1238 	dp = xcalloc(sizeof(struct directory), 1);
1239 	dp->di_name = Strsave(*cp);
1240 	dp->di_count = 0;
1241 	dp->di_prev = dcwd;
1242 	dp->di_next = dcwd->di_next;
1243 	dcwd->di_next = dp;
1244 	dp->di_next->di_prev = dp;
1245     }
1246     dgetstack();	/* Make $dirstack reflect the current state */
1247 }
1248 
1249 static void
1250 dgetstack(void)
1251 {
1252     int i = 0;
1253     Char **dblk, **dbp;
1254     struct directory *dn;
1255 
1256     if (adrof(STRdirstack) == NULL)
1257     	return;
1258 
1259     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++)
1260 	continue;
1261     dbp = dblk = xmalloc((i + 1) * sizeof(Char *));
1262     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++)
1263 	 *dbp = Strsave(dn->di_name);
1264     *dbp = NULL;
1265     cleanup_push(dblk, blk_cleanup);
1266     setq(STRdirstack, dblk, &shvhed, VAR_READWRITE);
1267     cleanup_ignore(dblk);
1268     cleanup_until(dblk);
1269 }
1270 
1271 /*
1272  * getstakd - added by kfk 17 Jan 1984
1273  * Support routine for the stack hack.  Finds nth directory in
1274  * the directory stack, or finds last directory in stack.
1275  */
1276 const Char *
1277 getstakd(int cnt)
1278 {
1279     struct directory *dp;
1280 
1281     dp = dcwd;
1282     if (cnt < 0) {		/* < 0 ==> last dir requested. */
1283 	dp = dp->di_next;
1284 	if (dp == &dhead)
1285 	    dp = dp->di_next;
1286     }
1287     else {
1288 	while (cnt-- > 0) {
1289 	    dp = dp->di_prev;
1290 	    if (dp == &dhead)
1291 		dp = dp->di_prev;
1292 	    if (dp == dcwd)
1293 		return NULL;
1294 	}
1295     }
1296     return dp->di_name;
1297 }
1298 
1299 /*
1300  * Karl Kleinpaste - 10 Feb 1984
1301  * Added dextract(), which is used in pushd +n.
1302  * Instead of just rotating the entire stack around, dextract()
1303  * lets the user have the nth dir extracted from its current
1304  * position, and pushes it onto the top.
1305  */
1306 static void
1307 dextract(struct directory *dp)
1308 {
1309     if (dp == dcwd)
1310 	return;
1311     dp->di_next->di_prev = dp->di_prev;
1312     dp->di_prev->di_next = dp->di_next;
1313     dp->di_next = dcwd->di_next;
1314     dp->di_prev = dcwd;
1315     dp->di_next->di_prev = dp;
1316     dcwd->di_next = dp;
1317 }
1318 
1319 static void
1320 bequiet_cleanup(void *dummy)
1321 {
1322     USE(dummy);
1323     bequiet = 0;
1324 }
1325 
1326 void
1327 loaddirs(Char *fname)
1328 {
1329     static Char *loaddirs_cmd[] = { STRsource, NULL, NULL };
1330 
1331     bequiet = 1;
1332     cleanup_push(&bequiet, bequiet_cleanup);
1333     if (fname)
1334 	loaddirs_cmd[1] = fname;
1335     else if ((fname = varval(STRdirsfile)) != STRNULL)
1336 	loaddirs_cmd[1] = fname;
1337     else
1338 	loaddirs_cmd[1] = STRtildotdirs;
1339     dosource(loaddirs_cmd, NULL);
1340     cleanup_until(&bequiet);
1341 }
1342 
1343 /*
1344  * create a file called ~/.cshdirs which has a sequence
1345  * of pushd commands which will restore the dir stack to
1346  * its state before exit/logout. remember that the order
1347  * is reversed in the file because we are pushing.
1348  * -strike
1349  */
1350 void
1351 recdirs(Char *fname, int def)
1352 {
1353     int     fp, ftmp, oldidfds;
1354     int     cdflag = 0;
1355     struct directory *dp;
1356     unsigned int    num;
1357     Char   *snum;
1358     struct Strbuf qname = Strbuf_INIT;
1359 
1360     if (fname == NULL && !def)
1361 	return;
1362 
1363     if (fname == NULL) {
1364 	if ((fname = varval(STRdirsfile)) == STRNULL)
1365 	    fname = Strspl(varval(STRhome), &STRtildotdirs[1]);
1366 	else
1367 	    fname = Strsave(fname);
1368     }
1369     else
1370 	fname = globone(fname, G_ERROR);
1371     cleanup_push(fname, xfree);
1372 
1373     if ((fp = xcreat(short2str(fname), 0600)) == -1) {
1374 	cleanup_until(fname);
1375 	return;
1376     }
1377 
1378     if ((snum = varval(STRsavedirs)) == STRNULL || snum[0] == '\0')
1379 	num = (unsigned int) ~0;
1380     else
1381 	num = (unsigned int) atoi(short2str(snum));
1382 
1383     oldidfds = didfds;
1384     didfds = 0;
1385     ftmp = SHOUT;
1386     SHOUT = fp;
1387 
1388     cleanup_push(&qname, Strbuf_cleanup);
1389     dp = dcwd->di_next;
1390     do {
1391 	if (dp == &dhead)
1392 	    continue;
1393 
1394 	if (cdflag == 0) {
1395 	    cdflag = 1;
1396 	    xprintf("cd %S\n", quote_meta(&qname, dp->di_name));
1397 	}
1398 	else
1399 	    xprintf("pushd %S\n", quote_meta(&qname, dp->di_name));
1400 
1401 	if (num-- == 0)
1402 	    break;
1403 
1404     } while ((dp = dp->di_next) != dcwd->di_next);
1405 
1406     xclose(fp);
1407     SHOUT = ftmp;
1408     didfds = oldidfds;
1409     cleanup_until(fname);
1410 }
1411