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