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