/*
   Unix SMB/CIFS implementation.
   Transparent registry backend handling
   Copyright (C) Jelmer Vernooij			2003-2007.

   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 3 of the License, 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.
*/

#include "includes.h"
#include "../lib/util/dlinklist.h"
#include "lib/registry/registry.h"
#include "system/filesys.h"

struct reg_key_path {
	uint32_t predefined_key;
	const char **elements;
};

struct registry_local {
	const struct registry_operations *ops;

	struct mountpoint {
		struct reg_key_path path;
		struct hive_key *key;
		struct mountpoint *prev, *next;
	} *mountpoints;
};

struct local_key {
	struct registry_key global;
	struct reg_key_path path;
	struct hive_key *hive_key;
};


struct registry_key *reg_import_hive_key(struct registry_context *ctx,
					 struct hive_key *hive,
					 uint32_t predefined_key,
					 const char **elements)
{
	struct local_key *local_key;
	struct reg_key_path parent_path;

	parent_path.predefined_key = predefined_key;
	parent_path.elements = elements;

	local_key = talloc(ctx, struct local_key);
	if (local_key != NULL) {
		local_key->hive_key = talloc_reference(local_key, hive);
		local_key->global.context = talloc_reference(local_key, ctx);
		local_key->path = parent_path;
	}

	return (struct registry_key *)local_key;
}


static WERROR local_open_key(TALLOC_CTX *mem_ctx,
			     struct registry_key *parent,
			     const char *path,
			     struct registry_key **result)
{
	char *orig, *curbegin, *curend;
	struct local_key *local_parent = talloc_get_type(parent,
							 struct local_key);
	struct hive_key *curkey = local_parent->hive_key;
	WERROR error;
	const char **elements = NULL;
	int el;

	if (path == NULL || path[0] == '\0') {
		return WERR_INVALID_PARAM;
	}

	orig = talloc_strdup(mem_ctx, path);
	W_ERROR_HAVE_NO_MEMORY(orig);
	curbegin = orig;
	curend = strchr(orig, '\\');

	if (local_parent->path.elements != NULL) {
		elements = talloc_array(mem_ctx, const char *,
					str_list_length(local_parent->path.elements) + 1);
		W_ERROR_HAVE_NO_MEMORY(elements);
		for (el = 0; local_parent->path.elements[el] != NULL; el++) {
			elements[el] = talloc_reference(elements,
							local_parent->path.elements[el]);
		}
		elements[el] = NULL;
	} else {
		elements = NULL;
		el = 0;
	}

	do {
		if (curend != NULL)
			*curend = '\0';
		elements = talloc_realloc(mem_ctx, elements, const char *, el+2);
		W_ERROR_HAVE_NO_MEMORY(elements);
		elements[el] = talloc_strdup(elements, curbegin);
		W_ERROR_HAVE_NO_MEMORY(elements[el]);
		el++;
		elements[el] = NULL;
		error = hive_get_key_by_name(mem_ctx, curkey,
					     curbegin, &curkey);
		if (!W_ERROR_IS_OK(error)) {
			DEBUG(2, ("Opening key %s failed: %s\n", curbegin,
				win_errstr(error)));
			talloc_free(orig);
			return error;
		}
		if (curend == NULL)
			break;
		curbegin = curend + 1;
		curend = strchr(curbegin, '\\');
	} while (curbegin[0] != '\0');
	talloc_free(orig);

	*result = reg_import_hive_key(local_parent->global.context, curkey,
				      local_parent->path.predefined_key,
				      talloc_steal(curkey, elements));

	return WERR_OK;
}

WERROR local_get_predefined_key(struct registry_context *ctx,
				uint32_t key_id, struct registry_key **key)
{
	struct registry_local *rctx = talloc_get_type(ctx,
						      struct registry_local);
	struct mountpoint *mp;

	for (mp = rctx->mountpoints; mp != NULL; mp = mp->next) {
		if (mp->path.predefined_key == key_id &&
			mp->path.elements == NULL)
			break;
	}

	if (mp == NULL)
		return WERR_BADFILE;

	*key = reg_import_hive_key(ctx, mp->key,
				   mp->path.predefined_key,
				   mp->path.elements);

	return WERR_OK;
}

static WERROR local_enum_key(TALLOC_CTX *mem_ctx,
			     const struct registry_key *key, uint32_t idx,
			     const char **name,
			     const char **keyclass,
			     NTTIME *last_changed_time)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_enum_key(mem_ctx, local->hive_key, idx, name, keyclass,
			     last_changed_time);
}

static WERROR local_create_key(TALLOC_CTX *mem_ctx,
			       struct registry_key *parent,
			       const char *path,
			       const char *key_class,
			       struct security_descriptor *security,
			       struct registry_key **result)
{
	char *orig, *curbegin, *curend;
	struct local_key *local_parent = talloc_get_type(parent,
							 struct local_key);
	struct hive_key *curkey = local_parent->hive_key;
	WERROR error;
	const char **elements = NULL;
	int el;

	if (path == NULL || path[0] == '\0') {
		return WERR_INVALID_PARAM;
	}

	orig = talloc_strdup(mem_ctx, path);
	W_ERROR_HAVE_NO_MEMORY(orig);
	curbegin = orig;
	curend = strchr(orig, '\\');

	if (local_parent->path.elements != NULL) {
		elements = talloc_array(mem_ctx, const char *,
					str_list_length(local_parent->path.elements) + 1);
		W_ERROR_HAVE_NO_MEMORY(elements);
		for (el = 0; local_parent->path.elements[el] != NULL; el++) {
			elements[el] = talloc_reference(elements,
							local_parent->path.elements[el]);
		}
		elements[el] = NULL;
	} else {
		elements = NULL;
		el = 0;
	}

	do {
		if (curend != NULL)
			*curend = '\0';
		elements = talloc_realloc(mem_ctx, elements, const char *, el+2);
		W_ERROR_HAVE_NO_MEMORY(elements);
		elements[el] = talloc_strdup(elements, curbegin);
		W_ERROR_HAVE_NO_MEMORY(elements[el]);
		el++;
		elements[el] = NULL;
		error = hive_get_key_by_name(mem_ctx, curkey,
					     curbegin, &curkey);
		if (W_ERROR_EQUAL(error, WERR_BADFILE)) {
			error = hive_key_add_name(mem_ctx, curkey, curbegin,
						  key_class, security,
						  &curkey);
		}
		if (!W_ERROR_IS_OK(error)) {
			DEBUG(2, ("Open/Creation of key %s failed: %s\n",
				curbegin, win_errstr(error)));
			talloc_free(orig);
			return error;
		}
		if (curend == NULL)
			break;
		curbegin = curend + 1;
		curend = strchr(curbegin, '\\');
	} while (curbegin[0] != '\0');
	talloc_free(orig);

	*result = reg_import_hive_key(local_parent->global.context, curkey,
				      local_parent->path.predefined_key,
				      talloc_steal(curkey, elements));

	return WERR_OK;
}

static WERROR local_set_value(struct registry_key *key, const char *name,
			      uint32_t type, const DATA_BLOB data)
{
	struct local_key *local = (struct local_key *)key;

	if (name == NULL) {
		return WERR_INVALID_PARAM;
	}

	return hive_key_set_value(local->hive_key, name, type, data);
}

static WERROR local_get_value(TALLOC_CTX *mem_ctx,
			      const struct registry_key *key,
			      const char *name, uint32_t *type, DATA_BLOB *data)
{
	const struct local_key *local = (const struct local_key *)key;

	if (name == NULL) {
		return WERR_INVALID_PARAM;
	}

	return hive_get_value(mem_ctx, local->hive_key, name, type, data);
}

static WERROR local_enum_value(TALLOC_CTX *mem_ctx,
			       const struct registry_key *key, uint32_t idx,
			       const char **name,
			       uint32_t *type,
			       DATA_BLOB *data)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_get_value_by_index(mem_ctx, local->hive_key, idx,
				       name, type, data);
}

static WERROR local_delete_key(TALLOC_CTX *mem_ctx, struct registry_key *key,
			       const char *name)
{
	const struct local_key *local = (const struct local_key *)key;

	if (name == NULL) {
		return WERR_INVALID_PARAM;
	}

	return hive_key_del(mem_ctx, local->hive_key, name);
}

static WERROR local_delete_value(TALLOC_CTX *mem_ctx, struct registry_key *key,
				 const char *name)
{
	const struct local_key *local = (const struct local_key *)key;

	if (name == NULL) {
		return WERR_INVALID_PARAM;
	}

	return hive_key_del_value(mem_ctx, local->hive_key, name);
}

static WERROR local_flush_key(struct registry_key *key)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_key_flush(local->hive_key);
}

static WERROR local_get_key_info(TALLOC_CTX *mem_ctx,
				 const struct registry_key *key,
				 const char **classname,
				 uint32_t *num_subkeys,
				 uint32_t *num_values,
				 NTTIME *last_change_time,
				 uint32_t *max_subkeynamelen,
				 uint32_t *max_valnamelen,
				 uint32_t *max_valbufsize)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_key_get_info(mem_ctx, local->hive_key,
				 classname, num_subkeys, num_values,
				 last_change_time, max_subkeynamelen, 
				 max_valnamelen, max_valbufsize);
}
static WERROR local_get_sec_desc(TALLOC_CTX *mem_ctx, 
				 const struct registry_key *key, 
				 struct security_descriptor **security)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_get_sec_desc(mem_ctx, local->hive_key, security);
}
static WERROR local_set_sec_desc(struct registry_key *key, 
				 const struct security_descriptor *security)
{
	const struct local_key *local = (const struct local_key *)key;

	return hive_set_sec_desc(local->hive_key, security);
}
const static struct registry_operations local_ops = {
	.name = "local",
	.open_key = local_open_key,
	.get_predefined_key = local_get_predefined_key,
	.enum_key = local_enum_key,
	.create_key = local_create_key,
	.set_value = local_set_value,
	.get_value = local_get_value,
	.enum_value = local_enum_value,
	.delete_key = local_delete_key,
	.delete_value = local_delete_value,
	.flush_key = local_flush_key,
	.get_key_info = local_get_key_info,
	.get_sec_desc = local_get_sec_desc,
	.set_sec_desc = local_set_sec_desc,
};

WERROR reg_open_local(TALLOC_CTX *mem_ctx, struct registry_context **ctx)
{
	struct registry_local *ret = talloc_zero(mem_ctx,
						 struct registry_local);

	W_ERROR_HAVE_NO_MEMORY(ret);

	ret->ops = &local_ops;

	*ctx = (struct registry_context *)ret;

	return WERR_OK;
}

WERROR reg_mount_hive(struct registry_context *rctx,
		      struct hive_key *hive_key,
		      uint32_t key_id,
		      const char **elements)
{
	struct registry_local *reg_local = talloc_get_type(rctx,
							   struct registry_local);
	struct mountpoint *mp;
	unsigned int i = 0;

	mp = talloc(rctx, struct mountpoint);
	W_ERROR_HAVE_NO_MEMORY(mp);
	mp->path.predefined_key = key_id;
	mp->prev = mp->next = NULL;
	mp->key = hive_key;
	if (elements != NULL && str_list_length(elements) != 0) {
		mp->path.elements = talloc_array(mp, const char *,
						 str_list_length(elements));
		W_ERROR_HAVE_NO_MEMORY(mp->path.elements);
		for (i = 0; elements[i] != NULL; i++) {
			mp->path.elements[i] = talloc_reference(mp->path.elements,
								elements[i]);
		}
		mp->path.elements[i] = NULL;
	} else {
		mp->path.elements = NULL;
	}

	DLIST_ADD(reg_local->mountpoints, mp);

	return WERR_OK;
}
