cd.c revision 1556
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
38static char sccsid[] = "@(#)cd.c	8.1 (Berkeley) 5/31/93";
39#endif /* not lint */
40
41/*
42 * The cd and pwd commands.
43 */
44
45#include "shell.h"
46#include "var.h"
47#include "nodes.h"	/* for jobs.h */
48#include "jobs.h"
49#include "options.h"
50#include "output.h"
51#include "memalloc.h"
52#include "error.h"
53#include "mystring.h"
54#include <sys/types.h>
55#include <sys/stat.h>
56#include <errno.h>
57
58
59#ifdef __STDC__
60STATIC int docd(char *, int);
61STATIC void updatepwd(char *);
62STATIC void getpwd(void);
63STATIC char *getcomponent(void);
64#else
65STATIC int docd();
66STATIC void updatepwd();
67STATIC void getpwd();
68STATIC char *getcomponent();
69#endif
70
71
72char *curdir;			/* current working directory */
73char *prevdir;			/* previous working directory */
74STATIC char *cdcomppath;
75
76int
77cdcmd(argc, argv)  char **argv; {
78	char *dest;
79	char *path;
80	char *p;
81	struct stat statb;
82	char *padvance();
83	int print = 0;
84
85	nextopt(nullstr);
86	if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
87		error("HOME not set");
88	if (dest[0] == '-' && dest[1] == '\0') {
89		dest = prevdir ? prevdir : curdir;
90		print = 1;
91	}
92	if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
93		path = nullstr;
94	while ((p = padvance(&path, dest)) != NULL) {
95		if (stat(p, &statb) >= 0
96		 && (statb.st_mode & S_IFMT) == S_IFDIR) {
97			if (!print) {
98				/*
99				 * XXX - rethink
100				 */
101				if (p[0] == '.' && p[1] == '/')
102					p += 2;
103				print = strcmp(p, dest);
104			}
105			if (docd(p, print) >= 0)
106				return 0;
107
108		}
109	}
110	error("can't cd to %s", dest);
111}
112
113
114/*
115 * Actually do the chdir.  If the name refers to symbolic links, we
116 * compute the actual directory name before doing the cd.  In an
117 * interactive shell, print the directory name if "print" is nonzero
118 * or if the name refers to a symbolic link.  We also print the name
119 * if "/u/logname" was expanded in it, since this is similar to a
120 * symbolic link.  (The check for this breaks if the user gives the
121 * cd command some additional, unused arguments.)
122 */
123
124#if SYMLINKS == 0
125STATIC int
126docd(dest, print)
127	char *dest;
128	{
129	INTOFF;
130	if (chdir(dest) < 0) {
131		INTON;
132		return -1;
133	}
134	updatepwd(dest);
135	INTON;
136	if (print && iflag)
137		out1fmt("%s\n", stackblock());
138	return 0;
139}
140
141#else
142
143
144
145STATIC int
146docd(dest, print)
147	char *dest;
148	{
149	register char *p;
150	register char *q;
151	char *symlink;
152	char *component;
153	struct stat statb;
154	int first;
155	int i;
156
157	TRACE(("docd(\"%s\", %d) called\n", dest, print));
158
159top:
160	cdcomppath = dest;
161	STARTSTACKSTR(p);
162	if (*dest == '/') {
163		STPUTC('/', p);
164		cdcomppath++;
165	}
166	first = 1;
167	while ((q = getcomponent()) != NULL) {
168		if (q[0] == '\0' || q[0] == '.' && q[1] == '\0')
169			continue;
170		if (! first)
171			STPUTC('/', p);
172		first = 0;
173		component = q;
174		while (*q)
175			STPUTC(*q++, p);
176		if (equal(component, ".."))
177			continue;
178		STACKSTRNUL(p);
179		if (lstat(stackblock(), &statb) < 0)
180			error("lstat %s failed", stackblock());
181		if ((statb.st_mode & S_IFMT) != S_IFLNK)
182			continue;
183
184		/* Hit a symbolic link.  We have to start all over again. */
185		print = 1;
186		STPUTC('\0', p);
187		symlink = grabstackstr(p);
188		i = (int)statb.st_size + 2;		/* 2 for '/' and '\0' */
189		if (cdcomppath != NULL)
190			i += strlen(cdcomppath);
191		p = stalloc(i);
192		if (readlink(symlink, p, (int)statb.st_size) < 0) {
193			error("readlink %s failed", stackblock());
194		}
195		if (cdcomppath != NULL) {
196			p[(int)statb.st_size] = '/';
197			scopy(cdcomppath, p + (int)statb.st_size + 1);
198		} else {
199			p[(int)statb.st_size] = '\0';
200		}
201		if (p[0] != '/') {	/* relative path name */
202			char *r;
203			q = r = symlink;
204			while (*q) {
205				if (*q++ == '/')
206					r = q;
207			}
208			*r = '\0';
209			dest = stalloc(strlen(symlink) + strlen(p) + 1);
210			scopy(symlink, dest);
211			strcat(dest, p);
212		} else {
213			dest = p;
214		}
215		goto top;
216	}
217	STPUTC('\0', p);
218	p = grabstackstr(p);
219	INTOFF;
220	if (chdir(p) < 0) {
221		INTON;
222		return -1;
223	}
224	updatepwd(p);
225	INTON;
226	if (print && iflag)
227		out1fmt("%s\n", p);
228	return 0;
229}
230#endif /* SYMLINKS */
231
232
233
234/*
235 * Get the next component of the path name pointed to by cdcomppath.
236 * This routine overwrites the string pointed to by cdcomppath.
237 */
238
239STATIC char *
240getcomponent() {
241	register char *p;
242	char *start;
243
244	if ((p = cdcomppath) == NULL)
245		return NULL;
246	start = cdcomppath;
247	while (*p != '/' && *p != '\0')
248		p++;
249	if (*p == '\0') {
250		cdcomppath = NULL;
251	} else {
252		*p++ = '\0';
253		cdcomppath = p;
254	}
255	return start;
256}
257
258
259
260/*
261 * Update curdir (the name of the current directory) in response to a
262 * cd command.  We also call hashcd to let the routines in exec.c know
263 * that the current directory has changed.
264 */
265
266void hashcd();
267
268STATIC void
269updatepwd(dir)
270	char *dir;
271	{
272	char *new;
273	char *p;
274
275	hashcd();				/* update command hash table */
276	cdcomppath = stalloc(strlen(dir) + 1);
277	scopy(dir, cdcomppath);
278	STARTSTACKSTR(new);
279	if (*dir != '/') {
280		if (curdir == NULL)
281			return;
282		p = curdir;
283		while (*p)
284			STPUTC(*p++, new);
285		if (p[-1] == '/')
286			STUNPUTC(new);
287	}
288	while ((p = getcomponent()) != NULL) {
289		if (equal(p, "..")) {
290			while (new > stackblock() && (STUNPUTC(new), *new) != '/');
291		} else if (*p != '\0' && ! equal(p, ".")) {
292			STPUTC('/', new);
293			while (*p)
294				STPUTC(*p++, new);
295		}
296	}
297	if (new == stackblock())
298		STPUTC('/', new);
299	STACKSTRNUL(new);
300	INTOFF;
301	if (prevdir)
302		ckfree(prevdir);
303	prevdir = curdir;
304	curdir = savestr(stackblock());
305	INTON;
306}
307
308
309
310int
311pwdcmd(argc, argv)  char **argv; {
312	getpwd();
313	out1str(curdir);
314	out1c('\n');
315	return 0;
316}
317
318
319
320/*
321 * Run /bin/pwd to find out what the current directory is.  We suppress
322 * interrupts throughout most of this, but the user can still break out
323 * of it by killing the pwd program.  If we already know the current
324 * directory, this routine returns immediately.
325 */
326
327#define MAXPWD 256
328
329STATIC void
330getpwd() {
331	char buf[MAXPWD];
332	char *p;
333	int i;
334	int status;
335	struct job *jp;
336	int pip[2];
337
338	if (curdir)
339		return;
340	INTOFF;
341	if (pipe(pip) < 0)
342		error("Pipe call failed");
343	jp = makejob((union node *)NULL, 1);
344	if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
345		close(pip[0]);
346		if (pip[1] != 1) {
347			close(1);
348			copyfd(pip[1], 1);
349			close(pip[1]);
350		}
351		execl("/bin/pwd", "pwd", (char *)0);
352		error("Cannot exec /bin/pwd");
353	}
354	close(pip[1]);
355	pip[1] = -1;
356	p = buf;
357	while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0
358	     || i == -1 && errno == EINTR) {
359		if (i > 0)
360			p += i;
361	}
362	close(pip[0]);
363	pip[0] = -1;
364	status = waitforjob(jp);
365	if (status != 0)
366		error((char *)0);
367	if (i < 0 || p == buf || p[-1] != '\n')
368		error("pwd command failed");
369	p[-1] = '\0';
370	curdir = savestr(buf);
371	INTON;
372}
373