/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 *
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file contains routines to manipulate lists of repository values that
 * are used to store process ids and the internal state. There are routines
 * to read/write the lists from/to the repository and routines to modify or
 * inspect the lists. It also contains routines that deal with the
 * repository side of contract ids.
 */

#include <errno.h>
#include <stdlib.h>
#include <libintl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libscf_priv.h>
#include "inetd_impl.h"

/*
 * Number of consecutive repository bind retries performed by bind_to_rep()
 * before failing.
 */
#define	BIND_TO_REP_RETRIES	10

/* Name of property group where inetd's state for a service is stored. */
#define	PG_NAME_INSTANCE_STATE (const char *) "inetd_state"

/* uu_list repval list pool */
static uu_list_pool_t *rep_val_pool = NULL;

/*
 * Repository object pointers that get set-up in repval_init() and closed down
 * in repval_fini(). They're used in _retrieve_rep_vals(), _store_rep_vals(),
 * add_remove_contract_norebind(), and adopt_repository_contracts().  They're
 * global so they can be initialized once on inetd startup, and re-used
 * there-after in the referenced functions.
 */
static scf_handle_t		*rep_handle = NULL;
static scf_propertygroup_t	*pg = NULL;
static scf_instance_t		*inst = NULL;
static scf_transaction_t	*trans = NULL;
static scf_transaction_entry_t	*entry = NULL;
static scf_property_t		*prop = NULL;

/*
 * Pathname storage for paths generated from the fmri.
 * Used when updating the ctid and (start) pid files for an inetd service.
 */
static char genfmri_filename[MAXPATHLEN] = "";
static char genfmri_temp_filename[MAXPATHLEN] = "";

/*
 * Try and make the given handle bind be bound to the repository. If
 * it's already bound, or we succeed a new bind return 0; else return
 * -1 on failure, with the SCF error set to one of the following:
 * SCF_ERROR_NO_SERVER
 * SCF_ERROR_NO_RESOURCES
 */
int
make_handle_bound(scf_handle_t *hdl)
{
	uint_t retries;

	for (retries = 0; retries <= BIND_TO_REP_RETRIES; retries++) {
		if ((scf_handle_bind(hdl) == 0) ||
		    (scf_error() == SCF_ERROR_IN_USE))
			return (0);

		assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT);
	}

	return (-1);
}

int
repval_init(void)
{
	debug_msg("Entering repval_init");

	/*
	 * Create the repval list pool.
	 */
	rep_val_pool = uu_list_pool_create("rep_val_pool", sizeof (rep_val_t),
	    offsetof(rep_val_t, link), NULL, UU_LIST_POOL_DEBUG);
	if (rep_val_pool == NULL) {
		error_msg("%s: %s", gettext("Failed to create rep_val pool"),
		    uu_strerror(uu_error()));
		return (-1);
	}

	/*
	 * Create and bind a repository handle, and create all repository
	 * objects that we'll use later that are associated with it. On any
	 * errors we simply return -1 and let repval_fini() clean-up after
	 * us.
	 */
	if ((rep_handle = scf_handle_create(SCF_VERSION)) == NULL) {
		error_msg("%s: %s",
		    gettext("Failed to create repository handle"),
		    scf_strerror(scf_error()));
		goto cleanup;
	} else if (make_handle_bound(rep_handle) == -1) {
		goto cleanup;
	} else if (((pg = scf_pg_create(rep_handle)) == NULL) ||
	    ((inst = scf_instance_create(rep_handle)) == NULL) ||
	    ((trans = scf_transaction_create(rep_handle)) == NULL) ||
	    ((entry = scf_entry_create(rep_handle)) == NULL) ||
	    ((prop = scf_property_create(rep_handle)) == NULL)) {
		error_msg("%s: %s",
		    gettext("Failed to create repository object"),
		    scf_strerror(scf_error()));
		goto cleanup;
	}

	return (0);
cleanup:
	repval_fini();
	return (-1);
}

void
repval_fini(void)
{
	debug_msg("Entering repval_fini");

	if (rep_handle != NULL) {
		/*
		 * We unbind from the repository before we free the repository
		 * objects for efficiency reasons.
		 */
		(void) scf_handle_unbind(rep_handle);

		scf_pg_destroy(pg);
		pg = NULL;
		scf_instance_destroy(inst);
		inst = NULL;
		scf_transaction_destroy(trans);
		trans = NULL;
		scf_entry_destroy(entry);
		entry = NULL;
		scf_property_destroy(prop);
		prop = NULL;

		scf_handle_destroy(rep_handle);
		rep_handle = NULL;
	}

	if (rep_val_pool != NULL) {
		uu_list_pool_destroy(rep_val_pool);
		rep_val_pool = NULL;
	}
}

uu_list_t *
create_rep_val_list(void)
{
	uu_list_t	*ret;

	debug_msg("Entering create_rep_val_list");

	if ((ret = uu_list_create(rep_val_pool, NULL, 0)) == NULL)
		assert(uu_error() == UU_ERROR_NO_MEMORY);

	return (ret);
}

void
destroy_rep_val_list(uu_list_t *list)
{
	debug_msg("Entering destroy_rep_val_list");

	if (list != NULL) {
		empty_rep_val_list(list);
		uu_list_destroy(list);
	}
}

rep_val_t *
find_rep_val(uu_list_t *list, int64_t val)
{
	rep_val_t *rv;

	debug_msg("Entering find_rep_val: val: %lld", val);

	for (rv = uu_list_first(list); rv != NULL;
	    rv = uu_list_next(list, rv)) {
		if (rv->val == val)
			break;
	}
	return (rv);
}

int
add_rep_val(uu_list_t *list, int64_t val)
{
	rep_val_t *rv;

	debug_msg("Entering add_rep_val: val: %lld", val);

	if ((rv = malloc(sizeof (rep_val_t))) == NULL)
		return (-1);

	uu_list_node_init(rv, &rv->link, rep_val_pool);
	rv->val = val;
	rv->scf_val = NULL;
	(void) uu_list_insert_after(list, NULL, rv);

	return (0);
}

void
remove_rep_val(uu_list_t *list, int64_t val)
{
	rep_val_t *rv;

	debug_msg("Entering remove_rep_val: val: %lld", val);

	if ((rv = find_rep_val(list, val)) != NULL) {
		uu_list_remove(list, rv);
		assert(rv->scf_val == NULL);
		free(rv);
	}
}

void
empty_rep_val_list(uu_list_t *list)
{
	void		*cookie = NULL;
	rep_val_t	*rv;

	debug_msg("Entering empty_rep_val_list");

	while ((rv = uu_list_teardown(list, &cookie)) != NULL) {
		if (rv->scf_val != NULL)
			scf_value_destroy(rv->scf_val);
		free(rv);
	}
}

int64_t
get_single_rep_val(uu_list_t *list)
{
	rep_val_t *rv = uu_list_first(list);

	debug_msg("Entering get_single_rep_val");

	assert(rv != NULL);
	return (rv->val);
}

int
set_single_rep_val(uu_list_t *list, int64_t val)
{
	rep_val_t *rv = uu_list_first(list);

	debug_msg("Entering set_single_rep_val");

	if (rv == NULL) {
		if (add_rep_val(list, val) == -1)
			return (-1);
	} else {
		rv->val = val;
	}

	return (0);
}

/*
 * Partner to add_tr_entry_values. This function frees the scf_values created
 * in add_tr_entry_values() in the list 'vals'.
 */
static void
remove_tr_entry_values(uu_list_t *vals)
{
	rep_val_t	*rval;

	debug_msg("Entering remove_tr_entry_values");

	for (rval = uu_list_first(vals); rval != NULL;
	    rval = uu_list_next(vals, rval)) {
		if (rval->scf_val != NULL) {
			scf_value_destroy(rval->scf_val);
			rval->scf_val = NULL;
		}
	}
}

/*
 * This function creates and associates with transaction entry 'entry' an
 * scf value for each value in 'vals'. The pointers to the scf values
 * are stored in the list for later cleanup by remove_tr_entry_values.
 * Returns 0 on success, else -1 on error with scf_error() set to:
 * SCF_ERROR_NO_MEMORY if memory allocation failed.
 * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken.
 */
static int
add_tr_entry_values(scf_handle_t *hdl, scf_transaction_entry_t *entry,
    uu_list_t *vals)
{
	rep_val_t *rval;

	debug_msg("Entering add_tr_entry_values");

	for (rval = uu_list_first(vals); rval != NULL;
	    rval = uu_list_next(vals, rval)) {

		assert(rval->scf_val == NULL);
		if ((rval->scf_val = scf_value_create(hdl)) == NULL) {
			remove_tr_entry_values(vals);
			return (-1);
		}

		scf_value_set_integer(rval->scf_val, rval->val);

		if (scf_entry_add_value(entry, rval->scf_val) < 0) {
			remove_tr_entry_values(vals);
			return (-1);
		}
	}

	return (0);
}

/*
 * Stores the values contained in the list 'vals' into the property 'prop_name'
 * of the instance with fmri 'inst_fmri', within the instance's instance
 * state property group.
 *
 * Returns 0 on success, else one of the following on failure:
 * SCF_ERROR_NO_MEMORY if memory allocation failed.
 * SCF_ERROR_NO_RESOURCES if the server doesn't have required resources.
 * SCF_ERROR_VERSION_MISMATCH if program compiled against a newer libscf
 * than on system.
 * SCF_ERROR_PERMISSION_DENIED if insufficient privileges to modify pg.
 * SCF_ERROR_BACKEND_ACCESS if the repository back-end refused the pg modify.
 * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken.
 */
static scf_error_t
_store_rep_vals(uu_list_t *vals, const char *inst_fmri, const char *prop_name)
{
	int			cret;
	int			ret;

	debug_msg("Entering _store_rep_vals: fmri: %s, prop: %s", inst_fmri,
	    prop_name);

	if (scf_handle_decode_fmri(rep_handle, inst_fmri, NULL, NULL, inst,
	    NULL, NULL, SCF_DECODE_FMRI_EXACT) == -1)
		return (scf_error());

	/*
	 * Fetch the instance state pg, and if it doesn't exist try and
	 * create it.
	 */
	if (scf_instance_get_pg(inst, PG_NAME_INSTANCE_STATE, pg) < 0) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			return (scf_error());
		if (scf_instance_add_pg(inst, PG_NAME_INSTANCE_STATE,
		    SCF_GROUP_FRAMEWORK, SCF_PG_FLAG_NONPERSISTENT, pg) < 0)
			return (scf_error());
	}

	/*
	 * Perform a transaction to write the values to the requested property.
	 * If someone got there before us, loop and retry.
	 */
	do {
		if (scf_transaction_start(trans, pg) < 0)
			return (scf_error());

		if ((scf_transaction_property_new(trans, entry,
		    prop_name, SCF_TYPE_INTEGER) < 0) &&
		    (scf_transaction_property_change_type(trans, entry,
		    prop_name, SCF_TYPE_INTEGER) < 0)) {
			ret = scf_error();
			goto cleanup;
		}

		if (add_tr_entry_values(rep_handle, entry, vals) < 0) {
			ret = scf_error();
			goto cleanup;
		}

		if ((cret = scf_transaction_commit(trans)) < 0) {
			ret = scf_error();
			goto cleanup;
		} else if (cret == 0) {
			scf_transaction_reset(trans);
			scf_entry_reset(entry);
			remove_tr_entry_values(vals);
			if (scf_pg_update(pg) < 0) {
				ret = scf_error();
				goto cleanup;
			}
		}
	} while (cret == 0);

	ret = 0;
cleanup:
	scf_transaction_reset(trans);
	scf_entry_reset(entry);
	remove_tr_entry_values(vals);
	return (ret);
}

/*
 * Retrieves the repository values of property 'prop_name', of the instance
 * with fmri 'fmri', from within the instance's instance state property
 * group and adds them to the value list 'list'.
 *
 * Returns 0 on success, else one of the following values on error:
 * SCF_ERROR_NOT_FOUND if the property doesn't exist.
 * SCF_ERROR_NO_MEMORY if memory allocation failed.
 * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken.
 * SCF_ERROR_TYPE_MISMATCH if the property was of an unexpected type.
 *
 */
static scf_error_t
_retrieve_rep_vals(uu_list_t *list, const char *fmri, const char *prop_name)
{
	scf_simple_prop_t	*sp;
	int64_t			*ip;

	debug_msg("Entering _retrieve_rep_vals: fmri: %s, prop: %s", fmri,
	    prop_name);

	if ((sp = scf_simple_prop_get(rep_handle, fmri, PG_NAME_INSTANCE_STATE,
	    prop_name)) == NULL)
		return (scf_error());

	while ((ip = scf_simple_prop_next_integer(sp)) != NULL) {
		if (add_rep_val(list, *ip) == -1) {
			empty_rep_val_list(list);
			scf_simple_prop_free(sp);
			return (SCF_ERROR_NO_MEMORY);
		}
	}
	if (scf_error() != SCF_ERROR_NONE) {
		assert(scf_error() == SCF_ERROR_TYPE_MISMATCH);
		empty_rep_val_list(list);
		scf_simple_prop_free(sp);
		return (scf_error());
	}

	scf_simple_prop_free(sp);
	return (0);
}

/*
 * Writes the repository values in the vals list to
 * a file that is generated based on the passed in fmri and name.
 * Returns 0 on success,
 * ENAMETOOLONG if unable to generate filename from fmri (including
 * the inability to create the directory for the generated filename) and
 * ENOENT on all other failures.
 */
static int
repvals_to_file(const char *fmri, const char *name, uu_list_t *vals)
{
	int		tfd;
	FILE		*tfp;		/* temp fp */
	rep_val_t	*spval;		/* Contains a start_pid or ctid */
	int		ret = 0;

	debug_msg("Entering repvals_to_file, fmri:%s, name=%s\n",
	    fmri, name);

	if (gen_filenms_from_fmri(fmri, name, genfmri_filename,
	    genfmri_temp_filename) != 0) {
		/* Failure either from fmri too long or mkdir failure */
		return (ENAMETOOLONG);
	}

	if ((tfd = mkstemp(genfmri_temp_filename)) == -1) {
		return (ENOENT);
	}

	if (fchmod(tfd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
		(void) close(tfd);
		ret = ENOENT;
		goto unlink_out;
	}

	if ((tfp = fdopen(tfd, "w")) == NULL) {
		(void) close(tfd);
		ret = ENOENT;
		goto unlink_out;
	}

	for (spval = uu_list_first(vals); spval != NULL;
	    spval = uu_list_next(vals, spval)) {
		if (fprintf(tfp, "%lld\n", spval->val) <= 0) {
			(void) fclose(tfp);
			ret = ENOENT;
			goto unlink_out;
		}
	}
	if (fclose(tfp) != 0) {
		ret = ENOENT;
		goto unlink_out;
	}
	if (rename(genfmri_temp_filename, genfmri_filename) != 0) {
		ret = ENOENT;
		goto unlink_out;
	}
	return (0);

unlink_out:
	if (unlink(genfmri_temp_filename) != 0) {
		warn_msg(gettext("Removal of temp file "
		    "%s failed. Please remove manually."),
		    genfmri_temp_filename);
	}
	return (ret);
}

/*
 * A routine that loops trying to read/write values until either success,
 * an error other than a broken repository connection or
 * the number of retries reaches REP_OP_RETRIES.
 * This action is used to read/write the values:
 *   reads/writes to a file for the START_PIDS property due to scalability
 *	problems with libscf
 *   reads/writes to the repository for all other properties.
 * Returns 0 on success, else the error value from either _store_rep_vals or
 * _retrieve_rep_vals (based on whether 'store' was set or not), or one of the
 * following:
 * SCF_ERROR_NO_RESOURCES if the server doesn't have adequate resources
 * SCF_ERROR_NO_MEMORY if a memory allocation failure
 * SCF_ERROR_NO_SERVER if the server isn't running.
 * SCF_ERROR_CONSTRAINT_VIOLATED if an error in dealing with the speedy files
 */
static scf_error_t
store_retrieve_rep_vals(uu_list_t *vals, const char *fmri,
    const char *prop, boolean_t store)
{
	scf_error_t	ret = 0;
	uint_t		retries;
	FILE		*tfp;		/* temp fp */
	int64_t		tval;		/* temp val holder */
	int		fscanf_ret;
	int		fopen_retry_cnt = 2;

	debug_msg("Entering store_retrieve_rep_vals, store: %d", store);

	/* inetd specific action for START_PIDS property */
	if (strcmp(prop, PR_NAME_START_PIDS) == 0) {
		/*
		 * Storage performance of START_PIDS is important,
		 * so each instance has its own file and all start_pids
		 * in the list are written to a temp file and then
		 * moved (renamed).
		 */
		if (store) {
			/* Write all values in list to file */
			if (repvals_to_file(fmri, "pid", vals)) {
				return (SCF_ERROR_CONSTRAINT_VIOLATED);
			}
		} else {
			/* no temp name needed */
			if (gen_filenms_from_fmri(fmri, "pid", genfmri_filename,
			    NULL) != 0)
				return (SCF_ERROR_CONSTRAINT_VIOLATED);

retry_fopen:
			/* It's ok if no file, there are just no pids */
			if ((tfp = fopen(genfmri_filename, "r")) == NULL) {
				if ((errno == EINTR) && (fopen_retry_cnt > 0)) {
					fopen_retry_cnt--;
					goto retry_fopen;
				}
				return (0);
			}
			/* fscanf may not set errno, so clear it first */
			errno = 0;
			while ((fscanf_ret = fscanf(tfp, "%lld", &tval)) == 1) {
				/* If tval isn't a valid pid, then fail. */
				if ((tval > MAXPID) || (tval <= 0)) {
					empty_rep_val_list(vals);
					return (SCF_ERROR_CONSTRAINT_VIOLATED);
				}
				if (add_rep_val(vals, tval) == -1) {
					empty_rep_val_list(vals);
					return (SCF_ERROR_NO_MEMORY);
				}
				errno = 0;
			}
			/* EOF is ok when no errno */
			if ((fscanf_ret != EOF) || (errno != 0)) {
				empty_rep_val_list(vals);
				return (SCF_ERROR_CONSTRAINT_VIOLATED);
			}
			if (fclose(tfp) != 0) {
				/* for close failure just log a message */
				warn_msg(gettext("Close of file %s failed."),
				    genfmri_filename);
			}
		}
	} else {
		for (retries = 0; retries <= REP_OP_RETRIES; retries++) {
			if (make_handle_bound(rep_handle) == -1) {
				ret = scf_error();
				break;
			}

			if ((ret = (store ? _store_rep_vals(vals, fmri, prop) :
			    _retrieve_rep_vals(vals, fmri, prop))) !=
			    SCF_ERROR_CONNECTION_BROKEN)
				break;

			(void) scf_handle_unbind(rep_handle);
		}
	}

out:
	return (ret);
}

scf_error_t
store_rep_vals(uu_list_t *vals, const char *fmri, const char *prop)
{
	return (store_retrieve_rep_vals(vals, fmri, prop, B_TRUE));
}

scf_error_t
retrieve_rep_vals(uu_list_t *vals, const char *fmri, const char *prop)
{
	return (store_retrieve_rep_vals(vals, fmri, prop, B_FALSE));
}

/*
 * Adds/removes a contract id to/from the cached list kept in the instance.
 * Then the cached list is written to a file named "ctid" in a directory
 * based on the fmri.  Cached list is written to a file due to scalability
 * problems in libscf.  The file "ctid" is used when inetd is restarted
 * so that inetd can adopt the contracts that it had previously.
 * Returns:
 *   0 on success
 *   ENAMETOOLONG if unable to generate filename from fmri (including
 *   the inability to create the directory for the generated filename)
 *   ENOENT - failure accessing file
 *   ENOMEM - memory allocation failure
 */
int
add_remove_contract(instance_t *inst, boolean_t add, ctid_t ctid)
{
	FILE		*tfp;		/* temp fp */
	int		ret = 0;
	int		repval_ret = 0;
	int		fopen_retry_cnt = 2;

	debug_msg("Entering add_remove_contract, add: %d\n", add);

	/*
	 * Storage performance of contract ids is important,
	 * so each instance has its own file.  An add of a
	 * ctid will be appended to the ctid file.
	 * The removal of a ctid will result in the remaining
	 * ctids in the list being written to a temp file and then
	 * moved (renamed).
	 */
	if (add) {
		if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename,
		    NULL) != 0) {
			/* Failure either from fmri too long or mkdir failure */
			return (ENAMETOOLONG);
		}

retry_fopen:
		if ((tfp = fopen(genfmri_filename, "a")) == NULL) {
			if ((errno == EINTR) && (fopen_retry_cnt > 0)) {
				fopen_retry_cnt--;
				goto retry_fopen;
			}
			ret = ENOENT;
			goto out;
		}

		/* Always store ctids as long long */
		if (fprintf(tfp, "%llu\n", (uint64_t)ctid) <= 0) {
			(void) fclose(tfp);
			ret = ENOENT;
			goto out;
		}

		if (fclose(tfp) != 0) {
			ret = ENOENT;
			goto out;
		}

		if (add_rep_val(inst->start_ctids, ctid) != 0) {
			ret = ENOMEM;
			goto out;
		}
	} else {
		remove_rep_val(inst->start_ctids, ctid);

		/* Write all values in list to file */
		if ((repval_ret = repvals_to_file(inst->fmri, "ctid",
		    inst->start_ctids)) != 0) {
			ret = repval_ret;
			goto out;
		}
	}

out:
	return (ret);
}

/*
 * If sig !=0, iterate over all contracts in the cached list of contract
 * ids kept in the instance.  Send each contract the specified signal.
 * If sig == 0, read in the contract ids that were last associated
 * with this instance (reload the cache) and call adopt_contract()
 * to take ownership.
 *
 * Returns 0 on success;
 * ENAMETOOLONG if unable to generate filename from fmri (including
 * the inability to create the directory for the generated filename) and
 * ENXIO if a failure accessing the file
 * ENOMEM if there was a memory allocation failure
 * ENOENT if the instance, its restarter property group, or its
 *   contract property don't exist
 * EIO if invalid data read from the file
 */
int
iterate_repository_contracts(instance_t *inst, int sig)
{
	int		ret = 0;
	FILE		*fp;
	rep_val_t	*spval = NULL;	/* Contains a start_pid */
	uint64_t	tval;		/* temp val holder */
	uu_list_t	*uup = NULL;
	int		fscanf_ret;
	int		fopen_retry_cnt = 2;

	debug_msg("Entering iterate_repository_contracts, sig: %d", sig);

	if (sig != 0) {
		/*
		 * Send a signal to all in the contract; ESRCH just
		 * means they all exited before we could kill them
		 */
		for (spval = uu_list_first(inst->start_ctids); spval != NULL;
		    spval = uu_list_next(inst->start_ctids, spval)) {
			if (sigsend(P_CTID, (ctid_t)spval->val, sig) == -1 &&
			    errno != ESRCH) {
				warn_msg(gettext("Unable to signal all "
				    "contract members of instance %s: %s"),
				    inst->fmri, strerror(errno));
			}
		}
		return (0);
	}

	/*
	 * sig == 0 case.
	 * Attempt to adopt the contract for each ctid.
	 */
	if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename,
	    NULL) != 0) {
		/* Failure either from fmri too long or mkdir failure */
		return (ENAMETOOLONG);
	}

retry_fopen:
	/* It's ok if no file, there are no ctids to adopt */
	if ((fp = fopen(genfmri_filename, "r")) == NULL) {
		if ((errno == EINTR) && (fopen_retry_cnt > 0)) {
			fopen_retry_cnt--;
			goto retry_fopen;
		}
		return (0);
	}

	/*
	 * Read ctids from file into 2 lists:
	 * - temporary list to be traversed (uup)
	 * - cached list that can be modified if adoption of
	 *   contract fails (inst->start_ctids).
	 * Always treat ctids as long longs.
	 */
	uup = create_rep_val_list();
	/* fscanf may not set errno, so clear it first */
	errno = 0;
	while ((fscanf_ret = fscanf(fp, "%llu", &tval)) == 1) {
		/* If tval isn't a valid ctid, then fail. */
		if (tval == 0) {
			(void) fclose(fp);
			ret = EIO;
			goto out;
		}
		if ((add_rep_val(uup, tval) == -1) ||
		    (add_rep_val(inst->start_ctids, tval) == -1)) {
			(void) fclose(fp);
			ret = ENOMEM;
			goto out;
		}
		errno = 0;
	}
	/* EOF is not a failure when no errno */
	if ((fscanf_ret != EOF) || (errno != 0)) {
		ret = EIO;
		goto out;
	}

	if (fclose(fp) != 0) {
		ret = ENXIO;
		goto out;
	}

	for (spval = uu_list_first(uup); spval != NULL;
	    spval = uu_list_next(uup, spval)) {
		/* Try to adopt the contract */
		if (adopt_contract((ctid_t)spval->val,
		    inst->fmri) != 0) {
			/*
			 * Adoption failed.  No reason to think it'll
			 * work later, so remove the id from our list
			 * in the instance.
			 */
			remove_rep_val(inst->start_ctids, spval->val);
		}
	}
out:
	if (uup) {
		empty_rep_val_list(uup);
		destroy_rep_val_list(uup);
	}

	if (ret != 0)
		empty_rep_val_list(inst->start_ctids);

	return (ret);
}