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