/*
* 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 2008 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)
{
/*
* 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)
{
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;
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)
{
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;
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;
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;
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;
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);
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);
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;
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;
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;
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;
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;
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;
/* 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;
/*
* 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;
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);
}