/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Cimarron D. Taylor of the University of California, Berkeley.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#include "tweak.h"
#ifdef HAVE_UNISTD_H
#ifndef __hpux
#include <unistd.h>
#endif
#endif
#include <stdio.h>
#ifdef STDC_HEADERS
#include <stdlib.h>
#endif
#include "my-string.h"
#ifndef VMS
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#else
#include "types.h"
#include "stat.h"
#include "param.h"
#endif
#ifdef HAVE_TZFILE_H
#include <tzfile.h>
#endif

#ifndef VMS
#include <sys/wait.h>
#else
#include "wait.h"
#endif
#include "find.h"

#define	FIND_EQUAL	0
#define	FIND_LESSTHAN	1
#define	FIND_GREATER	2

#define	COMPARE(a, b) { \
  switch(plan->flags) { \
    case FIND_EQUAL: \
      return(a == b); \
    case FIND_LESSTHAN: \
      return(a < b); \
    case FIND_GREATER: \
      return(a > b); \
  } \
  return(0); \
}

#ifdef HAVE_ANSI_PROTO
static PLAN *palloc (enum ntype t, int (*f)())
#else
static PLAN *palloc (t, f)
    enum ntype t;
    int (*f)();
#endif
{
  PLAN *new;

  if (new = (PLAN *) malloc(sizeof(PLAN))) {
    new->type = t;
    new->eval = f;
    new->flags = 0;
    new->next = NULL;
    return(new);
  }
  perror("palloc");
  exit(1);
}

extern int isoutput;
extern int process;

/*
 * find_parsenum --
 *	Parse a string of the form [+-]# and return the value.
 */
static long find_parsenum PROTO4(PLAN *, plan, char *, option, char *, str,
				 char *, endch)
{
  long value;
  char *endchar;		/* pointer to character ending conversion */
     
  /* determine comparison from leading + or - */
  switch(*str) {
    case '+':
      ++str;
      plan->flags = FIND_GREATER;
      break;
    case '-':
      ++str;
      plan->flags = FIND_LESSTHAN;
      break;
    default:
      plan->flags = FIND_EQUAL;
      break;
  }
     
  /*
   * convert the string with strtol().  Note, if strtol() returns zero
   * and endchar points to the beginning of the string we know we have
   * a syntax error.
   */
  value = strtol(str, &endchar, 10);
  if (!value && endchar == str || endchar[0] &&
      (!endch || endchar[0] != *endch)) {
    fprintf(stderr,"%s: %s", option, "illegal numeric value");
    exit(1);
  }
  if (endch)
    *endch = endchar[0];
  return(value);
}

/*
 * -time n functions --
 *
 *	True if the difference between the file time and the
 *	current time is n 24 hour periods.
 *
 */

static int find_time PROTO3(PLAN *, plan, struct stat *, sbuf,  char *, path)
{
  extern time_t now;

  /* with FSP sbuf.st_atime=sbuf.st_ctime=sbuf.st_mtime */
  COMPARE((now - sbuf->st_atime + SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
}

 
PLAN * c_time PROTO1(char *, arg)
{
  PLAN *new;

  new = palloc(N_TIME, find_time);
  new->t_data = find_parsenum(new, "-time", arg, NULL);
  return(new);
}

/*
 * brace_subst --
 *      Replace occurrences of {} in s1 with s2 and return the result string.
 */
void brace_subst PROTO4(char *, orig, char **, store, char *, path, int,len)
{
  register int plen;
  register char ch, *p;

  plen = strlen(path);
  for (p = *store; ch = *orig; ++orig)
    if (ch == '{' && orig[1] == '}') {
      while ((p - *store) + plen > len)
        if (!(*store = (char *)realloc(*store, len *= 2))) {
          perror("realloc");
          client_done();
          exit(1);
        }
      bcopy(path, p, plen);
      p += plen;
      ++orig;
    } else
      *p++ = ch;
  *p = '\0';
}

/*
 * queryuser --
 *      print a message to standard error and then read input from standard
 *      input. If the input is 'y' then 1 is returned.
 */
int queryuser PROTO1(char **, argv)
{
  int ch, first, nl;

  fprintf(stderr, "\"%s", *argv);
  while (*++argv) fprintf(stderr, " %s", *argv);
  fprintf(stderr, "\"? ");
  fflush(stderr);

  first = ch = getchar();
  for (nl = 0;;) {
    if (ch == '\n') {
      nl = 1;
      break;
    }
    if (ch == EOF) break;
    ch = getchar();
  }

  if (!nl) {
    fprintf(stderr, "\n");
    fflush(stderr);
  }
  return(first == 'y');
}

/*
 * [-exec | -ok] utility [arg ... ] ; functions --
 *
 *	True if the executed utility returns a zero value as exit status.
 *	The end of the primary expression is delimited by a semicolon.  If
 *	"{}" occurs anywhere, it gets replaced by the current pathname.
 *	The current directory for the execution of utility is the same as
 *	the current directory when the find utility was started.
 *
 *	The primary -ok is different in that it requests affirmation of the
 *	user before executing the utility.
 */
static int find_exec PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  register int cnt;
  pid_t pid;
#ifndef HAVE_UNION_WAIT
  int status;
#else
  union wait status;
#endif

  for (cnt = 0; plan->e_argv[cnt]; ++cnt)
    if (plan->e_len[cnt])
      brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], path,
		  plan->e_len[cnt]);

  if (plan->flags && !queryuser(plan->e_argv)) return(0);

#ifndef VMS
  switch(pid = fork())
#else
  switch(pid = vfork())
#endif
   {
    case -1:
      perror ("fork");
      exit(1);
    case 0:
      execvp(plan->e_argv[0], plan->e_argv);
      perror ("execvp");
      exit(1);
  }
  pid = wait(&status);

 return(pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
}

char *emalloc_ffind PROTO1(unsigned int, len)
{
  char *p;

  if (p = (char *)malloc(len)) return((char *)p);
  perror("malloc");
  exit(1);
}
 
/*
 * c_exec --
 *	build three parallel arrays, one with pointers to the strings passed
 *	on the command line, one with (possibly duplicated) pointers to the
 *	argv array, and one with integer values that are lengths of the
 *	strings, but also flags meaning that the string has to be massaged.
 */
PLAN *c_exec PROTO2(char ***, argvp, int, isok)
{
  PLAN *new;			/* node returned */
  register int cnt;
  register char **argv, **ap, *p;

  isoutput = 1;
    
  new = palloc(N_EXEC, find_exec);
  new->flags = isok;

  for (ap = argv = *argvp;; ++ap) {
    if (!*ap) {
      fprintf(stderr,"%s: no terminating", isok ? "-ok" : "-exec");
      exit(1);
    }
    if (**ap == ';') break;
  }

  cnt = ap - *argvp + 1;
  new->e_argv = (char **)emalloc_ffind((unsigned int)cnt * sizeof(char *));
  new->e_orig = (char **)emalloc_ffind((unsigned int)cnt * sizeof(char *));
  new->e_len = (int *)emalloc_ffind((unsigned int)cnt * sizeof(int));

  for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
    new->e_orig[cnt] = *argv;
    for (p = *argv; *p; ++p)
      if (p[0] == '{' && p[1] == '}') {
       	new->e_argv[cnt] = (char *)emalloc_ffind((unsigned int)MAXPATHLEN);
      	new->e_len[cnt] = MAXPATHLEN;
      	break;
      }
    if (!*p) {
      new->e_argv[cnt] = *argv;
      new->e_len[cnt] = 0;
    }
  }
  new->e_argv[cnt] = new->e_orig[cnt] = NULL;

  *argvp = argv + 1;
  return(new);
}

int printtime_ffind PROTO1(time_t, ftime)
{
  int i;
  char *longstring;

  longstring = (char *)ctime((long *)&ftime);
  for (i = 4; i < 11; ++i) putchar(longstring[i]);

#define SIXMONTHS       ((DAYSPERNYEAR / 2) * SECSPERDAY)

  if (ftime + SIXMONTHS > time((time_t *)NULL))
    for (i = 11; i < 16; ++i) putchar(longstring[i]);
  else {
    (void)putchar(' ');
    for (i = 20; i < 24; ++i) putchar(longstring[i]);
  }
  putchar(' ');
}

#define BLK(A) (((A)+1023)/1024)

void printlong_ffind PROTO2(char *, name, struct stat *, sb)
{
  char *modep;

  printf("%4ld ", BLK(sb->st_size));
  modep = ((S_IFDIR & sb->st_mode)) ? "drwxrwxrwx" : "-rw-rw-rw-" ;
  printf("%s %3u %-*s %-*s ", modep, sb->st_nlink, 8, "nobody", 8, "nobody");

  printf("%8ld ", sb->st_size);
  printtime_ffind(sb->st_mtime);
  printf("%s", name);
  putchar('\n');
}
 
/*
 * -ls functions --
 *
 *	Always true - prints the current sbuf to stdout in "ls" format.
 */
static int find_ls PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  printlong_ffind(path, sbuf);
  return(1);
}
 
PLAN *c_ls PROTO0((void))
{
  isoutput = 1;
  return(palloc(N_LS, find_ls));
}

/*
 * -name functions --
 *
 *	True if the basename of the filename being examined
 *	matches pattern using Pattern Matching Notation S3.14
 */
static int find_name PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  register char *name;

  /* extract filename */
  for (name = path + strlen(path); name > path && *name != '/'; name--);
  if (*name == '/') name++;
  return(fnmatch(plan->c_data, name));
}
 
PLAN *c_name PROTO1(char *, pattern)
{
  PLAN *new;

  new = palloc(N_NAME, find_name);
  new->c_data = pattern;
  return(new);
}
 
/*
 * -newer file functions --
 *
 *	True if the current file has been modified more recently
 *	then the modification time of the file named by the pathname
 *	file.
 */
static int find_newer PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  return(sbuf->st_mtime > plan->t_data);
}
 
PLAN *c_newer PROTO1(char *, filename)
{
  PLAN *new;
  struct stat sb;
    
  if (stat(filename, &sb)) {
    perror("stat");
    exit(1);
  }
  new = palloc(N_NEWER, find_newer);
  new->t_data = sb.st_mtime;
  return(new);
}
 
/*
 * -print functions --
 *
 *	Always true, causes the current pathame to be written to
 *	standard output.
 */
static int find_print PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  (void)printf("%s\n", path);
  return(1);
}
 
PLAN *c_print PROTO0((void))
{
  isoutput = 1;

  return(palloc(N_PRINT, find_print));
}
 
/*
 * -prune functions --
 *
 *	Prune a portion of the hierarchy.
 */
static int find_prune PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  process = -1;
  return(1);
}
 
PLAN *c_prune PROTO0((void))
{
  return(palloc(N_PRUNE, find_prune));
}
 
/*
 * -size n[c] functions --
 *
 *	True if the file size in bytes, divided by an implementation defined
 *	value and rounded up to the next integer, is n.  If n is followed by
 *	a c, the size is in bytes.
 */
#define	FIND_SIZE	512
static int divsize = 1;

static int find_size PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  off_t size;

  size = divsize ? ((sbuf->st_size + FIND_SIZE - 1)/FIND_SIZE) : sbuf->st_size;
  COMPARE(size, plan->o_data);
}
 
PLAN *c_size PROTO1(char *, arg)
{
  PLAN *new;
  char endch='c';
    
  new = palloc(N_SIZE, find_size);
  new->o_data = find_parsenum(new, "-size", arg, &endch);
  if (endch == 'c') divsize = 0;
  return(new);
}
 
/*
 * -type c functions --
 *
 *	True if the type of the file is c, where c is d or f for
 *	directory or regular file, respectively.
 */
static int find_type PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  return((sbuf->st_mode & S_IFMT) == plan->m_data);
}
 
PLAN *c_type PROTO1(char *, typestring)
{
  PLAN *new;
  mode_t  mask;
    
  switch (typestring[0]) {
    case 'd':
      mask = S_IFDIR;
      break;
    case 'f':
      mask = S_IFREG;
      break;
    default:
      fprintf(stderr,"-type: unknown type");
      exit(1);
  }
     
  new = palloc(N_TYPE, find_type);
  new->m_data = mask;
  return(new);
}
 
/*
 * ( expression ) functions --
 *
 *	True if expression is true.
 */
int find_expr PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  register PLAN *p;
  register int state;

  for(p=plan->p_data[0]; p && (state=(p->eval)(p, sbuf, path)); p=p->next);
  return(state);
}
 
/*
 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers.  They are
 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
 * to a N_EXPR node containing the expression and the ')' node is discarded.
 */
PLAN *c_openparen PROTO0((void))
{
  return(palloc(N_OPENPAREN, (int (*)())-1));
}
 
PLAN *c_closeparen PROTO0((void))
{
  return(palloc(N_CLOSEPAREN, (int (*)())-1));
}
 

/*
 * ! expression functions --
 *
 *	Negation of a primary; the unary NOT operator.
 */
static int find_not PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  register PLAN *p;
  register int state;

  for(p=plan->p_data[0]; p && (state=(p->eval)(p, sbuf, path)); p=p->next);
  return(!state);
}
 
PLAN *c_not PROTO0((void))
{
  return(palloc(N_NOT, find_not));
}
 
/*
 * expression -o expression functions --
 *
 *	Alternation of primaries; the OR operator.  The second expression is
 * not evaluated if the first expression is true.
 */
static int find_or PROTO3(PLAN *, plan, struct stat *, sbuf, char *, path)
{
  register PLAN *p;
  register int state;

  for(p=plan->p_data[0]; p && (state=(p->eval)(p, sbuf, path)); p=p->next);

  if (state) return(1);

  for(p=plan->p_data[1]; p && (state=(p->eval)(p, sbuf, path)); p=p->next);
  return(state);
}

PLAN *c_or PROTO0((void))
{
  return(palloc(N_OR, find_or));
}
