xref: /original-bsd/bin/csh/dir.c (revision 963f8367)
1 /*-
2  * Copyright (c) 1980, 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)dir.c	5.5 (Berkeley) 04/04/91";
10 #endif /* not lint */
11 
12 #include "sh.h"
13 #include "sh.dir.h"
14 
15 /*
16  * C Shell - directory management
17  */
18 
19 struct	directory *dfind();
20 char	*dfollow();
21 char	*dcanon();
22 struct	directory dhead;		/* "head" of loop */
23 int	printd;				/* force name to be printed */
24 static	char *fakev[] = { "dirs", NOSTR };
25 
26 /*
27  * dinit - initialize current working directory
28  */
29 dinit(hp)
30 	char *hp;
31 {
32 	register char *cp;
33 	register struct directory *dp;
34 	char path[MAXPATHLEN];
35 
36 	if (loginsh && hp)
37 		cp = hp;
38 	else {
39 		cp = getwd(path);
40 		if (cp == NULL) {
41 #define	WDERR	"csh: can't get current directory.\n"
42 			(void) write(SHDIAG, WDERR, strlen(WDERR));
43 			exit(1);
44 		}
45 	}
46 	dp = (struct directory *)calloc(sizeof (struct directory), 1);
47 	dp->di_name = savestr(cp);
48 	dp->di_count = 0;
49 	dhead.di_next = dhead.di_prev = dp;
50 	dp->di_next = dp->di_prev = &dhead;
51 	printd = 0;
52 	dnewcwd(dp);
53 }
54 
55 /*
56  * dodirs - list all directories in directory loop
57  */
58 dodirs(v)
59 	char **v;
60 {
61 	register struct directory *dp;
62 	bool lflag;
63 	char *hp = value("home");
64 
65 	if (*hp == '\0')
66 		hp = NOSTR;
67 	if (*++v != NOSTR)
68 		if (eq(*v, "-l") && *++v == NOSTR)
69 			lflag = 1;
70 		else
71 			error("Usage: dirs [ -l ]");
72 	else
73 		lflag = 0;
74 	dp = dcwd;
75 	do {
76 		if (dp == &dhead)
77 			continue;
78 		if (!lflag && hp != NOSTR) {
79 			dtildepr(hp, dp->di_name);
80 		} else
81 			printf("%s", dp->di_name);
82 		printf(" ");
83 	} while ((dp = dp->di_prev) != dcwd);
84 	printf("\n");
85 }
86 
87 dtildepr(home, dir)
88 	register char *home, *dir;
89 {
90 
91 	if (!eq(home, "/") && prefix(home, dir))
92 		printf("~%s", dir + strlen(home));
93 	else
94 		printf("%s", dir);
95 }
96 
97 /*
98  * dochngd - implement chdir command.
99  */
100 dochngd(v)
101 	char **v;
102 {
103 	register char *cp;
104 	register struct directory *dp;
105 
106 	printd = 0;
107 	if (*++v == NOSTR) {
108 		if ((cp = value("home")) == NOSTR || *cp == 0)
109 			bferr("No home directory");
110 		if (chdir(cp) < 0)
111 			bferr("Can't change to home directory");
112 		cp = savestr(cp);
113 	} else if ((dp = dfind(*v)) != 0) {
114 		printd = 1;
115 		if (chdir(dp->di_name) < 0)
116 			Perror(dp->di_name);
117 		dcwd->di_prev->di_next = dcwd->di_next;
118 		dcwd->di_next->di_prev = dcwd->di_prev;
119 		goto flushcwd;
120 	} else
121 		cp = dfollow(*v);
122 	dp = (struct directory *)calloc(sizeof (struct directory), 1);
123 	dp->di_name = cp;
124 	dp->di_count = 0;
125 	dp->di_next = dcwd->di_next;
126 	dp->di_prev = dcwd->di_prev;
127 	dp->di_prev->di_next = dp;
128 	dp->di_next->di_prev = dp;
129 flushcwd:
130 	dfree(dcwd);
131 	dnewcwd(dp);
132 }
133 
134 /*
135  * dfollow - change to arg directory; fall back on cdpath if not valid
136  */
137 char *
138 dfollow(cp)
139 	register char *cp;
140 {
141 	register char *dp;
142 	struct varent *c;
143 
144 	cp = globone(cp);
145 	if (chdir(cp) >= 0)
146 		goto gotcha;
147 	if (cp[0] != '/' && !prefix("./", cp) && !prefix("../", cp)
148 	    && (c = adrof("cdpath"))) {
149 		char **cdp;
150 		register char *p;
151 		char buf[MAXPATHLEN];
152 
153 		for (cdp = c->vec; *cdp; cdp++) {
154 			for (dp = buf, p = *cdp; *dp++ = *p++;)
155 				;
156 			dp[-1] = '/';
157 			for (p = cp; *dp++ = *p++;)
158 				;
159 			if (chdir(buf) >= 0) {
160 				printd = 1;
161 				xfree(cp);
162 				cp = savestr(buf);
163 				goto gotcha;
164 			}
165 		}
166 	}
167 	dp = value(cp);
168 	if ((dp[0] == '/' || dp[0] == '.') && chdir(dp) >= 0) {
169 		xfree(cp);
170 		cp = savestr(dp);
171 		printd = 1;
172 		goto gotcha;
173 	}
174 	xfree(cp);			/* XXX, use after free */
175 	Perror(cp);
176 
177 gotcha:
178 	if (*cp != '/') {
179 		register char *p, *q;
180 		int cwdlen;
181 
182 		/*
183 		 * All in the name of efficiency?
184 		 */
185 		for (p = dcwd->di_name; *p++;)
186 			;
187 		if ((cwdlen = p - dcwd->di_name - 1) == 1)	/* root */
188 			cwdlen = 0;
189 		for (p = cp; *p++;)
190 			;
191 		dp = xalloc((unsigned) (cwdlen + (p - cp) + 1));
192 		for (p = dp, q = dcwd->di_name; *p++ = *q++;)
193 			;
194 		if (cwdlen)
195 			p[-1] = '/';
196 		else
197 			p--;			/* don't add a / after root */
198 		for (q = cp; *p++ = *q++;)
199 			;
200 		xfree(cp);
201 		cp = dp;
202 		dp += cwdlen;
203 	} else
204 		dp = cp;
205 	return dcanon(cp, dp);
206 }
207 
208 /*
209  * dopushd - push new directory onto directory stack.
210  *	with no arguments exchange top and second.
211  *	with numeric argument (+n) bring it to top.
212  */
213 dopushd(v)
214 	char **v;
215 {
216 	register struct directory *dp;
217 
218 	printd = 1;
219 	if (*++v == NOSTR) {
220 		if ((dp = dcwd->di_prev) == &dhead)
221 			dp = dhead.di_prev;
222 		if (dp == dcwd)
223 			bferr("No other directory");
224 		if (chdir(dp->di_name) < 0)
225 			Perror(dp->di_name);
226 		dp->di_prev->di_next = dp->di_next;
227 		dp->di_next->di_prev = dp->di_prev;
228 		dp->di_next = dcwd->di_next;
229 		dp->di_prev = dcwd;
230 		dcwd->di_next->di_prev = dp;
231 		dcwd->di_next = dp;
232 	} else if (dp = dfind(*v)) {
233 		if (chdir(dp->di_name) < 0)
234 			Perror(dp->di_name);
235 	} else {
236 		register char *cp;
237 
238 		cp = dfollow(*v);
239 		dp = (struct directory *)calloc(sizeof (struct directory), 1);
240 		dp->di_name = cp;
241 		dp->di_count = 0;
242 		dp->di_prev = dcwd;
243 		dp->di_next = dcwd->di_next;
244 		dcwd->di_next = dp;
245 		dp->di_next->di_prev = dp;
246 	}
247 	dnewcwd(dp);
248 }
249 
250 /*
251  * dfind - find a directory if specified by numeric (+n) argument
252  */
253 struct directory *
254 dfind(cp)
255 	register char *cp;
256 {
257 	register struct directory *dp;
258 	register int i;
259 	register char *ep;
260 
261 	if (*cp++ != '+')
262 		return (0);
263 	for (ep = cp; digit(*ep); ep++)
264 		continue;
265 	if (*ep)
266 		return (0);
267 	i = getn(cp);
268 	if (i <= 0)
269 		return (0);
270 	for (dp = dcwd; i != 0; i--) {
271 		if ((dp = dp->di_prev) == &dhead)
272 			dp = dp->di_prev;
273 		if (dp == dcwd)
274 			bferr("Directory stack not that deep");
275 	}
276 	return (dp);
277 }
278 
279 /*
280  * dopopd - pop a directory out of the directory stack
281  *	with a numeric argument just discard it.
282  */
283 dopopd(v)
284 	char **v;
285 {
286 	register struct directory *dp, *p;
287 
288 	printd = 1;
289 	if (*++v == NOSTR)
290 		dp = dcwd;
291 	else if ((dp = dfind(*v)) == 0)
292 		bferr("Bad directory");
293 	if (dp->di_prev == &dhead && dp->di_next == &dhead)
294 		bferr("Directory stack empty");
295 	if (dp == dcwd) {
296 		if ((p = dp->di_prev) == &dhead)
297 			p = dhead.di_prev;
298 		if (chdir(p->di_name) < 0)
299 			Perror(p->di_name);
300 	}
301 	dp->di_prev->di_next = dp->di_next;
302 	dp->di_next->di_prev = dp->di_prev;
303 	if (dp == dcwd)
304 		dnewcwd(p);
305 	else
306 		dodirs(fakev);
307 	dfree(dp);
308 }
309 
310 /*
311  * dfree - free the directory (or keep it if it still has ref count)
312  */
313 dfree(dp)
314 	register struct directory *dp;
315 {
316 
317 	if (dp->di_count != 0)
318 		dp->di_next = dp->di_prev = 0;
319 	else
320 		xfree(dp->di_name), xfree((char *)dp);
321 }
322 
323 /*
324  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
325  *	we are of course assuming that the file system is standardly
326  *	constructed (always have ..'s, directories have links)
327  */
328 char *
329 dcanon(cp, p)
330 	register char *cp, *p;
331 {
332 	register char *sp;
333 	register char *p1, *p2;		/* general purpose */
334 	bool slash;
335 
336 	if (*cp != '/')
337 		abort();
338 	while (*p) {			/* for each component */
339 		sp = p;			/* save slash address */
340 		while (*++p == '/')	/* flush extra slashes */
341 			;
342 		if (p != ++sp)
343 			for (p1 = sp, p2 = p; *p1++ = *p2++;)
344 				;
345 		p = sp;			/* save start of component */
346 		slash = 0;
347 		while (*++p)		/* find next slash or end of path */
348 			if (*p == '/') {
349 				slash = 1;
350 				*p = 0;
351 				break;
352 			}
353 		if (*sp == '\0')	/* if component is null */
354 			if (--sp == cp)	/* if path is one char (i.e. /) */
355 				break;
356 			else
357 				*sp = '\0';
358 		else if (sp[0] == '.' && sp[1] == 0) {
359 			if (slash) {
360 				for (p1 = sp, p2 = p + 1; *p1++ = *p2++;)
361 					;
362 				p = --sp;
363 			} else if (--sp != cp)
364 				*sp = '\0';
365 		} else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
366 			char link[MAXPATHLEN];
367 			int cc;
368 			char *newcp;
369 
370 			/*
371 			 * We have something like "yyy/xxx/..", where "yyy"
372 			 * can be null or a path starting at /, and "xxx"
373 			 * is a single component.
374 			 * Before compressing "xxx/..", we want to expand
375 			 * "yyy/xxx", if it is a symbolic link.
376 			 */
377 			*--sp = 0;	/* form the pathname for readlink */
378 			if (sp != cp &&
379 			    (cc = readlink(cp, link, sizeof link)) >= 0) {
380 				link[cc] = '\0';
381 				if (slash)
382 					*p = '/';
383 				/*
384 				 * Point p to the '/' in "/..", and restore
385 				 * the '/'.
386 				 */
387 				*(p = sp) = '/';
388 				/*
389 				 * find length of p
390 				 */
391 				for (p1 = p; *p1++;)
392 					;
393 				if (*link != '/') {
394 					/*
395 					 * Relative path, expand it between
396 					 * the "yyy/" and the "/..".
397 					 * First, back sp up to the character
398 					 * past "yyy/".
399 					 */
400 					while (*--sp != '/')
401 						;
402 					sp++;
403 					*sp = 0;
404 					/*
405 					 * New length is
406 					 * "yyy/" + link + "/.." and rest
407 					 */
408 					p1 = newcp = xalloc((unsigned)
409 						((sp - cp) + cc + (p1 - p)));
410 					/*
411 					 * Copy new path into newcp
412 					 */
413 					for (p2 = cp; *p1++ = *p2++;)
414 						;
415 					for (p1--, p2 = link; *p1++ = *p2++;)
416 						;
417 					for (p1--, p2 = p; *p1++ = *p2++;)
418 						;
419 					/*
420 					 * Restart canonicalization at
421 					 * expanded "/xxx".
422 					 */
423 					p = sp - cp - 1 + newcp;
424 				} else {
425 					/*
426 					 * New length is link + "/.." and rest
427 					 */
428 					p1 = newcp = xalloc((unsigned)
429 						(cc + (p1 - p)));
430 					/*
431 					 * Copy new path into newcp
432 					 */
433 					for (p2 = link; *p1++ = *p2++;)
434 						;
435 					for (p1--, p2 = p; *p1++ = *p2++;)
436 						;
437 					/*
438 					 * Restart canonicalization at beginning
439 					 */
440 					p = newcp;
441 				}
442 				xfree(cp);
443 				cp = newcp;
444 				continue;	/* canonicalize the link */
445 			}
446 			*sp = '/';
447 			if (sp != cp)
448 				while (*--sp != '/')
449 					;
450 			if (slash) {
451 				for (p1 = sp + 1, p2 = p + 1; *p1++ = *p2++;)
452 					;
453 				p = sp;
454 			} else if (cp == sp)
455 				*++sp = '\0';
456 			else
457 				*sp = '\0';
458 		} else if (slash)
459 			*p = '/';
460 	}
461 	return cp;
462 }
463 
464 /*
465  * dnewcwd - make a new directory in the loop the current one
466  */
467 dnewcwd(dp)
468 	register struct directory *dp;
469 {
470 
471 	dcwd = dp;
472 	set("cwd", savestr(dcwd->di_name));
473 	if (printd)
474 		dodirs(fakev);
475 }
476