/*
 *	$Source: /afs/sipb.mit.edu/project/sipbsrc/src/RCS/where.c,v $
 *	$Author: gsstark $
 *	$Locker: gsstark $
 *	$Header: /afs/sipb.mit.edu/project/sipbsrc/src/RCS/where.c,v 1.6 1994/10/19 22:43:27 gsstark Exp gsstark $
 *      $Log: where.c,v $
 * Revision 1.6  1994/10/19  22:43:27  gsstark
 * sorry bout null revision,
 * ANSIfied code, fixed behaviour with symlinks, added -pwd option
 *
 * Revision 1.5  1994/10/19  22:42:23  gsstark
 * *** empty log message ***
 *
 * Revision 1.5  1994/10/19  22:42:23  gsstark
 * *** empty log message ***
 *
 * Revision 1.4  1994/10/19  21:10:12  gsstark
 * after applying fixes I wrote while in montreal,
 * I have to look at them again before building,
 *
 * Revision 1.3  1994/01/29  01:26:20  svalente
 * Made it compile on Linux.
 *
 * Revision 1.2  87/12/20  09:56:35  raeburn
 * Removed group checks, just use "access()" instead.
 * 
 * Revision 1.1  86/08/03  12:44:34  spook
 * Initial revision
 * 
 * Revision 1.6  86/04/29  23:10:37  theschun
 * General cleanup to permit more additions.  Added link-chasing capacity.
 * 
 * Revision 1.5  86/04/21  23:02:54  theschun
 * No longer accepts bogus control arguments.
 * 
 * Revision 1.4  86/04/21  22:14:40  theschun
 * Fixed bug so that -once will only print out one entry (or none), even
 * if it is a directory.
 * 
 * Revision 1.3  86/04/21  22:08:11  theschun
 * Does not show directories in default state anymore.
 * 
 * Revision 1.2  86/04/21  21:59:31  theschun
 * Added -no_dirs (-nd) , -show_dirs (-sd) and fixed it so it doesn't 
 * quit as soon as it finds a directory.  Also added -quote (-qt) 
 * to signal the end of the control_args and to indicate that
 * the next arg is a filename
 * 
 * Revision 1.1  86/04/21  21:50:58  theschun
 * Initial revision
 */

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef BSD
#include <sys/file.h>
#define strrchr rindex
#else
#include <unistd.h>
#endif

#ifndef S_ISDIR
#define S_ISDIR(mode) ((mode & S_IFMT) == S_IFDIR)
#endif
#ifndef S_ISLNK
#define LNKMASK 0020000
#define S_ISLNK(mode) ((mode & LNKMASK) != 0)
#endif

extern int errno;

int all = 0;					       /* Don't show all */
int ndflg = 1;					       /* Don't show dirs */
int chase = 0;					       /* Chase links? */
int pwdflg= 0;                                         /* do a pwd before printing ? */
    
void
where_usage(char *me);
int 
getnextdir(char **path, char *dirname);
void
check_and_print_path(char *pathname, int *found);

int 
main(int argc, char *argv[])
{
    char pathname[MAXPATHLEN], dirname[MAXPATHLEN];
    char *path, *getenv();
    int found;					       /* Found yet? */
    char *whoami;

    if ((whoami = strrchr(argv[0], '/')) != NULL)
        whoami++;
    else
	whoami = argv[0];

    while (++argv, --argc > 0 && **argv == '-') {
	if (!strcmp(*argv, "-a") || !strcmp(*argv, "-all"))
	    all = 1;
	else if (!strcmp(*argv, "-o") || !strcmp(*argv, "-once"))
	    all = 0;
	else if (!strcmp(*argv, "-sd") || !strcmp(*argv, "-show_dirs"))
	    ndflg = 0;
	else if (!strcmp(*argv, "-nd") || !strcmp(*argv, "-no_dirs"))
	    ndflg = 1;
	else if (!strcmp(*argv, "-qt") || !strcmp(*argv, "-quote"))
	    break;
	else if (!strcmp(*argv, "-cl") || !strcmp(*argv, "-chase_links"))
	    chase = 1;
	else if (!strcmp(*argv, "-ncl") || !strcmp(*argv, "-no_chase_links"))
	    chase = 0;
	else if (!strcmp(*argv, "-p") || !strcmp(*argv, "-pwd"))
	    pwdflg = 1;
	else {
	    where_usage(whoami);
	    exit(1);
	}
    }

    while (argc > 0) {
	found = 0;
	path = getenv("PATH");
	while ((getnextdir(&path, dirname) != 0) && (all || !found)) {
	    strcpy(pathname, dirname);
	    strcat(pathname, "/");		       /* Create path */
	    strcat(pathname, *argv);
	    check_and_print_path(pathname, &found);
	}
	++argv, --argc;
    }
    exit(0);
}
	    
void
check_and_print_path (char *pathname, int *found)
{
    struct stat statbuf;
    char target[MAXPATHLEN];			       /* For symlinks */

    /* [gsstark:19941019.1733EST] I've changed it to always check with stat first 
       this slyly avoids the too many links problem with circular links 
       without explicitly checking for it*/
    if (stat(pathname, &statbuf) == -1)	       /* Doesn't exist */
	return;
    if (chase && (lstat(pathname, &statbuf) == -1))
	return;

    /* loop invariant: statbuf is valid for pathname */
    while (chase && (S_ISLNK (statbuf.st_mode))) {	/* Symlink */
	register int cc;
	if ((cc = readlink(pathname, target, MAXPATHLEN)) < 0) {
	    perror(pathname);
	    return;
	} else {
	    target[cc] = 0;			       /* Null-terminate */
	    if (target[0]=='/')   /*absolute link*/
		strcpy(pathname,target);
	    else
		/* [gsstark:19941019.1737EST] this will make paths like:
		   /foo/bar/../baz/qux 
		   which is nice but it could break on very long symlink chains
		   since it could overflow MAXPATHPLEN even though it might be 
		   a valid binary that the system could track down, oh well,
		   the only way to fix this bug would be to call pwd whenever
		   we are in danger of overflowing like that, which would be gross, 
		   maybe, anyway, here's the one line of code that sparked this comment:*/
		strcpy(strrchr(pathname,'/')+1,target);
	    /* make sure statbuf is valid for pathname */
	    if (lstat(pathname,&statbuf) == -1)
		return;
	}
    }

    if (S_ISDIR (statbuf.st_mode)) { 			/* Directory */
	if (!ndflg) {
	    if (pwdflg) {
		char fixname[MAXPATHLEN];
		if (chdir(pathname)) {
		    perror(pathname);
		    return;
		}
		if (!getcwd(fixname,MAXPATHLEN)) {
		    perror(pathname);
		    return;
		}
		strcpy(pathname,fixname);
	    }
	    printf("%s\n", pathname);
	    (*found)++;
	}
    } else if (!access(pathname, X_OK)) {
	if (pwdflg) {
	    char dirname[MAXPATHLEN];
	    char fixname[MAXPATHLEN];
	    char *itsname;
	    strcpy(dirname,pathname);
	    *((itsname=strrchr(dirname,'/')+1)-1)='\0';
	    if (chdir(dirname)) {
		perror(pathname);
		return;
	    }
	    if (!getcwd(fixname,MAXPATHLEN)) {
		perror(pathname);
		return;
	    }
	    sprintf(pathname,"%s%c%s",fixname,'/',itsname);
	}
	printf("%s\n", pathname);
	(*found)++;
    }
}

int
getnextdir(char **path, char *dirname)
{
    int i=0;
    
    if (**path == '\0')
	return(0);
    else if (**path == ':')
	(*path)++;
    
    while (**path != '\0' && **path != ':') {
	dirname[i] = **path;
	i++;
	(*path)++;
    }
    dirname[i] = '\0';
    return 1;
}

void
where_usage(char *me)
{
    fprintf(stderr, "usage: %s [options] name1 name2 ... \n", me);
    fprintf(stderr, "where options are:\n");
    fprintf(stderr, "\t-all, -a\n");
    fprintf(stderr, "\t-once, -o\n");
    fprintf(stderr, "\t-show_dirs, -sd\n");
    fprintf(stderr, "\t-no_dirs, -nd\n");
    fprintf(stderr, "\t-chase_links, -cl\n");
    fprintf(stderr, "\t-no_chase_links, -ncl\n");
    fprintf(stderr, "\t-pwd, -p\n");
    fprintf(stderr, "\t-quote, -qt\n");
    
}
