3fd8d703664aea55ddc351227032e1116810594a
[dragonfly.git] / contrib / tcsh-6 / sh.dir.c
1 /* $Header: /p/tcsh/cvsroot/tcsh/sh.dir.c,v 3.79 2006/09/25 18:17:26 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 #include "ed.h"
35
36 RCSID("$tcsh: sh.dir.c,v 3.79 2006/09/25 18:17:26 christos Exp $")
37
38 /*
39  * C Shell - directory management
40  */
41
42 static  Char                    *agetcwd        (void);
43 static  void                     dstart         (const char *);
44 static  struct directory        *dfind          (Char *);
45 static  Char                    *dfollow        (Char *);
46 static  void                     printdirs      (int);
47 static  Char                    *dgoto          (Char *);
48 static  void                     dnewcwd        (struct directory *, int);
49 static  void                     dset           (Char *);
50 static  void                     dextract       (struct directory *);
51 static  int                      skipargs       (Char ***, const char *,
52                                                  const char *);
53 static  void                     dgetstack      (void);
54
55 static struct directory dhead INIT_ZERO_STRUCT;         /* "head" of loop */
56 static int    printd;                   /* force name to be printed */
57
58 int     bequiet = 0;            /* do not print dir stack -strike */
59
60 static Char *
61 agetcwd(void)
62 {
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
90 static void
91 dstart(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  */
99 void
100 dinit(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
177 static void
178 dset(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
198 static int
199 skipargs(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 {
208             char *p;
209             while (*s != '\0')  /* examine flags */
210             {
211                 if ((p = strchr(dstr, *s++)) != NULL)
212                     dflag |= (1 << (p - dstr));
213                 else
214                     stderror(ERR_DIRUS, short2str(**v), dstr, str);
215             }
216         }
217     if (*n && (dflag & DIR_OLD))
218         stderror(ERR_DIRUS, short2str(**v), dstr, str);
219     *v = n;
220     /* make -l, -v, and -n imply -p */
221     if (dflag & (DIR_LONG|DIR_VERT|DIR_LINE))
222         dflag |= DIR_PRINT;
223     return dflag;
224 }
225
226 /*
227  * dodirs - list all directories in directory loop
228  */
229 /*ARGSUSED*/
230 void
231 dodirs(Char **v, struct command *c)
232 {
233     static const char flags[] = "plvnSLc";
234     int dflag = skipargs(&v, flags, "");
235
236     USE(c);
237     if ((dflag & DIR_CLEAR) != 0) {
238         struct directory *dp, *fdp;
239         for (dp = dcwd->di_next; dp != dcwd; ) {
240             fdp = dp;
241             dp = dp->di_next;
242             if (fdp != &dhead)
243                 dfree(fdp);
244         }
245         dhead.di_next = dhead.di_prev = dp;
246         dp->di_next = dp->di_prev = &dhead;
247     }
248     if ((dflag & DIR_LOAD) != 0) 
249         loaddirs(*v);
250     else if ((dflag & DIR_SAVE) != 0)
251         recdirs(*v, 1);
252
253     if (*v && (dflag & (DIR_SAVE|DIR_LOAD)))
254         v++;
255
256     if (*v != NULL || (dflag & DIR_OLD))
257         stderror(ERR_DIRUS, "dirs", flags, "");
258     if ((dflag & (DIR_CLEAR|DIR_LOAD|DIR_SAVE)) == 0 || (dflag & DIR_PRINT))
259         printdirs(dflag);
260 }
261
262 static void
263 printdirs(int dflag)
264 {
265     struct directory *dp;
266     Char   *s, *user;
267     int     idx, len, cur;
268
269     dp = dcwd;
270     idx = 0;
271     cur = 0;
272     do {
273         if (dp == &dhead)
274             continue;
275         if (dflag & DIR_VERT) {
276             xprintf("%d\t", idx++);
277             cur = 0;
278         }
279         s = dp->di_name;                
280         user = NULL;
281         if (!(dflag & DIR_LONG) && (user = getusername(&s)) != NULL)
282             len = (int) (Strlen(user) + Strlen(s) + 2);
283         else
284             len = (int) (Strlen(s) + 1);
285
286         cur += len;
287         if ((dflag & DIR_LINE) && cur >= TermH - 1 && len < TermH) {
288             xputchar('\n');
289             cur = len;
290         }
291         if (user) 
292             xprintf("~%S", user);
293         xprintf("%-S%c", s, (dflag & DIR_VERT) ? '\n' : ' ');
294     } while ((dp = dp->di_prev) != dcwd);
295     if (!(dflag & DIR_VERT))
296         xputchar('\n');
297 }
298
299 void
300 dtildepr(Char *dir)
301 {
302     Char* user;
303     if ((user = getusername(&dir)) != NULL)
304         xprintf("~%-S%S", user, dir);
305     else
306         xprintf("%S", dir);
307 }
308
309 void
310 dtilde(void)
311 {
312     struct directory *d = dcwd;
313
314     do {
315         if (d == &dhead)
316             continue;
317         d->di_name = dcanon(d->di_name, STRNULL);
318     } while ((d = d->di_prev) != dcwd);
319
320     dset(dcwd->di_name);
321 }
322
323
324 /* dnormalize():
325  *      The path will be normalized if it
326  *      1) is "..",
327  *      2) or starts with "../",
328  *      3) or ends with "/..",
329  *      4) or contains the string "/../",
330  *      then it will be normalized, unless those strings are quoted. 
331  *      Otherwise, a copy is made and sent back.
332  */
333 Char   *
334 dnormalize(const Char *cp, int expnd)
335 {
336
337 /* return true if dp is of the form "../xxx" or "/../xxx" */
338 #define IS_DOTDOT(sp, p) (ISDOTDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
339 #define IS_DOT(sp, p) (ISDOT(p) && ((p) == (sp) || *((p) - 1) == '/'))
340
341 #ifdef S_IFLNK
342     if (expnd) {
343         struct Strbuf buf = Strbuf_INIT;
344         int     dotdot = 0;
345         Char   *dp, *cwd;
346         const Char *start = cp;
347 # ifdef HAVE_SLASHSLASH
348         int slashslash;
349 # endif /* HAVE_SLASHSLASH */
350
351         /*
352          * count the number of "../xxx" or "xxx/../xxx" in the path
353          */
354         for ( ; *cp && *(cp + 1); cp++)
355             if (IS_DOTDOT(start, cp))
356                 dotdot++;
357
358         /*
359          * if none, we are done.
360          */
361         if (dotdot == 0)
362             return (Strsave(start));
363         
364 # ifdef notdef
365         struct stat sb;
366         /*
367          * We disable this test because:
368          * cd /tmp; mkdir dir1 dir2; cd dir2; ln -s /tmp/dir1; cd dir1;
369          * echo ../../dir1 does not expand. We had enabled this before
370          * because it was bothering people with expansions in compilation
371          * lines like -I../../foo. Maybe we need some kind of finer grain
372          * control?
373          *
374          * If the path doesn't exist, we are done too.
375          */
376         if (lstat(short2str(start), &sb) != 0 && errno == ENOENT)
377             return (Strsave(start));
378 # endif
379
380         cwd = xmalloc((Strlen(dcwd->di_name) + 3) * sizeof(Char));
381         (void) Strcpy(cwd, dcwd->di_name);
382
383         /*
384          * If the path starts with a slash, we are not relative to
385          * the current working directory.
386          */
387         if (ABSOLUTEP(start))
388             *cwd = '\0';
389 # ifdef HAVE_SLASHSLASH
390         slashslash = cwd[0] == '/' && cwd[1] == '/';
391 # endif /* HAVE_SLASHSLASH */
392
393         /*
394          * Ignore . and count ..'s
395          */
396         cp = start;
397         do {
398             dotdot = 0;
399             buf.len = 0;
400             while (*cp) 
401                 if (IS_DOT(start, cp)) {
402                     if (*++cp)
403                         cp++;
404                 }
405                 else if (IS_DOTDOT(start, cp)) {
406                     if (buf.len != 0)
407                         break; /* finish analyzing .././../xxx/[..] */
408                     dotdot++;
409                     cp += 2;
410                     if (*cp)
411                         cp++;
412                 }
413                 else 
414                     Strbuf_append1(&buf, *cp++);
415
416             Strbuf_terminate(&buf);
417             while (dotdot > 0) 
418                 if ((dp = Strrchr(cwd, '/')) != NULL) {
419 # ifdef HAVE_SLASHSLASH
420                     if (dp == &cwd[1]) 
421                         slashslash = 1;
422 # endif /* HAVE_SLASHSLASH */
423                         *dp = '\0';
424                         dotdot--;
425                 }
426                 else
427                     break;
428
429             if (!*cwd) {        /* too many ..'s, starts with "/" */
430                 cwd[0] = '/';
431 # ifdef HAVE_SLASHSLASH
432                 /*
433                  * Only append another slash, if already the former cwd
434                  * was in a double-slash path.
435                  */
436                 cwd[1] = slashslash ? '/' : '\0';
437                 cwd[2] = '\0';
438 # else /* !HAVE_SLASHSLASH */
439                 cwd[1] = '\0';
440 # endif /* HAVE_SLASHSLASH */
441             }
442 # ifdef HAVE_SLASHSLASH
443             else if (slashslash && cwd[1] == '\0') {
444                 cwd[1] = '/';
445                 cwd[2] = '\0';
446             }
447 # endif /* HAVE_SLASHSLASH */
448
449             if (buf.len != 0) {
450                 size_t i;
451
452                 i = Strlen(cwd);
453                 if (TRM(cwd[i - 1]) != '/') {
454                     cwd[i++] = '/';
455                     cwd[i] = '\0';
456                 }
457                 dp = Strspl(cwd, TRM(buf.s[0]) == '/' ? &buf.s[1] : buf.s);
458                 xfree(cwd);
459                 cwd = dp;
460                 i = Strlen(cwd) - 1;
461                 if (TRM(cwd[i]) == '/')
462                     cwd[i] = '\0';
463             }
464             /* Reduction of ".." following the stuff we collected in buf
465              * only makes sense if the directory item in buf really exists.
466              * Avoid reduction of "-I../.." (typical compiler call) to ""
467              * or "/usr/nonexistant/../bin" to "/usr/bin":
468              */
469             if (cwd[0]) {
470                 struct stat exists;
471                 if (0 != stat(short2str(cwd), &exists)) {
472                     xfree(buf.s);
473                     xfree(cwd);
474                     return Strsave(start);
475                 }
476             }
477         } while (*cp != '\0');
478         xfree(buf.s);
479         return cwd;
480     }
481 #endif /* S_IFLNK */
482     return Strsave(cp);
483 }
484
485
486 /*
487  * dochngd - implement chdir command.
488  */
489 /*ARGSUSED*/
490 void
491 dochngd(Char **v, struct command *c)
492 {
493     Char *cp;
494     struct directory *dp;
495     int dflag = skipargs(&v, "plvn", "[-|<dir>]");
496
497     USE(c);
498     printd = 0;
499     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
500
501     if (cp == NULL) {
502         if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
503             stderror(ERR_NAME | ERR_NOHOMEDIR);
504         if (chdir(short2str(cp)) < 0)
505             stderror(ERR_NAME | ERR_CANTCHANGE);
506         cp = Strsave(cp);
507     }
508     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
509         stderror(ERR_NAME | ERR_TOOMANY);
510         /* NOTREACHED */
511         return;
512     }
513     else if ((dp = dfind(cp)) != 0) {
514         char   *tmp;
515
516         printd = 1;
517         if (chdir(tmp = short2str(dp->di_name)) < 0)
518             stderror(ERR_SYSTEM, tmp, strerror(errno));
519         dcwd->di_prev->di_next = dcwd->di_next;
520         dcwd->di_next->di_prev = dcwd->di_prev;
521         dfree(dcwd);
522         dnewcwd(dp, dflag);
523         return;
524     }
525     else
526         if ((cp = dfollow(cp)) == NULL)
527             return;
528     dp = xcalloc(sizeof(struct directory), 1);
529     dp->di_name = cp;
530     dp->di_count = 0;
531     dp->di_next = dcwd->di_next;
532     dp->di_prev = dcwd->di_prev;
533     dp->di_prev->di_next = dp;
534     dp->di_next->di_prev = dp;
535     dfree(dcwd);
536     dnewcwd(dp, dflag);
537 }
538
539 static Char *
540 dgoto(Char *cp)
541 {
542     Char *dp, *ret;
543
544     if (!ABSOLUTEP(cp))
545     {
546         Char *p, *q;
547         size_t cwdlen;
548
549         cwdlen = Strlen(dcwd->di_name);
550         if (cwdlen == 1)        /* root */
551             cwdlen = 0;
552         dp = xmalloc((cwdlen + Strlen(cp) + 2) * sizeof(Char));
553         for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
554             continue;
555         if (cwdlen)
556             p[-1] = '/';
557         else
558             p--;                /* don't add a / after root */
559         Strcpy(p, cp);
560         xfree(cp);
561         cp = dp;
562         dp += cwdlen;
563     }
564     else
565         dp = cp;
566
567 #if defined(WINNT_NATIVE)
568     return agetcwd();
569 #elif defined(__CYGWIN__)
570     if (ABSOLUTEP(cp) && cp[1] == ':') { /* Only DOS paths are treated that way */
571         return agetcwd();
572     } else {
573         cleanup_push(cp, xfree);
574         ret = dcanon(cp, dp);
575         cleanup_ignore(cp);
576         cleanup_until(cp);
577     }
578 #else /* !WINNT_NATIVE */
579     cleanup_push(cp, xfree);
580     ret = dcanon(cp, dp);
581     cleanup_ignore(cp);
582     cleanup_until(cp);
583 #endif /* WINNT_NATIVE */
584     return ret;
585 }
586
587 /*
588  * dfollow - change to arg directory; fall back on cdpath if not valid
589  */
590 static Char *
591 dfollow(Char *cp)
592 {
593     Char *dp;
594     struct varent *c;
595     int serrno;
596
597     cp = globone(cp, G_ERROR);
598     cleanup_push(cp, xfree);
599 #ifdef apollo
600     if (Strchr(cp, '`')) {
601         char *dptr;
602         if (chdir(dptr = short2str(cp)) < 0) 
603             stderror(ERR_SYSTEM, dptr, strerror(errno));
604         dp = agetcwd();
605         cleanup_push(dp, xfree);
606         if (dp != NULL) {
607             cleanup_until(cp);
608             return dgoto(dp);
609         }
610         else
611             stderror(ERR_SYSTEM, dptr, strerror(errno));
612     }
613 #endif /* apollo */
614
615     /*
616      * if we are ignoring symlinks, try to fix relatives now.
617      * if we are expading symlinks, it should be done by now.
618      */ 
619     dp = dnormalize(cp, symlinks == SYM_IGNORE);
620     if (chdir(short2str(dp)) >= 0) {
621         cleanup_until(cp);
622         return dgoto(dp);
623     }
624     else {
625         xfree(dp);
626         if (chdir(short2str(cp)) >= 0) {
627             cleanup_ignore(cp);
628             cleanup_until(cp);
629             return dgoto(cp);
630         }
631         else if (errno != ENOENT && errno != ENOTDIR) {
632             int err;
633
634             err = errno;
635             stderror(ERR_SYSTEM, short2str(cp), strerror(err));
636         }
637         serrno = errno;
638     }
639
640     if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
641         && (c = adrof(STRcdpath)) && c->vec != NULL) {
642         struct Strbuf buf = Strbuf_INIT;
643         Char  **cdp;
644
645         for (cdp = c->vec; *cdp; cdp++) {
646             buf.len = 0;
647             Strbuf_append(&buf, *cdp);
648             Strbuf_append1(&buf, '/');
649             Strbuf_append(&buf, cp);
650             Strbuf_terminate(&buf);
651             /*
652              * We always want to fix the directory here
653              * If we are normalizing symlinks
654              */
655             dp = dnormalize(buf.s, symlinks == SYM_IGNORE || 
656                                    symlinks == SYM_EXPAND);
657             if (chdir(short2str(dp)) >= 0) {
658                 printd = 1;
659                 xfree(buf.s);
660                 cleanup_until(cp);
661                 return dgoto(dp);
662             }
663             else if (chdir(short2str(cp)) >= 0) {
664                 printd = 1;
665                 xfree(dp);
666                 xfree(buf.s);
667                 cleanup_ignore(cp);
668                 cleanup_until(cp);
669                 return dgoto(cp);
670             }
671         }
672         xfree(buf.s);
673     }
674     dp = varval(cp);
675     if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
676         cleanup_until(cp);
677         cp = Strsave(dp);
678         printd = 1;
679         return dgoto(cp);
680     }
681     /*
682      * on login source of ~/.cshdirs, errors are eaten. the dir stack is all
683      * directories we could get to.
684      */
685     if (!bequiet)
686         stderror(ERR_SYSTEM, short2str(cp), strerror(serrno));
687     cleanup_until(cp);
688     return (NULL);
689 }
690
691
692 /*
693  * dopushd - push new directory onto directory stack.
694  *      with no arguments exchange top and second.
695  *      with numeric argument (+n) bring it to top.
696  */
697 /*ARGSUSED*/
698 void
699 dopushd(Char **v, struct command *c)
700 {
701     struct directory *dp;
702     Char *cp;
703     int dflag = skipargs(&v, "plvn", " [-|<dir>|+<n>]");
704     
705     USE(c);
706     printd = 1;
707     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
708
709     if (cp == NULL) {
710         if (adrof(STRpushdtohome)) {
711             if ((cp = varval(STRhome)) == STRNULL || *cp == 0)
712                 stderror(ERR_NAME | ERR_NOHOMEDIR);
713             if (chdir(short2str(cp)) < 0)
714                 stderror(ERR_NAME | ERR_CANTCHANGE);
715             if ((cp = dfollow(cp)) == NULL)
716                 return;
717             dp = xcalloc(sizeof(struct directory), 1);
718             dp->di_name = cp;
719             dp->di_count = 0;
720             dp->di_prev = dcwd;
721             dp->di_next = dcwd->di_next;
722             dcwd->di_next = dp;
723             dp->di_next->di_prev = dp;
724         }
725         else {
726             char   *tmp;
727
728             if ((dp = dcwd->di_prev) == &dhead)
729                 dp = dhead.di_prev;
730             if (dp == dcwd)
731                 stderror(ERR_NAME | ERR_NODIR);
732             if (chdir(tmp = short2str(dp->di_name)) < 0)
733                 stderror(ERR_SYSTEM, tmp, strerror(errno));
734             dp->di_prev->di_next = dp->di_next;
735             dp->di_next->di_prev = dp->di_prev;
736             dp->di_next = dcwd->di_next;
737             dp->di_prev = dcwd;
738             dcwd->di_next->di_prev = dp;
739             dcwd->di_next = dp;
740         }
741     }
742     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
743         stderror(ERR_NAME | ERR_TOOMANY);
744         /* NOTREACHED */
745         return;
746     }
747     else if ((dp = dfind(cp)) != NULL) {
748         char   *tmp;
749
750         if (chdir(tmp = short2str(dp->di_name)) < 0)
751             stderror(ERR_SYSTEM, tmp, strerror(errno));
752         /*
753          * kfk - 10 Feb 1984 - added new "extraction style" pushd +n
754          */
755         if (adrof(STRdextract))
756             dextract(dp);
757     }
758     else {
759         Char *ccp;
760
761         if ((ccp = dfollow(cp)) == NULL)
762             return;
763         dp = xcalloc(sizeof(struct directory), 1);
764         dp->di_name = ccp;
765         dp->di_count = 0;
766         dp->di_prev = dcwd;
767         dp->di_next = dcwd->di_next;
768         dcwd->di_next = dp;
769         dp->di_next->di_prev = dp;
770     }
771     dnewcwd(dp, dflag);
772 }
773
774 /*
775  * dfind - find a directory if specified by numeric (+n) argument
776  */
777 static struct directory *
778 dfind(Char *cp)
779 {
780     struct directory *dp;
781     int i;
782     Char *ep;
783
784     if (*cp++ != '+')
785         return (0);
786     for (ep = cp; Isdigit(*ep); ep++)
787         continue;
788     if (*ep)
789         return (0);
790     i = getn(cp);
791     if (i <= 0)
792         return (0);
793     for (dp = dcwd; i != 0; i--) {
794         if ((dp = dp->di_prev) == &dhead)
795             dp = dp->di_prev;
796         if (dp == dcwd)
797             stderror(ERR_NAME | ERR_DEEP);
798     }
799     return (dp);
800 }
801
802 /*
803  * dopopd - pop a directory out of the directory stack
804  *      with a numeric argument just discard it.
805  */
806 /*ARGSUSED*/
807 void
808 dopopd(Char **v, struct command *c)
809 {
810     Char *cp;
811     struct directory *dp, *p = NULL;
812     int dflag = skipargs(&v, "plvn", " [-|+<n>]");
813
814     USE(c);
815     printd = 1;
816     cp = (dflag & DIR_OLD) ? varval(STRowd) : *v;
817
818     if (cp == NULL)
819         dp = dcwd;
820     else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) {
821         stderror(ERR_NAME | ERR_TOOMANY);
822         /* NOTREACHED */
823         return;
824     }
825     else if ((dp = dfind(cp)) == 0)
826         stderror(ERR_NAME | ERR_BADDIR);
827     if (dp->di_prev == &dhead && dp->di_next == &dhead)
828         stderror(ERR_NAME | ERR_EMPTY);
829     if (dp == dcwd) {
830         char   *tmp;
831
832         if ((p = dp->di_prev) == &dhead)
833             p = dhead.di_prev;
834         if (chdir(tmp = short2str(p->di_name)) < 0)
835             stderror(ERR_SYSTEM, tmp, strerror(errno));
836     }
837     dp->di_prev->di_next = dp->di_next;
838     dp->di_next->di_prev = dp->di_prev;
839     dfree(dp);
840     if (dp == dcwd) {
841         dnewcwd(p, dflag);
842     }
843     else {
844         printdirs(dflag);
845     }
846 }
847
848 /*
849  * dfree - free the directory (or keep it if it still has ref count)
850  */
851 void
852 dfree(struct directory *dp)
853 {
854
855     if (dp->di_count != 0) {
856         dp->di_next = dp->di_prev = 0;
857     }
858     else {
859         xfree(dp->di_name);
860         xfree(dp);
861     }
862 }
863
864 /*
865  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
866  *      we are of course assuming that the file system is standardly
867  *      constructed (always have ..'s, directories have links)
868  */
869 Char   *
870 dcanon(Char *cp, Char *p)
871 {
872     Char *sp;
873     Char *p1, *p2;      /* general purpose */
874     int    slash;
875 #ifdef HAVE_SLASHSLASH
876     int    slashslash;
877 #endif /* HAVE_SLASHSLASH */
878     size_t  clen;
879
880 #ifdef S_IFLNK                  /* if we have symlinks */
881     Char *mlink, *newcp;
882     char *tlink;
883     size_t cc;
884 #endif /* S_IFLNK */
885
886     clen = Strlen(cp);
887
888     /*
889      * christos: if the path given does not start with a slash prepend cwd. If
890      * cwd does not start with a slash or the result would be too long try to
891      * correct it.
892      */
893     if (!ABSOLUTEP(cp)) {
894         Char *tmpdir;
895         size_t  len;
896
897         p1 = varval(STRcwd);
898         if (p1 == STRNULL || !ABSOLUTEP(p1)) {
899             Char *new_cwd = agetcwd();
900
901             if (new_cwd == NULL) {
902                 xprintf("%s: %s\n", progname, strerror(errno));
903                 setcopy(STRcwd, str2short("/"), VAR_READWRITE|VAR_NOGLOB);
904             }
905             else
906                 setv(STRcwd, new_cwd, VAR_READWRITE|VAR_NOGLOB);
907             p1 = varval(STRcwd);
908         }
909         len = Strlen(p1);
910         tmpdir = xmalloc((len + clen + 2) * sizeof (*tmpdir));
911         (void) Strcpy(tmpdir, p1);
912         (void) Strcat(tmpdir, STRslash);
913         (void) Strcat(tmpdir, cp);
914         xfree(cp);
915         cp = p = tmpdir;
916     }
917
918 #ifdef HAVE_SLASHSLASH
919     slashslash = (cp[0] == '/' && cp[1] == '/');
920 #endif /* HAVE_SLASHSLASH */
921
922     while (*p) {                /* for each component */
923         sp = p;                 /* save slash address */
924         while (*++p == '/')     /* flush extra slashes */
925             continue;
926         if (p != ++sp)
927             for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
928                 continue;
929         p = sp;                 /* save start of component */
930         slash = 0;
931         if (*p) 
932             while (*++p)        /* find next slash or end of path */
933                 if (*p == '/') {
934                     slash = 1;
935                     *p = 0;
936                     break;
937                 }
938
939 #ifdef HAVE_SLASHSLASH
940         if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0')
941             slashslash = 1;
942 #endif /* HAVE_SLASHSLASH */
943         if (*sp == '\0') {      /* if component is null */
944             if (--sp == cp)     /* if path is one char (i.e. /) */ 
945                 break;
946             else
947                 *sp = '\0';
948         }
949         else if (sp[0] == '.' && sp[1] == 0) {
950             if (slash) {
951                 for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
952                     continue;
953                 p = --sp;
954             }
955             else if (--sp != cp)
956                 *sp = '\0';
957             else
958                 sp[1] = '\0';
959         }
960         else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
961             /*
962              * We have something like "yyy/xxx/..", where "yyy" can be null or
963              * a path starting at /, and "xxx" is a single component. Before
964              * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
965              * symbolic link.
966              */
967             *--sp = 0;          /* form the pathname for readlink */
968 #ifdef S_IFLNK                  /* if we have symlinks */
969             if (sp != cp && /* symlinks != SYM_IGNORE && */
970                 (tlink = areadlink(short2str(cp))) != NULL) {
971                 mlink = str2short(tlink);
972                 xfree(tlink);
973
974                 if (slash)
975                     *p = '/';
976                 /*
977                  * Point p to the '/' in "/..", and restore the '/'.
978                  */
979                 *(p = sp) = '/';
980                 if (*mlink != '/') {
981                     /*
982                      * Relative path, expand it between the "yyy/" and the
983                      * "/..". First, back sp up to the character past "yyy/".
984                      */
985                     while (*--sp != '/')
986                         continue;
987                     sp++;
988                     *sp = 0;
989                     /*
990                      * New length is "yyy/" + mlink + "/.." and rest
991                      */
992                     p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
993                                           Strlen(p) + 1) * sizeof(Char));
994                     /*
995                      * Copy new path into newcp
996                      */
997                     for (p2 = cp; (*p1++ = *p2++) != '\0';)
998                         continue;
999                     for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
1000                         continue;
1001                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1002                         continue;
1003                     /*
1004                      * Restart canonicalization at expanded "/xxx".
1005                      */
1006                     p = sp - cp - 1 + newcp;
1007                 }
1008                 else {
1009                     newcp = Strspl(mlink, p);
1010                     /*
1011                      * Restart canonicalization at beginning
1012                      */
1013                     p = newcp;
1014                 }
1015                 xfree(cp);
1016                 cp = newcp;
1017 #ifdef HAVE_SLASHSLASH
1018                 slashslash = (cp[0] == '/' && cp[1] == '/');
1019 #endif /* HAVE_SLASHSLASH */
1020                 continue;       /* canonicalize the link */
1021             }
1022 #endif /* S_IFLNK */
1023             *sp = '/';
1024             if (sp != cp)
1025                 while (*--sp != '/')
1026                     continue;
1027             if (slash) {
1028                 for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
1029                     continue;
1030                 p = sp;
1031             }
1032             else if (cp == sp)
1033                 *++sp = '\0';
1034             else
1035                 *sp = '\0';
1036         }
1037         else {                  /* normal dir name (not . or .. or nothing) */
1038
1039 #ifdef S_IFLNK                  /* if we have symlinks */
1040             if (sp != cp && symlinks == SYM_CHASE &&
1041                 (tlink = areadlink(short2str(cp))) != NULL) {
1042                 mlink = str2short(tlink);
1043                 xfree(tlink);
1044
1045                 /*
1046                  * restore the '/'.
1047                  */
1048                 if (slash)
1049                     *p = '/';
1050
1051                 /*
1052                  * point sp to p (rather than backing up).
1053                  */
1054                 sp = p;
1055
1056                 if (*mlink != '/') {
1057                     /*
1058                      * Relative path, expand it between the "yyy/" and the
1059                      * remainder. First, back sp up to the character past
1060                      * "yyy/".
1061                      */
1062                     while (*--sp != '/')
1063                         continue;
1064                     sp++;
1065                     *sp = 0;
1066                     /*
1067                      * New length is "yyy/" + mlink + "/.." and rest
1068                      */
1069                     p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) +
1070                                           Strlen(p) + 1) * sizeof(Char));
1071                     /*
1072                      * Copy new path into newcp
1073                      */
1074                     for (p2 = cp; (*p1++ = *p2++) != '\0';)
1075                         continue;
1076                     for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';)
1077                         continue;
1078                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
1079                         continue;
1080                     /*
1081                      * Restart canonicalization at expanded "/xxx".
1082                      */
1083                     p = sp - cp - 1 + newcp;
1084                 }
1085                 else {
1086                     newcp = Strspl(mlink, p);
1087                     /*
1088                      * Restart canonicalization at beginning
1089                      */
1090                     p = newcp;
1091                 }
1092                 xfree(cp);
1093                 cp = newcp;
1094 #ifdef HAVE_SLASHSLASH
1095                 slashslash = (cp[0] == '/' && cp[1] == '/');
1096 #endif /* HAVE_SLASHSLASH */
1097                 continue;       /* canonicalize the mlink */
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 = Strlen(p1);
1111     /*
1112      * See if we're not in a subdir of STRhome
1113      */
1114     if (p1 && *p1 == '/' && (Strncmp(p1, cp, 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         Char *copy;
1122
1123         /*
1124          * Get dev and ino of STRhome
1125          */
1126         if (home_ptr != p1 &&
1127             stat(short2str(p1), &statbuf) != -1) {
1128             home_dev = statbuf.st_dev;
1129             home_ino = statbuf.st_ino;
1130             home_ptr = p1;
1131         }
1132         /*
1133          * Start comparing dev & ino backwards
1134          */
1135         p2 = copy = Strsave(cp);
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(cp);
1155             cp = newcp;
1156         }
1157         xfree(copy);
1158     }
1159 #endif /* S_IFLNK */
1160
1161 #ifdef HAVE_SLASHSLASH
1162     if (slashslash) {
1163         if (cp[1] != '/') {
1164             p = xmalloc((Strlen(cp) + 2) * sizeof(Char));
1165             *p = '/';
1166             (void) Strcpy(&p[1], cp);
1167             xfree(cp);
1168             cp = p;
1169         }
1170     }
1171     if (cp[1] == '/' && cp[2] == '/') {
1172         for (p1 = &cp[1], p2 = &cp[2]; (*p1++ = *p2++) != '\0';)
1173             continue;
1174     }
1175 #endif /* HAVE_SLASHSLASH */
1176     return cp;
1177 }
1178
1179
1180 /*
1181  * dnewcwd - make a new directory in the loop the current one
1182  */
1183 static void
1184 dnewcwd(struct directory *dp, int dflag)
1185 {
1186     int print;
1187
1188     if (adrof(STRdunique)) {
1189         struct directory *dn;
1190
1191         for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev) 
1192             if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) {
1193                 dn->di_next->di_prev = dn->di_prev;
1194                 dn->di_prev->di_next = dn->di_next;
1195                 dfree(dn);
1196                 break;
1197             }
1198     }
1199     dcwd = dp;
1200     dset(dcwd->di_name);
1201     dgetstack();
1202     print = printd;             /* if printd is set, print dirstack... */
1203     if (adrof(STRpushdsilent))  /* but pushdsilent overrides printd... */
1204         print = 0;
1205     if (dflag & DIR_PRINT)      /* but DIR_PRINT overrides pushdsilent... */
1206         print = 1;
1207     if (bequiet)                /* and bequiet overrides everything */
1208         print = 0;
1209     if (print)
1210         printdirs(dflag);
1211     cwd_cmd();                  /* PWP: run the defined cwd command */
1212 }
1213
1214 void
1215 dsetstack(void)
1216 {
1217     Char **cp;
1218     struct varent *vp;
1219     struct directory *dn, *dp;
1220
1221     if ((vp = adrof(STRdirstack)) == NULL || vp->vec == NULL)
1222         return;
1223
1224     /* Free the whole stack */
1225     while ((dn = dhead.di_prev) != &dhead) {
1226         dn->di_next->di_prev = dn->di_prev;
1227         dn->di_prev->di_next = dn->di_next;
1228         if (dn != dcwd)
1229             dfree(dn);
1230     }
1231
1232     /* thread the current working directory */
1233     dhead.di_prev = dhead.di_next = dcwd;
1234     dcwd->di_next = dcwd->di_prev = &dhead;
1235
1236     /* put back the stack */
1237     for (cp = vp->vec; cp && *cp && **cp; cp++) {
1238         dp = xcalloc(sizeof(struct directory), 1);
1239         dp->di_name = Strsave(*cp);
1240         dp->di_count = 0;
1241         dp->di_prev = dcwd;
1242         dp->di_next = dcwd->di_next;
1243         dcwd->di_next = dp;
1244         dp->di_next->di_prev = dp;
1245     }
1246     dgetstack();        /* Make $dirstack reflect the current state */
1247 }
1248
1249 static void
1250 dgetstack(void)
1251 {
1252     int i = 0;
1253     Char **dblk, **dbp;
1254     struct directory *dn;
1255
1256     if (adrof(STRdirstack) == NULL) 
1257         return;
1258
1259     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++)
1260         continue;
1261     dbp = dblk = xmalloc((i + 1) * sizeof(Char *));
1262     for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++)
1263          *dbp = Strsave(dn->di_name);
1264     *dbp = NULL;
1265     cleanup_push(dblk, blk_cleanup);
1266     setq(STRdirstack, dblk, &shvhed, VAR_READWRITE);
1267     cleanup_ignore(dblk);
1268     cleanup_until(dblk);
1269 }
1270
1271 /*
1272  * getstakd - added by kfk 17 Jan 1984
1273  * Support routine for the stack hack.  Finds nth directory in
1274  * the directory stack, or finds last directory in stack.
1275  */
1276 const Char *
1277 getstakd(int cnt)
1278 {
1279     struct directory *dp;
1280
1281     dp = dcwd;
1282     if (cnt < 0) {              /* < 0 ==> last dir requested. */
1283         dp = dp->di_next;
1284         if (dp == &dhead)
1285             dp = dp->di_next;
1286     }
1287     else {
1288         while (cnt-- > 0) {
1289             dp = dp->di_prev;
1290             if (dp == &dhead)
1291                 dp = dp->di_prev;
1292             if (dp == dcwd)
1293                 return NULL;
1294         }
1295     }
1296     return dp->di_name;
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  */
1306 static void
1307 dextract(struct directory *dp)
1308 {
1309     if (dp == dcwd)
1310         return;
1311     dp->di_next->di_prev = dp->di_prev;
1312     dp->di_prev->di_next = dp->di_next;
1313     dp->di_next = dcwd->di_next;
1314     dp->di_prev = dcwd;
1315     dp->di_next->di_prev = dp;
1316     dcwd->di_next = dp;
1317 }
1318
1319 static void
1320 bequiet_cleanup(void *dummy)
1321 {
1322     USE(dummy);
1323     bequiet = 0;
1324 }
1325
1326 void
1327 loaddirs(Char *fname)
1328 {
1329     static Char *loaddirs_cmd[] = { STRsource, NULL, NULL };
1330
1331     bequiet = 1;
1332     cleanup_push(&bequiet, bequiet_cleanup);
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, NULL);
1340     cleanup_until(&bequiet);
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  */
1350 void
1351 recdirs(Char *fname, int def)
1352 {
1353     int     fp, ftmp, oldidfds;
1354     int     cdflag = 0;
1355     struct directory *dp;
1356     unsigned int    num;
1357     Char   *snum;
1358     struct Strbuf qname = Strbuf_INIT;
1359
1360     if (fname == NULL && !def) 
1361         return;
1362
1363     if (fname == NULL) {
1364         if ((fname = varval(STRdirsfile)) == STRNULL)
1365             fname = Strspl(varval(STRhome), &STRtildotdirs[1]);
1366         else
1367             fname = Strsave(fname);
1368     }
1369     else 
1370         fname = globone(fname, G_ERROR);
1371     cleanup_push(fname, xfree);
1372
1373     if ((fp = xcreat(short2str(fname), 0600)) == -1) {
1374         cleanup_until(fname);
1375         return;
1376     }
1377
1378     if ((snum = varval(STRsavedirs)) == STRNULL || snum[0] == '\0') 
1379         num = (unsigned int) ~0;
1380     else
1381         num = (unsigned int) atoi(short2str(snum));
1382
1383     oldidfds = didfds;
1384     didfds = 0;
1385     ftmp = SHOUT;
1386     SHOUT = fp;
1387
1388     cleanup_push(&qname, Strbuf_cleanup);
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     xclose(fp);
1407     SHOUT = ftmp;
1408     didfds = oldidfds;
1409     cleanup_until(fname);
1410 }