/* @(#)hash.c	1.15 02/02/10 joerg */
#ifndef lint
static	char sccsid[] =
	"@(#)hash.c	1.15 02/02/10 joerg";

#endif
/*
 * File hash.c - generate hash tables for iso9660 filesystem.

   Written by Eric Youngdale (1993).

   Copyright 1993 Yggdrasil Computing, Incorporated
   Copyright (c) 1999,2000 J. Schilling

   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.  */

/* APPLE_HYB James Pearson j.pearson@ge.ucl.ac.uk 23/2/2000 */

/*
 * From jb@danware.dk:
 *
 * Cygwin fakes inodes by hashing file info, actual collisions observed!
 * This is documented in the cygwin source, look at winsup/cygwin/path.cc
 * and search for the word 'Hash'.  On NT, cygwin ORs together the
 * high and low 32 bits of the 64 bit genuine inode, look at fhandler.cc.
 *
 * Note:	Other operating systems which support the FAT filesystem may
 *		have the same problem because FAT does not use the inode
 *		concept.  For NTFS, genuine inode numbers exist, but they are
 *		64 bits and available only through an open file handle.
 *
 * The solution is the new options -no-cache-inodes/-cache-inodes that
 * allow to disable the mkisofs inode cache.
 */

#include <mconfig.h>
#include "mkisofs.h"
#include <schily.h>

#define NR_HASH 1024

#define HASH_FN(DEV, INO) ((DEV + INO + (INO >> 2) + (INO << 8)) % NR_HASH)

static struct file_hash *hash_table[NR_HASH] = {0,};

EXPORT	void		add_hash	__PR((struct directory_entry *spnt));
EXPORT	struct file_hash *find_hash	__PR((dev_t dev, ino_t inode));
EXPORT	void		flush_hash	__PR((void));
EXPORT	void		add_directory_hash __PR((dev_t dev, ino_t inode));
EXPORT	struct file_hash *find_directory_hash __PR((dev_t dev, ino_t inode));
LOCAL	unsigned int	name_hash	__PR((const char *name));
EXPORT	void		add_file_hash	__PR((struct directory_entry *de));
EXPORT	struct directory_entry *find_file_hash __PR((char const *name));
EXPORT	int		delete_file_hash __PR((struct directory_entry *de));
EXPORT	void		flush_file_hash	__PR((void));

EXPORT void
add_hash(struct directory_entry *spnt)
{
	struct file_hash *s_hash;
	unsigned int    hash_number;

	if (spnt->size == 0 || spnt->starting_block == 0)
		if (spnt->size != 0 || spnt->starting_block != 0) {
#ifdef	USE_LIBSCHILY
			comerrno(EX_BAD,
			"Non zero-length file '%s' assigned zero extent.\n",
							spnt->name);
#else
			fprintf(stderr,
			"Non zero-length file '%s' assigned zero extent.\n",
							spnt->name);
			exit(1);
#endif
		};

	if (!cache_inodes)
		return;
	if (spnt->dev == (dev_t) UNCACHED_DEVICE ||
				spnt->inode == UNCACHED_INODE) {
		return;
	}
	hash_number = HASH_FN((unsigned int) spnt->dev,
						(unsigned int) spnt->inode);

#if 0
	if (verbose > 1)
		fprintf(stderr, "%s ", spnt->name);
#endif
	s_hash = (struct file_hash *) e_malloc(sizeof(struct file_hash));
	s_hash->next = hash_table[hash_number];
	s_hash->inode = spnt->inode;
	s_hash->dev = spnt->dev;
	s_hash->starting_block = spnt->starting_block;
	s_hash->size = spnt->size;
#ifdef SORTING
	s_hash->de = spnt;
#endif /* SORTING */
	hash_table[hash_number] = s_hash;
}

EXPORT struct file_hash *
find_hash(dev_t dev, ino_t inode)
{
	unsigned int    hash_number;
	struct file_hash *spnt;

	if (!cache_inodes)
		return NULL;
	if (dev == (dev_t) UNCACHED_DEVICE || inode == UNCACHED_INODE)
		return NULL;

	hash_number = HASH_FN((unsigned int) dev, (unsigned int) inode);
	spnt = hash_table[hash_number];
	while (spnt) {
		if (spnt->inode == inode && spnt->dev == dev)
			return spnt;
		spnt = spnt->next;
	};
	return NULL;
}

/*
 * based on flush_file_hash() below - needed as we want to re-use the
 * file hash table.
 */
EXPORT void
flush_hash()
{
	struct file_hash *fh,
	               *fh1;
	int             i;

	for (i = 0; i < NR_HASH; i++) {
		fh = hash_table[i];
		while (fh) {
			fh1 = fh->next;
			free(fh);
			fh = fh1;
		}
		hash_table[i] = NULL;
	}
}

static struct file_hash *directory_hash_table[NR_HASH] = {0,};

EXPORT void
add_directory_hash(dev_t dev, ino_t inode)
{
	struct file_hash *s_hash;
	unsigned int    hash_number;

	if (!cache_inodes)
		return;
	if (dev == (dev_t) UNCACHED_DEVICE || inode == UNCACHED_INODE)
		return;

	hash_number = HASH_FN((unsigned int) dev, (unsigned int) inode);

	s_hash = (struct file_hash *) e_malloc(sizeof(struct file_hash));
	s_hash->next = directory_hash_table[hash_number];
	s_hash->inode = inode;
	s_hash->dev = dev;
	directory_hash_table[hash_number] = s_hash;
}

EXPORT struct file_hash *
find_directory_hash(dev_t dev, ino_t inode)
{
	unsigned int    hash_number;
	struct file_hash *spnt;

	if (!cache_inodes)
		return NULL;
	if (dev == (dev_t) UNCACHED_DEVICE || inode == UNCACHED_INODE)
		return NULL;

	hash_number = HASH_FN((unsigned int) dev, (unsigned int) inode);
	spnt = directory_hash_table[hash_number];
	while (spnt) {
		if (spnt->inode == inode && spnt->dev == dev)
			return spnt;
		spnt = spnt->next;
	};
	return NULL;
}

struct name_hash {
	struct name_hash *next;
	struct directory_entry *de;
};

#define NR_NAME_HASH 128

static struct name_hash *name_hash_table[NR_NAME_HASH] = {0,};

/*
 * Find the hash bucket for this name.
 */
LOCAL unsigned int
name_hash(char const *name)
{
	unsigned int    hash = 0;
	const char     *p;

	p = name;

	while (*p) {
		/*
		 * Don't hash the  iso9660 version number.
		 * This way we can detect duplicates in cases where we have
		 * directories (i.e. foo) and non-directories (i.e. foo;1).
		 */
		if (*p == ';') {
			break;
		}
		hash = (hash << 15) + (hash << 3) + (hash >> 3) + *p++;
	}
	return hash % NR_NAME_HASH;
}

EXPORT void
add_file_hash(struct directory_entry *de)
{
	struct name_hash *newhash;
	int             hash;

	newhash = (struct name_hash *) e_malloc(sizeof(struct name_hash));
	newhash->de = de;
	newhash->next = NULL;
	hash = name_hash(de->isorec.name);

	/* Now insert into the hash table */
	newhash->next = name_hash_table[hash];
	name_hash_table[hash] = newhash;
}

EXPORT struct directory_entry *
find_file_hash(char const *name)
{
	struct name_hash *nh;
	char const     *p1;
	char           *p2;

	for (nh = name_hash_table[name_hash(name)]; nh; nh = nh->next) {
		p1 = name;
		p2 = nh->de->isorec.name;

		/* Look for end of string, or a mismatch. */
		while (1 == 1) {
			if ((*p1 == '\0' || *p1 == ';')
				|| (*p2 == '\0' || *p2 == ';')
				|| (*p1 != *p2)) {
				break;
			}
			p1++;
			p2++;
		}

		/*
		 * If we are at the end of both strings, then we have a match.
		 */
		if ((*p1 == '\0' || *p1 == ';')
			&& (*p2 == '\0' || *p2 == ';')) {
			return nh->de;
		}
	}
	return NULL;
}

EXPORT int
delete_file_hash(struct directory_entry *de)
{
	struct name_hash *nh,
	               *prev;
	int             hash;

	prev = NULL;
	hash = name_hash(de->isorec.name);
	for (nh = name_hash_table[hash]; nh; nh = nh->next) {
		if (nh->de == de)
			break;
		prev = nh;
	}
	if (!nh)
		return 1;
	if (!prev)
		name_hash_table[hash] = nh->next;
	else
		prev->next = nh->next;
	free(nh);
	return 0;
}

EXPORT void
flush_file_hash()
{
	struct name_hash *nh,
	               *nh1;
	int             i;

	for (i = 0; i < NR_NAME_HASH; i++) {
		nh = name_hash_table[i];
		while (nh) {
			nh1 = nh->next;
			free(nh);
			nh = nh1;
		}
		name_hash_table[i] = NULL;

	}
}
