cd.c revision 100661
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
39static char sccsid[] = "@(#)cd.c	8.2 (Berkeley) 5/4/95";
40#endif
41#endif /* not lint */
42#include <sys/cdefs.h>
43__FBSDID("$FreeBSD: head/bin/sh/cd.c 100661 2002-07-25 09:56:08Z tjr $");
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
71STATIC int cdlogical(char *);
72STATIC int cdphysical(char *);
73STATIC int docd(char *, int, int);
74STATIC char *getcomponent(void);
75STATIC int updatepwd(char *);
76
77char *curdir = NULL;		/* current working directory */
78char *prevdir;			/* previous working directory */
79STATIC char *cdcomppath;
80
81int
82cdcmd(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; /* initialize getopt */
91	phys = 0;
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 */
148STATIC int
149docd(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
164STATIC int
165cdlogical(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
215STATIC int
216cdphysical(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 */
232STATIC char *
233getcomponent(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 */
258STATIC int
259updatepwd(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
320int
321pwdcmd(int argc, char **argv)
322{
323	char buf[PATH_MAX];
324	int ch, phys;
325
326	optreset = 1; optind = 1; /* initialize getopt */
327	phys = 0;
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 */
364char *
365getpwd(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