sh.dir.c revision 100616
1/* $Header: /src/pub/tcsh/sh.dir.c,v 3.60 2002/07/08 21:03:04 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.60 2002/07/08 21:03:04 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("%S%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("~%S%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#ifdef WINNT_NATIVE
533    cp = SAVE(getcwd(NULL, 0));
534#else /* !WINNT_NATIVE */
535    cp = dcanon(cp, dp);
536#endif /* WINNT_NATIVE */
537    return cp;
538}
539
540/*
541 * dfollow - change to arg directory; fall back on cdpath if not valid
542 */
543static Char *
544dfollow(cp)
545    register Char *cp;
546{
547    register Char *dp;
548    struct varent *c;
549    char    ebuf[MAXPATHLEN];
550    int serrno;
551
552    cp = globone(cp, G_ERROR);
553#ifdef apollo
554    if (Strchr(cp, '`')) {
555	char *dptr, *ptr;
556	if (chdir(dptr = short2str(cp)) < 0)
557	    stderror(ERR_SYSTEM, dptr, strerror(errno));
558	else if ((ptr = getcwd(ebuf, sizeof(ebuf))) && *ptr != '\0') {
559		xfree((ptr_t) cp);
560		cp = Strsave(str2short(ptr));
561		return dgoto(cp);
562	}
563	else
564	    stderror(ERR_SYSTEM, dptr, ebuf);
565    }
566#endif /* apollo */
567
568    (void) strncpy(ebuf, short2str(cp), MAXPATHLEN);
569    ebuf[MAXPATHLEN-1] = '\0';
570    /*
571     * if we are ignoring symlinks, try to fix relatives now.
572     * if we are expading symlinks, it should be done by now.
573     */
574    dp = dnormalize(cp, symlinks == SYM_IGNORE);
575    if (chdir(short2str(dp)) >= 0) {
576        xfree((ptr_t) cp);
577        return dgoto(dp);
578    }
579    else {
580        xfree((ptr_t) dp);
581        if (chdir(short2str(cp)) >= 0)
582	    return dgoto(cp);
583	else if (errno != ENOENT && errno != ENOTDIR)
584	    stderror(ERR_SYSTEM, ebuf, strerror(errno));
585	serrno = errno;
586    }
587
588    if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
589	&& (c = adrof(STRcdpath)) && c->vec != NULL) {
590	Char  **cdp;
591	register Char *p;
592	Char    buf[MAXPATHLEN];
593
594	for (cdp = c->vec; *cdp; cdp++) {
595	    for (dp = buf, p = *cdp; (*dp++ = *p++) != '\0';)
596		continue;
597	    dp[-1] = '/';
598	    for (p = cp; (*dp++ = *p++) != '\0';)
599		continue;
600	    /*
601	     * We always want to fix the directory here
602	     * If we are normalizing symlinks
603	     */
604	    dp = dnormalize(buf, symlinks == SYM_IGNORE ||
605				 symlinks == SYM_EXPAND);
606	    if (chdir(short2str(dp)) >= 0) {
607		printd = 1;
608		xfree((ptr_t) cp);
609		return dgoto(dp);
610	    }
611	    else if (chdir(short2str(cp)) >= 0) {
612		printd = 1;
613		xfree((ptr_t) dp);
614		return dgoto(cp);
615	    }
616	}
617    }
618    dp = varval(cp);
619    if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
620	xfree((ptr_t) cp);
621	cp = Strsave(dp);
622	printd = 1;
623	return dgoto(cp);
624    }
625    xfree((ptr_t) cp);
626    /*
627     * on login source of ~/.cshdirs, errors are eaten. the dir stack is all
628     * directories we could get to.
629     */
630    if (!bequiet) {
631	stderror(ERR_SYSTEM, ebuf, strerror(serrno));
632	return (NULL);
633    }
634    else
635	return (NULL);
636}
637
638
639/*
640 * dopushd - push new directory onto directory stack.
641 *	with no arguments exchange top and second.
642 *	with numeric argument (+n) bring it to top.
643 */
644/*ARGSUSED*/
645void
646dopushd(v, c)
647    Char  **v;
648    struct command *c;
649{
650    register struct directory *dp;
651    register Char *cp;
652    int dflag = skipargs(&v, "plvn", " [-|<dir>|+<n>]");
653
654    USE(c);
655    printd = 1;
656    cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
657
658    if (cp == NULL) {
659	if (adrof(STRpushdtohome)) {
660	    if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
661		stderror(ERR_NAME | ERR_NOHOMEDIR);
662	    if (chdir(short2str(cp)) < 0)
663		stderror(ERR_NAME | ERR_CANTCHANGE);
664	    cp = Strsave(cp);	/* hmmm... PWP */
665	    if ((cp = dfollow(cp)) == NULL)
666		return;
667	    dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
668	    dp->di_name = cp;
669	    dp->di_count = 0;
670	    dp->di_prev = dcwd;
671	    dp->di_next = dcwd->di_next;
672	    dcwd->di_next = dp;
673	    dp->di_next->di_prev = dp;
674	}
675	else {
676	    char   *tmp;
677
678	    if ((dp = dcwd->di_prev) == &dhead)
679		dp = dhead.di_prev;
680	    if (dp == dcwd)
681		stderror(ERR_NAME | ERR_NODIR);
682	    if (chdir(tmp = short2str(dp->di_name)) < 0)
683		stderror(ERR_SYSTEM, tmp, strerror(errno));
684	    dp->di_prev->di_next = dp->di_next;
685	    dp->di_next->di_prev = dp->di_prev;
686	    dp->di_next = dcwd->di_next;
687	    dp->di_prev = dcwd;
688	    dcwd->di_next->di_prev = dp;
689	    dcwd->di_next = dp;
690	}
691    }
692    else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
693	stderror(ERR_NAME | ERR_TOOMANY);
694	/* NOTREACHED */
695	return;
696    }
697    else if ((dp = dfind(cp)) != NULL) {
698	char   *tmp;
699
700	if (chdir(tmp = short2str(dp->di_name)) < 0)
701	    stderror(ERR_SYSTEM, tmp, strerror(errno));
702	/*
703	 * kfk - 10 Feb 1984 - added new "extraction style" pushd +n
704	 */
705	if (adrof(STRdextract))
706	    dextract(dp);
707    }
708    else {
709	register Char *ccp;
710
711	if ((ccp = dfollow(cp)) == NULL)
712	    return;
713	dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
714	dp->di_name = ccp;
715	dp->di_count = 0;
716	dp->di_prev = dcwd;
717	dp->di_next = dcwd->di_next;
718	dcwd->di_next = dp;
719	dp->di_next->di_prev = dp;
720    }
721    dnewcwd(dp, dflag);
722}
723
724/*
725 * dfind - find a directory if specified by numeric (+n) argument
726 */
727static struct directory *
728dfind(cp)
729    register Char *cp;
730{
731    register struct directory *dp;
732    register int i;
733    register Char *ep;
734
735    if (*cp++ != '+')
736	return (0);
737    for (ep = cp; Isdigit(*ep); ep++)
738	continue;
739    if (*ep)
740	return (0);
741    i = getn(cp);
742    if (i <= 0)
743	return (0);
744    for (dp = dcwd; i != 0; i--) {
745	if ((dp = dp->di_prev) == &dhead)
746	    dp = dp->di_prev;
747	if (dp == dcwd)
748	    stderror(ERR_NAME | ERR_DEEP);
749    }
750    return (dp);
751}
752
753/*
754 * dopopd - pop a directory out of the directory stack
755 *	with a numeric argument just discard it.
756 */
757/*ARGSUSED*/
758void
759dopopd(v, c)
760    Char  **v;
761    struct command *c;
762{
763    Char *cp;
764    register struct directory *dp, *p = NULL;
765    int dflag = skipargs(&v, "plvn", " [-|+<n>]");
766
767    USE(c);
768    printd = 1;
769    cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
770
771    if (cp == NULL)
772	dp = dcwd;
773    else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
774	stderror(ERR_NAME | ERR_TOOMANY);
775	/* NOTREACHED */
776	return;
777    }
778    else if ((dp = dfind(cp)) == 0)
779	stderror(ERR_NAME | ERR_BADDIR);
780    if (dp->di_prev == &dhead && dp->di_next == &dhead)
781	stderror(ERR_NAME | ERR_EMPTY);
782    if (dp == dcwd) {
783	char   *tmp;
784
785	if ((p = dp->di_prev) == &dhead)
786	    p = dhead.di_prev;
787	if (chdir(tmp = short2str(p->di_name)) < 0)
788	    stderror(ERR_SYSTEM, tmp, strerror(errno));
789    }
790    dp->di_prev->di_next = dp->di_next;
791    dp->di_next->di_prev = dp->di_prev;
792    if (dp == dcwd) {
793	dnewcwd(p, dflag);
794    }
795    else {
796	printdirs(dflag);
797    }
798    dfree(dp);
799}
800
801/*
802 * dfree - free the directory (or keep it if it still has ref count)
803 */
804void
805dfree(dp)
806    register struct directory *dp;
807{
808
809    if (dp->di_count != 0) {
810	dp->di_next = dp->di_prev = 0;
811    }
812    else {
813	xfree((ptr_t) dp->di_name);
814	xfree((ptr_t) dp);
815    }
816}
817
818/*
819 * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
820 *	we are of course assuming that the file system is standardly
821 *	constructed (always have ..'s, directories have links)
822 */
823Char   *
824dcanon(cp, p)
825    register Char *cp, *p;
826{
827    register Char *sp;
828    register Char *p1, *p2;	/* general purpose */
829    bool    slash;
830#ifdef apollo
831    bool    slashslash;
832#endif /* apollo */
833    size_t  clen;
834
835#ifdef S_IFLNK			/* if we have symlinks */
836    Char    link[MAXPATHLEN];
837    char    tlink[MAXPATHLEN];
838    int     cc;
839    Char   *newcp;
840#endif /* S_IFLNK */
841
842    /*
843     * if the path given is too long truncate it!
844     */
845    if ((clen = Strlen(cp)) >= MAXPATHLEN)
846	cp[clen = MAXPATHLEN - 1] = '\0';
847
848    /*
849     * christos: if the path given does not start with a slash prepend cwd. If
850     * cwd does not start with a slash or the result would be too long try to
851     * correct it.
852     */
853    if (!ABSOLUTEP(cp)) {
854	Char    tmpdir[MAXPATHLEN];
855	size_t	len;
856
857	p1 = varval(STRcwd);
858	if (p1 == STRNULL || !ABSOLUTEP(p1)) {
859	    char *tmp = (char *)getcwd((char *)tmpdir, sizeof(tmpdir));
860	    if (tmp == NULL || *tmp == '\0') {
861		xprintf("%s: %s\n", progname, strerror(errno));
862		set(STRcwd, SAVE("/"), VAR_READWRITE|VAR_NOGLOB);
863	    } else {
864		set(STRcwd, SAVE(tmp), VAR_READWRITE|VAR_NOGLOB);
865	    }
866	    p1 = varval(STRcwd);
867	}
868	len = Strlen(p1);
869	if (len + clen + 1 >= MAXPATHLEN)
870	    cp[MAXPATHLEN - (len + 1)] = '\0';
871	(void) Strcpy(tmpdir, p1);
872	(void) Strcat(tmpdir, STRslash);
873	(void) Strcat(tmpdir, cp);
874	xfree((ptr_t) cp);
875	cp = p = Strsave(tmpdir);
876    }
877
878#ifdef apollo
879    slashslash = (cp[0] == '/' && cp[1] == '/');
880#endif /* apollo */
881
882    while (*p) {		/* for each component */
883	sp = p;			/* save slash address */
884	while (*++p == '/')	/* flush extra slashes */
885	    continue;
886	if (p != ++sp)
887	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
888		continue;
889	p = sp;			/* save start of component */
890	slash = 0;
891	if (*p)
892	    while (*++p)	/* find next slash or end of path */
893		if (*p == '/') {
894		    slash = 1;
895		    *p = 0;
896		    break;
897		}
898
899#ifdef apollo
900	if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0')
901	    slashslash = 1;
902#endif /* apollo */
903	if (*sp == '\0') {	/* if component is null */
904	    if (--sp == cp)	/* if path is one char (i.e. /) */
905		break;
906	    else
907		*sp = '\0';
908	}
909	else if (sp[0] == '.' && sp[1] == 0) {
910	    if (slash) {
911		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
912		    continue;
913		p = --sp;
914	    }
915	    else if (--sp != cp)
916		*sp = '\0';
917	    else
918		sp[1] = '\0';
919	}
920	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
921	    /*
922	     * We have something like "yyy/xxx/..", where "yyy" can be null or
923	     * a path starting at /, and "xxx" is a single component. Before
924	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
925	     * symbolic link.
926	     */
927	    *--sp = 0;		/* form the pathname for readlink */
928#ifdef S_IFLNK			/* if we have symlinks */
929	    if (sp != cp && /* symlinks != SYM_IGNORE && */
930		(cc = readlink(short2str(cp), tlink,
931			       sizeof tlink)) >= 0) {
932		tlink[cc] = '\0';
933		(void) Strncpy(link, str2short(tlink),
934		    sizeof(link) / sizeof(Char));
935		link[sizeof(link) / sizeof(Char) - 1] = '\0';
936
937		if (slash)
938		    *p = '/';
939		/*
940		 * Point p to the '/' in "/..", and restore the '/'.
941		 */
942		*(p = sp) = '/';
943		/*
944		 * find length of p
945		 */
946		for (p1 = p; *p1++;)
947		    continue;
948		if (*link != '/') {
949		    /*
950		     * Relative path, expand it between the "yyy/" and the
951		     * "/..". First, back sp up to the character past "yyy/".
952		     */
953		    while (*--sp != '/')
954			continue;
955		    sp++;
956		    *sp = 0;
957		    /*
958		     * New length is "yyy/" + link + "/.." and rest
959		     */
960		    p1 = newcp = (Char *) xmalloc((size_t)
961						(((sp - cp) + cc + (p1 - p)) *
962						 sizeof(Char)));
963		    /*
964		     * Copy new path into newcp
965		     */
966		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
967			continue;
968		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
969			continue;
970		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
971			continue;
972		    /*
973		     * Restart canonicalization at expanded "/xxx".
974		     */
975		    p = sp - cp - 1 + newcp;
976		}
977		else {
978		    /*
979		     * New length is link + "/.." and rest
980		     */
981		    p1 = newcp = (Char *) xmalloc((size_t)
982					    ((cc + (p1 - p)) * sizeof(Char)));
983		    /*
984		     * Copy new path into newcp
985		     */
986		    for (p2 = link; (*p1++ = *p2++) != '\0';)
987			continue;
988		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
989			continue;
990		    /*
991		     * Restart canonicalization at beginning
992		     */
993		    p = newcp;
994		}
995		xfree((ptr_t) cp);
996		cp = newcp;
997#ifdef apollo
998                slashslash = (cp[0] == '/' && cp[1] == '/');
999#endif /* apollo */
1000		continue;	/* canonicalize the link */
1001	    }
1002#endif /* S_IFLNK */
1003	    *sp = '/';
1004	    if (sp != cp)
1005		while (*--sp != '/')
1006		    continue;
1007	    if (slash) {
1008		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
1009		    continue;
1010		p = sp;
1011	    }
1012	    else if (cp == sp)
1013		*++sp = '\0';
1014	    else
1015		*sp = '\0';
1016	}
1017	else {			/* normal dir name (not . or .. or nothing) */
1018
1019#ifdef S_IFLNK			/* if we have symlinks */
1020	    if (sp != cp && symlinks == SYM_CHASE &&
1021		(cc = readlink(short2str(cp), tlink,
1022			       sizeof tlink)) >= 0) {
1023		tlink[cc] = '\0';
1024		(void) Strncpy(link, str2short(tlink),
1025		    sizeof(link) / sizeof(Char));
1026		link[sizeof(link) / sizeof(Char) - 1] = '\0';
1027
1028		/*
1029		 * restore the '/'.
1030		 */
1031		if (slash)
1032		    *p = '/';
1033
1034		/*
1035		 * point sp to p (rather than backing up).
1036		 */
1037		sp = p;
1038
1039		/*
1040		 * find length of p
1041		 */
1042		for (p1 = p; *p1++;)
1043		    continue;
1044		if (*link != '/') {
1045		    /*
1046		     * Relative path, expand it between the "yyy/" and the
1047		     * remainder. First, back sp up to the character past
1048		     * "yyy/".
1049		     */
1050		    while (*--sp != '/')
1051			continue;
1052		    sp++;
1053		    *sp = 0;
1054		    /*
1055		     * New length is "yyy/" + link + "/.." and rest
1056		     */
1057		    p1 = newcp = (Char *) xmalloc((size_t)
1058						  (((sp - cp) + cc + (p1 - p))
1059						   * sizeof(Char)));
1060		    /*
1061		     * Copy new path into newcp
1062		     */
1063		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
1064			continue;
1065		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
1066			continue;
1067		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1068			continue;
1069		    /*
1070		     * Restart canonicalization at expanded "/xxx".
1071		     */
1072		    p = sp - cp - 1 + newcp;
1073		}
1074		else {
1075		    /*
1076		     * New length is link + the rest
1077		     */
1078		    p1 = newcp = (Char *) xmalloc((size_t)
1079					    ((cc + (p1 - p)) * sizeof(Char)));
1080		    /*
1081		     * Copy new path into newcp
1082		     */
1083		    for (p2 = link; (*p1++ = *p2++) != '\0';)
1084			continue;
1085		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1086			continue;
1087		    /*
1088		     * Restart canonicalization at beginning
1089		     */
1090		    p = newcp;
1091		}
1092		xfree((ptr_t) cp);
1093		cp = newcp;
1094#ifdef apollo
1095                slashslash = (cp[0] == '/' && cp[1] == '/');
1096#endif /* apollo */
1097		continue;	/* canonicalize the link */
1098	    }
1099#endif /* S_IFLNK */
1100	    if (slash)
1101		*p = '/';
1102	}
1103    }
1104
1105    /*
1106     * fix home...
1107     */
1108#ifdef S_IFLNK
1109    p1 = varval(STRhome);
1110    cc = (int) Strlen(p1);
1111    /*
1112     * See if we're not in a subdir of STRhome
1113     */
1114    if (p1 && *p1 == '/' && (Strncmp(p1, cp, (size_t) cc) != 0 ||
1115	(cp[cc] != '/' && cp[cc] != '\0'))) {
1116	static ino_t home_ino = (ino_t) -1;
1117	static dev_t home_dev = (dev_t) -1;
1118	static Char *home_ptr = NULL;
1119	struct stat statbuf;
1120	int found;
1121
1122	/*
1123	 * Get dev and ino of STRhome
1124	 */
1125	if (home_ptr != p1 &&
1126	    stat(short2str(p1), &statbuf) != -1) {
1127	    home_dev = statbuf.st_dev;
1128	    home_ino = statbuf.st_ino;
1129	    home_ptr = p1;
1130	}
1131	/*
1132	 * Start comparing dev & ino backwards
1133	 */
1134	p2 = Strncpy(link, cp, sizeof(link) / sizeof(Char));
1135	link[sizeof(link) / sizeof(Char) - 1] = '\0';
1136	found = 0;
1137	while (*p2 && stat(short2str(p2), &statbuf) != -1) {
1138	    if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) &&
1139			statbuf.st_ino == home_ino) {
1140			found = 1;
1141			break;
1142	    }
1143	    if ((sp = Strrchr(p2, '/')) != NULL)
1144		*sp = '\0';
1145	}
1146	/*
1147	 * See if we found it
1148	 */
1149	if (*p2 && found) {
1150	    /*
1151	     * Use STRhome to make '~' work
1152	     */
1153	    newcp = Strspl(p1, cp + Strlen(p2));
1154	    xfree((ptr_t) cp);
1155	    cp = newcp;
1156	}
1157    }
1158#endif /* S_IFLNK */
1159
1160#ifdef apollo
1161    if (slashslash) {
1162	if (cp[1] != '/') {
1163	    p = (Char *) xmalloc((size_t) (Strlen(cp) + 2) * sizeof(Char));
1164	    *p = '/';
1165	    (void) Strcpy(&p[1], cp);
1166	    xfree((ptr_t) cp);
1167	    cp = p;
1168	}
1169    }
1170    if (cp[1] == '/' && cp[2] == '/')
1171	(void) Strcpy(&cp[1], &cp[2]);
1172#endif /* apollo */
1173    return cp;
1174}
1175
1176
1177/*
1178 * dnewcwd - make a new directory in the loop the current one
1179 */
1180static void
1181dnewcwd(dp, dflag)
1182    register struct directory *dp;
1183    int dflag;
1184{
1185    int print;
1186
1187    if (adrof(STRdunique)) {
1188	struct directory *dn;
1189
1190	for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev)
1191	    if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) {
1192		dn->di_next->di_prev = dn->di_prev;
1193		dn->di_prev->di_next = dn->di_next;
1194		dfree(dn);
1195		break;
1196	    }
1197    }
1198    dcwd = dp;
1199    dset(dcwd->di_name);
1200    dgetstack();
1201    print = printd;		/* if printd is set, print dirstack... */
1202    if (adrof(STRpushdsilent))	/* but pushdsilent overrides printd... */
1203	print = 0;
1204    if (dflag & DIR_PRINT)	/* but DIR_PRINT overrides pushdsilent... */
1205	print = 1;
1206    if (bequiet)		/* and bequiet overrides everything */
1207	print = 0;
1208    if (print)
1209	printdirs(dflag);
1210    cwd_cmd();			/* PWP: run the defined cwd command */
1211}
1212
1213void
1214dsetstack()
1215{
1216    Char **cp;
1217    struct varent *vp;
1218    struct directory *dn, *dp;
1219
1220    if ((vp = adrof(STRdirstack)) == NULL || vp->vec == NULL)
1221	return;
1222
1223    /* Free the whole stack */
1224    while ((dn = dhead.di_prev) != &dhead) {
1225	dn->di_next->di_prev = dn->di_prev;
1226	dn->di_prev->di_next = dn->di_next;
1227	if (dn != dcwd)
1228	    dfree(dn);
1229    }
1230
1231    /* thread the current working directory */
1232    dhead.di_prev = dhead.di_next = dcwd;
1233    dcwd->di_next = dcwd->di_prev = &dhead;
1234
1235    /* put back the stack */
1236    for (cp = vp->vec; cp && *cp && **cp; cp++) {
1237	dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
1238	dp->di_name = Strsave(*cp);
1239	dp->di_count = 0;
1240	dp->di_prev = dcwd;
1241	dp->di_next = dcwd->di_next;
1242	dcwd->di_next = dp;
1243	dp->di_next->di_prev = dp;
1244    }
1245    dgetstack();	/* Make $dirstack reflect the current state */
1246}
1247
1248static void
1249dgetstack()
1250{
1251    int i = 0;
1252    Char **dblk, **dbp;
1253    struct directory *dn;
1254
1255    if (adrof(STRdirstack) == NULL)
1256    	return;
1257
1258    for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++)
1259	continue;
1260    dbp = dblk = (Char**) xmalloc((size_t) (i + 1) * sizeof(Char *));
1261    for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++)
1262	 *dbp = Strsave(dn->di_name);
1263    *dbp = NULL;
1264    setq(STRdirstack, dblk, &shvhed, VAR_READWRITE);
1265}
1266
1267/*
1268 * getstakd - added by kfk 17 Jan 1984
1269 * Support routine for the stack hack.  Finds nth directory in
1270 * the directory stack, or finds last directory in stack.
1271 */
1272int
1273getstakd(s, cnt)
1274    Char   *s;
1275    int     cnt;
1276{
1277    struct directory *dp;
1278
1279    dp = dcwd;
1280    if (cnt < 0) {		/* < 0 ==> last dir requested. */
1281	dp = dp->di_next;
1282	if (dp == &dhead)
1283	    dp = dp->di_next;
1284    }
1285    else {
1286	while (cnt-- > 0) {
1287	    dp = dp->di_prev;
1288	    if (dp == &dhead)
1289		dp = dp->di_prev;
1290	    if (dp == dcwd)
1291		return (0);
1292	}
1293    }
1294    (void) Strncpy(s, dp->di_name, BUFSIZE);
1295    s[BUFSIZE - 1] = '\0';
1296    return (1);
1297}
1298
1299/*
1300 * Karl Kleinpaste - 10 Feb 1984
1301 * Added dextract(), which is used in pushd +n.
1302 * Instead of just rotating the entire stack around, dextract()
1303 * lets the user have the nth dir extracted from its current
1304 * position, and pushes it onto the top.
1305 */
1306static void
1307dextract(dp)
1308    struct directory *dp;
1309{
1310    if (dp == dcwd)
1311	return;
1312    dp->di_next->di_prev = dp->di_prev;
1313    dp->di_prev->di_next = dp->di_next;
1314    dp->di_next = dcwd->di_next;
1315    dp->di_prev = dcwd;
1316    dp->di_next->di_prev = dp;
1317    dcwd->di_next = dp;
1318}
1319
1320void
1321loaddirs(fname)
1322    Char *fname;
1323{
1324    static Char *loaddirs_cmd[] = { STRsource, NULL, NULL };
1325
1326    bequiet = 1;
1327    if (fname)
1328	loaddirs_cmd[1] = fname;
1329    else if ((fname = varval(STRdirsfile)) != STRNULL)
1330	loaddirs_cmd[1] = fname;
1331    else
1332	loaddirs_cmd[1] = STRtildotdirs;
1333    dosource(loaddirs_cmd, (struct command *)0);
1334    bequiet = 0;
1335}
1336
1337/*
1338 * create a file called ~/.cshdirs which has a sequence
1339 * of pushd commands which will restore the dir stack to
1340 * its state before exit/logout. remember that the order
1341 * is reversed in the file because we are pushing.
1342 * -strike
1343 */
1344void
1345recdirs(fname, def)
1346    Char *fname;
1347    int def;
1348{
1349    int     fp, ftmp, oldidfds;
1350    int     cdflag = 0;
1351    extern struct directory *dcwd;
1352    struct directory *dp;
1353    unsigned int    num;
1354    Char   *snum;
1355    Char    qname[MAXPATHLEN*2];
1356
1357    if (fname == NULL && !def)
1358	return;
1359
1360    if (fname == NULL) {
1361	if ((fname = varval(STRdirsfile)) == STRNULL)
1362	    fname = Strspl(varval(STRhome), &STRtildotdirs[1]);
1363	else
1364	    fname = Strsave(fname);
1365    }
1366    else
1367	fname = globone(fname, G_ERROR);
1368
1369    if ((fp = creat(short2str(fname), 0600)) == -1) {
1370	xfree((ptr_t) fname);
1371	return;
1372    }
1373
1374    if ((snum = varval(STRsavedirs)) == STRNULL || snum[0] == '\0')
1375	num = (unsigned int) ~0;
1376    else
1377	num = (unsigned int) atoi(short2str(snum));
1378
1379    oldidfds = didfds;
1380    didfds = 0;
1381    ftmp = SHOUT;
1382    SHOUT = fp;
1383
1384    dp = dcwd->di_next;
1385    do {
1386	if (dp == &dhead)
1387	    continue;
1388
1389	if (cdflag == 0) {
1390	    cdflag = 1;
1391	    xprintf("cd %S\n", quote_meta(qname, dp->di_name));
1392	}
1393	else
1394	    xprintf("pushd %S\n", quote_meta(qname, dp->di_name));
1395
1396	if (num-- == 0)
1397	    break;
1398
1399    } while ((dp = dp->di_next) != dcwd->di_next);
1400
1401    (void) close(fp);
1402    SHOUT = ftmp;
1403    didfds = oldidfds;
1404    xfree((ptr_t) fname);
1405}
1406