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