/* inv.c: project tree inventory library routines
 *
 ****************************************************************
 * Copyright (C) 2002 Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/stdlib.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/vu/vu.h"
#include "hackerlab/vu/vfdbuf.h"
#include "hackerlab/vu/vu-utils-vfdbuf.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "file-utils/liblarch/inv.h"


/* __STDC__ prototypes for static functions */
static int cmp_files (const void * va, const void * vb);
static int contains_illegal_character (char * filename);
static int filename_matches (int * errn, regex_t * pattern, char * filename);
static int is_nested_tree (int * errn,
			   char ** bad_file,
			   struct alloc_limits * limits,
			   char * path,
			   char * control_dir_name);
static int is_comment_line (t_uchar * line, long len);
static int sets_re (struct alloc_limits * limits, char * kw, char ** re, t_uchar * line, long len);
static int sets_tagging_method (char * kw, enum ftag_method * method_var, enum ftag_method method, t_uchar * line, long len);



static int
cmp_files (const void * va, const void * vb)
{
  char * a;
  char * b;

  a = *(char **)va;
  b = *(char **)vb;

  return str_cmp (a, b);
}

static int
right_order_for_recursion (char * a, char * b)
{
  /* a and b are already in lexical order (a < b)
   */
  while ((*a == *b) && *a && *b)
    {
      ++a;
      ++b;
    }

  if (!*a)
    {
      /* Does "A/" come before "B" in an alphabetical listing?
       */
      return (*b > '/');
    }
  else if (!*b)
    {
      /* Does "B/" come after "A" in an aphabetical listing?
       */
      return (*a < '/');
    }
  else
    {
      invariant (*a < *b);
      return 1;
    }
}

int
inv_traversal (int * errn,
	       char ** bad_file,
	       struct alloc_limits * limits,
	       struct inv_options * options,
	       t_uchar * path,
	       inv_callback callback,
	       void * closure)
{
  DIR * dir;
  int is;
  char ** files;
  int n_files;
  int ign;
  int x;
  int deferred_recursions_head;
  int deferred_recursions_tail;
  int * deferred_recursions;
  int * is_deferred_nested;
  char * rel_file;


  if (0 > vu_opendir (errn, &dir, path))
    {
      *bad_file = str_save (limits, path);
      return -1;
    }

  files = 0;
  n_files = 0;

  while (1)
    {
      char * file;

      if (0 > vu_readdir (errn, limits, &file, dir))
	{
	  vu_closedir (&ign, dir);
	  
	  if (*errn)
	    {
	      for (x = 0; x < n_files; ++x)
		{
		  lim_free (limits, files[x]);
		}
	      *bad_file = str_save (limits, path);
	      return -1;
	    }
	  break;
	}
      {
	char ** p;

	p = (char **)ar_push ((void **)&files, limits, sizeof (char *));
	if (!p)
	  goto enomem_error;
	*p = file;
	++n_files;
      }
    }

  qsort ((void *)files, n_files, sizeof (char *), cmp_files);

  /* We want to invoke `callback' on a lexically sorted list of paths.
   * Suppose that "foo" is a directory, but "foo-bar" also exists.
   * That means we have to invoke callbacks in the order:
   *
   *		foo
   *		foo-bar
   *		foo/xyzzy
   *
   * When we detect that "foo" is a directory, we can't
   * necessarilly recurse immediately. Instead, we keep a queue
   * of deferred directories, recursing on them at the right time.
   */

  rel_file = 0;
  deferred_recursions_head = 0;
  deferred_recursions_tail = 0;
  deferred_recursions = 0;
  is_deferred_nested = 0;
  ar_setsize ((void **)&deferred_recursions, limits, ar_size ((void *)files, limits, sizeof (char *)), sizeof (int));
  ar_setsize ((void **)&is_deferred_nested, limits, ar_size ((void *)files, limits, sizeof (char *)), sizeof (int));

  if (!deferred_recursions)
    {
    enomem_error:
      *errn = ENOMEM;
    error_return:
      {
	if (rel_file)
	  *bad_file = str_save (limits, rel_file);
	else
	  *bad_file = 0;

      error_return_bad_file_set:
	lim_free (limits, rel_file);
	for (x = 0; x < n_files; ++x)
	  {
	    lim_free (limits, files[x]);
	  }
	ar_free ((void **)&files, limits);
	ar_free ((void **)&deferred_recursions, limits);
	ar_free ((void **)&is_deferred_nested, limits);
	return -1;
      }
    }

  x = 0;
  while ((x < n_files) || (deferred_recursions_head != deferred_recursions_tail))
    {
      int is_deferred;
      int deferred_nested;
      char * file;
      struct stat stat_buf;

      if ((deferred_recursions_head != deferred_recursions_tail)
	  && ((x >= n_files)
	      || right_order_for_recursion (files[deferred_recursions[deferred_recursions_head]], files[x])))
	{
	  is_deferred = 1;
	  file = files[deferred_recursions[deferred_recursions_head]];
	  deferred_nested = is_deferred_nested[deferred_recursions_head];
	  ++deferred_recursions_head;
	}
      else
	{
	  is_deferred = 0;
	  file = files[x];
	  ++x;
	}

      rel_file = file_name_in_vicinity (limits, path, file);
      if (!rel_file)
	goto enomem_error;
      
      if (is_deferred)
	{
	  if (deferred_nested)
	    goto handle_deferred_nested;
	  else
	    goto handle_deferred;
	}

      /* . and .. are mandatory exclude files
       */
      if (!str_cmp (".", file) || !str_cmp ("..", file))
	{
	next_file:
	  lim_free (limits, rel_file);
	  rel_file = 0;
	  continue;
	}

      if (0 > vu_lstat (errn, rel_file, &stat_buf))
	goto error_return;


      /* non-printing characters, spaces, and glob characters are
       * mandatory unrecognized files
       */
      if (contains_illegal_character (file))
	{
	unrecognized_file:
	  if ((options->categories & inv_unrecognized) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_unrecognized, 0, closure)))
	    goto error_return;
	  goto next_file;
	}

      /* callers can specify a pattern for additional files to
       * exclude from consideration.
       */
      is = !options->include_excluded && filename_matches (errn, &options->excludes_pattern, file);
      if (is < 0)
	goto error_return;
      if (is)
	goto next_file;

      /* callers can specify a pattern for "junk" files -- files
       * presumed safe-to-be-removed by automatic tools, barring
       * concurrent tools.
       * 
       * file names beginning with ",," are always considered junk files.
       */
      is = (file[0] == ',' && file[1] == ',') || filename_matches (errn, &options->junk_pattern, file);
      if (is < 0)
	goto error_return;
      else if (is)
	{
	  if ((options->categories & inv_junk) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_junk, 0, closure)))
	    goto error_return;
	  goto next_file;
	}
	
      /* callers can specify a pattern for "backup" files -- files
       * that are created by editors and similar programs to save old
       * versions
       */
      is = filename_matches (errn, &options->backup_pattern, file);
      if (is < 0)
	goto error_return;
      else if (is)
	{
	  if ((options->categories & inv_backup) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_backup, 0, closure)))
	    goto error_return;
	  goto next_file;
	}
	
      /* callers can specify a pattern for "precious" files -- files
       * that are not part of the source, but which should never be 
       * automatically removed.
       */
      is = filename_matches (errn, &options->precious_pattern, file);
      if (is < 0)
	goto error_return;
      else if (is)
	{
	  if ((options->categories & inv_precious) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_precious, 0, closure)))
	    goto error_return;
	  goto next_file;
	}

      /* callers can specify a pattern for explicitly "unrecognized" files --
       * files that should be flagged as errors in tree-lint reports.
       */
      is = filename_matches (errn, &options->unrecognized_pattern, file);
      if (is < 0)
	goto error_return;
      else if (is)
	{
	  goto unrecognized_file;
	}

      /* finally, a pattern for "source" files -- files which are expected
       * to be source files.  Note that if the tagging method is "explicit" and
       * an apparent source file lacks a tag and is not a nested tree, it reverts 
       * to being an unrecognized file.
       * 
       * if a directory appears to be a source directory, but contains a rules
       * directory of its own, then it is in fact the root of a nested tree -- not
       * a regular source file.
       */
      is = filename_matches (errn, &options->source_pattern, file);
      if (is < 0)
	goto error_return;
      else if (is)
	{
	  int is_nested;

	  is_nested = (S_ISDIR (stat_buf.st_mode) && is_nested_tree (errn, bad_file, limits, rel_file, options->control_dir_name));
	  if (is_nested < 0)
	    goto error_return_bad_file_set;
	  else if (is_nested)
	    {
	      if ((options->categories & inv_tree) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_tree, 0, closure)))
		goto error_return;

	      if (options->nested)
		{

		  if ((x < n_files) && !right_order_for_recursion (file, files[x]))
		    {
		      deferred_recursions[deferred_recursions_tail] = x - 1;
		      is_deferred_nested[deferred_recursions_tail] = 1;
		      ++deferred_recursions_tail;
		      continue;
		    }

		handle_deferred_nested:
		  if (0 > inv_traversal (errn, bad_file, limits, options, rel_file, callback, closure))
		    goto error_return;
		}

	      goto next_file;
	    }
	  else
	    {
	      t_uchar * tag;

	      tag = 0;
	      if (options->want_tags || (options->method == ftag_explicit))
		{
		  tag = file_tag (errn, limits, options->method, rel_file);

		  if (!tag)
		    {
		      if (*errn)
			goto error_return;
		      else if (!options->include_untagged)
			goto unrecognized_file;
		    }
		}

	      if ((options->categories & inv_source) && (0 > callback (errn, limits, rel_file, &stat_buf, inv_source, tag, closure)))
		{
		  lim_free (limits, tag);
		  tag = 0;
		  goto error_return;
		}

	      if (tag)
		{
		  lim_free (limits, tag);
		  tag = 0;
		}

	      if (S_ISDIR (stat_buf.st_mode))
		{
		  if ((x < n_files) && !right_order_for_recursion (file, files[x]))
		    {
		      deferred_recursions[deferred_recursions_tail] = x - 1;
		      is_deferred_nested[deferred_recursions_tail] = 0;
		      ++deferred_recursions_tail;
		      continue;
		    }

		handle_deferred:
		  if (0 > inv_traversal (errn, bad_file, limits, options, rel_file, callback, closure))
		    goto error_return;
		}
	      goto next_file;
	    }
	}
      else
	goto unrecognized_file;
    }

  for (x = 0; x < n_files; ++x)
    {
      lim_free (limits, files[x]);
    }
  ar_free ((void **)&files, limits);
  return 0;
}



struct conventions_conventions
{
  char * tree_control_system_name;
  char * control_dir_name;
  char * conventions_file_name;
};

static struct conventions_conventions conventional_conventions_conventions[] =
{
  { "arch", "{arch}", "=tagging-method" },
  { 0, 0, 0 }
};
  
int
inv_guess_conventions_files (int * errn,
			     struct alloc_limits * limits,
			     struct inv_options * options,
			     char * path)
{
  int here_fd;
  int ign;
  int c;

  options->tree_control_system_name = 0;
  options->control_dir_name = 0;
  options->conventions_file_name = 0;

  here_fd = vu_open (errn, ".", O_RDONLY, 0);
  if (here_fd < 0)
    return -1;

  if (0 > vu_chdir (errn, path))
    {
      vu_close (&ign, here_fd);
    }

  path = current_working_directory (errn, limits);
  /* error check further down
   */

  if (0 > vu_fchdir (&ign, here_fd))
    panic ("inv_get_naming_conventions: unable to restore current working directory\n");
  vu_close (&ign, here_fd);

  if (!path)
    return -1;

  {
    char * maybe_root;
    char * ctl_file_suffix;
    char * ctl_file;

    maybe_root = 0;
    ctl_file_suffix = 0;
    ctl_file = 0;

    maybe_root = str_save (limits, path);
    if (!maybe_root)
      {
      enomem_ctl_file:
	*errn = ENOMEM;

	lim_free (limits, maybe_root);
	lim_free (limits, ctl_file_suffix);
	lim_free (limits, ctl_file);
	lim_free (limits, path);
	return -1;
      }

    while (1)			/* maybe_root iterates from path to "/", following (syntactic) ".." */
      {
	for (c = 0; conventional_conventions_conventions[c].tree_control_system_name; ++c)
	  {
	    int fd;
	  
	    /* In each directory, do any of the conventional choices for 
	     * "control_dir_name/conventions_file_name" exist?
	     */
	    ctl_file_suffix = file_name_in_vicinity (limits,
						     conventional_conventions_conventions[c].control_dir_name,
						     conventional_conventions_conventions[c].conventions_file_name);
	    if (!ctl_file_suffix)
	      goto enomem_ctl_file;

	    ctl_file = file_name_in_vicinity (limits, maybe_root, ctl_file_suffix);
	    if (!ctl_file)
	      goto enomem_ctl_file;

	    fd = vu_open (errn, ctl_file, O_RDONLY, 0);
	    if (fd < 0)
	      {
		lim_free (limits, ctl_file_suffix);
		ctl_file_suffix = 0;
		lim_free (limits, ctl_file);
		ctl_file = 0;
		continue;	/* try next control file name choice */
	      }
	    else
	      {
		options->tree_control_system_name = str_save (limits, conventional_conventions_conventions[c].tree_control_system_name);
		options->control_dir_name = str_save (limits, conventional_conventions_conventions[c].control_dir_name);
		options->conventions_file_name = str_save (limits, conventional_conventions_conventions[c].conventions_file_name);
		if (!options->tree_control_system_name || !options->control_dir_name || !options->conventions_file_name)
		  goto enomem_ctl_file;

		lim_free (limits, ctl_file_suffix);
		lim_free (limits, ctl_file);
		lim_free (limits, maybe_root);
		lim_free (limits, path);
		return 0;
	      }
	  }

	/* None of the conventional control files found here, try the parent directory.
	 */
	if (!str_cmp (maybe_root, "/"))
	  {
	  no_ctl_files:
	    lim_free (limits, maybe_root);
	    lim_free (limits, ctl_file_suffix);
	    lim_free (limits, ctl_file);
	    lim_free (limits, path);
	    return 0;
	  }
	{
	  t_uchar * new_root;
	  t_uchar * d;

	  d = file_name_from_directory (limits, maybe_root);
	  if (!d)
	    goto enomem_ctl_file;
	  new_root = file_name_directory (limits, d); /* HEY: see following comment */
	  lim_free (limits, d);

	  /* when the fs.h interface changes, fix the error detection here.
	   * it fails to distinguish between non-absolute filenames without "/" and
	   * allocation failures.
	   */
	  if (!new_root)
	    {
	      if (maybe_root[0] != '/')
		goto no_ctl_files;
	      goto enomem_ctl_file;
	    }
	  lim_free (limits, maybe_root);
	  maybe_root = new_root;
	}
      }
  }

  panic ("not reached in inv_guess_conventions_files");
  *errn = EINVAL;
  return -1;
}

int
inv_get_naming_conventions (int * errn,
			    int * re_error,
			    struct alloc_limits * limits,
			    struct inv_options * options,
			    char * path)
{
  char * excludes;
  char * junk;
  char * backup;
  char * precious;
  char * unrecognized;
  char * source;
  int here_fd;
  int ign;


  here_fd = vu_open (errn, ".", O_RDONLY, 0);
  if (here_fd < 0)
    return -1;

  if (0 > vu_chdir (errn, path))
    {
      vu_close (&ign, here_fd);
    }

  path = current_working_directory (errn, limits);
  /* error check further down
   */

  if (0 > vu_fchdir (&ign, here_fd))
    panic ("inv_get_naming_conventions: unable to restore current working directory\n");
  vu_close (&ign, here_fd);

  if (!path)
    {
      return -1;
    }

  excludes = str_save (limits, "^(.arch-ids|\\{arch\\})$");
  junk = str_save (limits, "^(,.*)$");
  backup = str_save (limits, "^.*(~|\\.~[0-9]+~|\\.bak|\\.orig|\\.rej|\\.original|\\.modified|\\.reject)$");
  precious = str_save (limits, "^(\\+.*|\\.gdbinit|\\.#ckpts-lock|=build\\.*|=install\\.*|CVS|CVS\\.adm|RCS|RCSLOG|SCCS|TAGS)$");
  unrecognized = str_save (limits, "^(.*\\.(o|a|so|core)|core)$");
  source = str_save (limits, "^([_=a-zA-Z0-9].*|\\.arch-ids|\\{arch\\}|\\.arch-project-tree)$");

  if (!excludes || !junk || !backup || !precious || !unrecognized || !source)
    {
      lim_free (limits, excludes);
      lim_free (limits, junk);
      lim_free (limits, backup);
      lim_free (limits, precious);
      lim_free (limits, unrecognized);
      lim_free (limits, source);
      lim_free (limits, path);
      *errn = ENOMEM;
      return -1;
    }

  if (options->control_dir_name)
    {
      char * maybe_root;
      char * ctl_file_suffix;
      char * ctl_file;

      maybe_root = 0;
      ctl_file_suffix = 0;
      ctl_file = 0;

      if (options->conventions_file_name)
	ctl_file_suffix = file_name_in_vicinity (limits, options->control_dir_name, options->conventions_file_name);
      else
	ctl_file_suffix = file_name_in_vicinity (limits, options->control_dir_name, "=tagging-method");
      if (!ctl_file_suffix)
	{
	enomem_ctl_file:
	  *errn = ENOMEM;
	error_ctl_file:
	  lim_free (limits, maybe_root);
	  lim_free (limits, ctl_file_suffix);
	  lim_free (limits, ctl_file);
	  lim_free (limits, excludes);
	  lim_free (limits, junk);
	  lim_free (limits, backup);
	  lim_free (limits, precious);
	  lim_free (limits, unrecognized);
	  lim_free (limits, source);
	  lim_free (limits, path);
	  return -1;
	}

      maybe_root = str_save (limits, path);
      if (!maybe_root)
	goto enomem_ctl_file;

      while (1)
	{
	  int fd;
	  
	  ctl_file = file_name_in_vicinity (limits, maybe_root, ctl_file_suffix);
	  if (!ctl_file)
	    goto enomem_ctl_file;

	  fd = vu_open (errn, ctl_file, O_RDONLY, 0);
	  if (fd < 0)
	    {
	      char * new_root;

	      if (*errn != ENOENT)
		goto error_ctl_file;


	      {
		char * ctl_dir;
		struct stat ctl_dir_stat;
		int stat_res;

		ctl_dir = file_name_in_vicinity (limits, maybe_root, options->control_dir_name);
		if (!ctl_dir)
		  goto error_ctl_file;

		stat_res = vu_lstat (errn, ctl_dir, &ctl_dir_stat);

		lim_free (limits, ctl_dir);

		if (0 <= stat_res)
		  {
		    /* found a control directory, but no control file.
		     */
		    goto no_ctl_files;
		  }
		else if (*errn != ENOENT)
		  {
		    /* odd error trying to find control file
		     */
		    goto error_ctl_file;
		  }
		/* else -- fallthrough -- try the parent directory. */
	      }
	      


	      if (!str_cmp (maybe_root, "/"))
		{
		no_ctl_files:
		  lim_free (limits, maybe_root);
		  maybe_root = 0;
		  lim_free (limits, ctl_file_suffix);
		  ctl_file_suffix = 0;
		  lim_free (limits, ctl_file);
		  ctl_file = 0;
		  goto compile_conventions;
		}
	      {
		t_uchar * d;

		d = file_name_from_directory (limits, maybe_root);
		if (!d)
		  goto enomem_ctl_file;
		new_root = file_name_directory (limits, d); /* HEY: see following comment */
		lim_free (limits, d);

		/* when the fs.h interface changes, fix the error detection here.
		 * it fails to distinguish between non-absolute filenames without "/" and
		 * allocation failures.
		 */
		if (!new_root)
		  {
		    if (maybe_root[0] != '/')
		      goto no_ctl_files;
		    goto enomem_ctl_file;
		  }
		lim_free (limits, maybe_root);
		maybe_root = new_root;
	      }
	    }
	  else
	    {
	      t_uchar * line;
	      long len;

	      if (0 > vfdbuf_buffer_fd (errn, fd, 0, O_RDONLY, 0))
		goto error_ctl_file;
	      while (1)
		{
		  if (0 > vfdbuf_next_line (errn, &line, &len, fd))
		    {
		      int ign;
		      vu_close (&ign, fd);
		      goto error_ctl_file;
		    }

		  if (!len)
		    {
		      int ign;
		      vu_close (&ign, fd);
		      break;
		    }

		  if (!is_comment_line (line, len)
		      && !sets_tagging_method ("implicit", &options->method, ftag_implicit, line, len)
		      && !sets_tagging_method ("explicit", &options->method, ftag_explicit, line, len)
		      && !sets_tagging_method ("names", &options->method, ftag_names, line, len)
		      && !sets_re (limits, "exclude", &excludes, line, len)
		      && !sets_re (limits, "junk", &junk, line, len)
		      && !sets_re (limits, "backup", &backup, line, len)
		      && !sets_re (limits, "precious", &precious, line, len)
		      && !sets_re (limits, "unrecognized", &unrecognized, line, len)
		      && !sets_re (limits, "source", &source, line, len))
		    {
		      /* very bogus */
		      safe_printfmt (2, "inv_get_naming_conventions: illegal naming conventions spec\n");
		      safe_printfmt (2, "  file: %s\n", ctl_file);
		      safe_printfmt (2, "  line: %.*s\n", (int)len, line);
		      exit (2);
		    }
		}
	      break;
	    }
	}
    }

 compile_conventions:


  *re_error = regcomp (&options->excludes_pattern, excludes, REG_EXTENDED);
  if (*re_error)
    {
    dflt_re_error:
      *errn = 0;
      lim_free (limits, excludes);
      lim_free (limits, junk);
      lim_free (limits, backup);
      lim_free (limits, precious);
      lim_free (limits, unrecognized);
      lim_free (limits, source);
      lim_free (limits, path);
      return -1;
    }
  *re_error = regcomp (&options->junk_pattern, junk, REG_EXTENDED);
  if (*re_error)
    goto dflt_re_error;
  *re_error = regcomp (&options->backup_pattern, backup, REG_EXTENDED);
  if (*re_error)
    goto dflt_re_error;
  *re_error = regcomp (&options->precious_pattern, precious, REG_EXTENDED);
  if (*re_error)
    goto dflt_re_error;
  *re_error = regcomp (&options->unrecognized_pattern, unrecognized, REG_EXTENDED);
  if (*re_error)
    goto dflt_re_error;
  *re_error = regcomp (&options->source_pattern, source, REG_EXTENDED);
  if (*re_error)
    goto dflt_re_error;

  lim_free (limits, excludes);
  lim_free (limits, junk);
  lim_free (limits, backup);
  lim_free (limits, precious);
  lim_free (limits, unrecognized);
  lim_free (limits, source);
  lim_free (limits, path);

  return 0;
}

void
inv_free_naming_conventions (struct inv_options * options)
{
  regfree (&options->excludes_pattern);
  regfree (&options->junk_pattern);
  regfree (&options->backup_pattern);
  regfree (&options->precious_pattern);
  regfree (&options->unrecognized_pattern);
  regfree (&options->source_pattern);
}




static int
contains_illegal_character (char * filename)
{
  int x;

  for (x = 0; filename[x]; ++x)
    if ((filename[x] == '*')
	|| (filename[x] == '?')
	|| (filename[x] == '[')
	|| (filename[x] == ']')
	|| (filename[x] == '\\')
	|| (filename[x] == ' ')
	|| (filename[x] == '\t')
	|| (!char_is_printable (filename[x])))
      return 1;

  return 0;
}

static int
filename_matches (int * errn, regex_t * pattern, char * filename)
{
  int answer;

  answer = regexec (pattern, filename, 0, 0, 0);

  if (answer == REG_ESPACE)
    {
      *errn = ENOMEM;
      return -1;
    }

  if (answer == REG_NOMATCH)
    return 0;

  if (answer == REG_NOERROR)
    return 1;

  panic ("unexpected regexec error in inv_traversal");
  return -1;
}

static int
is_nested_tree (int * errn,
		char ** bad_file,
		struct alloc_limits * limits,
		char * path,
		char * control_dir_name)
{
  t_uchar * ctl_file;
  struct stat stat_buf;

  if (!control_dir_name)
    return 0;

  ctl_file = file_name_in_vicinity (limits, path, control_dir_name);
  if (!ctl_file)
    {
      *bad_file = 0;
      *errn = ENOMEM;
      return -1;
    }

  if (0 > vu_lstat (errn, ctl_file, &stat_buf))
    {
      if (*errn == ENOENT)
	{
	  lim_free (limits, ctl_file);
	  return 0;
	}
      else
	{
	  *bad_file = str_save (limits, ctl_file);
	  lim_free (limits, ctl_file);
	  return -1;
	}
    }

  lim_free (limits, ctl_file);
  return 1;
}

static int
is_comment_line (t_uchar * line, long len)
{
  return !len || char_is_space (line[0]) || (line[0] == '#');
}

static int
sets_re (struct alloc_limits * limits, char * kw, char ** re, t_uchar * line, long len)
{
  int l;

  l = str_length (kw);

  if (len < (l + 1))
    return 0;

  if (str_cmp_prefix (kw, line) || !char_is_space (line[l]))
    return 0;

  lim_free (limits, *re);

  line += l;
  len -= l;
  while (len && char_is_space (line[0]))
    {
      ++line;
      --len;
    }
  while (len && char_is_space (line [len - 1]))
    --len;
  *re = str_save_n (limits, line, len);
  if (!*re)
    return 0;

  return 1;
}

static int
sets_tagging_method (char * kw, enum ftag_method * method_var, enum ftag_method method, t_uchar * line, long len)
{
  int l;

  l = str_length (kw);

  if (len < (l + 1))
    return 0;

  if (str_cmp_prefix (kw, line) || !char_is_space (line[l]))
    return 0;

  *method_var = method;
  return 1;
}

/* tag: Tom Lord Sun Feb 24 18:23:43 2002 (inv.c)
 */
