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