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;
                 }