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