/*
 * 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@
 */

/*
 * nilib2 ± more NetInfo library routines.
 * Written by Marc Majka
 *
 * Copyright 1994,  NeXT Computer Inc.
 */

//#include <NetInfo/ni_glue.h>
#include <NetInfo/nilib2.h>
#include <NetInfo/system_log.h>
#include <NetInfo/dsutil.h>
#include <stdlib.h>
#include <rpc/rpc.h>

static unsigned long ni_connect_timeout = 300;

enum ni_parse_status
ni_parse_server_tag(char *str, struct sockaddr_in *server, char **t)
{
	/* utility to parse a server/tag string */

	int len, i;
	char *host, *tag, *slash;
	struct hostent *hent;

	len = strlen(str);

	/* find the "/" character */
	slash = index(str, '/');

	/* check to see if the "/" is missing */
	if (slash == NULL) return NI_PARSE_BADFORMAT;

	/* find the location of the '/' */
	i = slash - str;

	/* check if host string is empty */
	if (i == 0) return NI_PARSE_NOHOST;

	/* check if tag string is empty */
	if (i == (len - 1)) return NI_PARSE_NOTAG;

	/* allocate some space for the host and tag */
	host = (char *)malloc(i + 1);
	*t = (char *)malloc(len - i);
	tag = *t;

	/* copy out the host */
	strncpy(host, str, i);
	host[i] = '\0';

	/* copy out the tag */
	strcpy(tag, slash + 1);

	/* try interpreting the host portion as an address */
	server->sin_addr.s_addr = inet_addr(host);

	if (server->sin_addr.s_addr == -1)
	{
		/* This isn't a valid address.  Is it a known hostname? */
 		hent = gethostbyname(host);
		if (hent != NULL)
		{
			/* found a host with that name */
			bcopy(hent->h_addr, &server->sin_addr, hent->h_length);
		}
		else
		{
			fprintf(stderr, "Can't find address for %s\n", host);
			free(host);
			free(tag);
			return NI_PARSE_HOSTNOTFOUND;
		}
   }

	free(host);
	return NI_PARSE_OK;
}

const char *
ni_parse_error_string(enum ni_parse_status status)
{
	switch (status)
	{
		case NI_PARSE_OK: return("Operation succeeded");
		case NI_PARSE_BADFORMAT: return("Bad format");
		case NI_PARSE_NOHOST: return("No host");
		case NI_PARSE_NOTAG: return("No tag");
		case NI_PARSE_BADADDR: return("Bad address");
		case NI_PARSE_HOSTNOTFOUND: return("Host not found");
	}
	return NULL;
}

int
do_open(char *tool, char *name, void **domain, bool bytag, int timeout, char *user, char *passwd)
{
	/* do an ni_open or an ni_connect, as appropriate */

	char *tag;
	enum ni_parse_status pstatus;
	ni_status status;
	struct sockaddr_in server;
	ni_id rootdir;
	void *localni;

	if (bytag) {
		/* connect by tag */
		/* call a function to parse the input arg */
		pstatus = ni_parse_server_tag(name, &server, &tag);
		if (pstatus != NI_PARSE_OK)
		{
			fprintf(stderr, "%s: incorrect format for domain %s (%s)\n",
				tool, name, ni_parse_error_string(pstatus));
			fprintf(stderr, "usage: -t <host>/<tag>\n");
			fprintf(stderr, "<host> can be a host name or IP address\n");
			return NI_FAILED + 1 + pstatus;
		}

		/* connect to the specified server */
		*domain = ni_connect(&server, tag);
		free(tag);
		if (*domain == NULL) {
			fprintf(stderr, "%s: can't connect to server %s\n", tool, name);
			return NI_FAILED + 1;
		}
	}
	else
	{
		if (!strcmp(name, "."))
		{
			status = ni_open(NULL, ".", domain);
		}
		else
		{
			status = ni_open(NULL, ".", &localni);
			if (status != NI_OK)
			{
				fprintf(stderr, "%s: can't connect to server for local domain\n", tool);
				return status;
			}
			ni_setabort(localni, 1);
			ni_setreadtimeout(localni, timeout);
			ni_setwritetimeout(localni, timeout);
		
			status = ni_open(localni, name, domain);
			ni_free(localni);
		}

		if (status != NI_OK)
		{
			fprintf(stderr, "%s: can't connect to server for domain %s\n", tool, name);
			return status;
		}
	}

	/* abort on errors */
	ni_setabort(*domain, 1);
	
	/* set timeouts */
	ni_setreadtimeout(*domain, timeout);
	ni_setwritetimeout(*domain, timeout);

	/* authentication */
	if (user != NULL) {
		ni_setuser(*domain, user);
		if (passwd != NULL) ni_setpassword(*domain, passwd);
	}

	/* get the root directory to see if the connection is alive */
	status = ni_root(*domain, &rootdir);
	if (status != NI_OK) {
		if (bytag)
			fprintf(stderr, "%s: can't connect to server %s: %s\n",
				tool, name, ni_error(status));
		else
			fprintf(stderr, "%s: can't connect to server for domain %s: %s\n",
				tool, name, ni_error(status));
		return status;
	}

	return 0;
}

ni_status ni2_create(void *domain, char *pathname)
{
	/* make a directory with the given pathname */
	/* do nothing if the directory already exists */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if it already exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret == NI_OK) return NI_OK;

	/* doesn't exist: create it */
	ret = ni_root(domain, &dir);
	if (ret != NI_OK) return ret;

	if (pathname[0] == '/') ret = ni2_createpath(domain, &dir, pathname+1);
	else ret = ni2_createpath(domain, &dir, pathname);

	return ret; 
}

ni_status ni2_createprop(void *domain, char *pathname, const ni_name key, ni_namelist values)
{
	/* create a new property with a given key and list of values */
	/* replaces an existing property if it already exists */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory already exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_createdirprop(domain, &dir, key, values);
}

ni_status ni2_createdirprop(void *domain, ni_id *dir, const ni_name key, ni_namelist values)
{
	/* createprop given a directory rather than a pathname */

	ni_status ret;
	ni_property p;
	ni_namelist nl;
	ni_index where;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for existing property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, create it */
	if (where == NI_INDEX_NULL) {
		NI_INIT(&p);
		p.nip_name = ni_name_dup(key);
		p.nip_val = ni_namelist_dup(values);
		ret = ni_createprop(domain, dir, p, NI_INDEX_NULL);
		ni_prop_free(&p);
		return ret;
	}

	/* property exists: replace the existing values */
	ret = ni_writeprop(domain, dir, where, values);
	return ret;
}

ni_status ni2_appendprop(void *domain, char *pathname, const ni_name key, ni_namelist values)
{
	/* append a list of values to a property */
	/* a new property is created if it doesn't exist */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory already exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_appenddirprop(domain, &dir, key, values);
}

ni_status ni2_appenddirprop(void *domain, ni_id *dir, const ni_name key, ni_namelist values)
{
	/* appendprop given a directory rather than a pathname */

	ni_status ret;
	ni_property p;
	ni_namelist nl;
	ni_index where;
	int i;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for existing property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, create it */
	if (where == NI_INDEX_NULL) {
		NI_INIT(&p);
		p.nip_name = ni_name_dup(key);
		p.nip_val = ni_namelist_dup(values);
		ret = ni_createprop(domain, dir, p, NI_INDEX_NULL);
		ni_prop_free(&p);
		return ret;
	}


	/* property exists: replace the existing values */
	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, where, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* append new values */
	for (i = 0; i < values.ni_namelist_len; i++) {
		ni_namelist_insert(&nl, values.ni_namelist_val[i], NI_INDEX_NULL);
	}

	/* write the new list back */
	ret = ni_writeprop(domain, dir, where, nl);

	ni_namelist_free(&nl);
	return ret;
}

ni_status ni2_insertval(void *domain, char *pathname, const ni_name key, const ni_name value, ni_index where)
{
	/* insert a new value into a property */
	/* the property is created if it doesn't exist */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_insertdirval(domain, &dir, key, value, where);
}

ni_status ni2_insertdirval(void *domain, ni_id *dir, const ni_name key, const ni_name value, ni_index whereval)
{
	/* insertval given a directory rather than a pathname */

	ni_status ret;
	ni_property p;
	ni_namelist nl;
	ni_index where;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for existing property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, create it */
	if (where == NI_INDEX_NULL) {
		NI_INIT(&nl);
		ni_namelist_insert(&nl, value, NI_INDEX_NULL);
		NI_INIT(&p);
		p.nip_name = ni_name_dup(key);
		p.nip_val = ni_namelist_dup(nl);
		ret = ni_createprop(domain, dir, p, NI_INDEX_NULL);
		ni_namelist_free(&nl);
		ni_prop_free(&p);
		return ret;
	}

	/* property exists: replace the existing values */
	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, where, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* insert new value */
	ni_namelist_insert(&nl, value, whereval);

	/* write the new list back */
	ret = ni_writeprop(domain, dir, where, nl);
	ni_namelist_free(&nl);
	return ret;
}

ni_status ni2_mergeprop(void *domain, char *pathname, const ni_name key, ni_namelist values)
{
	/* merge a list of values into a property (to prevent duplicates) */
	/* creates the property if it doesn't already exist */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory already exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_mergedirprop(domain, &dir, key, values);
}

ni_status ni2_mergedirprop(void *domain, ni_id *dir, const ni_name key, ni_namelist values)
{
	/* mergeprop given a directory rather than a pathname */

	ni_status ret;
	ni_property p;
	ni_namelist nl;
	ni_index where, whereval;
	int i;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for existing property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, create it */
	if (where == NI_INDEX_NULL) {
		NI_INIT(&p);
		p.nip_name = ni_name_dup(key);
		p.nip_val = ni_namelist_dup(values);
		ret = ni_createprop(domain, dir, p, NI_INDEX_NULL);
		ni_prop_free(&p);
		return ret;
	}


	/* property exists: replace the existing values */
	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, where, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* merge new values */
	for (i = 0; i < values.ni_namelist_len; i++) {
		whereval = ni_namelist_match(nl, values.ni_namelist_val[i]);
		if (whereval == NI_INDEX_NULL) {
			ni_namelist_insert(&nl, values.ni_namelist_val[i], NI_INDEX_NULL);
		}
	}

	/* write the new list back */
	ret = ni_writeprop(domain, dir, where, nl);
	ni_namelist_free(&nl);
	return ret;
}

ni_status ni2_destroy(void *domain, char *pathname)
{
	/* destroy a directory */
	/* this version recursively destroys all subdirectories as well */

	ni_status ret;
	ni_id dir, parent;
	ni_index pi;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	/* get the parent directory index (nii_object) */
	ret = ni_parent(domain, &dir, &pi);
	if (ret != NI_OK) return ret;

	/* get the parent directory id */
	parent.nii_object = pi;
	ret = ni_self(domain, &parent);
	if (ret != NI_OK) return ret;

	return ni2_destroydir(domain, &dir, &parent);
}

ni_status ni2_destroydir(void *domain, ni_id *dir, ni_id *parent)
{
	/* destroy a directory and all it's subdirectories */
	/* this is the recursive workhorse */

	ni_status ret;
	int i;
	ni_idlist children;
	ni_id child;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* get a list of all my children */
	NI_INIT(&children);
	ret = ni_children(domain, dir, &children);
	if (ret != NI_OK) return ret;

	/* destroy each child */
	for (i = 0; i < children.ni_idlist_len; i++) {
		child.nii_object = children.ni_idlist_val[i];
		ret = ni_self(domain, &child);
		if (ret != NI_OK) return ret;
		ret = ni2_destroydir(domain, &child, dir);
		if (ret != NI_OK) return ret;
	}

	/* free list of child ids */
	ni_idlist_free(&children);

	/* destroy myself */
	return ni_destroy(domain, parent, *dir);
}

ni_status ni2_destroyprop(void *domain, char *pathname, ni_namelist keys)
{
	/* destroy a property */
	/* destroys all properties with the same key */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_destroydirprop(domain, &dir, keys);
}

ni_status ni2_destroydirprop(void *domain, ni_id *dir, ni_namelist keys)
{
	/* destroyprop given a directory rather than a pathname */

	ni_status ret;
	ni_index where;
	ni_namelist nl;
	int i;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* destroy all occurrences of each key */
	for (i = 0; i < keys.ni_namelist_len; i++) {

		where = ni_namelist_match(nl, keys.ni_namelist_val[i]);

		/* keep looking for all occurrences */
		while (where != NI_INDEX_NULL) {
			ret = ni_destroyprop(domain, dir, where);
			if (ret != NI_OK) {
				ni_namelist_free(&nl);
				return ret;
			}

			/* update the namelist */  
			ni_namelist_delete(&nl, where);
			where = ni_namelist_match(nl, keys.ni_namelist_val[i]);
		}
	}
	ni_namelist_free(&nl);
	return NI_OK;
}

ni_status ni2_destroyval(void *domain, char *pathname, const ni_name key, ni_namelist values)
{
	/* destroy all occurances of a value in a property */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_destroydirval(domain, &dir, key, values);
}

ni_status ni2_destroydirval(void *domain, ni_id *dir, const ni_name key, ni_namelist values)
{
	/* destroyval given a directory rather than a pathname */

	ni_status ret;
	ni_namelist nl;
	ni_index where, whereval;
	int i;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for existing property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, nothing to do */
	if (where == NI_INDEX_NULL) {
		return NI_OK;
	}

	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, where, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* delete values */
	for (i = 0; i < values.ni_namelist_len; i++) {
		whereval = ni_namelist_match(nl, values.ni_namelist_val[i]);
		while (whereval != NI_INDEX_NULL) {
			ni_namelist_delete(&nl, whereval);
			whereval = ni_namelist_match(nl, values.ni_namelist_val[i]);
		}
	}

	/* write the new list back */
	ret = ni_writeprop(domain, dir, where, nl);
	ni_namelist_free(&nl);

	return ret;
}

ni_status ni2_renameprop(void *domain, char *pathname, const ni_name oldname, const ni_name newname)
{
	/* rename a property */

	ni_status ret;
	ni_id dir;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* see if the directory already exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_renamedirprop(domain, &dir, oldname, newname);
}

ni_status ni2_renamedirprop(void *domain, ni_id *dir, const ni_name oldname, const ni_name newname)
{
	/* renameprop given a directory rather than a pathname */

	ni_status ret;
	ni_index where;
	ni_namelist nl;

	/* need to be talking to the master */
	ni_needwrite(domain, 1);

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* look up old name */
	where = ni_namelist_match(nl, oldname);
	ni_namelist_free(&nl);

	/* if it's not there, return an error */
	if (where == NI_INDEX_NULL) {
		return NI_NOPROP;
	}

	return ni_renameprop(domain, dir, where, newname);
}

ni_status ni2_pathsearch(void *domain, ni_id *dir, char *pathname)
{
	/* same as pathsearch, but if pathname is an integer */
	/* then use it as a directory id */

	int i, len;
	bool is_id;

	len = strlen(pathname);
	is_id = true;

	for (i = 0; i < len && is_id; i++)
		if (!isdigit(pathname[i])) is_id = false;

	if (is_id) {
		dir->nii_object = (unsigned long)atoi(pathname);
		return ni_self(domain, dir);
	}
	else {
		return ni_pathsearch(domain, dir, pathname);
	}
}

ni_status ni2_createpath(void *domain, ni_id *dir, char *pathname)
{
	/* make a directory with the given pathname */

	ni_status ret;
	ni_id checkdir;
	int i, j, len;
	char *dirname = NULL;
	bool simple;

	/* pull out every pathname component and create the directory */
	i = 0;
	while (pathname[i] != '\0') {

		/* search forward for a path component (a directory) */
		simple = true;
		for (j = i; pathname[j] != '\0' && simple; j++) {
			if (pathname[j] == '\\' && pathname[j+1] == '/') j+=2;
			if (pathname[j] == '/') simple = false;
		}

		len = j - i;
		if (!simple) len--;
		dirname = malloc(len + 1);
		strncpy(dirname, pathname+i, len);
		dirname[len] = '\0';

		/* advance the pointer */
		i = j;

		/* does this directory exist? */
		checkdir = *dir;
		ret = ni_pathsearch(domain, dir, dirname);

		/* if it doesn't exist, create it */
		if (ret == NI_NODIR) {
			*dir = checkdir;
			ret = ni2_createchild(domain, dir, dirname);
			if (ret != NI_OK) return ret;
		}
		free(dirname);
	}

	return NI_OK;
}

ni_status ni2_createchild(void *domain, ni_id *dir, const ni_name dirname)
{
	/* make a child directory with the given name */

	ni_status ret;
	ni_proplist p;
	ni_id child;
	int i, j, k, len;
	char *key = NULL;
	char *value = NULL;

	/* if the name contains "=", then we've got "foo=bar" */
	/* property key is "foo", not "name" */

	len = 0;
	for (i = 0; dirname[i] != '\0' && dirname[i] != '='; i++);
	if (dirname[i] == '=') len = i;

	if (len > 0) {
		key = malloc(len + 1);
		/* check for backslashes in property key */
		for (i = 0, j = 0; i < len; i++, j++) {
			if (dirname[i] == '\\' && dirname[i+1] == '/') i++;
			key[j] = dirname[i];
		}
		key[j] = '\0';
		i = len + 1;
	}
	else {
		key = malloc(5);
		strcpy(key, "name");
		i = 0;
	}

	/* compress out backslashes in value */
	j = strlen(dirname);
	len = j - i;
	value = malloc(len + 1);
	for (k = 0; i < j; k++, i++) {
		if (dirname[i] == '\\' && dirname[i+1] == '/') i++;
		value[k] = dirname[i];
	}
	value[k] = '\0';
		
	/* set up the new directory */
	NI_INIT(&p);
	nipl_createprop(&p, key);
	nipl_appendprop(&p, key, value);

	/* create it */
	ret = ni_create(domain, dir, p, &child, NI_INDEX_NULL);
	if (ret != NI_OK) {
		ni_proplist_free(&p);
		return ret;
	}

	ni_proplist_free(&p);
	free(key);
	free(value);
	*dir = child;
	return NI_OK;
}

void nipl_createprop(ni_proplist *l, const ni_name n)
{
	/* property list utility */
	/* add a name property to a property list */

	ni_property p;

	NI_INIT(&p);
	p.nip_name = ni_name_dup(n);
	p.nip_val.ninl_len = 0;
	p.nip_val.ninl_val = NULL;
	ni_proplist_insert(l, p, NI_INDEX_NULL);
	ni_prop_free(&p);
}

void nipl_appendprop(ni_proplist *l, const ni_name n, const ni_name v)
{
	/* property list utility */
	/* append a value to a property in a property list */

	ni_index where;

	where = ni_proplist_match(*l, n, NULL);
	if (where == NI_INDEX_NULL) {
		nipl_createprop(l, n);
		where = ni_proplist_match(*l, n, NULL);
	}
	ni_namelist_insert(&(l->nipl_val[where].nip_val), v, NI_INDEX_NULL);
}

void nipl_mergeprop(ni_proplist *l, const ni_name n, const ni_name v)
{
	/* property list utility */
	/* merge a value into a property in a property list */

	ni_index where;

	where = ni_proplist_match(*l, n, NULL);
	if (where == NI_INDEX_NULL) {
		nipl_createprop(l, n);
		where = ni_proplist_match(*l, n, NULL);
	}
	where = ni_namelist_match(l->nipl_val[where].nip_val, v);
	if (where == NI_INDEX_NULL) {
		ni_namelist_insert(&(l->nipl_val[where].nip_val), v, NI_INDEX_NULL);
	}
}

ni_status ni2_statprop(void *domain, char *pathname, const ni_name key, ni_index *where)
{
	/* match a property in a given property in a given directory */

	ni_status ret;
	ni_id dir;

	/* assume there's no match */
	*where = NI_INDEX_NULL;

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_statpropdir(domain, &dir, key, where);
}

ni_status ni2_statpropdir(void *domain, ni_id *dir, const ni_name key, ni_index *where)
{
	/* statprop given a directory rather than a pathname */

	ni_status ret;
	ni_namelist nl;

	/* assume there's no match */
	*where = NI_INDEX_NULL;

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for property with this key */
	*where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, no match */
	if (*where == NI_INDEX_NULL) {
		return NI_NOPROP;
	}

	return NI_OK;
}

ni_status ni2_statval(void *domain, char *pathname, const ni_name key, const ni_name value, ni_index *where)
{
	/* match a value in a given property in a given directory */

	ni_status ret;
	ni_id dir;

	/* assume there's no match */
	*where = NI_INDEX_NULL;

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_statvaldir(domain, &dir, key, value, where);
}

ni_status ni2_statvaldir(void *domain, ni_id *dir, const ni_name key, const ni_name value, ni_index *where)
{
	/* statval given a directory rather than a pathname */

	ni_status ret;
	ni_namelist nl;
	ni_index wh;

	/* assume there's no match */
	*where = NI_INDEX_NULL;

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for property with this key */
	wh = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, no match */
	if (wh == NI_INDEX_NULL) {
		return NI_NOPROP;
	}

	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, wh, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for this value */
	wh = ni_namelist_match(nl, value);
	ni_namelist_free(&nl);

	/* if value doesn't exist, no match */
	if (wh == NI_INDEX_NULL) {
		return NI_NONAME;
	}

	*where = wh;
	return NI_OK;
}

ni_status ni2_reapprop(void *domain, char *pathname, const ni_name key)
{
	/* remove a property in a given directory if the property is empty */

	ni_status ret;
	ni_id dir;

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni2_reappropdir(domain, &dir, key);
}

ni_status ni2_reappropdir(void *domain, ni_id *dir, const ni_name key)
{
	/* reapprop given a directory rather than a pathname */

	ni_status ret;
	ni_namelist nl;
	ni_index where;

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* check for property with this key */
	where = ni_namelist_match(nl, key);
	ni_namelist_free(&nl);

	/* if property doesn't exist, return */
	if (where == NI_INDEX_NULL) {
		return NI_OK;
	}

	/* fetch existing namelist for this property */
	NI_INIT(&nl);
	ret = ni_readprop(domain, dir, where, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* if the property contains any values, leave it alone */
	if (nl.ni_namelist_len > 0) {
		ni_namelist_free(&nl);
		return NI_OK;
	}

	/* property is empty, delete it */
	ni_namelist_free(&nl);
	return ni_destroyprop(domain, dir, where);
}

ni_status ni2_reapdir(void *domain, char *pathname)
{
	/* destroy a directory if it has nothing but a name */

	ni_status ret;
	ni_id dir;
	ni_namelist nl;

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	/* fetch list of property keys from directory */
	NI_INIT(&nl);
	ret = ni_listprops(domain, &dir, &nl);
	if (ret != NI_OK) {
		return ret;
	}

	/* if more than one property, leave it alone */
	if (nl.ni_namelist_len > 1) {
		ni_namelist_free(&nl);
		return NI_OK;
	}

	/* directory is empty (except for name), delete it */
	ni_namelist_free(&nl);
	return ni2_destroy(domain, pathname);
}

ni_status ni2_copy(void *srcdomain, char *path, void*dstdomain, bool recursive)
{
	/* copy a directory from src to dst */

	ni_status ret;
	ni_id srcdir, dstdir;

	/* see if src directory exists */
	ret = ni2_pathsearch(srcdomain, &srcdir, path);
	if (ret != NI_OK) return ret;

	/* create dstdir if necessary */
	ret = ni2_create(dstdomain, path);
	if (ret != NI_OK) return ret;

	/* get dstdir */
	ret = ni2_pathsearch(dstdomain, &dstdir, path);
	if (ret != NI_OK) return ret;

	return ni2_copydir(srcdomain, &srcdir, dstdomain, &dstdir, recursive);
}

ni_status ni2_copydir(void *srcdomain, ni_id *srcdir, void*dstdomain, ni_id *dstdir , bool recursive)
{
	ni_status ret;
	ni_idlist children;
	int i, len;
	ni_proplist p;
	ni_id dir;

	NI_INIT(&p);
	
	/* get proplist from src dir */
	ret = ni_read(srcdomain, srcdir, &p);
	if (ret != NI_OK) {
		return ret;
	}

	/* write the property list to the dst dir */
	ret = ni_write(dstdomain, dstdir, p);
	if (ret != NI_OK) {
		ni_proplist_free(&p);
		return ret;
	}
	
	ni_proplist_free(&p);

	if (recursive) {
		NI_INIT(&children);

		/* get list of children */
		ret = ni_children(srcdomain, srcdir, &children);
		if (ret != NI_OK) {
			return ret;
		}

		len = children.ni_idlist_len;
		for (i = 0; i < len; i++) {
			dir.nii_object = children.ni_idlist_val[i];
			ret = ni_self(srcdomain, &dir);
			if (ret != NI_OK) {
				ni_idlist_free(&children);
				return ret;
			}
			ret = ni2_copydirtoparentdir(srcdomain,&dir,dstdomain,dstdir,recursive);
		}
	
		ni_idlist_free(&children);
	}

	return NI_OK;
}

ni_status ni2_copydirtoparentdir(void *srcdomain, ni_id *srcdir, void*dstdomain, ni_id *dstdir , bool recursive)
{
	ni_status ret;
	ni_idlist children;
	int i, len;
	ni_proplist p;
	ni_id dir, newdstdir;

	NI_INIT(&p);
	
	/* get proplist from src dir */
	ret = ni_read(srcdomain, srcdir, &p);
	if (ret != NI_OK) {
		return ret;
	}

	/* create the destination dir */
	ret = ni_create(dstdomain, dstdir, p, &newdstdir, NI_INDEX_NULL);
	if (ret != NI_OK) {
		ni_proplist_free(&p);
		return ret;
	}
	
	ni_proplist_free(&p);

	if (recursive) {
		NI_INIT(&children);

		/* get list of children */
		ret = ni_children(srcdomain, srcdir, &children);
		if (ret != NI_OK) {
			return ret;
		}

		len = children.ni_idlist_len;
		for (i = 0; i < len; i++) {
			dir.nii_object = children.ni_idlist_val[i];
			ret = ni_self(srcdomain, &dir);
			if (ret != NI_OK) {
				ni_idlist_free(&children);
				return ret;
			}
			ret = ni2_copydirtoparentdir(srcdomain,&dir,dstdomain, &newdstdir,recursive);
		}
	
		ni_idlist_free(&children);
	}

	return NI_OK;
}

ni_status ni2_lookupprop(void *domain, char *pathname, const ni_name key, ni_namelist *values)
{
	/* read a property */

	ni_status ret;
	ni_id dir;

	/* see if the directory exists */
	ret = ni2_pathsearch(domain, &dir, pathname);
	if (ret != NI_OK) return ret;

	return ni_lookupprop(domain, &dir, key, values);
}

ni_index ni_namelist_insert_sorted(ni_namelist *values, const ni_name newvalue)
{
	int i, len;

	len = values->ni_namelist_len;
	for (i = 0; i < len; i++) {
		if (strcmp(newvalue, values->ni_namelist_val[i]) <= 0) {
			ni_namelist_insert(values, newvalue, (ni_index)i);
			return (ni_index)i;
		}
	}

	ni_namelist_insert(values, newvalue, NI_INDEX_NULL);
	return NI_INDEX_NULL;
}

void *
niHandleForTag(char *tag, struct sockaddr_in *addr)
{
	void *domain;
	int a;
	unsigned long t;
	ni_status status;
	ni_id root;

	if (tag == NULL) return NULL;
	if (addr == NULL) return NULL;
	if (addr->sin_addr.s_addr == -1) return NULL;

	domain = ni_connect(addr, tag);

	if (domain == NULL) return NULL;

	a = 1;
	t = ni_connect_timeout;

	if (!strcmp(tag, "local") && (addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK)))
	{
		a = 0;
		t = 0;
	}

	ni_setabort(domain, a);
	ni_setreadtimeout(domain, t);

	root.nii_object = 0;

	status = ni_self(domain, &root);

	if (status != NI_OK) 
	{
		system_log(LOG_ALERT,
			"NetInfo open failed for %s@%s (%s)",
			tag, inet_ntoa(addr->sin_addr), ni_error(status));
		ni_free(domain);
		return NULL;
	}

	ni_setabort(domain, 1);
	ni_setreadtimeout(domain, ni_connect_timeout);

	return domain;
}

void *
niHandleForPath(char *path, struct sockaddr_in *addr)
{
	void *d0, *d1;
	ni_status status;
	char **parts;
	int a, i;
	unsigned long t;
	ni_id root;

	if (path == NULL) return NULL;
	if (addr == NULL) return NULL;
	if (addr->sin_addr.s_addr == -1) return NULL;

	root.nii_object = 0;

	d0 = ni_connect(addr, "local");

	if (d0 == NULL) return NULL;

	a = 1;
	t = ni_connect_timeout;

	if (!strcmp(path, ".") && (addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK)))
	{
		a = 0;
		t = 0;
	}

	ni_setabort(d0, a);
	ni_setreadtimeout(d0, t);

	status = ni_self(d0, &root);

	if (status != NI_OK) 
	{
		system_log(LOG_ALERT,
			"NetInfo open failed for local@%s (%s)",
			inet_ntoa(addr->sin_addr), ni_error(status));
		ni_free(d0);
		return NULL;
	}
		
	ni_setabort(d0, 1);
	ni_setreadtimeout(d0, ni_connect_timeout);

	if (!strcmp(path, ".")) return d0;

	status = NI_OK;
	while (status == NI_OK)
	{
		status = ni_open(d0, "..", &d1);
		if (status == NI_OK) status = ni_self(d1, &root);

		if (status == NI_OK)
		{
			ni_free(d0);
			d0 = d1;
		}
		if (!strcmp(path, "..")) return d0;
	}

	if (!strcmp(path, "/")) return d0;

	parts = explode(path+1, "/");

	for (i = 0; parts[i] != NULL; i++)
	{
		status = ni_open(d0, parts[i], &d1);
		if (status == NI_OK) status = ni_self(d1, &root);

		if (status != NI_OK)
		{
			system_log(LOG_ALERT,
				"NetInfo open failed for domain component %s at address %s (%s)",
				parts[i], inet_ntoa(addr->sin_addr), ni_error(status));
			freeList(parts);
			return NULL;
		}
		ni_free(d0);
		d0 = d1;
	}

	freeList(parts);
	return d0;
}

void *
niHandleForName(char *name)
{
	void *domain;
	char *p_colon, *p_at;
	struct sockaddr_in server;

	if (name == NULL) return NULL;

	/*
	 * names may be of the following formats:
	 *
	 * path -> domain with given pathname
	 * tag@address -> connect by tag to given address
	 * path@address -> path relative to local domain at host
	 *
	 * niserver:tag -> connect by tag, localhost
	 * niserver:tag@address -> connect by tag
	 * nidomain:path -> domain with given pathname
	 * nidomain:path@address -> path relative to local domain at host
	 */

	domain = NULL;

	p_colon = strchr(name, ':');
	p_at = strchr(name, '@');

	memset(&server, 0, sizeof(struct sockaddr_in));
	server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	if (p_at != NULL)
	{
		*p_at = '\0';
		server.sin_addr.s_addr = inet_addr(p_at + 1);
	}

	if (p_colon != NULL)
	{
		if (!strncmp(name, "niserver:", 9))
			domain = niHandleForTag(name+9, &server);

		if (!strncmp(name, "nidomain:", 9))
			domain = niHandleForPath(name+9, &server);
	}
	else
	{
		if ((name[0] == '/') || (name[0] == '.'))
			domain = niHandleForPath(name, &server);

		else domain = niHandleForTag(name, &server);
	}

	if (domain == NULL)
	{
		system_log(LOG_ALERT, "NetInfo open failed for %s", name);
	}

	if (p_at != NULL) *p_at = '@';

	return domain;
}
