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