dsengine.c   [plain text]


/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <NetInfo/dsengine.h>
#include <NetInfo/dsutil.h>
#include <NetInfo/dsx500.h>
#include <NetInfo/dsindex.h>
#include <NetInfo/utf-8.h>

/*
 * N.B.  This is also defined in netinfod/ni_file.c
 */
#define ORDERLIST_KEY "ni_attribute_order"

/*
 * Creates and opens a new data store with the given pathname.
 */
dsstatus
dsengine_new(dsengine **s, char *name, u_int32_t flags)
{
	dsstatus status;
	dsstore *x;
	dsrecord *r;

	if (name == NULL) return DSStatusInvalidStore;

	status = dsstore_new(&x, name, flags);
	if (status != DSStatusOK) return status;

	*s = (dsengine *)malloc(sizeof(dsengine));
	(*s)->store = x;
	(*s)->delegate = NULL;
	(*s)->private = NULL;

	/* create root if necessary */
	r = dsstore_fetch(x, 0);
	if (r == NULL)
	{
		r = dsrecord_new();
		r->super = 0;

		status = dsstore_save((*s)->store, r);
	}
	
	dsrecord_release(r);
	return status;
}

/*
 * Opens a data store with the given pathname.
 */
dsstatus
dsengine_open(dsengine **s, char *name, u_int32_t flags)
{
	dsstatus status;
	dsstore *x;

	if (name == NULL) return DSStatusInvalidStore;

	status = dsstore_open(&x, name, flags);
	if (status != DSStatusOK) return status;

	*s = (dsengine *)malloc(sizeof(dsengine));
	(*s)->store = x;

	return status;
}

/*
 * Closes the data store.
 */
dsstatus
dsengine_close(dsengine *s)
{
	if (s == NULL) return DSStatusOK;
	if (s->store == NULL) return DSStatusInvalidStore;

	dsstore_close(s->store);
	free(s);

	return DSStatusOK;
}

/*
 * Autheticate.
 */
dsstatus
dsengine_authenticate(dsengine *s, dsdata *user, dsdata *password)
{
	if (s == NULL) return DSStatusOK;
	if (s->store == NULL) return DSStatusInvalidStore;
	return dsstore_authenticate(s->store, user, password);
}

/*
 * Detach a child (specified by its ID) from a record.
 * This just edits the record's list of children, and does not remove the
 * child record from the data store.  You should never do this!  This
 * routine is provided to allow emergency repairs to a corrupt data store.
 */
dsstatus
dsengine_detach(dsengine *s, dsrecord *r, u_int32_t dsid)
{
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;
	if (r == NULL) return DSStatusInvalidRecord;

	/* Remove child's dsid */
	dsrecord_remove_sub(r, dsid);

	/* Reset parent's index */
	dsindex_free(r->index);
	r->index = NULL;

	status = dsstore_save(s->store, r);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Attach a child (specified by its ID) to a record.
 * This just edits the record's list of children, and does not create the
 * child record in the data store.  You should never do this!  This
 * routine is provided to allow emergency repairs to a corrupt data store.
 */
dsstatus
dsengine_attach(dsengine *s, dsrecord *r, u_int32_t dsid)
{
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;
	if (r == NULL) return DSStatusInvalidRecord;

	/* Add child's dsid */
	dsrecord_append_sub(r, dsid);

	/* Reset parent's index */
	dsindex_free(r->index);
	r->index = NULL;

	status = dsstore_save(s->store, r);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Sets a record's parent.  This is for emergency repairs.
 */
dsstatus
dsengine_set_parent(dsengine *s, dsrecord *r, u_int32_t dsid)
{
	dsstatus status;
	dsrecord *parent;

	if (s == NULL) return DSStatusInvalidStore;
	if (r == NULL) return DSStatusInvalidRecord;

	parent = dsstore_fetch(s->store, dsid);
	if (parent == NULL) return DSStatusInvalidRecord;

	/* Reset parent's index */
	dsindex_free(parent->index);
	parent->index = NULL;

	dsrecord_release(parent);

	r->super = dsid;
	status = dsstore_save(s->store, r);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Adds a new record as a child of another record (specified by its ID).
 */
dsstatus
dsengine_create(dsengine *s, dsrecord *r, u_int32_t dsid)
{
	dsrecord *parent;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;
	if (r == NULL) return DSStatusInvalidRecord;

	parent = dsstore_fetch(s->store, dsid);
	if (parent == NULL) return DSStatusInvalidPath;

	r->super = dsid;

	status = dsstore_save(s->store, r);
	if (status != DSStatusOK)
	{
		dsrecord_release(parent);
		return status;
	}

	dsrecord_append_sub(parent, r->dsid);
	dsindex_insert_record(parent->index, r);

	if (s->store->flags & DSSTORE_FLAGS_REMOTE_NETINFO)
		status = DSStatusOK;
	else
		status = dsstore_save(s->store, parent);

	dsrecord_release(parent);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Fetch a record specified by its ID.
 */
dsstatus
dsengine_fetch(dsengine *s, u_int32_t dsid, dsrecord **r)
{
	dsdata *key;

	if (s == NULL) return DSStatusInvalidStore;

	*r = dsstore_fetch(s->store, dsid);
	if (*r == NULL) return DSStatusInvalidPath;

	/*
	 * Remove the ordering meta-attribute - it is private to netinfod
	 * (which doesn't use dsengine).  If a client edits a record and
	 * saves it using dsengine_save or dsengine_save_attribute,
	 * the record switches to "native" ordering.
	 */
	key = cstring_to_dsdata(ORDERLIST_KEY);
	dsrecord_remove_key(*r, key, SELECT_META_ATTRIBUTE);
	dsdata_release(key);
	 
	return DSStatusOK;
}

/*
 * Fetch a list of records specified by ID.
 */
dsstatus
dsengine_fetch_list(dsengine *s, u_int32_t count, u_int32_t *dsid,
	dsrecord ***l)
{
	dsstatus status;
	u_int32_t i, j;
	dsrecord **list;

	if (s == NULL) return DSStatusInvalidStore;

	if ((count == 0) || (dsid == NULL))
	{
		*l = NULL;
		return DSStatusOK;
	}

	list = (dsrecord **)malloc(count * sizeof(dsrecord *));
	*l = list;

	status = DSStatusOK;
	for (i = 0; i < count; i++)
	{
		status = dsengine_fetch(s, dsid[i], &(list[i]));
		if (status != DSStatusOK) break;
	}

	if (status != DSStatusOK)
	{
		for (j = 0; j < i; j++) dsrecord_release(list[j]);
		free(list);
		*l = NULL;
		return status;
	}

	return DSStatusOK;
}

/*
 * Save a record.
 */
dsstatus
dsengine_save(dsengine *s, dsrecord *r)
{
	u_int32_t i;
	dsrecord *parent;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	i = dsstore_record_super(s->store, r->dsid);
	if (i == IndexNull) return DSStatusInvalidPath;

	parent = dsstore_fetch(s->store, i);
	if (parent == NULL)
	{
		/* XXX Parent doesn't exist! */
		return DSStatusInvalidPath;
	}

	/* Update child in parent's index */
	dsindex_delete_dsid(parent->index, r->dsid);
	dsindex_insert_record(parent->index, r);

	dsrecord_release(parent);
	status = dsstore_save(s->store, r);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Save a record quickly.
 */
dsstatus
dsengine_save_fast(dsengine *s, dsrecord *r)
{
	u_int32_t i;
	dsrecord *parent;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	i = dsstore_record_super(s->store, r->dsid);
	if (i == IndexNull) return DSStatusInvalidPath;

	parent = dsstore_fetch(s->store, i);
	if (parent == NULL)
	{
		/* XXX Parent doesn't exist! */
		return DSStatusInvalidPath;
	}

	/* Update child in parent's index */
	dsindex_delete_dsid(parent->index, r->dsid);
	dsindex_insert_record(parent->index, r);

	dsrecord_release(parent);
	status = dsstore_save_fast(s->store, r, 0);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Modify a record attribute. 
 */
dsstatus
dsengine_save_attribute(dsengine *s, dsrecord *r, dsattribute *a, u_int32_t asel)
{
	u_int32_t i;
	dsrecord *parent;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	i = dsstore_record_super(s->store, r->dsid);
	if (i == IndexNull) return DSStatusInvalidPath;

	parent = dsstore_fetch(s->store, i);
	if (parent == NULL)
	{
		/* XXX Parent doesn't exist! */
		return DSStatusInvalidPath;
	}

	/* Update child in parent's index */
	dsindex_delete_dsid(parent->index, r->dsid);
	dsindex_insert_record(parent->index, r);

	dsrecord_release(parent);
	status = dsstore_save_attribute(s->store, r, a, asel);
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Remove a record from the data store.
 */
dsstatus
dsengine_remove(dsengine *s, u_int32_t dsid)
{
	u_int32_t i, n, *kids;
	dsrecord *r, *parent;
	dsstatus status;
	char *ns;

	if (s == NULL) return DSStatusInvalidStore;

	r = dsstore_fetch(s->store, dsid);
	if (r == NULL) return DSStatusOK;
	
	/* PROTECT FROM MULTIPLE NOTIFICATIONS */
	ns = s->store->notification_name;
	s->store->notification_name = NULL;

	if (r->sub_count > 0)
	{
		n = r->sub_count;
		kids = (u_int32_t *)malloc(n * sizeof(u_int32_t));

		for (i = 0; i < n; i++) kids[i] = r->sub[i];

		for (i = 0; i < n; i++)
		{
			status = dsengine_remove(s, kids[i]);
			if (status != DSStatusOK)
			{
				free(kids);
				s->store->notification_name = ns;
				return status;
			}
		}

		free(kids);
	}

	i = dsstore_record_super(s->store, dsid);
	if (i == IndexNull)
	{
		s->store->notification_name = ns;
		return DSStatusInvalidPath;
	}

	parent = dsstore_fetch(s->store, i);
	if (parent == NULL)
	{
		/* XXX Parent doesn't exist! */
		s->store->notification_name = ns;
		return DSStatusInvalidPath;
	}

	/* Remove the record from the parent's sub list */
	dsrecord_remove_sub(parent, dsid);

	/* Remove child from parent's index */
	dsindex_delete_dsid(parent->index, dsid);

	if (s->store->flags & DSSTORE_FLAGS_REMOTE_NETINFO)
		status = DSStatusOK;
	else
		status = dsstore_save(s->store, parent);

	dsrecord_release(parent);
	if (status != DSStatusOK)
	{
		/* XXX can't save changes to parent! */
		s->store->notification_name = ns;
		return status;
	}

	status = dsstore_remove(s->store, dsid);

	s->store->notification_name = ns;
	if (status == DSStatusOK) dsstore_notify(s->store);

	return status;
}

/*
 * Move a record to a new parent.
 */
dsstatus
dsengine_move(dsengine *s, u_int32_t dsid, u_int32_t pdsid)
{
	u_int32_t old;
	dsrecord *r, *child;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;
	if (dsid == 0) return DSStatusInvalidRecordID;
	
	child = dsstore_fetch(s->store, dsid);
	if (child == NULL) return DSStatusInvalidPath;
	if (child->super == pdsid)
	{
		dsrecord_release(child);
		return DSStatusOK;
	}

	old = child->super;
	child->super = pdsid;
	status = dsstore_save(s->store, child);
	if (status != DSStatusOK)
	{
		/* XXX can't save changes to record! */
		dsrecord_release(child);
		return status;
	}

	r = dsstore_fetch(s->store, old);
	if (r == NULL)
	{
		dsrecord_release(child);
		return DSStatusInvalidPath;
	}

	dsrecord_remove_sub(r, dsid);

	/* Remove child from original parent's index */
	dsindex_delete_dsid(r->index, dsid);

	status = dsstore_save(s->store, r);
	dsrecord_release(r);
	if (status != DSStatusOK)
	{
		/* XXX can't save changes to original parent! */
		dsrecord_release(child);
		return status;
	}

	r = dsstore_fetch(s->store, pdsid);
	if (r == NULL) return DSStatusInvalidPath;

	dsrecord_append_sub(r, dsid);

	/* Add child to new parent's index */
	dsindex_insert_record(r->index, child);
	dsrecord_release(child);

	status = dsstore_save(s->store, r);
	dsrecord_release(r);
	if (status != DSStatusOK)
	{
		/* XXX can't save changes to original parent! */
		return status;
	}

	dsstore_notify(s->store);

	return DSStatusOK;
}
	
/*
 * Copy a record to a new parent.
 */
dsstatus
dsengine_copy(dsengine *s, u_int32_t dsid, u_int32_t pdsid)
{
	dsrecord *r, *x;
	u_int32_t *kids, i, count;
	dsstatus status;
	char *ns;

	if (s == NULL) return DSStatusInvalidStore;
	
	r = dsstore_fetch(s->store, dsid);
	if (r == NULL) return DSStatusInvalidPath;

	x = dsrecord_copy(r);
	dsrecord_release(r);

	count = x->sub_count;
	kids = x->sub;
	x->sub_count = 0;
	x->sub = NULL;

	/* PROTECT FROM MULTIPLE NOTIFICATIONS */
	ns = s->store->notification_name;
	s->store->notification_name = NULL;

	status = dsengine_create(s, x, pdsid);
	if (status != DSStatusOK)
	{
		/* XXX Can't add new record! */
		dsrecord_release(x);
		s->store->notification_name = ns;
		return status;
	}
		
	for (i = 0; i < count; i++)
	{
		status = dsengine_copy(s, kids[i], x->dsid);
		if (status != DSStatusOK)
		{
			/* XXX Can't add child! */
			dsrecord_release(x);
			s->store->notification_name = ns;
			return status;
		}
	}

	s->store->notification_name = ns;
	dsstore_notify(s->store);

	return DSStatusOK;
}

/*
 * Warning! count is overloaded - if we are searching for the
 * first match (dsengine_find_pattern()) then count is the output parameter
 * returning the dsid of the matching record.
 */
static dsstatus
_pattern_searcher(dsengine *s, u_int32_t dsid, dsrecord *pattern, u_int32_t scopemin, u_int32_t scopemax, u_int32_t **match, u_int32_t *count, u_int32_t findall)
{
	dsrecord *r;
	u_int32_t i, x;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	r = dsstore_fetch(s->store, dsid);
	if (r == NULL) return DSStatusInvalidPath;
	
	if (findall == 0) *count = (u_int32_t)-1;

	if (scopemin == 0)
	{
		if (dsrecord_match(r, pattern) == 1)
		{
			if (findall == 0)
			{
				*count = dsid;
				dsrecord_release(r);
				return DSStatusOK;
			}

			if (*count == 0) *match = (u_int32_t *)malloc(sizeof(u_int32_t));
			else *match = (u_int32_t *)realloc(*match, (1 + *count) * sizeof(u_int32_t));
			(*match)[*count] = dsid;
			*count = *count + 1;
		}
	}
	else scopemin--;

	if (scopemax == 0)
	{
		dsrecord_release(r);
		return DSStatusOK;
	}

	x = scopemax - 1;
	if (scopemax == (u_int32_t)-1) x = scopemax;

	for (i = 0; i < r->sub_count; i++)
	{
		status = _pattern_searcher(s, r->sub[i], pattern, scopemin, x, match, count, findall);
		if (status  != DSStatusOK)
		{
			dsrecord_release(r);
			return status;
		}

		if ((findall == 0) && (*count != (u_int32_t)-1)) break;
	}

	dsrecord_release(r);
	return DSStatusOK;
}

/*
 * Search starting at a given record (specified by ID dsid) for all
 * records matching the attributes of the "pattern" record.
 * Scope is n for n-level deep search. Use (u-int32_t)-1 for unlimited
 * depth.  Scope 0 tests the given node against the pattern.
 * Search with null pattern or null attributes matchs all.
 * Search with null pattern or attributes and scope 1 returns all child IDs.
 */
dsstatus
dsengine_search_pattern(dsengine *s, u_int32_t dsid, dsrecord *pattern, u_int32_t scopemin, u_int32_t scopemax, u_int32_t **match, u_int32_t *count)
{
	*count = 0;	
	return _pattern_searcher(s, dsid, pattern, scopemin, scopemax, match, count, 1);
}

/*
 * Find first match.
 */
dsstatus
dsengine_find_pattern(dsengine *s, u_int32_t dsid, dsrecord *pattern, u_int32_t scopemin, u_int32_t scopemax, u_int32_t *match)
{
	return _pattern_searcher(s, dsid, pattern, scopemin, scopemax, NULL, match, 0);
}

/*
 * Warning! count is overloaded - if we are searching for the
 * first match (dsengine_find_pattern()) then count is the output parameter
 * returning the dsid of the matching record.
 */
static dsstatus
_filter_searcher(dsengine *s, u_int32_t dsid, dsfilter *f, u_int32_t scopemin, u_int32_t scopemax, u_int32_t **match, u_int32_t *count, u_int32_t findall)
{
	dsrecord *r;
	u_int32_t i, x;
	dsstatus status;
	Logic3 eval;

	if (s == NULL) return DSStatusInvalidStore;

	r = dsstore_fetch(s->store, dsid);
	if (r == NULL) return DSStatusInvalidPath;
	
	if (findall == 0) *count = (u_int32_t)-1;

	if (scopemin == 0)
	{
		if (s->delegate != NULL)
			eval = (s->delegate)(f, r, s->private);
		else
			eval = dsfilter_test(f, r);

		if (eval == L3True)
		{
			if (findall == 0)
			{
				*count = dsid;
				dsrecord_release(r);
				return DSStatusOK;
			}

			if (*count == 0) *match = (u_int32_t *)malloc(sizeof(u_int32_t));
			else *match = (u_int32_t *)realloc(*match, (1 + *count) * sizeof(u_int32_t));
			(*match)[*count] = dsid;
			*count = *count + 1;
		}
	}
	else scopemin--;

	if (scopemax == 0)
	{
		dsrecord_release(r);
		return DSStatusOK;
	}

	x = scopemax - 1;
	if (scopemax == (u_int32_t)-1) x = scopemax;

	for (i = 0; i < r->sub_count; i++)
	{
		status = _filter_searcher(s, r->sub[i], f, scopemin, x, match, count, findall);
		if (status  != DSStatusOK)
		{
			dsrecord_release(r);
			return status;
		}

		if ((findall == 0) && (*count != (u_int32_t)-1)) break;
	}

	dsrecord_release(r);
	return DSStatusOK;
}

dsstatus
dsengine_search_filter(dsengine *s, u_int32_t dsid, dsfilter *f, u_int32_t scopemin, u_int32_t scopemax, u_int32_t **match, u_int32_t *count)
{
	*count = 0;	
	return _filter_searcher(s, dsid, f, scopemin, scopemax, match, count, 1);
}

/*
 * Find first match.
 */
dsstatus
dsengine_find_filter(dsengine *s, u_int32_t dsid, dsfilter *f, u_int32_t scopemin, u_int32_t scopemax, u_int32_t *match)
{
	return _filter_searcher(s, dsid, f, scopemin, scopemax, NULL, match, 0);
}

/*
 * Find the first child record of a given record that has an attribute with "key" and "val".
 */
dsstatus
dsengine_match(dsengine *s, u_int32_t dsid, dsdata *key, dsdata *val, u_int32_t *match)
{
	return dsstore_match(s->store, dsid, key, val, SELECT_ATTRIBUTE, match);
}

static dsstatus
dsengine_pathutil(dsengine *s, u_int32_t dsid, dsrecord *path, u_int32_t *match, u_int32_t *create)
{
	u_int32_t i, n, c, do_create;
	dsstatus status;
	dsattribute *a;
	dsdata *k, *v;
	dsrecord *r;
	dsdata *keyname, *dot, *dotdot;

	do_create = *create;
	*create = 0;

	*match = (u_int32_t)-1;

	if (s == NULL) return DSStatusInvalidStore;

	if (path == NULL)
	{
		*match = dsid;
		return DSStatusOK;
	}
	
	/* Special cases: */
	/* key="name" val="."  matches this  record. */
	/* key="name" val=".." matches super record. */
	keyname = cstring_to_dsdata("name");
	dot = cstring_to_dsdata(".");
	dotdot = cstring_to_dsdata("..");

	n = dsid;
	for (i = 0; i < path->count; i++)
	{
		a = path->attribute[i];
		k = a->key;
		v = NULL;

		if (a->count > 0) v = a->value[0];

		if (dsdata_equal(k, keyname))
		{
			if (dsdata_equal(v, dot)) continue;
			if (dsdata_equal(v, dotdot))
			{
				r = dsstore_fetch(s->store, n);
				if (r == NULL)
				{
					dsdata_release(keyname);
					dsdata_release(dot);
					dsdata_release(dotdot);
					return DSStatusInvalidPath;
				}
				n = r->super;
				dsrecord_release(r);
				continue;
			}
		}

		status = dsengine_match(s, n, k, v, &c);
		if (status != DSStatusOK)
		{
			dsdata_release(keyname);
			dsdata_release(dot);
			dsdata_release(dotdot);
			return status;
		}

		if (c == (u_int32_t)-1)
		{
			if (do_create == 0)
			{
				dsdata_release(keyname);
				dsdata_release(dot);
				dsdata_release(dotdot);
				return DSStatusInvalidPath;
			}
			else
			{
				/* Create the path component */
				*create = 1;
				r = dsrecord_new();
				dsrecord_append_attribute(r, a, SELECT_ATTRIBUTE);
				status = dsengine_create(s, r, n);
				c = r->dsid;
				dsrecord_release(r);
				if (status != DSStatusOK)
				{
					dsdata_release(keyname);
					dsdata_release(dot);
					dsdata_release(dotdot);
					return status;
				}
			}
		}
		n = c;
	}

	*match = n;

	dsdata_release(keyname);
	dsdata_release(dot);
	dsdata_release(dotdot);

	return DSStatusOK;
}

/*
 * Returns a list of dsids, representing the path the given record to root.
 * The final dsid in the list will always be 0 (root).
 */
dsstatus
dsengine_path(dsengine *s, u_int32_t dsid, u_int32_t **list)
{
	u_int32_t i, n;

	i = dsid;

	*list = (u_int32_t *)malloc(sizeof(u_int32_t));
	(*list)[0] = i;
	n = 1;

	while (i != 0)
	{
		i = dsstore_record_super(s->store, i);
		if (i == IndexNull) return DSStatusReadFailed;
	
		*list = (u_int32_t *)realloc(*list, (n + 1) * sizeof(u_int32_t));
		(*list)[n] = i;
		n++;
	}

	return DSStatusOK;
}

/*
 * Find a record following a list of key=value pairs, which are given as
 * the attributes of a "path" record.
 */
dsstatus
dsengine_pathmatch(dsengine *s, u_int32_t dsid, dsrecord *path, u_int32_t *match)
{
	int x;

	x = 0;
	return dsengine_pathutil(s, dsid, path, match, &x);
}

/*
 * Create a path following a list of key=value pairs, which are given as
 * the attributes of a "path" record.  Returns dsid of last directory in the
 * chain of created directories.  Follows existing directories if they exist.
 */
dsstatus
dsengine_pathcreate(dsengine *s, u_int32_t dsid, dsrecord *path, u_int32_t *match)
{
	dsstatus status;
	char *ns;
	u_int32_t create;

	/* PROTECT FROM MULTIPLE NOTIFICATIONS */
	ns = s->store->notification_name;
	s->store->notification_name = NULL;

	create = 1;
	status = dsengine_pathutil(s, dsid, path, match, &create);

	s->store->notification_name = ns;

	if ((status == DSStatusOK) && (create != 0))
	{
		/* Send notification */
		dsstore_notify(s->store);
	}

	return status;
}

/*
 * Returns a list of dsids and values for a given attribute key.
 * Results are returned in a dsrecord.  Keys are dsids encoded using
 * int32_to_dsdata().  Values are attribute values for the
 * corresponding record.
 */
dsstatus
dsengine_list(dsengine *s, u_int32_t dsid, dsdata *key, u_int32_t scopemin, u_int32_t scopemax, dsrecord **list)
{
	dsrecord *r;
	dsattribute *a;
	u_int32_t i, j, *matches, len, x;
	dsdata *n;
	dsstatus status;
	
	if (s == NULL) return DSStatusInvalidStore;
	if (list == NULL) return DSStatusFailed;

	*list = NULL;

	if ((scopemin == 1) && (scopemax == 1))
	{
		return dsstore_list(s->store, dsid, key, SELECT_ATTRIBUTE, list);
	}

	if (key == NULL) return DSStatusInvalidKey;

	r = dsrecord_new();
	a = dsattribute_new(key);
	dsrecord_append_attribute(r, a, SELECT_ATTRIBUTE);
	dsattribute_release(a);

	status = dsengine_search_pattern(s, dsid, r, scopemin, scopemax, &matches, &len);
	dsrecord_release(r);
	if (status != DSStatusOK) return status;

	*list = dsrecord_new();
	n = cstring_to_dsdata("key");
	a = dsattribute_new(n);
	dsattribute_append(a, key);
	dsrecord_append_attribute(*list, a, SELECT_META_ATTRIBUTE);
	dsdata_release(n);
	dsattribute_release(a);

	for (i = 0; i < len; i++)
	{
		status = dsengine_fetch(s, matches[i], &r);
		if (status != DSStatusOK)
		{
			dsrecord_release(*list);
			return status;
		}

		x = dsrecord_attribute_index(r, key, SELECT_ATTRIBUTE);
		if (x == IndexNull)
		{
			dsrecord_release(r);
			continue;
		}

		n = int32_to_dsdata(r->dsid);
		a = dsattribute_new(n);
		dsdata_release(n);

		a->count = r->attribute[x]->count;

		if (a->count > 0)
			a->value = (dsdata **)malloc(a->count * sizeof(dsdata *));

		for (j = 0; j < a->count; j++)
			a->value[j] = dsdata_retain(r->attribute[x]->value[j]);

		dsrecord_append_attribute(*list, a, SELECT_ATTRIBUTE);
		dsattribute_release(a);
		dsrecord_release(r);
	}

	if (len > 0) free(matches);
	
	return DSStatusOK;
}

dsstatus
dsengine_netinfo_string_pathmatch(dsengine *s, u_int32_t dsid, char *path, u_int32_t *match)
{
	dsrecord *p;
	u_int32_t i, numeric;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	*match = dsid;

	if (path == NULL) return DSStatusOK;

	numeric = 1;
	for (i = 0; (numeric == 1) && (path[i] != '\0'); i++)
	{
		if ((path[i] < '0') || (path[i] > '9')) numeric = 0;
	}
	
	if (numeric == 1)
	{
		i = atoi(path);
		if ((i == 0) && (strcmp(path, "0")))
		{
			*match = (u_int32_t)-1;
			return DSStatusInvalidRecordID;
		}
		*match = i;
		return DSStatusOK;
	}

	p = dsutil_parse_netinfo_string_path(path);
	if (p == NULL) return DSStatusInvalidPath;

	if (path[0] == '/') *match = 0;
	status = dsengine_pathmatch(s, *match, p, match);
	dsrecord_release(p);

	return status;
}

dsstatus
dsengine_netinfo_string_pathcreate(dsengine *s, u_int32_t dsid, char *path, u_int32_t *match)
{
	dsrecord *p;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	*match = dsid;

	if (path == NULL) return DSStatusOK;

	p = dsutil_parse_netinfo_string_path(path);
	if (p == NULL) return DSStatusInvalidPath;

	if (path[0] == '/') *match = 0;
	status = dsengine_pathcreate(s, *match, p, match);
	dsrecord_release(p);

	return status;
}

char *
dsengine_netinfo_string_path(dsengine *s, u_int32_t dsid)
{
	char *p, *path, str[64], *x;
	u_int32_t i, len, plen, dirno;
	dsrecord *r;
	dsattribute *name;
	dsstatus status;
	dsdata *d;

	if (s == NULL) return NULL;

	if (dsid == 0)
	{
		path = malloc(2);
		path[0] = '/';
		path[1] = '\0';
		return path;
	}

	path = NULL;
	plen = 0;
	d = cstring_to_dsdata("name");

	i = dsid;
	while (i != 0)
	{
		dirno = 0;
		r = NULL;
		name = NULL;

		status = dsengine_fetch(s, i, &r);
		if (status != DSStatusOK)
		{
			dirno = 1;
			i = 0;
		}
		else
		{
			i = r->super;
			name = dsrecord_attribute(r, d, SELECT_ATTRIBUTE);
			if (name == NULL) dirno = 1;
			else if (name->count == 0) dirno = 1;
		}

		if ((dirno == 1) ||
			((x = dsdata_to_cstring(name->value[0])) == NULL))
		{
			if (r == NULL) sprintf(str, "dir:?");
			else sprintf(str, "dir:%u", r->dsid);
			x = str;
		}

		len = strlen(x);
		p = malloc(1 + len + plen + 1);
		if (path == NULL)
		{
			sprintf(p, "/%s", x);
		}
		else
		{
			sprintf(p, "/%s%s", x, path);
			free(path);
		}
		
		path = p;
		plen = strlen(path);

		dsattribute_release(name);
		dsrecord_release(r);
	}
	
	dsdata_release(d);

	return path;
}

/*
 * Written by Luke Howard 02-07-2000 to work with OpenLDAP back-netinfo.
 *
 * Here is the algorithm we use to construct a DN:
 *
 * for each directory in the name {
 *  if directory has "_rdn" attribute {
 *    use attribute named by value of rdn attribute as RDN.
 *  } else if directory has "name "attribute {
 *    use "name" as RDN
 *  } else {
 *    use DSID=%s as RDN
 *  }
 * }
 *
 * A remaining issue is dealing with the fact that NetInfo
 * names are case sensitive but X.500 names are not. We may
 * need to resort to DSID=%s for all RDNs.
 *
 * Also, multi-valued RDNs are not supported. We could support
 * them by having multiple values for the _rdn attribute (for
 * example, to form a DN cn=foo+sn=bar,c=US) but there are 
 * tricky canonicalization issues to be dealt with.
 *
 */
char *
dsengine_x500_string_path(dsengine *s, u_int32_t dsid)
{
	char *p, *path, str[64], *escaped;
	u_int32_t plen;
	dsrecord *r;
	dsattribute *rdn_attr, *name;
	dsstatus status;
	dsdata *rdn_key, *default_rdn_key, *name_rdn, *name_dsid;
	dsdata *x, tmp;

	if (s == NULL) return NULL;
	if (dsid == 0) return copyString("");

	path = NULL;
	plen = 0;

	default_rdn_key = cstring_to_dsdata("name");
	name_rdn = cstring_to_dsdata("rdn");
	name_dsid = casecstring_to_dsdata("DSID");

	status = dsengine_fetch(s, dsid, &r);
	if (status != DSStatusOK) return NULL;

	while (r->dsid != 0)
	{
		dsrecord *parent;

		rdn_attr = dsrecord_attribute(r, name_rdn, SELECT_META_ATTRIBUTE);
		if (rdn_attr == NULL || rdn_attr->count == 0)
		{
			rdn_key = dsdata_retain(default_rdn_key);
		}
		else
		{
			rdn_key = dsdata_retain(rdn_attr->value[0]);
		}
		dsattribute_release(rdn_attr);

		name = dsrecord_attribute(r, rdn_key, SELECT_ATTRIBUTE);
		if (name == NULL || name->count == 0)
		{
			/* No name, so let's use the directory ID. */
			dsdata_release(rdn_key);
			rdn_key = dsdata_retain(name_dsid);
		}

		escaped = escape_rdn(rdn_key);

		if (path == NULL)
		{
			plen = strlen(escaped) + 1;
			path = malloc(plen + 1);
			sprintf(path, "%s=", escaped);
		}
		else
		{
			plen += strlen(escaped) + 2;
			p = malloc(plen + 1);
			sprintf(p, "%s,%s=", path, escaped);
			free(path);
			path = p;
		}

		free(escaped);

		if (name == NULL || name->count == 0)
		{
			sprintf(str, "%u", r->dsid);
			tmp.type = DataTypeCStr;
			tmp.length = strlen(str) + 1;
			tmp.data = str;
			tmp.retain = 1;
			x = &tmp;
		}
		else
		{
			x = name->value[0];
		}

		escaped = escape_rdn(x);

		plen += strlen(escaped);
		p = malloc(plen + 1);
		sprintf(p, "%s%s", path, escaped);
		free(path);
		path = p;

		free(escaped);
		dsdata_release(rdn_key);
		dsattribute_release(name);

		status = dsengine_fetch(s, r->super, &parent);
		if (status != DSStatusOK)
		{
			dsrecord_release(r);
			dsdata_release(default_rdn_key);
			dsdata_release(name_rdn);
			dsdata_release(name_dsid);
			return NULL;
		}
		dsrecord_release(r);
		r = parent;
	}

	dsrecord_release(r);
	dsdata_release(default_rdn_key);
	dsdata_release(name_rdn);
	dsdata_release(name_dsid);

	return path;
}

/*
 * Get a record's parent dsid.
 */
dsstatus
dsengine_record_super(dsengine *s, u_int32_t dsid, u_int32_t *super)
{
	if (s == NULL) return DSStatusInvalidStore;

	*super = dsstore_record_super(s->store, dsid);
	if (*super == IndexNull) return DSStatusInvalidRecordID;
	return DSStatusOK;
}

/*
 * Get a record's version number.
 */
dsstatus
dsengine_record_version(dsengine *s, u_int32_t dsid, u_int32_t *version)
{
	if (s == NULL) return DSStatusInvalidStore;

	*version = dsstore_record_version(s->store, dsid);
	if (*version == IndexNull) return DSStatusInvalidRecordID;
	return DSStatusOK;
}

/*
 * Get a record's serial number.
 */
dsstatus
dsengine_record_serial(dsengine *s, u_int32_t dsid, u_int32_t *serial)
{
	if (s == NULL) return DSStatusInvalidStore;

	*serial = dsstore_record_serial(s->store, dsid);
	if (*serial == IndexNull) return DSStatusInvalidRecordID;
	return DSStatusOK;
}

/*
 * Get the dsid of the record with a given version number.
 */
dsstatus
dsengine_version_record(dsengine *s, u_int32_t version, u_int32_t *dsid)
{
	if (s == NULL) return DSStatusInvalidStore;

	*dsid = dsstore_version_record(s->store, version);
	if (*dsid == IndexNull) return DSStatusInvalidRecordID;
	return DSStatusOK;
}

/*
 * Get data store version number.
 */
dsstatus
dsengine_version(dsengine *s, u_int32_t *version)
{
	if (s == NULL) return DSStatusInvalidStore;

	*version = dsstore_version(s->store);
	if (*version == IndexNull) return DSStatusInvalidStore;
	return DSStatusOK;
}

/*
 * Get a record's version number, serial number, and parent's dsid.
 */
dsstatus dsengine_vital_statistics(dsengine *s, u_int32_t dsid, u_int32_t *version, u_int32_t *serial, u_int32_t *super)
{
	if (s == NULL) return DSStatusInvalidStore;
	return dsstore_vital_statistics(s->store, dsid, version, serial, super);
}

void
dsengine_flush_cache(dsengine *s)
{
	if (s == NULL) return;
	dsstore_flush_cache(s->store);
}

dsstatus
dsengine_x500_string_pathmatch(dsengine *s, u_int32_t dsid, char *path, u_int32_t *match)
{
	dsstatus status;
	dsrecord *p;
	char **exploded;
	char *key, *value;

	if (s == NULL) return DSStatusInvalidStore;

	*match = dsid;

	if (path == NULL) return DSStatusOK;

	/*
	 * As a short cut, we let clients use the special base DN
	 *     DSID=<dsid>,<arbitary DN info>
	 * to refer to a specific directory ID.
	 */
	exploded = dsx500_explode_dn(path, 0);
	if (exploded != NULL)
	{
		if (exploded[0] != NULL)
		{
			key = dsx500_rdn_attr_type(exploded[0]);
			value = dsx500_rdn_attr_value(exploded[0]);

			if ((key != NULL) && (value != NULL) && (strcasecmp(key, "DSID") == 0))
			{
				*match = strtoul(value, (char **)NULL, 10);
				free(key);
				free(value);
				freeList(exploded);

				return DSStatusOK;
			}

			if (key != NULL) free(key);
			if (value != NULL) free(value);
		}
		freeList(exploded);
	}

	p = dsutil_parse_x500_string_path(path);
	if (p == NULL) return DSStatusInvalidPath;

	status = dsengine_pathmatch(s, dsid, p, match);
	dsrecord_release(p);

	return status;
}

dsstatus
dsengine_x500_string_pathcreate(dsengine *s, u_int32_t dsid, char *path, u_int32_t *match)
{
	dsrecord *p;
	dsstatus status;

	if (s == NULL) return DSStatusInvalidStore;

	*match = dsid;

	if (path == NULL) return DSStatusOK;

	p = dsutil_parse_x500_string_path(path);
	if (p == NULL) return DSStatusInvalidPath;

	status = dsengine_pathcreate(s, dsid, p, match);
	dsrecord_release(p);

	return status;
}

dsdata *
dsengine_map_name(dsengine *e, dsdata *name, u_int32_t intype)
{
	if (e == NULL) return NULL;

	/* This in principle only works if name types and flags are in sync. */
	return dsengine_convert_name(e, name, intype, (e->store->flags & DSENGINE_FLAGS_NAMING_MASK));
}

static dsstatus
find_user(dsengine *s, u_int32_t keyType, dsdata *name, u_int32_t *match)
{
	dsrecord *r;
	dsdata *k;
	dsattribute *a;
	dsstatus status;
	u_int32_t users;

	/* get the /users directory for future use */
	status = dsengine_netinfo_string_pathmatch(s, 0, "users", &users);
	if (status != DSStatusOK) return status;

	r = dsrecord_new();
	k = cstring_to_dsdata(keyType == NameTypeUserName ? "name" : "principal");
	a = dsattribute_new(k);
	dsattribute_insert(a, name, 0);
	dsrecord_append_attribute(r, a, SELECT_ATTRIBUTE);

	status = dsengine_find_pattern(s, users, r, 1, 1, match);

	dsattribute_release(a);
	dsdata_release(k);
	dsrecord_release(r);

	return status;
}

static dsdata *
read_user_attribute(dsengine *s, char *attr, u_int32_t match)
{
	dsrecord *r;
	dsdata *k, *d;
	dsattribute *a;
	dsstatus status;
	u_int32_t users, p;

	/* get the /users directory for future use */
	status = dsengine_netinfo_string_pathmatch(s, 0, "users", &users);
	if (status != DSStatusOK) return NULL;

	status = dsengine_record_super(s, match, &p);
	if (status != DSStatusOK) return NULL;

	if (p != users) return NULL;

	status = dsengine_fetch(s, match, &r);
	if (status != DSStatusOK) return NULL;

	k = cstring_to_dsdata(attr);
	a = dsrecord_attribute(r, k, SELECT_ATTRIBUTE);
	d = dsattribute_value(a, 0);

	dsattribute_release(a);
	dsdata_release(k);
	dsrecord_release(r);

	return d;
}

dsdata *
dsengine_convert_name(dsengine *e, dsdata *name, u_int32_t intype, u_int32_t desiredtype)
{
	/* Name map switcheroo. */
	dsdata *d;
	char *str, *tname;
	u_int32_t match;

	if (name == NULL) return NULL;

	d = NULL;
	str = NULL;
	tname = NULL;
	match = 0;

	switch (name->type)
	{
		case DataTypeCStr:
		case DataTypeCaseCStr:
		case DataTypeUTF8Str:
		case DataTypeCaseUTF8Str:
			tname = dsdata_to_utf8string(name);
			break;
		case DataTypeDirectoryID:
			match = dsdata_to_dsid(name);
			break;
		default:
			break;
	}

	switch (intype)
	{
		case NameTypeNetInfo:
			switch (desiredtype)
			{
				case NameTypeNetInfo:
					d = dsdata_retain(name);
					break;
				case NameTypeX500:
					str = dsx500_netinfo_string_path_to_dn(tname);
					break;
				case NameTypeDirectoryID:
					if (dsengine_netinfo_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = dsid_to_dsdata(match);
					break;
				case NameTypeUserName:
					if (dsengine_netinfo_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = read_user_attribute(e, "name", match);
					break;
				case NameTypePrincipalName:
					if (dsengine_netinfo_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = read_user_attribute(e, "principal", match);
					break;
				default:
					break;
			}
			break;
		case NameTypeX500:
			switch (desiredtype)
			{
				case NameTypeNetInfo:
					str = dsx500_dn_to_netinfo_string_path(tname);
					break;
				case NameTypeX500:
					d = dsdata_retain(name);
					break;
				case NameTypeDirectoryID:
					if (dsengine_x500_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = dsid_to_dsdata(match);
					break;
				case NameTypeUserName:
					if (dsengine_x500_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = read_user_attribute(e, "name", match);
					break;
				case NameTypePrincipalName:
					if (dsengine_x500_string_pathmatch(e, 0, tname, &match) != DSStatusOK)
						return NULL;
					d = read_user_attribute(e, "principal", match);
					break;
				default:
					break;
			}
			break;
		case NameTypeUserName:
		case NameTypePrincipalName:
			switch (desiredtype)
			{
				case NameTypeUserName:
				case NameTypePrincipalName:
					d = dsdata_retain(name);
					break;
				case NameTypeNetInfo:
					if (find_user(e, intype, name, &match) != DSStatusOK)
						return NULL;
					str = dsengine_netinfo_string_path(e, match);
					break;
				case NameTypeX500:
					if (find_user(e, intype, name, &match) != DSStatusOK)
						return NULL;
					str = dsengine_x500_string_path(e, match);
					break;
				case NameTypeDirectoryID:
					if (find_user(e, intype, name, &match) != DSStatusOK)
						return NULL;
					d = dsid_to_dsdata(match);
					break;
				default:
					break;
			}
			break;
		case NameTypeDirectoryID:
			switch (desiredtype)
			{
				case NameTypeNetInfo:
					str = dsengine_netinfo_string_path(e, match);
					break;
				case NameTypeX500:
					str = dsengine_x500_string_path(e, match);
					break;
				case NameTypeDirectoryID:
					d = dsdata_retain(name);
					break;
				case NameTypeUserName:
					d = read_user_attribute(e, "name", match);
					break;
				case NameTypePrincipalName:
					d = read_user_attribute(e, "principal", match);
					break;
				default:
					break;
			}
			break;
		default:
			break;
	}

	if (str != NULL) 
	{
		if (d != NULL) dsdata_release(d);
		d = cstring_to_dsdata(str);
		free(str);
	}

	return d;
}

void dsengine_set_filter_test_delegate(dsengine *e, Logic3 (*delegate)(dsfilter *, dsrecord *, void *), void *private)
{
	if (e == NULL) return;
	e->private = private;
	e->delegate = delegate;
}