/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)files.c	1.12 04/03/04 joerg */
/*
 * File files.c - Handle ADD_FILES related stuff.
 *
 * Written by Eric Youngdale (1993).
 *
 * Copyright 1993 Yggdrasil Computing, Incorporated
 *
 * 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 2, 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.
 */

/* ADD_FILES changes made by Ross Biro biro@yggdrasil.com 2/23/95 */

#include <mconfig.h>
#include "genisoimage.h"
#include <errno.h>
#include <schily.h>
#include <ctype.h>

#ifdef ADD_FILES

void	add_one_file(char *addpath, char *path);
void	add_file_list(int argc, char **argv, int ind);
void	add_file(char *filename);
char *look_up_addition(char **newpath, char *path, struct dirent **de);
void	nuke_duplicates(char *path, struct dirent **de);
struct dirent  *readdir_add_files(char **pathp, char *path, DIR *dir);

struct file_adds {
	char			*name;
	struct file_adds	*child;
	struct file_adds	*next;
	int			add_count;
	int			used;
	union diru {
	/*
	 * XXX Struct dirent is not guaranteed to be any size on a POSIX
	 * XXX compliant system.
	 * XXX We need to allocate enough space here, to allow the hacky
	 * XXX code in tree.c made by Ross Biro biro@yggdrasil.com
	 * XXX to work on operating systems other than Linux :-(
	 * XXX Changes made by Joerg Schilling
	 * XXX joerg@schily.isdn.cs.tu-berlin.de
	 * XXX to prevent core dumps on Solaris.
	 * XXX Space allocated:
	 * XXX		1024 bytes == NAME_MAX
	 * XXX	+	2   bytes for directory record length
	 * XXX	+	2*8 bytes for inode number & offset (64 for future exp)
	 */
		struct dirent	de;
		char		dspace[NAME_MAX + 2 + 2 * 8];
	} du;
	struct {
		char		*path;
		char		*name;
	} *adds;
};
extern struct file_adds *root_file_adds;

/*
 * FIXME(eric) - the file adding code really doesn't work very well
 * at all.  We should differentiate between adding directories, and adding
 * single files, as adding a full directory affects how we should be
 * searching for things.  Ideally what we should do is make two passes
 * through the local filesystem - one to figure out what trees we need
 * to scan (and merge in any additions at that point), and the second to
 * actually fill out each structure with the appropriate contents.
 */

struct file_adds *root_file_adds = NULL;

void
add_one_file(char *addpath, char *path)
{
	char			*cp;
	char			*name;
	struct file_adds	*f;
	struct file_adds	*tmp;

	f = root_file_adds;
	tmp = NULL;

	name = strrchr(addpath, PATH_SEPARATOR);
	if (name == NULL) {
		name = addpath;
	} else {
		name++;
	}

	cp = strtok(addpath, SPATH_SEPARATOR);

	while (cp != NULL && strcmp(name, cp)) {
		if (f == NULL) {
			root_file_adds = e_malloc(sizeof (*root_file_adds));
			f = root_file_adds;
			f->name = NULL;
			f->child = NULL;
			f->next = NULL;
			f->add_count = 0;
			f->adds = NULL;
			f->used = 0;
		}
		if (f->child) {
			for (tmp = f->child; tmp->next != NULL;
							tmp = tmp->next) {
				if (strcmp(tmp->name, cp) == 0) {
					f = tmp;
					goto next;
				}
			}
			if (strcmp(tmp->name, cp) == 0) {
				f = tmp;
				goto next;
			}
			/* add a new node. */
			tmp->next = e_malloc(sizeof (*tmp->next));
			f = tmp->next;
			f->name = strdup(cp);
			f->child = NULL;
			f->next = NULL;
			f->add_count = 0;
			f->adds = NULL;
			f->used = 0;
		} else {
			/* no children. */
			f->child = e_malloc(sizeof (*f->child));
			f = f->child;
			f->name = strdup(cp);
			f->child = NULL;
			f->next = NULL;
			f->add_count = 0;
			f->adds = NULL;
			f->used = 0;

		}
next:
		cp = strtok(NULL, SPATH_SEPARATOR);
	}
	/* Now f if non-null points to where we should add things */
	if (f == NULL) {
		root_file_adds = e_malloc(sizeof (*root_file_adds));
		f = root_file_adds;
		f->name = NULL;
		f->child = NULL;
		f->next = NULL;
		f->add_count = 0;
		f->adds = NULL;
	}
	/* Now f really points to where we should add this name. */
	f->add_count++;
	f->adds = realloc(f->adds, sizeof (*f->adds) * f->add_count);
	f->adds[f->add_count - 1].path = strdup(path);
	f->adds[f->add_count - 1].name = strdup(name);
}

/*
 * Function:	add_file_list
 *
 * Purpose:	Register an add-in file.
 *
 * Arguments:
 */
void
add_file_list(int argc, char **argv, int ind)
{
	char	*ptr;
	char	*dup_arg;

	while (ind < argc) {
		dup_arg = strdup(argv[ind]);
		ptr = strchr(dup_arg, '=');
		if (ptr == NULL) {
			free(dup_arg);
			return;
		}
		*ptr = 0;
		ptr++;
		add_one_file(dup_arg, ptr);
		free(dup_arg);
		ind++;
	}
}

void
add_file(char *filename)
{
	char	buff[PATH_MAX];
	FILE	*f;
	char	*ptr;
	char	*p2;
	int	count = 0;

	if (strcmp(filename, "-") == 0) {
		f = stdin;
	} else {
		f = fopen(filename, "r");
		if (f == NULL) {
#ifdef	USE_LIBSCHILY
			comerr("Cannot open '%s'.\n", filename);
#else
			perror("fopen");
			exit(1);
#endif
		}
	}
	while (fgets(buff, sizeof (buff), f)) {
		count++;
		ptr = buff;
		while (isspace(*ptr))
			ptr++;
		if (*ptr == 0)
			continue;
		if (*ptr == '#')
			continue;

		if (ptr[strlen(ptr) - 1] == '\n')
			ptr[strlen(ptr) - 1] = 0;
		p2 = strchr(ptr, '=');
		if (p2 == NULL) {
#ifdef	USE_LIBSCHILY
			comerrno(EX_BAD, "Error in file '%s' line %d: %s\n",
						filename, count, buff);
#else
			fprintf(stderr, "Error in file '%s' line %d: %s\n",
						filename, count, buff);
			exit(1);
#endif
		}
		*p2 = 0;
		p2++;
		add_one_file(ptr, p2);
	}
	if (f != stdin)
		fclose(f);
}

/* This function looks up additions. */
char *
look_up_addition(char **newpath, char *path, struct dirent **de)
{
	char			*dup_path;
	char			*cp;
	struct file_adds	*f;
	struct file_adds	*tmp = NULL;

	f = root_file_adds;
	if (!f)
		return (NULL);

	/* I don't trust strtok */
	dup_path = strdup(path);

	cp = strtok(dup_path, SPATH_SEPARATOR);
	while (cp != NULL) {
		for (tmp = f->child; tmp != NULL; tmp = tmp->next) {
			if (strcmp(tmp->name, cp) == 0)
				break;
		}
		if (tmp == NULL) {
			/* no match */
			free(dup_path);
			return (NULL);
		}
		f = tmp;
		cp = strtok(NULL, SPATH_SEPARATOR);
	}
	free(dup_path);

	/* If nothing, then return. */
	if (tmp == NULL) {
	/* no match */
		return (NULL);
	}
	/* looks like we found something. */
	if (tmp->used >= tmp->add_count)
		return (NULL);

	*newpath = tmp->adds[tmp->used].path;
	tmp->used++;
	*de = &(tmp->du.de);
	return (tmp->adds[tmp->used - 1].name);

}

/* This function looks up additions. */
void
nuke_duplicates(char *path, struct dirent **de)
{
	char			*dup_path;
	char			*cp;
	struct file_adds	*f;
	struct file_adds	*tmp;

	f = root_file_adds;
	if (!f)
		return;

	/* I don't trust strtok */
	dup_path = strdup(path);

	cp = strtok(dup_path, SPATH_SEPARATOR);
	while (cp != NULL) {
		for (tmp = f->child; tmp != NULL; tmp = tmp->next) {
			if (strcmp(tmp->name, cp) == 0)
				break;
		}
		if (tmp == NULL) {
			/* no match */
			free(dup_path);
			return;
		}
		f = tmp;
		cp = strtok(NULL, SPATH_SEPARATOR);
	}
	free(dup_path);

#if 0
	/* looks like we found something. */
	if (tmp->used >= tmp->add_count)
		return;

	*newpath = tmp->adds[tmp->used].path;
	tmp->used++;
	*de = &(tmp->du.de);
	return (tmp->adds[tmp->used - 1].name);
#endif
}

/*
 * This function lets us add files from outside the standard file tree.
 * It is useful if we want to duplicate a cd, but add/replace things.
 * We should note that the real path will be used for exclusions.
 */

struct dirent  *
readdir_add_files(char **pathp, char **path, DIR *dir)
{
	struct dirent  *de;

	char	*addpath;
	char	*name;

	de = readdir(dir);
	if (de) {
		nuke_duplicates(path, &de);
		return (de);
	}
	name = look_up_addition(&addpath, path, &de);

	if (!name) {
		return (NULL);
	}
	*pathp = addpath;

	/*
	 * Now we must create the directory entry.
	 * fortuneately only the name seems to matter.
	 */
/*	de->d_ino = -1; de->d_off = 0; de->d_reclen = strlen (name); */
	strncpy(de->d_name, name, NAME_MAX);
	de->d_name[NAME_MAX] = 0;
	nuke_duplicates(path, &de);
	return (de);

}

#else
struct dirent  *
readdir_add_files(char **pathp, char *path, DIR *dir)
{
	return (readdir(dir));
}

#endif
