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