maart@nat.vu.nl (Maarten Litmaath) (02/22/91)
Submitted-by: Maarten Litmaath <maart@nat.vu.nl> Posting-number: Volume 17, Issue 12 Archive-name: which6/part01 I've included the latest version of the `fast which' program. Though the changes are small, I decided to resubmit the source in its entirety, because a patch, which is always a hassle, would not have been substantially smaller. This sixth version supersedes the following issues, which can be deleted from the archives (or replaced with pointers to `which6'): v04i010, v05i016, v10i064, and v11i096 Maarten Litmaath <maart@nat.vu.nl> : This is a shar archive. Extract with sh, not csh. : This archive ends with exit, so do not worry about trailing junk. : --------------------------- cut here -------------------------- PATH=/bin:/usr/bin:/usr/ucb echo Extracting 'which6.c' sed 's/^X//' > 'which6.c' << '+ END-OF-FILE ''which6.c' X/* X * which [-i] [-a] [--] [<command>] X * alias which alias !\$ \| /usr/local/bin/which -i !\* X * alias which 'eval alias \$$# | /usr/local/bin/which -i ${1+"$@"}' X * which() X * { X * eval last=\"\$$#\" X * set | sed -n "/^$last(){$/,/^}$/p" | X * /usr/local/bin/which -i ${1+"$@"} X * } X * X * Author: Maarten Litmaath @ VU University Amsterdam (maart@cs.vu.nl) X * X * First change: X * Emile LeBlanc (leblanc%math.Berkeley.EDU@ucbvax.berkeley.edu) notes X * the access() system call considering everything executable for X * root (!), so we give root a special treatment. :-( X * `which', `which -i' and `which -a' with no further arguments now X * return the PATH environment variable, split up into its components. X * The aliases defined above are slightly different from the previous X * version - now it's the shell who's doing the alias checking. X * Second change: X * Added support for shell functions and multiline aliases, added the X * `--' option, changed the source style. X * Third change: X * To hell with access()! X * Now stat() is used to give the right answer even if the effective X * uid (gid) differs from the real uid (gid). X * We can't use setuid(geteuid()), because that's nonportable. :-( X * Fourth change: X * Jim Meyering <meyering@cs.utexas.edu> notes convert() will clobber X * the stack if the PATH is longer than BUF_SIZE - 1 characters. X * I've changed convert() altogether to return a path vector (cf. argv), X * whose components are the respective directories in the PATH. X * Furthermore in printing the PATH there are no trailing colons anymore. X * Fifth change: X * I've added support for multiple groups (see getgroups(2) on BSD- X * derivatives). X * Thanks to John M. Sellens <jmsellens@watdragon.uwaterloo.ca> (and X * Andy at the same site). X * Furthermore, if a matching executable is found in an unreadable X * directory, beside the warning on stderr the name is now printed on X * stdout as well. X */ X X#ifdef MULTIPLE_GROUPS X#include <sys/param.h> X#else X#include <sys/types.h> X#endif /* MULTIPLE_GROUPS */ X#include <sys/stat.h> X#include <stdio.h> X X#define BUF_SIZE 512 X#define M_USR 0700 X#define M_GRP 0070 X#define M_OTH 0007 X#define X_ALL 0111 X#define R_ALL 0444 X Xchar Version[] = X "@(#)which 6.0 91/02/02 Maarten Litmaath @ VU Informatika Amsterdam"; Xchar *Prog; X X Xvoid usage() X{ X fprintf(stderr, "Usage: %s [-i] [-a] [--] [<command>]\n", Prog); X exit(1); X} X X Xmain(argc, argv) Xint argc; Xregister char **argv; X{ X register char *path, *s, **pathv, **p; X char *strcpy(), *getenv(), *fgets(), buf[BUF_SIZE], **convert(); X int all = 0, inter = 0, stop = 0, found = 0, uid, gid, mask, X xmask, rmask; X struct stat st; X void usage(); X X X Prog = *argv++; X --argc; X X while (!stop && (s = *argv) && (*s == '-')) { X ++argv; X --argc; X ++s; X while (*s) X switch (*s++) { X case 'a': X all = 1; X break; X case 'i': X inter = 1; X break; X case '-': X stop = 1; X break; X default: X usage(); X } X } X X if (argc > 1) X usage(); X X if (inter && *argv) { X while (fgets(buf, sizeof buf, stdin)) { X if (!found) { X printf("%s", *argv); X found = 1; X } X printf("\t%s", buf); X } X if (found && !all) X exit(0); X } X X if (!(path = getenv("PATH"))) { X fprintf(stderr, "%s: no PATH in environment!\n", Prog); X exit(1); X } X X if (!*path) X path = "."; /* another dubious convention */ X X pathv = convert(path); /* convert path string to vector */ X X if (!*argv) { /* print path if no more arguments */ X while (*pathv) X puts(*pathv++); X exit(0); X } X X uid = geteuid(); X gid = getegid(); X if (uid == 0) { X xmask = X_ALL; X rmask = R_ALL; X } X X for (p = pathv; path = *p++; ) { /* try every component */ X s = buf; X while (*s++ = *path++) X ; X (void) strcpy(s, *argv); X *--s = '/'; X X if (stat(buf, &st) != 0 || (st.st_mode & S_IFMT) != S_IFREG) X continue; X X /* file exists and is regular */ X X if (uid != 0) { X mask = st.st_uid == uid ? M_USR : X st.st_gid == gid ? M_GRP : X#ifdef MULTIPLE_GROUPS X groups_member(st.st_gid) ? M_GRP : X#endif /* MULTIPLE_GROUPS */ X M_OTH; X xmask = X_ALL & mask; X rmask = R_ALL & mask; X } X X if (!(st.st_mode & xmask)) X continue; X X /* file is executable */ X X *s = 0; X if (stat(buf, &st) != 0) { X perror(buf); X continue; X } X X /* warn user if the directory is unreadable */ X X if (!(st.st_mode & rmask)) X fprintf(stderr, X "%s: %s found in unreadable directory `%s'!\n", X Prog, *argv, buf); X X *s = '/'; X puts(buf); X if (!all) X exit(0); X found = 1; X } X X if (found) X exit(0); X X fprintf(stderr, "%s not found in:\n", *argv); X while (*pathv) X fprintf(stderr, "%s\n", *pathv++); X exit(1); X /* NOTREACHED */ X} X X Xchar **convert(path) Xchar *path; X{ X register char *s, c; X register int pathc; /* the length of the path vector */ X char **v, **pathv, *malloc(); X X for (s = path, pathc = 2; c = *s++; ) X if (c == ':') X ++pathc; X X if (!(pathv = (char **) malloc(pathc * sizeof(char *)))) { X perror("malloc"); X exit(1); X } X X for (s = path, v = pathv; (c = *s) != '\0'; ) { X if (c == ':') { X /* X * This colon is spurious. According to some X * dubious convention it is made equivalent to a dot. X */ X *v++ = "."; X if (*++s == '\0') X *v++ = "."; X /* X * The PATH ended in a spurious colon. X * To be consistent we add another dot X * to the path vector. One day you'll X * be glad we did. X */ X } else { X *v++ = s; X while ((c = *++s) != '\0') X if (c == ':') { X *s++ = '\0'; X if (*s == '\0') X *v++ = "."; X /* X * The PATH ended in a X * (spurious) colon, so X * add dot to the vector. X */ X break; X } X } X } X X *v = 0; /* Signal the end of the path vector. */ X X return pathv; X} X X X#ifdef MULTIPLE_GROUPS Xint groups_member(gid) Xint gid; X{ X register X int *p, ngroups; X int groups[NGROUPS]; X X if ((ngroups = getgroups(NGROUPS, groups)) < 0) X return 0; X p = groups; X while (--ngroups >= 0) X if (*p++ == gid) X return 1; X return 0; X} X#endif /* MULTIPLE_GROUPS */ + END-OF-FILE which6.c chmod 'u=rw,g=r,o=r' 'which6.c' set `wc -c 'which6.c'` count=$1 case $count in 5970) :;; *) echo 'Bad character count in ''which6.c' >&2 echo 'Count should be 5970' >&2 esac echo Extracting 'which.1' sed 's/^X//' > 'which.1' << '+ END-OF-FILE ''which.1' X.TH WHICH 1 Apr\ 04\ 1990 X.SH NAME Xwhich \- give alias, function or path expansion of command X.SH SYNOPSIS X.B which X[ X.B \-i X] [ X.B \-a X] [ X.B \-\- X] [ X.I command X] X.SH DESCRIPTION X.I Which Xprovides the user with the full expansion of the X.I command Xargument, be it either an \fIalias\fR, a \fIshell function\fR Xor an executable file (default). To enable search for X.I aliases Xand \fIshell functions\fR Xthe user should supply the `\fI\-i\fR' X(= interactive) flag. In that case X.I which Xexpects as standard input the expansion of the \fIalias\fR Xor \fIshell function\fR. XIf the standard input is empty or the `\fI\-i\fR' flag has not been Xgiven, \fIwhich\fR will try to locate \fIcommand\fR Xin the user's \fBPATH\fR. XThe interactive mode is easily used by setting an X.I alias Xlike the following: X.ft B X.nf X X alias which alias !\\$ \\| /usr/local/bin/which \-i !\\* X X.fi X.ft R Xin \fIcsh\fR, or X.ft B X.nf X X alias which eval alias '\\"\\$$#\\" |' \\ X /usr/local/bin/which \-i '${1+"$@"}' X X.fi X.ft R Xin shells which are supersets of X.I sh Xand which know \fIaliases\fR. If your shell has \fIshell functions\fR, you Xcan use the following function: X.ft B X.nf X X which() X { X eval last=\\"\\$$#\\" X set | sed \-n "/^$last(){$/,/^}$/p" | X /usr/local/bin/which \-i ${1+"$@"} X } X X.fi X.ft R XIf the `\fI\-a\fR' (= all) flag is given, X.I which Xwill not stop after the first `match', but search for all occurrences of X.I command Xin the user's X.B PATH. XThe `\fI\-\-\fR' Xflag can be used to end the list of options: the next argument (if present) Xwill be taken as \fIcommand\fR, even if it starts with a `\-'. X\fIWhich [\-i] [\-a] [\-\-]\fR Xwithout further arguments prints the user's X.B PATH Xbroken up into its components, Xone per line. X.PP XThis new version of the X.I which Xcommand is not a X.I csh Xscript. XBeing an executable it is much faster, and not sourcing X.I .cshrc Xit gives a true picture of one's X.I aliases. XFurthermore it will give the correct answers even if: X.IP \- Xthe \fIeffective uid\fR X(\fIgid\fR) differs from the \fIreal\fR uid (gid) X.IP \- Xthe effective uid is 0 (\fIroot\fR). X.SH EXAMPLE X.ft B X.nf X% alias Xwhich alias !$ | /usr/local/bin/which \-i !* X% which which Xwhich alias !$ | /usr/local/bin/which \-i !* X% which \-a which Xwhich alias !$ | /usr/local/bin/which \-i !* X/usr/local/bin/which X/usr/ucb/which X% X.fi X.ft R X.SH AUTHOR XMaarten Litmaath @ VU Informatika Amsterdam + END-OF-FILE which.1 chmod 'u=rw,g=r,o=r' 'which.1' set `wc -c 'which.1'` count=$1 case $count in 2384) :;; *) echo 'Bad character count in ''which.1' >&2 echo 'Count should be 2384' >&2 esac echo Extracting 'Makefile' sed 's/^X//' > 'Makefile' << '+ END-OF-FILE ''Makefile' X# Makefile for /usr/local/bin/which X# If your operating system does not support multiple groups (see getgroups(2) X# on BSD-derivatives), comment out the next line. XMULTIPLE_GROUPS=-DMULTIPLE_GROUPS X Xwhich: which6.c X cc -O $(MULTIPLE_GROUPS) -o which which6.c X Xinstall: which doc X /bin/mv -f which /usr/local/bin X /bin/mv -f which.man /usr/local/man/cat1/which.1 X /bin/cp which.1 /usr/local/man/man1 X Xdoc: X nroff -man which.1 > which.man + END-OF-FILE Makefile chmod 'u=rw,g=r,o=r' 'Makefile' set `wc -c 'Makefile'` count=$1 case $count in 443) :;; *) echo 'Bad character count in ''Makefile' >&2 echo 'Count should be 443' >&2 esac exit 0 exit 0 # Just in case... -- Kent Landfield INTERNET: kent@sparky.IMD.Sterling.COM Sterling Software, IMD UUCP: uunet!sparky!kent Phone: (402) 291-8300 FAX: (402) 291-4362 Please send comp.sources.misc-related mail to kent@uunet.uu.net.