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