dir.c revision 1.29
1/* $NetBSD: dir.c,v 1.29 2007/07/16 18:26:09 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
35static char sccsid[] = "@(#)dir.c	8.1 (Berkeley) 5/31/93";
36#else
37__RCSID("$NetBSD: dir.c,v 1.29 2007/07/16 18:26:09 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
56static struct directory *dfind(Char *);
57static Char *dfollow(Char *);
58static void printdirs(void);
59static Char *dgoto(Char *);
60static void skipargs(Char ***, const char *);
61static void dnewcwd(struct directory *);
62static void dset(Char *);
63
64struct directory dhead;		/* "head" of loop */
65int printd;			/* force name to be printed */
66
67static int dirflag = 0;
68
69/*
70 * dinit - initialize current working directory
71 */
72void
73dinit(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
138static void
139dset(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 = (Char **)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
159static void
160skipargs(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 */
188void
189/*ARGSUSED*/
190dodirs(Char **v, struct command *t)
191{
192    skipargs(&v, "");
193
194    if (*v != NULL)
195	stderror(ERR_DIRUS, "dirs", "");
196    printdirs();
197}
198
199static void
200printdirs(void)
201{
202    struct directory *dp;
203    Char *hp, *s;
204    int 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, "%d\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
238void
239dtildepr(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
247void
248dtilde(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 */
267Char *
268dnormalize(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	int     dotdot = 0;
278	Char   *dp, *cwd;
279
280	cwd = (Char *)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 */
332void
333/*ARGSUSED*/
334dochngd(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
375static Char *
376dgoto(Char *cp)
377{
378    Char *dp;
379
380    if (*cp != '/') {
381	Char *p, *q;
382	int cwdlen;
383
384	for (p = dcwd->di_name; *p++;)
385	    continue;
386	if ((cwdlen = p - dcwd->di_name - 1) == 1)	/* root */
387	    cwdlen = 0;
388	for (p = cp; *p++;)
389	    continue;
390	dp = (Char *)xmalloc((size_t)((cwdlen + (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 */
413static Char *
414dfollow(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 */
475void
476/*ARGSUSED*/
477dopushd(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 */
525static struct directory *
526dfind(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 */
554void
555/*ARGSUSED*/
556dopopd(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 */
591void
592dfree(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 */
609Char *
610dcanon(Char *cp, Char *p)
611{
612    Char slink[MAXPATHLEN];
613    char tlink[MAXPATHLEN];
614    Char *newcp, *sp;
615    Char *p1, *p2;	/* general purpose */
616    int cc;
617    int slash;
618
619    /*
620     * christos: if the path given does not start with a slash prepend cwd. If
621     * cwd does not start with a path or the result would be too long abort().
622     */
623    if (*cp != '/') {
624	Char tmpdir[MAXPATHLEN];
625
626	p1 = value(STRcwd);
627	if (p1 == NULL || *p1 != '/')
628	    abort();
629	if (Strlen(p1) + Strlen(cp) + 1 >= MAXPATHLEN)
630	    abort();
631	(void)Strcpy(tmpdir, p1);
632	(void)Strcat(tmpdir, STRslash);
633	(void)Strcat(tmpdir, cp);
634	xfree((ptr_t) cp);
635	cp = p = Strsave(tmpdir);
636    }
637
638    while (*p) {		/* for each component */
639	sp = p;			/* save slash address */
640	while (*++p == '/')	/* flush extra slashes */
641	    continue;
642	if (p != ++sp)
643	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
644		continue;
645	p = sp;			/* save start of component */
646	slash = 0;
647	while (*++p)		/* find next slash or end of path */
648	    if (*p == '/') {
649		slash = 1;
650		*p = 0;
651		break;
652	    }
653
654	if (*sp == '\0') {	/* if component is null */
655	    if (--sp == cp)	/* if path is one char (i.e. /) */
656		break;
657	    else
658		*sp = '\0';
659	} else if (sp[0] == '.' && sp[1] == 0) {
660	    if (slash) {
661		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
662		    continue;
663		p = --sp;
664	    }
665	    else if (--sp != cp)
666		*sp = '\0';
667	}
668	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
669	    /*
670	     * We have something like "yyy/xxx/..", where "yyy" can be null or
671	     * a path starting at /, and "xxx" is a single component. Before
672	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
673	     * symbolic link.
674	     */
675	    *--sp = 0;		/* form the pathname for readlink */
676	    if (sp != cp && !adrof(STRignore_symlinks) &&
677		(cc = readlink(short2str(cp), tlink,
678			       sizeof(tlink) - 1)) >= 0) {
679		tlink[cc] = '\0';
680		(void)Strcpy(slink, str2short(tlink));
681
682		if (slash)
683		    *p = '/';
684		/*
685		 * Point p to the '/' in "/..", and restore the '/'.
686		 */
687		*(p = sp) = '/';
688		/*
689		 * find length of p
690		 */
691		for (p1 = p; *p1++;)
692		    continue;
693		if (*slink != '/') {
694		    /*
695		     * Relative path, expand it between the "yyy/" and the
696		     * "/..". First, back sp up to the character past "yyy/".
697		     */
698		    while (*--sp != '/')
699			continue;
700		    sp++;
701		    *sp = 0;
702		    /*
703		     * New length is "yyy/" + slink + "/.." and rest
704		     */
705		    p1 = newcp = (Char *)xmalloc(
706		        (size_t)(((sp - cp) + cc + (p1 - p)) * sizeof(Char)));
707		    /*
708		     * Copy new path into newcp
709		     */
710		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
711			continue;
712		    for (p1--, p2 = slink; (*p1++ = *p2++) != '\0';)
713			continue;
714		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
715			continue;
716		    /*
717		     * Restart canonicalization at expanded "/xxx".
718		     */
719		    p = sp - cp - 1 + newcp;
720		}
721		else {
722		    /*
723		     * New length is slink + "/.." and rest
724		     */
725		    p1 = newcp = (Char *)xmalloc(
726		        (size_t)((cc + (p1 - p)) * sizeof(Char)));
727		    /*
728		     * Copy new path into newcp
729		     */
730		    for (p2 = slink; (*p1++ = *p2++) != '\0';)
731			continue;
732		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
733			continue;
734		    /*
735		     * Restart canonicalization at beginning
736		     */
737		    p = newcp;
738		}
739		xfree((ptr_t) cp);
740		cp = newcp;
741		continue;	/* canonicalize the link */
742	    }
743	    *sp = '/';
744	    if (sp != cp)
745		while (*--sp != '/')
746		    continue;
747	    if (slash) {
748		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
749		    continue;
750		p = sp;
751	    }
752	    else if (cp == sp)
753		*++sp = '\0';
754	    else
755		*sp = '\0';
756	}
757	else {			/* normal dir name (not . or .. or nothing) */
758
759	    if (sp != cp && adrof(STRchase_symlinks) &&
760		!adrof(STRignore_symlinks) &&
761		(cc = readlink(short2str(cp), tlink, sizeof(tlink)-1)) >= 0) {
762		tlink[cc] = '\0';
763		(void)Strcpy(slink, str2short(tlink));
764
765		/*
766		 * restore the '/'.
767		 */
768		if (slash)
769		    *p = '/';
770
771		/*
772		 * point sp to p (rather than backing up).
773		 */
774		sp = p;
775
776		/*
777		 * find length of p
778		 */
779		for (p1 = p; *p1++;)
780		    continue;
781		if (*slink != '/') {
782		    /*
783		     * Relative path, expand it between the "yyy/" and the
784		     * remainder. First, back sp up to the character past
785		     * "yyy/".
786		     */
787		    while (*--sp != '/')
788			continue;
789		    sp++;
790		    *sp = 0;
791		    /*
792		     * New length is "yyy/" + slink + "/.." and rest
793		     */
794		    p1 = newcp = (Char *)xmalloc(
795		        (size_t)(((sp - cp) + cc + (p1 - p)) * sizeof(Char)));
796		    /*
797		     * Copy new path into newcp
798		     */
799		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
800			continue;
801		    for (p1--, p2 = slink; (*p1++ = *p2++) != '\0';)
802			continue;
803		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
804			continue;
805		    /*
806		     * Restart canonicalization at expanded "/xxx".
807		     */
808		    p = sp - cp - 1 + newcp;
809		}
810		else {
811		    /*
812		     * New length is slink + the rest
813		     */
814		    p1 = newcp = (Char *)xmalloc(
815		        (size_t)((cc + (p1 - p)) * sizeof(Char)));
816		    /*
817		     * Copy new path into newcp
818		     */
819		    for (p2 = slink; (*p1++ = *p2++) != '\0';)
820			continue;
821		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
822			continue;
823		    /*
824		     * Restart canonicalization at beginning
825		     */
826		    p = newcp;
827		}
828		xfree((ptr_t) cp);
829		cp = newcp;
830		continue;	/* canonicalize the link */
831	    }
832	    if (slash)
833		*p = '/';
834	}
835    }
836
837    /*
838     * fix home...
839     */
840    p1 = value(STRhome);
841    cc = Strlen(p1);
842    /*
843     * See if we're not in a subdir of STRhome
844     */
845    if (p1 && *p1 == '/' &&
846	(Strncmp(p1, cp, cc) != 0 || (cp[cc] != '/' && cp[cc] != '\0'))) {
847	static ino_t home_ino;
848	static dev_t home_dev = NODEV;
849	static Char *home_ptr = NULL;
850	struct stat statbuf;
851
852	/*
853	 * Get dev and ino of STRhome
854	 */
855	if (home_ptr != p1 &&
856	    stat(short2str(p1), &statbuf) != -1) {
857	    home_dev = statbuf.st_dev;
858	    home_ino = statbuf.st_ino;
859	    home_ptr = p1;
860	}
861	/*
862	 * Start comparing dev & ino backwards
863	 */
864	p2 = Strcpy(slink, cp);
865	for (sp = NULL; *p2 && stat(short2str(p2), &statbuf) != -1;) {
866	    if (statbuf.st_dev == home_dev &&
867		statbuf.st_ino == home_ino) {
868		sp = (Char *) - 1;
869		break;
870	    }
871	    if ((sp = Strrchr(p2, '/')) != NULL)
872		*sp = '\0';
873	}
874	/*
875	 * See if we found it
876	 */
877	if (*p2 && sp == (Char *) -1) {
878	    /*
879	     * Use STRhome to make '~' work
880	     */
881	    newcp = Strspl(p1, cp + Strlen(p2));
882	    xfree((ptr_t) cp);
883	    cp = newcp;
884	}
885    }
886    return cp;
887}
888
889
890/*
891 * dnewcwd - make a new directory in the loop the current one
892 */
893static void
894dnewcwd(struct directory *dp)
895{
896    dcwd = dp;
897    dset(dcwd->di_name);
898    if (printd && !(adrof(STRpushdsilent)))
899	printdirs();
900}
901