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

/*
 * NetInfo directory (XXX.nidb) handling and database transfer
 * Copyright (C) 1989 by NeXT, Inc.
 *
 * There are three kinds of NetInfo directories. 
 * .nidb extension: A directory in use.
 * .temp extension: Temporary directory being loaded from master server.
 * .move extension: Old directory moved away to make room for new one.
 *
 * The transactional implications on startup are the following:
 * 	1. A ".temp" directory is assumed to be in a partial state of 
 *	   creation and cannot be used UNLESS there is also a ".move"
 *	   directory.
 *
 *	2. A ".move" directory is assumed to be in a partial state of deletion
 *	   and cannot be used. If there is a ".temp" directory, it should
 *	   be renamed ".nidb" and used.
 */
#include <NetInfo/config.h>
#include "ni_server.h"
#include <unistd.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <rpc/xdr.h>
#include "ni_globals.h"
#include "ni_file.h"
#include <NetInfo/mm.h>
#include "getstuff.h"
#include <NetInfo/system.h>
#include <NetInfo/system_log.h>
#include <NetInfo/network.h>
#include "event.h"
#include <NetInfo/socket_lock.h>
#include <NetInfo/syslock.h>
#include <NetInfo/systhread.h>

#ifndef S_IRWXU
#define S_IRWXU 0000700
#endif

struct sockaddr_in readall_sin;
ni_name readall_tag = NULL;

#undef NFS_RM_BUG /* hasn't been fixed, just assume no NFS databases */
#define READALL_TIMEOUT 60

static void *new_ni;		/* NetInfo handle for database just received */

/*
 * Destroy a database directory
 */
static int
dir_destroy(char *dir)
{
	char path1[MAXPATHLEN + 1];
#ifdef NFS_RM_BUG
	char path2[MAXPATHLEN + 1];
#endif
	DIR *dp;
	struct direct *d;

	socket_lock();
	dp = opendir(dir);
	socket_unlock();
	if (dp == NULL) return (0);

	while (NULL != (d = readdir(dp)))
	{
		sprintf(path1, "%s/%.*s", dir, d->d_namlen, d->d_name);
#ifdef NFS_RM_BUG
		sprintf(path2, "./%.*s.tmp", d->d_namlen, d->d_name);

		/*
		 * rename, then unlink in case NFS leaves tmp files behind
		 * (.nfs* files, that is).
		 */
		if ((rename(path1, path2) != 0) || (unlink(path2) != 0))
		{
			/* ignore error: rmdir will catch ENOTEMPTY */
		}
#else
		(void)unlink(path1);
#endif
	}

	socket_lock();
	closedir(dp);
	socket_unlock();
	return (rmdir(dir));
}

static const char NI_SUFFIX_CUR[] = ".nidb";
static const char NI_SUFFIX_MOVED[] = ".move";
static const char NI_SUFFIX_TMP[] = ".temp";

/*
 * Returns the three kinds of directory names used by NetInfo
 */
void
dir_getnames(ni_name orig, ni_name *target, ni_name *moved, ni_name *tmp)
{
	if (target != NULL)
	{
		*target = malloc(strlen(orig) + strlen(NI_SUFFIX_CUR) + 1);
		sprintf(*target, "%s%s", orig, NI_SUFFIX_CUR);
	}

	if (moved != NULL)
	{
		*moved = malloc(strlen(orig) + strlen(NI_SUFFIX_MOVED) + 1);
		sprintf(*moved, "%s%s", orig, NI_SUFFIX_MOVED);
	}

	if (tmp != NULL)
	{
		*tmp = malloc(strlen(orig) + strlen(NI_SUFFIX_TMP) + 1);
		sprintf(*tmp, "%s%s", orig, NI_SUFFIX_TMP);
	}
}

/*
 * Switcharoo of directories
 *
 * new database is built in ".temp".
 * old database is moved to ".move".
 * ".temp" is renamed to ".nidb".
 * ".move" is destroyed.
 */
static int
dir_switch(void *ni)
{
	int res;
	int removeit;
	char *target;
	char *moved;
	char *tmp;
	ni_name tag;

	tag = ni_tagname(ni);
	/* close all files */
	ni_free(ni);

	/*
	 * target = tag.nidb
	 * moved = tag.move
	 * tmp = tag.temp
	 */
	dir_getnames(tag, &target, &moved, &tmp);
	ni_name_free(&tag);

	removeit = 1;
	if (access(target, F_OK) == 0)
	{
		/* tag.nidb exists.  rename it to tag.move */
		res = rename(target, moved) == 0;
		if (res < 0)
		{
			system_log(LOG_ERR, "can't rename %s to %s: %m", target, moved);
			goto cleanup;
		}
	}
	else
	{
		/*
		 * yikes!  tag.nidb doesn't exist!
		 */
		system_log(LOG_WARNING, "can't access %s: %m", target);
		removeit = 0;
	}

	/*
	 * now rename tag.temp to tag.nidb
	 */
	res = rename(tmp, target);
	if (res < 0)
	{
		system_log(LOG_ERR, "can't rename %s to %s: %m", tmp, target);
		goto cleanup;
	}
	
	/*
	 * final step: remove tag.move (if there is one)
	 */
	if (removeit)
	{
		res = dir_destroy(moved);
		if (res < 0)
		{
			system_log(LOG_ERR, "can't remove %s: %m", moved);
		}
	}

 cleanup:
	ni_name_free(&target);
	ni_name_free(&moved);
	ni_name_free(&tmp);

	return (res);
}

/*
 * Check to see if anything needs cleaned up (like maybe we crashed
 * while a switch was going on).
 *
 * If ".temp" exists but ".move" does not then destroy ".temp".
 *
 * If ".temp" and ".move" exist, then rename ".temp" to ".nidb" 
 * and destroy ".move". 
 *
 * If ".move" exists and ".temp" does not, then destroy ".move".
 *
 */
void
dir_cleanup(char *domain)
{
	ni_name target = NULL;
	ni_name tmp = NULL;
	ni_name moved = NULL;

	dir_getnames(domain, &target, &moved, &tmp);
	if (access(tmp, F_OK) == 0)
	{
		if (access(moved, F_OK) == 0)
		{
			if (rename(tmp, target) == 0)
			{
				dir_destroy(moved);
			}
		}
		else
		{
			dir_destroy(tmp);
		}
	}
	else if (access(moved, F_OK) == 0)
	{
		dir_destroy(moved);
	}

	ni_name_free(&target);
	ni_name_free(&tmp);
	ni_name_free(&moved);
}

/*
 * Log a failure for the given ID
 */
static void
log_failure(ni_index id, char *message)
{
	if (id == NI_INDEX_NULL)
		system_log(LOG_ERR, "Transfer failed at %s", message);
	else
		system_log(LOG_ERR, "Transfer failed at %s: id=%d", message, id);
}

/*
 * XDR routine to write out a new database.
 */
static bool_t
xdr_writeall(XDR *xdr, char *dir)
{
	ni_status status;
	int more;
	void *fh;
	ni_object object;
	unsigned int highest_id;
	char *tag = NULL;
	unsigned given_checksum;	/* checksum given by master */
	unsigned db_checksum;		/* checksum of current database */

	if (mkdir(dir, S_IRWXU) < 0)
	{
		log_failure(NI_INDEX_NULL, "mkdir");
		return (FALSE);
	}

	status = file_init(dir, &fh);
	if (status != NI_OK)
	{
		log_failure(NI_INDEX_NULL, "file_init");
		return (FALSE);
	}

	if (!xdr_ni_status(xdr, &status))
	{
		log_failure(NI_INDEX_NULL, "status");
		file_free(fh);
		return (FALSE);
	}

	if (status != NI_OK)
	{
		if (status == NI_MASTERBUSY)
		{
			if (db_ni && (tag = ni_tagname(db_ni)))
			{
				system_log(LOG_ERR,
					"Master server for tag %s is busy; will "
					"retry transfer later", tag);
				ni_name_free(&tag);
			}
			else
			{
				system_log(LOG_ERR,
					"Master server is busy; will retry transfer later");
			}
		}
		else
		{
			log_failure(NI_INDEX_NULL, (char *)ni_error(status));
		}

		file_free(fh);
		return (FALSE);
	}

	if (!xdr_u_int(xdr, &given_checksum))
	{
		log_failure(NI_INDEX_NULL, "no checksum");
		file_free(fh);
		return (FALSE);
	}

	db_checksum = ni_getchecksum(db_ni);

	if (given_checksum == 0)
	{
		/*
		 * Database is already up to date
		 *
		 * XXX note assumption, above, that the checksum can never
		 * legitmately be 0.  If it overflows, it CAN be 0!
		 */
		system_log(LOG_DEBUG,
			"reading all from %s/%s: checksums match {%u}",
			inet_ntoa(readall_sin.sin_addr), readall_tag,
			db_checksum);
		file_free(fh);
		return (FALSE);
	}

	system_log(LOG_DEBUG,
		"reading all from %s/%s starting; "
		"old checksum %u, new checksum %u",
		inet_ntoa(readall_sin.sin_addr), readall_tag, db_checksum,
		given_checksum);

	if (!xdr_u_int(xdr, &highest_id))
	{
		log_failure(NI_INDEX_NULL, "no highest_id");
		file_free(fh);
		return (FALSE);
	}

	have_transferred++;
	for (;;)
	{
		if (!xdr_bool(xdr, &more))
		{
			log_failure(object.nio_id.nii_object, "no more");
			file_free(fh);
			return (FALSE);
		}

		if (!more)
		{
			file_free(fh);
			return (TRUE);
		}

		MM_ZERO(&object);
		if (!xdr_ni_object(xdr, &object))
		{
			log_failure(object.nio_id.nii_object, "no object");
			file_free(fh);
			return (FALSE);
		}

		if (file_writecopy(fh, &object) != NI_OK)
		{
			log_failure(object.nio_id.nii_object, "write");
			file_free(fh);
			return (FALSE);
		}

		xdr_free(xdr_ni_object, (char *)&object);
	}
}

/*
 * Reads a new database from the master and writes it out. Can be
 * short-circuited if it is detected that the master's copy is not
 * different than our current version (via checksums).
 */
static int
dir_transfer(struct sockaddr_in *sin, char *tag, char *dir, unsigned checksum)
{
	int status;
	int sock;
	CLIENT *cl;
	struct timeval tv;
	nibind_getregister_res res;

	/*
	 * Use UDP for this query of the binder daemon, if possible, to
	 * decrease network overhead.
	 */
	sock = socket_open(sin, NIBIND_PROG, NIBIND_VERS);
	if (sock < 0)
	{
		sock = socket_connect(sin, NIBIND_PROG, NIBIND_VERS);
		if (sock < 0) return (0);

		system_log(LOG_DEBUG, "dir_transfer using TCP to binder");
		FD_SET(sock, &clnt_fdset);	/* protect client socket */
		cl = clnttcp_create(sin, NIBIND_PROG, NIBIND_VERS, &sock, 0, 0);
	}
	else
	{
		tv.tv_sec = READALL_TIMEOUT;
		tv.tv_usec = 0;
		FD_SET(sock, &clnt_fdset);	/* protect client socket */
		cl = clntudp_create(sin, NIBIND_PROG, NIBIND_VERS, tv, &sock);
	}

	if (cl == NULL)
	{
		socket_close(sock);
		FD_CLR(sock, &clnt_fdset);	/* unprotect client socket */
		return (0);
	}

	tv.tv_sec = READALL_TIMEOUT;
	tv.tv_usec = 0;
	if (clnt_call(cl, NIBIND_GETREGISTER, xdr_ni_name, &tag,
		  	xdr_nibind_getregister_res, &res, tv) != RPC_SUCCESS)
	{
		socket_lock();
		clnt_destroy(cl);
		socket_unlock();
		socket_close(sock);
		FD_CLR(sock, &clnt_fdset);	/* unprotect client socket */
		return (0);
	}

	socket_lock();
	clnt_destroy(cl);
	socket_unlock();
	socket_close(sock);
	FD_CLR(sock, &clnt_fdset);	/* unprotect client socket */
	if (res.status != NI_OK) return (0);

	sin->sin_port = htons(res.nibind_getregister_res_u.addrs.tcp_port);
	sock = socket_connect(sin, NI_PROG, NI_VERS);
	if (sock < 0) return (0);

	FD_SET(sock, &clnt_fdset);	/* protect client socket */
	cl = clnttcp_create(sin, NI_PROG, NI_VERS, &sock, 0, 0);
	if (cl == NULL)
	{
		socket_close(sock);
		FD_CLR(sock, &clnt_fdset);	/* unprotect client socket */
		return (0);
	}

	status = clnt_call(cl, _NI_READALL, xdr_u_int, &checksum, xdr_writeall, dir, tv);
	socket_lock();
	clnt_destroy(cl);
	socket_unlock();
	socket_close(sock);
	FD_CLR(sock, &clnt_fdset);	/* unprotect client socket */
	return (status == RPC_SUCCESS);
}

/*
 * Information needed by transfer thread
 */
typedef struct transfer_info
{
	struct sockaddr_in sin;
	unsigned checksum;
	ni_name tag;
} transfer_info;

/*
 * For locking
 */
static syslock *transfer_syslock;
static volatile int transfer_inprogress;

/*
 * The transfer thread: Let the server continue serving while the transfer
 * is going on.
 */
static void
transfer(transfer_info *info)
{
	ni_name tmp = NULL;
	ni_name tag = NULL;
	ni_status status;

	tag = ni_tagname(db_ni);
	dir_getnames(tag, NULL, NULL, &tmp);
	reading_all = TRUE;
	readall_sin = info->sin;
	if (readall_tag) ni_name_free(&readall_tag);
	readall_tag = info->tag;
	if (dir_transfer(&info->sin, info->tag, tmp, info->checksum))
	{
		/*
		 * Make sure everything is sunc to disk. 
 		 * XXX: sync() just starts the process - no way to
		 * know when everything is actually sunc. We hope
		 * the database initialization takes longer than
		 * a filsystem sync.
		 */
		sync();

		/*
		 * Init the new database to see if it transferred
		 * correctly.
		 */
		status = ni_init(tmp, &new_ni);
		if (status != NI_OK)
		{
			new_ni = NULL;
			system_log(LOG_ERR, "cannot init new database");
		}
		else
		{
			/*
			 * Successfully initialized
			 */
			event_post();
		}
	}
	else
	{
		new_ni = NULL;
		dir_destroy(tmp);
	}

	ni_name_free(&tag);
	ni_name_free(&tmp);
	MM_FREE(info);

	if (new_ni == NULL)
	{
		/*
		 * If we failed the transfer, unlock now. Otherwise
		 * do not unlock - wait for the main event loop to do
		 * it.
		 */
		syslock_lock(transfer_syslock);
		transfer_inprogress = 0;
		syslock_unlock(transfer_syslock);
	}

	reading_all = FALSE;

	systhread_exit();
}

/*
 * Callback routine to switch to the new database. The svc_run() event
 * checker will call us back.
 */
static void
cb_switch(void)
{
	ni_name dbname = NULL;
	ni_name tag = NULL;

	/*
	 * OK, it seems to work. Let's commit to it now.
	 */
	tag = ni_tagname(db_ni);
	dir_getnames(tag, &dbname, NULL, NULL);
	ni_name_free(&tag);
	if (dir_switch(db_ni) < 0)
	{
		ni_free(new_ni);
		system_log(LOG_ALERT, "couldn't switch directories");
                system_log(LOG_ALERT, "Aborting!");
                abort();
	}
	else
	{
		db_ni = new_ni;
		ni_renamedir(db_ni, dbname);
		ni_name_free(&dbname);
	}

	syslock_lock(transfer_syslock);
	transfer_inprogress = 0;
	syslock_unlock(transfer_syslock);
}


/*
 * For clone, check to see if we are out of date wrt the master.
 */
void
dir_clonecheck(void)
{
	transfer_info *info;
	ni_name tag;
	systhread *t;

	if (have_transferred)
	{
		/*
		 * Do not transfer if already have done so in last
		 * cleanup period.
		 */
		system_log(LOG_INFO,
			"dir_clonecheck: have transferred recently; "
			"doing readall anyway");
	}
	if (transfer_syslock == NULL) transfer_syslock = syslock_new(0);

	/*
	 * Do not check if already transferring
	 */
	syslock_lock(transfer_syslock);
	if (transfer_inprogress)
	{
		syslock_unlock(transfer_syslock);
		return;
	}
	syslock_unlock(transfer_syslock);

	master_addr = getmasteraddr(db_ni, &tag);
	if (master_addr == 0)
	{
		system_log(LOG_ERR, "cannot locate master - transfer failed");
		system_log(LOG_ERR, "Forcing local-only writability");
		master_addr = INADDR_LOOPBACK;
		return;
	}

	event_init(cb_switch);
	MM_ALLOC(info);
	info->checksum = ni_getchecksum(db_ni);
	info->sin.sin_family = AF_INET;
	info->sin.sin_port = 0;
	MM_ZERO(info->sin.sin_zero);
	info->sin.sin_addr.s_addr = master_addr;
	info->tag = tag;

	transfer_inprogress = 1; /* no thread yet, so no need to lock */
	t = systhread_new();
	systhread_set_name(t, "transfer");
	systhread_run(t, (void (*)(void *))transfer, (void *)info);
}

/*
 * Useful routine for insert the given (key, val) pair as a new property
 * the the given property list.
 */
static void
pl_insert(ni_proplist *props, ni_name_const key, ni_name_const val)
{
	ni_property prop;

	MM_ZERO(&prop);
	prop.nip_name = ni_name_dup(key);
	ni_namelist_insert(&prop.nip_val, val, NI_INDEX_NULL);
	ni_proplist_insert(props, prop, NI_INDEX_NULL);
	ni_prop_free(&prop);
}

/*
 * Create a master server
 */
ni_status
dir_mastercreate(char *domain)
{
	ni_status status;
	ni_name dbname = NULL;
	void *ni;
	ni_property prop;
	ni_name masterloc;
	ni_name serves;
	ni_proplist props;
	ni_id mach_id;
	ni_id mast_id;
	ni_name myname;
	ni_id id;
	interface_list_t *l;
	int i;

	dir_getnames(domain, &dbname, NULL, NULL);
	if (mkdir(dbname, S_IRWXU) < 0)
	{
		ni_name_free(&dbname);
		return (NI_SYSTEMERR);
	}

	status = ni_init(dbname, &ni);
	if (status != NI_OK)
	{
		dir_destroy(dbname);
		ni_name_free(&dbname);
		return (status);
	}
	ni_setuser(ni, ACCESS_USER_SUPER);
	ni_name_free(&dbname);

	/*
	 * Initialize the master property to self
	 */
	myname = (ni_name)sys_hostname();
	masterloc = malloc(strlen(myname) + strlen(domain) + 2);
	sprintf(masterloc, "%s/%s", myname, domain);

	status = ni_root(ni, &id);
	if (status != NI_OK)
	{
		goto cleanup;
	}

	MM_ZERO(&prop);
	prop.nip_name = ni_name_dup(NAME_MASTER);
	ni_namelist_insert(&prop.nip_val, masterloc, NI_INDEX_NULL);
	status = ni_createprop(ni, &id, prop, NI_INDEX_NULL);
	ni_prop_free(&prop);
	if (status != NI_OK)
	{
		goto cleanup;
	}

	/*
	 * Create a "machines" directory
	 */
	MM_ZERO(&props);
	pl_insert(&props, NAME_NAME, NAME_MACHINES);
	mach_id.nii_object = NI_INDEX_NULL;
	status = ni_create(ni, &id, props, &mach_id, NI_INDEX_NULL);
	ni_proplist_free(&props);
	if (status != NI_OK)
	{
		goto cleanup;
	}

	/*
	 * Insert self into "machines" directory
	 */
	MM_ZERO(&props);
	pl_insert(&props, NAME_NAME, myname);

	l = sys_interfaces();
	if (l == NULL)
	{
		status = NI_SYSTEMERR;
		goto cleanup;
	}
	
	if (l->count == 0)
	{
		status = NI_SYSTEMERR;
		goto cleanup;
	}

	MM_ZERO(&prop);
	prop.nip_name = ni_name_dup(NAME_IP_ADDRESS);

	for (i = 0; i < l->count; i++)
	{
		if ((l->interface[i].flags & IFF_UP) == 0) continue;
		if (l->interface[i].flags & IFF_LOOPBACK) continue;
		ni_namelist_insert(&prop.nip_val, inet_ntoa(l->interface[i].addr), NI_INDEX_NULL);
	}
	
	ni_proplist_insert(&props, prop, NI_INDEX_NULL);
	ni_prop_free(&prop);

	serves = malloc(strlen(NAME_DOT) + strlen(domain) + 2);
	sprintf(serves, "%s/%s", NAME_DOT, domain);
	pl_insert(&props, NAME_SERVES, serves);
	ni_name_free(&serves);

	mast_id.nii_object = NI_INDEX_NULL;
	status = ni_create(ni, &mach_id, props, &mast_id, NI_INDEX_NULL);
	ni_proplist_free(&props);
	if (status != NI_OK)
	{
		goto cleanup;
	}

 cleanup:
	ni_free(ni);
	if (status != NI_OK)
	{
		dir_destroy(dbname);
	}
	ni_name_free(&dbname);
	return (status);
}

/*
 * Create a clone server
 */
ni_status
dir_clonecreate(char *domain, char *master_name, char *master_addr,  char *master_domain)
{
	ni_status status;
	ni_name dbname = NULL;
	void *ni;
	ni_property prop;
	ni_name masterloc;
	ni_name serves;
	ni_proplist props;
	ni_id mach_id;
	ni_id mast_id;
	ni_id id;

	dir_getnames(domain, &dbname, NULL, NULL);
	if (mkdir(dbname, S_IRWXU) < 0)
	{
		ni_name_free(&dbname);
		return (NI_SYSTEMERR);
	}

	status = ni_init(dbname, &ni);
	if (status != NI_OK)
	{
		dir_destroy(dbname);
		ni_name_free(&dbname);
		return (status);
	}

	ni_setuser(ni, ACCESS_USER_SUPER);
	ni_name_free(&dbname);

	/*
	 * Initialize master property to real master
	 */
	masterloc = malloc(strlen(master_name) + strlen(master_domain) + 2);
	sprintf(masterloc, "%s/%s", master_name, master_domain);


	status = ni_root(ni, &id);
	if (status != NI_OK)
	{
		goto cleanup;
	}

	MM_ZERO(&prop);
	prop.nip_name = ni_name_dup(NAME_MASTER);
	ni_namelist_insert(&prop.nip_val, masterloc, NI_INDEX_NULL);
	status = ni_createprop(ni, &id, prop, NI_INDEX_NULL);
	ni_prop_free(&prop);
	if (status != NI_OK) {
		goto cleanup;
	}

	/*
	 * Create "machines" directory
	 */
	MM_ZERO(&props);
	pl_insert(&props, NAME_NAME, NAME_MACHINES);
	mach_id.nii_object = NI_INDEX_NULL;
	status = ni_create(ni, &id, props, &mach_id, NI_INDEX_NULL);
	ni_proplist_free(&props);
	if (status != NI_OK)
	{
		goto cleanup;
	}

	/*
	 * Put master in "machines" directory
	 */
	MM_ZERO(&props);
	pl_insert(&props, NAME_NAME, master_name);
	pl_insert(&props, NAME_IP_ADDRESS, master_addr);
	serves = malloc(strlen(NAME_DOT) + strlen(master_domain) + 2);
	sprintf(serves, "%s/%s", NAME_DOT, master_domain);
	pl_insert(&props, NAME_SERVES, serves);
	ni_name_free(&serves);
	mast_id.nii_object = NI_INDEX_NULL;
	status = ni_create(ni, &mach_id, props, &mast_id, NI_INDEX_NULL);
	ni_proplist_free(&props);
	if (status != NI_OK)
	{
		goto cleanup;
	}

 cleanup:
	ni_free(ni);
	if (status != NI_OK) {
		dir_destroy(dbname);
	}
	ni_name_free(&dbname);
	return (status);
}