type
[unix-history] / usr / src / bin / sh / cd.c
CommitLineData
d003fee2
KB
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
ddba57cd 12static char sccsid[] = "@(#)cd.c 5.2 (Berkeley) %G%";
d003fee2
KB
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__
34STATIC int docd(char *, int);
35STATIC void updatepwd(char *);
36STATIC void getpwd(void);
37STATIC char *getcomponent(void);
38#else
39STATIC int docd();
40STATIC void updatepwd();
41STATIC void getpwd();
42STATIC char *getcomponent();
43#endif
44
45
46char *curdir; /* current working directory */
47STATIC char *cdcomppath;
48
49#if UDIR
50extern int didudir; /* set if /u/logname expanded */
51#endif
52
53
54int
55cdcmd(argc, argv) char **argv; {
56 char *dest;
57 char *path;
58 char *p;
59 struct stat statb;
60 char *padvance();
61
ddba57cd
MT
62 nextopt(nullstr);
63 if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
d003fee2
KB
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
88STATIC int
89docd(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
112STATIC int
113docd(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
130top:
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
210STATIC char *
211getcomponent() {
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
237void hashcd();
238
239STATIC void
240updatepwd(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
278int
279pwdcmd(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
297STATIC void
298getpwd() {
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}