// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2023-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "libxfs.h"
#include "libxfs/xfile.h"
#include "libxfs/xfblob.h"
#include "repair/strblobs.h"

/*
 * String Blob Structure
 * =====================
 *
 * This data structure wraps the storage of strings with explicit length in an
 * xfblob structure.  It stores a hashtable of string checksums to provide
 * fast(ish) lookups of existing strings to enable deduplication of the strings
 * contained within.
 */
struct strblob_hashent {
	struct strblob_hashent	*next;

	xfblob_cookie		str_cookie;
	unsigned int		str_len;
	xfs_dahash_t		str_hash;
};

struct strblobs {
	struct xfblob		*strings;
	unsigned int		nr_buckets;

	struct strblob_hashent	*buckets[];
};

static inline size_t strblobs_sizeof(unsigned int nr_buckets)
{
	return sizeof(struct strblobs) +
			(nr_buckets * sizeof(struct strblobs_hashent *));
}

/* Initialize a string blob structure. */
int
strblobs_init(
	const char		*descr,
	unsigned int		hash_buckets,
	struct strblobs		**sblobs)
{
	struct strblobs		*sb;
	int			error;

	sb = calloc(strblobs_sizeof(hash_buckets), 1);
	if (!sb)
		return ENOMEM;

	error = -xfblob_create(descr, &sb->strings);
	if (error)
		goto out_free;

	sb->nr_buckets = hash_buckets;
	*sblobs = sb;
	return 0;

out_free:
	free(sb);
	return error;
}

/* Deconstruct a string blob structure. */
void
strblobs_destroy(
	struct strblobs		**sblobs)
{
	struct strblobs		*sb = *sblobs;
	struct strblob_hashent	*ent, *ent_next;
	unsigned int		bucket;

	for (bucket = 0; bucket < sb->nr_buckets; bucket++) {
		ent = sb->buckets[bucket];
		while (ent != NULL) {
			ent_next = ent->next;
			free(ent);
			ent = ent_next;
		}
	}

	xfblob_destroy(sb->strings);
	free(sb);
	*sblobs = NULL;
}

/*
 * Search the string hashtable for a matching entry.  Sets sets the cookie and
 * returns 0 if one is found; ENOENT if there is no match; or a positive errno.
 */
static int
__strblobs_lookup(
	struct strblobs		*sblobs,
	xfblob_cookie		*str_cookie,
	const unsigned char	*str,
	unsigned int		str_len,
	xfs_dahash_t		str_hash)
{
	struct strblob_hashent	*ent;
	unsigned char		*buf = NULL;
	unsigned int		bucket;
	int			error;

	bucket = str_hash % sblobs->nr_buckets;
	ent = sblobs->buckets[bucket];

	for (ent = sblobs->buckets[bucket]; ent != NULL; ent = ent->next) {
		if (ent->str_len != str_len || ent->str_hash != str_hash)
			continue;

		if (!buf) {
			buf = malloc(str_len);
			if (!buf)
				return ENOMEM;
		}

		error = strblobs_load(sblobs, ent->str_cookie, buf, str_len);
		if (error)
			goto out;

		if (memcmp(str, buf, str_len))
			continue;

		*str_cookie = ent->str_cookie;
		goto out;
	}
	error = ENOENT;

out:
	free(buf);
	return error;
}

/*
 * Search the string hashtable for a matching entry.  Sets sets the cookie and
 * returns 0 if one is found; ENOENT if there is no match; or a positive errno.
 */
int
strblobs_lookup(
	struct strblobs		*sblobs,
	xfblob_cookie		*str_cookie,
	const unsigned char	*str,
	unsigned int		str_len,
	xfs_dahash_t		str_hash)
{
	return __strblobs_lookup(sblobs, str_cookie, str, str_len, str_hash);
}

/* Remember a string in the hashtable. */
static int
strblobs_hash(
	struct strblobs		*sblobs,
	xfblob_cookie		str_cookie,
	const unsigned char	*str,
	unsigned int		str_len,
	xfs_dahash_t		str_hash)
{
	struct strblob_hashent	*ent;
	unsigned int		bucket;

	bucket = str_hash % sblobs->nr_buckets;

	ent = malloc(sizeof(struct strblob_hashent));
	if (!ent)
		return ENOMEM;

	ent->str_cookie = str_cookie;
	ent->str_len = str_len;
	ent->str_hash = str_hash;
	ent->next = sblobs->buckets[bucket];

	sblobs->buckets[bucket] = ent;
	return 0;
}

/* Store a string and return a cookie for its retrieval. */
int
strblobs_store(
	struct strblobs		*sblobs,
	xfblob_cookie		*str_cookie,
	const unsigned char	*str,
	unsigned int		str_len,
	xfs_dahash_t		str_hash)
{
	int			error;

	error = __strblobs_lookup(sblobs, str_cookie, str, str_len, str_hash);
	if (error != ENOENT)
		return error;

	error = -xfblob_store(sblobs->strings, str_cookie, str, str_len);
	if (error)
		return error;

	return strblobs_hash(sblobs, *str_cookie, str, str_len, str_hash);
}

/* Retrieve a previously stored string. */
int
strblobs_load(
	struct strblobs		*sblobs,
	xfblob_cookie		str_cookie,
	unsigned char		*str,
	unsigned int		str_len)
{
	return -xfblob_load(sblobs->strings, str_cookie, str, str_len);
}
