cd.c revision 5234
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
40static 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__
63STATIC int docd(char *, int);
64STATIC void updatepwd(char *);
65STATIC void getpwd(void);
66STATIC char *getcomponent(void);
67#else
68STATIC int docd();
69STATIC void updatepwd();
70STATIC void getpwd();
71STATIC char *getcomponent();
72#endif
73
74
75char *curdir;			/* current working directory */
76char *prevdir;			/* previous working directory */
77STATIC char *cdcomppath;
78
79int
80cdcmd(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
130STATIC int
131docd(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
150STATIC int
151docd(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
164top:
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
244STATIC char *
245getcomponent() {
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
271void hashcd();
272
273STATIC void
274updatepwd(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
315int
316pwdcmd(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
334STATIC void
335getpwd() {
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