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