xref: /freebsd/contrib/tcsh/sh.dir.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
1 /* $Header: /src/pub/tcsh/sh.dir.c,v 3.52 1998/10/25 15:10:03 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.52 1998/10/25 15:10:03 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
517     cp = SAVE(getcwd(NULL, 0));
518 #else /* !WINNT */
519     cp = dcanon(cp, dp);
520 #endif /* WINNT */
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 
818 #ifdef S_IFLNK			/* if we have symlinks */
819     Char    link[MAXPATHLEN];
820     char    tlink[MAXPATHLEN];
821     int     cc;
822     Char   *newcp;
823 #endif /* S_IFLNK */
824 
825     /*
826      * kim: if the path given is too long abort().
827      */
828     if (Strlen(cp) >= MAXPATHLEN)
829 	abort();
830 
831     /*
832      * christos: if the path given does not start with a slash prepend cwd. If
833      * cwd does not start with a slash or the result would be too long abort().
834      */
835     if (!ABSOLUTEP(cp)) {
836 	Char    tmpdir[MAXPATHLEN];
837 
838 	p1 = varval(STRcwd);
839 	if (p1 == STRNULL || !ABSOLUTEP(p1))
840 	    abort();
841 	if (Strlen(p1) + Strlen(cp) + 1 >= MAXPATHLEN)
842 	    abort();
843 	(void) Strcpy(tmpdir, p1);
844 	(void) Strcat(tmpdir, STRslash);
845 	(void) Strcat(tmpdir, cp);
846 	xfree((ptr_t) cp);
847 	cp = p = Strsave(tmpdir);
848     }
849 
850 #ifdef COMMENT
851     if (*cp != '/')
852 	abort();
853 #endif /* COMMENT */
854 
855 #ifdef apollo
856     slashslash = (cp[0] == '/' && cp[1] == '/');
857 #endif /* apollo */
858 
859     while (*p) {		/* for each component */
860 	sp = p;			/* save slash address */
861 	while (*++p == '/')	/* flush extra slashes */
862 	    continue;
863 	if (p != ++sp)
864 	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
865 		continue;
866 	p = sp;			/* save start of component */
867 	slash = 0;
868 	if (*p)
869 	    while (*++p)	/* find next slash or end of path */
870 		if (*p == '/') {
871 		    slash = 1;
872 		    *p = 0;
873 		    break;
874 		}
875 
876 #ifdef apollo
877 	if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0')
878 	    slashslash = 1;
879 #endif /* apollo */
880 	if (*sp == '\0') {	/* if component is null */
881 	    if (--sp == cp)	/* if path is one char (i.e. /) */
882 		break;
883 	    else
884 		*sp = '\0';
885 	}
886 	else if (sp[0] == '.' && sp[1] == 0) {
887 	    if (slash) {
888 		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
889 		    continue;
890 		p = --sp;
891 	    }
892 	    else if (--sp != cp)
893 		*sp = '\0';
894 	    else
895 		sp[1] = '\0';
896 	}
897 	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
898 	    /*
899 	     * We have something like "yyy/xxx/..", where "yyy" can be null or
900 	     * a path starting at /, and "xxx" is a single component. Before
901 	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
902 	     * symbolic link.
903 	     */
904 	    *--sp = 0;		/* form the pathname for readlink */
905 #ifdef S_IFLNK			/* if we have symlinks */
906 	    if (sp != cp && /* symlinks != SYM_IGNORE && */
907 		(cc = readlink(short2str(cp), tlink,
908 			       sizeof tlink)) >= 0) {
909 		tlink[cc] = '\0';
910 		(void) Strncpy(link, str2short(tlink),
911 		    sizeof(link) / sizeof(Char));
912 		link[sizeof(link) / sizeof(Char) - 1] = '\0';
913 
914 		if (slash)
915 		    *p = '/';
916 		/*
917 		 * Point p to the '/' in "/..", and restore the '/'.
918 		 */
919 		*(p = sp) = '/';
920 		/*
921 		 * find length of p
922 		 */
923 		for (p1 = p; *p1++;)
924 		    continue;
925 		if (*link != '/') {
926 		    /*
927 		     * Relative path, expand it between the "yyy/" and the
928 		     * "/..". First, back sp up to the character past "yyy/".
929 		     */
930 		    while (*--sp != '/')
931 			continue;
932 		    sp++;
933 		    *sp = 0;
934 		    /*
935 		     * New length is "yyy/" + link + "/.." and rest
936 		     */
937 		    p1 = newcp = (Char *) xmalloc((size_t)
938 						(((sp - cp) + cc + (p1 - p)) *
939 						 sizeof(Char)));
940 		    /*
941 		     * Copy new path into newcp
942 		     */
943 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
944 			continue;
945 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
946 			continue;
947 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
948 			continue;
949 		    /*
950 		     * Restart canonicalization at expanded "/xxx".
951 		     */
952 		    p = sp - cp - 1 + newcp;
953 		}
954 		else {
955 		    /*
956 		     * New length is link + "/.." and rest
957 		     */
958 		    p1 = newcp = (Char *) xmalloc((size_t)
959 					    ((cc + (p1 - p)) * sizeof(Char)));
960 		    /*
961 		     * Copy new path into newcp
962 		     */
963 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
964 			continue;
965 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
966 			continue;
967 		    /*
968 		     * Restart canonicalization at beginning
969 		     */
970 		    p = newcp;
971 		}
972 		xfree((ptr_t) cp);
973 		cp = newcp;
974 #ifdef apollo
975                 slashslash = (cp[0] == '/' && cp[1] == '/');
976 #endif /* apollo */
977 		continue;	/* canonicalize the link */
978 	    }
979 #endif /* S_IFLNK */
980 	    *sp = '/';
981 	    if (sp != cp)
982 		while (*--sp != '/')
983 		    continue;
984 	    if (slash) {
985 		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
986 		    continue;
987 		p = sp;
988 	    }
989 	    else if (cp == sp)
990 		*++sp = '\0';
991 	    else
992 		*sp = '\0';
993 	}
994 	else {			/* normal dir name (not . or .. or nothing) */
995 
996 #ifdef S_IFLNK			/* if we have symlinks */
997 	    if (sp != cp && symlinks == SYM_CHASE &&
998 		(cc = readlink(short2str(cp), tlink,
999 			       sizeof tlink)) >= 0) {
1000 		tlink[cc] = '\0';
1001 		(void) Strncpy(link, str2short(tlink),
1002 		    sizeof(link) / sizeof(Char));
1003 		link[sizeof(link) / sizeof(Char) - 1] = '\0';
1004 
1005 		/*
1006 		 * restore the '/'.
1007 		 */
1008 		if (slash)
1009 		    *p = '/';
1010 
1011 		/*
1012 		 * point sp to p (rather than backing up).
1013 		 */
1014 		sp = p;
1015 
1016 		/*
1017 		 * find length of p
1018 		 */
1019 		for (p1 = p; *p1++;)
1020 		    continue;
1021 		if (*link != '/') {
1022 		    /*
1023 		     * Relative path, expand it between the "yyy/" and the
1024 		     * remainder. First, back sp up to the character past
1025 		     * "yyy/".
1026 		     */
1027 		    while (*--sp != '/')
1028 			continue;
1029 		    sp++;
1030 		    *sp = 0;
1031 		    /*
1032 		     * New length is "yyy/" + link + "/.." and rest
1033 		     */
1034 		    p1 = newcp = (Char *) xmalloc((size_t)
1035 						  (((sp - cp) + cc + (p1 - p))
1036 						   * sizeof(Char)));
1037 		    /*
1038 		     * Copy new path into newcp
1039 		     */
1040 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
1041 			continue;
1042 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
1043 			continue;
1044 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1045 			continue;
1046 		    /*
1047 		     * Restart canonicalization at expanded "/xxx".
1048 		     */
1049 		    p = sp - cp - 1 + newcp;
1050 		}
1051 		else {
1052 		    /*
1053 		     * New length is link + the rest
1054 		     */
1055 		    p1 = newcp = (Char *) xmalloc((size_t)
1056 					    ((cc + (p1 - p)) * sizeof(Char)));
1057 		    /*
1058 		     * Copy new path into newcp
1059 		     */
1060 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
1061 			continue;
1062 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1063 			continue;
1064 		    /*
1065 		     * Restart canonicalization at beginning
1066 		     */
1067 		    p = newcp;
1068 		}
1069 		xfree((ptr_t) cp);
1070 		cp = newcp;
1071 #ifdef apollo
1072                 slashslash = (cp[0] == '/' && cp[1] == '/');
1073 #endif /* apollo */
1074 		continue;	/* canonicalize the link */
1075 	    }
1076 #endif /* S_IFLNK */
1077 	    if (slash)
1078 		*p = '/';
1079 	}
1080     }
1081 
1082     /*
1083      * fix home...
1084      */
1085 #ifdef S_IFLNK
1086     p1 = varval(STRhome);
1087     cc = (int) Strlen(p1);
1088     /*
1089      * See if we're not in a subdir of STRhome
1090      */
1091     if (p1 && *p1 == '/' && (Strncmp(p1, cp, (size_t) cc) != 0 ||
1092 	(cp[cc] != '/' && cp[cc] != '\0'))) {
1093 	static ino_t home_ino = (ino_t) -1;
1094 	static dev_t home_dev = (dev_t) -1;
1095 	static Char *home_ptr = NULL;
1096 	struct stat statbuf;
1097 	int found;
1098 
1099 	/*
1100 	 * Get dev and ino of STRhome
1101 	 */
1102 	if (home_ptr != p1 &&
1103 	    stat(short2str(p1), &statbuf) != -1) {
1104 	    home_dev = statbuf.st_dev;
1105 	    home_ino = statbuf.st_ino;
1106 	    home_ptr = p1;
1107 	}
1108 	/*
1109 	 * Start comparing dev & ino backwards
1110 	 */
1111 	p2 = Strncpy(link, cp, sizeof(link) / sizeof(Char));
1112 	link[sizeof(link) / sizeof(Char) - 1] = '\0';
1113 	found = 0;
1114 	while (*p2 && stat(short2str(p2), &statbuf) != -1) {
1115 	    if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) &&
1116 			statbuf.st_ino == home_ino) {
1117 			found = 1;
1118 			break;
1119 	    }
1120 	    if ((sp = Strrchr(p2, '/')) != NULL)
1121 		*sp = '\0';
1122 	}
1123 	/*
1124 	 * See if we found it
1125 	 */
1126 	if (*p2 && found) {
1127 	    /*
1128 	     * Use STRhome to make '~' work
1129 	     */
1130 	    newcp = Strspl(p1, cp + Strlen(p2));
1131 	    xfree((ptr_t) cp);
1132 	    cp = newcp;
1133 	}
1134     }
1135 #endif /* S_IFLNK */
1136 
1137 #ifdef apollo
1138     if (slashslash) {
1139 	if (cp[1] != '/') {
1140 	    p = (Char *) xmalloc((size_t) (Strlen(cp) + 2) * sizeof(Char));
1141 	    *p = '/';
1142 	    (void) Strcpy(&p[1], cp);
1143 	    xfree((ptr_t) cp);
1144 	    cp = p;
1145 	}
1146     }
1147     if (cp[1] == '/' && cp[2] == '/')
1148 	(void) Strcpy(&cp[1], &cp[2]);
1149 #endif /* apollo */
1150     return cp;
1151 }
1152 
1153 
1154 /*
1155  * dnewcwd - make a new directory in the loop the current one
1156  */
1157 static void
1158 dnewcwd(dp, dflag)
1159     register struct directory *dp;
1160     int dflag;
1161 {
1162     int print;
1163 
1164     if (adrof(STRdunique)) {
1165 	struct directory *dn;
1166 
1167 	for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev)
1168 	    if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) {
1169 		dn->di_next->di_prev = dn->di_prev;
1170 		dn->di_prev->di_next = dn->di_next;
1171 		dfree(dn);
1172 		break;
1173 	    }
1174     }
1175     dcwd = dp;
1176     dset(dcwd->di_name);
1177     dgetstack();
1178     print = printd;		/* if printd is set, print dirstack... */
1179     if (adrof(STRpushdsilent))	/* but pushdsilent overrides printd... */
1180 	print = 0;
1181     if (dflag & DIR_PRINT)	/* but DIR_PRINT overrides pushdsilent... */
1182 	print = 1;
1183     if (bequiet)		/* and bequiet overrides everything */
1184 	print = 0;
1185     if (print)
1186 	printdirs(dflag);
1187     cwd_cmd();			/* PWP: run the defined cwd command */
1188 }
1189 
1190 void
1191 dsetstack()
1192 {
1193     Char **cp;
1194     struct varent *vp;
1195     struct directory *dn, *dp;
1196 
1197     if ((vp = adrof(STRdirstack)) == NULL)
1198 	return;
1199 
1200     /* Free the whole stack */
1201     while ((dn = dhead.di_prev) != &dhead) {
1202 	dn->di_next->di_prev = dn->di_prev;
1203 	dn->di_prev->di_next = dn->di_next;
1204 	if (dn != dcwd)
1205 	    dfree(dn);
1206     }
1207 
1208     /* thread the current working directory */
1209     dhead.di_prev = dhead.di_next = dcwd;
1210     dcwd->di_next = dcwd->di_prev = &dhead;
1211 
1212     /* put back the stack */
1213     for (cp = vp->vec; cp && *cp && **cp; cp++) {
1214 	dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
1215 	dp->di_name = Strsave(*cp);
1216 	dp->di_count = 0;
1217 	dp->di_prev = dcwd;
1218 	dp->di_next = dcwd->di_next;
1219 	dcwd->di_next = dp;
1220 	dp->di_next->di_prev = dp;
1221     }
1222     dgetstack();	/* Make $dirstack reflect the current state */
1223 }
1224 
1225 static void
1226 dgetstack()
1227 {
1228     int i = 0;
1229     Char **dblk, **dbp;
1230     struct directory *dn;
1231 
1232     if (adrof(STRdirstack) == NULL)
1233     	return;
1234 
1235     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++)
1236 	continue;
1237     dbp = dblk = (Char**) xmalloc((size_t) (i + 1) * sizeof(Char *));
1238     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++)
1239 	 *dbp = Strsave(dn->di_name);
1240     *dbp = NULL;
1241     setq(STRdirstack, dblk, &shvhed, VAR_READWRITE);
1242 }
1243 
1244 /*
1245  * getstakd - added by kfk 17 Jan 1984
1246  * Support routine for the stack hack.  Finds nth directory in
1247  * the directory stack, or finds last directory in stack.
1248  */
1249 int
1250 getstakd(s, cnt)
1251     Char   *s;
1252     int     cnt;
1253 {
1254     struct directory *dp;
1255 
1256     dp = dcwd;
1257     if (cnt < 0) {		/* < 0 ==> last dir requested. */
1258 	dp = dp->di_next;
1259 	if (dp == &dhead)
1260 	    dp = dp->di_next;
1261     }
1262     else {
1263 	while (cnt-- > 0) {
1264 	    dp = dp->di_prev;
1265 	    if (dp == &dhead)
1266 		dp = dp->di_prev;
1267 	    if (dp == dcwd)
1268 		return (0);
1269 	}
1270     }
1271     (void) Strcpy(s, dp->di_name);
1272     return (1);
1273 }
1274 
1275 /*
1276  * Karl Kleinpaste - 10 Feb 1984
1277  * Added dextract(), which is used in pushd +n.
1278  * Instead of just rotating the entire stack around, dextract()
1279  * lets the user have the nth dir extracted from its current
1280  * position, and pushes it onto the top.
1281  */
1282 static void
1283 dextract(dp)
1284     struct directory *dp;
1285 {
1286     if (dp == dcwd)
1287 	return;
1288     dp->di_next->di_prev = dp->di_prev;
1289     dp->di_prev->di_next = dp->di_next;
1290     dp->di_next = dcwd->di_next;
1291     dp->di_prev = dcwd;
1292     dp->di_next->di_prev = dp;
1293     dcwd->di_next = dp;
1294 }
1295 
1296 void
1297 loaddirs(fname)
1298     Char *fname;
1299 {
1300     static Char *loaddirs_cmd[] = { STRsource, NULL, NULL };
1301 
1302     bequiet = 1;
1303     if (fname)
1304 	loaddirs_cmd[1] = fname;
1305     else if ((fname = varval(STRdirsfile)) != STRNULL)
1306 	loaddirs_cmd[1] = fname;
1307     else
1308 	loaddirs_cmd[1] = STRtildotdirs;
1309     dosource(loaddirs_cmd, (struct command *)0);
1310     bequiet = 0;
1311 }
1312 
1313 /*
1314  * create a file called ~/.cshdirs which has a sequence
1315  * of pushd commands which will restore the dir stack to
1316  * its state before exit/logout. remember that the order
1317  * is reversed in the file because we are pushing.
1318  * -strike
1319  */
1320 void
1321 recdirs(fname, def)
1322     Char *fname;
1323     int def;
1324 {
1325     int     fp, ftmp, oldidfds;
1326     int     cdflag = 0;
1327     extern struct directory *dcwd;
1328     struct directory *dp;
1329     unsigned int    num;
1330     Char   *snum;
1331     Char    qname[MAXPATHLEN*2];
1332 
1333     if (fname == NULL && !def)
1334 	return;
1335 
1336     if (fname == NULL) {
1337 	if ((fname = varval(STRdirsfile)) == STRNULL)
1338 	    fname = Strspl(varval(STRhome), &STRtildotdirs[1]);
1339 	else
1340 	    fname = Strsave(fname);
1341     }
1342     else
1343 	fname = globone(fname, G_ERROR);
1344 
1345     if ((fp = creat(short2str(fname), 0600)) == -1) {
1346 	xfree((ptr_t) fname);
1347 	return;
1348     }
1349 
1350     if ((snum = varval(STRsavedirs)) == STRNULL)
1351 	num = (unsigned int) ~0;
1352     else
1353 	num = (unsigned int) atoi(short2str(snum));
1354 
1355     oldidfds = didfds;
1356     didfds = 0;
1357     ftmp = SHOUT;
1358     SHOUT = fp;
1359 
1360     dp = dcwd->di_next;
1361     do {
1362 	if (dp == &dhead)
1363 	    continue;
1364 
1365 	if (cdflag == 0) {
1366 	    cdflag = 1;
1367 	    xprintf("cd %S\n", quote_meta(qname, dp->di_name));
1368 	}
1369 	else
1370 	    xprintf("pushd %S\n", quote_meta(qname, dp->di_name));
1371 
1372 	if (num-- == 0)
1373 	    break;
1374 
1375     } while ((dp = dp->di_next) != dcwd->di_next);
1376 
1377     (void) close(fp);
1378     SHOUT = ftmp;
1379     didfds = oldidfds;
1380     xfree((ptr_t) fname);
1381 }
1382