xref: /freebsd/bin/sh/cd.c (revision 52ec752989b2e6d4e9a59a8ff25d8ff596d85e62)
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 
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)cd.c	8.2 (Berkeley) 5/4/95";
40 #endif
41 #endif /* not lint */
42 #include <sys/cdefs.h>
43 __FBSDID("$FreeBSD$");
44 
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <errno.h>
51 #include <limits.h>
52 
53 /*
54  * The cd and pwd commands.
55  */
56 
57 #include "shell.h"
58 #include "var.h"
59 #include "nodes.h"	/* for jobs.h */
60 #include "jobs.h"
61 #include "options.h"
62 #include "output.h"
63 #include "memalloc.h"
64 #include "error.h"
65 #include "exec.h"
66 #include "redir.h"
67 #include "mystring.h"
68 #include "show.h"
69 #include "cd.h"
70 
71 STATIC int cdlogical(char *);
72 STATIC int cdphysical(char *);
73 STATIC int docd(char *, int, int);
74 STATIC char *getcomponent(void);
75 STATIC int updatepwd(char *);
76 
77 STATIC char *curdir = NULL;	/* current working directory */
78 STATIC char *prevdir;		/* previous working directory */
79 STATIC char *cdcomppath;
80 
81 int
82 cdcmd(int argc, char **argv)
83 {
84 	char *dest;
85 	char *path;
86 	char *p;
87 	struct stat statb;
88 	int ch, phys, print = 0;
89 
90 	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
91 	phys = Pflag;
92 	while ((ch = getopt(argc, argv, "LP")) != -1) {
93 		switch (ch) {
94 		case 'L':
95 			phys = 0;
96 			break;
97 		case 'P':
98 			phys = 1;
99 			break;
100 		default:
101 			error("unknown option: -%c", optopt);
102 			break;
103 		}
104 	}
105 	argc -= optind;
106 	argv += optind;
107 
108 	if (argc > 1)
109 		error("too many arguments");
110 
111 	if ((dest = *argv) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
112 		error("HOME not set");
113 	if (*dest == '\0')
114 		dest = ".";
115 	if (dest[0] == '-' && dest[1] == '\0') {
116 		dest = prevdir ? prevdir : curdir;
117 		if (dest)
118 			print = 1;
119 		else
120 			dest = ".";
121 	}
122 	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
123 		path = nullstr;
124 	while ((p = padvance(&path, dest)) != NULL) {
125 		if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
126 			if (!print) {
127 				/*
128 				 * XXX - rethink
129 				 */
130 				if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
131 					p += 2;
132 				print = strcmp(p, dest);
133 			}
134 			if (docd(p, print, phys) >= 0)
135 				return 0;
136 		}
137 	}
138 	error("can't cd to %s", dest);
139 	/*NOTREACHED*/
140 	return 0;
141 }
142 
143 
144 /*
145  * Actually change the directory.  In an interactive shell, print the
146  * directory name if "print" is nonzero.
147  */
148 STATIC int
149 docd(char *dest, int print, int phys)
150 {
151 
152 	TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
153 
154 	/* If logical cd fails, fall back to physical. */
155 	if ((phys || cdlogical(dest) < 0) && cdphysical(dest) < 0)
156 		return (-1);
157 
158 	if (print && iflag && curdir)
159 		out1fmt("%s\n", curdir);
160 
161 	return 0;
162 }
163 
164 STATIC int
165 cdlogical(char *dest)
166 {
167 	char *p;
168 	char *q;
169 	char *component;
170 	struct stat statb;
171 	int first;
172 	int badstat;
173 
174 	/*
175 	 *  Check each component of the path. If we find a symlink or
176 	 *  something we can't stat, clear curdir to force a getcwd()
177 	 *  next time we get the value of the current directory.
178 	 */
179 	badstat = 0;
180 	cdcomppath = stalloc(strlen(dest) + 1);
181 	scopy(dest, cdcomppath);
182 	STARTSTACKSTR(p);
183 	if (*dest == '/') {
184 		STPUTC('/', p);
185 		cdcomppath++;
186 	}
187 	first = 1;
188 	while ((q = getcomponent()) != NULL) {
189 		if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
190 			continue;
191 		if (! first)
192 			STPUTC('/', p);
193 		first = 0;
194 		component = q;
195 		while (*q)
196 			STPUTC(*q++, p);
197 		if (equal(component, ".."))
198 			continue;
199 		STACKSTRNUL(p);
200 		if (lstat(stackblock(), &statb) < 0) {
201 			badstat = 1;
202 			break;
203 		}
204 	}
205 
206 	INTOFF;
207 	if (updatepwd(badstat ? NULL : dest) < 0 || chdir(curdir) < 0) {
208 		INTON;
209 		return (-1);
210 	}
211 	INTON;
212 	return (0);
213 }
214 
215 STATIC int
216 cdphysical(char *dest)
217 {
218 
219 	INTOFF;
220 	if (chdir(dest) < 0 || updatepwd(NULL) < 0) {
221 		INTON;
222 		return (-1);
223 	}
224 	INTON;
225 	return (0);
226 }
227 
228 /*
229  * Get the next component of the path name pointed to by cdcomppath.
230  * This routine overwrites the string pointed to by cdcomppath.
231  */
232 STATIC char *
233 getcomponent(void)
234 {
235 	char *p;
236 	char *start;
237 
238 	if ((p = cdcomppath) == NULL)
239 		return NULL;
240 	start = cdcomppath;
241 	while (*p != '/' && *p != '\0')
242 		p++;
243 	if (*p == '\0') {
244 		cdcomppath = NULL;
245 	} else {
246 		*p++ = '\0';
247 		cdcomppath = p;
248 	}
249 	return start;
250 }
251 
252 
253 /*
254  * Update curdir (the name of the current directory) in response to a
255  * cd command.  We also call hashcd to let the routines in exec.c know
256  * that the current directory has changed.
257  */
258 STATIC int
259 updatepwd(char *dir)
260 {
261 	char *new;
262 	char *p;
263 
264 	hashcd();				/* update command hash table */
265 
266 	/*
267 	 * If our argument is NULL, we don't know the current directory
268 	 * any more because we traversed a symbolic link or something
269 	 * we couldn't stat().
270 	 */
271 	if (dir == NULL || curdir == NULL)  {
272 		if (prevdir)
273 			ckfree(prevdir);
274 		INTOFF;
275 		prevdir = curdir;
276 		curdir = NULL;
277 		if (getpwd() == NULL) {
278 			INTON;
279 			return (-1);
280 		}
281 		setvar("PWD", curdir, VEXPORT);
282 		setvar("OLDPWD", prevdir, VEXPORT);
283 		INTON;
284 		return (0);
285 	}
286 	cdcomppath = stalloc(strlen(dir) + 1);
287 	scopy(dir, cdcomppath);
288 	STARTSTACKSTR(new);
289 	if (*dir != '/') {
290 		p = curdir;
291 		while (*p)
292 			STPUTC(*p++, new);
293 		if (p[-1] == '/')
294 			STUNPUTC(new);
295 	}
296 	while ((p = getcomponent()) != NULL) {
297 		if (equal(p, "..")) {
298 			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
299 		} else if (*p != '\0' && ! equal(p, ".")) {
300 			STPUTC('/', new);
301 			while (*p)
302 				STPUTC(*p++, new);
303 		}
304 	}
305 	if (new == stackblock())
306 		STPUTC('/', new);
307 	STACKSTRNUL(new);
308 	INTOFF;
309 	if (prevdir)
310 		ckfree(prevdir);
311 	prevdir = curdir;
312 	curdir = savestr(stackblock());
313 	setvar("PWD", curdir, VEXPORT);
314 	setvar("OLDPWD", prevdir, VEXPORT);
315 	INTON;
316 
317 	return (0);
318 }
319 
320 int
321 pwdcmd(int argc, char **argv)
322 {
323 	char buf[PATH_MAX];
324 	int ch, phys;
325 
326 	optreset = 1; optind = 1; opterr = 0; /* initialize getopt */
327 	phys = Pflag;
328 	while ((ch = getopt(argc, argv, "LP")) != -1) {
329 		switch (ch) {
330 		case 'L':
331 			phys = 0;
332 			break;
333 		case 'P':
334 			phys = 1;
335 			break;
336 		default:
337 			error("unknown option: -%c", optopt);
338 			break;
339 		}
340 	}
341 	argc -= optind;
342 	argv += optind;
343 
344 	if (argc != 0)
345 		error("too many arguments");
346 
347 	if (!phys && getpwd()) {
348 		out1str(curdir);
349 		out1c('\n');
350 	} else {
351 		if (getcwd(buf, sizeof(buf)) == NULL)
352 			error(".: %s", strerror(errno));
353 		out1str(buf);
354 		out1c('\n');
355 	}
356 
357 	return 0;
358 }
359 
360 /*
361  * Find out what the current directory is. If we already know the current
362  * directory, this routine returns immediately.
363  */
364 char *
365 getpwd(void)
366 {
367 	char buf[PATH_MAX];
368 
369 	if (curdir)
370 		return curdir;
371 	if (getcwd(buf, sizeof(buf)) == NULL) {
372 		char *pwd = getenv("PWD");
373 		struct stat stdot, stpwd;
374 
375 		if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
376 		    stat(pwd, &stpwd) != -1 &&
377 		    stdot.st_dev == stpwd.st_dev &&
378 		    stdot.st_ino == stpwd.st_ino) {
379 			curdir = savestr(pwd);
380 			return curdir;
381 		}
382 		return NULL;
383 	}
384 	curdir = savestr(buf);
385 
386 	return curdir;
387 }
388