xref: /freebsd/bin/sh/cd.c (revision 417ed37975261df51f61d13e179ad04d8f4839c7)
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  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  *	$Id: cd.c,v 1.3 1994/11/06 01:29:26 jkh Exp $
37  */
38 
39 #ifndef lint
40 static char sccsid[] = "@(#)cd.c	8.1 (Berkeley) 5/31/93";
41 #endif /* not lint */
42 
43 /*
44  * The cd and pwd commands.
45  */
46 
47 #include "shell.h"
48 #include "var.h"
49 #include "nodes.h"	/* for jobs.h */
50 #include "jobs.h"
51 #include "options.h"
52 #include "output.h"
53 #include "memalloc.h"
54 #include "error.h"
55 #include "mystring.h"
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <sys/unistd.h>
59 #include <errno.h>
60 
61 
62 #ifdef __STDC__
63 STATIC int docd(char *, int);
64 STATIC void updatepwd(char *);
65 STATIC void getpwd(void);
66 STATIC char *getcomponent(void);
67 #else
68 STATIC int docd();
69 STATIC void updatepwd();
70 STATIC void getpwd();
71 STATIC char *getcomponent();
72 #endif
73 
74 
75 char *curdir;			/* current working directory */
76 char *prevdir;			/* previous working directory */
77 STATIC char *cdcomppath;
78 
79 int
80 cdcmd(argc, argv)  char **argv; {
81 	char *dest;
82 	char *path;
83 	char *p;
84 	struct stat statb;
85 	char *padvance();
86 	int print = 0;
87 
88 	nextopt(nullstr);
89 	if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
90 		error("HOME not set");
91 	if (*dest == '\0')
92 		dest = ".";
93 	if (dest[0] == '-' && dest[1] == '\0') {
94 		dest = prevdir ? prevdir : curdir;
95 		print = 1;
96 	}
97 	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
98 		path = nullstr;
99 	while ((p = padvance(&path, dest)) != NULL) {
100 		if (stat(p, &statb) >= 0
101 		 && (statb.st_mode & S_IFMT) == S_IFDIR) {
102 			if (!print) {
103 				/*
104 				 * XXX - rethink
105 				 */
106 				if (p[0] == '.' && p[1] == '/')
107 					p += 2;
108 				print = strcmp(p, dest);
109 			}
110 			if (docd(p, print) >= 0)
111 				return 0;
112 
113 		}
114 	}
115 	error("can't cd to %s", dest);
116 }
117 
118 
119 /*
120  * Actually do the chdir.  If the name refers to symbolic links, we
121  * compute the actual directory name before doing the cd.  In an
122  * interactive shell, print the directory name if "print" is nonzero
123  * or if the name refers to a symbolic link.  We also print the name
124  * if "/u/logname" was expanded in it, since this is similar to a
125  * symbolic link.  (The check for this breaks if the user gives the
126  * cd command some additional, unused arguments.)
127  */
128 
129 #if SYMLINKS == 0
130 STATIC int
131 docd(dest, print)
132 	char *dest;
133 	{
134 	INTOFF;
135 	if (chdir(dest) < 0) {
136 		INTON;
137 		return -1;
138 	}
139 	updatepwd(dest);
140 	INTON;
141 	if (print && iflag)
142 		out1fmt("%s\n", stackblock());
143 	return 0;
144 }
145 
146 #else
147 
148 
149 
150 STATIC int
151 docd(dest, print)
152 	char *dest;
153 	{
154 	register char *p;
155 	register char *q;
156 	char *symlink;
157 	char *component;
158 	struct stat statb;
159 	int first;
160 	int i;
161 
162 	TRACE(("docd(\"%s\", %d) called\n", dest, print));
163 
164 top:
165 	cdcomppath = dest;
166 	STARTSTACKSTR(p);
167 	if (*dest == '/') {
168 		STPUTC('/', p);
169 		cdcomppath++;
170 	}
171 	first = 1;
172 	while ((q = getcomponent()) != NULL) {
173 		if (q[0] == '\0' || q[0] == '.' && q[1] == '\0')
174 			continue;
175 		if (! first)
176 			STPUTC('/', p);
177 		first = 0;
178 		component = q;
179 		while (*q)
180 			STPUTC(*q++, p);
181 		if (equal(component, ".."))
182 			continue;
183 		STACKSTRNUL(p);
184 		if (lstat(stackblock(), &statb) < 0)
185 			error("lstat %s failed", stackblock());
186 		if ((statb.st_mode & S_IFMT) != S_IFLNK)
187 			continue;
188 
189 		/* Hit a symbolic link.  We have to start all over again. */
190 		print = 1;
191 		STPUTC('\0', p);
192 		symlink = grabstackstr(p);
193 		i = (int)statb.st_size + 2;		/* 2 for '/' and '\0' */
194 		if (cdcomppath != NULL)
195 			i += strlen(cdcomppath);
196 		p = stalloc(i);
197 		if (readlink(symlink, p, (int)statb.st_size) < 0) {
198 			error("readlink %s failed", stackblock());
199 		}
200 		if (cdcomppath != NULL) {
201 			p[(int)statb.st_size] = '/';
202 			scopy(cdcomppath, p + (int)statb.st_size + 1);
203 		} else {
204 			p[(int)statb.st_size] = '\0';
205 		}
206 		if (p[0] != '/') {	/* relative path name */
207 			char *r;
208 			q = r = symlink;
209 			while (*q) {
210 				if (*q++ == '/')
211 					r = q;
212 			}
213 			*r = '\0';
214 			dest = stalloc(strlen(symlink) + strlen(p) + 1);
215 			scopy(symlink, dest);
216 			strcat(dest, p);
217 		} else {
218 			dest = p;
219 		}
220 		goto top;
221 	}
222 	STPUTC('\0', p);
223 	p = grabstackstr(p);
224 	INTOFF;
225 	if (chdir(*p ? p : ".") < 0) {
226 		INTON;
227 		return -1;
228 	}
229 	updatepwd(p);
230 	INTON;
231 	if (print && iflag)
232 		out1fmt("%s\n", p);
233 	return 0;
234 }
235 #endif /* SYMLINKS */
236 
237 
238 
239 /*
240  * Get the next component of the path name pointed to by cdcomppath.
241  * This routine overwrites the string pointed to by cdcomppath.
242  */
243 
244 STATIC char *
245 getcomponent() {
246 	register char *p;
247 	char *start;
248 
249 	if ((p = cdcomppath) == NULL)
250 		return NULL;
251 	start = cdcomppath;
252 	while (*p != '/' && *p != '\0')
253 		p++;
254 	if (*p == '\0') {
255 		cdcomppath = NULL;
256 	} else {
257 		*p++ = '\0';
258 		cdcomppath = p;
259 	}
260 	return start;
261 }
262 
263 
264 
265 /*
266  * Update curdir (the name of the current directory) in response to a
267  * cd command.  We also call hashcd to let the routines in exec.c know
268  * that the current directory has changed.
269  */
270 
271 void hashcd();
272 
273 STATIC void
274 updatepwd(dir)
275 	char *dir;
276 	{
277 	char *new;
278 	char *p;
279 
280 	hashcd();				/* update command hash table */
281 	cdcomppath = stalloc(strlen(dir) + 1);
282 	scopy(dir, cdcomppath);
283 	STARTSTACKSTR(new);
284 	if (*dir != '/') {
285 		if (curdir == NULL)
286 			return;
287 		p = curdir;
288 		while (*p)
289 			STPUTC(*p++, new);
290 		if (p[-1] == '/')
291 			STUNPUTC(new);
292 	}
293 	while ((p = getcomponent()) != NULL) {
294 		if (equal(p, "..")) {
295 			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
296 		} else if (*p != '\0' && ! equal(p, ".")) {
297 			STPUTC('/', new);
298 			while (*p)
299 				STPUTC(*p++, new);
300 		}
301 	}
302 	if (new == stackblock())
303 		STPUTC('/', new);
304 	STACKSTRNUL(new);
305 	INTOFF;
306 	if (prevdir)
307 		ckfree(prevdir);
308 	prevdir = curdir;
309 	curdir = savestr(stackblock());
310 	INTON;
311 }
312 
313 
314 
315 int
316 pwdcmd(argc, argv)  char **argv; {
317 	getpwd();
318 	out1str(curdir);
319 	out1c('\n');
320 	return 0;
321 }
322 
323 
324 
325 /*
326  * Run /bin/pwd to find out what the current directory is.  We suppress
327  * interrupts throughout most of this, but the user can still break out
328  * of it by killing the pwd program.  If we already know the current
329  * directory, this routine returns immediately.
330  */
331 
332 #define MAXPWD 256
333 
334 STATIC void
335 getpwd() {
336 	char buf[MAXPWD];
337 	char *p;
338 	int i;
339 	int status;
340 	struct job *jp;
341 	int pip[2];
342 	char *pwd_bin = "/bin/pwd";
343 
344 	if (curdir)
345 		return;
346 	INTOFF;
347 	if (pipe(pip) < 0)
348 		error("Pipe call failed");
349 	/* make a fall-back guess, otherwise we're simply screwed */
350 	if (access(pwd_bin, X_OK) == -1)
351 		pwd_bin = "/stand/pwd";
352 	jp = makejob((union node *)NULL, 1);
353 	if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
354 		close(pip[0]);
355 		if (pip[1] != 1) {
356 			close(1);
357 			copyfd(pip[1], 1);
358 			close(pip[1]);
359 		}
360 		execl(pwd_bin, "pwd", (char *)0);
361 		error("Cannot exec %s", pwd_bin);
362 	}
363 	close(pip[1]);
364 	pip[1] = -1;
365 	p = buf;
366 	while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0
367 	     || i == -1 && errno == EINTR) {
368 		if (i > 0)
369 			p += i;
370 	}
371 	close(pip[0]);
372 	pip[0] = -1;
373 	status = waitforjob(jp);
374 	if (status != 0)
375 		error((char *)0);
376 	if (i < 0 || p == buf || p[-1] != '\n')
377 		error("pwd command failed");
378 	p[-1] = '\0';
379 	curdir = savestr(buf);
380 	INTON;
381 }
382