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