xref: /original-bsd/bin/sh/cd.c (revision b3c06cab)
1 /*-
2  * Copyright (c) 1991, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kenneth Almquist.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 static char sccsid[] = "@(#)cd.c	8.2 (Berkeley) 05/04/95";
13 #endif /* not lint */
14 
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <errno.h>
20 
21 /*
22  * The cd and pwd commands.
23  */
24 
25 #include "shell.h"
26 #include "var.h"
27 #include "nodes.h"	/* for jobs.h */
28 #include "jobs.h"
29 #include "options.h"
30 #include "output.h"
31 #include "memalloc.h"
32 #include "error.h"
33 #include "redir.h"
34 #include "mystring.h"
35 #include "show.h"
36 
37 STATIC int docd __P((char *, int));
38 STATIC char *getcomponent __P((void));
39 STATIC void updatepwd __P((char *));
40 STATIC void getpwd __P((void));
41 
42 char *curdir;			/* current working directory */
43 char *prevdir;			/* previous working directory */
44 STATIC char *cdcomppath;
45 
46 int
47 cdcmd(argc, argv)
48 	int argc;
49 	char **argv;
50 {
51 	char *dest;
52 	char *path;
53 	char *p;
54 	struct stat statb;
55 	char *padvance();
56 	int print = 0;
57 
58 	nextopt(nullstr);
59 	if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
60 		error("HOME not set");
61 	if (dest[0] == '-' && dest[1] == '\0') {
62 		dest = prevdir ? prevdir : curdir;
63 		print = 1;
64 	}
65 	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
66 		path = nullstr;
67 	while ((p = padvance(&path, dest)) != NULL) {
68 		if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
69 			if (!print) {
70 				/*
71 				 * XXX - rethink
72 				 */
73 				if (p[0] == '.' && p[1] == '/')
74 					p += 2;
75 				print = strcmp(p, dest);
76 			}
77 			if (docd(p, print) >= 0)
78 				return 0;
79 
80 		}
81 	}
82 	error("can't cd to %s", dest);
83 	/*NOTREACHED*/
84 	return 0;
85 }
86 
87 
88 /*
89  * Actually do the chdir.  If the name refers to symbolic links, we
90  * compute the actual directory name before doing the cd.  In an
91  * interactive shell, print the directory name if "print" is nonzero
92  * or if the name refers to a symbolic link.  We also print the name
93  * if "/u/logname" was expanded in it, since this is similar to a
94  * symbolic link.  (The check for this breaks if the user gives the
95  * cd command some additional, unused arguments.)
96  */
97 
98 #if SYMLINKS == 0
99 STATIC int
100 docd(dest, print)
101 	char *dest;
102 	{
103 	INTOFF;
104 	if (chdir(dest) < 0) {
105 		INTON;
106 		return -1;
107 	}
108 	updatepwd(dest);
109 	INTON;
110 	if (print && iflag)
111 		out1fmt("%s\n", stackblock());
112 	return 0;
113 }
114 
115 #else
116 
117 
118 
119 STATIC int
120 docd(dest, print)
121 	char *dest;
122 	int print;
123 {
124 	register char *p;
125 	register char *q;
126 	char *symlink;
127 	char *component;
128 	struct stat statb;
129 	int first;
130 	int i;
131 
132 	TRACE(("docd(\"%s\", %d) called\n", dest, print));
133 
134 top:
135 	cdcomppath = dest;
136 	STARTSTACKSTR(p);
137 	if (*dest == '/') {
138 		STPUTC('/', p);
139 		cdcomppath++;
140 	}
141 	first = 1;
142 	while ((q = getcomponent()) != NULL) {
143 		if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
144 			continue;
145 		if (! first)
146 			STPUTC('/', p);
147 		first = 0;
148 		component = q;
149 		while (*q)
150 			STPUTC(*q++, p);
151 		if (equal(component, ".."))
152 			continue;
153 		STACKSTRNUL(p);
154 		if (lstat(stackblock(), &statb) < 0)
155 			error("lstat %s failed", stackblock());
156 		if (!S_ISLNK(statb.st_mode))
157 			continue;
158 
159 		/* Hit a symbolic link.  We have to start all over again. */
160 		print = 1;
161 		STPUTC('\0', p);
162 		symlink = grabstackstr(p);
163 		i = (int)statb.st_size + 2;		/* 2 for '/' and '\0' */
164 		if (cdcomppath != NULL)
165 			i += strlen(cdcomppath);
166 		p = stalloc(i);
167 		if (readlink(symlink, p, (int)statb.st_size) < 0) {
168 			error("readlink %s failed", stackblock());
169 		}
170 		if (cdcomppath != NULL) {
171 			p[(int)statb.st_size] = '/';
172 			scopy(cdcomppath, p + (int)statb.st_size + 1);
173 		} else {
174 			p[(int)statb.st_size] = '\0';
175 		}
176 		if (p[0] != '/') {	/* relative path name */
177 			char *r;
178 			q = r = symlink;
179 			while (*q) {
180 				if (*q++ == '/')
181 					r = q;
182 			}
183 			*r = '\0';
184 			dest = stalloc(strlen(symlink) + strlen(p) + 1);
185 			scopy(symlink, dest);
186 			strcat(dest, p);
187 		} else {
188 			dest = p;
189 		}
190 		goto top;
191 	}
192 	STPUTC('\0', p);
193 	p = grabstackstr(p);
194 	INTOFF;
195 	if (chdir(p) < 0) {
196 		INTON;
197 		return -1;
198 	}
199 	updatepwd(p);
200 	INTON;
201 	if (print && iflag)
202 		out1fmt("%s\n", p);
203 	return 0;
204 }
205 #endif /* SYMLINKS */
206 
207 
208 
209 /*
210  * Get the next component of the path name pointed to by cdcomppath.
211  * This routine overwrites the string pointed to by cdcomppath.
212  */
213 
214 STATIC char *
215 getcomponent() {
216 	register char *p;
217 	char *start;
218 
219 	if ((p = cdcomppath) == NULL)
220 		return NULL;
221 	start = cdcomppath;
222 	while (*p != '/' && *p != '\0')
223 		p++;
224 	if (*p == '\0') {
225 		cdcomppath = NULL;
226 	} else {
227 		*p++ = '\0';
228 		cdcomppath = p;
229 	}
230 	return start;
231 }
232 
233 
234 
235 /*
236  * Update curdir (the name of the current directory) in response to a
237  * cd command.  We also call hashcd to let the routines in exec.c know
238  * that the current directory has changed.
239  */
240 
241 void hashcd();
242 
243 STATIC void
244 updatepwd(dir)
245 	char *dir;
246 	{
247 	char *new;
248 	char *p;
249 
250 	hashcd();				/* update command hash table */
251 	cdcomppath = stalloc(strlen(dir) + 1);
252 	scopy(dir, cdcomppath);
253 	STARTSTACKSTR(new);
254 	if (*dir != '/') {
255 		if (curdir == NULL)
256 			return;
257 		p = curdir;
258 		while (*p)
259 			STPUTC(*p++, new);
260 		if (p[-1] == '/')
261 			STUNPUTC(new);
262 	}
263 	while ((p = getcomponent()) != NULL) {
264 		if (equal(p, "..")) {
265 			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
266 		} else if (*p != '\0' && ! equal(p, ".")) {
267 			STPUTC('/', new);
268 			while (*p)
269 				STPUTC(*p++, new);
270 		}
271 	}
272 	if (new == stackblock())
273 		STPUTC('/', new);
274 	STACKSTRNUL(new);
275 	INTOFF;
276 	if (prevdir)
277 		ckfree(prevdir);
278 	prevdir = curdir;
279 	curdir = savestr(stackblock());
280 	INTON;
281 }
282 
283 
284 
285 int
286 pwdcmd(argc, argv)
287 	int argc;
288 	char **argv;
289 {
290 	getpwd();
291 	out1str(curdir);
292 	out1c('\n');
293 	return 0;
294 }
295 
296 
297 
298 /*
299  * Run /bin/pwd to find out what the current directory is.  We suppress
300  * interrupts throughout most of this, but the user can still break out
301  * of it by killing the pwd program.  If we already know the current
302  * directory, this routine returns immediately.
303  */
304 
305 #define MAXPWD 256
306 
307 STATIC void
308 getpwd() {
309 	char buf[MAXPWD];
310 	char *p;
311 	int i;
312 	int status;
313 	struct job *jp;
314 	int pip[2];
315 
316 	if (curdir)
317 		return;
318 	INTOFF;
319 	if (pipe(pip) < 0)
320 		error("Pipe call failed");
321 	jp = makejob((union node *)NULL, 1);
322 	if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
323 		close(pip[0]);
324 		if (pip[1] != 1) {
325 			close(1);
326 			copyfd(pip[1], 1);
327 			close(pip[1]);
328 		}
329 		execl("/bin/pwd", "pwd", (char *)0);
330 		error("Cannot exec /bin/pwd");
331 	}
332 	close(pip[1]);
333 	pip[1] = -1;
334 	p = buf;
335 	while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0
336 	     || (i == -1 && errno == EINTR)) {
337 		if (i > 0)
338 			p += i;
339 	}
340 	close(pip[0]);
341 	pip[0] = -1;
342 	status = waitforjob(jp);
343 	if (status != 0)
344 		error((char *)0);
345 	if (i < 0 || p == buf || p[-1] != '\n')
346 		error("pwd command failed");
347 	p[-1] = '\0';
348 	curdir = savestr(buf);
349 	INTON;
350 }
351