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