#include <conf.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef  HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <utils.h>
#include <fileutil.h>
#include <x_regex.h>

#define	CLEANUP	{ goto cleanup; }
#define	GETOUT	{ goto getout; }

/* Function for inserting the output of a typical "save"-function
 * into a possibly existing file into the block marked by two
 * delimiters determined by <blockname> and END_<blockname>.
 *
 * save_block_func is the users save-function, that wants to insert
 * into the file named by <filename>. <block> is the data structure,
 * the user's function wants to save.
 */
Int32	save_insert(
  UChar		*filename,
  UChar		*blockname,
  Int32	(*save_block_func)(FILE *,void *),
  void		*block)
{
  FILE		*fpw;
  UChar		end_mark[100];
  Int32	not_in_file;
  Int32	i, j;
  Int32	own_line, line_ended;
  UChar		*cptr = NULL;
  UChar		**file;
  Int32	num_lines;

  /* if(errno)
    return(errno); *//* no longer critical */

  num_lines = 0;

/* read the existing file into a string array
 */
  file = read_asc_file(filename, &num_lines);

/* if the file doesn't exist, this is no error
 */
  if(file == NULL)
    errno = NO_ERROR;

/* open the (possibly same) file for writing
 */
  fpw = fopen(filename, "w");

  if(fpw == NULL){
    free_asc_file(file, num_lines);
    return(errno = NO_ACCESS_TO_FILE);
  }

  i = -1;
  not_in_file = 1;

  line_ended = 0;

  if(file){
    forever{

     /* search for the beginning block delimiter <blockname>
      */
      i++;
      if(i >= num_lines)
	break;
      cptr = strstr(file[i], blockname);

      if(cptr != NULL){

       /* block marker is found
	*/
	UChar	tmp_buf[BUFSIZ];

       /* handle it, if the marker is not at the beginning of the line
	*/
	if(cptr != file[i]){
	    strncpy(tmp_buf, file[i], (Int32) (cptr - file[i]));
	    fprintf(fpw, "%s", tmp_buf);
	}

	not_in_file = 0;

	break;
      }

      own_line = 0;

     /* if there is some information left in the existing line,
      * put it into a new own line
      */
      for(j = 0; file[i][j] != '\0'; j++)
	if(! isspace(file[i][j]))
	  own_line = 1;

      if(!line_ended && !own_line){
	fprintf(fpw, "\n");
	line_ended = 1;
      }

      if(own_line){
	fprintf(fpw, "%s\n", file[i]);
	line_ended = 0;
      }
    }
  }

/* call the users save-function with his data as parameter,
 * handle user's errors (his function has to return 0, if no
 * error occured)
 */
  if((errno = save_block_func(fpw, block)) != NO_ERROR){
    fclose(fpw);
    free_asc_file(file, num_lines);
    return(errno);
  }

/* append the rest of the previously existing file to the
 * newly generated one
 */
  fprintf(fpw, "\n");

  if(file){
    if(! not_in_file){
      sprintf(end_mark, "END_%s", blockname);

     /* handle the case, that the end-mark is in the same line
      * as the begin-mark
      */
      if(strstr(file[i], end_mark) > (char *) cptr && cptr != NULL){
	cptr = strstr(file[i], end_mark) + strlen(end_mark);
        fprintf(fpw, "%s\n", cptr);
      }

     /* search for the end-mark
      */
      forever{
	i++;
	if(i >= num_lines){
	  /* There is no end-mark -> error
	   */
	    fclose(fpw);
	    free_asc_file(file, num_lines);
	    return(errno = FILE_FORMAT_ERROR);
	}
	if(strstr(file[i], end_mark) != NULL){
	  /* end-mark found. If it's not the last expression in the
	   * line, copy the rest
	   */
	    cptr = strstr(file[i], end_mark) + strlen(end_mark);
	    fprintf(fpw, "%s\n", cptr);
	    break;
	}
      }
    }

   /* copy the rest of the lines into the new file, skip empty lines
    */
    j = 0;
    for(i++; i < num_lines; i++){
	if(! empty_string(file[i])){
	    fprintf(fpw, "%s\n", file[i]);
	    j = 0;
	}
	else{
	    if(! j)
		fprintf(fpw, "\n");
	    j = 1;
	}
    }
  }		/* end of if(file) */

  fclose(fpw);

  return(errno);
}

/**** Function to read parameters out of a file ****/

#define	re_start_buffer	(re_buffers + 0)
#define	re_end_buffer	(re_buffers + 1)

static	RE_cmp_buffer	*re_buffers = NULL;
static	Int32		num_re_buffers = 0;

static	Int32		allocate_re_buffers(Int32);
static	UChar		*read_a_line(FILE *);
static	Int32		get_entry(ParamFileEntry *, UChar *);

Int32
read_param_file(
  UChar			*filename,
  ParamFileEntry	*entries,
  Int32		num_entries,
  UChar			*start_pat,
  UChar			*end_pat)
{
  FILE		*fp = NULL;
  Int32	errc, i, j, ret = NO_ERROR;
  UChar		*line;
  UChar		*flags;

  line = NULL;

  if(num_entries < 1)
    return(num_entries < 0 ? (errno = ILLEGAL_VALUE) : NO_ERROR);

  fp = fopen(filename, "r");

  if(!fp)
    return(errno = NO_SUCH_FILE);

  if( (errc = allocate_re_buffers(num_entries + 2)) )
    return(errc);

  flags = NEWP(UChar, num_entries);
  if(!flags)
    return(errno);

  memset(flags, 0, num_entries * sizeof(UChar));

  for(i = 0; i < num_entries; i++){
    if(re_compile_pattern(entries[i].pattern, strlen(entries[i].pattern),
		re_buffers + i + 2))
	GETOUT;

    if(entries[i].num_entries)
	*(entries[i].num_entries) = 0;
  }

  if(start_pat)
    if(re_compile_pattern(start_pat, strlen(start_pat), re_start_buffer))
	GETOUT;

  if(end_pat)
    if(re_compile_pattern(end_pat, strlen(end_pat), re_end_buffer))
	GETOUT;

  if(start_pat)
    forever{
	if(line){
	  free(line);
	  line = NULL;
	}

	if(!(line = read_a_line(fp)))
	  CLEANUP;

	if(re_find_match(re_start_buffer, line, NULL, NULL) >= 0)
	  break;
    }

  if(line)
    free(line);

  while( (line = read_a_line(fp)) ){
    if(end_pat)
      if(re_find_match(re_end_buffer, line, NULL, NULL) >= 0)
	CLEANUP;

    for(i = 0; i < num_entries; i++){
      if(flags[i])
	continue;

      if(re_find_match(re_buffers + i + 2, line, NULL, &j) >= 0){
	get_entry(entries + i, line + j);
	flags[i] = 1;
      }
    }

    free(line);
  }

 cleanup:
  if(line)
    free(line);
  if(flags)
    free(flags);
  if(fp)
    fclose(fp);

  return(ret);

 getout:
  ret = errno;
  CLEANUP;
}

static Int32
get_entry(
  ParamFileEntry	*entry,
  UChar			*line)
{
  UChar		*cptr;
  Uns32	num_values;
  void		*values = NULL;
  double	indouble;
  float		infloat;
  long int	inlongint;
  unsigned long	inlonguns;
  short int	inshortint;
  unsigned short inshortuns;
  unsigned char	inchar;
  int		j;

  num_values = 0;

  cptr = line;

  if(!entry->entry_ptr)
    return(NO_ERROR);

  if(entry->num_entries)
    *entry->num_entries = 0;

  switch(entry->type){
    case TypeReal64:
	for(; *cptr;){
	  if(sscanf(cptr, "%lf", &indouble) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Real64 *) entry->entry_ptr) = (Real64) indouble;

	    break;
	  }
	}
	break;
    case TypeReal32:
	for(; *cptr;){
	  if(sscanf(cptr, "%f", &infloat) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Real32 *) entry->entry_ptr) = (Real32) infloat;

	    break;
	  }
	}
	break;
    case TypeInt32:
	for(; *cptr;){
	  if(sscanf(cptr, "%ld", &inlongint) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Int32 *) entry->entry_ptr) = (Int32) inlongint;

	    break;
	  }
	}
	break;
    case TypeUns32:
	for(; *cptr;){
	  if(sscanf(cptr, "%lu", &inlonguns) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Uns32 *) entry->entry_ptr) = (Uns32) inlonguns;

	    break;
	  }
	}
	break;
    case TypeInt16:
	for(; *cptr;){
	  if(sscanf(cptr, "%hd", &inshortint) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Int16 *) entry->entry_ptr) = (Int16) inshortint;

	    break;
	  }
	}
	break;
    case TypeUns16:
	for(; *cptr;){
	  if(sscanf(cptr, "%hu", &inshortuns) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((Uns16 *) entry->entry_ptr) = (Uns16) inshortuns;

	    break;
	  }
	}
	break;
    case TypeUChar:
    case TypeSChar:
	for(; *cptr;){
	  if(sscanf(cptr, "%c", &inchar) < 1)
	    cptr++;

	  else{
	    if(entry->num_entries)
		*entry->num_entries = 1;

	    *((UChar *) entry->entry_ptr) = (UChar) inchar;

	    break;
	  }
	}
	break;
    case TypeReal64PTR:
	values = seg_malloc(sizeof(Real64));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%lf", &indouble) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Real64),
					num_values * sizeof(Real64));

	  if(!values)
	    GETOUT;

	  *((Real64 *) values + num_values) = indouble;

	  num_values++;

	  sscanf(cptr, "%*f%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Real64 **) entry->entry_ptr) = values;

	break;

    case TypeReal32PTR:
	values = seg_malloc(sizeof(Real32));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%f", &infloat) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Real32),
					num_values * sizeof(Real32));

	  if(!values)
	    GETOUT;

	  *((Real32 *) values + num_values) = infloat;

	  num_values++;

	  sscanf(cptr, "%*f%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Real32 **) entry->entry_ptr) = values;

	break;

    case TypeInt32PTR:
	values = seg_malloc(sizeof(Int32));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%ld", &inlongint) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Int32),
					num_values * sizeof(Int32));

	  if(!values)
	    GETOUT;

	  *((Int32 *) values + num_values) = inlongint;

	  num_values++;

	  sscanf(cptr, "%*d%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Int32 **) entry->entry_ptr) = values;

	break;

    case TypeUns32PTR:
	values = seg_malloc(sizeof(Uns32));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%lu", &inlonguns) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Uns32),
					num_values * sizeof(Uns32));

	  if(!values)
	    GETOUT;

	  *((Uns32 *) values + num_values) = inlonguns;

	  num_values++;

	  sscanf(cptr, "%*u%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Uns32 **) entry->entry_ptr) = values;

	break;

    case TypeInt16PTR:
	values = seg_malloc(sizeof(Int16));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%hd", &inshortint) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Int16),
					num_values * sizeof(Int16));

	  if(!values)
	    GETOUT;

	  *((Int16 *) values + num_values) = inshortint;

	  num_values++;

	  sscanf(cptr, "%*d%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Int16 **) entry->entry_ptr) = values;

	break;

    case TypeUns16PTR:
	values = seg_malloc(sizeof(Uns16));

	if(!values)
	  GETOUT;

	for(; *cptr;){
	  if(sscanf(cptr, "%hu", &inshortuns) < 1){
	    cptr++;
	    continue;
	  }

	  values = seg_realloc(values, (num_values + 1) * sizeof(Uns16),
					num_values * sizeof(Uns16));

	  if(!values)
	    GETOUT;

	  *((Uns16 *) values + num_values) = inshortuns;

	  num_values++;

	  sscanf(cptr, "%*u%n", &j);

	  cptr += j;
	}

	if(entry->num_entries)
	  *entry->num_entries = num_values;

	*((Uns16 **) entry->entry_ptr) = values;

	break;

    case TypeUCharPTR:
    case TypeSCharPTR:
	*((UChar **) entry->entry_ptr) = strdup(cptr);

	if(! (*((UChar **) entry->entry_ptr)))
	  GETOUT;

	if(entry->num_entries)
	  *entry->num_entries = strlen(cptr);

	break;
  }

  return(NO_ERROR);

 getout:
  if(values)
    free(values);

  return(errno);
}

static UChar *
read_a_line(FILE * fp)
{
  UChar		*line = NULL;
  UChar		*next_line = NULL;
  UChar		*cptr;
  Int32	len, oldlen;

  line = fget_alloc_str(fp);

  if(!line)
    return(NULL);

  oldlen = strlen(line) + 1;

  cptr = strchr(line, '#');
  if(cptr)
    *cptr = '\0';

  len = strlen(line);

  if(line[len - 1] == '\n'){
    len--;
    line[len] = '\0';
  }

  while(line[len - 1] == '\\'){
    line[len - 1] = '\0';

    next_line = fget_alloc_str(fp);

    if(!next_line)
	return(line);

    line = seg_realloc(line, sizeof(UChar) * (len + 1 + strlen(next_line)),
			oldlen);
    oldlen = sizeof(UChar) * (len + 1 + strlen(next_line));

    if(!line)
	GETOUT;

    strcpy(line + len - 1, next_line);

    free(next_line);

    cptr = strchr(line, '#');
    if(cptr)
	*cptr = '\0';

    len = strlen(line);
    if(line[len - 1] == '\n'){
	len--;
	line[len] = '\0';
    }
  }

  return(line);

 getout:

  if(line)
    free(line);

  if(next_line)
    free(next_line);

  return(NULL);
}

static Int32
allocate_re_buffers(Int32 new_num_buffers)
{
  if(new_num_buffers <= num_re_buffers)
    return(0);

  if(! num_re_buffers)
    re_buffers = (RE_cmp_buffer *)
		seg_malloc(sizeof(RE_cmp_buffer) * new_num_buffers);
  else
    re_buffers = (RE_cmp_buffer *) seg_realloc(re_buffers,
			sizeof(RE_cmp_buffer) * new_num_buffers,
			sizeof(RE_cmp_buffer) * num_re_buffers);

  if(!re_buffers){
    num_re_buffers = 0;
    return(errno);
  }

  memset(re_buffers + num_re_buffers, 0,
	sizeof(RE_cmp_buffer) * (new_num_buffers - num_re_buffers));

  num_re_buffers = new_num_buffers;

  return(NO_ERROR);
}

UChar *
find_program(UChar * argv0)
{
  UChar		*largv0 = NULL, *lpath = NULL, *ret = NULL, *cptr, *next;

  if(FN_ISPATH(argv0))
    return((UChar *) strdup(argv0));

  largv0 = (UChar *) strdup(argv0);
  if(!largv0)
    CLEANUP;
  lpath = getenv("PATH");
  if(!lpath)
    CLEANUP;
  lpath = (UChar *) strdup(lpath);
  if(!lpath)
    CLEANUP;
  ret = NEWP(UChar, strlen(argv0) + strlen(lpath) + 2);
  if(!ret)
    CLEANUP;

  cptr = lpath;
  forever{
    next = (UChar *) strchr(cptr, ENV_PATHSEPCHR);
    if(next)
      *next = '\0';
    strcpy(ret, cptr);
    strcat(ret, FN_DIRSEPSTR);
    strcat(ret, argv0);
    if(!access(ret, X_OK))
      CLEANUP;

    if(!next)
	break;

    cptr = next + 1;
  }

  free(ret);
  ret = NULL;

 cleanup:
  if(largv0)
    free(largv0);
  if(lpath)
    free(lpath);

  return(ret);
}

Int32
cleanpath(UChar * path)
{
  UChar		*cptr1, *cptr2, modified;

  if(!path){
    errno = EINVAL;
    return(errno);
  }
  if(!(*path))
    return(0);

  do{
    modified = 0;
    if(FN_TRAILINGDUMMY(path)){
	*(FN_LASTDIRDELIM(path) + 1) = '\0';
	modified = 1;
    }

    cptr1 = path + strlen(path) - 1;
    while(FN_ISDIRSEP(*cptr1) && ! FN_ISROOTDIR(path)){
	*(cptr1--) = '\0';
	modified = 1;
    }
  } while(modified);

  while( (cptr1 = FN_STRDBLDIRSEP(path)) ){
    cptr1++;
    cptr2 = cptr1 + 1;
    while(FN_ISDIRSEP(*cptr2))
	cptr2++;
    while(*cptr2)
      *(cptr1++) = *(cptr2++);

    *cptr1 = '\0';
  }

  while( (cptr1 = FN_STREMPTYDIRSEP(path)) ){
    cptr1++;
    cptr2 = cptr1 + 2;
    while(*cptr2)
      *(cptr1++) = *(cptr2++);

    *cptr1 = '\0';
  }

  if(FN_LEADINGDUMMY(path)){
    cptr1 = path;
    cptr2 = path + 2;
    while(*cptr2)
      *(cptr1++) = *(cptr2++);

    *cptr1 = '\0';
  }

  return(0);
}

Int32
cleanpath__(UChar * path)
{
  UChar		*cptr1, *cptr2, *cptr3, *orgpath;
  UChar		updir[10], dirup[10], rootdummy[10];
  Int32		updirlen, diruplen, rootdummylen;

  cleanpath(path);

  strcpy(updir, FN_PARENTDIR FN_DIRSEPSTR);
  updirlen = strlen(updir);
  strcpy(dirup, FN_DIRSEPSTR FN_PARENTDIR);
  diruplen = strlen(dirup);
  strcpy(rootdummy, FN_DIRSEPSTR FN_PARENTDIR);
  rootdummylen = strlen(rootdummy);

  orgpath = path;
  forever{
    while(!strncmp(path, updir, updirlen))
      path += updirlen;

    while(!strncmp(path, rootdummy, rootdummylen)
		&& (!path[rootdummylen] || FN_ISDIRSEP(path[rootdummylen]))){
      cptr1 = path;
      cptr2 = cptr1 + rootdummylen;
      if(FN_ISDIRSEP(*cptr2)){
	cptr1++;
	cptr2++;
      }

      while(*cptr2)
	*(cptr1++) = *(cptr2++);
      *cptr1 = '\0';

      if(!*path){
	path[0] = FN_DIRSEPCHR;
	path[1] = '\0';
	GETOUT;
      }
    }

    if(FN_ISDIRSEP(*path))
	path++;

    cptr1 = FN_FIRSTDIRSEP(path);
    if(!cptr1)
	GETOUT;

    cptr2 = cptr1 + 1 - updirlen;
    cptr3 = cptr1 + diruplen;
    if(!strncmp(cptr1, dirup, diruplen)
			&& (!(*cptr3) || FN_ISDIRSEP(*cptr3))
			&& strncmp(cptr2, updir, updirlen)){
	cptr2 = cptr1 + diruplen;
	if(FN_ISDIRSEP(*cptr2))
	  cptr2++;

	cptr1 = path;
	while(*cptr2)
	  *(cptr1++) = *(cptr2++);
	*cptr1 = '\0';

	path = orgpath;
    }
    else{
	path = cptr1 + 1;
    }
  }

 getout:
  cleanpath(orgpath);
  return(0);
}

UChar *
mkabspath(UChar * path, UChar * retpath)
{
  UChar	*curpath;

  if(FN_ISABSPATH(path)){
    if(retpath){
	strcpy(retpath, path);
	return(retpath);
    }
    else{
	return(strdup(path));
    }
  }

  if(FN_LEADINGDUMMY(path))
    path += 2;

  curpath = getcwd(NULL, MAXPATHLEN);
  if(!curpath)
    return(NULL);

  if(retpath){
    sprintf(retpath, "%s" FN_DIRSEPSTR "%s", curpath, path);
  }
  else{
    retpath = strchain(curpath, FN_DIRSEPSTR, path, NULL);
  }
  free(curpath);

  return(retpath);
}

/* Function to resolve symbolic links from path, producing a new path
 * not containing any symbolic link any more. New path is stored in
 * retpath, if not NULL, otherwise malloced. Pointer to it returned.
 */
UChar *
resolvepath(UChar * path, UChar * retpath)
{
  UChar		*newpath, *cptr, *oldpath, *dirdelim;
  UChar		linkpath[MAXPATHLEN + 1], c;
  Int32		i;
  struct stat	statb;

  path = strdup(path);
  if(!path)
    return(NULL);

 restart:
  cleanpath(path);
  oldpath = path;
  if(FN_ISABSPATH(oldpath))
    oldpath = FN_FIRSTDIRSEP(oldpath) + 1;

  forever{
    while(FN_ISDIRSEP(*oldpath))
	oldpath++;

    if(!oldpath[0])
	break;

    dirdelim = FN_FIRSTDIRSEP(oldpath);
    if(dirdelim){
	c = *dirdelim;
	*dirdelim = '\0';
    }

    if(lstat(path, &statb))
	CLEANUP;

    if(S_ISLNK(statb.st_mode)){
	i = readlink(path, linkpath, MAXPATHLEN);
	if(i < 0)
	  CLEANUP;
	linkpath[i] = '\0';
	cptr = dirdelim ? dirdelim + 1 : (UChar *) "";

	if(FN_ISABSPATH(linkpath)){
	  newpath = strchain(linkpath, FN_DIRSEPSTR, cptr, NULL);
	}
	else{
	  *oldpath = '\0';
	  newpath = strchain(path, path[0] ? FN_DIRSEPSTR : "",
				linkpath, FN_DIRSEPSTR, cptr, NULL);
	}
	if(!newpath)
	  CLEANUP;

	free(path);
	path = newpath;
	newpath = NULL;

	goto restart;
    }

    if(!dirdelim)
	break;

    *dirdelim = c;
    oldpath = dirdelim + 1;
  }

  if(retpath){
    strcpy(retpath, path);
    return(retpath);
  }
  else
    return(path);

 cleanup:
  ZFREE(path);
  return(NULL);
}

/* Same as resolvepath, but furthermore removing ../ parts from
 * path, thus giving the true position of the given file in the
 * filesystem
 */
UChar *
resolvepath__(UChar * path, UChar * retpath)
{
  UChar		*newpath;

  newpath = resolvepath(path, retpath);
  if(!newpath)
    return(NULL);

  cleanpath__(newpath);

  return(newpath);
}

static Int32
real_find1(
  UChar		*filename,
  FindParams	*params,
  Int32	(*func)(UChar *, void *),
  void		*funcarg,
  dev_t		device,
  UChar		have_device)
{
  FindParams	lparams;
  DIR		*dir = NULL;
  Int32	ret, i;
  UChar		**files, **excl_files, **excl_dirs;
  UChar		**local_excl_files = NULL;
  Uns32	num_local_excl_files = 0;
  UChar		*excl_filename;
  Uns32	options;
  UChar		*cptr, **names;
  UChar		*buf = NULL;
  struct stat	statb;
  struct dirent	*entry;
  UChar		processit;
  UChar		*bname;

  if(!filename || !func)
    return(EINVAL);

  ret = 0;

  filename = strdup(filename);
  if(!filename)
    return(errno);

  cleanpath(filename);

  if(!params){
    memset(&lparams, 0, sizeof(lparams));
    params = &lparams;
  }
  excl_dirs = params->excl_dirs;
  excl_files = params->excl_names;
  files = params->names;
  options = params->options;

  if(options & FIND_FOLLOW_SLINKS){
    if(stat(filename, &statb)){
      if(params->errfunc){
	ret = (*params->errfunc)(filename, params->errfunc_param);
      }
      CLEANUP;
    }
  }
  else{
    if(lstat(filename, &statb)){
      if(params->errfunc){
	ret = (*params->errfunc)(filename, params->errfunc_param);
      }
      CLEANUP;
    }
  }

  if(S_ISLNK(statb.st_mode) && (options & FIND_NO_SLINKS)){
      ret = 0;
      CLEANUP;
  }
  if(S_ISFIFO(statb.st_mode) && (options & FIND_NO_FIFOS)){
      ret = 0;
      CLEANUP;
  }
  if(S_ISREG(statb.st_mode) && (options & FIND_NO_FILES)){
      ret = 0;
      CLEANUP;
  }
  if(S_ISBLK(statb.st_mode) && (options & FIND_NO_BDEVS)){
      ret = 0;
      CLEANUP;
  }
  if(S_ISCHR(statb.st_mode) && (options & FIND_NO_CDEVS)){
      ret = 0;
      CLEANUP;
  }
  if(S_ISSOCK(statb.st_mode) && (options & FIND_NO_SOCKS)){
      ret = 0;
      CLEANUP;
  }
  if((options & FIND_LOCAL_DEV) && have_device && device != statb.st_dev){
      ret = 0;
      CLEANUP;
  }

  processit = 1;

  if(params->newer_than)
    if(statb.st_mtime < params->newer_than)
      processit = 0;
  if(params->older_than)
    if(statb.st_mtime > params->older_than)
      processit = 0;

  bname = FN_BASENAME(filename);

  if(files && processit){
    processit = 0;

    for(names = files; *names; names++){
      if(FN_ISPATH(*names)){
	if(!fnmatch(*names, filename, 0))
	  break;
      }
      else{
	if(!fnmatch(*names, bname, FNM_PATHNAME))
          break;
      }
    }

    if(*names)
      processit = 1;
  }

  if(excl_files && processit){
    for(names = excl_files; *names; names++){
      if(FN_ISPATH(*names)){
	if(!fnmatch(*names, filename, 0))
	  break;
      }
      else{
	if(!fnmatch(*names, bname, FNM_PATHNAME))
	  break;
      }
    }

    if(*names)
      processit = 0;
  }

  if(processit){
    if( (ret = (*func)(filename, funcarg)) )
	CLEANUP;
  }

  if(! S_ISDIR(statb.st_mode)){
    ret = 0;
    CLEANUP;
  }

  if(excl_dirs){
    for(names = excl_dirs; *names; names++){
      if(FN_ISPATH(*names)){
	if(!fnmatch(*names, filename, 0))
	  break;
      }
      else{
	if(!fnmatch(*names, bname, FNM_PATHNAME))
	  break;
      }
    }

    if(*names){
	ret = 0;
	CLEANUP;
    }
  }

  if((options & FIND_LOCAL_DEV) && !have_device){
    have_device = 1;
    device = statb.st_dev;
  }

  dir = opendir(filename);
  if(!dir){
    if(params->errfunc){
	ret = (*params->errfunc)(filename, params->errfunc_param);
    }
    CLEANUP;
  }

  buf = NEWP(UChar, 1);
  if(!buf){
    ret = ENOMEM;
    CLEANUP;
  }

  if(params->excl_filename){
    excl_filename = strchain(filename, FN_DIRSEPSTR,
					 params->excl_filename, NULL);
    if(!excl_filename){
	ret = ENOMEM;
	CLEANUP;
    }

    if(!access(excl_filename, R_OK)){
	FILE	*fp;

	fp = fopen(excl_filename, "r");

	while(!feof(fp)){
	  cptr = fget_alloc_str(fp);
	  if(!cptr)
	    break;

	  massage_string(cptr);

	  if(num_local_excl_files)
	    local_excl_files = RENEWP(local_excl_files, UChar *, num_local_excl_files + 1);
	  else
	    local_excl_files = NEWP(UChar *, 1);

	  local_excl_files[num_local_excl_files++] = cptr;
	}

	fclose(fp);
    }

    free(excl_filename);
  }
    
  forever{
    entry = readdir(dir);
    if(! entry)
      break;

    cptr = (UChar *) &(entry->d_name[0]);

    if(!strcmp(cptr, ".") || !strcmp(cptr, ".."))
      continue;

    if(local_excl_files){
      for(i = 0; i < num_local_excl_files; i++){
	if(!fnmatch(local_excl_files[i], cptr, 0))
	  break;
      }

      if(i < num_local_excl_files)
	continue;
    }

    buf = RENEWP(buf, UChar, strlen(filename) + strlen(cptr) + 2);
    if(!buf){
      ret = ENOMEM;
      CLEANUP;
    }

    sprintf(buf, "%s%c%s", filename, FN_DIRSEPCHR, cptr);

    if( (ret = real_find1(buf, params, func, funcarg, device, have_device)) ){
      CLEANUP;
    }
  }

 cleanup:
  if(buf)
    free(buf);
  if(dir)
    closedir(dir);
  if(filename)
    free(filename);
  if(local_excl_files){
    for(i = 0; i > num_local_excl_files; i++)
	free(local_excl_files[i]);
    free(local_excl_files);
  }
  return(ret);
}

Int32
find1(
  UChar		*filename,
  FindParams	*params,
  Int32	(*func)(UChar *, void *),
  void		*funcarg)
{
  return(real_find1(filename, params, func, funcarg, (dev_t) 0, 0));
}

UChar **
fnglob1(UChar * namepat)
{
  UChar		**matches = NULL, **m, *cptr;
  UChar		*buf = NULL;
  DIR		*dir = NULL;
  UChar		*dirname = NULL;
  struct dirent	*entry;
  Int32	num_matches;

  if(!namepat){
    errno = EINVAL;
    return(NULL);
  }

  namepat = strdup(namepat);
  if(!namepat)
    return(NULL);

  cleanpath(namepat);

  if(FN_ISROOTDIR(namepat)){
    matches = NEWP(UChar *, 2);
    if(!matches)
	return(NULL);
    matches[0] = strdup(namepat);
    if(!matches[0])
	GETOUT;
    matches[1] = NULL;
    return(matches);
  }

  if(FN_ISPATH(namepat)){
    dirname = strdup(namepat);
    if(!dirname)
      return(NULL);
    cptr = FN_LASTDIRDELIM(dirname);
    if(cptr){
      if(cptr > dirname)
	*cptr = '\0';
      else
	dirname[1] = '\0';
    }
    dir = opendir(dirname);
  }
  else{
    dir = opendir(".");
  }

  if( (matches = NEWP(UChar *, 1)) )
    *matches = NULL;

  if(!dir)
    CLEANUP;

  buf = strdup("");
  if(!buf || !matches)
    GETOUT;

  num_matches = 0;

  forever{
    entry = readdir(dir);
    if(! entry)
      break;

    cptr = (UChar *) &(entry->d_name[0]);

    if((!strcmp(cptr, ".") || !strcmp(cptr, ".."))
				&& strcmp(cptr, FN_BASENAME(namepat)))
      continue;

    buf = RENEWP(buf, UChar,
			(dirname ? strlen(dirname) : 0) + strlen(cptr) + 2);
    if(!buf)
	GETOUT;

    if(dirname){
      sprintf(buf, "%s%c%s", dirname, FN_DIRSEPCHR, cptr);
    }
    else{
      strcpy(buf, cptr);
    }

    cleanpath(buf);

    if(!fnmatch(namepat, buf, FNM_PATHNAME)){
	matches = RENEWP(matches, UChar *, num_matches + 2);
	if(!matches)
	  GETOUT;
	matches[num_matches] = strdup(buf);
	if(!matches[num_matches])
	  GETOUT;
	num_matches++;
	matches[num_matches] = NULL;
    }
  }

 cleanup:
  if(buf)
    free(buf);
  if(dirname)
    free(dirname);
  if(namepat)
    free(namepat);
  if(dir)
    closedir(dir);

  return(matches);

 getout:
  if(matches){
    for(m = matches; *m; m++)
      free(*m);
    free(matches);
  }
  matches = NULL;

  CLEANUP;
}

UChar **
fnglob(UChar * namepat)
{
  UChar		**uppermatches = NULL, **matches = NULL, **m;
  UChar		**newmatches = NULL, **mi;
  UChar		*buf = NULL, *bname = NULL, *cptr;
  Int32	i, j;

  if(FN_ISROOTDIR(namepat) || ! FN_ISPATH(namepat))
    return(fnglob1(namepat));

  buf = strdup("");
  namepat = strdup(namepat);
  if(!namepat || !buf)
    GETOUT;

  cleanpath(namepat);
  bname = strdup(FN_BASENAME(namepat));
  if(!bname)
    GETOUT;

  cptr = FN_LASTDIRDELIM(namepat);
  if(cptr){
    if(cptr > namepat)
	*cptr = '\0';
    else
	namepat[1] = '\0';
  }

  uppermatches = fnglob(namepat);
  if(!uppermatches)
    GETOUT;

  matches = NEWP(UChar *, 1);
  if(!matches)
    GETOUT;
  *matches = NULL;

  for(m = uppermatches; *m; m++){
    buf = RENEWP(buf, UChar, strlen(*m) + strlen(bname) + 2);
    if(!buf)
      GETOUT;

    sprintf(buf, "%s%c%s", *m, FN_DIRSEPCHR, bname);
    newmatches = fnglob1(buf);
    if(!newmatches)
      GETOUT;

    for(mi = matches, i = 0; *mi; mi++, i++);
    for(mi = newmatches, j = 0; *mi; mi++, j++);

    matches = RENEWP(matches, UChar *, i + j + 1);
    if(!matches)
      GETOUT;

    memcpy(matches + i, newmatches, j * sizeof(UChar *));
    matches[i + j] = NULL;
    free(newmatches);
    newmatches = NULL;
  }

 cleanup:
  if(uppermatches){
    for(m = uppermatches; *m; m++)
      free(*m);
    free(uppermatches);
  }
  if(buf)
    free(buf);
  if(bname)
    free(bname);
  if(namepat)
    free(namepat);

  return(matches);

 getout:
  if(matches){
    for(m = matches; *m; m++)
      free(*m);
    free(matches);
  }
  if(newmatches){
    for(m = newmatches; *m; m++)
      free(*m);
    free(newmatches);
  }
  matches = NULL;

  CLEANUP;
}

Int32
find(
  UChar		**filenames,
  FindParams	*params,
  Int32	(*func)(UChar *, void *),
  void		*funcarg)
{
  UChar		**matches = NULL, **m, **fn;
  Int32	ret = 0;

  if(!filenames){
    return(errno = EINVAL);
  }

  for(fn = filenames; *fn; fn++){
    matches = fnglob(*fn);
    if(!matches){
	ret = errno;
	CLEANUP;
    }

    for(m = matches; *m; m++){
      if( (ret = find1(*m, params, func, funcarg)) )
	CLEANUP;
    }

    for(m = matches; *m; m++)
	free(*m);
    free(matches);
    matches = NULL;
  }

 cleanup:
  if(matches){
    for(m = matches; *m; m++)
      free(*m);
    free(matches);
  }

  return(ret);
}

Int32
copy_file(UChar * srcfile, UChar * destfile)
{
  int		infd, outfd, i;
  UChar		buf[BUFSIZ];
  struct stat	statb;
  Int32	ret = 0;

  if(stat(srcfile, &statb))
    return(errno);

  infd = open(srcfile, O_RDONLY);
  if(infd < 0)
    return(errno);
  outfd = open(destfile, O_WRONLY | O_BINARY | O_CREAT, 0600);
  if(outfd < 0){
    close(infd);
    return(errno);
  }

  while((i = read(infd, buf, BUFSIZ)) > 0){
    if(write(outfd, buf, i) < i){
	ret = errno;
	break;
    }
  }

  close(outfd);
  close(infd);

  if(chmod(destfile, statb.st_mode)){
    if(!ret)
	ret = errno;
  }

  return(ret);
}

/************ end of $RCSfile$ ******************/
