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