components/visual-panels/smf/src/cmd/rad/mod/smf/smfutil.c
author Gary Pennington <gary.pennington@oracle.com>
Sat, 30 Jun 2012 06:30:16 -0700
changeset 901 19b502ccabc8
parent 827 0944d8c0158b
permissions -rw-r--r--
7179786 libadr rename impacts various modules in the userland consolidation

/*
 * 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 (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/list.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>

#include <libscf.h>
#include <libscf_priv.h>

#include <rad/adr.h>
#include <rad/rad_modapi.h>

#include "api_smf.h"
#include "rhandle.h"
#include "smfutil.h"

/* Shared with other files */
ssize_t max_fmri;
ssize_t max_name;
ssize_t max_value;
ssize_t max_pgtype;

svcerr_t
error_scf(adr_data_t **error, adr_data_t *code, adr_data_t *target,
    const char *aux, const char *msg)
{
	assert(adr_data_type(code) == &t__ErrorCode);
	assert(target == NULL || adr_data_type(target) == &t__ErrorTarget);

	if (error != NULL) {
		adr_data_t *e = adr_data_new_struct(&t__SmfError);
		adr_struct_set(e, "error", code);
		adr_struct_set(e, "target", target == NULL ?
		    &e__ErrorTarget_NONE : target);
		if (aux != NULL)
			adr_struct_set(e, "aux",
			    adr_data_new_string(aux, LT_COPY));
		if (msg != NULL)
			adr_struct_set(e, "message",
			    adr_data_new_string(msg, LT_COPY));
		*error = adr_data_purify(e);
	}
	return (SE_FATAL);
}

svcerr_t
internal_error(adr_data_t **error, const char *msg)
{
	return (error_scf(error, &e__ErrorCode_INTERNAL, NULL, NULL, msg));
}

/*
 * Generic error-mapping routine.
 *
 * Maps connection errors to SE_RECONNECT, deletions to SE_RETRY,
 * notfound to SE_NOTFOUND, and everything else to SE_FATAL.
 */
svcerr_t
smfu_maperr(scf_error_t e)
{
	switch (e) {
	case SCF_ERROR_DELETED:
		return (SE_RETRY);
	case SCF_ERROR_NO_SERVER:
	case SCF_ERROR_NOT_BOUND:
	case SCF_ERROR_CONNECTION_BROKEN:
		return (SE_RECONNECT);
	case SCF_ERROR_NOT_FOUND:
		return (SE_NOTFOUND);
	default:
		return (SE_FATAL);
	}
}


/*
 * Reads the specified instance's "enabled" property from the named
 * property group.  Returns an error code or 0 on success.
 */
scf_error_t
smfu_read_enabled(scf_instance_t *inst, scf_propertygroup_t *pg,
    scf_property_t *prop, scf_value_t *value, const char *pgname, uint8_t *bool)
{
	if (scf_instance_get_pg(inst, pgname, pg) == -1 ||
	    scf_pg_get_property(pg, SCF_PROPERTY_ENABLED, prop) == -1 ||
	    scf_property_get_value(prop, value) == -1 ||
	    scf_value_get_boolean(value, bool) == -1)
		return (scf_error());
	return (0);
}

/*
 * Gets a value as a string, allocating the necessary buffer and
 * returning it in *strp.  Returns an error code or 0 on success.
 */
scf_error_t
smfu_value_get_string(scf_value_t *val, char **strp)
{
	ssize_t len, l;
	char *s = NULL;

	len = scf_value_get_as_string(val, NULL, 0);
	for (; len != -1; len = l) {
		s = malloc(len + 1);
		if (s == NULL)
			return (SCF_ERROR_NO_MEMORY);

		l = scf_value_get_as_string(val, s, len + 1);
		if (l == len) {
			*strp = s;
			return (0);
		}
		free(s);
	}

	return (scf_error());
}

/*
 * Gets the named pg in the composed view of the named snapshot. If
 * snapname is not specified, the current pgs/properties are returned.
 * The caller must allocate the pg.  Returns an error code or SE_OK
 * on success.
 */
svcerr_t
smfu_instance_get_composed_pg(scf_handle_t *handle, scf_instance_t *instance,
    const char *snapname, const char *pgname, scf_propertygroup_t *pg,
    adr_data_t **error)
{
	svcerr_t se = SE_OK;
	scf_snapshot_t *snap = NULL;

	if (snapname != NULL) {
		if ((snap = scf_snapshot_create(handle)) == NULL)
			return (SE_FATAL);
		if (scf_instance_get_snapshot(instance, snapname,
		    snap) == -1) {
			if ((se = smfu_maperr(scf_error())) == SE_NOTFOUND)
				(void) error_scf(error, &e__ErrorCode_NOTFOUND,
				    &e__ErrorTarget_SNAPSHOT, snapname, NULL);
			scf_snapshot_destroy(snap);
			return (se);
		}
	}
	if (scf_instance_get_pg_composed(instance, snap, pgname,
	    pg) == -1) {
		if ((se = smfu_maperr(scf_error())) == SE_NOTFOUND)
			(void) error_scf(error, &e__ErrorCode_NOTFOUND,
			    &e__ErrorTarget_PROPERTYGROUP, pgname, NULL);
	}
	if (snap != NULL)
		scf_snapshot_destroy(snap);
	return (se);
}

/*
 * Wrappers around the scf_iter_{instance,service} routines that take an
 * optional type.  Behave like normal scf_* routines on error.
 */

static int
iter_instance_pgs(scf_iter_t *iter, scf_instance_t *inst, const char *type)
{
	if (type == NULL)
		return (scf_iter_instance_pgs(iter, inst));
	return (scf_iter_instance_pgs_typed(iter, inst, type));
}

static int
iter_instance_pgs_composed(scf_iter_t *iter, scf_instance_t *inst,
    scf_snapshot_t *snap, const char *type)
{
	if (type == NULL)
		return (scf_iter_instance_pgs_composed(iter, inst, snap));
	return (scf_iter_instance_pgs_typed_composed(iter, inst, snap, type));
}

static int
iter_service_pgs(scf_iter_t *iter, scf_service_t *service, const char *type)
{
	if (type == NULL)
		return (scf_iter_service_pgs(iter, service));
	return (scf_iter_service_pgs_typed(iter, service, type));
}


/*
 * Iterates over pgs in the composed view of the named snapshot. If
 * snapname is not specified, the current pgs/properties are used. A
 * property group type may be specified.  Returns an error code or 0
 * on success.
 */
scf_error_t
smfu_instance_iter_composed_pgs(scf_handle_t *handle, scf_instance_t *instance,
    const char *snapname, const char *pgtype, scf_iter_t *iter)
{
	int err = 0;
	scf_snapshot_t *snap = NULL;
	if (snapname != NULL) {
		if ((snap = scf_snapshot_create(handle)) == NULL ||
		    scf_instance_get_snapshot(instance, snapname, snap) == -1) {
			err = scf_error();
			goto done;
		}
	}
	if (iter_instance_pgs_composed(iter, instance, snap, pgtype) == -1)
		err = scf_error();

done:
	if (snap != NULL)
		scf_snapshot_destroy(snap);
	return (err);
}


/*
 * Operates like scf_pg_get_property, but takes a fallback property
 * that is returned if the first one isn't found.  Behaves like normal
 * scf_* routines on error.
 */
static int
smfu_pg_get_fb_prop(scf_propertygroup_t *pg,
    const char *name, const char *fbname, scf_property_t *prop)
{
	int res = scf_pg_get_property(pg, name, prop);
	if (res != 0 && scf_error() == SCF_ERROR_NOT_FOUND)
		res = scf_pg_get_property(pg, fbname, prop);
	return (res);
}

/*
 * Reads a string value from a traditionally localized template
 * property group (i.e. description, common name).  First tries the
 * specified locale and then falls back to the C locale.
 */
svcerr_t
smfu_get_l10n_str(scf_handle_t *handle, scf_propertygroup_t *pg,
    const char *locale, char **result)
{
	scf_property_t *prop = scf_property_create(handle);
	scf_value_t *value = scf_value_create(handle);
	svcerr_t se = SE_OK;

	if (prop == NULL || value == NULL) {
		se = SE_FATAL;
		goto done;
	}

	if (smfu_pg_get_fb_prop(pg, locale, "C", prop) != 0 ||
	    scf_property_get_value(prop, value) != 0) {
		se = smfu_maperr(scf_error());
		goto done;
	}

	scf_error_t serr = smfu_value_get_string(value, result);
	if (serr != 0)
		se = smfu_maperr(serr);

done:
	scf_property_destroy(prop);
	scf_value_destroy(value);
	return (se);
}

/*
 * Obtains the specified property group of the service/instance
 * identified by "entity".  For a service, returns the directly
 * attached property group, for an instance, returns the property group
 * from the composed view of the running snapshot.
 */
svcerr_t
smfu_get_pg_r(scf_handle_t *handle, smfu_entity_t *entity, const char *name,
    scf_propertygroup_t *pg)
{
	svcerr_t se = SE_OK;

	if (entity->instance != NULL) {
		se = smfu_instance_get_composed_pg(handle, entity->instance,
		    "running", name, pg, NULL);
	} else if (scf_service_get_pg(entity->service, name, pg) != 0) {
		se = smfu_maperr(scf_error());
	}

	return (se);
}

/*
 * Iterates over the property groups, of type "type" if "type" is
 * non-NULL, of the service/instance identified by "entity".  For a
 * service, iterates over directly attached property groups, for an
 * instance, iterates over the property groups in the composed view of
 * the running snapshot.
 */
svcerr_t
smfu_iter_pg_r(scf_handle_t *handle, smfu_entity_t *entity, const char *type,
    scf_iter_t *iter)
{
	svcerr_t se = SE_OK;

	if (entity->instance != NULL) {
		scf_error_t scferr = smfu_instance_iter_composed_pgs(handle,
		    entity->instance, "running", type, iter);
		if (scferr != 0)
			se = smfu_maperr(scferr);
	} else if (iter_service_pgs(iter, entity->service, type) != 0) {
		se = smfu_maperr(scf_error());
	}

	return (se);
}

/*
 * Obtains the specified property group of the service/instance
 * identified by "entity".  For both services and instances, returns
 * the directly attached property group.
 */
svcerr_t
smfu_get_pg(smfu_entity_t *entity, const char *name,
    scf_propertygroup_t *pg, adr_data_t **error)
{
	svcerr_t se = SE_OK;

	if (entity->instance != NULL) {
		if (scf_instance_get_pg(entity->instance, name, pg) != 0)
			se = smfu_maperr(scf_error());
	} else if (scf_service_get_pg(entity->service, name, pg) != 0) {
		se = smfu_maperr(scf_error());
	}

	if (se == SE_NOTFOUND)
		(void) error_scf(error, &e__ErrorCode_NOTFOUND,
		    &e__ErrorTarget_PROPERTYGROUP, NULL, NULL);

	return (se);
}

/*
 * Iterates over the property groups, of type "type" if "type" is
 * non-NULL, of the service/instance identified by "entity".  For both
 * services and instances, iterates over directly attached property
 * groups.
 */
svcerr_t
smfu_iter_pg(smfu_entity_t *entity, const char *type,
    scf_iter_t *iter)
{
	svcerr_t se = SE_OK;

	if (entity->instance != NULL) {
		if (iter_instance_pgs(iter, entity->instance, type) != 0)
			se = smfu_maperr(scf_error());
	} else if (iter_service_pgs(iter, entity->service, type) != 0) {
		se = smfu_maperr(scf_error());
	}

	return (se);
}

/*
 * Iterates over all services and instances on the system.  Calls scb()
 * on each service if scb is non-NULL.  Calls icb() on each instance if
 * icb is non-NULL.
 */
svcerr_t
smfu_iter_svcs(scf_handle_t *h, svc_callback_t scb, inst_callback_t icb,
    void *arg)
{
	svcerr_t se = SE_OK;

	scf_scope_t *scope = scf_scope_create(h);
	scf_iter_t *siter = scf_iter_create(h);
	scf_iter_t *iiter = scf_iter_create(h);
	scf_service_t *service = scf_service_create(h);
	scf_instance_t *instance = scf_instance_create(h);
	char sname[max_name + 1];
	char iname[max_name + 1];

	if (scope == NULL || siter == NULL || iiter == NULL ||
	    service == NULL || instance == NULL) {
		se = SE_FATAL;
		goto out;
	}

	if (scf_handle_get_scope(h, SCF_SCOPE_LOCAL, scope) == -1 ||
	    scf_iter_scope_services(siter, scope) == -1) {
		se = smfu_maperr(scf_error());
		goto out;
	}

	int e;
	while ((e = scf_iter_next_service(siter, service)) > 0) {
		if (scf_service_get_name(service, sname, sizeof (sname))
		    == -1) {
			se = smfu_maperr(scf_error());
			goto out;
		}
		if (scb) {
			if ((se = scb(h, service, sname, arg)) != SE_OK)
				goto out;
		}

		if (icb) {
			if (scf_iter_service_instances(iiter, service) == -1) {
				se = smfu_maperr(scf_error());
				goto out;
			}
			while (scf_iter_next_instance(iiter, instance) > 0) {
				if (scf_instance_get_name(instance, iname,
				    sizeof (iname)) == -1) {
					se = smfu_maperr(scf_error());
					goto out;
				}
				se = icb(h, instance, sname, iname, arg);
				if (se != SE_OK)
					goto out;
			}
		}
	}
	if (e != 0)
		se = smfu_maperr(scf_error());

out:
	scf_instance_destroy(instance);
	scf_service_destroy(service);
	scf_iter_destroy(iiter);
	scf_iter_destroy(siter);
	scf_scope_destroy(scope);
	return (se == SE_BREAK ? SE_OK : se);
}

/*
 * Look up an entity based on service and (optional) instance name.
 */
svcerr_t
smfu_lookup(scf_handle_t *scfhandle, const char *sname, const char *iname,
    smfu_entity_t *entity)
{
	svcerr_t se = SE_FATAL;
	scf_scope_t *scope = scf_scope_create(scfhandle);
	scf_service_t *service = scf_service_create(scfhandle);
	scf_instance_t *instance = scf_instance_create(scfhandle);

	if (scope == NULL || service == NULL || instance == NULL) {
		se = SE_FATAL;
		goto error;
	}

	if (scf_handle_get_scope(scfhandle, SCF_SCOPE_LOCAL, scope) != 0 ||
	    scf_scope_get_service(scope, sname, service) != 0) {
		se = smfu_maperr(scf_error());
		goto error;
	}

	if (iname != NULL) {
		if (scf_service_get_instance(service, iname, instance) != 0) {
			se = smfu_maperr(scf_error());
			goto error;
		}
		entity->instance = instance;
	}

	entity->service = service;
	scf_scope_destroy(scope);
	return (SE_OK);

error:
	scf_instance_destroy(instance);
	scf_service_destroy(service);
	scf_scope_destroy(scope);

	if (se == SE_NOTFOUND)
		return (SE_STALE);
	return (se);
}

/*
 * Free an entity.
 */
void
smfu_entity_destroy(smfu_entity_t *entity)
{
	scf_service_destroy(entity->service);
	scf_instance_destroy(entity->instance);
}


/*
 * Test for the existence of service sname, optionally with instance iname.
 */
svcerr_t
smfu_test(const char *sname, const char *iname)
{
	svcerr_t se;
	do {
		rad_handle_t *rh = rh_fetch();
		if (rh == NULL) {
			se = smfu_maperr(scf_error());
		} else {
			smfu_entity_t entity = SMFU_ENTITY_INIT;
			se = smfu_lookup(rh_hdl(rh), sname, iname, &entity);
			smfu_entity_destroy(&entity);
		}

		if (se == SE_RECONNECT)
			rh_kill(rh);
		rh_rele(rh);
	} while (se == SE_RETRY || se == SE_RECONNECT);

	return (se);
}

/*
 * Construct the ADR name for a service/instance.
 */
adr_name_t *
smfu_name_alloc(const char *sname, const char *iname)
{
	if (iname != NULL)
		return (adr_name_vcreate(SMF_DOMAIN, 3,
		    SMF_KEY_TYPE, SMF_TYPE_INSTANCE,
		    SMF_KEY_SERVICE, sname,
		    SMF_KEY_INSTANCE, iname));
	return (adr_name_vcreate(SMF_DOMAIN, 2,
	    SMF_KEY_TYPE, SMF_TYPE_SERVICE,
	    SMF_KEY_SERVICE, sname));
}

/*
 * Construct the FMRI for a service/instance.
 */
char *
smfu_fmri_alloc(const char *sname, const char *iname)
{
	char *result = NULL;
	if (iname != NULL)
		(void) asprintf(&result, "svc:/%s:%s", sname, iname);
	else
		(void) asprintf(&result, "svc:/%s", sname);
	return (result);
}

void
smfu_obj_free(smfobj_t *obj)
{
	free(obj->sname);
	free(obj->iname);
	free(obj->fmri);
	free(obj);
}

smfobj_t *
smfu_obj_alloc(const char *sname, const char *iname)
{
	smfobj_t *smfo = rad_zalloc(sizeof (smfobj_t));
	if (smfo == NULL)
		return (NULL);

	if ((smfo->sname = strdup(sname)) == NULL ||
	    (iname != NULL && (smfo->iname = strdup(iname)) == NULL) ||
	    (smfo->fmri = smfu_fmri_alloc(sname, iname)) == NULL) {
		smfu_obj_free(smfo);
		return (NULL);
	}

	return (smfo);
}


/*
 * Automatic retry mechanism.
 *
 * Calls cb() repeatedly while it returns SE_RETRY, reconnecting to the
 * repository as necessary.
 *
 * For the convenience of the callback, if a adr_data_t pointer is stored
 * in its result argument on failure it is freed.  Also verifies the
 * result on success.
 */
static conerr_t
smfu_rtrun_int(smfu_rtfunc_t cb, void *arg, adr_data_t **ret,
    adr_data_t **error, boolean_t nullable)
{
	svcerr_t se = SE_OK;
	adr_data_t *result = NULL;

	do {
		rad_handle_t *rh = rh_fetch();
		if (rh == NULL)
			se = smfu_maperr(scf_error());
		else
			se = cb(rh_hdl(rh), arg, &result, error);

		if (se != SE_OK && result != NULL) {
			adr_data_free(result);
			result = NULL;
		}

		if (se == SE_RECONNECT)
			rh_kill(rh);
		rh_rele(rh);
	} while (se == SE_RETRY || se == SE_RECONNECT);

	assert(ret != NULL || result == NULL);
	if (ret != NULL && se == SE_OK) {
		if ((nullable && result == NULL) ||
		    adr_data_verify(result, NULL, B_TRUE)) {
			*ret = result;
		} else {
			adr_data_free(result);
			se = internal_error(error, NULL);
		}
	}

	if (se == SE_OK)
		return (ce_ok);
	if (se == SE_STALE)
		return (ce_notfound);

	if (se == SE_FATAL || se == SE_NOTFOUND) {
		if (error != NULL && *error == NULL)
			(void) internal_error(error, NULL);
		return (ce_object);
	}

	/* Shouldn't be seeing SE_RETRY, SE_RECONNECT, SE_BREAK */
	rad_log(RL_FATAL, "smfu_rtrun: callback returned bad error: %d", se);
	return (ce_system);
}

conerr_t
smfu_rtrun_opt(smfu_rtfunc_t cb, void *arg, adr_data_t **ret,
    adr_data_t **error)
{
	return (smfu_rtrun_int(cb, arg, ret, error, B_TRUE));
}

conerr_t
smfu_rtrun(smfu_rtfunc_t cb, void *arg, adr_data_t **ret, adr_data_t **error)
{
	return (smfu_rtrun_int(cb, arg, ret, error, B_FALSE));
}


void
smfu_init()
{
	/*
	 * Initialize constants
	 */
	max_fmri = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
	max_pgtype = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH);
	max_name = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
	max_value = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
}