#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(POSIX) || defined(SOLARIS)
#  include <dirent.h>
#else
#  include <sys/dir.h>
#endif
#include <sys/param.h>
#ifndef BSD
#  include <unistd.h>
#  include <sys/stat.h>
#endif
#include <sys/errno.h>

#ifdef AFS_MOUNTPOINTS
#include <sys/ioctl.h>
#include <afs/param.h>
#include <afs/vice.h>
#include <netinet/in.h>
#include <afs/venus.h>
#endif

#ifdef SYSV
#  define getwd(a) getcwd(a,MAXPATHLEN+1)
#endif

#if defined(BSD) && !defined(__NetBSD__)
#  define FILENAME_MAX MAXPATHLEN
#  define dirent direct
#endif

void usage(), expand(), tree_execute(), make_command();
int execute(), count();
char *lastpart(char *);

int links,       /* follow links */
  bottom_up,     /* bottom-up recursion */
  mountpts,      /* follow mountpoints */
  dotfiles,      /* work on dotfiles */
  display,       /* printout current path */
  small;         /* use short names for the links */

void main(argc, argv)
int argc;
char *argv[];
{
extern char *optarg;
extern int optind;

#ifdef DEBUG
int x = 0;
#endif

int arg,count=0,temp;
char from[MAXPATHLEN+1], start[MAXPATHLEN+1];
char *command;

small=links=mountpts=display=bottom_up=0;
dotfiles=1;
strcpy(from,"./");
while ((arg= getopt(argc, argv, "lmdbvsDf:")) != EOF)
  {
    switch (arg) {
    case 'l':
      links++;
      break;
    case 'm':
      mountpts++;
      break;
    case 'd':
      dotfiles--;
      break;
    case 'f':
      strcpy(from,optarg);
      break;
    case 'b':
      bottom_up++;
      break;
    case 'v':
      display++;
      break;
    case 's':
      small++;
      break;
    default:
      usage();
      exit(2);
    } 
  }
if (argc <= optind) {
  usage();
  exit(2);
}
temp=optind;
while (argc-temp)
  count += strlen(argv[temp++]) + 1;
if ((command=(char *)malloc(count*sizeof(char)))==NULL){
  perror("malloc");
  exit(2);
}
else {
  temp=optind;
  command[0]=NULL;
  strcat(command,argv[temp++]);
  while (argc-temp) {
    strcat(command," ");
    strcat(command,argv[temp++]);
  }
}
#ifdef DEBUG
printf("%d, %d, %d, %d, %d, %s\n",links,mountpts,dotfiles,display,bottom_up,from);
printf("%d, %d, %s\n",argc, optind, command);
#endif
getwd(start);    /*  this is to keep xsaber happy, and should not slow down the */
                 /*  program by too much.                                       */
chdir(from);
tree_execute((char *)getwd(from), command);
free(command);
chdir(start);
}

void tree_execute(path,command)
     char *path;
     char *command;
{
  char cwd[MAXPATHLEN+1], filename[FILENAME_MAX+1], name[MAXPATHLEN+1];
  char link[MAXPATHLEN+1];
  int count;
  DIR *directory; 
#ifdef SOLARIS
  struct  direct {
    u_long  d_ino;                  /* inode number of entry */
    u_short d_reclen;               /* length of this record */
    u_short d_namlen;               /* length of string in d_name */
    char    d_name[MAXNAMLEN+1];    /* name of entry */
  } *current;
#else
  struct dirent *current;
#endif
  struct stat buf;
  extern int errno;
  unsigned short info;
  
  if ((directory=opendir(path))==NULL) {
    if (display)
      perror("opendir");
  }
  else{
    if (bottom_up==0){
      if (display){
	printf("%s\n",path);
	fflush(stdout);
      }
      execute(command, path);
    }
    while (current=readdir(directory)) {
      if (current->d_ino){
	strcpy(filename,current->d_name);
	if (strcmp(filename,".") && strcmp(filename,"..") && 
	    strcmp(filename,"")){
	  strcpy(name,path);
	  if (!((path[0]=='/') && (path[1]==NULL)))
	    strcat(name,"/");
	  strcat(name,filename);
	  if (lstat(name,&buf) >= 0)
	    if ((filename[0] != '.') || dotfiles)
	      if (((info=(buf.st_mode & S_IFMT))==S_IFLNK) && links) {
		stat(name, &buf);
		if ((buf.st_mode & S_IFMT)==S_IFDIR)
		  if ((count=readlink(name, link, MAXPATHLEN)) >= 0) {
		    link[count]=NULL;
		    if (display)
		      if (small)
			printf("%s -> ",filename);
		      else
			printf("%s -> ",name);
		    if ((count > 1) && (link[count-1]=='/'))
		      link[count-1]=NULL;
		    if (!(link[0]=='/')) {
		      strcpy(cwd,path);
		      strcat(cwd,"/");
		      strcat(cwd,link);
		    }
		    else
		      strcpy(cwd,link);
		    tree_execute(cwd, command);
		  }
		  else
		    printf("\n\n\n\n%d\n\n\n\n",errno);
	      }
	      else
		if ((info==S_IFDIR) &&
		    (!is_mountpoint(name,&buf) || mountpts)) {
		  strcpy(cwd,path);
		  if (path[strlen(path)-1] != '/')
		    strcat(cwd,"/");
		  strcat(cwd,filename);
		  tree_execute(cwd, command); 
		}
	}
      }
    }
    closedir(directory);
    if (bottom_up) {
      if (display){
	printf("%s\n",path);
	fflush(stdout);
      }
      execute(command, path);
    }
  }
}



void usage()       /*   generic how to use message    */
{
printf("\n\tUSAGE:   wv [-dlmbvs] [-f {path}] {command}\n\n");
printf("d - follow dotfile directories\n");
printf("l - follow links\n");
printf("s - use just filename when describing links with -v option\n");
printf("m - follow mountpoints\n");
printf("b - follow the tree in bottom up manner\n");
printf("v - print out the full paths being examined\n\n");
}

#if 0

void expand(listargV)
     char **listargV[];
{

}

execute (listargV,path)
     /*  This part of the program should execute the array passed to it */
     char *listargV[];
     char *path;
{
  int pid, status;

  if ((pid=fork()) < 0) {
    perror("fork");
    exit(1);
  }
  if (pid == 0) { 
    chdir(path);
    expand(&listargV);
    execvp(*listargV, listargV);
    perror(*listargV);
    exit(1);
  }
  while (wait(&status) != pid);
}

#endif

void make_command(listargV, command)
     char *listargV[];
     char *command;
{
  int x=0;
  command[0]='\0';
  while (listargV[x] != NULL){
    strcat(command,listargV[x++]);
    strcat(command," ");
  }
}


/* this should be changed soon to the above version */

execute (command, path)
     char *command;
     char *path;
{  
  chdir(path);
  system(command);
}


/*
 * This is one of the few procedures that is allowed to break the
 * rule of always returning an error value if an error occurs.  That's
 * because it's stupid to expect a boolean function to do that, and
 * because defaulting to not being a mountpoint if there is an error
 * is a reasonable thing to do.
 */
/*
 * The second parameter is optional -- if it is non-NULL, it is
 * presumed to be a stat structure for the file being passed in.
 */
int is_mountpoint(name, oldbuf)
char *name;
struct stat *oldbuf;
{
  extern int display;
     struct stat statbuf;
     dev_t device;
     char buf[MAXPATHLEN];
#ifdef AFS_MOUNTPOINTS
     struct ViceIoctl blob;
     char retbuf[MAXPATHLEN];
     int retval;
     char *shortname;
#endif

     /* First way to check for a mount point -- if the device number */
     /* of name is different from the device number of name/..       */
     if (oldbuf)
	  statbuf = *oldbuf;
     else if (lstat(name, &statbuf) < 0) {
       perror(name);
       return 0;
     }
     device = statbuf.st_dev;

     if (strlen(name) + 4 /* length of "/.." + a NULL */ > MAXPATHLEN) {
	  errno = ENAMETOOLONG;
	  perror(name);
	  return 0;
     }
     strcpy(buf, name);
     strcat(buf, "/..");
     if ((lstat(buf, &statbuf) < 0) && (display)) {
       perror(name);
	  return 0;
     }
     if (statbuf.st_dev != device)
	  return 1;

#ifdef AFS_MOUNTPOINTS
     /* Check for AFS mountpoint using the AFS pioctl call. */
  if ((shortname = lastpart(name)) == name) {
	  strcpy(buf, ".");
	  blob.in = name;
	  blob.in_size = strlen(name) + 1;
	  blob.out = retbuf;
	  blob.out_size = MAXPATHLEN;
	  bzero(retbuf, MAXPATHLEN);
     }
     else {
	  strncpy(buf, name, shortname - name - 1);
	  buf[shortname - name - 1] = '\0';
	  if (*buf == '\0')
	       strcpy(buf, "/");
	  blob.in = shortname;
	  blob.in_size = strlen(shortname) + 1;
	  blob.out = retbuf;
	  blob.out_size = MAXPATHLEN;
	  bzero(retbuf, MAXPATHLEN);
     }

     retval = pioctl(buf, VIOC_AFS_STAT_MT_PT, &blob, 0);

     if (retval == 0) {
#ifdef DEBUG
	  printf("%s is an AFS mountpoint, is_mountpoint returning true.\n",
		 name);
#endif
	  return 1;
	}
     else {
       if (errno != EINVAL) {
	 perror(name);
       }
     }
#endif /* AFS_MOUNTPOINTS */
  return 0;
}

char *lastpart(filename)
char *filename;
{
     char *part;

     part = rindex(filename, '/');
     if (! part)
	  part = filename;
     else if (part == filename)
	  part++;
     else if (part - filename + 1 == strlen(filename)) {
	  part = rindex(--part, '/');
	  if (! part)
	       part = filename;
	  else
	       part++;
     }
     else
	  part++;

     return(part);
}
