xref: /netbsd-src/bin/sh/cd.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: cd.c,v 1.47 2016/12/26 02:27:57 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1991, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Kenneth Almquist.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)cd.c	8.2 (Berkeley) 5/4/95";
39 #else
40 __RCSID("$NetBSD: cd.c,v 1.47 2016/12/26 02:27:57 christos Exp $");
41 #endif
42 #endif /* not lint */
43 
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <errno.h>
50 
51 /*
52  * The cd and pwd commands.
53  */
54 
55 #include "shell.h"
56 #include "var.h"
57 #include "nodes.h"	/* for jobs.h */
58 #include "jobs.h"
59 #include "options.h"
60 #include "builtins.h"
61 #include "output.h"
62 #include "memalloc.h"
63 #include "error.h"
64 #include "exec.h"
65 #include "redir.h"
66 #include "mystring.h"
67 #include "show.h"
68 #include "cd.h"
69 
70 STATIC int docd(const char *, int);
71 STATIC char *getcomponent(void);
72 STATIC void updatepwd(const char *);
73 STATIC void find_curdir(int noerror);
74 
75 char *curdir = NULL;		/* current working directory */
76 char *prevdir;			/* previous working directory */
77 STATIC char *cdcomppath;
78 
79 int
80 cdcmd(int argc, char **argv)
81 {
82 	const char *dest;
83 	const char *path, *p;
84 	char *d;
85 	struct stat statb;
86 	int print = cdprint;	/* set -cdprint to enable */
87 
88 	while (nextopt("P") != '\0')
89 		;
90 
91 	/*
92 	 * Try (quite hard) to have 'curdir' defined, nothing has set
93 	 * it on entry to the shell, but we want 'cd fred; cd -' to work.
94 	 */
95 	getpwd(1);
96 	dest = *argptr;
97 	if (dest == NULL) {
98 		dest = bltinlookup("HOME", 1);
99 		if (dest == NULL)
100 			error("HOME not set");
101 	} else {
102 		if (argptr[1]) {
103 			/* Do 'ksh' style substitution */
104 			if (!curdir)
105 				error("PWD not set");
106 			p = strstr(curdir, dest);
107 			if (!p)
108 				error("bad substitution");
109 			d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1);
110 			memcpy(d, curdir, p - curdir);
111 			strcpy(d + (p - curdir), argptr[1]);
112 			strcat(d, p + strlen(dest));
113 			dest = d;
114 			print = 1;
115 		}
116 	}
117 
118 	if (dest[0] == '-' && dest[1] == '\0') {
119 		dest = prevdir ? prevdir : curdir;
120 		print = 1;
121 	}
122 	if (*dest == '\0')
123 	        dest = ".";
124 	p = dest;
125 	if (*p == '.' && *++p == '.')
126 	    p++;
127 	if (*p == 0 || *p == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
128 		path = nullstr;
129 	while ((p = padvance(&path, dest)) != NULL) {
130 		if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
131 			if (!print) {
132 				/*
133 				 * XXX - rethink
134 				 */
135 				if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
136 					print = strcmp(p + 2, dest);
137 				else
138 					print = strcmp(p, dest);
139 			}
140 			if (docd(p, print) >= 0)
141 				return 0;
142 
143 		}
144 	}
145 	error("can't cd to %s", dest);
146 	/* NOTREACHED */
147 }
148 
149 
150 /*
151  * Actually do the chdir.  In an interactive shell, print the
152  * directory name if "print" is nonzero.
153  */
154 
155 STATIC int
156 docd(const char *dest, int print)
157 {
158 	char *p;
159 	char *q;
160 	char *component;
161 	struct stat statb;
162 	int first;
163 	int badstat;
164 
165 	TRACE(("docd(\"%s\", %d) called\n", dest, print));
166 
167 	/*
168 	 *  Check each component of the path. If we find a symlink or
169 	 *  something we can't stat, clear curdir to force a getcwd()
170 	 *  next time we get the value of the current directory.
171 	 */
172 	badstat = 0;
173 	cdcomppath = stalloc(strlen(dest) + 1);
174 	scopy(dest, cdcomppath);
175 	STARTSTACKSTR(p);
176 	if (*dest == '/') {
177 		STPUTC('/', p);
178 		cdcomppath++;
179 	}
180 	first = 1;
181 	while ((q = getcomponent()) != NULL) {
182 		if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
183 			continue;
184 		if (! first)
185 			STPUTC('/', p);
186 		first = 0;
187 		component = q;
188 		while (*q)
189 			STPUTC(*q++, p);
190 		if (equal(component, ".."))
191 			continue;
192 		STACKSTRNUL(p);
193 		if (lstat(stackblock(), &statb) < 0) {
194 			badstat = 1;
195 			break;
196 		}
197 	}
198 
199 	INTOFF;
200 	if (chdir(dest) < 0) {
201 		INTON;
202 		return -1;
203 	}
204 	updatepwd(badstat ? NULL : dest);
205 	INTON;
206 	if (print && iflag == 1 && curdir)
207 		out1fmt("%s\n", curdir);
208 	return 0;
209 }
210 
211 
212 /*
213  * Get the next component of the path name pointed to by cdcomppath.
214  * This routine overwrites the string pointed to by cdcomppath.
215  */
216 
217 STATIC char *
218 getcomponent(void)
219 {
220 	char *p;
221 	char *start;
222 
223 	if ((p = cdcomppath) == NULL)
224 		return NULL;
225 	start = cdcomppath;
226 	while (*p != '/' && *p != '\0')
227 		p++;
228 	if (*p == '\0') {
229 		cdcomppath = NULL;
230 	} else {
231 		*p++ = '\0';
232 		cdcomppath = p;
233 	}
234 	return start;
235 }
236 
237 
238 
239 /*
240  * Update curdir (the name of the current directory) in response to a
241  * cd command.  We also call hashcd to let the routines in exec.c know
242  * that the current directory has changed.
243  */
244 
245 STATIC void
246 updatepwd(const char *dir)
247 {
248 	char *new;
249 	char *p;
250 
251 	hashcd();				/* update command hash table */
252 
253 	/*
254 	 * If our argument is NULL, we don't know the current directory
255 	 * any more because we traversed a symbolic link or something
256 	 * we couldn't stat().
257 	 */
258 	if (dir == NULL || curdir == NULL)  {
259 		if (prevdir)
260 			ckfree(prevdir);
261 		INTOFF;
262 		prevdir = curdir;
263 		curdir = NULL;
264 		getpwd(1);
265 		INTON;
266 		if (curdir) {
267 			setvar("OLDPWD", prevdir, VEXPORT);
268 			setvar("PWD", curdir, VEXPORT);
269 		} else
270 			unsetvar("PWD", 0);
271 		return;
272 	}
273 	cdcomppath = stalloc(strlen(dir) + 1);
274 	scopy(dir, cdcomppath);
275 	STARTSTACKSTR(new);
276 	if (*dir != '/') {
277 		p = curdir;
278 		while (*p)
279 			STPUTC(*p++, new);
280 		if (p[-1] == '/')
281 			STUNPUTC(new);
282 	}
283 	while ((p = getcomponent()) != NULL) {
284 		if (equal(p, "..")) {
285 			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
286 		} else if (*p != '\0' && ! equal(p, ".")) {
287 			STPUTC('/', new);
288 			while (*p)
289 				STPUTC(*p++, new);
290 		}
291 	}
292 	if (new == stackblock())
293 		STPUTC('/', new);
294 	STACKSTRNUL(new);
295 	INTOFF;
296 	if (prevdir)
297 		ckfree(prevdir);
298 	prevdir = curdir;
299 	curdir = savestr(stackblock());
300 	setvar("OLDPWD", prevdir, VEXPORT);
301 	setvar("PWD", curdir, VEXPORT);
302 	INTON;
303 }
304 
305 /*
306  * Posix says the default should be 'pwd -L' (as below), however
307  * the 'cd' command (above) does something much nearer to the
308  * posix 'cd -P' (not the posix default of 'cd -L').
309  * If 'cd' is changed to support -P/L then the default here
310  * needs to be revisited if the historic behaviour is to be kept.
311  */
312 
313 int
314 pwdcmd(int argc, char **argv)
315 {
316 	int i;
317 	char opt = 'L';
318 
319 	while ((i = nextopt("LP")) != '\0')
320 		opt = i;
321 	if (*argptr)
322 		error("unexpected argument");
323 
324 	if (opt == 'L')
325 		getpwd(0);
326 	else
327 		find_curdir(0);
328 
329 	setvar("OLDPWD", prevdir, VEXPORT);
330 	setvar("PWD", curdir, VEXPORT);
331 	out1str(curdir);
332 	out1c('\n');
333 	return 0;
334 }
335 
336 
337 
338 void
339 initpwd(void)
340 {
341 	getpwd(1);
342 	if (curdir)
343 		setvar("PWD", curdir, VEXPORT);
344 	else
345 		sh_warnx("Cannot determine current working directory");
346 }
347 
348 #define MAXPWD 256
349 
350 /*
351  * Find out what the current directory is. If we already know the current
352  * directory, this routine returns immediately.
353  */
354 void
355 getpwd(int noerror)
356 {
357 	char *pwd;
358 	struct stat stdot, stpwd;
359 	static int first = 1;
360 
361 	if (curdir)
362 		return;
363 
364 	if (first) {
365 		first = 0;
366 		pwd = getenv("PWD");
367 		if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
368 		    stat(pwd, &stpwd) != -1 &&
369 		    stdot.st_dev == stpwd.st_dev &&
370 		    stdot.st_ino == stpwd.st_ino) {
371 			curdir = savestr(pwd);
372 			return;
373 		}
374 	}
375 
376 	find_curdir(noerror);
377 
378 	return;
379 }
380 
381 STATIC void
382 find_curdir(int noerror)
383 {
384 	int i;
385 	char *pwd;
386 
387 	/*
388 	 * Things are a bit complicated here; we could have just used
389 	 * getcwd, but traditionally getcwd is implemented using popen
390 	 * to /bin/pwd. This creates a problem for us, since we cannot
391 	 * keep track of the job if it is being ran behind our backs.
392 	 * So we re-implement getcwd(), and we suppress interrupts
393 	 * throughout the process. This is not completely safe, since
394 	 * the user can still break out of it by killing the pwd program.
395 	 * We still try to use getcwd for systems that we know have a
396 	 * c implementation of getcwd, that does not open a pipe to
397 	 * /bin/pwd.
398 	 */
399 #if defined(__NetBSD__) || defined(__SVR4)
400 
401 	for (i = MAXPWD;; i *= 2) {
402 		pwd = stalloc(i);
403 		if (getcwd(pwd, i) != NULL) {
404 			curdir = savestr(pwd);
405 			return;
406 		}
407 		stunalloc(pwd);
408 		if (errno == ERANGE)
409 			continue;
410 		if (!noerror)
411 			error("getcwd() failed: %s", strerror(errno));
412 		return;
413 	}
414 #else
415 	{
416 		char *p;
417 		int status;
418 		struct job *jp;
419 		int pip[2];
420 
421 		pwd = stalloc(MAXPWD);
422 		INTOFF;
423 		if (pipe(pip) < 0)
424 			error("Pipe call failed");
425 		jp = makejob(NULL, 1);
426 		if (forkshell(jp, NULL, FORK_NOJOB) == 0) {
427 			(void) close(pip[0]);
428 			movefd(pip[1], 1);
429 			(void) execl("/bin/pwd", "pwd", (char *)0);
430 			error("Cannot exec /bin/pwd");
431 		}
432 		(void) close(pip[1]);
433 		pip[1] = -1;
434 		p = pwd;
435 		while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0
436 		     || (i == -1 && errno == EINTR)) {
437 			if (i > 0)
438 				p += i;
439 		}
440 		(void) close(pip[0]);
441 		pip[0] = -1;
442 		status = waitforjob(jp);
443 		if (status != 0)
444 			error((char *)0);
445 		if (i < 0 || p == pwd || p[-1] != '\n') {
446 			if (noerror) {
447 				INTON;
448 				return;
449 			}
450 			error("pwd command failed");
451 		}
452 		p[-1] = '\0';
453 		INTON;
454 		curdir = savestr(pwd);
455 		return;
456 	}
457 #endif
458 }
459