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