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