/* The Predicates and associated routines for Find.
   Copyright (C) 1987, 1990 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
   This port is also distributed under the terms of the
   GNU General Public License as published by the
   Free Software Foundation.

   Please note that this file is not identical to the
   original GNU release, you should have received this
   code as patch to the official release.

   $Header: e:/gnu/find/RCS/pred.c 1.2.0.3 90/09/23 16:09:50 tho Exp $
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#ifndef MSDOS
struct passwd *getpwuid ();
#include <grp.h>
struct group *getgrgid ();
#endif /* not MSDOS */
#ifndef USG
#include <strings.h>
#else
#include <string.h>
#define index strchr
#define rindex strrchr
#endif
#include "defs.h"

#ifdef MSDOS

#include <time.h>
#include <process.h>

extern void error (int status, int errnum, char *message, ...);
extern char *basename (char *fname);
extern char *filesystem_type (struct stat *statp);
extern void list_file (char *name, struct stat *statp);
extern int glob_match (char *pattern, char *text, int dot_special);

static char launch (struct pred_struct *pred_ptr);

#else /* not MSDOS */

int fork ();
int wait ();

boolean pred_and ();
boolean pred_atime ();
boolean pred_close ();
boolean pred_ctime ();
/* no pred_depth */
boolean pred_exec ();
boolean pred_fstype ();
/* no pred_fulldays */
boolean pred_group ();
boolean pred_inum ();
boolean pred_links ();
boolean pred_ls ();
boolean pred_mtime ();
boolean pred_name ();
boolean pred_negate ();
boolean pred_newer ();
boolean pred_nogroup ();
boolean pred_nouser ();
boolean pred_ok ();
boolean pred_open ();
boolean pred_or ();
boolean pred_perm ();
boolean pred_permmask ();
boolean pred_print ();
boolean pred_prune ();
boolean pred_regex ();
boolean pred_size ();
boolean pred_type ();
boolean pred_user ();
/* no pred_version */
/* no pred_xdev */

boolean launch ();
char *basename ();
char *filesystem_type ();
void list_file ();

#endif /* not MSDOS */

#ifdef	DEBUG
struct pred_assoc
{
#ifdef MSDOS
  PRED_FCT pred_func;
#else
  PFB pred_func;
#endif
  char *pred_name;
};

struct pred_assoc pred_table[] =
{
  {pred_and, "and     "},
  {pred_atime, "atime   "},
  {pred_close, ")       "},
  {pred_ctime, "ctime   "},
  {pred_exec, "exec    "},
  {pred_fstype, "fstype  "},
  {pred_group, "group   "},
  {pred_inum, "inum    "},
  {pred_links, "links   "},
  {pred_ls, "ls      "},
  {pred_mtime, "mtime   "},
  {pred_name, "name    "},
  {pred_negate, "!       "},
  {pred_newer, "newer   "},
  {pred_nogroup, "nogroup "},
  {pred_nouser, "nouser  "},
  {pred_ok, "ok      "},
  {pred_open, "(       "},
  {pred_or, "or      "},
  {pred_perm, "perm    "},
  {pred_permmask, "permmask"},
  {pred_print, "print   "},
  {pred_prune, "prune   "},
  {pred_regex, "regex   "},
  {pred_size, "size    "},
  {pred_type, "type    "},
  {pred_user, "user    "},
  {0, "none    "}
};

struct op_assoc
{
  short type;
  char *type_name;
};

struct op_assoc type_table[] =
{
  {NO_TYPE, "no_type	"},
  {VICTIM_TYPE, "victim_type	"},
  {UNI_OP, "uni_op	"},
  {BI_OP, "bi_op	"},
  {OPEN_PAREN, "open_paren	"},
  {CLOSE_PAREN, "close_paren	"},
  {-1, "unknown	"}
};

struct prec_assoc
{
  short prec;
  char *prec_name;
};

struct prec_assoc prec_table[] =
{
  {NO_PREC, "no_prec     "},
  {OR_PREC, "or_prec     "},
  {AND_PREC, "and_prec	"},
  {NEGATE_PREC, "negate_prec "},
  {MAX_PREC, "max_prec    "},
  {-1, "unknown	"}
};
#endif	/* DEBUG */

/* Predicate processing routines.
 
   PATHNAME is the full pathname of the file being checked.
   *STAT_BUF contains information about PATHNAME.
   *PRED_PTR contains information for applying the predicate.
 
   Return true if the file passes this predicate, false if not. */

boolean
pred_and (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if ((*pred_ptr->pred_left->pred_func) (pathname, stat_buf,
					 pred_ptr->pred_left))
    return ((*pred_ptr->pred_right->pred_func) (pathname, stat_buf,
						pred_ptr->pred_right));
  else
    return (false);
}

boolean
pred_atime (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
#ifdef	DEBUG
  printf ("pred_atime: checking %s %ld %s", pathname, stat_buf->st_atime,
	  ctime (&stat_buf->st_atime));
#endif	/* DEBUG */
  switch (pred_ptr->args.info.kind)
    {
    case COMP_GT:
      if (stat_buf->st_atime > pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_LT:
      if (stat_buf->st_atime < pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_EQ:
      if ((stat_buf->st_atime >= pred_ptr->args.info.l_val)
	  && (stat_buf->st_atime < pred_ptr->args.info.l_val
	      + DAYSECS))
	return (true);
      break;
    }
  return (false);
}

boolean
pred_close (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  error (0, 0, "oops -- got into pred_close!");
  return (true);
}

boolean
pred_ctime (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  switch (pred_ptr->args.info.kind)
    {
    case COMP_GT:
      if (stat_buf->st_ctime > pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_LT:
      if (stat_buf->st_ctime < pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_EQ:
      if ((stat_buf->st_ctime >= pred_ptr->args.info.l_val)
	  && (stat_buf->st_ctime < pred_ptr->args.info.l_val
	      + DAYSECS))
	return (true);
      break;
    }
  return (false);
}

boolean
pred_exec (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  int i, path_pos;

  for (path_pos = 0, i = pred_ptr->args.exec_vec.path_loc[0];
       i != -1;
       path_pos++, i = pred_ptr->args.exec_vec.path_loc[path_pos])
    pred_ptr->args.exec_vec.vec[i] = pathname;
  return (launch (pred_ptr));
}

boolean
pred_fstype (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  char *fstype;

  fstype = filesystem_type (stat_buf);
  if (fstype && strcmp (fstype, pred_ptr->args.str) == 0)
    return (true);
  return (false);
}

boolean
pred_group (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (pred_ptr->args.gid == stat_buf->st_gid)
    return (true);
  else
    return (false);
}

boolean
pred_inum (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  switch (pred_ptr->args.info.kind)
    {
    case COMP_GT:
      if (stat_buf->st_ino > pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_LT:
      if (stat_buf->st_ino < pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_EQ:
      if (stat_buf->st_ino == pred_ptr->args.info.l_val)
	return (true);
      break;
    }
  return (false);
}

boolean
pred_links (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  switch (pred_ptr->args.info.kind)
    {
    case COMP_GT:
      if (stat_buf->st_nlink > pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_LT:
      if (stat_buf->st_nlink < pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_EQ:
      if (stat_buf->st_nlink == pred_ptr->args.info.l_val)
	return (true);
      break;
    }
  return (false);
}

boolean
pred_ls (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  list_file (pathname, stat_buf);
  return (true);
}

boolean
pred_mtime (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  switch (pred_ptr->args.info.kind)
    {
    case COMP_GT:
      if (stat_buf->st_mtime > pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_LT:
      if (stat_buf->st_mtime < pred_ptr->args.info.l_val)
	return (true);
      break;
    case COMP_EQ:
      if ((stat_buf->st_mtime >= pred_ptr->args.info.l_val)
	  && (stat_buf->st_mtime < pred_ptr->args.info.l_val
	      + DAYSECS))
	return (true);
      break;
    }
  return (false);
}

boolean
pred_name (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  char *just_fname;

  just_fname = basename (pathname);
  if (glob_match (pred_ptr->args.str, just_fname, 1))
    return (true);
  return (false);
}

boolean
pred_negate (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  return (!(*pred_ptr->pred_left->pred_func) (pathname, stat_buf,
					      pred_ptr->pred_left));
}

boolean
pred_newer (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (stat_buf->st_mtime > pred_ptr->args.time)
    return (true);
  return (false);
}

boolean
pred_nogroup (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  return getgrgid (stat_buf->st_gid) == NULL;
}

boolean
pred_nouser (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  return getpwuid (stat_buf->st_uid) == NULL;
}

boolean
pred_ok (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  int i, yes, path_pos;

  for (path_pos = 0, i = pred_ptr->args.exec_vec.path_loc[0];
       i != -1;
       path_pos++, i = pred_ptr->args.exec_vec.path_loc[path_pos])
    pred_ptr->args.exec_vec.vec[i] = pathname;
  fprintf (stderr, "< %s ... %s > ? ",
	   pred_ptr->args.exec_vec.vec[0], pathname);
  fflush (stderr);
  i = getchar ();
  yes = (i == 'y' || i == 'Y');
  while (i != EOF && i != '\n')
    i = getchar ();
  if (yes)
    return (launch (pred_ptr));
  else
    return (false);
}

boolean
pred_open (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  error (0, 0, "oops -- got into pred_open!");
  return (true);
}

boolean
pred_or (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (!(*pred_ptr->pred_left->pred_func) (pathname, stat_buf,
					  pred_ptr->pred_left))
    return ((*pred_ptr->pred_right->pred_func) (pathname, stat_buf,
						pred_ptr->pred_right));
  else
    return (true);
}

boolean
pred_perm (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (pred_ptr->args.perm & 010000)
    {
      /* Magic flag set in parse_perm: compare suid, sgid, sticky bits as well;
	 also, true if at least the given bits are set. */
      if ((stat_buf->st_mode & 07777 & perm_mask & pred_ptr->args.perm)
	  == (pred_ptr->args.perm & 07777))
	return (true);
    }
  else
    {
      if ((stat_buf->st_mode & 0777 & perm_mask) == pred_ptr->args.perm)
	return (true);
    }
  return (false);
}

boolean
pred_permmask (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  perm_mask = pred_ptr->args.perm;
  return (true);
}

boolean
pred_print (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  puts (pathname);
  return (true);
}

boolean
pred_prune (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  stop_at_current_level = true;
  return (do_dir_first);	/* This is what SunOS find seems to do. */
}

boolean
pred_regex (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (re_match (pred_ptr->args.regex, pathname, strlen (pathname), 0,
		(struct re_registers *) NULL) != -1)
    return (true);
  return (false);
}

boolean
pred_size (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  unsigned long f_val;

  if (pred_ptr->args.size.block)
    f_val = (stat_buf->st_size + BLKSIZE - 1) / BLKSIZE;
  else
    f_val = stat_buf->st_size;
  switch (pred_ptr->args.size.kind)
    {
    case COMP_GT:
      if (f_val > pred_ptr->args.size.size)
	return (true);
      break;
    case COMP_LT:
      if (f_val < pred_ptr->args.size.size)
	return (true);
      break;
    case COMP_EQ:
      if (f_val == pred_ptr->args.size.size)
	return (true);
      break;
    }
  return (false);
}

boolean
pred_type (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if ((stat_buf->st_mode & S_IFMT) == pred_ptr->args.type)
    return (true);
  else
    return (false);
}

boolean
pred_user (pathname, stat_buf, pred_ptr)
     char *pathname;
     struct stat *stat_buf;
     struct pred_struct *pred_ptr;
{
  if (pred_ptr->args.uid == stat_buf->st_uid)
    return (true);
  else
    return (false);
}

boolean
launch (pred_ptr)
     struct pred_struct *pred_ptr;
{
  int status, wait_ret, child_pid;

  /*  1) fork to get a child; parent remembers the child pid
      2) child execs the command requested
      3) parent waits, with stat_loc non_zero
      check for proper pid of child
      Possible returns:
   
      ret	errno	status(h)   status(l)
      pid	x	signal#	    0177	stopped
      pid	x	exit arg    0		term by exit or _exit
      pid	x	0	    signal #	term by signal
      -1	EINTR				parent got signal
      -1	other				some other kind of error
      
      Return true only if the pid matches, status(l) is
      zero, and the exit arg (status high) is 0.
      Otherwise return false, possibly printing an error message. */

#ifdef MSDOS

  status = spawnvp (P_WAIT, pred_ptr->args.exec_vec.vec[0],\
		    pred_ptr->args.exec_vec.vec);

#else /* not MSDOS */

  child_pid = fork ();
  if (child_pid == -1)
    error (1, errno, "cannot fork");
  if (child_pid == 0)
    {
      /* We be the child. */
      execvp (pred_ptr->args.exec_vec.vec[0], pred_ptr->args.exec_vec.vec);
      error (1, errno, "%s", pred_ptr->args.exec_vec.vec[0]);
    }
  wait_ret = wait (&status);
  if (wait_ret == -1)
    {
      error (0, errno, "error waiting for child process");
      exit_status = 1;
      return (false);
    }
  if (wait_ret != child_pid)
    {
      error (0, 0, "wait saw another child, pid %d", wait_ret);
      error (0, 0, "expected child pid %d; status: %d %d",
	     child_pid, status >> 8, status & 0xff);
      exit_status = 1;
      return (false);
    }
  if (status & 0xff == 0177)
    {
      error (0, 0, "child stopped; status %d %d\n",
	     status >> 8, status & 0xff);
      exit_status = 1;
      return (false);
    }

#endif /* not MSDOS */

  if (status & 0xff != 0)
    {
      error (0, 0, "child terminated abnormally; status %d %d",
	     status >> 8, status & 0xff);
      exit_status = 1;
      return (false);
    }
  return (!(status >> 8));
}

#ifdef	DEBUG
/* Return a pointer to the string representation of 
   the predicate function PRED_FUNC. */

char *
find_pred_name (pred_func)
#ifdef MSDOS
     PRED_FCT pred_func;
#else
     PFB pred_func;
#endif
{
  int i;

  for (i = 0; pred_table[i].pred_func != 0; i++)
    if (pred_table[i].pred_func == pred_func)
      break;
  return (pred_table[i].pred_name);
}

char *
type_name (type)
     short type;
{
  int i;

  for (i = 0; type_table[i].type != (short) -1; i++)
    if (type_table[i].type == type)
      break;
  return (type_table[i].type_name);
}

char *
prec_name (prec)
     short prec;
{
  int i;

  for (i = 0; prec_table[i].prec != (short) -1; i++)
    if (prec_table[i].prec == prec)
      break;
  return (prec_table[i].prec_name);
}

/* Walk the expression tree NODE to stdout.
   INDENT is the number of levels to indent the left margin. */

void
print_tree (node, indent)
     struct pred_struct *node;
     int indent;
{
  int i;

  if (node == NULL)
    return;
  for (i = 0; i < indent; i++)
    printf ("    ");
  printf ("%s %s %s %x\n", find_pred_name (node->pred_func),
	  type_name (node->p_type), prec_name (node->p_prec), node);
  for (i = 0; i < indent; i++)
    printf ("    ");
  printf ("left:\n");
  print_tree (node->pred_left, indent + 1);
  for (i = 0; i < indent; i++)
    printf ("    ");
  printf ("right:\n");
  print_tree (node->pred_right, indent + 1);
}

/* Copy STR into BUF and trim blanks from the end of BUF.
   Return BUF. */

char *
blank_rtrim (str, buf)
     char *str;
     char *buf;
{
  int i;

  if (str == NULL)
    return (NULL);
  strcpy (buf, str);
  i = strlen (buf) - 1;
  while ((i >= 0) && ((buf[i] == ' ') || buf[i] == '\t'))
    i--;
  buf[++i] = '\0';
  return (buf);
}

/* Print out the predicate list starting at NODE. */

void
print_list (node)
     struct pred_struct *node;
{
  struct pred_struct *cur;
  char name[256];

  cur = node;
  while (cur != NULL)
    {
      printf ("%s ", blank_rtrim (find_pred_name (cur->pred_func), name));
      cur = cur->pred_next;
    }
  printf ("\n");
}
#endif	/* DEBUG */
