cd.c revision 1.37 1 /* $NetBSD: cd.c,v 1.37 2005/10/10 21:14:42 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1991, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Kenneth Almquist.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35 #include <sys/cdefs.h>
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95";
39 #else
40 __RCSID("$NetBSD: cd.c,v 1.37 2005/10/10 21:14:42 christos Exp $");
41 #endif
42 #endif /* not lint */
43
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <errno.h>
50
51 /*
52 * The cd and pwd commands.
53 */
54
55 #include "shell.h"
56 #include "var.h"
57 #include "nodes.h" /* for jobs.h */
58 #include "jobs.h"
59 #include "options.h"
60 #include "output.h"
61 #include "memalloc.h"
62 #include "error.h"
63 #include "exec.h"
64 #include "redir.h"
65 #include "mystring.h"
66 #include "show.h"
67 #include "cd.h"
68
69 STATIC int docd(const char *, int);
70 STATIC char *getcomponent(void);
71 STATIC void updatepwd(const char *);
72 STATIC void find_curdir(int noerror);
73
74 char *curdir = NULL; /* current working directory */
75 char *prevdir; /* previous working directory */
76 STATIC char *cdcomppath;
77
78 int
79 cdcmd(int argc, char **argv)
80 {
81 const char *dest;
82 const char *path, *p;
83 char *d;
84 struct stat statb;
85 int print = cdprint; /* set -cdprint to enable */
86
87 nextopt(nullstr);
88
89 /*
90 * Try (quite hard) to have 'curdir' defined, nothing has set
91 * it on entry to the shell, but we want 'cd fred; cd -' to work.
92 */
93 getpwd(1);
94 dest = *argptr;
95 if (dest == NULL) {
96 dest = bltinlookup("HOME", 1);
97 if (dest == NULL)
98 error("HOME not set");
99 } else {
100 if (argptr[1]) {
101 /* Do 'ksh' style substitution */
102 if (!curdir)
103 error("PWD not set");
104 p = strstr(curdir, dest);
105 if (!p)
106 error("bad substitution");
107 d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1);
108 memcpy(d, curdir, p - curdir);
109 strcpy(d + (p - curdir), argptr[1]);
110 strcat(d, p + strlen(dest));
111 dest = d;
112 print = 1;
113 }
114 }
115
116 if (dest[0] == '-' && dest[1] == '\0') {
117 dest = prevdir ? prevdir : curdir;
118 print = 1;
119 }
120 if (*dest == '\0')
121 dest = ".";
122 p = dest;
123 if (*p == '.' && *++p == '.')
124 p++;
125 if (*p == 0 || *p == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
126 path = nullstr;
127 while ((p = padvance(&path, dest)) != NULL) {
128 if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
129 if (!print) {
130 /*
131 * XXX - rethink
132 */
133 if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
134 p += 2;
135 print = strcmp(p, dest);
136 }
137 if (docd(p, print) >= 0)
138 return 0;
139
140 }
141 }
142 error("can't cd to %s", dest);
143 /* NOTREACHED */
144 }
145
146
147 /*
148 * Actually do the chdir. In an interactive shell, print the
149 * directory name if "print" is nonzero.
150 */
151
152 STATIC int
153 docd(const char *dest, int print)
154 {
155 char *p;
156 char *q;
157 char *component;
158 struct stat statb;
159 int first;
160 int badstat;
161
162 TRACE(("docd(\"%s\", %d) called\n", dest, print));
163
164 /*
165 * Check each component of the path. If we find a symlink or
166 * something we can't stat, clear curdir to force a getcwd()
167 * next time we get the value of the current directory.
168 */
169 badstat = 0;
170 cdcomppath = stalloc(strlen(dest) + 1);
171 scopy(dest, cdcomppath);
172 STARTSTACKSTR(p);
173 if (*dest == '/') {
174 STPUTC('/', p);
175 cdcomppath++;
176 }
177 first = 1;
178 while ((q = getcomponent()) != NULL) {
179 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
180 continue;
181 if (! first)
182 STPUTC('/', p);
183 first = 0;
184 component = q;
185 while (*q)
186 STPUTC(*q++, p);
187 if (equal(component, ".."))
188 continue;
189 STACKSTRNUL(p);
190 if ((lstat(stackblock(), &statb) < 0)
191 || (S_ISLNK(statb.st_mode))) {
192 /* print = 1; */
193 badstat = 1;
194 break;
195 }
196 }
197
198 INTOFF;
199 if (chdir(dest) < 0) {
200 INTON;
201 return -1;
202 }
203 updatepwd(badstat ? NULL : dest);
204 INTON;
205 if (print && iflag == 1 && curdir)
206 out1fmt("%s\n", curdir);
207 return 0;
208 }
209
210
211 /*
212 * Get the next component of the path name pointed to by cdcomppath.
213 * This routine overwrites the string pointed to by cdcomppath.
214 */
215
216 STATIC char *
217 getcomponent(void)
218 {
219 char *p;
220 char *start;
221
222 if ((p = cdcomppath) == NULL)
223 return NULL;
224 start = cdcomppath;
225 while (*p != '/' && *p != '\0')
226 p++;
227 if (*p == '\0') {
228 cdcomppath = NULL;
229 } else {
230 *p++ = '\0';
231 cdcomppath = p;
232 }
233 return start;
234 }
235
236
237
238 /*
239 * Update curdir (the name of the current directory) in response to a
240 * cd command. We also call hashcd to let the routines in exec.c know
241 * that the current directory has changed.
242 */
243
244 STATIC void
245 updatepwd(const char *dir)
246 {
247 char *new;
248 char *p;
249
250 hashcd(); /* update command hash table */
251
252 /*
253 * If our argument is NULL, we don't know the current directory
254 * any more because we traversed a symbolic link or something
255 * we couldn't stat().
256 */
257 if (dir == NULL || curdir == NULL) {
258 if (prevdir)
259 ckfree(prevdir);
260 INTOFF;
261 prevdir = curdir;
262 curdir = NULL;
263 getpwd(1);
264 INTON;
265 if (curdir)
266 setvar("PWD", curdir, VEXPORT);
267 else
268 unsetvar("PWD", 0);
269 return;
270 }
271 cdcomppath = stalloc(strlen(dir) + 1);
272 scopy(dir, cdcomppath);
273 STARTSTACKSTR(new);
274 if (*dir != '/') {
275 p = curdir;
276 while (*p)
277 STPUTC(*p++, new);
278 if (p[-1] == '/')
279 STUNPUTC(new);
280 }
281 while ((p = getcomponent()) != NULL) {
282 if (equal(p, "..")) {
283 while (new > stackblock() && (STUNPUTC(new), *new) != '/');
284 } else if (*p != '\0' && ! equal(p, ".")) {
285 STPUTC('/', new);
286 while (*p)
287 STPUTC(*p++, new);
288 }
289 }
290 if (new == stackblock())
291 STPUTC('/', new);
292 STACKSTRNUL(new);
293 INTOFF;
294 if (prevdir)
295 ckfree(prevdir);
296 prevdir = curdir;
297 curdir = savestr(stackblock());
298 setvar("PWD", curdir, VEXPORT);
299 INTON;
300 }
301
302 /*
303 * Posix says the default should be 'pwd -L' (as below), however
304 * the 'cd' command (above) does something much nearer to the
305 * posix 'cd -P' (not the posix default of 'cd -L').
306 * If 'cd' is changed to support -P/L then the default here
307 * needs to be revisited if the historic behaviour is to be kept.
308 */
309
310 int
311 pwdcmd(int argc, char **argv)
312 {
313 int i;
314 char opt = 'L';
315
316 while ((i = nextopt("LP")) != '\0')
317 opt = i;
318 if (*argptr)
319 error("unexpected argument");
320
321 if (opt == 'L')
322 getpwd(0);
323 else
324 find_curdir(0);
325
326 setvar("PWD", curdir, VEXPORT);
327 out1str(curdir);
328 out1c('\n');
329 return 0;
330 }
331
332
333
334 void
335 initpwd(void)
336 {
337 getpwd(0);
338 setvar("PWD", curdir, VEXPORT);
339 }
340
341 #define MAXPWD 256
342
343 /*
344 * Find out what the current directory is. If we already know the current
345 * directory, this routine returns immediately.
346 */
347 void
348 getpwd(int noerror)
349 {
350 char *pwd;
351 struct stat stdot, stpwd;
352 static int first = 1;
353
354 if (curdir)
355 return;
356
357 if (first) {
358 first = 0;
359 pwd = getenv("PWD");
360 if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
361 stat(pwd, &stpwd) != -1 &&
362 stdot.st_dev == stpwd.st_dev &&
363 stdot.st_ino == stpwd.st_ino) {
364 curdir = savestr(pwd);
365 return;
366 }
367 }
368
369 find_curdir(noerror);
370
371 return;
372 }
373
374 STATIC void
375 find_curdir(int noerror)
376 {
377 int i;
378 char *pwd;
379
380 /*
381 * Things are a bit complicated here; we could have just used
382 * getcwd, but traditionally getcwd is implemented using popen
383 * to /bin/pwd. This creates a problem for us, since we cannot
384 * keep track of the job if it is being ran behind our backs.
385 * So we re-implement getcwd(), and we suppress interrupts
386 * throughout the process. This is not completely safe, since
387 * the user can still break out of it by killing the pwd program.
388 * We still try to use getcwd for systems that we know have a
389 * c implementation of getcwd, that does not open a pipe to
390 * /bin/pwd.
391 */
392 #if defined(__NetBSD__) || defined(__SVR4)
393
394 for (i = MAXPWD;; i *= 2) {
395 pwd = stalloc(i);
396 if (getcwd(pwd, i) != NULL) {
397 curdir = savestr(pwd);
398 return;
399 }
400 stunalloc(pwd);
401 if (errno == ERANGE)
402 continue;
403 if (!noerror)
404 error("getcwd() failed: %s", strerror(errno));
405 return;
406 }
407 #else
408 {
409 char *p;
410 int status;
411 struct job *jp;
412 int pip[2];
413
414 pwd = stalloc(MAXPWD);
415 INTOFF;
416 if (pipe(pip) < 0)
417 error("Pipe call failed");
418 jp = makejob((union node *)NULL, 1);
419 if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
420 (void) close(pip[0]);
421 if (pip[1] != 1) {
422 close(1);
423 copyfd(pip[1], 1);
424 close(pip[1]);
425 }
426 (void) execl("/bin/pwd", "pwd", (char *)0);
427 error("Cannot exec /bin/pwd");
428 }
429 (void) close(pip[1]);
430 pip[1] = -1;
431 p = pwd;
432 while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0
433 || (i == -1 && errno == EINTR)) {
434 if (i > 0)
435 p += i;
436 }
437 (void) close(pip[0]);
438 pip[0] = -1;
439 status = waitforjob(jp);
440 if (status != 0)
441 error((char *)0);
442 if (i < 0 || p == pwd || p[-1] != '\n') {
443 if (noerror) {
444 INTON;
445 return;
446 }
447 error("pwd command failed");
448 }
449 p[-1] = '\0';
450 INTON;
451 curdir = savestr(pwd);
452 return;
453 }
454 #endif
455 }
456