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