Print this page
12721 would like svcadm disable -c
@@ -22,11 +22,11 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
- * Copyright 2015, Joyent, Inc. All rights reserved.
+ * Copyright 2020, Joyent, Inc. All rights reserved.
*/
/*
* svcadm - request adminstrative actions for service instances
*/
@@ -80,10 +80,23 @@
boolean_t active;
char str[1];
};
+/*
+ * Callback data for enable/disable.
+ */
+#define SET_ENABLED 0x1
+#define SET_TEMPORARY 0x2
+#define SET_RECURSIVE 0x4
+
+typedef struct {
+ char ed_comment[SCF_COMMENT_MAX_LENGTH];
+ int ed_flags;
+} enable_data_t;
+
+
scf_handle_t *h;
ssize_t max_scf_fmri_sz;
static const char *emsg_permission_denied;
static const char *emsg_nomem;
static const char *emsg_create_pg_perm_denied;
@@ -144,11 +157,11 @@
usage()
{
(void) fprintf(stderr, gettext(
"Usage: %1$s [-S <state>] [-v] [-Z | -z zone] [cmd [args ... ]]\n\n"
"\t%1$s enable [-rst] [<service> ...]\t- enable and online service(s)\n"
- "\t%1$s disable [-st] [<service> ...]\t- disable and offline "
+ "\t%1$s disable [-c comment] [-st] [<service> ...] - disable "
"service(s)\n"
"\t%1$s restart [-d] [<service> ...]\t- restart specified service(s)\n"
"\t%1$s refresh [<service> ...]\t\t- re-read service configuration\n"
"\t%1$s mark [-It] <state> [<service> ...] - set maintenance state\n"
"\t%1$s clear [<service> ...]\t\t- clear maintenance state\n"
@@ -565,24 +578,154 @@
out:
scf_pg_destroy(pg);
return (ret);
}
+static int
+delete_prop(const char *fmri, scf_instance_t *inst, const char *pgname,
+ const char *propname)
+{
+ int r = scf_instance_delete_prop(inst, pgname, propname);
+
+ switch (r) {
+ case 0:
+ break;
+
+ case ECANCELED:
+ uu_warn(emsg_no_service, fmri);
+ break;
+
+ case EACCES:
+ uu_warn(gettext("Could not delete %s/%s "
+ "property of %s: backend access denied.\n"),
+ pgname, propname, fmri);
+ break;
+
+ case EROFS:
+ uu_warn(gettext("Could not delete %s/%s "
+ "property of %s: backend is read-only.\n"),
+ pgname, propname, fmri);
+ break;
+
+ default:
+ bad_error("scf_instance_delete_prop", r);
+ }
+
+ return (r);
+}
+
/*
- * Enable or disable inst, per enable. If temp is true, set
- * general_ovr/enabled. Otherwise set general/enabled and delete
- * general_ovr/enabled if it exists (order is important here: we don't want the
- * enabled status to glitch).
+ * Returns 0, EPERM, or EROFS.
*/
+static int
+set_enabled_props(scf_propertygroup_t *pg, enable_data_t *ed)
+{
+ scf_transaction_entry_t *ent1;
+ scf_transaction_entry_t *ent2;
+ scf_transaction_t *tx;
+ scf_value_t *v2;
+ scf_value_t *v1;
+ int ret = 0, r;
+
+ if ((tx = scf_transaction_create(h)) == NULL ||
+ (ent1 = scf_entry_create(h)) == NULL ||
+ (ent2 = scf_entry_create(h)) == NULL ||
+ (v1 = scf_value_create(h)) == NULL ||
+ (v2 = scf_value_create(h)) == NULL)
+ scfdie();
+
+ for (;;) {
+ if (scf_transaction_start(tx, pg) == -1) {
+ switch (scf_error()) {
+ case SCF_ERROR_PERMISSION_DENIED:
+ ret = EPERM;
+ goto out;
+
+ case SCF_ERROR_BACKEND_READONLY:
+ ret = EROFS;
+ goto out;
+
+ default:
+ scfdie();
+ }
+ }
+
+ if (scf_transaction_property_change_type(tx, ent1,
+ SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN) != 0) {
+ if (scf_error() != SCF_ERROR_NOT_FOUND)
+ scfdie();
+
+ if (scf_transaction_property_new(tx, ent1,
+ SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN) != 0)
+ scfdie();
+ }
+
+ scf_value_set_boolean(v1, !!(ed->ed_flags & SET_ENABLED));
+
+ r = scf_entry_add_value(ent1, v1);
+ assert(r == 0);
+
+ if (scf_transaction_property_change_type(tx, ent2,
+ SCF_PROPERTY_COMMENT, SCF_TYPE_ASTRING) != 0) {
+ if (scf_error() != SCF_ERROR_NOT_FOUND)
+ scfdie();
+
+ if (scf_transaction_property_new(tx, ent2,
+ SCF_PROPERTY_COMMENT, SCF_TYPE_ASTRING) != 0)
+ scfdie();
+ }
+
+ if (scf_value_set_astring(v2, ed->ed_comment) != SCF_SUCCESS)
+ scfdie();
+
+ if (scf_entry_add_value(ent2, v2) != SCF_SUCCESS)
+ scfdie();
+
+ r = scf_transaction_commit(tx);
+ if (r == 1)
+ break;
+
+ scf_transaction_reset(tx);
+
+ if (r != 0) {
+ switch (scf_error()) {
+ case SCF_ERROR_PERMISSION_DENIED:
+ ret = EPERM;
+ goto out;
+
+ case SCF_ERROR_BACKEND_READONLY:
+ ret = EROFS;
+ goto out;
+
+ default:
+ scfdie();
+ }
+ }
+
+ if (scf_pg_update(pg) == -1)
+ scfdie();
+ }
+
+out:
+ scf_transaction_destroy(tx);
+ scf_entry_destroy(ent1);
+ scf_entry_destroy(ent2);
+ scf_value_destroy(v1);
+ scf_value_destroy(v2);
+ return (ret);
+}
+
+/*
+ * Enable or disable an instance. SET_TEMPORARY modifications apply to
+ * general_ovr/ property group.
+ */
static void
-set_inst_enabled(const char *fmri, scf_instance_t *inst, boolean_t temp,
- boolean_t enable)
+set_inst_enabled(const char *fmri, scf_instance_t *inst, enable_data_t *ed)
{
scf_propertygroup_t *pg;
uint8_t b;
const char *pgname = NULL; /* For emsg_pg_perm_denied */
- int r;
pg = scf_pg_create(h);
if (pg == NULL)
scfdie();
@@ -623,18 +766,17 @@
assert(0);
abort();
}
}
- if (temp) {
- /* Set general_ovr/enabled */
+ if (ed->ed_flags & SET_TEMPORARY) {
pgname = SCF_PG_GENERAL_OVR;
if (pg_get_or_add(inst, pgname, SCF_PG_GENERAL_OVR_TYPE,
SCF_PG_GENERAL_OVR_FLAGS, pg) != 0)
goto eperm;
- switch (set_bool_prop(pg, SCF_PROPERTY_ENABLED, enable)) {
+ switch (set_enabled_props(pg, ed)) {
case 0:
break;
case EPERM:
goto eperm;
@@ -654,11 +796,11 @@
assert(0);
abort();
}
if (verbose)
- (void) printf(enable ?
+ (void) printf((ed->ed_flags & SET_ENABLED) ?
gettext("%s temporarily enabled.\n") :
gettext("%s temporarily disabled.\n"), fmri);
} else {
again:
/*
@@ -669,11 +811,11 @@
*/
if (pg_get_or_add(inst, pgname, SCF_PG_GENERAL_TYPE,
SCF_PG_GENERAL_FLAGS, pg) != 0)
goto eperm;
- switch (set_bool_prop(pg, SCF_PROPERTY_ENABLED, enable)) {
+ switch (set_enabled_props(pg, ed)) {
case 0:
break;
case EPERM:
goto eperm;
@@ -683,11 +825,11 @@
* If general/enabled is already set the way we want,
* proceed.
*/
switch (get_bool_prop(pg, SCF_PROPERTY_ENABLED, &b)) {
case 0:
- if ((b != 0) == (enable != B_FALSE))
+ if (!(b) == !(ed->ed_flags & SET_ENABLED))
break;
/* FALLTHROUGH */
case ENOENT:
case EINVAL:
@@ -714,44 +856,40 @@
default:
assert(0);
abort();
}
- pgname = SCF_PG_GENERAL_OVR;
- r = scf_instance_delete_prop(inst, pgname,
- SCF_PROPERTY_ENABLED);
- switch (r) {
+ switch (delete_prop(fmri, inst, SCF_PG_GENERAL_OVR,
+ SCF_PROPERTY_ENABLED)) {
case 0:
break;
- case ECANCELED:
- uu_warn(emsg_no_service, fmri);
- goto out;
-
case EPERM:
goto eperm;
- case EACCES:
- uu_warn(gettext("Could not delete %s/%s "
- "property of %s: backend access denied.\n"),
- pgname, SCF_PROPERTY_ENABLED, fmri);
+ default:
goto out;
+ }
- case EROFS:
- uu_warn(gettext("Could not delete %s/%s "
- "property of %s: backend is read-only.\n"),
- pgname, SCF_PROPERTY_ENABLED, fmri);
- goto out;
+ switch (delete_prop(fmri, inst, SCF_PG_GENERAL_OVR,
+ SCF_PROPERTY_COMMENT)) {
+ case 0:
+ break;
+ case EPERM:
+ goto eperm;
+
default:
- bad_error("scf_instance_delete_prop", r);
+ goto out;
}
- if (verbose)
- (void) printf(enable ? gettext("%s enabled.\n") :
+ if (verbose) {
+ (void) printf((ed->ed_flags & SET_ENABLED) ?
+ gettext("%s enabled.\n") :
gettext("%s disabled.\n"), fmri);
}
+ }
scf_pg_destroy(pg);
return;
eperm:
@@ -1012,11 +1150,11 @@
* fmri must point to a writable max_scf_fmri_sz buffer. Returns EINVAL if fmri
* is invalid, E2BIG if fmri identifies a service with multiple instances, ELOOP
* on cycle detection, or 0 on success.
*/
static int
-enable_fmri_rec(char *fmri, boolean_t temp)
+enable_fmri_rec(char *fmri, enable_data_t *ed)
{
scf_instance_t *inst;
scf_snapshot_t *snap;
scf_propertygroup_t *pg;
scf_property_t *prop;
@@ -1066,11 +1204,11 @@
default:
he->active = B_FALSE;
return (0);
}
- set_inst_enabled(fmri, inst, temp, B_TRUE);
+ set_inst_enabled(fmri, inst, ed);
if ((snap = scf_snapshot_create(h)) == NULL ||
(pg = scf_pg_create(h)) == NULL ||
(prop = scf_property_create(h)) == NULL ||
(v = scf_value_create(h)) == NULL ||
@@ -1116,11 +1254,11 @@
"\"%s/%s\" is too long).\n"), fmri, SCF_PG_GENERAL,
SCF_PROPERTY_RESTARTER);
ret = 0;
goto out;
} else if (sz >= 0) {
- switch (enable_fmri_rec(buf, temp)) {
+ switch (enable_fmri_rec(buf, ed)) {
case 0:
break;
case EINVAL:
uu_warn(gettext("Restarter FMRI for \"%s\" is "
@@ -1267,11 +1405,11 @@
if (scf_value_get_astring(v, buf, max_scf_fmri_sz) ==
-1)
scfdie();
- switch (enable_fmri_rec(buf, temp)) {
+ switch (enable_fmri_rec(buf, ed)) {
case 0:
break;
case EINVAL:
uu_warn(gettext("\"%s\" dependency of \"%s\" "
@@ -1666,21 +1804,14 @@
scf_pg_destroy(pg);
scf_instance_destroy(inst);
}
-/*
- * Flags to control enable and disable actions.
- */
-#define SET_ENABLED 0x1
-#define SET_TEMPORARY 0x2
-#define SET_RECURSIVE 0x4
-
static int
set_fmri_enabled(void *data, scf_walkinfo_t *wip)
{
- int flags = (int)data;
+ enable_data_t *ed = data;
assert(wip->inst != NULL);
assert(wip->pg == NULL);
if (svcsearch) {
@@ -1690,11 +1821,11 @@
return (0);
if (strcmp(state, svcstate) != 0)
return (0);
}
- if (flags & SET_RECURSIVE) {
+ if (ed->ed_flags & SET_RECURSIVE) {
char *fmri_buf = malloc(max_scf_fmri_sz);
if (fmri_buf == NULL)
uu_die(emsg_nomem);
visited = calloc(HT_BUCKETS, sizeof (*visited));
@@ -1703,11 +1834,11 @@
/* scf_walk_fmri() guarantees that fmri isn't too long */
assert(strlen(wip->fmri) <= max_scf_fmri_sz);
(void) strlcpy(fmri_buf, wip->fmri, max_scf_fmri_sz);
- switch (enable_fmri_rec(fmri_buf, (flags & SET_TEMPORARY))) {
+ switch (enable_fmri_rec(fmri_buf, ed)) {
case E2BIG:
uu_warn(gettext("operation on service %s is ambiguous; "
"instance specification needed.\n"), fmri_buf);
break;
@@ -1718,12 +1849,11 @@
free(visited);
free(fmri_buf);
} else {
- set_inst_enabled(wip->fmri, wip->inst,
- (flags & SET_TEMPORARY) != 0, (flags & SET_ENABLED) != 0);
+ set_inst_enabled(wip->fmri, wip->inst, ed);
}
return (0);
}
@@ -1970,11 +2100,10 @@
static void
set_milestone(const char *fmri, boolean_t temporary)
{
scf_instance_t *inst;
scf_propertygroup_t *pg;
- int r;
if (temporary) {
set_astring_prop(SCF_SERVICE_STARTD, SCF_PG_OPTIONS_OVR,
SCF_PG_OPTIONS_OVR_TYPE, SCF_PG_OPTIONS_OVR_FLAGS,
SCF_PROPERTY_MILESTONE, fmri);
@@ -1996,47 +2125,14 @@
*/
set_astring_prop(SCF_SERVICE_STARTD, SCF_PG_OPTIONS,
SCF_PG_OPTIONS_TYPE, SCF_PG_OPTIONS_FLAGS, SCF_PROPERTY_MILESTONE,
fmri);
- r = scf_instance_delete_prop(inst, SCF_PG_OPTIONS_OVR,
- SCF_PROPERTY_MILESTONE);
- switch (r) {
- case 0:
- break;
-
- case ECANCELED:
- uu_warn(emsg_no_service, fmri);
+ if (delete_prop(SCF_SERVICE_STARTD, inst, SCF_PG_OPTIONS_OVR,
+ SCF_PROPERTY_MILESTONE) != 0)
exit_status = 1;
- goto out;
- case EPERM:
- uu_warn(gettext("Could not delete %s/%s property of "
- "%s: permission denied.\n"), SCF_PG_OPTIONS_OVR,
- SCF_PROPERTY_MILESTONE, SCF_SERVICE_STARTD);
- exit_status = 1;
- goto out;
-
- case EACCES:
- uu_warn(gettext("Could not delete %s/%s property of "
- "%s: access denied.\n"), SCF_PG_OPTIONS_OVR,
- SCF_PROPERTY_MILESTONE, SCF_SERVICE_STARTD);
- exit_status = 1;
- goto out;
-
- case EROFS:
- uu_warn(gettext("Could not delete %s/%s property of "
- "%s: backend read-only.\n"), SCF_PG_OPTIONS_OVR,
- SCF_PROPERTY_MILESTONE, SCF_SERVICE_STARTD);
- exit_status = 1;
- goto out;
-
- default:
- bad_error("scf_instance_delete_prop", r);
- }
-
-out:
scf_pg_destroy(pg);
scf_instance_destroy(inst);
}
static char const *milestones[] = {
@@ -2313,21 +2409,24 @@
if (optind >= argc)
usage();
if (strcmp(argv[optind], "enable") == 0) {
- int flags = SET_ENABLED;
+ enable_data_t ed = {
+ .ed_flags = SET_ENABLED,
+ .ed_comment = ""
+ };
int wait = 0;
int error = 0;
++optind;
while ((o = getopt(argc, argv, "rst")) != -1) {
if (o == 'r')
- flags |= SET_RECURSIVE;
+ ed.ed_flags |= SET_RECURSIVE;
else if (o == 't')
- flags |= SET_TEMPORARY;
+ ed.ed_flags |= SET_TEMPORARY;
else if (o == 's')
wait = 1;
else if (o == '?')
usage();
else {
@@ -2349,19 +2448,19 @@
* invalid options, but not if an enable failed. We
* squelch output the second time we walk fmris; we saw
* the errors the first time.
*/
if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
- set_fmri_enabled, (void *)flags, &error, pr_warn)) != 0) {
+ set_fmri_enabled, &ed, &error, pr_warn)) != 0) {
pr_warn(gettext("failed to iterate over "
"instances: %s\n"), scf_strerror(err));
exit_status = UU_EXIT_FATAL;
} else if (wait && exit_status == 0 &&
(err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
- wait_fmri_enabled, (void *)flags, &error, quiet)) != 0) {
+ wait_fmri_enabled, NULL, &error, quiet)) != 0) {
pr_warn(gettext("failed to iterate over "
"instances: %s\n"), scf_strerror(err));
exit_status = UU_EXIT_FATAL;
}
@@ -2368,19 +2467,29 @@
if (error > 0)
exit_status = error;
} else if (strcmp(argv[optind], "disable") == 0) {
- int flags = 0;
+ enable_data_t ed = {
+ .ed_flags = 0,
+ .ed_comment = ""
+ };
int wait = 0;
int error = 0;
++optind;
- while ((o = getopt(argc, argv, "st")) != -1) {
- if (o == 't')
- flags |= SET_TEMPORARY;
+ while ((o = getopt(argc, argv, "c:st")) != -1) {
+ if (o == 'c') {
+ if (strlcpy(ed.ed_comment, optarg,
+ sizeof (ed.ed_comment)) >=
+ sizeof (ed.ed_comment)) {
+ uu_die(gettext("disable -c comment "
+ "too long.\n"));
+ }
+ } else if (o == 't')
+ ed.ed_flags |= SET_TEMPORARY;
else if (o == 's')
wait = 1;
else if (o == '?')
usage();
else {
@@ -2402,20 +2511,20 @@
* invalid options, but not if a disable failed. We
* squelch output the second time we walk fmris; we saw
* the errors the first time.
*/
if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
- set_fmri_enabled, (void *)flags, &exit_status,
+ set_fmri_enabled, &ed, &exit_status,
pr_warn)) != 0) {
pr_warn(gettext("failed to iterate over "
"instances: %s\n"), scf_strerror(err));
exit_status = UU_EXIT_FATAL;
} else if (wait && exit_status == 0 &&
(err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
- wait_fmri_disabled, (void *)flags, &error, quiet)) != 0) {
+ wait_fmri_disabled, NULL, &error, quiet)) != 0) {
pr_warn(gettext("failed to iterate over "
"instances: %s\n"), scf_strerror(err));
exit_status = UU_EXIT_FATAL;
}