/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980 Regents of the University of California. * All rights reserved. The Berkeley Software License Agreement * specifies the terms and conditions for redistribution. */ #pragma ident "%Z%%M% %I% %E% SMI" #include "sh.h" #include "sh.dir.h" #include "sh.tconst.h" /* * C Shell - directory management */ struct directory *dfind(tchar *); tchar *dfollow(tchar *); tchar *dcanon(tchar *, tchar *); void dtildepr(tchar *, tchar *); void dfree(struct directory *); void dnewcwd(struct directory *); struct directory dhead; /* "head" of loop */ int printd; /* force name to be printed */ static tchar *fakev[] = { S_dirs, NOSTR }; /* * dinit - initialize current working directory */ void dinit(tchar *hp) { tchar *cp; struct directory *dp; tchar path[MAXPATHLEN]; #ifdef TRACE tprintf("TRACE- dinit()\n"); #endif /* * If this is a login shell, we should have a home directory. But, * if we got here via 'su - ' where the user has no directory * in their passwd file, then su has passed HOME=, so hp is * non-null, but has zero length. Thus, we do not know the current * working directory based on the home directory. */ if (loginsh && hp && *hp) cp = hp; else { cp = getwd_(path); if (cp == NULL) { printf("Warning: cannot determine current directory\n"); cp = S_DOT; } } dp = (struct directory *)xcalloc(sizeof (struct directory), 1); dp->di_name = savestr(cp); dp->di_count = 0; dhead.di_next = dhead.di_prev = dp; dp->di_next = dp->di_prev = &dhead; printd = 0; dnewcwd(dp); } /* * dodirs - list all directories in directory loop */ void dodirs(tchar **v) { struct directory *dp; bool lflag; tchar *hp = value(S_home); #ifdef TRACE tprintf("TRACE- dodirs()\n"); #endif if (*hp == '\0') hp = NOSTR; if (*++v != NOSTR) if (eq(*v, S_MINl /* "-l" */) && *++v == NOSTR) lflag = 1; else error("Usage: dirs [ -l ]"); else lflag = 0; dp = dcwd; do { if (dp == &dhead) continue; if (!lflag && hp != NOSTR) { dtildepr(hp, dp->di_name); } else printf("%t", dp->di_name); printf(" "); } while ((dp = dp->di_prev) != dcwd); printf("\n"); } void dtildepr(tchar *home, tchar *dir) { #ifdef TRACE tprintf("TRACE- dtildepr()\n"); #endif if (!eq(home, S_SLASH /* "/" */) && prefix(home, dir)) printf("~%t", dir + strlen_(home)); else printf("%t", dir); } /* * dochngd - implement chdir command. */ void dochngd(tchar **v) { tchar *cp; struct directory *dp; #ifdef TRACE tprintf("TRACE- dochngd()\n"); #endif printd = 0; if (*++v == NOSTR) { if ((cp = value(S_home)) == NOSTR || *cp == 0) bferr("No home directory"); if (chdir_(cp) < 0) bferr("Can't change to home directory"); cp = savestr(cp); } else if ((dp = dfind(*v)) != 0) { printd = 1; if (chdir_(dp->di_name) < 0) Perror(dp->di_name); dcwd->di_prev->di_next = dcwd->di_next; dcwd->di_next->di_prev = dcwd->di_prev; goto flushcwd; } else cp = dfollow(*v); dp = (struct directory *)xcalloc(sizeof (struct directory), 1); dp->di_name = cp; dp->di_count = 0; dp->di_next = dcwd->di_next; dp->di_prev = dcwd->di_prev; dp->di_prev->di_next = dp; dp->di_next->di_prev = dp; flushcwd: dfree(dcwd); dnewcwd(dp); } /* * dfollow - change to arg directory; fall back on cdpath if not valid */ tchar * dfollow(tchar *cp) { tchar *dp; struct varent *c; int cdhashval, cdhashval1; int index; int slash; /* slashes in the argument */ tchar *fullpath; tchar *slashcp; /* cp string prepended with a slash */ #ifdef TRACE tprintf("TRACE- dfollow()\n"); #endif cp = globone(cp); if (chdir_(cp) >= 0) goto gotcha; /* * If the directory argument has a slash in it, * for example, directory/directory, then can't * find that in the cache table. */ slash = any('/', cp); /* * Try interpreting wrt successive components of cdpath. * cdpath caching is turned off or directory argument * has a slash in it. */ if (cp[0] != '/' && !prefix(S_DOTSLA /* "./" */, cp) && !prefix(S_DOTDOTSLA /* "../" */, cp) && (c = adrof(S_cdpath)) && (!havhash2 || slash)) { tchar **cdp; tchar *p; tchar buf[MAXPATHLEN]; for (cdp = c->vec; *cdp; cdp++) { for (dp = buf, p = *cdp; *dp++ = *p++; ) ; dp[-1] = '/'; for (p = cp; *dp++ = *p++; ) ; if (chdir_(buf) >= 0) { printd = 1; xfree(cp); cp = savestr(buf); goto gotcha; } } } /* cdpath caching turned on */ if (cp[0] != '/' && !prefix(S_DOTSLA /* "./" */, cp) && !prefix(S_DOTDOTSLA /* "../" */, cp) && (c = adrof(S_cdpath)) && havhash2 && !slash) { tchar **pv; /* If no cdpath or no paths in cdpath, leave */ if (c == 0 || c->vec[0] == 0) pv = justabs; else pv = c->vec; slashcp = strspl(S_SLASH, cp); cdhashval = hashname(cp); /* index points to next path component to test */ index = 0; /* * Look at each path in cdpath until get a match. * Only look at those path beginning with a slash */ do { /* only check cache for absolute pathnames */ if (pv[0][0] == '/') { cdhashval1 = hash(cdhashval, index); if (bit(xhash2, cdhashval1)) { /* * concatenate found path with * arg directory */ fullpath = strspl(*pv, slashcp); if (chdir_(fullpath) >= 0) { printd = 1; xfree(cp); cp = savestr(fullpath); xfree(slashcp); xfree(fullpath); goto gotcha; } } } /* * relative pathnames are not cached, and must be * checked manually */ else { tchar *p; tchar buf[MAXPATHLEN]; for (dp = buf, p = *pv; *dp++ = *p++; ) ; dp[-1] = '/'; for (p = cp; *dp++ = *p++; ) ; if (chdir_(buf) >= 0) { printd = 1; xfree(cp); cp = savestr(buf); xfree(slashcp); goto gotcha; } } pv++; index++; } while (*pv); } /* * Try dereferencing the variable named by the argument. */ dp = value(cp); if ((dp[0] == '/' || dp[0] == '.') && chdir_(dp) >= 0) { xfree(cp); cp = savestr(dp); printd = 1; goto gotcha; } xfree(cp); /* XXX, use after free */ Perror(cp); gotcha: if (*cp != '/') { tchar *p, *q; int cwdlen; int len; /* * All in the name of efficiency? */ if ((cwdlen = (strlen_(dcwd->di_name))) == 1) { if (*dcwd->di_name == '/') /* root */ cwdlen = 0; else { /* * if we are here, when the shell started * it was unable to getwd(), lets try it again */ tchar path[MAXPATHLEN]; p = getwd_(path); if (p == NULL) error("cannot determine current directory"); else { xfree(dcwd->di_name); dcwd->di_name = savestr(p); xfree(cp); cp = savestr(p); return dcanon(cp, cp); } } } /* * * for (p = cp; *p++;) * ; * dp = (tchar *)xalloc((unsigned) (cwdlen + (p - cp) + 1)*sizeof (tchar)) */ len = strlen_(cp); dp = (tchar *)xalloc((unsigned)(cwdlen + len + 2) * sizeof (tchar)); for (p = dp, q = dcwd->di_name; *p++ = *q++; ) ; if (cwdlen) p[-1] = '/'; else p--; /* don't add a / after root */ for (q = cp; *p++ = *q++; ) ; xfree(cp); cp = dp; dp += cwdlen; } else dp = cp; return dcanon(cp, dp); } /* * dopushd - push new directory onto directory stack. * with no arguments exchange top and second. * with numeric argument (+n) bring it to top. */ void dopushd(tchar **v) { struct directory *dp; #ifdef TRACE tprintf("TRACE- dopushd()\n"); #endif printd = 1; if (*++v == NOSTR) { if ((dp = dcwd->di_prev) == &dhead) dp = dhead.di_prev; if (dp == dcwd) bferr("No other directory"); if (chdir_(dp->di_name) < 0) Perror(dp->di_name); dp->di_prev->di_next = dp->di_next; dp->di_next->di_prev = dp->di_prev; dp->di_next = dcwd->di_next; dp->di_prev = dcwd; dcwd->di_next->di_prev = dp; dcwd->di_next = dp; } else if (dp = dfind(*v)) { if (chdir_(dp->di_name) < 0) Perror(dp->di_name); } else { tchar *cp; cp = dfollow(*v); dp = (struct directory *)xcalloc(sizeof (struct directory), 1); dp->di_name = cp; dp->di_count = 0; dp->di_prev = dcwd; dp->di_next = dcwd->di_next; dcwd->di_next = dp; dp->di_next->di_prev = dp; } dnewcwd(dp); } /* * dfind - find a directory if specified by numeric (+n) argument */ struct directory * dfind(tchar *cp) { struct directory *dp; int i; tchar *ep; #ifdef TRACE tprintf("TRACE- dfind()\n"); #endif if (*cp++ != '+') return (0); for (ep = cp; digit(*ep); ep++) continue; if (*ep) return (0); i = getn(cp); if (i <= 0) return (0); for (dp = dcwd; i != 0; i--) { if ((dp = dp->di_prev) == &dhead) dp = dp->di_prev; if (dp == dcwd) bferr("Directory stack not that deep"); } return (dp); } /* * dopopd - pop a directory out of the directory stack * with a numeric argument just discard it. */ void dopopd(tchar **v) { struct directory *dp, *p; #ifdef TRACE tprintf("TRACE- dopopd()\n"); #endif printd = 1; if (*++v == NOSTR) dp = dcwd; else if ((dp = dfind(*v)) == 0) bferr("Invalid argument"); if (dp->di_prev == &dhead && dp->di_next == &dhead) bferr("Directory stack empty"); if (dp == dcwd) { if ((p = dp->di_prev) == &dhead) p = dhead.di_prev; if (chdir_(p->di_name) < 0) Perror(p->di_name); } dp->di_prev->di_next = dp->di_next; dp->di_next->di_prev = dp->di_prev; if (dp == dcwd) dnewcwd(p); else dodirs(fakev); dfree(dp); } /* * dfree - free the directory (or keep it if it still has ref count) */ void dfree(struct directory *dp) { #ifdef TRACE tprintf("TRACE- dfree()\n"); #endif if (dp->di_count != 0) dp->di_next = dp->di_prev = 0; else xfree(dp->di_name), xfree((tchar *)dp); } /* * dcanon - canonicalize the pathname, removing excess ./ and ../ etc. * We are of course assuming that the file system is standardly * constructed (always have ..'s, directories have links). * * If the hardpaths shell variable is set, resolve the * resulting pathname to contain no symbolic link components. */ tchar * dcanon(tchar *cp, tchar *p) { tchar *sp; /* rightmost component currently under consideration */ tchar *p1, /* general purpose */ *p2; bool slash, dotdot, hardpaths; #ifdef TRACE tprintf("TRACE- dcannon()\n"); #endif if (*cp != '/') abort(); if (hardpaths = (adrof(S_hardpaths) != NULL)) { /* * Be paranoid: don't trust the initial prefix * to be symlink-free. */ p = cp; } /* * Loop invariant: cp points to the overall path start, * p to its as yet uncanonicalized trailing suffix. */ while (*p) { /* for each component */ sp = p; /* save slash address */ while (*++p == '/') /* flush extra slashes */ ; if (p != ++sp) for (p1 = sp, p2 = p; *p1++ = *p2++; ) ; p = sp; /* save start of component */ slash = 0; if (*p) while (*++p) /* find next slash or end of path */ if (*p == '/') { slash = 1; *p = '\0'; break; } if (*sp == '\0') { /* component is null */ if (--sp == cp) /* if path is one tchar (i.e. /) */ break; else *sp = '\0'; continue; } if (sp[0] == '.' && sp[1] == '\0') { /* Squeeze out component consisting of "." */ if (slash) { for (p1 = sp, p2 = p + 1; *p1++ = *p2++; ) ; p = --sp; } else if (--sp != cp) *sp = '\0'; continue; } /* * At this point we have a path of the form "x/yz", * where "x" is null or rooted at "/", "y" is a single * component, and "z" is possibly null. The pointer cp * points to the start of "x", sp to the start of "y", * and p to the beginning of "z", which has been forced * to a null. */ /* * Process symbolic link component. Provided that either * the hardpaths shell variable is set or "y" is really * ".." we replace the symlink with its contents. The * second condition for replacement is necessary to make * the command "cd x/.." produce the same results as the * sequence "cd x; cd ..". * * Note that the two conditions correspond to different * potential symlinks. When hardpaths is set, we must * check "x/y"; otherwise, when "y" is known to be "..", * we check "x". */ dotdot = sp[0] == '.' && sp[1] == '.' && sp[2] == '\0'; if (hardpaths || dotdot) { tchar link[MAXPATHLEN]; int cc; tchar *newcp; /* * Isolate the end of the component that is to * be checked for symlink-hood. */ sp--; if (! hardpaths) *sp = '\0'; /* * See whether the component is really a symlink by * trying to read it. If the read succeeds, it is. */ if ((hardpaths || sp > cp) && (cc = readlink_(cp, link, MAXPATHLEN)) >= 0) { /* * readlink_ put null, so we don't need this. */ /* link[cc] = '\0'; */ /* Restore path. */ if (slash) *p = '/'; /* * Point p at the start of the trailing * path following the symlink component. * It's already there is hardpaths is set. */ if (! hardpaths) { /* Restore path as well. */ *(p = sp) = '/'; } /* * Find length of p. */ for (p1 = p; *p1++; ) ; if (*link != '/') { /* * Relative path: replace the symlink * component with its value. First, * set sp to point to the slash at * its beginning. If hardpaths is * set, this is already the case. */ if (! hardpaths) { while (*--sp != '/') ; } /* * Terminate the leading part of the * path, including trailing slash. */ sp++; *sp = '\0'; /* * New length is: "x/" + link + "z" */ p1 = newcp = (tchar *)xalloc((unsigned) ((sp - cp) + cc + (p1 - p)) * sizeof (tchar)); /* * Copy new path into newcp */ for (p2 = cp; *p1++ = *p2++; ) ; for (p1--, p2 = link; *p1++ = *p2++; ) ; for (p1--, p2 = p; *p1++ = *p2++; ) ; /* * Restart canonicalization at * expanded "/y". */ p = sp - cp - 1 + newcp; } else { /* * New length is: link + "z" */ p1 = newcp = (tchar *)xalloc((unsigned) (cc + (p1 - p))*sizeof (tchar)); /* * Copy new path into newcp */ for (p2 = link; *p1++ = *p2++; ) ; for (p1--, p2 = p; *p1++ = *p2++; ) ; /* * Restart canonicalization at beginning */ p = newcp; } xfree(cp); cp = newcp; continue; /* canonicalize the link */ } /* The component wasn't a symlink after all. */ if (! hardpaths) *sp = '/'; } if (dotdot) { if (sp != cp) while (*--sp != '/') ; if (slash) { for (p1 = sp + 1, p2 = p + 1; *p1++ = *p2++; ) ; p = sp; } else if (cp == sp) *++sp = '\0'; else *sp = '\0'; continue; } if (slash) *p = '/'; } return cp; } /* * dnewcwd - make a new directory in the loop the current one * and export its name to the PWD environment variable. */ void dnewcwd(struct directory *dp) { #ifdef TRACE tprintf("TRACE- dnewcwd()\n"); #endif dcwd = dp; #ifdef notdef /* * If we have a fast version of getwd available * and hardpaths is set, it would be reasonable * here to verify that dcwd->di_name really does * name the current directory. Later... */ #endif /* notdef */ didchdir = 1; set(S_cwd, savestr(dcwd->di_name)); didchdir = 0; local_setenv(S_PWD, dcwd->di_name); if (printd) dodirs(fakev); }