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