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