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