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