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