sh.dir.c revision 167466
189857Sobrien/* $Header: /p/tcsh/cvsroot/tcsh/sh.dir.c,v 3.79 2006/09/25 18:17:26 christos Exp $ */
289857Sobrien/*
389857Sobrien * sh.dir.c: Directory manipulation functions
489857Sobrien */
589857Sobrien/*-
689857Sobrien * Copyright (c) 1980, 1991 The Regents of the University of California.
777298Sobrien * All rights reserved.
877298Sobrien *
977298Sobrien * Redistribution and use in source and binary forms, with or without
1077298Sobrien * modification, are permitted provided that the following conditions
1177298Sobrien * are met:
1277298Sobrien * 1. Redistributions of source code must retain the above copyright
1377298Sobrien *    notice, this list of conditions and the following disclaimer.
1477298Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1577298Sobrien *    notice, this list of conditions and the following disclaimer in the
1677298Sobrien *    documentation and/or other materials provided with the distribution.
1777298Sobrien * 3. Neither the name of the University nor the names of its contributors
1877298Sobrien *    may be used to endorse or promote products derived from this software
1977298Sobrien *    without specific prior written permission.
2077298Sobrien *
2177298Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2277298Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2377298Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2477298Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2577298Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2677298Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2777298Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2877298Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2977298Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3077298Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3177298Sobrien * SUCH DAMAGE.
3277298Sobrien */
3377298Sobrien#include "sh.h"
3477298Sobrien#include "ed.h"
3577298Sobrien
3677298SobrienRCSID("$tcsh: sh.dir.c,v 3.79 2006/09/25 18:17:26 christos Exp $")
3777298Sobrien
3877298Sobrien/*
3977298Sobrien * C Shell - directory management
4077298Sobrien */
4177298Sobrien
4277298Sobrienstatic	Char			*agetcwd	(void);
4377298Sobrienstatic	void			 dstart		(const char *);
4477298Sobrienstatic	struct directory	*dfind		(Char *);
4577298Sobrienstatic	Char 			*dfollow	(Char *);
4677298Sobrienstatic	void 	 	 	 printdirs	(int);
4777298Sobrienstatic	Char 			*dgoto		(Char *);
4877298Sobrienstatic	void 	 	 	 dnewcwd	(struct directory *, int);
4977298Sobrienstatic	void 	 	 	 dset		(Char *);
5077298Sobrienstatic  void 			 dextract	(struct directory *);
5177298Sobrienstatic  int 			 skipargs	(Char ***, const char *,
5277298Sobrien						 const char *);
5377298Sobrienstatic	void			 dgetstack	(void);
5477298Sobrien
5577298Sobrienstatic struct directory dhead INIT_ZERO_STRUCT;		/* "head" of loop */
5677298Sobrienstatic int    printd;			/* force name to be printed */
5777298Sobrien
5877298Sobrienint     bequiet = 0;		/* do not print dir stack -strike */
5977298Sobrien
6077298Sobrienstatic Char *
6177298Sobrienagetcwd(void)
6277298Sobrien{
6377298Sobrien    char *buf;
6477298Sobrien    Char *cwd;
6577298Sobrien    size_t len;
6677298Sobrien
6777298Sobrien    len = MAXPATHLEN;
6877298Sobrien    buf = xmalloc(len);
6977298Sobrien    while (getcwd(buf, len) == NULL) {
7077298Sobrien	int err;
7177298Sobrien
7277298Sobrien	err = errno;
7377298Sobrien	if (err != ERANGE) {
7477298Sobrien	    xfree(buf);
7577298Sobrien	    errno = err;
7677298Sobrien	    return NULL;
7777298Sobrien	}
7877298Sobrien	len *= 2;
7977298Sobrien	buf = xrealloc(buf, len);
8077298Sobrien    }
8177298Sobrien    if (*buf == '\0') {
8277298Sobrien	xfree(buf);
8377298Sobrien	return NULL;
8477298Sobrien    }
8577298Sobrien    cwd = SAVE(buf);
8677298Sobrien    xfree(buf);
8777298Sobrien    return cwd;
8877298Sobrien}
8977298Sobrien
9077298Sobrienstatic void
9177298Sobriendstart(const char *from)
9277298Sobrien{
9377298Sobrien    xprintf(CGETS(12, 1, "%s: Trying to start from \"%s\"\n"), progname, from);
9477298Sobrien}
9577298Sobrien
9677298Sobrien/*
9777298Sobrien * dinit - initialize current working directory
9877298Sobrien */
9977298Sobrienvoid
10077298Sobriendinit(Char *hp)
10177298Sobrien{
10277298Sobrien    Char *cp, *tcp;
10377298Sobrien    struct directory *dp;
10477298Sobrien
10577298Sobrien    /* Don't believe the login shell home, because it may be a symlink */
10677298Sobrien    tcp = agetcwd();
10777298Sobrien    if (tcp == NULL) {
10877298Sobrien	xprintf("%s: %s\n", progname, strerror(errno));
10977298Sobrien	if (hp && *hp) {
11077298Sobrien	    char *xcp = short2str(hp);
11177298Sobrien	    dstart(xcp);
11277298Sobrien	    if (chdir(xcp) == -1)
11377298Sobrien		cp = NULL;
11477298Sobrien	    else
11577298Sobrien		cp = Strsave(hp);
11677298Sobrien	}
11777298Sobrien	else
11877298Sobrien	    cp = NULL;
11977298Sobrien	if (cp == NULL) {
12077298Sobrien	    dstart("/");
12177298Sobrien	    if (chdir("/") == -1)
12277298Sobrien		/* I am not even try to print an error message! */
12377298Sobrien		xexit(1);
12477298Sobrien	    cp = SAVE("/");
12577298Sobrien	}
12677298Sobrien    }
12777298Sobrien    else {
12877298Sobrien#ifdef S_IFLNK
12977298Sobrien	struct stat swd, shp;
13077298Sobrien	int swd_ok;
13177298Sobrien
13277298Sobrien	swd_ok = stat(short2str(tcp), &swd) == 0;
13377298Sobrien	/*
13477298Sobrien	 * See if $HOME is the working directory we got and use that
13577298Sobrien	 */
13677298Sobrien	if (swd_ok && hp && *hp && stat(short2str(hp), &shp) != -1 &&
13777298Sobrien	    DEV_DEV_COMPARE(swd.st_dev, shp.st_dev)  &&
13877298Sobrien		swd.st_ino == shp.st_ino)
13977298Sobrien	    cp = Strsave(hp);
14077298Sobrien	else {
14177298Sobrien	    char   *cwd;
14277298Sobrien
14377298Sobrien	    /*
14477298Sobrien	     * use PWD if we have it (for subshells)
14577298Sobrien	     */
14677298Sobrien	    if (swd_ok && (cwd = getenv("PWD")) != NULL) {
14777298Sobrien		if (stat(cwd, &shp) != -1 &&
14877298Sobrien			DEV_DEV_COMPARE(swd.st_dev, shp.st_dev) &&
14977298Sobrien		        swd.st_ino == shp.st_ino) {
15077298Sobrien		    tcp = SAVE(cwd);
15177298Sobrien		    cleanup_push(tcp, xfree);
15277298Sobrien		}
15377298Sobrien	    }
15477298Sobrien	    cleanup_push(tcp, xfree);
15577298Sobrien	    cp = dcanon(tcp, STRNULL);
15677298Sobrien	    cleanup_ignore(tcp);
15777298Sobrien	    cleanup_until(tcp);
15877298Sobrien	}
15977298Sobrien#else /* S_IFLNK */
16077298Sobrien	cleanup_push(tcp, xfree);
16177298Sobrien	cp = dcanon(tcp, STRNULL);
16277298Sobrien	cleanup_ignore(tcp);
16377298Sobrien	cleanup_until(tcp);
16477298Sobrien#endif /* S_IFLNK */
16577298Sobrien    }
16677298Sobrien
16777298Sobrien    dp = xcalloc(sizeof(struct directory), 1);
16877298Sobrien    dp->di_name = cp;
16977298Sobrien    dp->di_count = 0;
17077298Sobrien    dhead.di_next = dhead.di_prev = dp;
17177298Sobrien    dp->di_next = dp->di_prev = &dhead;
17277298Sobrien    printd = 0;
17377298Sobrien    dnewcwd(dp, 0);
17477298Sobrien    setcopy(STRdirstack, dp->di_name, VAR_READWRITE|VAR_NOGLOB);
17577298Sobrien}
17677298Sobrien
17777298Sobrienstatic void
17877298Sobriendset(Char *dp)
17977298Sobrien{
18077298Sobrien    /*
18177298Sobrien     * Don't call set() directly cause if the directory contains ` or
18277298Sobrien     * other junk characters glob will fail.
18389857Sobrien     */
18489857Sobrien    setcopy(STRowd, varval(STRcwd), VAR_READWRITE|VAR_NOGLOB);
18577298Sobrien    setcopy(STRcwd, dp, VAR_READWRITE|VAR_NOGLOB);
18677298Sobrien    tsetenv(STRPWD, dp);
18777298Sobrien}
18877298Sobrien
18977298Sobrien#define DIR_PRINT	0x01	/* -p */
19077298Sobrien#define DIR_LONG  	0x02	/* -l */
19177298Sobrien#define DIR_VERT  	0x04	/* -v */
19277298Sobrien#define DIR_LINE  	0x08	/* -n */
19377298Sobrien#define DIR_SAVE 	0x10	/* -S */
19477298Sobrien#define DIR_LOAD	0x20	/* -L */
19577298Sobrien#define DIR_CLEAR	0x40	/* -c */
19677298Sobrien#define DIR_OLD	  	0x80	/* - */
19777298Sobrien
19877298Sobrienstatic int
19977298Sobrienskipargs(Char ***v, const char *dstr, const char *str)
20077298Sobrien{
20177298Sobrien    Char  **n = *v, *s;
20277298Sobrien
20377298Sobrien    int dflag = 0, loop = 1;
20477298Sobrien    for (n++; loop && *n != NULL && (*n)[0] == '-'; n++)
20577298Sobrien	if (*(s = &((*n)[1])) == '\0')	/* test for bare "-" argument */
20677298Sobrien	    dflag |= DIR_OLD;
20777298Sobrien	else {
20877298Sobrien	    char *p;
20977298Sobrien	    while (*s != '\0')	/* examine flags */
21077298Sobrien	    {
21177298Sobrien		if ((p = strchr(dstr, *s++)) != NULL)
21277298Sobrien		    dflag |= (1 << (p - dstr));
21377298Sobrien	        else
21477298Sobrien		    stderror(ERR_DIRUS, short2str(**v), dstr, str);
21577298Sobrien	    }
21677298Sobrien	}
21777298Sobrien    if (*n && (dflag & DIR_OLD))
21877298Sobrien	stderror(ERR_DIRUS, short2str(**v), dstr, str);
21977298Sobrien    *v = n;
22077298Sobrien    /* make -l, -v, and -n imply -p */
22177298Sobrien    if (dflag & (DIR_LONG|DIR_VERT|DIR_LINE))
22277298Sobrien	dflag |= DIR_PRINT;
22377298Sobrien    return dflag;
22477298Sobrien}
22577298Sobrien
22677298Sobrien/*
22777298Sobrien * dodirs - list all directories in directory loop
22877298Sobrien */
22977298Sobrien/*ARGSUSED*/
23077298Sobrienvoid
23177298Sobriendodirs(Char **v, struct command *c)
23277298Sobrien{
23377298Sobrien    static const char flags[] = "plvnSLc";
23477298Sobrien    int dflag = skipargs(&v, flags, "");
23577298Sobrien
23677298Sobrien    USE(c);
23777298Sobrien    if ((dflag & DIR_CLEAR) != 0) {
23877298Sobrien	struct directory *dp, *fdp;
23977298Sobrien	for (dp = dcwd->di_next; dp != dcwd; ) {
24077298Sobrien	    fdp = dp;
24177298Sobrien	    dp = dp->di_next;
24277298Sobrien	    if (fdp != &dhead)
24377298Sobrien		dfree(fdp);
24477298Sobrien	}
24577298Sobrien	dhead.di_next = dhead.di_prev = dp;
24677298Sobrien	dp->di_next = dp->di_prev = &dhead;
24777298Sobrien    }
24877298Sobrien    if ((dflag & DIR_LOAD) != 0)
24977298Sobrien	loaddirs(*v);
25077298Sobrien    else if ((dflag & DIR_SAVE) != 0)
25177298Sobrien	recdirs(*v, 1);
25277298Sobrien
25377298Sobrien    if (*v && (dflag & (DIR_SAVE|DIR_LOAD)))
25477298Sobrien	v++;
25577298Sobrien
25677298Sobrien    if (*v != NULL || (dflag & DIR_OLD))
25777298Sobrien	stderror(ERR_DIRUS, "dirs", flags, "");
25877298Sobrien    if ((dflag & (DIR_CLEAR|DIR_LOAD|DIR_SAVE)) == 0 || (dflag & DIR_PRINT))
25977298Sobrien	printdirs(dflag);
26077298Sobrien}
26177298Sobrien
26277298Sobrienstatic void
26377298Sobrienprintdirs(int dflag)
26477298Sobrien{
26577298Sobrien    struct directory *dp;
26677298Sobrien    Char   *s, *user;
26777298Sobrien    int     idx, len, cur;
26877298Sobrien
26977298Sobrien    dp = dcwd;
27077298Sobrien    idx = 0;
27177298Sobrien    cur = 0;
27277298Sobrien    do {
27377298Sobrien	if (dp == &dhead)
27477298Sobrien	    continue;
27577298Sobrien	if (dflag & DIR_VERT) {
27677298Sobrien	    xprintf("%d\t", idx++);
27777298Sobrien	    cur = 0;
27877298Sobrien	}
27977298Sobrien	s = dp->di_name;
28077298Sobrien	user = NULL;
28177298Sobrien	if (!(dflag & DIR_LONG) && (user = getusername(&s)) != NULL)
28277298Sobrien	    len = (int) (Strlen(user) + Strlen(s) + 2);
28377298Sobrien	else
28477298Sobrien	    len = (int) (Strlen(s) + 1);
28577298Sobrien
28677298Sobrien	cur += len;
28777298Sobrien	if ((dflag & DIR_LINE) && cur >= TermH - 1 && len < TermH) {
28877298Sobrien	    xputchar('\n');
28977298Sobrien	    cur = len;
29077298Sobrien	}
29177298Sobrien	if (user)
29277298Sobrien	    xprintf("~%S", user);
29377298Sobrien	xprintf("%-S%c", s, (dflag & DIR_VERT) ? '\n' : ' ');
29477298Sobrien    } while ((dp = dp->di_prev) != dcwd);
29577298Sobrien    if (!(dflag & DIR_VERT))
29677298Sobrien	xputchar('\n');
29777298Sobrien}
29877298Sobrien
29977298Sobrienvoid
30077298Sobriendtildepr(Char *dir)
30177298Sobrien{
30277298Sobrien    Char* user;
30377298Sobrien    if ((user = getusername(&dir)) != NULL)
30477298Sobrien	xprintf("~%-S%S", user, dir);
30577298Sobrien    else
30677298Sobrien	xprintf("%S", dir);
30777298Sobrien}
30877298Sobrien
30977298Sobrienvoid
31077298Sobriendtilde(void)
31177298Sobrien{
31277298Sobrien    struct directory *d = dcwd;
31377298Sobrien
31477298Sobrien    do {
31577298Sobrien	if (d == &dhead)
31677298Sobrien	    continue;
31777298Sobrien	d->di_name = dcanon(d->di_name, STRNULL);
31877298Sobrien    } while ((d = d->di_prev) != dcwd);
31977298Sobrien
32077298Sobrien    dset(dcwd->di_name);
32177298Sobrien}
32277298Sobrien
32377298Sobrien
32477298Sobrien/* dnormalize():
32577298Sobrien *	The path will be normalized if it
32677298Sobrien *	1) is "..",
32777298Sobrien *	2) or starts with "../",
32877298Sobrien *	3) or ends with "/..",
32977298Sobrien *	4) or contains the string "/../",
33077298Sobrien *	then it will be normalized, unless those strings are quoted.
33177298Sobrien *	Otherwise, a copy is made and sent back.
33277298Sobrien */
33377298SobrienChar   *
33477298Sobriendnormalize(const Char *cp, int expnd)
33577298Sobrien{
33677298Sobrien
33777298Sobrien/* return true if dp is of the form "../xxx" or "/../xxx" */
33877298Sobrien#define IS_DOTDOT(sp, p) (ISDOTDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
33977298Sobrien#define IS_DOT(sp, p) (ISDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
34077298Sobrien
34177298Sobrien#ifdef S_IFLNK
34277298Sobrien    if (expnd) {
34377298Sobrien	struct Strbuf buf = Strbuf_INIT;
34477298Sobrien 	int     dotdot = 0;
34577298Sobrien	Char   *dp, *cwd;
34677298Sobrien	const Char *start = cp;
34777298Sobrien# ifdef HAVE_SLASHSLASH
34877298Sobrien	int slashslash;
34977298Sobrien# endif /* HAVE_SLASHSLASH */
35077298Sobrien
35177298Sobrien	/*
35277298Sobrien	 * count the number of "../xxx" or "xxx/../xxx" in the path
35377298Sobrien	 */
35477298Sobrien	for ( ; *cp && *(cp + 1); cp++)
35577298Sobrien	    if (IS_DOTDOT(start, cp))
35677298Sobrien	        dotdot++;
35777298Sobrien
35877298Sobrien	/*
35977298Sobrien	 * if none, we are done.
36077298Sobrien	 */
36177298Sobrien        if (dotdot == 0)
36277298Sobrien	    return (Strsave(start));
36377298Sobrien
36477298Sobrien# ifdef notdef
36577298Sobrien	struct stat sb;
36677298Sobrien	/*
36777298Sobrien	 * We disable this test because:
36877298Sobrien	 * cd /tmp; mkdir dir1 dir2; cd dir2; ln -s /tmp/dir1; cd dir1;
36977298Sobrien	 * echo ../../dir1 does not expand. We had enabled this before
37077298Sobrien	 * because it was bothering people with expansions in compilation
37177298Sobrien	 * lines like -I../../foo. Maybe we need some kind of finer grain
37277298Sobrien	 * control?
37377298Sobrien	 *
37477298Sobrien	 * If the path doesn't exist, we are done too.
37577298Sobrien	 */
37677298Sobrien	if (lstat(short2str(start), &sb) != 0 && errno == ENOENT)
37777298Sobrien	    return (Strsave(start));
37877298Sobrien# endif
37977298Sobrien
38077298Sobrien	cwd = xmalloc((Strlen(dcwd->di_name) + 3) * sizeof(Char));
38177298Sobrien	(void) Strcpy(cwd, dcwd->di_name);
38277298Sobrien
38377298Sobrien	/*
38477298Sobrien	 * If the path starts with a slash, we are not relative to
38577298Sobrien	 * the current working directory.
38677298Sobrien	 */
38777298Sobrien	if (ABSOLUTEP(start))
38889857Sobrien	    *cwd = '\0';
38977298Sobrien# ifdef HAVE_SLASHSLASH
39077298Sobrien	slashslash = cwd[0] == '/' && cwd[1] == '/';
39177298Sobrien# endif /* HAVE_SLASHSLASH */
39277298Sobrien
39377298Sobrien	/*
39477298Sobrien	 * Ignore . and count ..'s
39577298Sobrien	 */
39677298Sobrien	cp = start;
39777298Sobrien	do {
39877298Sobrien	    dotdot = 0;
39977298Sobrien	    buf.len = 0;
40077298Sobrien	    while (*cp)
40177298Sobrien	        if (IS_DOT(start, cp)) {
40277298Sobrien	            if (*++cp)
40377298Sobrien	                cp++;
40477298Sobrien	        }
40577298Sobrien	        else if (IS_DOTDOT(start, cp)) {
40677298Sobrien		    if (buf.len != 0)
40777298Sobrien		        break; /* finish analyzing .././../xxx/[..] */
40877298Sobrien		    dotdot++;
40977298Sobrien		    cp += 2;
41077298Sobrien		    if (*cp)
41177298Sobrien		        cp++;
41277298Sobrien	        }
41377298Sobrien	        else
41477298Sobrien		    Strbuf_append1(&buf, *cp++);
41577298Sobrien
41677298Sobrien	    Strbuf_terminate(&buf);
41777298Sobrien	    while (dotdot > 0)
41877298Sobrien	        if ((dp = Strrchr(cwd, '/')) != NULL) {
41977298Sobrien# ifdef HAVE_SLASHSLASH
42077298Sobrien		    if (dp == &cwd[1])
42177298Sobrien		        slashslash = 1;
42277298Sobrien# endif /* HAVE_SLASHSLASH */
42377298Sobrien		        *dp = '\0';
42477298Sobrien		        dotdot--;
42577298Sobrien	        }
42677298Sobrien	        else
42777298Sobrien		    break;
42877298Sobrien
42977298Sobrien	    if (!*cwd) {	/* too many ..'s, starts with "/" */
43077298Sobrien	        cwd[0] = '/';
43177298Sobrien# ifdef HAVE_SLASHSLASH
43277298Sobrien		/*
43377298Sobrien		 * Only append another slash, if already the former cwd
43477298Sobrien		 * was in a double-slash path.
43577298Sobrien		 */
43677298Sobrien		cwd[1] = slashslash ? '/' : '\0';
43777298Sobrien		cwd[2] = '\0';
43877298Sobrien# else /* !HAVE_SLASHSLASH */
43977298Sobrien		cwd[1] = '\0';
44077298Sobrien# endif /* HAVE_SLASHSLASH */
44177298Sobrien	    }
44277298Sobrien# ifdef HAVE_SLASHSLASH
44377298Sobrien	    else if (slashslash && cwd[1] == '\0') {
44477298Sobrien		cwd[1] = '/';
44577298Sobrien		cwd[2] = '\0';
44677298Sobrien	    }
44777298Sobrien# endif /* HAVE_SLASHSLASH */
44877298Sobrien
44977298Sobrien	    if (buf.len != 0) {
45077298Sobrien		size_t i;
45177298Sobrien
45277298Sobrien		i = Strlen(cwd);
45377298Sobrien		if (TRM(cwd[i - 1]) != '/') {
45477298Sobrien		    cwd[i++] = '/';
45577298Sobrien		    cwd[i] = '\0';
45677298Sobrien		}
45777298Sobrien	        dp = Strspl(cwd, TRM(buf.s[0]) == '/' ? &buf.s[1] : buf.s);
45877298Sobrien	        xfree(cwd);
45977298Sobrien	        cwd = dp;
46077298Sobrien		i = Strlen(cwd) - 1;
46177298Sobrien	        if (TRM(cwd[i]) == '/')
46277298Sobrien		    cwd[i] = '\0';
46377298Sobrien	    }
46477298Sobrien	    /* Reduction of ".." following the stuff we collected in buf
46577298Sobrien	     * only makes sense if the directory item in buf really exists.
46677298Sobrien	     * Avoid reduction of "-I../.." (typical compiler call) to ""
46777298Sobrien	     * or "/usr/nonexistant/../bin" to "/usr/bin":
46877298Sobrien	     */
46977298Sobrien	    if (cwd[0]) {
47077298Sobrien	        struct stat exists;
47177298Sobrien		if (0 != stat(short2str(cwd), &exists)) {
47277298Sobrien		    xfree(buf.s);
47377298Sobrien		    xfree(cwd);
47477298Sobrien		    return Strsave(start);
47577298Sobrien		}
47677298Sobrien	    }
47777298Sobrien	} while (*cp != '\0');
47877298Sobrien	xfree(buf.s);
47977298Sobrien	return cwd;
48077298Sobrien    }
48177298Sobrien#endif /* S_IFLNK */
48277298Sobrien    return Strsave(cp);
48377298Sobrien}
48477298Sobrien
48577298Sobrien
48677298Sobrien/*
48777298Sobrien * dochngd - implement chdir command.
48877298Sobrien */
48977298Sobrien/*ARGSUSED*/
49077298Sobrienvoid
49177298Sobriendochngd(Char **v, struct command *c)
49277298Sobrien{
49377298Sobrien    Char *cp;
49477298Sobrien    struct directory *dp;
49577298Sobrien    int dflag = skipargs(&v, "plvn", "[-|<dir>]");
49677298Sobrien
49777298Sobrien    USE(c);
49877298Sobrien    printd = 0;
49977298Sobrien    cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
50077298Sobrien
50177298Sobrien    if (cp == NULL) {
50277298Sobrien	if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
50377298Sobrien	    stderror(ERR_NAME | ERR_NOHOMEDIR);
50477298Sobrien	if (chdir(short2str(cp)) < 0)
50577298Sobrien	    stderror(ERR_NAME | ERR_CANTCHANGE);
50677298Sobrien	cp = Strsave(cp);
50777298Sobrien    }
50877298Sobrien    else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
50977298Sobrien	stderror(ERR_NAME | ERR_TOOMANY);
51077298Sobrien	/* NOTREACHED */
51177298Sobrien	return;
51277298Sobrien    }
51377298Sobrien    else if ((dp = dfind(cp)) != 0) {
51477298Sobrien	char   *tmp;
51577298Sobrien
51677298Sobrien	printd = 1;
51777298Sobrien	if (chdir(tmp = short2str(dp->di_name)) < 0)
51877298Sobrien	    stderror(ERR_SYSTEM, tmp, strerror(errno));
51977298Sobrien	dcwd->di_prev->di_next = dcwd->di_next;
52077298Sobrien	dcwd->di_next->di_prev = dcwd->di_prev;
52177298Sobrien	dfree(dcwd);
52277298Sobrien	dnewcwd(dp, dflag);
52377298Sobrien	return;
52477298Sobrien    }
52577298Sobrien    else
52677298Sobrien	if ((cp = dfollow(cp)) == NULL)
52777298Sobrien	    return;
52877298Sobrien    dp = xcalloc(sizeof(struct directory), 1);
52977298Sobrien    dp->di_name = cp;
53077298Sobrien    dp->di_count = 0;
53177298Sobrien    dp->di_next = dcwd->di_next;
53277298Sobrien    dp->di_prev = dcwd->di_prev;
53377298Sobrien    dp->di_prev->di_next = dp;
53477298Sobrien    dp->di_next->di_prev = dp;
53577298Sobrien    dfree(dcwd);
53677298Sobrien    dnewcwd(dp, dflag);
53777298Sobrien}
53877298Sobrien
53977298Sobrienstatic Char *
54077298Sobriendgoto(Char *cp)
54177298Sobrien{
54277298Sobrien    Char *dp, *ret;
54377298Sobrien
54477298Sobrien    if (!ABSOLUTEP(cp))
54577298Sobrien    {
54677298Sobrien	Char *p, *q;
54777298Sobrien	size_t cwdlen;
54877298Sobrien
54977298Sobrien	cwdlen = Strlen(dcwd->di_name);
55077298Sobrien	if (cwdlen == 1)	/* root */
55177298Sobrien	    cwdlen = 0;
55277298Sobrien	dp = xmalloc((cwdlen + Strlen(cp) + 2) * sizeof(Char));
55377298Sobrien	for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
55477298Sobrien	    continue;
55577298Sobrien	if (cwdlen)
55677298Sobrien	    p[-1] = '/';
55777298Sobrien	else
55877298Sobrien	    p--;		/* don't add a / after root */
55977298Sobrien	Strcpy(p, cp);
56077298Sobrien	xfree(cp);
56177298Sobrien	cp = dp;
56277298Sobrien	dp += cwdlen;
56377298Sobrien    }
56477298Sobrien    else
56577298Sobrien	dp = cp;
56677298Sobrien
56777298Sobrien#if defined(WINNT_NATIVE)
56877298Sobrien    return agetcwd();
56977298Sobrien#elif defined(__CYGWIN__)
57077298Sobrien    if (ABSOLUTEP(cp) && cp[1] == ':') { /* Only DOS paths are treated that way */
57177298Sobrien	return agetcwd();
57277298Sobrien    } else {
57377298Sobrien	cleanup_push(cp, xfree);
57477298Sobrien    	ret = dcanon(cp, dp);
57577298Sobrien	cleanup_ignore(cp);
57677298Sobrien	cleanup_until(cp);
57777298Sobrien    }
57877298Sobrien#else /* !WINNT_NATIVE */
57977298Sobrien    cleanup_push(cp, xfree);
58077298Sobrien    ret = dcanon(cp, dp);
58177298Sobrien    cleanup_ignore(cp);
58277298Sobrien    cleanup_until(cp);
58377298Sobrien#endif /* WINNT_NATIVE */
58477298Sobrien    return ret;
58577298Sobrien}
58677298Sobrien
58777298Sobrien/*
58877298Sobrien * dfollow - change to arg directory; fall back on cdpath if not valid
58977298Sobrien */
59077298Sobrienstatic Char *
59177298Sobriendfollow(Char *cp)
59277298Sobrien{
59377298Sobrien    Char *dp;
59477298Sobrien    struct varent *c;
59577298Sobrien    int serrno;
59677298Sobrien
59777298Sobrien    cp = globone(cp, G_ERROR);
59877298Sobrien    cleanup_push(cp, xfree);
59977298Sobrien#ifdef apollo
60077298Sobrien    if (Strchr(cp, '`')) {
60177298Sobrien	char *dptr;
60277298Sobrien	if (chdir(dptr = short2str(cp)) < 0)
60377298Sobrien	    stderror(ERR_SYSTEM, dptr, strerror(errno));
60477298Sobrien	dp = agetcwd();
60577298Sobrien	cleanup_push(dp, xfree);
60677298Sobrien	if (dp != NULL) {
60777298Sobrien	    cleanup_until(cp);
60877298Sobrien	    return dgoto(dp);
60977298Sobrien	}
61077298Sobrien	else
61177298Sobrien	    stderror(ERR_SYSTEM, dptr, strerror(errno));
61277298Sobrien    }
61377298Sobrien#endif /* apollo */
61477298Sobrien
61577298Sobrien    /*
61677298Sobrien     * if we are ignoring symlinks, try to fix relatives now.
61777298Sobrien     * if we are expading symlinks, it should be done by now.
61877298Sobrien     */
61977298Sobrien    dp = dnormalize(cp, symlinks == SYM_IGNORE);
62077298Sobrien    if (chdir(short2str(dp)) >= 0) {
62177298Sobrien        cleanup_until(cp);
62277298Sobrien        return dgoto(dp);
62377298Sobrien    }
62477298Sobrien    else {
62577298Sobrien        xfree(dp);
62677298Sobrien        if (chdir(short2str(cp)) >= 0) {
62777298Sobrien	    cleanup_ignore(cp);
62877298Sobrien	    cleanup_until(cp);
62977298Sobrien	    return dgoto(cp);
63077298Sobrien	}
63177298Sobrien	else if (errno != ENOENT && errno != ENOTDIR) {
63277298Sobrien	    int err;
63377298Sobrien
63477298Sobrien	    err = errno;
63577298Sobrien	    stderror(ERR_SYSTEM, short2str(cp), strerror(err));
63677298Sobrien	}
63777298Sobrien	serrno = errno;
63877298Sobrien    }
63977298Sobrien
64077298Sobrien    if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
64177298Sobrien	&& (c = adrof(STRcdpath)) && c->vec != NULL) {
64277298Sobrien	struct Strbuf buf = Strbuf_INIT;
64377298Sobrien	Char  **cdp;
64477298Sobrien
64577298Sobrien	for (cdp = c->vec; *cdp; cdp++) {
64677298Sobrien	    buf.len = 0;
64777298Sobrien	    Strbuf_append(&buf, *cdp);
64877298Sobrien	    Strbuf_append1(&buf, '/');
64977298Sobrien	    Strbuf_append(&buf, cp);
65077298Sobrien	    Strbuf_terminate(&buf);
65177298Sobrien	    /*
65277298Sobrien	     * We always want to fix the directory here
65377298Sobrien	     * If we are normalizing symlinks
65477298Sobrien	     */
65577298Sobrien	    dp = dnormalize(buf.s, symlinks == SYM_IGNORE ||
65677298Sobrien				   symlinks == SYM_EXPAND);
65777298Sobrien	    if (chdir(short2str(dp)) >= 0) {
65877298Sobrien		printd = 1;
65977298Sobrien		xfree(buf.s);
66077298Sobrien		cleanup_until(cp);
66177298Sobrien		return dgoto(dp);
66277298Sobrien	    }
66377298Sobrien	    else if (chdir(short2str(cp)) >= 0) {
66477298Sobrien		printd = 1;
66577298Sobrien		xfree(dp);
66677298Sobrien		xfree(buf.s);
66777298Sobrien		cleanup_ignore(cp);
66877298Sobrien		cleanup_until(cp);
66977298Sobrien		return dgoto(cp);
67077298Sobrien	    }
67177298Sobrien	}
67277298Sobrien	xfree(buf.s);
67377298Sobrien    }
67477298Sobrien    dp = varval(cp);
67577298Sobrien    if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
67677298Sobrien	cleanup_until(cp);
67777298Sobrien	cp = Strsave(dp);
67877298Sobrien	printd = 1;
67977298Sobrien	return dgoto(cp);
68077298Sobrien    }
68177298Sobrien    /*
68277298Sobrien     * on login source of ~/.cshdirs, errors are eaten. the dir stack is all
68377298Sobrien     * directories we could get to.
68477298Sobrien     */
68577298Sobrien    if (!bequiet)
68677298Sobrien	stderror(ERR_SYSTEM, short2str(cp), strerror(serrno));
68777298Sobrien    cleanup_until(cp);
68877298Sobrien    return (NULL);
68977298Sobrien}
69077298Sobrien
69177298Sobrien
69277298Sobrien/*
69377298Sobrien * dopushd - push new directory onto directory stack.
69477298Sobrien *	with no arguments exchange top and second.
69577298Sobrien *	with numeric argument (+n) bring it to top.
69677298Sobrien */
69777298Sobrien/*ARGSUSED*/
69877298Sobrienvoid
69977298Sobriendopushd(Char **v, struct command *c)
70077298Sobrien{
70177298Sobrien    struct directory *dp;
70277298Sobrien    Char *cp;
70377298Sobrien    int dflag = skipargs(&v, "plvn", " [-|<dir>|+<n>]");
70477298Sobrien
70577298Sobrien    USE(c);
70677298Sobrien    printd = 1;
70777298Sobrien    cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
70877298Sobrien
70977298Sobrien    if (cp == NULL) {
71077298Sobrien	if (adrof(STRpushdtohome)) {
71177298Sobrien	    if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
71277298Sobrien		stderror(ERR_NAME | ERR_NOHOMEDIR);
71377298Sobrien	    if (chdir(short2str(cp)) < 0)
71477298Sobrien		stderror(ERR_NAME | ERR_CANTCHANGE);
71577298Sobrien	    if ((cp = dfollow(cp)) == NULL)
71677298Sobrien		return;
71777298Sobrien	    dp = xcalloc(sizeof(struct directory), 1);
71877298Sobrien	    dp->di_name = cp;
71977298Sobrien	    dp->di_count = 0;
72077298Sobrien	    dp->di_prev = dcwd;
72177298Sobrien	    dp->di_next = dcwd->di_next;
72277298Sobrien	    dcwd->di_next = dp;
72377298Sobrien	    dp->di_next->di_prev = dp;
72477298Sobrien	}
72577298Sobrien	else {
72677298Sobrien	    char   *tmp;
72777298Sobrien
72877298Sobrien	    if ((dp = dcwd->di_prev) == &dhead)
72977298Sobrien		dp = dhead.di_prev;
73077298Sobrien	    if (dp == dcwd)
73177298Sobrien		stderror(ERR_NAME | ERR_NODIR);
73277298Sobrien	    if (chdir(tmp = short2str(dp->di_name)) < 0)
73377298Sobrien		stderror(ERR_SYSTEM, tmp, strerror(errno));
73477298Sobrien	    dp->di_prev->di_next = dp->di_next;
73577298Sobrien	    dp->di_next->di_prev = dp->di_prev;
73677298Sobrien	    dp->di_next = dcwd->di_next;
73777298Sobrien	    dp->di_prev = dcwd;
73877298Sobrien	    dcwd->di_next->di_prev = dp;
73977298Sobrien	    dcwd->di_next = dp;
74077298Sobrien	}
74177298Sobrien    }
74277298Sobrien    else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
74377298Sobrien	stderror(ERR_NAME | ERR_TOOMANY);
74477298Sobrien	/* NOTREACHED */
74577298Sobrien	return;
74677298Sobrien    }
74777298Sobrien    else if ((dp = dfind(cp)) != NULL) {
74877298Sobrien	char   *tmp;
74977298Sobrien
75077298Sobrien	if (chdir(tmp = short2str(dp->di_name)) < 0)
75177298Sobrien	    stderror(ERR_SYSTEM, tmp, strerror(errno));
75277298Sobrien	/*
75377298Sobrien	 * kfk - 10 Feb 1984 - added new "extraction style" pushd +n
75477298Sobrien	 */
75577298Sobrien	if (adrof(STRdextract))
75677298Sobrien	    dextract(dp);
75777298Sobrien    }
75877298Sobrien    else {
75977298Sobrien	Char *ccp;
76077298Sobrien
76177298Sobrien	if ((ccp = dfollow(cp)) == NULL)
76277298Sobrien	    return;
76377298Sobrien	dp = xcalloc(sizeof(struct directory), 1);
76477298Sobrien	dp->di_name = ccp;
76577298Sobrien	dp->di_count = 0;
76677298Sobrien	dp->di_prev = dcwd;
76777298Sobrien	dp->di_next = dcwd->di_next;
76877298Sobrien	dcwd->di_next = dp;
76977298Sobrien	dp->di_next->di_prev = dp;
77077298Sobrien    }
77177298Sobrien    dnewcwd(dp, dflag);
77277298Sobrien}
77377298Sobrien
77477298Sobrien/*
77577298Sobrien * dfind - find a directory if specified by numeric (+n) argument
77677298Sobrien */
77777298Sobrienstatic struct directory *
77877298Sobriendfind(Char *cp)
77977298Sobrien{
78077298Sobrien    struct directory *dp;
78177298Sobrien    int i;
78277298Sobrien    Char *ep;
78377298Sobrien
78477298Sobrien    if (*cp++ != '+')
78577298Sobrien	return (0);
78677298Sobrien    for (ep = cp; Isdigit(*ep); ep++)
78777298Sobrien	continue;
78877298Sobrien    if (*ep)
78977298Sobrien	return (0);
79077298Sobrien    i = getn(cp);
79177298Sobrien    if (i <= 0)
79277298Sobrien	return (0);
79377298Sobrien    for (dp = dcwd; i != 0; i--) {
79477298Sobrien	if ((dp = dp->di_prev) == &dhead)
79577298Sobrien	    dp = dp->di_prev;
79677298Sobrien	if (dp == dcwd)
79777298Sobrien	    stderror(ERR_NAME | ERR_DEEP);
79877298Sobrien    }
79977298Sobrien    return (dp);
80077298Sobrien}
80177298Sobrien
80277298Sobrien/*
80377298Sobrien * dopopd - pop a directory out of the directory stack
80477298Sobrien *	with a numeric argument just discard it.
80577298Sobrien */
80677298Sobrien/*ARGSUSED*/
80777298Sobrienvoid
80877298Sobriendopopd(Char **v, struct command *c)
80977298Sobrien{
81077298Sobrien    Char *cp;
81177298Sobrien    struct directory *dp, *p = NULL;
81277298Sobrien    int dflag = skipargs(&v, "plvn", " [-|+<n>]");
81377298Sobrien
81477298Sobrien    USE(c);
81577298Sobrien    printd = 1;
81677298Sobrien    cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
81777298Sobrien
81877298Sobrien    if (cp == NULL)
81977298Sobrien	dp = dcwd;
82077298Sobrien    else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
82177298Sobrien	stderror(ERR_NAME | ERR_TOOMANY);
82277298Sobrien	/* NOTREACHED */
82377298Sobrien	return;
82477298Sobrien    }
82577298Sobrien    else if ((dp = dfind(cp)) == 0)
82677298Sobrien	stderror(ERR_NAME | ERR_BADDIR);
82777298Sobrien    if (dp->di_prev == &dhead && dp->di_next == &dhead)
82877298Sobrien	stderror(ERR_NAME | ERR_EMPTY);
82977298Sobrien    if (dp == dcwd) {
83077298Sobrien	char   *tmp;
83177298Sobrien
83277298Sobrien	if ((p = dp->di_prev) == &dhead)
83377298Sobrien	    p = dhead.di_prev;
83477298Sobrien	if (chdir(tmp = short2str(p->di_name)) < 0)
83577298Sobrien	    stderror(ERR_SYSTEM, tmp, strerror(errno));
83677298Sobrien    }
83777298Sobrien    dp->di_prev->di_next = dp->di_next;
83877298Sobrien    dp->di_next->di_prev = dp->di_prev;
83977298Sobrien    dfree(dp);
84077298Sobrien    if (dp == dcwd) {
84177298Sobrien        dnewcwd(p, dflag);
84277298Sobrien    }
84377298Sobrien    else {
84477298Sobrien	printdirs(dflag);
84577298Sobrien    }
84677298Sobrien}
84777298Sobrien
84877298Sobrien/*
84977298Sobrien * dfree - free the directory (or keep it if it still has ref count)
85077298Sobrien */
85177298Sobrienvoid
85277298Sobriendfree(struct directory *dp)
85377298Sobrien{
85477298Sobrien
85577298Sobrien    if (dp->di_count != 0) {
85677298Sobrien	dp->di_next = dp->di_prev = 0;
85777298Sobrien    }
85877298Sobrien    else {
85977298Sobrien	xfree(dp->di_name);
86077298Sobrien	xfree(dp);
86177298Sobrien    }
86277298Sobrien}
86377298Sobrien
86477298Sobrien/*
86577298Sobrien * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
86677298Sobrien *	we are of course assuming that the file system is standardly
86777298Sobrien *	constructed (always have ..'s, directories have links)
86877298Sobrien */
86977298SobrienChar   *
87077298Sobriendcanon(Char *cp, Char *p)
87177298Sobrien{
87277298Sobrien    Char *sp;
87377298Sobrien    Char *p1, *p2;	/* general purpose */
87477298Sobrien    int    slash;
87577298Sobrien#ifdef HAVE_SLASHSLASH
87677298Sobrien    int    slashslash;
87777298Sobrien#endif /* HAVE_SLASHSLASH */
87877298Sobrien    size_t  clen;
87977298Sobrien
88077298Sobrien#ifdef S_IFLNK			/* if we have symlinks */
88177298Sobrien    Char *mlink, *newcp;
88277298Sobrien    char *tlink;
88377298Sobrien    size_t cc;
88477298Sobrien#endif /* S_IFLNK */
88577298Sobrien
88677298Sobrien    clen = Strlen(cp);
88777298Sobrien
88877298Sobrien    /*
88977298Sobrien     * christos: if the path given does not start with a slash prepend cwd. If
89077298Sobrien     * cwd does not start with a slash or the result would be too long try to
89177298Sobrien     * correct it.
89277298Sobrien     */
89377298Sobrien    if (!ABSOLUTEP(cp)) {
89477298Sobrien	Char *tmpdir;
89577298Sobrien	size_t	len;
89677298Sobrien
89777298Sobrien	p1 = varval(STRcwd);
89877298Sobrien	if (p1 == STRNULL || !ABSOLUTEP(p1)) {
89977298Sobrien	    Char *new_cwd = agetcwd();
90077298Sobrien
90177298Sobrien	    if (new_cwd == NULL) {
90277298Sobrien		xprintf("%s: %s\n", progname, strerror(errno));
90377298Sobrien		setcopy(STRcwd, str2short("/"), VAR_READWRITE|VAR_NOGLOB);
90477298Sobrien	    }
90577298Sobrien	    else
90677298Sobrien		setv(STRcwd, new_cwd, VAR_READWRITE|VAR_NOGLOB);
90777298Sobrien	    p1 = varval(STRcwd);
90877298Sobrien	}
90977298Sobrien	len = Strlen(p1);
91077298Sobrien	tmpdir = xmalloc((len + clen + 2) * sizeof (*tmpdir));
91177298Sobrien	(void) Strcpy(tmpdir, p1);
91277298Sobrien	(void) Strcat(tmpdir, STRslash);
91377298Sobrien	(void) Strcat(tmpdir, cp);
91477298Sobrien	xfree(cp);
91577298Sobrien	cp = p = tmpdir;
91677298Sobrien    }
91777298Sobrien
91877298Sobrien#ifdef HAVE_SLASHSLASH
91977298Sobrien    slashslash = (cp[0] == '/' && cp[1] == '/');
92077298Sobrien#endif /* HAVE_SLASHSLASH */
92177298Sobrien
92277298Sobrien    while (*p) {		/* for each component */
92377298Sobrien	sp = p;			/* save slash address */
92477298Sobrien	while (*++p == '/')	/* flush extra slashes */
92577298Sobrien	    continue;
92677298Sobrien	if (p != ++sp)
92777298Sobrien	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
92877298Sobrien		continue;
92977298Sobrien	p = sp;			/* save start of component */
93077298Sobrien	slash = 0;
93177298Sobrien	if (*p)
93277298Sobrien	    while (*++p)	/* find next slash or end of path */
93377298Sobrien		if (*p == '/') {
93477298Sobrien		    slash = 1;
93577298Sobrien		    *p = 0;
93677298Sobrien		    break;
93777298Sobrien		}
93877298Sobrien
93977298Sobrien#ifdef HAVE_SLASHSLASH
94077298Sobrien	if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0')
94177298Sobrien	    slashslash = 1;
94277298Sobrien#endif /* HAVE_SLASHSLASH */
94377298Sobrien	if (*sp == '\0') {	/* if component is null */
94477298Sobrien	    if (--sp == cp)	/* if path is one char (i.e. /) */
94577298Sobrien		break;
94677298Sobrien	    else
94777298Sobrien		*sp = '\0';
94877298Sobrien	}
94977298Sobrien	else if (sp[0] == '.' && sp[1] == 0) {
95077298Sobrien	    if (slash) {
95177298Sobrien		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
95277298Sobrien		    continue;
95377298Sobrien		p = --sp;
95477298Sobrien	    }
95577298Sobrien	    else if (--sp != cp)
95677298Sobrien		*sp = '\0';
95777298Sobrien	    else
95877298Sobrien		sp[1] = '\0';
95977298Sobrien	}
96077298Sobrien	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
96177298Sobrien	    /*
96277298Sobrien	     * We have something like "yyy/xxx/..", where "yyy" can be null or
96377298Sobrien	     * a path starting at /, and "xxx" is a single component. Before
96477298Sobrien	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
96577298Sobrien	     * symbolic link.
96677298Sobrien	     */
96777298Sobrien	    *--sp = 0;		/* form the pathname for readlink */
96877298Sobrien#ifdef S_IFLNK			/* if we have symlinks */
96977298Sobrien	    if (sp != cp && /* symlinks != SYM_IGNORE && */
97077298Sobrien		(tlink = areadlink(short2str(cp))) != NULL) {
97177298Sobrien		mlink = str2short(tlink);
97277298Sobrien		xfree(tlink);
97377298Sobrien
97477298Sobrien		if (slash)
97577298Sobrien		    *p = '/';
97677298Sobrien		/*
97777298Sobrien		 * Point p to the '/' in "/..", and restore the '/'.
97877298Sobrien		 */
97977298Sobrien		*(p = sp) = '/';
98077298Sobrien		if (*mlink != '/') {
98177298Sobrien		    /*
98277298Sobrien		     * Relative path, expand it between the "yyy/" and the
98377298Sobrien		     * "/..". First, back sp up to the character past "yyy/".
98477298Sobrien		     */
98577298Sobrien		    while (*--sp != '/')
98677298Sobrien			continue;
98777298Sobrien		    sp++;
98877298Sobrien		    *sp = 0;
98977298Sobrien		    /*
99077298Sobrien		     * New length is "yyy/" + mlink + "/.." and rest
99177298Sobrien		     */
99277298Sobrien		    p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
99377298Sobrien					  Strlen(p) + 1) * sizeof(Char));
99477298Sobrien		    /*
99577298Sobrien		     * Copy new path into newcp
99677298Sobrien		     */
99777298Sobrien		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
99877298Sobrien			continue;
99977298Sobrien		    for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
100077298Sobrien			continue;
100177298Sobrien		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
100277298Sobrien			continue;
100377298Sobrien		    /*
100477298Sobrien		     * Restart canonicalization at expanded "/xxx".
100577298Sobrien		     */
100677298Sobrien		    p = sp - cp - 1 + newcp;
100777298Sobrien		}
100877298Sobrien		else {
100977298Sobrien		    newcp = Strspl(mlink, p);
101077298Sobrien		    /*
101177298Sobrien		     * Restart canonicalization at beginning
101277298Sobrien		     */
101377298Sobrien		    p = newcp;
101477298Sobrien		}
101577298Sobrien		xfree(cp);
101677298Sobrien		cp = newcp;
101777298Sobrien#ifdef HAVE_SLASHSLASH
101877298Sobrien                slashslash = (cp[0] == '/' && cp[1] == '/');
101977298Sobrien#endif /* HAVE_SLASHSLASH */
102077298Sobrien		continue;	/* canonicalize the link */
102177298Sobrien	    }
102277298Sobrien#endif /* S_IFLNK */
102377298Sobrien	    *sp = '/';
102477298Sobrien	    if (sp != cp)
102577298Sobrien		while (*--sp != '/')
102677298Sobrien		    continue;
102777298Sobrien	    if (slash) {
102877298Sobrien		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
102977298Sobrien		    continue;
103077298Sobrien		p = sp;
103177298Sobrien	    }
103277298Sobrien	    else if (cp == sp)
103377298Sobrien		*++sp = '\0';
103477298Sobrien	    else
103577298Sobrien		*sp = '\0';
103677298Sobrien	}
103777298Sobrien	else {			/* normal dir name (not . or .. or nothing) */
103877298Sobrien
103977298Sobrien#ifdef S_IFLNK			/* if we have symlinks */
104077298Sobrien	    if (sp != cp && symlinks == SYM_CHASE &&
104177298Sobrien		(tlink = areadlink(short2str(cp))) != NULL) {
104277298Sobrien		mlink = str2short(tlink);
104377298Sobrien		xfree(tlink);
104477298Sobrien
104577298Sobrien		/*
104677298Sobrien		 * restore the '/'.
104777298Sobrien		 */
104877298Sobrien		if (slash)
104977298Sobrien		    *p = '/';
105077298Sobrien
105177298Sobrien		/*
105277298Sobrien		 * point sp to p (rather than backing up).
105377298Sobrien		 */
105477298Sobrien		sp = p;
105577298Sobrien
105677298Sobrien		if (*mlink != '/') {
105777298Sobrien		    /*
105877298Sobrien		     * Relative path, expand it between the "yyy/" and the
105977298Sobrien		     * remainder. First, back sp up to the character past
106077298Sobrien		     * "yyy/".
106177298Sobrien		     */
106277298Sobrien		    while (*--sp != '/')
106377298Sobrien			continue;
106477298Sobrien		    sp++;
106577298Sobrien		    *sp = 0;
106677298Sobrien		    /*
106777298Sobrien		     * New length is "yyy/" + mlink + "/.." and rest
106877298Sobrien		     */
106977298Sobrien		    p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
107077298Sobrien					  Strlen(p) + 1) * sizeof(Char));
107177298Sobrien		    /*
107277298Sobrien		     * Copy new path into newcp
107377298Sobrien		     */
107477298Sobrien		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
107577298Sobrien			continue;
107677298Sobrien		    for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
107777298Sobrien			continue;
107877298Sobrien		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
107977298Sobrien			continue;
108077298Sobrien		    /*
108177298Sobrien		     * Restart canonicalization at expanded "/xxx".
108277298Sobrien		     */
108389857Sobrien		    p = sp - cp - 1 + newcp;
108477298Sobrien		}
108577298Sobrien		else {
108689857Sobrien		    newcp = Strspl(mlink, p);
108777298Sobrien		    /*
108877298Sobrien		     * Restart canonicalization at beginning
108977298Sobrien		     */
109077298Sobrien		    p = newcp;
109177298Sobrien		}
109277298Sobrien		xfree(cp);
109377298Sobrien		cp = newcp;
109477298Sobrien#ifdef HAVE_SLASHSLASH
109577298Sobrien                slashslash = (cp[0] == '/' && cp[1] == '/');
109677298Sobrien#endif /* HAVE_SLASHSLASH */
109777298Sobrien		continue;	/* canonicalize the mlink */
109877298Sobrien	    }
109977298Sobrien#endif /* S_IFLNK */
110077298Sobrien	    if (slash)
110177298Sobrien		*p = '/';
110277298Sobrien	}
110377298Sobrien    }
110477298Sobrien
110577298Sobrien    /*
110677298Sobrien     * fix home...
110777298Sobrien     */
110889857Sobrien#ifdef S_IFLNK
110977298Sobrien    p1 = varval(STRhome);
111077298Sobrien    cc = Strlen(p1);
111177298Sobrien    /*
111277298Sobrien     * See if we're not in a subdir of STRhome
111377298Sobrien     */
111477298Sobrien    if (p1 && *p1 == '/' && (Strncmp(p1, cp, cc) != 0 ||
111577298Sobrien	(cp[cc] != '/' && cp[cc] != '\0'))) {
111677298Sobrien	static ino_t home_ino = (ino_t) -1;
111777298Sobrien	static dev_t home_dev = (dev_t) -1;
111877298Sobrien	static Char *home_ptr = NULL;
111977298Sobrien	struct stat statbuf;
112077298Sobrien	int found;
112177298Sobrien	Char *copy;
112277298Sobrien
112377298Sobrien	/*
112477298Sobrien	 * Get dev and ino of STRhome
112577298Sobrien	 */
112677298Sobrien	if (home_ptr != p1 &&
112777298Sobrien	    stat(short2str(p1), &statbuf) != -1) {
112877298Sobrien	    home_dev = statbuf.st_dev;
112977298Sobrien	    home_ino = statbuf.st_ino;
113077298Sobrien	    home_ptr = p1;
113177298Sobrien	}
113277298Sobrien	/*
113377298Sobrien	 * Start comparing dev & ino backwards
113477298Sobrien	 */
113577298Sobrien	p2 = copy = Strsave(cp);
113677298Sobrien	found = 0;
113777298Sobrien	while (*p2 && stat(short2str(p2), &statbuf) != -1) {
113877298Sobrien	    if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) &&
113977298Sobrien			statbuf.st_ino == home_ino) {
114077298Sobrien			found = 1;
114177298Sobrien			break;
114277298Sobrien	    }
114377298Sobrien	    if ((sp = Strrchr(p2, '/')) != NULL)
114477298Sobrien		*sp = '\0';
114577298Sobrien	}
114677298Sobrien	/*
114777298Sobrien	 * See if we found it
114877298Sobrien	 */
114977298Sobrien	if (*p2 && found) {
115077298Sobrien	    /*
115177298Sobrien	     * Use STRhome to make '~' work
115277298Sobrien	     */
115377298Sobrien	    newcp = Strspl(p1, cp + Strlen(p2));
115477298Sobrien	    xfree(cp);
115577298Sobrien	    cp = newcp;
115677298Sobrien	}
115777298Sobrien	xfree(copy);
115877298Sobrien    }
115977298Sobrien#endif /* S_IFLNK */
116077298Sobrien
116177298Sobrien#ifdef HAVE_SLASHSLASH
116277298Sobrien    if (slashslash) {
116377298Sobrien	if (cp[1] != '/') {
116477298Sobrien	    p = xmalloc((Strlen(cp) + 2) * sizeof(Char));
116577298Sobrien	    *p = '/';
116677298Sobrien	    (void) Strcpy(&p[1], cp);
116777298Sobrien	    xfree(cp);
116877298Sobrien	    cp = p;
116977298Sobrien	}
117077298Sobrien    }
117177298Sobrien    if (cp[1] == '/' && cp[2] == '/') {
117277298Sobrien	for (p1 = &cp[1], p2 = &cp[2]; (*p1++ = *p2++) != '\0';)
117377298Sobrien	    continue;
117477298Sobrien    }
117577298Sobrien#endif /* HAVE_SLASHSLASH */
117677298Sobrien    return cp;
117777298Sobrien}
117877298Sobrien
117977298Sobrien
118077298Sobrien/*
118177298Sobrien * dnewcwd - make a new directory in the loop the current one
118277298Sobrien */
118377298Sobrienstatic void
118477298Sobriendnewcwd(struct directory *dp, int dflag)
118577298Sobrien{
118677298Sobrien    int print;
118777298Sobrien
118877298Sobrien    if (adrof(STRdunique)) {
118977298Sobrien	struct directory *dn;
119077298Sobrien
119177298Sobrien	for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev)
119277298Sobrien	    if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) {
119377298Sobrien		dn->di_next->di_prev = dn->di_prev;
119477298Sobrien		dn->di_prev->di_next = dn->di_next;
119577298Sobrien		dfree(dn);
119677298Sobrien		break;
119777298Sobrien	    }
119877298Sobrien    }
119977298Sobrien    dcwd = dp;
120077298Sobrien    dset(dcwd->di_name);
120177298Sobrien    dgetstack();
120277298Sobrien    print = printd;		/* if printd is set, print dirstack... */
120377298Sobrien    if (adrof(STRpushdsilent))	/* but pushdsilent overrides printd... */
120477298Sobrien	print = 0;
120577298Sobrien    if (dflag & DIR_PRINT)	/* but DIR_PRINT overrides pushdsilent... */
120677298Sobrien	print = 1;
120777298Sobrien    if (bequiet)		/* and bequiet overrides everything */
120877298Sobrien	print = 0;
120977298Sobrien    if (print)
121077298Sobrien	printdirs(dflag);
121177298Sobrien    cwd_cmd();			/* PWP: run the defined cwd command */
121277298Sobrien}
121377298Sobrien
121477298Sobrienvoid
121577298Sobriendsetstack(void)
121677298Sobrien{
121777298Sobrien    Char **cp;
121877298Sobrien    struct varent *vp;
121977298Sobrien    struct directory *dn, *dp;
122077298Sobrien
122177298Sobrien    if ((vp = adrof(STRdirstack)) == NULL || vp->vec == NULL)
122277298Sobrien	return;
122377298Sobrien
122477298Sobrien    /* Free the whole stack */
122577298Sobrien    while ((dn = dhead.di_prev) != &dhead) {
122677298Sobrien	dn->di_next->di_prev = dn->di_prev;
122777298Sobrien	dn->di_prev->di_next = dn->di_next;
122877298Sobrien	if (dn != dcwd)
122977298Sobrien	    dfree(dn);
123077298Sobrien    }
123177298Sobrien
123277298Sobrien    /* thread the current working directory */
123377298Sobrien    dhead.di_prev = dhead.di_next = dcwd;
123477298Sobrien    dcwd->di_next = dcwd->di_prev = &dhead;
123577298Sobrien
123677298Sobrien    /* put back the stack */
123777298Sobrien    for (cp = vp->vec; cp && *cp && **cp; cp++) {
123877298Sobrien	dp = xcalloc(sizeof(struct directory), 1);
123977298Sobrien	dp->di_name = Strsave(*cp);
124077298Sobrien	dp->di_count = 0;
124177298Sobrien	dp->di_prev = dcwd;
124277298Sobrien	dp->di_next = dcwd->di_next;
124377298Sobrien	dcwd->di_next = dp;
124477298Sobrien	dp->di_next->di_prev = dp;
124577298Sobrien    }
124677298Sobrien    dgetstack();	/* Make $dirstack reflect the current state */
124777298Sobrien}
124877298Sobrien
124977298Sobrienstatic void
125077298Sobriendgetstack(void)
125177298Sobrien{
125277298Sobrien    int i = 0;
125377298Sobrien    Char **dblk, **dbp;
125477298Sobrien    struct directory *dn;
125577298Sobrien
125677298Sobrien    if (adrof(STRdirstack) == NULL)
125777298Sobrien    	return;
125877298Sobrien
125977298Sobrien    for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++)
126077298Sobrien	continue;
126177298Sobrien    dbp = dblk = xmalloc((i + 1) * sizeof(Char *));
126277298Sobrien    for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++)
126377298Sobrien	 *dbp = Strsave(dn->di_name);
126477298Sobrien    *dbp = NULL;
126577298Sobrien    cleanup_push(dblk, blk_cleanup);
126677298Sobrien    setq(STRdirstack, dblk, &shvhed, VAR_READWRITE);
126777298Sobrien    cleanup_ignore(dblk);
126877298Sobrien    cleanup_until(dblk);
126977298Sobrien}
127077298Sobrien
127177298Sobrien/*
127277298Sobrien * getstakd - added by kfk 17 Jan 1984
127377298Sobrien * Support routine for the stack hack.  Finds nth directory in
127477298Sobrien * the directory stack, or finds last directory in stack.
127577298Sobrien */
127677298Sobrienconst Char *
127777298Sobriengetstakd(int cnt)
127877298Sobrien{
127977298Sobrien    struct directory *dp;
128077298Sobrien
128177298Sobrien    dp = dcwd;
128277298Sobrien    if (cnt < 0) {		/* < 0 ==> last dir requested. */
128377298Sobrien	dp = dp->di_next;
128477298Sobrien	if (dp == &dhead)
128577298Sobrien	    dp = dp->di_next;
128677298Sobrien    }
128777298Sobrien    else {
128877298Sobrien	while (cnt-- > 0) {
128977298Sobrien	    dp = dp->di_prev;
129077298Sobrien	    if (dp == &dhead)
129177298Sobrien		dp = dp->di_prev;
129277298Sobrien	    if (dp == dcwd)
129377298Sobrien		return NULL;
129477298Sobrien	}
129577298Sobrien    }
129677298Sobrien    return dp->di_name;
129777298Sobrien}
129877298Sobrien
129977298Sobrien/*
130077298Sobrien * Karl Kleinpaste - 10 Feb 1984
130177298Sobrien * Added dextract(), which is used in pushd +n.
130277298Sobrien * Instead of just rotating the entire stack around, dextract()
130377298Sobrien * lets the user have the nth dir extracted from its current
130477298Sobrien * position, and pushes it onto the top.
130577298Sobrien */
130677298Sobrienstatic void
130777298Sobriendextract(struct directory *dp)
130877298Sobrien{
130977298Sobrien    if (dp == dcwd)
131077298Sobrien	return;
131177298Sobrien    dp->di_next->di_prev = dp->di_prev;
131277298Sobrien    dp->di_prev->di_next = dp->di_next;
131377298Sobrien    dp->di_next = dcwd->di_next;
131477298Sobrien    dp->di_prev = dcwd;
131577298Sobrien    dp->di_next->di_prev = dp;
131677298Sobrien    dcwd->di_next = dp;
131777298Sobrien}
131877298Sobrien
131977298Sobrienstatic void
132077298Sobrienbequiet_cleanup(void *dummy)
132177298Sobrien{
132277298Sobrien    USE(dummy);
132377298Sobrien    bequiet = 0;
132477298Sobrien}
132577298Sobrien
132677298Sobrienvoid
132777298Sobrienloaddirs(Char *fname)
132877298Sobrien{
132977298Sobrien    static Char *loaddirs_cmd[] = { STRsource, NULL, NULL };
133077298Sobrien
133177298Sobrien    bequiet = 1;
133277298Sobrien    cleanup_push(&bequiet, bequiet_cleanup);
133377298Sobrien    if (fname)
133477298Sobrien	loaddirs_cmd[1] = fname;
133577298Sobrien    else if ((fname = varval(STRdirsfile)) != STRNULL)
133677298Sobrien	loaddirs_cmd[1] = fname;
133777298Sobrien    else
133877298Sobrien	loaddirs_cmd[1] = STRtildotdirs;
133977298Sobrien    dosource(loaddirs_cmd, NULL);
134077298Sobrien    cleanup_until(&bequiet);
134177298Sobrien}
134277298Sobrien
134377298Sobrien/*
134477298Sobrien * create a file called ~/.cshdirs which has a sequence
134577298Sobrien * of pushd commands which will restore the dir stack to
134677298Sobrien * its state before exit/logout. remember that the order
134777298Sobrien * is reversed in the file because we are pushing.
134877298Sobrien * -strike
134977298Sobrien */
135077298Sobrienvoid
135177298Sobrienrecdirs(Char *fname, int def)
135277298Sobrien{
135377298Sobrien    int     fp, ftmp, oldidfds;
135477298Sobrien    int     cdflag = 0;
135577298Sobrien    struct directory *dp;
135677298Sobrien    unsigned int    num;
135777298Sobrien    Char   *snum;
135877298Sobrien    struct Strbuf qname = Strbuf_INIT;
135977298Sobrien
136077298Sobrien    if (fname == NULL && !def)
136177298Sobrien	return;
136277298Sobrien
136377298Sobrien    if (fname == NULL) {
136477298Sobrien	if ((fname = varval(STRdirsfile)) == STRNULL)
136577298Sobrien	    fname = Strspl(varval(STRhome), &STRtildotdirs[1]);
136677298Sobrien	else
136777298Sobrien	    fname = Strsave(fname);
136877298Sobrien    }
136977298Sobrien    else
137077298Sobrien	fname = globone(fname, G_ERROR);
137177298Sobrien    cleanup_push(fname, xfree);
137277298Sobrien
137377298Sobrien    if ((fp = xcreat(short2str(fname), 0600)) == -1) {
137477298Sobrien	cleanup_until(fname);
137577298Sobrien	return;
137677298Sobrien    }
137777298Sobrien
137877298Sobrien    if ((snum = varval(STRsavedirs)) == STRNULL || snum[0] == '\0')
137977298Sobrien	num = (unsigned int) ~0;
138077298Sobrien    else
138177298Sobrien	num = (unsigned int) atoi(short2str(snum));
138277298Sobrien
138377298Sobrien    oldidfds = didfds;
138477298Sobrien    didfds = 0;
138577298Sobrien    ftmp = SHOUT;
138677298Sobrien    SHOUT = fp;
138777298Sobrien
138877298Sobrien    cleanup_push(&qname, Strbuf_cleanup);
138977298Sobrien    dp = dcwd->di_next;
139077298Sobrien    do {
139177298Sobrien	if (dp == &dhead)
139277298Sobrien	    continue;
139377298Sobrien
139477298Sobrien	if (cdflag == 0) {
139577298Sobrien	    cdflag = 1;
139677298Sobrien	    xprintf("cd %S\n", quote_meta(&qname, dp->di_name));
139777298Sobrien	}
139877298Sobrien	else
139977298Sobrien	    xprintf("pushd %S\n", quote_meta(&qname, dp->di_name));
140077298Sobrien
140177298Sobrien	if (num-- == 0)
140277298Sobrien	    break;
140377298Sobrien
140477298Sobrien    } while ((dp = dp->di_next) != dcwd->di_next);
140577298Sobrien
140677298Sobrien    xclose(fp);
140777298Sobrien    SHOUT = ftmp;
140877298Sobrien    didfds = oldidfds;
140977298Sobrien    cleanup_until(fname);
141077298Sobrien}
141177298Sobrien