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