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