/*$Id: spf_util.c,v 1.34 2000/05/27 13:47:43 jens Exp $*/
/*
 * Copyright (c) 1997, 1998, 1999
 *      Jens A. Nilsson, jnilsson@ludd.luth.se. All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

#ifdef MEMDEBUG
#include <memdebug.h>
#endif

#if defined(SunOS)
#define NO_FNMATCH
#endif

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <limits.h>
#include <unistd.h>

#ifdef NO_STRLCPY
#include "strlcpy.h"
#endif

#include "e_err.h"
#include "spegla.h"
#include "spf_util.h"
#include "parserow.h"

#ifdef NOPROTOS
int snprintf(char *str, size_t size, const char *format, ...);
char *strdup(char *);
int utimes(const char *path, const struct timeval *times);
int lstat(const char *path, struct stat *sb);
int symlink(const char *name1, const char *name2);
int readlink(const char *path, char *buf, size_t bufsiz);
#endif

char cur_dir[PATH_MAX] = ".";
static char cur_file[PATH_MAX] = "";

extern int sp_do_delete;
extern int sp_max_delete;
extern int sp_deletes;
extern int sp_dirs_traversed;
extern int sp_errors;
extern unsigned int sp_retrytime;


/* the data structure containing all files to skip */
struct cl_sps_que *skip;

/* the data structure containing all links to treat as directories */
struct cl_spt_que *treatasdir;

/*
 * have_dotdot_ref()
 *
 * Used by cur_dir_cd() to determine if f might result in a change
 * to directory above current directory.
 * Return 1 if the result might result in a change to a direcotry
 * above current directory.
 */
static int
have_dotdot_ref(char *f)
{
	if (strstr(f, "/../") != NULL)
		return 1;
	return 0;
}

/*
 * cur_dir_cd()
 *
 * Changes to a directory under cur_dir. Changes with a path containing
 * ".." is not allowed.
 * Returns the new current directory.
 */
char *
cur_dir_cd(char *str)
{
	size_t	len;

	/* we don't want to go over the dir we start from */
	if (have_dotdot_ref(str))	
		return NULL;

	if ((strlen(str) + strlen(cur_dir) + 1) > (PATH_MAX - 2))
		return NULL;				/* no overflow */
	len = strlen(cur_dir);
	if (len > 0 && cur_dir[len - 1] != '/')
		(void) strcat(cur_dir, "/");
	(void) strcat(cur_dir, str);
	(void) strcat(cur_dir, "/");
	return cur_dir;
}

/*
 * cur_dir_cdup()
 *
 * Changes to the directory above current directory in cur_dir.
 * Doesn't go above start directory. Tries to keep a '/' as last
 * characher in  cur_dir.
 */
char *
cur_dir_cdup(void)
{
	size_t	len;
	int		i;

	len = strlen(cur_dir);

	/* if cur_dir is empty or only containing a single '.' just return */
	if (len == 0 || (len == 1 && cur_dir[0] == '.'))
		return cur_dir;

	/* if cur_dir ends with a '/' strip it off */
	if (cur_dir[len - 1] == '/') {
		cur_dir[len - 1] = '\0';
		len--;
	}

	/* find last '/' in cur_dir */
	for (i = (int)len; (i > 0) && (cur_dir[i] != '/'); i--);

	/* terminate the string just behind the '/' */
	cur_dir[i + 1] = '\0';

	return cur_dir;
}

/*
 * full_name()
 *
 * Returns the path name of f relative to start dir.
 * A buffer is needed to hold the returned path in.
 * Used by spf_full_name() and spf_full_name2().
 */
static char *
full_name(char *f, char *buf, size_t len)
{
	/* we don't want to go over the dir we start from */
	if (have_dotdot_ref(f))
		return NULL;

	/* no overflow */
	if ((strlen(f) + strlen(cur_dir)) > (len - 2))
		return NULL;

	(void) sprintf(buf, "%s%s", cur_dir, f);
	return buf;
}

/*
 * spf_full_name()
 *
 * Returns the path name of spf relative to start dir.
 * cur_file is used as buffer to return.
 */
char *
spf_full_name(struct sp_file *spf)
{
	return spf_full_name2(spf, cur_file, (size_t)PATH_MAX);
}

/*
 * spf_full_name2()
 *
 * Returns the path name of spf relative to start dir.
 * A buffer is needed to hold the returned path in.
 */
char *
spf_full_name2(struct sp_file *spf, char *buf, size_t len)
{
	return full_name(spf->spf_name, buf, len);
}

/*
 * spf_use_tmp_name()
 *
 * Generate a unique file name and replace spf_name with it.
 *
 * A call to spf_restore_name() later will rename spf_name file
 * the original file name and free the allocated buffer for the
 * temporary filename.
 */
char *
spf_use_tmp_name(struct sp_file *spf)
{
	char	*str;
	char	buf[PATH_MAX], buf2[PATH_MAX];
	int		fd;

	if ((str = malloc(PATH_MAX)) == NULL)
		return NULL;
	snprintf(buf, PATH_MAX, ".in.%s.XXX", spf->spf_name);
	if (full_name(buf, buf2, PATH_MAX) == NULL)
		return NULL;
	if ((fd = mkstemp(buf2)) < 0)
		return NULL;
	close(fd);		/* can't pass the descriptor in any convenient way */
	if ((str = strdup(rindex(buf2, '/') + 1)) == NULL)
		return NULL;
	spf->spf_orig_name = spf->spf_name;
	spf->spf_name = str;
	return str;
}

/*
 * spf_restore()
 *
 * Restore the name of the downloaded file. Free the allocated buffer for the
 * temporary filename.
 */
int
spf_restore_name(struct sp_file *spf)
{
	struct	stat sb;
	char	*f, *o, buf[PATH_MAX];
	int		ret_val;

	if (spf->spf_orig_name == NULL)
		return 0;

	ret_val = 0;
	f = spf_full_name(spf);
	if (lstat(f, &sb) < 0 && errno == ENOENT) {
		/* the temp file does not exist anylonger,
		 * give back the original name
		 */
		goto ret;
	}

	o = full_name(spf->spf_orig_name, buf, sizeof(buf));
	if (rename(f, o) < 0) {
		e_warn("failed to rename %s to %s", o, f);
		ret_val = -1;
	}

ret:
	free(spf->spf_name);
	spf->spf_name = spf->spf_orig_name;
	spf->spf_orig_name = NULL;
	return ret_val;
}


/*
 * symlink_cd()
 * 
 * Used by spf_symlink_resolve() to resolve what a symlink points to
 */
static int
symlink_cd(char *dir, int dir_len, char *buf, size_t buf_siz)
{
	size_t	len;

	len = strlen(buf);
	if ((buf_siz - len - 1)<= dir_len)
		return -1;

	switch (dir_len) {
	case 0:
		return 0;
	case 1:
		if (*dir == '.')
			return 0;
		break;
	case 2:
		if (strncmp(dir, "..", (size_t)2) == 0)
			goto cd_up;
		break;
	default:
		break;
	}
	(void)memcpy(buf + len, dir, (size_t) dir_len);
	len += dir_len;
	if (buf[len - 1] != '/') {
		buf[len++] = '/';
	}
	buf[len] = '\0';
	
	return 0;

cd_up:
	/* change ./../ to ../ to keep consistent */
	if (strcmp(buf, "./../") == 0) {
		/* don't forget the NULL terminator */
		(void)memmove(buf, buf + 2, len - 1); 
		len -= 2;
	}

	/* first entry goes up */
	if (strncmp(buf, "../", (size_t)3) == 0) {
		(void) strcat(buf, "../");
		return 0;
	}

	/* path is empty */
	if (len == 0) {
		(void) strcpy(buf, "../");
		return 0;
	}

	/* up one dir */
	for (len--; buf[len] != '/' && len != 0; len--);
	if (buf[len] == '/') {
		/* up at root doesn't change anything */
		if (len == 0)
			return 0;
		buf[len + 1] = '\0';
		return 0;
	}

	/* empty path */
	strcpy(buf, "./");
	return 0;
}

/*
 * spf_symlink_resolve()
 *
 * Resolves what path a symlink points to. Returned path is the
 * shortest path regarding .. and so.
 */

char *
spf_symlink_resolve(struct sp_file *spf, char *buf, size_t len)
{
	char	*p;
	size_t	plen;
	int		i, j;

	p = spf->spf_symlink;
	if (p == NULL)
		return NULL;

	buf[0] = '\0';

	/* The symlink is relative */
	if (*p != '/') {
		/* buffer overrun check */
		if (len <= strlen(cur_dir))
			return NULL;

		strcat(buf, "./");
		if (strncmp(cur_dir, "./", (size_t)2) == 0)
			(void) strcat(buf, cur_dir + 2);
		else
			(void) strcat(buf, cur_dir);
	}

	plen = strlen(p);
	i = j = 0;
	while (i <= plen) {
		if (p[i] == '/' || p[i] == '\0') {
			if (symlink_cd(p + j, i - j, buf, len) < 0)
				return NULL;
			j = i + 1;
			i++;
		}
		i++;
	}
	/* remove trailing '/' */
	plen = strlen(buf);
	if (plen > 0 && buf[plen - 1] == '/')
		buf[plen - 1] = '\0';
	return buf;
}

/*
 * spf_is_a_skip_find()
 *
 * Used by spf_is_a_skip() to determine whether a file should be skipped.
 * Returns 1 the file is to be skipped.
 */
static int
spf_is_a_skip_find(struct sp_skip *sps, struct sp_file *spf)
{
	char	buf[PATH_MAX];
	char	*p;
	int		match;

	p = spf_full_name2(spf, buf, sizeof(buf));
	p++; /* remove the . in ./ */
	match = sps_match(sps, p);
	if (match || spf->spf_type != SYMLINK)
		return match;

	/* sps_type == SYMLINK */
	p = spf_symlink_resolve(spf, buf, sizeof(buf));
	match = sps_match(sps, p);
	return match;
}

/*
 * spf_is_a_skip()
 *
 * Returns 1 if spf is a file to skip.
 */
int
spf_is_a_skip(struct sp_file *spf)
{
	char	buf[PATH_MAX];
	char	*p;
	cl_sps_find_f	f_fun;

	if (skip == NULL || skip == CL_NEXT(skip))
		return 0;

	/* LINTED don't obfuscate the cl_sps_find() more that necessary */
	f_fun = (cl_sps_find_f) spf_is_a_skip_find;

	if (cl_sps_find(skip, NULL, f_fun, (struct sp_skip *)spf) < 0)
		return 0;

	p = spf_full_name2(spf, buf, sizeof(buf));
	e_warnx("skipping %s file '%s'",
		spf->spf_opt & SPFO_LOCAL ? "local" : "remote", p);
	return 1;
}

/*
 * spf_is_a_treatasdir_find()
 *
 * Used by spf_is_a_skip() to determine whether a link should be treated
 * as a directory.
 * Returns 1 the file is to be treated as a directory.
 */
static int
spf_is_a_treatasdir_find(struct sp_treatasdir *spt, struct sp_file *spf)
{
	char	buf[PATH_MAX];
	char	*p;
	int		match;

	if (spf->spf_type != SYMLINK)
		return 0;

	if (!spt->spt_deref) {
		p = spf_full_name2(spf, buf, sizeof(buf));
		p++; /* remove the . in ./ */
	} else {
#if 1
		p = spf_symlink_resolve(spf, buf, sizeof(buf));
		p++; /* remove the . in ./ */
#else
		if (spf->spf_symlink[0] != '/' &&
			strncmp(spf->spf_symlink, "..", (size_t)2) != 0) {
			(void) snprintf(buf, (size_t)PATH_MAX, "./%s", spf->spf_symlink);
			p = buf;
		} else
			p = spf->spf_symlink;
#endif
	}

	match = spt_match(spt, p);
#if 0
	e_warnx("comparing '%s' with regexp: %s", p, match ? "match" : "not match");
#endif
	return match;
}


/*
 * spf_is_a_treatasdir()
 *
 * Returns 1 if spf is a file to treat as directory.
 */
int
spf_is_a_treatasdir(struct sp_file *spf)
{
	char	buf[PATH_MAX], buf2[PATH_MAX];
	char	*p, *p2;
	cl_spt_find_f	f_fun;

	if (treatasdir == NULL || treatasdir == CL_NEXT(treatasdir))
		return 0;

	/* LINTED don't obfuscate the cl_sps_find() more that necessary */
	f_fun = (cl_spt_find_f) spf_is_a_treatasdir_find;

	if (cl_spt_find(treatasdir, NULL, f_fun, (struct sp_treatasdir *)spf) < 0)
		return 0;

	p = spf_full_name2(spf, buf, sizeof(buf));
	p2 = spf_symlink_resolve(spf, buf2, sizeof(buf2));
	e_warnx("treating %s link '%s -> %s' pointing at '%s' as directory '%s'",
		spf->spf_opt & SPFO_LOCAL ? "local" : "remote",
		p, spf->spf_symlink, p2, p);
	return 1;
}

/*
 * spf_time_cmp()
 *
 * Compares the time of two sp_file's.
 * Returns the result in the same style as strcmp()
 */
int
spf_time_cmp(struct sp_file *f1, struct sp_file *f2)
{
	if ((f1->spf_time - f1->spf_tgranul) <= (f2->spf_time + f2->spf_tgranul) &&
		(f1->spf_time + f1->spf_tgranul) >= (f2->spf_time - f2->spf_tgranul))
		return 0;
	if (f1->spf_time > f2->spf_time)
		return 1;
	return -1;
}

/*
 * spf_name_cmp()
 *
 * Compares the name of two sp_file's.
 * Returns the result in the same style as strcmp()
 */
int
spf_name_cmp(struct sp_file *f1, struct sp_file *f2)
{
	if (f1 == NULL && f2 == NULL)
		return 0;
	if (f1 == NULL) {
		if (*f2->spf_name != '\0')
			return -*f2->spf_name;
		return -1;
	}
	if (f2 == NULL) {
		if (*f2->spf_name != '\0')
			return *f1->spf_name;
		return 1;
	}
	return strcmp(f1->spf_name, f2->spf_name);
}

/* 
 * spf_push_comp()
 *
 * Used as argument to heap_spf_init() to maintain heap.
 */
int
spf_push_comp(struct sp_file * f1, struct sp_file * f2)
{
	return -spf_name_cmp(f1, f2);
}

/*
 * spf_unalloc()
 *
 * Frees a sp_file.
 */
void
spf_unalloc(struct sp_file *f)
{
	if (f != NULL) {
		if (f->spf_name != NULL)
			free(f->spf_name);
		if (f->spf_orig_name != NULL)
			free(f->spf_orig_name);
		if (f->spf_symlink != NULL)
			free(f->spf_symlink);
		free(f);
	}
}

/*
 * spf_new_time()
 *
 * Updates the time of the file in the filesystem according to
 * spf->spf_time.
 */
int
spf_new_time(struct sp_file *spf)
{
	char	*f;
	struct	timeval ftime[2];

	f = spf_full_name(spf);
	ftime[0].tv_sec = spf->spf_time;
	ftime[1].tv_sec = spf->spf_time;
	ftime[0].tv_usec = ftime[1].tv_usec = 0;
	if (utimes(f, &ftime[0]) < 0) {
		e_warn("failed to set time on %s", f);
		return -1;
	}
	return 0;
}

/* #ifdef DEBUG */

#if 0
void
spf_show_que(struct cl_spf_que *q)
{
	struct	cl_spf_que *p;
	struct	sp_file *spf;

	for (p = CL_NEXT(q); p != q; p = CL_NEXT(p)) {
		spf = CL_ELM(p);
		(void) printf("name = %s, time = %lu, mode = %o, "
			"type = %d, size = %lu\n",
			spf->spf_name, spf->spf_time, spf->spf_mode,
			spf->spf_type, (unsigned long) spf->spf_size);
	}
}
#endif

/*
 * is_dir()
 *
 * Used by rm_f() and rm_rf()
 * Returns 0 if f isn't a directory.
 */
static int
is_dir(char *f)
{
	struct stat sb;

	if (lstat(f, &sb) < 0)
		return 0;
	return S_ISDIR(sb.st_mode);
}

/*
 * rm_f()
 *
 * Used by rm_rf() and spf_rm()
 * Removes _one_ file or diretory. Will exit if the maximum number
 * of deletes is exceeded.
 */
static int
rm_f(char *f)
{
	int	ret;

	if (sp_max_delete != 0 && sp_max_delete <= sp_deletes) 
		e_errx(1, "Maximum number of deletes exceeded, quit");
	ret = is_dir(f) ? rmdir(f) : unlink(f);
	if (ret == 0)
		sp_deletes++;
	else
		e_warn("rm_f: failed to remove %s", f);
	return ret;
}

/*
 * rm_rf()
 *
 * Used by spf_rm()
 * Removes a hierarchy of diretories. Will exit if the maximum number
 * of deletes is exceeded.
 */
static int
rm_rf(char *f)
{
	DIR		*dirp;
	struct	dirent *dp;
	char	file_buf[PATH_MAX];

	/* XXX this shouldn't be needed */
	if (have_dotdot_ref(f)) {
		e_warnx("rm_rf: have .. reference in path: %s", f);
		return -1;
	}

	if (!is_dir(f))
		return rm_f(f);

	if (sp_max_delete != 0 && sp_max_delete <= sp_deletes) 
		e_errx(1, "Maximum number of deletes exceeded, quit");

	if ((dirp = opendir(f)) == NULL) 
		return -1;

	while ((dp = readdir(dirp)) != NULL) {
		if (strcmp("..", dp->d_name) == 0 || strcmp(".", dp->d_name) == 0)
			continue;
		(void) snprintf(file_buf, (size_t)PATH_MAX, "%s/%s", f, dp->d_name);
		file_buf[PATH_MAX - 1] = '\0';
		if (is_dir(file_buf)) {
			(void) rm_rf(file_buf);
			continue;
		}
		if (sp_max_delete != 0 && sp_max_delete <= sp_deletes) {
			(void) closedir(dirp);
			e_errx(1, "Maximum number of deletes exceeded, quit");
		}
		(void) rm_f(file_buf);
	}
	(void) closedir(dirp);
	return rm_f(f); 
}

/*
 * spf_rm()
 *
 * Removes a hierarchy of diretories if sp_do_delete is set. Will exit
 * if the maximum number of deletes is exceeded.
 */
int
spf_rm(struct sp_file *spf)
{
	char	*f;

	/* only delete if we are supposed to */
	if (sp_do_delete == 0)
		return 0;

	f = spf_full_name(spf);
	if (spf->spf_type == DIRECTORY) {
		e_warnx("rm -rf %s", f);
		if (rm_rf(f) < 0) {
			e_warn("failed to remove %s", f);
			return -1;
		}
		return 0;
	} 
	e_warnx("rm -f %s", f);
	if (rm_f(f) < 0) {
		e_warn("failed to remove %s", f);
		return -1;
	}
	return 0;
}

/*
 * spf_clone()
 *
 * Returns a copy of a sp_file
 */
struct sp_file *
spf_clone(struct sp_file *spf)
{
	struct sp_file *p;

	p = malloc(sizeof(*p));
	(void) memcpy(p, spf, sizeof(*p));

	if (spf->spf_name != NULL)
		p->spf_name = strdup(spf->spf_name);

	if (spf->spf_symlink != NULL)
		p->spf_symlink = strdup(spf->spf_symlink);

	return p;
}

/*
 * spf_chmod()
 *
 * Updates the modes of the file in the filesystem according to
 * spf->spf_mode with mode bitwise OR:ed in.
 */
int
spf_chmod(struct sp_file *spf, mode_t mode)
{
	char	*f;

	f = spf_full_name(spf);
	if (spf->spf_opt & SPFO_LOG_CHMOD)
		e_warnx("chmod %o %s", (unsigned) spf->spf_mode|mode, f);
	if (chmod(f, spf->spf_mode|mode) < 0) {
		e_warn("failed to chmod %s", f);
		return -1;
	}
	return 0;
}

/*
 * spf_mkdir()
 *
 * Creates a diretory if SPFO_DONT_CREATE isn't set in spf_opt.
 */
int
spf_mkdir(struct sp_file *spf)
{
	char	*f;

	/* Directory already exists, report success */
	if (spf->spf_opt & SPFO_DONT_CREATE)
		return 0;

	f = spf_full_name(spf);
	e_warnx("mkdir %s", f);
	if (mkdir(f, spf->spf_mode | S_IRWXU) < 0) {
		e_warn("mkdir %s failed", f);
		return -1;
	}
	return 0;
}

/*
 * spf_symlink()
 *
 * Creates a symlink according to spf_name and spf_symlink.
 */
int
spf_symlink(struct sp_file *spf)
{
	e_warnx("ln -s %s %s", spf->spf_symlink, spf_full_name(spf));
	if (symlink(spf->spf_symlink, spf_full_name(spf)) < 0) {
		e_warn("ln -s %s %s failed", spf->spf_symlink, spf_full_name(spf));
		return -1;
	}
	return 0;
}

/*
 * spf_stat_init()
 *
 * Creates a sp_file with the values from a file in the filesystem.
 */
int
spf_stat_init(char *f, struct sp_file **spf)
{
	char	*cf, *buf;
	int		ret;
	struct	sp_file *s;
	struct	stat sb;

	s = calloc((size_t)1, sizeof(*s));
	if (s == NULL)
		return -1;
	s->spf_name = strdup(f);
	cf = full_name(f, cur_file, (size_t)PATH_MAX);

	if (lstat(cf, &sb) < 0) {
		e_warn("failed to lstat %s", cf);
		goto ret_bad;
	}

	s->spf_time = sb.st_mtime;
	s->spf_tgranul = 0;

	s->spf_size = sb.st_size;
	s->spf_mode = sb.st_mode & 0xfff; /* mask out permissions */
	switch(sb.st_mode & S_IFMT) {
	case S_IFDIR:
		s->spf_type = DIRECTORY;
		break;
	case S_IFREG:
		s->spf_type = PLAINFILE;
		break;
	case S_IFLNK:
		s->spf_type = SYMLINK;
		break;
	default:
		s->spf_type = UNKNOWN;
	}
	if (s->spf_type == SYMLINK) {
		buf = alloca((size_t)(PATH_MAX + 1));
		if ((ret = readlink(cf, buf, (size_t)PATH_MAX)) < 0) {
			e_warn("failed to readlink %s", cf);
			goto ret_bad;
		}
		buf[ret] = '\0';
		s->spf_symlink = strdup(buf);
	}
	s->spf_opt |= SPFO_LOCAL;	/* mark spf as local */
	*spf = s;
	return 0;

ret_bad:
	spf_unalloc(s);
	return -1;
}


/* init the sp_skip struct */
struct sp_skip *
sps_init(const char *arg)
{
	size_t	len;
	int		cflags;
	struct	sp_skip *sps;

	len = strlen(arg);
	if ((sps = calloc((size_t)1, sizeof(*sps) + len + 1)) == NULL)
		return NULL;
	/* LINTED save us from one calloc */
	sps->sps_name = (char *)(sps + 1);
	(void) strcpy(sps->sps_name, arg);
	cflags = 0;
	cflags |= REG_EXTENDED;		/* extended RE's */
	cflags |= REG_NOSUB;		/* only report match or no match */
	sps->sps_reg_errno = regcomp(&sps->sps_reg, sps->sps_name, cflags);
	if (sps->sps_reg_errno != 0) {
		free(sps);
		return NULL;
	}
	return sps;
}

/* init the sp_treatasdir struct */
struct sp_treatasdir *
spt_init(const char *arg)
{
	size_t	len;
	int		cflags;
	struct	sp_treatasdir *spt;

	len = strlen(arg);
	if ((spt = calloc((size_t)1, sizeof(*spt) + len + 1)) == NULL)
		return NULL;
	/* LINTED save us from one calloc */
	spt->spt_name = (char *)(spt + 1);
	(void) strcpy(spt->spt_name, arg);
	cflags = 0;
	cflags |= REG_EXTENDED;		/* extended RE's */
	cflags |= REG_NOSUB;		/* only report match or no match */
	spt->spt_reg_errno = regcomp(&spt->spt_reg, spt->spt_name, cflags);
	spt->spt_reg_need_regfree = (spt->spt_reg_errno == 0);
	return spt;
}

/* return 1 if error */
int
spt_error(struct sp_treatasdir *spt)
{
	return (spt->spt_reg_errno != 0);
}

/* make a human readable error message */
char *
spt_strerror(struct sp_treatasdir *spt)
{
	size_t	buf_size;

	/* calculate needed size of buf */
	buf_size = regerror(spt->spt_reg_errno, &spt->spt_reg, NULL, (size_t)0);

	/* only malloc if spt_buf too small */
	if (spt->spt_buf_size < buf_size) {
		if (spt->spt_buf != NULL)
			free(spt->spt_buf);
		spt->spt_buf = malloc(buf_size);
		if (spt->spt_buf == NULL) {
			spt->spt_buf_size = 0;
			return NULL;
		}
		spt->spt_buf_size = buf_size;
	}

	(void) regerror(spt->spt_reg_errno, &spt->spt_reg,
		spt->spt_buf, spt->spt_buf_size);
	return spt->spt_buf;
}

/* free the sp_skip struct */
void
spt_unalloc(struct sp_treatasdir *spt)
{
	if (spt->spt_buf != NULL)
		free(spt->spt_buf);

	/* only regfree after a successful regcomp */
	if (spt->spt_reg_need_regfree)	
		regfree(&spt->spt_reg);

	free(spt);
}



/* return 1 if error */
int
sps_error(struct sp_skip *sps)
{
	return (sps->sps_reg_errno != 0);
}

/* make a human readable error message */
char *
sps_strerror(struct sp_skip *sps)
{
	size_t	buf_size;

	/* calculate needed size of buf */
	buf_size = regerror(sps->sps_reg_errno, &sps->sps_reg, NULL, (size_t)0);

	/* only malloc if sps_buf too small */
	if (sps->sps_buf_size < buf_size) {
		if (sps->sps_buf != NULL)
			free(sps->sps_buf);
		sps->sps_buf = malloc(buf_size);
		if (sps->sps_buf == NULL) {
			sps->sps_buf_size = 0;
			return NULL;
		}
		sps->sps_buf_size = buf_size;
	}

	(void) regerror(sps->sps_reg_errno, &sps->sps_reg,
		sps->sps_buf, sps->sps_buf_size);
	return sps->sps_buf;
}

/* free the sp_skip struct */
void
sps_unalloc(struct sp_skip *sps)
{
	if (sps->sps_buf != NULL)
		free(sps->sps_buf);

	regfree(&sps->sps_reg);

	free(sps);
}

int
sps_match(struct sp_skip *sps, char *name)
{
	regmatch_t *pmatch;
	size_t	nmatch;
	int		eflags;

	eflags = 0;
	nmatch = 0;		/* only want to know wether it matched or not */
	pmatch = NULL;	/* no need for storage */
	sps->sps_reg_errno = regexec(&sps->sps_reg, name, nmatch, pmatch, eflags);
	return (sps->sps_reg_errno == 0);
}

int
spt_match(struct sp_treatasdir *spt, char *name)
{
	regmatch_t *pmatch;
	size_t	nmatch;
	int		eflags;

	eflags = 0;
	nmatch = 0;		/* only want to know wether it matched or not */
	pmatch = NULL;	/* no need for storage */
	spt->spt_reg_errno = regexec(&spt->spt_reg, name, nmatch, pmatch, eflags);
	return (spt->spt_reg_errno == 0);
}

/***** functions for recursive list of files *****/

static char *
parse_dir_name(char *str)
{
	size_t	len;

	str += strspn(str, "./");
	/* remove trailing '\r', '\n' and ':' */
	len = strlen(str);
	if (str[len - 1] == '\n')
		str[len - 1] = '\0';
	len = strlen(str);
	if (str[len - 1] == '\r')
		str[len - 1] = '\0';
	len = strlen(str);
	if (str[len - 1] != ':')
		return NULL;
	str[len - 1] = '\0';
	return strdup(str);
}

static int
is_empty_line(char *str)
{
	size_t	l;

	l = strspn(str, " \n\r");
	return (str[l] == '\0');
}

static void
spd_unalloc(struct sp_dir *spd)
{
	if (spd->spd_spdl != NULL)
		sl_spd_free(spd->spd_spdl, spd_unalloc);
	if (spd->spd_full_name != NULL)
		free(spd->spd_full_name);
}

static struct sp_dir *
spd_init(FILE *f)
{
	char	buf[PATH_MAX];
	struct	sp_dir *spd;
	long	pos;
	int		oerrno;

	if ((spd = malloc(sizeof(*spd))) == NULL)
		return NULL;

	do {
		if ((pos = ftell(f)) < 0)
			goto ret_bad;
		if (fgets(buf, (int)sizeof(buf), f) == NULL)
			goto ret_bad;
	} while (is_empty_line(buf));
	
	if ((spd->spd_full_name = parse_dir_name(buf)) == NULL) {
		if (errno == ENOMEM)
			goto ret_bad;
	} else {
		if ((spd->spd_name = rindex(spd->spd_full_name, '/')) == NULL)
			spd->spd_name = spd->spd_full_name;
		else
			spd->spd_name++;
		if ((pos = ftell(f)) < 0)
			goto ret_bad;
		if (fgets(buf, (int)sizeof(buf), f) == NULL)
			goto ret_bad;
	}

	if (strncmp(buf + 1, "otal", (size_t)4) != 0) {
		if (fseek(f, pos, SEEK_SET) < 0)
			goto ret_bad;
	}

	spd->spd_begin = pos;
	do {
		if (fgets(buf, (int)sizeof(buf), f) == NULL) {
			if (feof(f))
				break;
			goto ret_bad;
		}
	} while (is_empty_line(buf) == 0);
	/* We are at end of file or at the empty line that follows each
	 * directory.
	 */
	if ((spd->spd_end = ftell(f)) < 0)
		goto ret_bad;
	return spd;

ret_bad:
	oerrno = errno;
	free(spd);
	errno = oerrno;
	return NULL;
}

static u_int32_t
spd_key(const char *str)
{
	u_int32_t	key;
	size_t		len;
	char		*s;
	int			i;

	key = 0;
	len = strlen(str);
	s = (char *)&key;
	for (i = 0; i < 4 && len > 0; i++, len--)
		s[i] = str[len];
	return key;
}

static int
spd_find(struct sp_dir *spd, const char *str, struct sp_dir **p)
{
	u_int32_t	key;
	struct		skip_spd_list_node *pos;

	key = spd_key(str);
	if (sl_spd_find_pos(spd->spd_spdl, key, &pos, p) < 0)
		goto ret_not_found;
	while (strcmp((*p)->spd_name, str) != 0) {
		if (sl_spd_walk(spd->spd_spdl, &pos, p) < 0)
			goto ret_not_found;
		if (spd_key((*p)->spd_name) != key)
			goto ret_not_found;
	}
	return 0;

ret_not_found:
	errno = ENOENT;
	return -1;
}

static char *
mystrsep(char **strp, const char *delim)
{
	char	*str;

	while ((str = strsep(strp, delim)) != NULL) {
		if (*str != '\0')
			return str;
	}
	return str;
}

static int
spd_find_path(struct sp_dir_idx *spdi, const char *path, struct sp_dir **spd)
{
	char	buf[PATH_MAX];
	char	*str, *s;
	struct	sp_dir *p1, *p2;

	strlcpy(buf, path, PATH_MAX);
	str = buf;
	if ((s = mystrsep(&str, "/")) == NULL)
		s = str;
	p1 = spdi->spdi_spd;
	for ( ;; ) {
		if (spd_find(p1, s, &p2) < 0)
			return -1;
		p1 = p2;
		if ((s = mystrsep(&str, "/")) == NULL)
			return 0;
	}
}

static int
spdi_insert_spd(struct sp_dir_idx *spdi, struct sp_dir *spd)
{
	char	buf[PATH_MAX], *str;
	struct	sp_dir *p;
	size_t	len;

	strlcpy(buf, spd->spd_full_name, PATH_MAX);
	for ( ;; ) {
		str = rindex(buf, '/');
		if ((len = strlen(str)) == 0)
			return -1;
		if (str[len - 1] != '/')
			break;
		str[len - 1] = '\0';
	}
	if (spd_find_path(spdi, buf, &p) < 0)
		return -1;
	if (sl_spd_ins(p->spd_spdl, spd_key(spd->spd_name), spd) < 0)
		return -1;
	return 0;
}

static int
spdi_index_file(struct sp_dir_idx *spdi)
{
	struct	sp_dir *spd;

	if ((spd = spd_init(spdi->spdi_file)) == NULL)
		return -1;

	spdi->spdi_spd = spd;

	for ( ;; ) {
		if ((spd = spd_init(spdi->spdi_file)) == NULL) {
			if (feof(spdi->spdi_file))
				return 0;
			return -1;
		}
		if (spdi_insert_spd(spdi, spd) < 0)
			return -1;
	}
}

struct sp_dir_idx *
spdi_init(const char *filename, const char *path)
{
	struct	sp_dir_idx *spdi;
	int		oerrno;

	if ((spdi = calloc(1, sizeof(*spdi))) == NULL)
		return NULL;
	if ((spdi->spdi_file = fopen(filename, "r")) == NULL)
		goto ret_bad;
	if ((spdi->spdi_path = strdup(path)) == NULL)
		goto ret_bad;
	if (spdi_index_file(spdi) < 0)
		goto ret_bad;
	return spdi;

ret_bad:
	oerrno = errno;
	if (spdi->spdi_file != NULL)
		fclose(spdi->spdi_file);
	if (spdi->spdi_path != NULL)
		free(spdi->spdi_path);
	if (spdi->spdi_spd != NULL)
		spd_unalloc(spdi->spdi_spd);
	free(spdi);
	errno = oerrno;
	return NULL;
}

void
spdi_unalloc(struct sp_dir_idx *spdi)
{
	if (spdi == NULL)
		return;
	spd_unalloc(spdi->spdi_spd);
	if (spdi->spdi_file != NULL)
		fclose(spdi->spdi_file);
	if (spdi->spdi_path != NULL)
		free(spdi->spdi_path);
	free(spdi);
}

int
spdi_chdir(struct sp_dir_idx *spdi, const char *dir)
{
	if (spd_find_path(spdi, dir, &spdi->spdi_cur_dir) < 0)
		return -1;
	if (fseek(spdi->spdi_file, spdi->spdi_cur_dir->spd_begin, SEEK_SET) < 0)
		return -1;
	return 0;
}

int
spdi_next_file(struct sp_dir_idx *spdi, struct sp_file **spf)
{
	char	buf[PATH_MAX];
	struct	sp_file *p;
	long	pos;

	if ((pos = ftell(spdi->spdi_file)) < 0)
		return -1;
	if (pos > spdi->spdi_cur_dir->spd_end) {
		*spf = NULL;	/* XXX to report end dir listing */
		return -1;	
	}
	if (fgets(buf, PATH_MAX, spdi->spdi_file) == NULL)
		return -1;
	if ((p = parse_row(buf)) == NULL)
		return -1;
	*spf = p;
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1