]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/csh/dir.c
This commit was generated by cvs2svn to compensate for changes in r24583,
[FreeBSD/FreeBSD.git] / bin / csh / dir.c
1 /*-
2  * Copyright (c) 1980, 1991, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. 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  *      $Id$
34  */
35
36 #ifndef lint
37 static char sccsid[] = "@(#)dir.c       8.1 (Berkeley) 5/31/93";
38 #endif /* not lint */
39
40 #include <sys/param.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #if __STDC__
47 # include <stdarg.h>
48 #else
49 # include <varargs.h>
50 #endif
51
52 #include "csh.h"
53 #include "dir.h"
54 #include "extern.h"
55
56 /* Directory management. */
57
58 static struct directory
59                 *dfind __P((Char *));
60 static Char     *dfollow __P((Char *));
61 static void      printdirs __P((void));
62 static Char     *dgoto __P((Char *));
63 static void      dnewcwd __P((struct directory *));
64 static void      dset __P((Char *));
65
66 struct directory dhead;         /* "head" of loop */
67 int     printd;                 /* force name to be printed */
68
69 static int dirflag = 0;
70
71 /*
72  * dinit - initialize current working directory
73  */
74 void
75 dinit(hp)
76     Char   *hp;
77 {
78     register char *tcp;
79     register Char *cp;
80     register struct directory *dp;
81     char    path[MAXPATHLEN];
82     static char *emsg = "csh: Trying to start from \"%s\"\n";
83
84     /* Don't believe the login shell home, because it may be a symlink */
85     tcp = getwd(path);          /* see ngetwd.c for System V version */
86     if (tcp == NULL || *tcp == '\0') {
87         (void) fprintf(csherr, "csh: %s: %s\n", path, strerror(errno));
88         if (hp && *hp) {
89             tcp = short2str(hp);
90             if (chdir(tcp) == -1)
91                 cp = NULL;
92             else
93                 cp = hp;
94             (void) fprintf(csherr, emsg, vis_str(hp));
95         }
96         else
97             cp = NULL;
98         if (cp == NULL) {
99             (void) fprintf(csherr, emsg, "/");
100             if (chdir("/") == -1)
101                 /* I am not even try to print an error message! */
102                 xexit(1);
103             cp = SAVE("/");
104         }
105     }
106     else {
107         struct stat swd, shp;
108
109         /*
110          * See if $HOME is the working directory we got and use that
111          */
112         if (hp && *hp &&
113             stat(tcp, &swd) != -1 && stat(short2str(hp), &shp) != -1 &&
114             swd.st_dev == shp.st_dev && swd.st_ino == shp.st_ino)
115             cp = hp;
116         else {
117             char   *cwd;
118
119             /*
120              * use PWD if we have it (for subshells)
121              */
122             if ((cwd = getenv("PWD")) != NULL) {
123                 if (stat(cwd, &shp) != -1 && swd.st_dev == shp.st_dev &&
124                     swd.st_ino == shp.st_ino)
125                     tcp = cwd;
126             }
127             cp = dcanon(SAVE(tcp), STRNULL);
128         }
129     }
130
131     dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
132     dp->di_name = Strsave(cp);
133     dp->di_count = 0;
134     dhead.di_next = dhead.di_prev = dp;
135     dp->di_next = dp->di_prev = &dhead;
136     printd = 0;
137     dnewcwd(dp);
138 }
139
140 static void
141 dset(dp)
142 Char *dp;
143 {
144     /*
145      * Don't call set() directly cause if the directory contains ` or
146      * other junk characters glob will fail.
147      */
148     register Char **vec = (Char **) xmalloc((size_t) (2 * sizeof(Char **)));
149
150     vec[0] = Strsave(dp);
151     vec[1] = 0;
152     setq(STRcwd, vec, &shvhed);
153     Setenv(STRPWD, dp);
154 }
155
156 #define DIR_LONG 1
157 #define DIR_VERT 2
158 #define DIR_LINE 4
159
160 static void
161 skipargs(v, str)
162     Char ***v;
163     char   *str;
164 {
165     Char  **n = *v, *s;
166
167     dirflag = 0;
168     for (n++; *n != NULL && (*n)[0] == '-'; n++)
169         for (s = &((*n)[1]); *s; s++)
170             switch (*s) {
171             case 'l':
172                 dirflag |= DIR_LONG;
173                 break;
174             case 'v':
175                 dirflag |= DIR_VERT;
176                 break;
177             case 'n':
178                 dirflag |= DIR_LINE;
179                 break;
180             default:
181                 stderror(ERR_DIRUS, vis_str(**v), str);
182                 break;
183             }
184     *v = n;
185 }
186
187 /*
188  * dodirs - list all directories in directory loop
189  */
190 void
191 /*ARGSUSED*/
192 dodirs(v, t)
193     Char **v;
194     struct command *t;
195 {
196     skipargs(&v, "");
197
198     if (*v != NULL)
199         stderror(ERR_DIRUS, "dirs", "");
200     printdirs();
201 }
202
203 static void
204 printdirs()
205 {
206     register struct directory *dp;
207     Char   *s, *hp = value(STRhome);
208     int     idx, len, cur;
209
210     if (*hp == '\0')
211         hp = NULL;
212     dp = dcwd;
213     idx = 0;
214     cur = 0;
215     do {
216         if (dp == &dhead)
217             continue;
218         if (dirflag & DIR_VERT) {
219             (void) fprintf(cshout, "%d\t", idx++);
220             cur = 0;
221         }
222         if (!(dirflag & DIR_LONG) && hp != NULL && !eq(hp, STRslash) &&
223             (len = Strlen(hp), Strncmp(hp, dp->di_name, len) == 0) &&
224             (dp->di_name[len] == '\0' || dp->di_name[len] == '/'))
225             len = Strlen(s = (dp->di_name + len)) + 2;
226         else
227             len = Strlen(s = dp->di_name) + 1;
228
229         cur += len;
230         if ((dirflag & DIR_LINE) && cur >= 80 - 1 && len < 80) {
231             (void) fprintf(cshout, "\n");
232             cur = len;
233         }
234         (void) fprintf(cshout, s != dp->di_name ? "~%s%c" : "%s%c",
235                 vis_str(s), (dirflag & DIR_VERT) ? '\n' : ' ');
236     } while ((dp = dp->di_prev) != dcwd);
237     if (!(dirflag & DIR_VERT))
238         (void) fprintf(cshout, "\n");
239 }
240
241 void
242 dtildepr(home, dir)
243     register Char *home, *dir;
244 {
245
246     if (!eq(home, STRslash) && prefix(home, dir))
247         (void) fprintf(cshout, "~%s", vis_str(dir + Strlen(home)));
248     else
249         (void) fprintf(cshout, "%s", vis_str(dir));
250 }
251
252 void
253 dtilde()
254 {
255     struct directory *d = dcwd;
256
257     do {
258         if (d == &dhead)
259             continue;
260         d->di_name = dcanon(d->di_name, STRNULL);
261     } while ((d = d->di_prev) != dcwd);
262
263     dset(dcwd->di_name);
264 }
265
266
267 /* dnormalize():
268  *      If the name starts with . or .. then we might need to normalize
269  *      it depending on the symbolic link flags
270  */
271 Char   *
272 dnormalize(cp)
273     Char   *cp;
274 {
275
276 #define UC (unsigned char)
277 #define ISDOT(c) (UC(c)[0] == '.' && ((UC(c)[1] == '\0') || (UC(c)[1] == '/')))
278 #define ISDOTDOT(c) (UC(c)[0] == '.' && ISDOT(&((c)[1])))
279
280     if ((unsigned char) cp[0] == '/')
281         return (Strsave(cp));
282
283     if (adrof(STRignore_symlinks)) {
284         int     dotdot = 0;
285         Char   *dp, *cwd;
286
287         cwd = (Char *) xmalloc((size_t) ((Strlen(dcwd->di_name) + 3) *
288                                          sizeof(Char)));
289         (void) Strcpy(cwd, dcwd->di_name);
290
291         /*
292          * Ignore . and count ..'s
293          */
294         while (*cp) {
295             if (ISDOT(cp)) {
296                 if (*++cp)
297                     cp++;
298             }
299             else if (ISDOTDOT(cp)) {
300                 dotdot++;
301                 cp += 2;
302                 if (*cp)
303                     cp++;
304             }
305             else
306                 break;
307         }
308         while (dotdot > 0)
309             if ((dp = Strrchr(cwd, '/'))) {
310                 *dp = '\0';
311                 dotdot--;
312             }
313             else
314                 break;
315
316         if (*cp) {
317             cwd[dotdot = Strlen(cwd)] = '/';
318             cwd[dotdot + 1] = '\0';
319             dp = Strspl(cwd, cp);
320             xfree((ptr_t) cwd);
321             return dp;
322         }
323         else {
324             if (!*cwd) {
325                 cwd[0] = '/';
326                 cwd[1] = '\0';
327             }
328             return cwd;
329         }
330     }
331     return Strsave(cp);
332 }
333
334 /*
335  * dochngd - implement chdir command.
336  */
337 void
338 /*ARGSUSED*/
339 dochngd(v, t)
340     Char **v;
341     struct command *t;
342 {
343     register Char *cp;
344     register struct directory *dp;
345
346     skipargs(&v, " [<dir>]");
347     printd = 0;
348     if (*v == NULL) {
349         if ((cp = value(STRhome)) == NULL || *cp == 0)
350             stderror(ERR_NAME | ERR_NOHOMEDIR);
351         if (chdir(short2str(cp)) < 0)
352             stderror(ERR_NAME | ERR_CANTCHANGE);
353         cp = Strsave(cp);
354     }
355     else if (v[1] != NULL) {
356         stderror(ERR_NAME | ERR_TOOMANY);
357         /* NOTREACHED */
358         return;
359     }
360     else if ((dp = dfind(*v)) != 0) {
361         char   *tmp;
362
363         printd = 1;
364         if (chdir(tmp = short2str(dp->di_name)) < 0)
365             stderror(ERR_SYSTEM, tmp, strerror(errno));
366         dcwd->di_prev->di_next = dcwd->di_next;
367         dcwd->di_next->di_prev = dcwd->di_prev;
368         dfree(dcwd);
369         dnewcwd(dp);
370         return;
371     }
372     else
373         cp = dfollow(*v);
374     dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
375     dp->di_name = cp;
376     dp->di_count = 0;
377     dp->di_next = dcwd->di_next;
378     dp->di_prev = dcwd->di_prev;
379     dp->di_prev->di_next = dp;
380     dp->di_next->di_prev = dp;
381     dfree(dcwd);
382     dnewcwd(dp);
383 }
384
385 static Char *
386 dgoto(cp)
387     Char   *cp;
388 {
389     Char   *dp;
390
391     if (*cp != '/') {
392         register Char *p, *q;
393         int     cwdlen;
394
395         for (p = dcwd->di_name; *p++;)
396             continue;
397         if ((cwdlen = p - dcwd->di_name - 1) == 1)      /* root */
398             cwdlen = 0;
399         for (p = cp; *p++;)
400             continue;
401         dp = (Char *) xmalloc((size_t)((cwdlen + (p - cp) + 1) * sizeof(Char)));
402         for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
403             continue;
404         if (cwdlen)
405             p[-1] = '/';
406         else
407             p--;                /* don't add a / after root */
408         for (q = cp; (*p++ = *q++) != '\0';)
409             continue;
410         xfree((ptr_t) cp);
411         cp = dp;
412         dp += cwdlen;
413     }
414     else
415         dp = cp;
416
417     cp = dcanon(cp, dp);
418     return cp;
419 }
420
421 /*
422  * dfollow - change to arg directory; fall back on cdpath if not valid
423  */
424 static Char *
425 dfollow(cp)
426     register Char *cp;
427 {
428     register Char *dp;
429     struct varent *c;
430     char    ebuf[MAXPATHLEN];
431     int serrno;
432
433     cp = globone(cp, G_ERROR);
434     /*
435      * if we are ignoring symlinks, try to fix relatives now.
436      */
437     dp = dnormalize(cp);
438     if (chdir(short2str(dp)) >= 0) {
439         xfree((ptr_t) cp);
440         return dgoto(dp);
441     }
442     else {
443         xfree((ptr_t) dp);
444         if (chdir(short2str(cp)) >= 0)
445             return dgoto(cp);
446         serrno = errno;
447     }
448
449     if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
450         && (c = adrof(STRcdpath))) {
451         Char  **cdp;
452         register Char *p;
453         Char    buf[MAXPATHLEN];
454
455         for (cdp = c->vec; *cdp; cdp++) {
456             for (dp = buf, p = *cdp; (*dp++ = *p++) != '\0';)
457                 continue;
458             dp[-1] = '/';
459             for (p = cp; (*dp++ = *p++) != '\0';)
460                 continue;
461             if (chdir(short2str(buf)) >= 0) {
462                 printd = 1;
463                 xfree((ptr_t) cp);
464                 cp = Strsave(buf);
465                 return dgoto(cp);
466             }
467         }
468     }
469     dp = value(cp);
470     if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
471         xfree((ptr_t) cp);
472         cp = Strsave(dp);
473         printd = 1;
474         return dgoto(cp);
475     }
476     (void) strcpy(ebuf, short2str(cp));
477     xfree((ptr_t) cp);
478     stderror(ERR_SYSTEM, ebuf, strerror(serrno));
479     return (NULL);
480 }
481
482
483 /*
484  * dopushd - push new directory onto directory stack.
485  *      with no arguments exchange top and second.
486  *      with numeric argument (+n) bring it to top.
487  */
488 void
489 /*ARGSUSED*/
490 dopushd(v, t)
491     Char **v;
492     struct command *t;
493 {
494     register struct directory *dp;
495
496     skipargs(&v, " [<dir>|+<n>]");
497     printd = 1;
498     if (*v == NULL) {
499         char   *tmp;
500
501         if ((dp = dcwd->di_prev) == &dhead)
502             dp = dhead.di_prev;
503         if (dp == dcwd)
504             stderror(ERR_NAME | ERR_NODIR);
505         if (chdir(tmp = short2str(dp->di_name)) < 0)
506             stderror(ERR_SYSTEM, tmp, strerror(errno));
507         dp->di_prev->di_next = dp->di_next;
508         dp->di_next->di_prev = dp->di_prev;
509         dp->di_next = dcwd->di_next;
510         dp->di_prev = dcwd;
511         dcwd->di_next->di_prev = dp;
512         dcwd->di_next = dp;
513     }
514     else if (v[1] != NULL) {
515         stderror(ERR_NAME | ERR_TOOMANY);
516         /* NOTREACHED */
517         return;
518     }
519     else if ((dp = dfind(*v)) != NULL) {
520         char   *tmp;
521
522         if (chdir(tmp = short2str(dp->di_name)) < 0)
523             stderror(ERR_SYSTEM, tmp, strerror(errno));
524     }
525     else {
526         register Char *ccp;
527
528         ccp = dfollow(*v);
529         dp = (struct directory *) xcalloc(sizeof(struct directory), 1);
530         dp->di_name = ccp;
531         dp->di_count = 0;
532         dp->di_prev = dcwd;
533         dp->di_next = dcwd->di_next;
534         dcwd->di_next = dp;
535         dp->di_next->di_prev = dp;
536     }
537     dnewcwd(dp);
538 }
539
540 /*
541  * dfind - find a directory if specified by numeric (+n) argument
542  */
543 static struct directory *
544 dfind(cp)
545     register Char *cp;
546 {
547     register struct directory *dp;
548     register int i;
549     register Char *ep;
550
551     if (*cp++ != '+')
552         return (0);
553     for (ep = cp; Isdigit(*ep); ep++)
554         continue;
555     if (*ep)
556         return (0);
557     i = getn(cp);
558     if (i <= 0)
559         return (0);
560     for (dp = dcwd; i != 0; i--) {
561         if ((dp = dp->di_prev) == &dhead)
562             dp = dp->di_prev;
563         if (dp == dcwd)
564             stderror(ERR_NAME | ERR_DEEP);
565     }
566     return (dp);
567 }
568
569 /*
570  * dopopd - pop a directory out of the directory stack
571  *      with a numeric argument just discard it.
572  */
573 void
574 /*ARGSUSED*/
575 dopopd(v, t)
576     Char **v;
577     struct command *t;
578 {
579     register struct directory *dp, *p = NULL;
580
581     skipargs(&v, " [+<n>]");
582     printd = 1;
583     if (*v == NULL)
584         dp = dcwd;
585     else if (v[1] != NULL) {
586         stderror(ERR_NAME | ERR_TOOMANY);
587         /* NOTREACHED */
588         return;
589     }
590     else if ((dp = dfind(*v)) == 0)
591         stderror(ERR_NAME | ERR_BADDIR);
592     if (dp->di_prev == &dhead && dp->di_next == &dhead)
593         stderror(ERR_NAME | ERR_EMPTY);
594     if (dp == dcwd) {
595         char   *tmp;
596
597         if ((p = dp->di_prev) == &dhead)
598             p = dhead.di_prev;
599         if (chdir(tmp = short2str(p->di_name)) < 0)
600             stderror(ERR_SYSTEM, tmp, strerror(errno));
601     }
602     dp->di_prev->di_next = dp->di_next;
603     dp->di_next->di_prev = dp->di_prev;
604     if (dp == dcwd)
605         dnewcwd(p);
606     else {
607         printdirs();
608     }
609     dfree(dp);
610 }
611
612 /*
613  * dfree - free the directory (or keep it if it still has ref count)
614  */
615 void
616 dfree(dp)
617     register struct directory *dp;
618 {
619
620     if (dp->di_count != 0) {
621         dp->di_next = dp->di_prev = 0;
622     }
623     else {
624         xfree((char *) dp->di_name);
625         xfree((ptr_t) dp);
626     }
627 }
628
629 /*
630  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
631  *      we are of course assuming that the file system is standardly
632  *      constructed (always have ..'s, directories have links)
633  */
634 Char   *
635 dcanon(cp, p)
636     register Char *cp, *p;
637 {
638     register Char *sp;
639     register Char *p1, *p2;     /* general purpose */
640     bool    slash;
641
642     Char    link[MAXPATHLEN];
643     char    tlink[MAXPATHLEN];
644     int     cc;
645     Char   *newcp;
646
647     /*
648      * christos: if the path given does not start with a slash prepend cwd. If
649      * cwd does not start with a path or the result would be too long abort().
650      */
651     if (*cp != '/') {
652         Char    tmpdir[MAXPATHLEN];
653
654         p1 = value(STRcwd);
655         if (p1 == NULL || *p1 != '/')
656             abort();
657         if (Strlen(p1) + Strlen(cp) + 1 >= MAXPATHLEN)
658             abort();
659         (void) Strcpy(tmpdir, p1);
660         (void) Strcat(tmpdir, STRslash);
661         (void) Strcat(tmpdir, cp);
662         xfree((ptr_t) cp);
663         cp = p = Strsave(tmpdir);
664     }
665
666     while (*p) {                /* for each component */
667         sp = p;                 /* save slash address */
668         while (*++p == '/')     /* flush extra slashes */
669             continue;
670         if (p != ++sp)
671             for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
672                 continue;
673         p = sp;                 /* save start of component */
674         slash = 0;
675         while (*++p)            /* find next slash or end of path */
676             if (*p == '/') {
677                 slash = 1;
678                 *p = 0;
679                 break;
680             }
681
682         if (*sp == '\0')        /* if component is null */
683             if (--sp == cp)     /* if path is one char (i.e. /) */
684                 break;
685             else
686                 *sp = '\0';
687         else if (sp[0] == '.' && sp[1] == 0) {
688             if (slash) {
689                 for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
690                     continue;
691                 p = --sp;
692             }
693             else if (--sp != cp)
694                 *sp = '\0';
695         }
696         else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
697             /*
698              * We have something like "yyy/xxx/..", where "yyy" can be null or
699              * a path starting at /, and "xxx" is a single component. Before
700              * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
701              * symbolic link.
702              */
703             *--sp = 0;          /* form the pathname for readlink */
704             if (sp != cp && !adrof(STRignore_symlinks) &&
705                 (cc = readlink(short2str(cp), tlink,
706                                sizeof tlink)) >= 0) {
707                 (void) Strcpy(link, str2short(tlink));
708                 link[cc] = '\0';
709
710                 if (slash)
711                     *p = '/';
712                 /*
713                  * Point p to the '/' in "/..", and restore the '/'.
714                  */
715                 *(p = sp) = '/';
716                 /*
717                  * find length of p
718                  */
719                 for (p1 = p; *p1++;)
720                     continue;
721                 if (*link != '/') {
722                     /*
723                      * Relative path, expand it between the "yyy/" and the
724                      * "/..". First, back sp up to the character past "yyy/".
725                      */
726                     while (*--sp != '/')
727                         continue;
728                     sp++;
729                     *sp = 0;
730                     /*
731                      * New length is "yyy/" + link + "/.." and rest
732                      */
733                     p1 = newcp = (Char *) xmalloc((size_t)
734                                                 (((sp - cp) + cc + (p1 - p)) *
735                                                  sizeof(Char)));
736                     /*
737                      * Copy new path into newcp
738                      */
739                     for (p2 = cp; (*p1++ = *p2++) != '\0';)
740                         continue;
741                     for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
742                         continue;
743                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
744                         continue;
745                     /*
746                      * Restart canonicalization at expanded "/xxx".
747                      */
748                     p = sp - cp - 1 + newcp;
749                 }
750                 else {
751                     /*
752                      * New length is link + "/.." and rest
753                      */
754                     p1 = newcp = (Char *) xmalloc((size_t)
755                                             ((cc + (p1 - p)) * sizeof(Char)));
756                     /*
757                      * Copy new path into newcp
758                      */
759                     for (p2 = link; (*p1++ = *p2++) != '\0';)
760                         continue;
761                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
762                         continue;
763                     /*
764                      * Restart canonicalization at beginning
765                      */
766                     p = newcp;
767                 }
768                 xfree((ptr_t) cp);
769                 cp = newcp;
770                 continue;       /* canonicalize the link */
771             }
772             *sp = '/';
773             if (sp != cp)
774                 while (*--sp != '/')
775                     continue;
776             if (slash) {
777                 for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
778                     continue;
779                 p = sp;
780             }
781             else if (cp == sp)
782                 *++sp = '\0';
783             else
784                 *sp = '\0';
785         }
786         else {                  /* normal dir name (not . or .. or nothing) */
787
788             if (sp != cp && adrof(STRchase_symlinks) &&
789                 !adrof(STRignore_symlinks) &&
790                 (cc = readlink(short2str(cp), tlink,
791                                sizeof tlink)) >= 0) {
792                 (void) Strcpy(link, str2short(tlink));
793                 link[cc] = '\0';
794
795                 /*
796                  * restore the '/'.
797                  */
798                 if (slash)
799                     *p = '/';
800
801                 /*
802                  * point sp to p (rather than backing up).
803                  */
804                 sp = p;
805
806                 /*
807                  * find length of p
808                  */
809                 for (p1 = p; *p1++;)
810                     continue;
811                 if (*link != '/') {
812                     /*
813                      * Relative path, expand it between the "yyy/" and the
814                      * remainder. First, back sp up to the character past
815                      * "yyy/".
816                      */
817                     while (*--sp != '/')
818                         continue;
819                     sp++;
820                     *sp = 0;
821                     /*
822                      * New length is "yyy/" + link + "/.." and rest
823                      */
824                     p1 = newcp = (Char *) xmalloc((size_t)
825                                                   (((sp - cp) + cc + (p1 - p))
826                                                    * sizeof(Char)));
827                     /*
828                      * Copy new path into newcp
829                      */
830                     for (p2 = cp; (*p1++ = *p2++) != '\0';)
831                         continue;
832                     for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
833                         continue;
834                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
835                         continue;
836                     /*
837                      * Restart canonicalization at expanded "/xxx".
838                      */
839                     p = sp - cp - 1 + newcp;
840                 }
841                 else {
842                     /*
843                      * New length is link + the rest
844                      */
845                     p1 = newcp = (Char *) xmalloc((size_t)
846                                             ((cc + (p1 - p)) * sizeof(Char)));
847                     /*
848                      * Copy new path into newcp
849                      */
850                     for (p2 = link; (*p1++ = *p2++) != '\0';)
851                         continue;
852                     for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
853                         continue;
854                     /*
855                      * Restart canonicalization at beginning
856                      */
857                     p = newcp;
858                 }
859                 xfree((ptr_t) cp);
860                 cp = newcp;
861                 continue;       /* canonicalize the link */
862             }
863             if (slash)
864                 *p = '/';
865         }
866     }
867
868     /*
869      * fix home...
870      */
871     p1 = value(STRhome);
872     cc = Strlen(p1);
873     /*
874      * See if we're not in a subdir of STRhome
875      */
876     if (p1 && *p1 == '/' &&
877         (Strncmp(p1, cp, cc) != 0 || (cp[cc] != '/' && cp[cc] != '\0'))) {
878         static ino_t home_ino = -1;
879         static dev_t home_dev = -1;
880         static Char *home_ptr = NULL;
881         struct stat statbuf;
882
883         /*
884          * Get dev and ino of STRhome
885          */
886         if (home_ptr != p1 &&
887             stat(short2str(p1), &statbuf) != -1) {
888             home_dev = statbuf.st_dev;
889             home_ino = statbuf.st_ino;
890             home_ptr = p1;
891         }
892         /*
893          * Start comparing dev & ino backwards
894          */
895         p2 = Strcpy(link, cp);
896         for (sp = NULL; *p2 && stat(short2str(p2), &statbuf) != -1;) {
897             if (statbuf.st_dev == home_dev &&
898                 statbuf.st_ino == home_ino) {
899                 sp = (Char *) - 1;
900                 break;
901             }
902             if ((sp = Strrchr(p2, '/')) != NULL)
903                 *sp = '\0';
904         }
905         /*
906          * See if we found it
907          */
908         if (*p2 && sp == (Char *) -1) {
909             /*
910              * Use STRhome to make '~' work
911              */
912             newcp = Strspl(p1, cp + Strlen(p2));
913             xfree((ptr_t) cp);
914             cp = newcp;
915         }
916     }
917     return cp;
918 }
919
920
921 /*
922  * dnewcwd - make a new directory in the loop the current one
923  */
924 static void
925 dnewcwd(dp)
926     register struct directory *dp;
927 {
928     dcwd = dp;
929     dset(dcwd->di_name);
930     if (printd && !(adrof(STRpushdsilent)))
931         printdirs();
932 }