1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * util.c contains a set of miscellaneous utility functions which, 29 * among other things: 30 * - start a child process 31 * - look up the zone name 32 * - look up/set SMF properties 33 * - drop/escalate privs 34 */ 35 36 #include <assert.h> 37 #include <errno.h> 38 #include <inetcfg.h> 39 #include <libdllink.h> 40 #include <limits.h> 41 #include <libscf.h> 42 #include <net/if.h> 43 #include <pthread.h> 44 #include <pwd.h> 45 #include <spawn.h> 46 #include <stdarg.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <strings.h> 51 #include <stropts.h> 52 #include <sys/socket.h> 53 #include <sys/sockio.h> 54 #include <sys/types.h> 55 #include <unistd.h> 56 #include <wait.h> 57 #include <zone.h> 58 59 #include "util.h" 60 #include "llp.h" 61 62 extern char **environ; 63 extern sigset_t original_sigmask; 64 65 /* 66 * A holder for all the resources needed to get a property value 67 * using libscf. 68 */ 69 typedef struct scf_resources { 70 scf_handle_t *sr_handle; 71 scf_instance_t *sr_inst; 72 scf_snapshot_t *sr_snap; 73 scf_propertygroup_t *sr_pg; 74 scf_property_t *sr_prop; 75 scf_value_t *sr_val; 76 scf_transaction_t *sr_tx; 77 scf_transaction_entry_t *sr_ent; 78 } scf_resources_t; 79 80 static pthread_mutex_t uid_mutex = PTHREAD_MUTEX_INITIALIZER; 81 static uid_t uid; 82 static int uid_cnt; 83 84 void 85 nwamd_to_root(void) { 86 (void) pthread_mutex_lock(&uid_mutex); 87 if (uid == 0) 88 uid = getuid(); 89 if (uid_cnt++ == 0) { 90 nwamd_escalate_privs(); 91 if (setuid(0) == -1) 92 nlog(LOG_ERR, "setuid(0) failed %s", strerror(errno)); 93 } 94 (void) pthread_mutex_unlock(&uid_mutex); 95 } 96 97 void 98 nwamd_from_root(void) { 99 (void) pthread_mutex_lock(&uid_mutex); 100 assert(uid_cnt > 0); 101 if (--uid_cnt == 0) { 102 if (setuid(uid) == -1) 103 nlog(LOG_ERR, "setuid(%d) failed %s", uid, 104 strerror(errno)); 105 nwamd_drop_unneeded_privs(); 106 } 107 (void) pthread_mutex_unlock(&uid_mutex); 108 } 109 110 /* 111 * 112 * This starts a child process determined by command. If command contains a 113 * slash then it is assumed to be a full path; otherwise the path is searched 114 * for an executable file with the name command. Command is also used as 115 * argv[0] of the new process. The rest of the arguments of the function 116 * up to the first NULL make up pointers to arguments of the new process. 117 * 118 * This function returns child exit status on success and -1 on failure. 119 * 120 * NOTE: original_sigmask must be set before this function is called. 121 */ 122 int 123 nwamd_start_childv(const char *command, char const * const *argv) 124 { 125 posix_spawnattr_t attr; 126 sigset_t fullset; 127 int i, rc, status, n; 128 pid_t pid; 129 char vbuf[1024]; 130 131 vbuf[0] = 0; 132 n = sizeof (vbuf); 133 for (i = 1; argv[i] != NULL && n > 2; i++) { 134 n -= strlcat(vbuf, " ", n); 135 n -= strlcat(vbuf, argv[i], n); 136 } 137 if (argv[i] != NULL || n < 0) 138 nlog(LOG_ERR, "nwamd_start_childv can't log full arg vector"); 139 140 if ((rc = posix_spawnattr_init(&attr)) != 0) { 141 nlog(LOG_DEBUG, "posix_spawnattr_init %d %s\n", 142 rc, strerror(rc)); 143 return (-1); 144 } 145 (void) sigfillset(&fullset); 146 if ((rc = posix_spawnattr_setsigdefault(&attr, &fullset)) != 0) { 147 nlog(LOG_DEBUG, "setsigdefault %d %s\n", rc, strerror(rc)); 148 return (-1); 149 } 150 if ((rc = posix_spawnattr_setsigmask(&attr, &original_sigmask)) != 0) { 151 nlog(LOG_DEBUG, "setsigmask %d %s\n", rc, strerror(rc)); 152 return (-1); 153 } 154 if ((rc = posix_spawnattr_setflags(&attr, 155 POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK)) != 0) { 156 nlog(LOG_DEBUG, "setflags %d %s\n", rc, strerror(rc)); 157 return (-1); 158 } 159 160 if ((rc = posix_spawnp(&pid, command, NULL, &attr, (char * const *)argv, 161 environ)) > 0) { 162 nlog(LOG_DEBUG, "posix_spawnp failed errno %d", rc); 163 return (-1); 164 } 165 166 if ((rc = posix_spawnattr_destroy(&attr)) != 0) { 167 nlog(LOG_DEBUG, "posix_spawn_attr_destroy %d %s\n", 168 rc, strerror(rc)); 169 return (-1); 170 } 171 172 (void) waitpid(pid, &status, 0); 173 if (WIFSIGNALED(status) || WIFSTOPPED(status)) { 174 i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); 175 nlog(LOG_ERR, "'%s%s' %s with signal %d (%s)", command, vbuf, 176 (WIFSIGNALED(status) ? "terminated" : "stopped"), i, 177 strsignal(i)); 178 return (-2); 179 } else { 180 nlog(LOG_INFO, "'%s%s' completed normally: %d", command, vbuf, 181 WEXITSTATUS(status)); 182 return (WEXITSTATUS(status)); 183 } 184 } 185 186 /* 187 * For global zone, check if the link is used by a non-global 188 * zone, note that the non-global zones doesn't need this check, 189 * because zoneadm has taken care of this when the zone boots. 190 * In the global zone, we ignore events for local-zone-owned links 191 * since these are taken care of by the local zone's network 192 * configuration services. 193 */ 194 boolean_t 195 nwamd_link_belongs_to_this_zone(const char *linkname) 196 { 197 zoneid_t zoneid; 198 char zonename[ZONENAME_MAX]; 199 int ret; 200 201 zoneid = getzoneid(); 202 if (zoneid == GLOBAL_ZONEID) { 203 datalink_id_t linkid; 204 dladm_status_t status; 205 char errstr[DLADM_STRSIZE]; 206 207 if ((status = dladm_name2info(dld_handle, linkname, &linkid, 208 NULL, NULL, NULL)) != DLADM_STATUS_OK) { 209 nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " 210 "could not get linkid for %s: %s", 211 linkname, dladm_status2str(status, errstr)); 212 return (B_FALSE); 213 } 214 zoneid = ALL_ZONES; 215 ret = zone_check_datalink(&zoneid, linkid); 216 if (ret == 0) { 217 (void) getzonenamebyid(zoneid, zonename, ZONENAME_MAX); 218 nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " 219 "%s is used by non-global zone: %s", 220 linkname, zonename); 221 return (B_FALSE); 222 } 223 } 224 return (B_TRUE); 225 } 226 227 void 228 nwamd_drop_unneeded_privs(void) 229 { 230 priv_set_t *priv_set, *allpriv_set; 231 232 /* build up our minimal set of privs; start with the basic set */ 233 priv_set = priv_str_to_set("basic", ",", NULL); 234 allpriv_set = priv_str_to_set("zone", ",", NULL); 235 if (priv_set == NULL || allpriv_set == NULL) 236 pfail("converting privilege sets: %s", strerror(errno)); 237 238 (void) priv_addset(priv_set, PRIV_FILE_CHOWN_SELF); 239 (void) priv_addset(priv_set, PRIV_FILE_DAC_READ); 240 (void) priv_addset(priv_set, PRIV_FILE_DAC_WRITE); 241 (void) priv_addset(priv_set, PRIV_NET_RAWACCESS); 242 (void) priv_addset(priv_set, PRIV_NET_PRIVADDR); 243 (void) priv_addset(priv_set, PRIV_PROC_AUDIT); 244 (void) priv_addset(priv_set, PRIV_PROC_OWNER); 245 (void) priv_addset(priv_set, PRIV_PROC_SETID); 246 (void) priv_addset(priv_set, PRIV_SYS_CONFIG); 247 (void) priv_addset(priv_set, PRIV_SYS_IP_CONFIG); 248 (void) priv_addset(priv_set, PRIV_SYS_IPC_CONFIG); 249 (void) priv_addset(priv_set, PRIV_SYS_MOUNT); 250 (void) priv_addset(priv_set, PRIV_SYS_NET_CONFIG); 251 (void) priv_addset(priv_set, PRIV_SYS_RES_CONFIG); 252 (void) priv_addset(priv_set, PRIV_SYS_RESOURCE); 253 254 /* 255 * Since our zone might not have all these privs, 256 * just ask for those that are available. 257 */ 258 priv_intersect(allpriv_set, priv_set); 259 260 if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { 261 priv_freeset(allpriv_set); 262 priv_freeset(priv_set); 263 pfail("setppriv inheritable: %s", strerror(errno)); 264 } 265 /* 266 * Need to ensure permitted set contains all privs so we can escalate 267 * later. 268 */ 269 if (setppriv(PRIV_SET, PRIV_PERMITTED, allpriv_set) == -1) { 270 priv_freeset(allpriv_set); 271 priv_freeset(priv_set); 272 pfail("setppriv permitted: %s", strerror(errno)); 273 } 274 /* 275 * We need to find a smaller set of privs that are important to us. 276 * Otherwise we really are not gaining much by doing this. 277 */ 278 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { 279 priv_freeset(allpriv_set); 280 priv_freeset(priv_set); 281 pfail("setppriv effective: %s", strerror(errno)); 282 } 283 284 priv_freeset(priv_set); 285 priv_freeset(allpriv_set); 286 } 287 288 void 289 nwamd_escalate_privs(void) 290 { 291 priv_set_t *priv_set; 292 priv_set = priv_str_to_set("zone", ",", NULL); 293 if (priv_set == NULL) 294 pfail("creating privilege set: %s", strerror(errno)); 295 296 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { 297 priv_freeset(priv_set); 298 pfail("setppriv effective: %s", strerror(errno)); 299 } 300 priv_freeset(priv_set); 301 } 302 303 /* 304 * Inputs: 305 * res is a pointer to the scf_resources_t to be released. 306 */ 307 static void 308 release_scf_resources(scf_resources_t *res) 309 { 310 scf_entry_destroy(res->sr_ent); 311 scf_transaction_destroy(res->sr_tx); 312 scf_value_destroy(res->sr_val); 313 scf_property_destroy(res->sr_prop); 314 scf_pg_destroy(res->sr_pg); 315 scf_snapshot_destroy(res->sr_snap); 316 scf_instance_destroy(res->sr_inst); 317 (void) scf_handle_unbind(res->sr_handle); 318 scf_handle_destroy(res->sr_handle); 319 } 320 321 /* 322 * Inputs: 323 * fmri is the instance to look up 324 * Outputs: 325 * res is a pointer to an scf_resources_t. This is an internal 326 * structure that holds all the handles needed to get a specific 327 * property from the running snapshot; on a successful return it 328 * contains the scf_value_t that should be passed to the desired 329 * scf_value_get_foo() function, and must be freed after use by 330 * calling release_scf_resources(). On a failure return, any 331 * resources that may have been assigned to res are released, so 332 * the caller does not need to do any cleanup in the failure case. 333 * Returns: 334 * 0 on success 335 * -1 on failure 336 */ 337 338 static int 339 create_scf_resources(const char *fmri, scf_resources_t *res) 340 { 341 res->sr_tx = NULL; 342 res->sr_ent = NULL; 343 res->sr_inst = NULL; 344 res->sr_snap = NULL; 345 res->sr_pg = NULL; 346 res->sr_prop = NULL; 347 res->sr_val = NULL; 348 349 if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) { 350 return (-1); 351 } 352 353 if (scf_handle_bind(res->sr_handle) != 0) { 354 scf_handle_destroy(res->sr_handle); 355 return (-1); 356 } 357 if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) { 358 goto failure; 359 } 360 if (scf_handle_decode_fmri(res->sr_handle, fmri, NULL, NULL, 361 res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { 362 goto failure; 363 } 364 if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) { 365 goto failure; 366 } 367 if (scf_instance_get_snapshot(res->sr_inst, "running", 368 res->sr_snap) != 0) { 369 goto failure; 370 } 371 if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { 372 goto failure; 373 } 374 if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { 375 goto failure; 376 } 377 if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { 378 goto failure; 379 } 380 if ((res->sr_tx = scf_transaction_create(res->sr_handle)) == NULL) { 381 goto failure; 382 } 383 if ((res->sr_ent = scf_entry_create(res->sr_handle)) == NULL) { 384 goto failure; 385 } 386 return (0); 387 388 failure: 389 nlog(LOG_ERR, "create_scf_resources failed: %s", 390 scf_strerror(scf_error())); 391 release_scf_resources(res); 392 return (-1); 393 } 394 395 /* 396 * Inputs: 397 * fmri is the instance to look up 398 * pg is the property group to look up 399 * prop is the property within that group to look up 400 * running specifies if running snapshot is to be used 401 * Outputs: 402 * res is a pointer to an scf_resources_t. This is an internal 403 * structure that holds all the handles needed to get a specific 404 * property from the running snapshot; on a successful return it 405 * contains the scf_value_t that should be passed to the desired 406 * scf_value_get_foo() function, and must be freed after use by 407 * calling release_scf_resources(). On a failure return, any 408 * resources that may have been assigned to res are released, so 409 * the caller does not need to do any cleanup in the failure case. 410 * Returns: 411 * 0 on success 412 * -1 on failure 413 */ 414 static int 415 get_property_value(const char *fmri, const char *pg, const char *prop, 416 boolean_t running, scf_resources_t *res) 417 { 418 if (create_scf_resources(fmri, res) != 0) 419 return (-1); 420 421 if (scf_instance_get_pg_composed(res->sr_inst, 422 running ? res->sr_snap : NULL, pg, res->sr_pg) != 0) { 423 goto failure; 424 } 425 if (scf_pg_get_property(res->sr_pg, prop, res->sr_prop) != 0) { 426 goto failure; 427 } 428 if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) { 429 goto failure; 430 } 431 return (0); 432 433 failure: 434 release_scf_resources(res); 435 return (-1); 436 } 437 438 /* 439 * Inputs: 440 * lfmri is the instance fmri to look up 441 * lpg is the property group to look up 442 * lprop is the property within that group to look up 443 * Outputs: 444 * answer is a pointer to the property value 445 * Returns: 446 * 0 on success 447 * -1 on failure 448 * If successful, the property value is retured in *answer. 449 * Otherwise, *answer is undefined, and it is up to the caller to decide 450 * how to handle that case. 451 */ 452 int 453 nwamd_lookup_boolean_property(const char *lfmri, const char *lpg, 454 const char *lprop, boolean_t *answer) 455 { 456 int result = -1; 457 scf_resources_t res; 458 uint8_t prop_val; 459 460 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 461 462 /* 463 * an error was already logged by get_property_value, 464 * and it released any resources assigned to res before 465 * returning. 466 */ 467 return (result); 468 } 469 if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) { 470 goto cleanup; 471 } 472 *answer = (boolean_t)prop_val; 473 result = 0; 474 cleanup: 475 release_scf_resources(&res); 476 return (result); 477 } 478 479 /* 480 * Inputs: 481 * lfmri is the instance fmri to look up 482 * lpg is the property group to look up 483 * lprop is the property within that group to look up 484 * buf is the place to put the answer 485 * bufsz is the size of buf 486 * Outputs: 487 * 488 * Returns: 489 * 0 on success 490 * -1 on failure 491 * If successful, the property value is retured in buf. 492 * Otherwise, buf is undefined, and it is up to the caller to decide 493 * how to handle that case. 494 */ 495 int 496 nwamd_lookup_string_property(const char *lfmri, const char *lpg, 497 const char *lprop, char *buf, size_t bufsz) 498 { 499 int result = -1; 500 scf_resources_t res; 501 502 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 503 /* 504 * The above function fails when trying to get a 505 * non-persistent property group from the running snapshot. 506 * Try going for the non-running snapshot. 507 */ 508 if (get_property_value(lfmri, lpg, lprop, B_FALSE, &res) != 0) { 509 /* 510 * an error was already logged by get_property_value, 511 * and it released any resources assigned to res before 512 * returning. 513 */ 514 return (result); 515 } 516 } 517 if (scf_value_get_astring(res.sr_val, buf, bufsz) == 0) 518 goto cleanup; 519 520 result = 0; 521 cleanup: 522 release_scf_resources(&res); 523 return (result); 524 } 525 526 /* 527 * Inputs: 528 * lfmri is the instance fmri to look up 529 * lpg is the property group to look up 530 * lprop is the property within that group to look up 531 * Outputs: 532 * answer is a pointer to the property value 533 * Returns: 534 * 0 on success 535 * -1 on failure 536 * If successful, the property value is retured in *answer. 537 * Otherwise, *answer is undefined, and it is up to the caller to decide 538 * how to handle that case. 539 */ 540 int 541 nwamd_lookup_count_property(const char *lfmri, const char *lpg, 542 const char *lprop, uint64_t *answer) 543 { 544 int result = -1; 545 scf_resources_t res; 546 547 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 548 549 /* 550 * an error was already logged by get_property_value, 551 * and it released any resources assigned to res before 552 * returning. 553 */ 554 return (result); 555 } 556 if (scf_value_get_count(res.sr_val, answer) != 0) { 557 goto cleanup; 558 } 559 result = 0; 560 cleanup: 561 release_scf_resources(&res); 562 return (result); 563 } 564 565 static int 566 set_property_value(scf_resources_t *res, const char *propname, 567 scf_type_t proptype) 568 { 569 int result = -1; 570 boolean_t new; 571 572 retry: 573 new = (scf_pg_get_property(res->sr_pg, propname, res->sr_prop) != 0); 574 575 if (scf_transaction_start(res->sr_tx, res->sr_pg) == -1) { 576 goto failure; 577 } 578 if (new) { 579 if (scf_transaction_property_new(res->sr_tx, res->sr_ent, 580 propname, proptype) == -1) { 581 goto failure; 582 } 583 } else { 584 if (scf_transaction_property_change(res->sr_tx, res->sr_ent, 585 propname, proptype) == -1) { 586 goto failure; 587 } 588 } 589 590 if (scf_entry_add_value(res->sr_ent, res->sr_val) != 0) { 591 goto failure; 592 } 593 594 result = scf_transaction_commit(res->sr_tx); 595 if (result == 0) { 596 scf_transaction_reset(res->sr_tx); 597 if (scf_pg_update(res->sr_pg) == -1) { 598 goto failure; 599 } 600 nlog(LOG_INFO, "set_property_value: transaction commit failed " 601 "for %s; retrying", propname); 602 goto retry; 603 } 604 if (result == -1) 605 goto failure; 606 return (0); 607 608 failure: 609 return (-1); 610 } 611 612 int 613 nwamd_set_count_property(const char *fmri, const char *pg, const char *prop, 614 uint64_t count) 615 { 616 scf_resources_t res; 617 618 if (create_scf_resources(fmri, &res) != 0) 619 return (-1); 620 621 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 622 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 623 if (scf_error() != SCF_ERROR_EXISTS) 624 goto failure; 625 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 626 res.sr_pg) != 0) 627 goto failure; 628 } 629 630 scf_value_set_count(res.sr_val, (uint64_t)count); 631 632 if (set_property_value(&res, prop, SCF_TYPE_COUNT) != 0) 633 goto failure; 634 635 release_scf_resources(&res); 636 return (0); 637 638 failure: 639 nlog(LOG_INFO, "nwamd_set_count_property: scf failure %s while " 640 "setting %s", scf_strerror(scf_error()), prop); 641 release_scf_resources(&res); 642 return (-1); 643 } 644 645 int 646 nwamd_set_string_property(const char *fmri, const char *pg, const char *prop, 647 const char *str) 648 { 649 scf_resources_t res; 650 651 if (create_scf_resources(fmri, &res) != 0) 652 return (-1); 653 654 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 655 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 656 if (scf_error() != SCF_ERROR_EXISTS) 657 goto failure; 658 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 659 res.sr_pg) != 0) 660 goto failure; 661 } 662 663 if (scf_value_set_astring(res.sr_val, str) != 0) 664 goto failure; 665 666 if (set_property_value(&res, prop, SCF_TYPE_ASTRING) != 0) 667 goto failure; 668 669 release_scf_resources(&res); 670 return (0); 671 672 failure: 673 nlog(LOG_INFO, "nwamd_set_string_property: scf failure %s while " 674 "setting %s", scf_strerror(scf_error()), prop); 675 release_scf_resources(&res); 676 return (-1); 677 } 678 679 /* 680 * Deletes property prop from property group pg in SMF instance fmri. 681 * Returns 0 on success, -1 on failure. 682 */ 683 int 684 nwamd_delete_scf_property(const char *fmri, const char *pg, const char *prop) 685 { 686 scf_resources_t res; 687 int result = -1; 688 689 if (create_scf_resources(fmri, &res) != 0) 690 return (-1); 691 692 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 693 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 694 if (scf_error() != SCF_ERROR_EXISTS) 695 goto failure; 696 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 697 res.sr_pg) != 0) 698 goto failure; 699 } 700 701 if (scf_pg_get_property(res.sr_pg, prop, res.sr_prop) != 0) 702 goto failure; 703 retry: 704 if (scf_transaction_start(res.sr_tx, res.sr_pg) == -1) 705 goto failure; 706 707 if (scf_transaction_property_delete(res.sr_tx, res.sr_ent, prop) == -1) 708 goto failure; 709 710 result = scf_transaction_commit(res.sr_tx); 711 if (result == 0) { 712 scf_transaction_reset(res.sr_tx); 713 if (scf_pg_update(res.sr_pg) == -1) 714 goto failure; 715 goto retry; 716 } 717 if (result == -1) 718 goto failure; 719 720 release_scf_resources(&res); 721 return (0); 722 failure: 723 release_scf_resources(&res); 724 return (-1); 725 } 726