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 #endif /* not lint */ 42 #include <sys/cdefs.h> 43 __FBSDID("$FreeBSD$"); 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 cdlogical(char *); 71 STATIC int cdphysical(char *); 72 STATIC int docd(char *, int, int); 73 STATIC char *getcomponent(void); 74 STATIC int updatepwd(char *); 75 76 char *curdir = NULL; /* current working directory */ 77 char *prevdir; /* previous working directory */ 78 STATIC char *cdcomppath; 79 80 int 81 cdcmd(int argc, char **argv) 82 { 83 char *dest; 84 char *path; 85 char *p; 86 struct stat statb; 87 int ch, phys, print = 0; 88 89 optreset = 1; optind = 1; /* initialize getopt */ 90 phys = 0; 91 while ((ch = getopt(argc, argv, "LP")) != -1) { 92 switch (ch) { 93 case 'L': 94 phys = 0; 95 break; 96 case 'P': 97 phys = 1; 98 break; 99 default: 100 error("unknown option: -%c", optopt); 101 break; 102 } 103 } 104 argc -= optind; 105 argv += optind; 106 107 if (argc > 1) 108 error("too many arguments"); 109 110 if ((dest = *argv) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) 111 error("HOME not set"); 112 if (*dest == '\0') 113 dest = "."; 114 if (dest[0] == '-' && dest[1] == '\0') { 115 dest = prevdir ? prevdir : curdir; 116 if (dest) 117 print = 1; 118 else 119 dest = "."; 120 } 121 if (*dest == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) 122 path = nullstr; 123 while ((p = padvance(&path, dest)) != NULL) { 124 if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { 125 if (!print) { 126 /* 127 * XXX - rethink 128 */ 129 if (p[0] == '.' && p[1] == '/' && p[2] != '\0') 130 p += 2; 131 print = strcmp(p, dest); 132 } 133 if (docd(p, print, phys) >= 0) 134 return 0; 135 } 136 } 137 error("can't cd to %s", dest); 138 /*NOTREACHED*/ 139 return 0; 140 } 141 142 143 /* 144 * Actually change the directory. In an interactive shell, print the 145 * directory name if "print" is nonzero. 146 */ 147 STATIC int 148 docd(char *dest, int print, int phys) 149 { 150 151 TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); 152 153 /* If logical cd fails, fall back to physical. */ 154 if ((phys || cdlogical(dest) < 0) && cdphysical(dest) < 0) 155 return (-1); 156 157 if (print && iflag && curdir) 158 out1fmt("%s\n", curdir); 159 160 return 0; 161 } 162 163 STATIC int 164 cdlogical(char *dest) 165 { 166 char *p; 167 char *q; 168 char *component; 169 struct stat statb; 170 int first; 171 int badstat; 172 173 /* 174 * Check each component of the path. If we find a symlink or 175 * something we can't stat, clear curdir to force a getcwd() 176 * next time we get the value of the current directory. 177 */ 178 badstat = 0; 179 cdcomppath = stalloc(strlen(dest) + 1); 180 scopy(dest, cdcomppath); 181 STARTSTACKSTR(p); 182 if (*dest == '/') { 183 STPUTC('/', p); 184 cdcomppath++; 185 } 186 first = 1; 187 while ((q = getcomponent()) != NULL) { 188 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) 189 continue; 190 if (! first) 191 STPUTC('/', p); 192 first = 0; 193 component = q; 194 while (*q) 195 STPUTC(*q++, p); 196 if (equal(component, "..")) 197 continue; 198 STACKSTRNUL(p); 199 if (lstat(stackblock(), &statb) < 0) { 200 badstat = 1; 201 break; 202 } 203 } 204 205 INTOFF; 206 if (updatepwd(badstat ? NULL : dest) < 0 || chdir(curdir) < 0) { 207 INTON; 208 return (-1); 209 } 210 INTON; 211 return (0); 212 } 213 214 STATIC int 215 cdphysical(char *dest) 216 { 217 218 INTOFF; 219 if (chdir(dest) < 0 || updatepwd(NULL) < 0) { 220 INTON; 221 return (-1); 222 } 223 INTON; 224 return (0); 225 } 226 227 /* 228 * Get the next component of the path name pointed to by cdcomppath. 229 * This routine overwrites the string pointed to by cdcomppath. 230 */ 231 STATIC char * 232 getcomponent(void) 233 { 234 char *p; 235 char *start; 236 237 if ((p = cdcomppath) == NULL) 238 return NULL; 239 start = cdcomppath; 240 while (*p != '/' && *p != '\0') 241 p++; 242 if (*p == '\0') { 243 cdcomppath = NULL; 244 } else { 245 *p++ = '\0'; 246 cdcomppath = p; 247 } 248 return start; 249 } 250 251 252 /* 253 * Update curdir (the name of the current directory) in response to a 254 * cd command. We also call hashcd to let the routines in exec.c know 255 * that the current directory has changed. 256 */ 257 STATIC int 258 updatepwd(char *dir) 259 { 260 char *new; 261 char *p; 262 263 hashcd(); /* update command hash table */ 264 265 /* 266 * If our argument is NULL, we don't know the current directory 267 * any more because we traversed a symbolic link or something 268 * we couldn't stat(). 269 */ 270 if (dir == NULL || curdir == NULL) { 271 if (prevdir) 272 ckfree(prevdir); 273 INTOFF; 274 prevdir = curdir; 275 curdir = NULL; 276 if (getpwd() == NULL) { 277 INTON; 278 return (-1); 279 } 280 setvar("PWD", curdir, VEXPORT); 281 setvar("OLDPWD", prevdir, VEXPORT); 282 INTON; 283 return (0); 284 } 285 cdcomppath = stalloc(strlen(dir) + 1); 286 scopy(dir, cdcomppath); 287 STARTSTACKSTR(new); 288 if (*dir != '/') { 289 p = curdir; 290 while (*p) 291 STPUTC(*p++, new); 292 if (p[-1] == '/') 293 STUNPUTC(new); 294 } 295 while ((p = getcomponent()) != NULL) { 296 if (equal(p, "..")) { 297 while (new > stackblock() && (STUNPUTC(new), *new) != '/'); 298 } else if (*p != '\0' && ! equal(p, ".")) { 299 STPUTC('/', new); 300 while (*p) 301 STPUTC(*p++, new); 302 } 303 } 304 if (new == stackblock()) 305 STPUTC('/', new); 306 STACKSTRNUL(new); 307 INTOFF; 308 if (prevdir) 309 ckfree(prevdir); 310 prevdir = curdir; 311 curdir = savestr(stackblock()); 312 setvar("PWD", curdir, VEXPORT); 313 setvar("OLDPWD", prevdir, VEXPORT); 314 INTON; 315 316 return (0); 317 } 318 319 #define MAXPWD 256 320 321 int 322 pwdcmd(int argc __unused, char **argv __unused) 323 { 324 char buf[MAXPWD]; 325 int ch, phys; 326 327 optreset = 1; optind = 1; /* initialize getopt */ 328 phys = 0; 329 while ((ch = getopt(argc, argv, "LP")) != -1) { 330 switch (ch) { 331 case 'L': 332 phys = 0; 333 break; 334 case 'P': 335 phys = 1; 336 break; 337 default: 338 error("unknown option: -%c", optopt); 339 break; 340 } 341 } 342 argc -= optind; 343 argv += optind; 344 345 if (argc != 0) 346 error("too many arguments"); 347 348 if (!phys && getpwd()) { 349 out1str(curdir); 350 out1c('\n'); 351 } else { 352 if (getcwd(buf, sizeof(buf)) == NULL) 353 error(".: %s", strerror(errno)); 354 out1str(buf); 355 out1c('\n'); 356 } 357 358 return 0; 359 } 360 361 /* 362 * Find out what the current directory is. If we already know the current 363 * directory, this routine returns immediately. 364 */ 365 char * 366 getpwd(void) 367 { 368 char buf[MAXPWD]; 369 370 if (curdir) 371 return curdir; 372 /* 373 * Things are a bit complicated here; we could have just used 374 * getcwd, but traditionally getcwd is implemented using popen 375 * to /bin/pwd. This creates a problem for us, since we cannot 376 * keep track of the job if it is being ran behind our backs. 377 * So we re-implement getcwd(), and we suppress interrupts 378 * throughout the process. This is not completely safe, since 379 * the user can still break out of it by killing the pwd program. 380 * We still try to use getcwd for systems that we know have a 381 * c implementation of getcwd, that does not open a pipe to 382 * /bin/pwd. 383 */ 384 #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__SVR4) 385 386 if (getcwd(buf, sizeof(buf)) == NULL) { 387 char *pwd = getenv("PWD"); 388 struct stat stdot, stpwd; 389 390 if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && 391 stat(pwd, &stpwd) != -1 && 392 stdot.st_dev == stpwd.st_dev && 393 stdot.st_ino == stpwd.st_ino) { 394 curdir = savestr(pwd); 395 return curdir; 396 } 397 return NULL; 398 } 399 curdir = savestr(buf); 400 #else 401 { 402 char *p; 403 int i; 404 int status; 405 struct job *jp; 406 int pip[2]; 407 408 INTOFF; 409 if (pipe(pip) < 0) 410 error("Pipe call failed: %s", strerror(errno)); 411 jp = makejob((union node *)NULL, 1); 412 if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) { 413 (void) close(pip[0]); 414 if (pip[1] != 1) { 415 close(1); 416 copyfd(pip[1], 1); 417 close(pip[1]); 418 } 419 (void) execl("/bin/pwd", "pwd", (char *)0); 420 error("Cannot exec /bin/pwd"); 421 } 422 (void) close(pip[1]); 423 pip[1] = -1; 424 p = buf; 425 while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0 426 || (i == -1 && errno == EINTR)) { 427 if (i > 0) 428 p += i; 429 } 430 (void) close(pip[0]); 431 pip[0] = -1; 432 status = waitforjob(jp); 433 if (status != 0) 434 error((char *)0); 435 if (i < 0 || p == buf || p[-1] != '\n') 436 error("pwd command failed"); 437 p[-1] = '\0'; 438 } 439 curdir = savestr(buf); 440 INTON; 441 #endif 442 return curdir; 443 } 444