/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <assert.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libuutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include "startd.h"
/*
* Return an allocated copy of str, with the Bourne shell's metacharacters
* escaped by '\'. Returns NULL on (allocation) failure.
*/
static char *
quote_for_shell(const char *str)
{
const char *sp;
char *dst, *dp;
size_t dst_len;
const char * const metachars = ";&()|^<>\n \t\\\"\'`";
dst_len = 0;
for (sp = str; *sp != '\0'; ++sp) {
++dst_len;
if (strchr(metachars, *sp) != NULL)
++dst_len;
}
if (sp - str == dst_len)
return (safe_strdup(str));
dst = malloc(dst_len + 1);
if (dst == NULL)
return (NULL);
for (dp = dst, sp = str; *sp != '\0'; ++dp, ++sp) {
if (strchr(metachars, *sp) != NULL)
*dp++ = '\\';
*dp = *sp;
}
*dp = '\0';
return (dst);
}
/*
* Return an allocated string representation of the value v.
* Return NULL on error.
*/
static char *
val_to_str(scf_value_t *v)
{
char *buf;
ssize_t buflen, ret;
buflen = scf_value_get_as_string(v, NULL, 0);
assert(buflen >= 0);
buf = malloc(buflen + 1);
if (buf == NULL)
return (NULL);
ret = scf_value_get_as_string(v, buf, buflen + 1);
assert(ret == buflen);
return (buf);
}
/*
* Look up a property in the given snapshot, or the editing one
* if not found. Returns scf_error() on failure, or 0 otherwise.
*/
static int
get_prop(const scf_instance_t *inst, scf_snapshot_t *snap,
const char *pgn, const char *pn, scf_propertygroup_t *pg,
scf_property_t *prop)
{
int ret;
ret = scf_instance_get_pg_composed(inst, snap, pgn, pg);
if (ret != 0) {
snap = NULL;
if (scf_error() == SCF_ERROR_NOT_FOUND)
ret = scf_instance_get_pg_composed(inst, snap, pgn, pg);
if (ret != 0)
return (scf_error());
}
if (scf_pg_get_property(pg, pn, prop) == 0)
return (0);
if (snap == NULL)
return (scf_error());
ret = scf_instance_get_pg_composed(inst, NULL, pgn, pg);
if (ret != 0)
return (scf_error());
if (scf_pg_get_property(pg, pn, prop) == 0)
return (0);
return (scf_error());
}
/*
* Get an allocated string representation of the values of the property
* specified by inst & prop_spec and store it in *retstr. prop_spec may
* be a full property FMRI, or a "property-group/property" pair relative
* to inst, or the name of a property in inst's "application" property
* group. In the latter two cases, the property is looked up in inst's
* snap snapshot. In the first case, the target instance's running
* snapshot will be used. In any case, if the property or its group
* can't be found, the "editing" snapshot will be checked. Multiple
* values will be separated by sep.
*
* On error, non-zero is returned, and *retstr is set to an error
* string.
*
* *retstr should always be freed by the caller.
*/
static int
get_prop_val_str(const scf_instance_t *inst, scf_snapshot_t *snap,
const char *prop_spec, char sep, char **retstr)
{
scf_handle_t *h = scf_instance_handle(inst);
scf_scope_t *scope = NULL;
scf_service_t *svc = NULL;
scf_instance_t *tmpinst = NULL;
scf_snapshot_t *tmpsnap = NULL;
scf_propertygroup_t *pg = NULL;
scf_iter_t *iter = NULL;
scf_property_t *prop = NULL;
scf_value_t *val = NULL;
char *spec;
char *str, *qstr;
size_t strl;
int ret;
spec = safe_strdup(prop_spec);
if (strstr(spec, ":properties") != NULL) {
const char *scn, *sn, *in, *pgn, *pn;
if (scf_parse_svc_fmri(spec, &scn, &sn, &in, &pgn,
&pn) != 0)
goto scferr;
if (sn == NULL || pgn == NULL || pn == NULL) {
free(spec);
*retstr = safe_strdup("parse error");
return (-1);
}
if ((scope = scf_scope_create(h)) == NULL ||
(svc = scf_service_create(h)) == NULL ||
(pg = scf_pg_create(h)) == NULL ||
(prop = scf_property_create(h)) == NULL)
goto scferr;
if (scf_handle_get_scope(h, scn == NULL ? SCF_SCOPE_LOCAL : scn,
scope) != 0)
goto properr;
if (scf_scope_get_service(scope, sn, svc) != 0)
goto properr;
if (in == NULL) {
if (scf_service_get_pg(svc, pgn, pg) != 0)
goto properr;
if (scf_pg_get_property(pg, pn, prop) != 0)
goto properr;
} else {
if ((tmpinst = scf_instance_create(h)) == NULL)
goto scferr;
if (scf_service_get_instance(svc, in, tmpinst) != 0)
goto properr;
tmpsnap = libscf_get_running_snapshot(tmpinst);
if (tmpsnap == NULL)
goto scferr;
if (get_prop(tmpinst, tmpsnap, pgn, pn, pg, prop) != 0)
goto properr;
}
} else {
char *slash, *pgn, *pn;
/* Try prop or pg/prop in inst. */
prop = scf_property_create(h);
if (prop == NULL)
goto scferr;
pg = scf_pg_create(h);
if (pg == NULL)
goto scferr;
slash = strchr(spec, '/');
if (slash == NULL) {
pgn = "application";
pn = spec;
} else {
*slash = '\0';
pgn = spec;
pn = slash + 1;
}
if (get_prop(inst, snap, pgn, pn, pg, prop) != 0)
goto properr;
}
iter = scf_iter_create(h);
if (iter == NULL)
goto scferr;
if (scf_iter_property_values(iter, prop) == -1)
goto scferr;
val = scf_value_create(h);
if (val == NULL)
goto scferr;
ret = scf_iter_next_value(iter, val);
if (ret == 0) {
*retstr = safe_strdup("");
goto out;
} else if (ret == -1) {
goto scferr;
}
str = val_to_str(val);
if (str == NULL)
goto err;
qstr = quote_for_shell(str);
free(str);
str = qstr;
if (qstr == NULL)
goto err;
strl = strlen(str);
while ((ret = scf_iter_next_value(iter, val)) == 1) {
char *nv, *qnv;
size_t nl;
void *p;
/* Append sep & val_to_str(val) to str. */
nv = val_to_str(val);
if (nv == NULL) {
free(str);
goto err;
}
qnv = quote_for_shell(nv);
free(nv);
if (qnv == NULL) {
free(str);
goto err;
}
nv = qnv;
nl = strl + 1 + strlen(nv);
p = realloc(str, nl + 1);
if (p == NULL) {
free(str);
free(nv);
goto err;
}
str = p;
str[strl] = sep;
(void) strcpy(&str[strl + 1], nv);
free(nv);
strl = nl;
}
if (ret == -1) {
free(str);
goto scferr;
}
*retstr = str;
out:
scf_value_destroy(val);
scf_iter_destroy(iter);
scf_property_destroy(prop);
scf_pg_destroy(pg);
scf_instance_destroy(tmpinst);
scf_snapshot_destroy(tmpsnap);
scf_service_destroy(svc);
scf_scope_destroy(scope);
free(spec);
return (ret);
scferr:
*retstr = safe_strdup(scf_strerror(scf_error()));
ret = -1;
goto out;
properr:
ret = -1;
if (scf_error() != SCF_ERROR_NOT_FOUND)
goto scferr;
*retstr = uu_msprintf("property \"%s\" not found", prop_spec);
if (*retstr != NULL)
goto out;
err:
*retstr = safe_strdup(strerror(errno));
ret = -1;
goto out;
}
/*
* Interpret the token at the beginning of str (which should be just
* after the escape character), and set *retstr to point at it. Returns
* the number of characters swallowed. On error, this returns -1, and
* *retstr is set to an error string.
*
* *retstr should always be freed by the caller.
*/
static int
expand_token(const char *str, scf_instance_t *inst, scf_snapshot_t *snap,
int method_type, char **retstr)
{
scf_handle_t *h = scf_instance_handle(inst);
switch (str[0]) {
case 's': { /* service */
scf_service_t *svc;
char *sname;
ssize_t sname_len, szret;
int ret;
svc = scf_service_create(h);
if (svc == NULL) {
*retstr = safe_strdup(strerror(scf_error()));
return (-1);
}
ret = scf_instance_get_parent(inst, svc);
if (ret != 0) {
int err = scf_error();
scf_service_destroy(svc);
*retstr = safe_strdup(scf_strerror(err));
return (-1);
}
sname_len = scf_service_get_name(svc, NULL, 0);
if (sname_len < 0) {
int err = scf_error();
scf_service_destroy(svc);
*retstr = safe_strdup(scf_strerror(err));
return (-1);
}
sname = malloc(sname_len + 1);
if (sname == NULL) {
int err = scf_error();
scf_service_destroy(svc);
*retstr = safe_strdup(scf_strerror(err));
return (-1);
}
szret = scf_service_get_name(svc, sname, sname_len + 1);
if (szret < 0) {
int err = scf_error();
free(sname);
scf_service_destroy(svc);
*retstr = safe_strdup(scf_strerror(err));
return (-1);
}
scf_service_destroy(svc);
*retstr = sname;
return (1);
}
case 'i': { /* instance */
char *iname;
ssize_t iname_len, szret;
iname_len = scf_instance_get_name(inst, NULL, 0);
if (iname_len < 0) {
*retstr = safe_strdup(scf_strerror(scf_error()));
return (-1);
}
iname = malloc(iname_len + 1);
if (iname == NULL) {
*retstr = safe_strdup(strerror(errno));
return (-1);
}
szret = scf_instance_get_name(inst, iname, iname_len + 1);
if (szret < 0) {
free(iname);
*retstr = safe_strdup(scf_strerror(scf_error()));
return (-1);
}
*retstr = iname;
return (1);
}
case 'f': { /* fmri */
char *fmri;
ssize_t fmri_len;
int ret;
fmri_len = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
if (fmri_len == -1) {
*retstr = safe_strdup(scf_strerror(scf_error()));
return (-1);
}
fmri = malloc(fmri_len + 1);
if (fmri == NULL) {
*retstr = safe_strdup(strerror(errno));
return (-1);
}
ret = scf_instance_to_fmri(inst, fmri, fmri_len + 1);
if (ret == -1) {
free(fmri);
*retstr = safe_strdup(scf_strerror(scf_error()));
return (-1);
}
*retstr = fmri;
return (1);
}
case 'm': { /* method */
char *str = NULL;
switch (method_type) {
case METHOD_START:
str = "start";
break;
case METHOD_STOP:
str = "stop";
break;
case METHOD_REFRESH:
str = "refresh";
break;
default:
assert(0);
return (-1);
}
*retstr = safe_strdup(str);
return (1);
}
case 'r': /* restarter */
*retstr = safe_strdup("svc.startd");
return (1);
case '{': {
/* prop_spec[,:]? See get_prop_val_str() for prop_spec. */
char *close;
size_t len;
char *buf;
char sep;
int ret;
int skip;
close = strchr(str + 1, '}');
if (close == NULL) {
*retstr = safe_strdup("parse error");
return (-1);
}
len = close - (str + 1); /* between the {}'s */
skip = len + 2; /* including the {}'s */
/*
* If the last character is , or :, use it as the separator.
* Otherwise default to space.
*/
sep = *(close - 1);
if (sep == ',' || sep == ':')
--len;
else
sep = ' ';
buf = malloc(len + 1);
if (buf == NULL) {
*retstr = safe_strdup(strerror(errno));
return (-1);
}
(void) strlcpy(buf, str + 1, len + 1);
ret = get_prop_val_str(inst, snap, buf, sep, retstr);
if (ret != 0) {
free(buf);
return (-1);
}
free(buf);
return (skip);
}
default:
*retstr = safe_strdup("unknown method token");
return (-1);
}
}
/*
* Expand method tokens in the given string, and place the result in
* *retstr. Tokens begin with the ESCAPE character. Returns 0 on
* success. On failure, returns -1 and an error string is placed in
* *retstr. Caller should free *retstr.
*/
#define ESCAPE '%'
int
expand_method_tokens(const char *str, scf_instance_t *inst,
scf_snapshot_t *snap, int method_type, char **retstr)
{
char *expanded;
size_t exp_sz;
const char *sp;
int ei;
if (scf_instance_handle(inst) == NULL) {
*retstr = safe_strdup(scf_strerror(scf_error()));
return (-1);
}
exp_sz = strlen(str) + 1;
expanded = malloc(exp_sz);
if (expanded == NULL) {
*retstr = safe_strdup(strerror(errno));
return (-1);
}
/*
* Copy str into expanded, expanding %-tokens & realloc()ing as we go.
*/
sp = str;
ei = 0;
for (;;) {
char *esc;
size_t len;
esc = strchr(sp, ESCAPE);
if (esc == NULL) {
(void) strcpy(expanded + ei, sp);
*retstr = expanded;
return (0);
}
/* Copy up to the escape character. */
len = esc - sp;
(void) strncpy(expanded + ei, sp, len);
sp += len;
ei += len;
if (sp[1] == '\0') {
expanded[ei] = '\0';
*retstr = expanded;
return (0);
}
if (sp[1] == ESCAPE) {
expanded[ei] = ESCAPE;
sp += 2;
ei++;
} else {
char *tokval;
int skip;
char *p;
skip = expand_token(sp + 1, inst, snap,
method_type, &tokval);
if (skip == -1) {
free(expanded);
*retstr = tokval;
return (-1);
}
len = strlen(tokval);
exp_sz += len;
p = realloc(expanded, exp_sz);
if (p == NULL) {
*retstr = safe_strdup(strerror(errno));
free(expanded);
free(tokval);
return (-1);
}
expanded = p;
(void) strcpy(expanded + ei, tokval);
sp += 1 + skip;
ei += len;
free(tokval);
}
}
/* NOTREACHED */
}