#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	which.1
#	Makefile
#	which.c
# This archive created: Mon Apr 28 10:14:21 1986
cat << \SHAR_EOF > which.1
.TH WHICH 1Local "28 April 1986"
.SH NAME
which \- locate program(s)
.SH SYNOPSIS
.B which
[-a] name ...
.SH DESCRIPTION
.I Which
looks for a file (or files) using the user's search path and prints out
the first occurrence of the named file(s) in this path.
This is useful for finding out where an executable lives.
The 
.I \-a 
option may be used to have 
.I which
list all locations of the executable(s).
.I Which understands the use of 
.I tilde notation
as used in the csh but does not rely on the csh to do this.
.SH FILES
.SH BUGS
SHAR_EOF
cat << \SHAR_EOF > Makefile
#   $Header: Makefile,v 1.2 85/07/14 23:53:18 tong Exp $
#
#	DEST		---	destination directory
#	HDRDEST		---	header destination directory
#	LIBDEST		---	library destination directory
#	LINTLIBDEST	---	lint library destination directory
#	EXTHDRS		---	headers external to the cwd
#	HDRS		---	headers which reside in the cwd
#	INCLUDES	---	include files (not for dbx -- different fmt)
#	CFLAGS		---	cflags for compilation
#	LINTFLAGS	---	lint flags for type checking
#	DBXFLAGS	---	dbx flags for debugging
#	EXTLIBS		---	external libraries for inclusion in ld'ing
#	LIBS		---	internal libraries for inclusion in ld'ing
#	LINKER		---	linker (typically cc)
#	LIBSRCS		---	sources for creating the library
#	LIBOBJS		---	objects included in the library
#	LLNAME		---	the library sub-name (i.e. tool name)
#	LIBRARY		---	the actual archive library name
#	LINTLIB		---	the lint-library name
#	MAKEFILE	---	the name of this makefile (used by mkmf)
#	PROGRAM		---	the program name
#	EXTOBJS		---	external objects used in making program
#	OBJS		---	internal objects used in making program
#	SRCS		---	sources used to make program (must be in cwd)
#	CSHS		---	csh scripts
#	MANS		---	manual pages
#	JUNKS		---	junk programs which may be removed
#	OWNER		---	the owner's name for installation purposes
#	VPATH		---	virtual path (vmake only)

DEST	      = .
HDRSDEST      = ../include
LIBDEST	      = ../lib
LINTLIBDEST   = ../lib/lint
EXTHDRS	      = /usr/include/pwd.h \
		/usr/include/stdio.h \
		/usr/include/strings.h \
		/usr/include/sys/file.h \
		/usr/include/sys/stat.h \
		/usr/include/sys/types.h
HDRS	      =
INCLUDES      = 
CFLAGS	      = -g $(INCLUDES)
LINTFLAGS     = $(INCLUDES)
DBXFLAGS      = 
EXTLIBS	      = 
LIBS	      = 
LINKER	      = cc
LIBSRCS	      = 
LIBOBJS	      = 
LLNAME	      = 
LIBRARY	      = lib$(LLNAME).a
LINTLIB	      = llib-l$(LLNAME).ln
MAKEFILE      = Makefile
PROGRAM	      = which
EXTOBJS	      =
OBJS	      = which.o
SRCS	      = which.c
CSHS	      =
MANS	      = 
JUNKS	      =
OWNER	      = 
VPATH	      =


#	Standard Targets 
#	-------- -------
#
#	All text files (SRCS, HDRS, CSHS, MANS) will be checked out of RCS
#	if not found in the directory.  If you do not have RCS then remove
#	the line following "all:"
#
#	all:		---	equivalent to make program
#	<program>	---	make the entire program; <program> == name
#	clean:		---	remove all re-makeable files and junks
#	diff:		---	find diffs between latest and present versions
#	index:		---	index of #defines/typdefs/procedures to stdout
#	installlib:	---	install library (and create it if we need to).
#	installprog:	---	install program into DEST directory
#	library:	---	create library (for tool)
#	lint:		---	lint the files (result on stdout)
#	program:	---	equivalent to make <program>
#	tags:		---	create a tags file for use with editor
#	update:		---	makes and installs program if out of date 
#	<dest/program>	---	equivalent to make update
#
#	make install installs the result in the destination directory;
#	make update checks that the result in the destination directory is
#	up to date.
#
#


all:		$(PROGRAM)

$(SRCS) $(HDRS) $(CSHS) $(MANS) :; @co $@

$(PROGRAM):     $(OBJS)
		@echo -n "Loading $(PROGRAM) ... "
		@$(LINKER) $(CFLAGS) $(OBJS) $(EXTOBJS) $(LIBS) $(EXTLIBS) \
			 -o $(PROGRAM)
		@echo "done"

clean:;		@rm -f $(OBJS) $(JUNKS)

depend:;	@mkmf -f $(MAKEFILE) PROGRAM=$(PROGRAM) DEST=$(DEST)

diff:;		@rcsdiff -r$(VERSION) $(HDRS) $(SRCS)

dbx:;		tdbx $(DBXFLAGS) $(PROGRAM)

index:;		@ctags -wx $(HDRS) $(SRCS)

installlib:	$(LIBRARY) $(LINTLIB)
		@-rm -f $(LIBDEST)/$(LIBRARY)
		@echo Installing $(LIBRARY) in $(LIBDEST)
		@cp $(LIBRARY) $(LIBDEST)
		@chmod 644 $(LIBDEST)/$(LIBRARY)
		@echo Installing $(HDRS) in $(HDRDEST)
		@(cd $(HDRSDEST) ; rm -f $(HDRS));
		@cp $(HDRS) $(HDRDEST)
		@chmod 444 $(HDRDEST)/$(HDRS)
		@echo Installing $(LINTLIB) in $(LINTLIBDEST)
		@-rm -f $(LINTLIBDEST)/$(LINTLIB)
		@cp $(LINTLIB) $(LINTLIBDEST)
		@chmod 444 $(LINTLIBDEST)/$(LINTLIB)
		@ranlib $(LIBDEST)/$(LIBRARY)

installprog:	$(PROGRAM)
		@rm -f $(DEST)/$(PROGRAM)
		@install -m 775 $(PROGRAM) $(DEST)

library:	$(LIBRARY)

$(LIBRARY):	$(LIBOBJS)
		@rm -f $(LIBRARY)
		@ar q $(LIBRARY) $(LIBOBJS)

lint:;		@lint $(LINTFLAGS) $(HDRS) $(SRCS) 

lintlib:	$(LINTLIB)

$(LINTLIB):	$(LIBSRCS) $(HDRS)
		@lint -C$(LLNAME) $(LINTFLAGS) $(HDRS) $(LIBSRCS)

program:        $(PROGRAM)

tags:           $(HDRS) $(SRCS); @ctags $(HDRS) $(SRCS)

updateprog:	$(DEST)/$(PROGRAM)
updatelib:	$(LIBDEST)/$(LIBRARY)

$(DEST)/$(PROGRAM): $(SRCS) $(LIBS) $(HDRS) $(EXTHDRS) $(EXTLIBS)
		@make -f $(MAKEFILE) DEST=$(DEST) installprog

#(LIBDEST)/$(LIBRARY):	$(LIBSRCS) $(LIBOBJS)
		@make -f $(MAKEFILE) LIBDEST=$(LIBDEST) installlib

# 	*** DO NOT REMOVE THE FOLLOWING LINE ***
#	*** `make depend' utilizes this line ***
###
which.o: /usr/include/stdio.h /usr/include/sys/file.h \
	/usr/include/sys/types.h /usr/include/sys/stat.h /usr/include/pwd.h \
	/usr/include/strings.h
SHAR_EOF
cat << \SHAR_EOF > which.c
#ifndef lint
static char which_id[] = "$Header: which.c,v 1.4 86/04/28 09:43:46 frodo Exp $";
#endif

/*
 * which.c --
 *
 *	Fast "which" -- doesn't handle aliases.
 *	Works for 4.2bsd, don't know about SYS V or others.
 *
 * Copyright (C) 1985 David Fletcher.
 * All rights reserved.
 */

#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <strings.h>

char *getenv();			/* To get the environment (PATH) */
char *pcPath;			/* The path */

/* This glob of stuff tests to see whether the file is accessible and
 * executable (and not a directory).
 */
#define TEST(file) \
(!access((file), X_OK) && !stat((file), &StatBuf) && \
 !(StatBuf.st_mode & S_IFDIR))

#define TRUE		(1)
#define FALSE		(0)



/*
 * --------------------------------------------------------------------------
 *
 * TildeExpand --
 *
 *	This routine does "tilde expansion" like the csh.
 *
 * Return Value:
 *	A pointer to the expanded filename is returned.  This should point
 *	to the buffer we were given -- if the file name is NULL then it won't.
 *
 * Side effects:
 *	The buffer is written.  We don't check for overflow of the buffer since
 *	it is supposedly the same size as the max filename length.  
 *
 * Design:
 *	We expect the 1st character of the filename to not bw whitespace.
 *	See if it's a "~" and if so then expand as the csh does.  Then append
 *	the remainder of the file on the of this glob.
 *
 * --------------------------------------------------------------------------
 */
char *
TildeExpand(pcFileName, acBuffer)
char *pcFileName, acBuffer[];
{
   struct passwd *pPasswdEntry;
   char *getlogin(), *pcFoo;

   /* No file name?
    */
   if (pcFileName == (char *) NULL || *pcFileName == '\0')
     return (NULL);

   /* No "~" so just copy the file and return.
    */
   if (*pcFileName != '~') return (strcpy(acBuffer, pcFileName));
   
   /*
    *  We have a "~" so now see if we are looking for this user's home
    *  directory or another user's.
    */
   setpwent();
   if (*++pcFileName == '/' || *pcFileName == '\0')	/* this user's */
   {
      if ((pPasswdEntry = getpwnam(getlogin())) == (struct passwd *) NULL)
      {
	 fprintf(stderr,
		 "TildeExpand error! Cannot find your home directory!!!");
	 return(NULL);
      }

      /* Copy the directory to the supplied buffer, then put a SLASH, and
       * then place the rest of the filename after this.  We rely on the fact
       * that /etc/passwd does not place a slash after the user's home
       * directory.  If there is one then UNIX should be able to find the
       * path ok -- it will just look ugly with the 2 slashes in the path
       * name.  Hopefully won't be too bad for novice users.
       */
      (void) strcpy(acBuffer, pPasswdEntry->pw_dir);
      (void) strcat(acBuffer, pcFileName);
      return(acBuffer);
   }

   /*
    *  Another user's....
    *  If we have anything after the user's name then do the following
    *  stuff.  We null out the rest of the stuff, get the home dir of
    *  the user, and then put the stuff on the tail end of the directory.
    */
   if ((pcFoo = index(pcFileName, '/')) != (char *) NULL)
   {
      *pcFoo = '\0';
      if ((pPasswdEntry = getpwnam(pcFileName)) == (struct passwd *) NULL)
      {
	 fprintf(stderr,
		 "error: Cannot find %s's home directory,",
		 pcFileName);
	 return(NULL);
      }
      
      (void) strcpy(acBuffer, pPasswdEntry->pw_dir);
      *pcFoo = '/';		/* restore this character */
      (void) strcat(acBuffer, pcFoo); /* get the rest of the stuff */
      return(acBuffer);
   }

   /*
    *  In this case all we are given is ~user so simply get the
    *  user's home directory and return.
    */
   if ((pPasswdEntry = getpwnam(pcFileName)) == (struct passwd *) NULL)
   {
      fprintf(stderr,
	      "TildeExpand error! Cannot find %d's home directory!!!",
	      pcFileName);
      exit(1);
   }
   (void) strcpy(acBuffer, pPasswdEntry->pw_dir);

   return(acBuffer);
}

#define MAX_PATHS	100
#define MAX_PATHS_SIZE	4096
#define SEPARATOR	':'
#define SPACE		' '
#define TILDE		'~'



/*
 * --------------------------------------------------------------------------
 *
 * main --
 *
 *	The main routine.
 *
 * Return Value:
 *	Exits with a 1 if we can't find the PATH value from the environment.
 *	We also exit with a value of 1 if we don't get the right number of
 *	arguments or we get an unknown option.
 *
 * --------------------------------------------------------------------------
 */
main(argc, argv)
char *argv[];
{
   char *pcProgName, *pcSlash, *pcFoo, *pcBar, *pcName;
   char acBuffer[BUFSIZ];
   struct stat StatBuf;
   char *apcArgv[MAX_PATHS], acPaths[MAX_PATHS_SIZE];
   int iNumPaths, iCount, iScan, bFoundFile, iAll;
   
   /* Keep the program name for error messages.
    */
   pcProgName = argv[0];
   if (argc < 2)
     Usage(pcProgName);

   iAll = FALSE;
   ++argv;
   --argc;

   if (argv[0][0] == '-')
   {
      if (strcmp(argv[0], "-a")) /* To prevent "which -which which" */
	Usage(pcProgName);	/*  from working.  */
      ++argv;
      --argc;
      iAll = TRUE;
      if (argc < 1)
	Usage(pcProgName);
   }
   

   /* Get the path from the environment. Complain if not there.
    */
   if ((pcPath = getenv("PATH")) == (char *) NULL)
   {
      fprintf(stderr,"%s error: Couldn't find envirnoment variable PATH.\n",
	      pcProgName);
      exit(1);
   }

   /* Here we eliminate any leading SEPARATORs from the PATH and then
    * "vectorize" it -- store pointers to the front of each path.
    * If I was really smart I would also TildeExpand each one here rather
    * than every time that I search for a file.  This should probably be
    * done but I figure that since I normally serach for only 1 or 2 items
    * then this is too much work for so little in return.  If this were to
    * used in part of a larger context (i.e., loaded once and then used
    * alot) then I would take the hit.
    */
   if (*pcPath == SEPARATOR) ++pcPath;
   apcArgv[0] = acPaths;
   for (pcFoo = pcPath, pcBar = acPaths, iNumPaths = 0;
	*pcFoo != '\0';)
   {
      if (*pcFoo == SEPARATOR)
      {
	 *pcBar = '\0';
	 apcArgv[++iNumPaths] = ++pcBar;
	 ++pcFoo;
      }
      else
	*pcBar++ = *pcFoo++;
   }
   apcArgv[++iNumPaths] = (char *) NULL;
   

   /* Now, scan all the arguments and TildeExpand each one and then
    * print out the results.
    */
   for (iCount = 0; iCount < argc; ++iCount)
   {
      if (argv[iCount][0] == '\0') continue; /* shouldn't happen */
      pcName = argv[iCount];

      /*
       *  Get rid of any leading white space -- shouldn't really have to
       *  do this but might as well be careful.  Doesn't hurt really.
       */
      while (*pcName != '\0' && (*pcName == SPACE || *pcName == '\t'))
	++pcName;
      if (*pcName == '\0') continue;

      bFoundFile = FALSE;

      if (*pcName == TILDE)	/* Starts with a "~" */
      {
	 /* Nothing returned.  Print out a message and boggie on.
	  */
	 if ((pcName = TildeExpand(pcName, acBuffer)) == (char *) NULL)
	 {
	    fprintf(stderr, "%s: Unable to process \"%s\".\n",
		    pcProgName, pcName);
	    continue;
	 }

	 if (TEST(pcName))
	 {
	    bFoundFile = TRUE;
	    printf("%s ", pcName);
	 }
      }
      else if (*pcName == '/')	/* Starts with a SLASH */
      {
	 if (TEST(pcName))
	 {
	    bFoundFile = TRUE;
	    printf("%s ", pcName);
	 }
      }
      else			/* "Other" file */
      {
	 for (iScan = 0; iScan < iNumPaths; ++iScan)
	 {
	    acBuffer[0] = '\0';	/* Build full path name */
	    strcpy(acBuffer, apcArgv[iScan]);
	    strcat(acBuffer, "/");
	    strcat(acBuffer, pcName);

	    if (TEST(acBuffer))
	    {
	       bFoundFile = TRUE;
	       printf("%s ", acBuffer);
	       if (iAll == FALSE) break;
	    }
	 }
      }

      /* Print out "not found" message.
       */
      if (bFoundFile == FALSE)
      {
	 printf("no %s in ", pcName);
	 for (iScan = 0; iScan < iNumPaths; ++iScan)
	   printf("%s ", apcArgv[iScan]);
      }
      printf("\n");
   }
}

Usage(pcProgName)
char *pcProgName;
{
   printf("Usage: %s [-a] file1 file2 ...\n", pcProgName);
   printf("       The \"-a\" switch lists all directories.\n");
   exit(1);
}

SHAR_EOF
#	End of shell archive
exit 0


