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