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