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