/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ #include "jove.h" RCS("$Id: dirs.c,v 14.31.0.12 1994/06/23 02:46:24 tom Exp tom $") #include "io.h" char *HomeDir; /* Home directory */ int HomeLen ZERO; /* Length of home directory string */ #ifdef CHDIR #if (NDIRS-0 <= 0) # define NDIRS 5 #endif private char *dir_stack[NDIRS] ZERO; private int DirSP ZERO; /* Directory stack pointer */ #ifndef CHDIR_EXT /* Fixed-size stack */ # define DirStack dir_stack # define DirStkSize NDIRS #else /* Expandable stack */ private char **DirStack = dir_stack; private int DirStkSize = NDIRS; DEF_INT( "dirs-unique", DUnique, V_BOOL ) _IF(def CHDIR_EXT)_IF(def PRIVATE) ZERO; DEF_INT( "dirs-extract", DExtract, V_BOOL ) _IF(def CHDIR_EXT)_IF(def PRIVATE) ZERO; private char *old_pwd ZERO; /* Previous working directory */ #endif #define PWD (DirStack[DirSP]) /* * Change to new working directory. * Note that the new working directory's name is NOT recorded in the * directory stack yet, since that depends too much on what we're doing. * (cd, pushd, or popd) */ private void do_chdir __(( const char *newdir )); private void do_chdir(newdir) register const char *newdir; { if (chdir(newdir) != 0) complain("%f: cannot change into %s.", newdir); #ifdef CHDIR_EXT if (strcmp(PWD, newdir) != 0) set_str(&old_pwd, PWD); #endif updmodline(); #ifdef MENUS /* since this may affect the file names in buffer menu */ Bufchange++; #endif } #ifdef CHDIR_EXT /* * Expand the directory stack. */ private void dirs_grow __(( void )); private void dirs_grow() { register char **newstack; newstack = (char **) emalloc((DirStkSize + NDIRS) * sizeof(char *)); byte_copy(DirStack, newstack, DirStkSize * sizeof(char *)); bzero(newstack + DirStkSize, NDIRS * sizeof(char *)); if (!ISSTATIC(DirStack)) free(DirStack); DirStack = newstack; DirStkSize += NDIRS; } /* * Find a directory in the stack. * Returns index of the duplicate in the stack if found, and -1 if missing. */ private int dirs_find __(( const char *_(dir) )); private int dirs_find(dir) const char *dir; { register char **dirp = &PWD + 1; while (strcmp(dir, *--dirp) != 0) { if (dirp == &DirStack[0]) return -1; } return (dirp - DirStack); } /* * Remove an entry from the stack. * Note: this should not be the top entry (PWD). */ private void dirs_delete __(( int entry )); private void dirs_delete(entry) { register char **dirp = &DirStack[entry], **top = &PWD; --DirSP; free(dirp[0]); while (dirp < top) { dirp[0] = dirp[1]; ++dirp; } dirp[0] = NULL; } /* * Initialize directory stack and old_pwd, possibly from environment. * The environment variable DIRS is supposed to contain the directory stack, * as listed with the csh(1) command `dirs' (or, preferably, `dirs -l' for * absolute pathnames if your version of csh supports it; even though JOVE * can handle leading tildes, many other programs cannot.) * This assumes that a valid PWD has already been set up. */ public void dirs_init() { register char *dir; if ((dir = getenv("DIRS")) && *dir) { char dirbuf[FILESIZE]; register char *sep; register char **dirp; do { if (sep = index(dir, ' ')) *sep++ = '\0'; if (*dir == '\0') /* catch leading spaces. */ continue; dir = PathParse(dir, dirbuf); /* to resolve "~/foo". */ if (True(DUnique) && dirs_find(dir) >= 0) continue; if (DirSP == 0 && strcmp(PWD, dir) == 0) continue; /* check that is is still valid. */ if (chdir(dir) != 0) continue; /* Add to the bottom, pushing the existing stack up. */ if (++DirSP >= DirStkSize) dirs_grow(); for (dirp = &DirStack[DirSP]; dirp > &DirStack[0]; --dirp) dirp[0] = dirp[-1]; dirp[0] = copystr(dir); } while ((dir = sep) && (*--sep = ' ', TRUE)); /* restore current working directory. */ chdir(PWD); } if ((dir = getenv("OLDPWD")) == NULL) /* bash(1) hack. */ dir = HomeDir; set_str(&old_pwd, dir); } /* * Rotate the directory stack `offset' times (for pushd); or * Delete the `offset'th entry in the stack (for popd). * Entry number `offset' is relative to the current directory, i.e., * the top of the stack, and may be positive or negative. * Returns whether some work has been done (it refuses to clobber current dir). */ private int dirs_rotate __(( int _(offset), int _(delete_only) )); private int dirs_rotate(offset, delete_only) { register int num; if ((num = offset) < 0) num += DirSP + 1; if ((unsigned) num > DirSP) complain("%f: out of range; max of %d entries.", DirSP); if (num == 0) return NO; if (delete_only) { dirs_delete(DirSP - num); } else { if (True(DExtract)) { /* Extract directory to top of stack. */ char extracteddir[FILESIZE]; num = DirSP - num; /* Copy the extracted dirname, since the original is destroyed by dirs_delete(). We have to delete _before_ DirSP is incremented and CWD is set since otherwise we may run out of dirstack space (either this or we must use dirs_grow). */ strcpy(extracteddir, DirStack[num]); dirs_delete(num); DirSP++; setCWD(extracteddir); } else { /* Rotate stack upwards `num' times. */ do { register char **dirp = &PWD; register char *tmp = dirp[0]; while (dirp > &DirStack[0]) { dirp[0] = dirp[-1]; --dirp; } dirp[0] = tmp; } while (--num); } do_chdir(PWD); } return YES; } #else /* !CHDIR_EXT */ # define dirs_grow() complain("%f: full stack; max of %d pushes.", NDIRS-1) #endif /* !CHDIR_EXT */ char * pwd() { return PWD; } #ifndef rel_name /* * rel_name(fname) returns the pathname relative to current directory, if * possible. The reason for this is that `fname' is usually an absolute * pathname, which can be slow when the pathname is long and there are * lots of symbolic links along the way (which has become very common in * my experience). So, this speeds up access to files in the current * directory or its subdirectories. It will not speed up things like * "../scm/foo.scm" simple because by the time we get here that's already * been expanded to an absolute pathname. But this is a start. */ char * rel_name(fname) register const char *fname; { register char *pwd; register int n; if ((pwd = PWD) && (pwd[n = numcomp(fname, pwd)] == '\0') && (fname[n] == SLASH)) fname += n + 1; return (char *) fname; } #endif char * pr_name(filename) const char *filename; { static char result[FILESIZE]; register const char *fname; if (fname = filename) { register char *pwd = PWD; register int n; if ((pwd[n = numcomp(fname, pwd)] == '\0') && (fname[n] == SLASH)) fname += n + 1; else if ((n = HomeLen) > 1 && (numcomp(fname, HomeDir) == n)) { sprintf(result, "~%s", fname + n); fname = result; } } return (char *) fname; /* return entire path name */ } void setCWD(d) const char *d; { set_str(&PWD, d); } #ifndef TINY extern int ask_dir_only; #endif DEF_CMD( "cd", Cd, NO ) _IF(def CHDIR) { char dirbuf[FILESIZE]; register const char *dir; #ifndef TINY ask_dir_only = YES; #endif dir = ask_file((char *) 0, PWD, dirbuf); do_chdir(dir); setCWD(dir); } DEF_CMD( "dirs", prDIRS, NO ) _IF(def CHDIR) { register char **dirp = &PWD + 1; #ifdef CHDIR_EXT if (exp_p) { register int n = 0; TOstart("*Dirs*", YES); do { Typeout("%2d %s", n++, pr_name(*--dirp)); } while (dirp > &DirStack[0]); TOstop(); return; } #endif s_mess(ProcFmt); do { add_mess("%s ", pr_name(*--dirp)); } while (dirp > &DirStack[0]); } DEF_CMD( "pwd", prCWD, NO ) _IF(def CHDIR) { s_mess("%N: %f => %s", PWD); } /* * Pop top entry off directory stack, or swap top two entries. */ private void popd_or_swapd __(( int _(swap) )); private void popd_or_swapd(swap) { register char **top; register char *newdir, *olddir; if (DirSP == 0) complain("%f: no other directory."); top = &PWD; olddir = *top; *top = NULL; newdir = *--top; *top = olddir; /* in case do_chdir() fails. */ --DirSP; do_chdir(newdir); if (swap) ++DirSP, *top++; else free(olddir); *top = newdir; } #define POPD() popd_or_swapd(NO) #define SWAPD() popd_or_swapd(YES) DEF_CMD( "pushd", Pushd, NO ) _IF(def CHDIR) { #ifdef CHDIR_EXT if (exp_p) { /* equivalent to csh's "pushd +n" */ if (dirs_rotate(exp, NO) == 0) SWAPD(); } else { #endif char dirbuf[FILESIZE]; register char *newdir; #ifndef TINY ask_dir_only = YES; #endif newdir = ask_file((char *) 0, NullStr, dirbuf); if (*newdir == '\0') { /* Wants to swap top two entries */ SWAPD(); } else { #ifdef CHDIR_EXT register char *base; register int num; /* Quick hack to get "+n" or "-n" */ if ((base = basename(newdir)) == pr_name(newdir) && (base[0] == '+' || base[0] == '-') && (num = chr_to_int(&base[1], 10, YES)) >= 0) { /* {{NOTE: chr_to_int("",...) returns 0!}} */ if (base[1] == '\0') ++num; if (base[0] == '-') num = -num; dirs_rotate(num, NO); } else { if (True(DUnique)) { while ((num = dirs_find(newdir)) >= 0) dirs_delete(num); } #endif if (DirSP >= DirStkSize - 1) dirs_grow(); do_chdir(newdir); ++DirSP; setCWD(newdir); #ifdef CHDIR_EXT } #endif } #ifdef CHDIR_EXT } exp_p = NO; #endif prDIRS(); } DEF_CMD( "popd", Popd, NO ) _IF(def CHDIR) { if (DirSP == 0) complain("%f: directory stack is empty."); #ifdef CHDIR_EXT /* equivalent to csh's "popd +n" */ if (!(exp_p && dirs_rotate(exp, YES))) #endif POPD(); #ifdef CHDIR_EXT exp_p = NO; #endif prDIRS(); } char * dfollow(file, into) const char *file; char *into; { register char *dp = into; register const char *sp = file; register int dots; register char *ep = &dp[FILESIZE]; #if unix # define base into #else register char *base = dp; #endif #if vms /* first convert filename to UNIX syntax */ char *vms2ux __(( char *_(uxname), const char *_(vmsname) )); char tmp[FILESIZE]; if ((sp = vms2ux(tmp, sp)) == NULL) complain("\"%s\" [Bad file specification]", file); #endif if (ISDIRSEP(*sp)) { /* Absolute pathname */ #ifndef DOS *dp++ = *sp++; #else if (PWD[1] == ':') { /* Add current drive */ *dp++ = *PWD; *dp++ = ':'; } sp++, *dp++ = SLASH; #endif } else { #ifdef DOS # ifdef ATARIST # define isadevice(name) (strlen(name) == 4 && (name)[3] == ':') # else extern char DevNames[]; /* from tune.c */ # define isadevice(name) (CaseIgnore++, \ dots = LookingAt(DevNames, name, 0), \ CaseIgnore--, \ dots) # endif if (*sp && sp[1] == ':') { /* {GEM,MS}DOS explicit drive */ *dp++ = *sp++; *dp++ = *sp++; } else if (isadevice(sp)) { /* {GEM,MS}DOS character device */ strcpy(dp, sp); return into; } else /* the appcpy statement */ #endif /* DOS */ #if vms /* check for network node name */ if (index(sp, '!') == index(sp, SLASH) - 1) { do *dp++ = *sp++; while (*sp != SLASH); base = dp; } else /* the appcpy statement */ #endif /* vms */ dp = appcpy(dp, PWD); /* copy pwd and advance dest ptr. */ if (dp[-1] != SLASH) *dp++ = SLASH; } #ifdef DOS if (base[1] == ':') /* skip drive name */ base += 2; #endif do { for (dots = 0; (*dp++ = *sp); ) { if (dp >= ep) /* some healthy paranoia. */ complain("Filename too long (max. %d): ...%s", FILESIZE, sp); if (*sp == SLASH) break; #if !(unix || vms) if (*sp == AltSlash) { dp[-1] = SLASH; /* normalize */ break; } #endif if (*sp++ == '.') dots++; else dots = 3; /* fake: no "." or ".." */ } if (dots < 3) { dp -= dots + 1; /* adjust for "//", "/./" */ if (dots == 2) { /* and "/../" (parent) */ /* * we are here --------------------v * "[...]/foo/../bar" * and want to end up here ----^ * "/../baz" * or here ---------------^ */ --dp; while (dp > base && *--dp != SLASH) ; ++dp; } } } while (*sp++); if (--dp > into && *dp == SLASH) *dp = '\0'; FIX_FILENAME(into); #undef base #undef isadevice return into; } #undef DirStack #undef DirStkSize #else /* ! CHDIR */ # define dfollow(file, into) strcpy(into, file) char * pr_name(filename) register const char *filename; { static char result[FILESIZE]; register const char *fname; if (fname = filename) { if (HomeLen > 1 && numcomp(fname, HomeDir) == HomeLen) { sprintf(result, "~%s", fname + HomeLen); fname = result; } } return (char *) fname; } #endif /* CHDIR */ #ifndef cmp_ino int cmp_ino(file1, file2) const char *file1, *file2; { struct stat st1, st2; return (stat(file1, &st1) == 0 && stat(file2, &st2) == 0 && ino_eq(st1.st_ino, st2.st_ino) && st1.st_dev == st2.st_dev); } #endif #define UNKNOWNUSER(u) add_mess(" [unknown user: %s]", u), complain((char *) 0) #ifdef PASSWD /* if your /etc/passwd does not have standard layout, or user names are maintained by NIS (Yellow Pages) or something similar. */ # include # define GET_HOMEDIR(user, buf) do { \ register struct passwd *p = getpwnam(user); \ if (p == NULL) \ UNKNOWNUSER(user); \ strcpy(buf, p->pw_dir); \ } while (0) #else /* !PASSWD */ # if unix # define GET_HOMEDIR(user, buf) do { \ register File *fp; \ fp = open_file("/etc/passwd", iobuff, \ F_READ|F_TEXT|F_COMPLAIN|F_QUIET); \ if (!re_fsearch(fp, sprint("^%s:[^:]*:[^:]*:[^:]*:[^:]*:\\([^:]*\\):", \ user))) \ UNKNOWNUSER(user); \ f_close(fp); \ putmatch(1, buf, FILESIZE); \ } while (0) # else /* !unix */ # define GET_HOMEDIR(user, buf) UNKNOWNUSER(user) # endif /* !unix */ #endif /* !PASSWD */ char * PathParse(name, intobuf) register const char *name; register char *intobuf; { char localbuf[FILESIZE]; register char *lp = localbuf; intobuf[0] = lp[0] = '\0'; if (*name == '\0') return intobuf; if (*name++ == '~') { if (ISDIRSEP(*name) || *name == '\0') { strcpy(lp, HomeDir); } else { /* "~user[/...]"; extract user name */ do *lp++ = *name++; while (*name && !ISDIRSEP(*name)); *lp = '\0'; lp = localbuf; GET_HOMEDIR(lp, lp); } } #ifdef CHDIR # ifdef CHDIR_EXT else if (name[-1] == '=') { /* Directory stack access. */ register int n = 0, c, sign = 0; if (*name == '-') { ++name; --sign; } while ((unsigned)(c = *name++ - '0') < 10) n = n * 10 + c; if (*--name == '\0' || ISDIRSEP(*name)) { if (n > DirSP) { add_mess((sign < 0) ? " [max =-%d]" : " [max =%d]", DirSP); complain((char *)0); } if (sign < 0) n--; else n = DirSP - n; strcpy(lp, (n >= 0) ? DirStack[n] : old_pwd); } else { while (*--name != '=') ; /* skip back. */ } } # endif #endif /* CHDIR */ #ifdef DOS else if (*--name == CTL('Q')) name++; #else else if (*--name == '\\') name++; #endif strcat(lp, name); return dfollow(lp, intobuf); } /*====================================================================== * $Log: dirs.c,v $ * Revision 14.31.0.12 1994/06/23 02:46:24 tom * (prDIRS): fix name of scratch buffer. * * Revision 14.31.0.9 1994/01/29 04:27:01 tom * (dirs_rotate): fix serious bug in "dirs-extract" handling; * various comment fixes. * * Revision 14.31.0.6 1993/10/28 00:54:48 tom * dfollow: replace conditional strlwr() with FIX_FILENAME(). * * Revision 14.31.0.1 1993/07/07 12:12:56 tom * (F_TEXT): new option for f_open et al. * * Revision 14.31 1993/02/16 11:44:19 tom * fix bug in dirs_find(); remove (void) casts. * * Revision 14.30 1993/01/26 18:43:08 tom * Initial revision, extracted from "io.c". * Added tcsh(1) features (as compile-time option CHDIR_EXT): numeric arguments * to pushd/popd to emulate "pushd/popd +N"; verbose "dirs" on numeric argument, * allow leading "=N" and "=-" in file names, initialize directory stack from * environment variables DIRS / OLDPWD; make chdir to old stack entries robust. * */