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 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * Share control API 31 */ 32 #include <stdio.h> 33 #include <string.h> 34 #include <ctype.h> 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <fcntl.h> 38 #include <unistd.h> 39 #include <libxml/parser.h> 40 #include <libxml/tree.h> 41 #include "libshare.h" 42 #include "libshare_impl.h" 43 #include <libscf.h> 44 #include "scfutil.h" 45 #include <ctype.h> 46 #include <libintl.h> 47 #include <thread.h> 48 #include <synch.h> 49 50 #if _NOT_SMF 51 #define CONFIG_FILE "/var/tmp/share.cfg" 52 #define CONFIG_FILE_TMP "/var/tmp/share.cfg.tmp" 53 #endif 54 #define TSTAMP(tm) (uint64_t)(((uint64_t)tm.tv_sec << 32) | \ 55 (tm.tv_nsec & 0xffffffff)) 56 57 #define DFS_LOCK_FILE "/etc/dfs/fstypes" 58 #define SA_STRSIZE 256 /* max string size for names */ 59 60 /* 61 * internal data structures 62 */ 63 64 extern struct sa_proto_plugin *sap_proto_list; 65 66 /* current SMF/SVC repository handle */ 67 extern void getlegacyconfig(sa_handle_t, char *, xmlNodePtr *); 68 extern int gettransients(sa_handle_impl_t, xmlNodePtr *); 69 extern int sa_valid_property(void *, char *, sa_property_t); 70 extern char *sa_fstype(char *); 71 extern int sa_is_share(void *); 72 extern ssize_t scf_max_name_len; /* defined in scfutil during initialization */ 73 extern int sa_group_is_zfs(sa_group_t); 74 extern int sa_path_is_zfs(char *); 75 extern int sa_zfs_set_sharenfs(sa_group_t, char *, int); 76 extern void update_legacy_config(sa_handle_t); 77 extern int issubdir(char *, char *); 78 extern int sa_zfs_init(sa_handle_impl_t); 79 extern void sa_zfs_fini(sa_handle_impl_t); 80 extern void sablocksigs(sigset_t *); 81 extern void saunblocksigs(sigset_t *); 82 83 /* 84 * Data structures for finding/managing the document root to access 85 * handle mapping. The list isn't expected to grow very large so a 86 * simple list is acceptable. The purpose is to provide a way to start 87 * with a group or share and find the library handle needed for 88 * various operations. 89 */ 90 mutex_t sa_global_lock; 91 struct doc2handle { 92 struct doc2handle *next; 93 xmlNodePtr root; 94 sa_handle_impl_t handle; 95 }; 96 97 /* definitions used in a couple of property functions */ 98 #define SA_PROP_OP_REMOVE 1 99 #define SA_PROP_OP_ADD 2 100 #define SA_PROP_OP_UPDATE 3 101 102 static struct doc2handle *sa_global_handles = NULL; 103 104 /* helper functions */ 105 106 /* 107 * sa_errorstr(err) 108 * 109 * convert an error value to an error string 110 */ 111 112 char * 113 sa_errorstr(int err) 114 { 115 static char errstr[32]; 116 char *ret = NULL; 117 118 switch (err) { 119 case SA_OK: 120 ret = dgettext(TEXT_DOMAIN, "ok"); 121 break; 122 case SA_NO_SUCH_PATH: 123 ret = dgettext(TEXT_DOMAIN, "path doesn't exist"); 124 break; 125 case SA_NO_MEMORY: 126 ret = dgettext(TEXT_DOMAIN, "no memory"); 127 break; 128 case SA_DUPLICATE_NAME: 129 ret = dgettext(TEXT_DOMAIN, "name in use"); 130 break; 131 case SA_BAD_PATH: 132 ret = dgettext(TEXT_DOMAIN, "bad path"); 133 break; 134 case SA_NO_SUCH_GROUP: 135 ret = dgettext(TEXT_DOMAIN, "no such group"); 136 break; 137 case SA_CONFIG_ERR: 138 ret = dgettext(TEXT_DOMAIN, "configuration error"); 139 break; 140 case SA_SYSTEM_ERR: 141 ret = dgettext(TEXT_DOMAIN, "system error"); 142 break; 143 case SA_SYNTAX_ERR: 144 ret = dgettext(TEXT_DOMAIN, "syntax error"); 145 break; 146 case SA_NO_PERMISSION: 147 ret = dgettext(TEXT_DOMAIN, "no permission"); 148 break; 149 case SA_BUSY: 150 ret = dgettext(TEXT_DOMAIN, "busy"); 151 break; 152 case SA_NO_SUCH_PROP: 153 ret = dgettext(TEXT_DOMAIN, "no such property"); 154 break; 155 case SA_INVALID_NAME: 156 ret = dgettext(TEXT_DOMAIN, "invalid name"); 157 break; 158 case SA_INVALID_PROTOCOL: 159 ret = dgettext(TEXT_DOMAIN, "invalid protocol"); 160 break; 161 case SA_NOT_ALLOWED: 162 ret = dgettext(TEXT_DOMAIN, "operation not allowed"); 163 break; 164 case SA_BAD_VALUE: 165 ret = dgettext(TEXT_DOMAIN, "bad property value"); 166 break; 167 case SA_INVALID_SECURITY: 168 ret = dgettext(TEXT_DOMAIN, "invalid security type"); 169 break; 170 case SA_NO_SUCH_SECURITY: 171 ret = dgettext(TEXT_DOMAIN, "security type not found"); 172 break; 173 case SA_VALUE_CONFLICT: 174 ret = dgettext(TEXT_DOMAIN, "property value conflict"); 175 break; 176 case SA_NOT_IMPLEMENTED: 177 ret = dgettext(TEXT_DOMAIN, "not implemented"); 178 break; 179 case SA_INVALID_PATH: 180 ret = dgettext(TEXT_DOMAIN, "invalid path"); 181 break; 182 case SA_NOT_SUPPORTED: 183 ret = dgettext(TEXT_DOMAIN, "operation not supported"); 184 break; 185 case SA_PROP_SHARE_ONLY: 186 ret = dgettext(TEXT_DOMAIN, "property not valid for group"); 187 break; 188 case SA_NOT_SHARED: 189 ret = dgettext(TEXT_DOMAIN, "not shared"); 190 break; 191 default: 192 (void) snprintf(errstr, sizeof (errstr), 193 dgettext(TEXT_DOMAIN, "unknown %d"), err); 194 ret = errstr; 195 } 196 return (ret); 197 } 198 199 /* 200 * Document root to active handle mapping functions. These are only 201 * used internally. A mutex is used to prevent access while the list 202 * is changing. In general, the list will be relatively short - one 203 * item per thread that has called sa_init(). 204 */ 205 206 sa_handle_impl_t 207 get_handle_for_root(xmlNodePtr root) 208 { 209 struct doc2handle *item; 210 211 (void) mutex_lock(&sa_global_lock); 212 for (item = sa_global_handles; item != NULL; item = item->next) { 213 if (item->root == root) 214 break; 215 } 216 (void) mutex_unlock(&sa_global_lock); 217 if (item != NULL) 218 return (item->handle); 219 return (NULL); 220 } 221 222 static int 223 add_handle_for_root(xmlNodePtr root, sa_handle_impl_t handle) 224 { 225 struct doc2handle *item; 226 int ret = SA_NO_MEMORY; 227 228 item = (struct doc2handle *)calloc(sizeof (struct doc2handle), 1); 229 if (item != NULL) { 230 item->root = root; 231 item->handle = handle; 232 (void) mutex_lock(&sa_global_lock); 233 item->next = sa_global_handles; 234 sa_global_handles = item; 235 (void) mutex_unlock(&sa_global_lock); 236 ret = SA_OK; 237 } 238 return (ret); 239 } 240 241 /* 242 * remove_handle_for_root(root) 243 * 244 * Walks the list of handles and removes the one for this "root" from 245 * the list. It is up to the caller to free the data. 246 */ 247 248 static void 249 remove_handle_for_root(xmlNodePtr root) 250 { 251 struct doc2handle *item, *prev; 252 253 (void) mutex_lock(&sa_global_lock); 254 for (prev = NULL, item = sa_global_handles; item != NULL; 255 item = item->next) { 256 if (item->root == root) { 257 /* first in the list */ 258 if (prev == NULL) 259 sa_global_handles = sa_global_handles->next; 260 else 261 prev->next = item->next; 262 /* Item is out of the list so free the list structure */ 263 free(item); 264 break; 265 } 266 prev = item; 267 } 268 (void) mutex_unlock(&sa_global_lock); 269 } 270 271 /* 272 * sa_find_group_handle(sa_group_t group) 273 * 274 * Find the sa_handle_t for the configuration associated with this 275 * group. 276 */ 277 sa_handle_t 278 sa_find_group_handle(sa_group_t group) 279 { 280 xmlNodePtr node = (xmlNodePtr)group; 281 sa_handle_t handle; 282 283 while (node != NULL) { 284 if (strcmp((char *)(node->name), "sharecfg") == 0) { 285 /* have the root so get the handle */ 286 handle = (sa_handle_t)get_handle_for_root(node); 287 return (handle); 288 } 289 node = node->parent; 290 } 291 return (NULL); 292 } 293 294 /* 295 * set_legacy_timestamp(root, path, timevalue) 296 * 297 * add the current timestamp value to the configuration for use in 298 * determining when to update the legacy files. For SMF, this 299 * property is kept in default/operation/legacy_timestamp 300 */ 301 302 static void 303 set_legacy_timestamp(xmlNodePtr root, char *path, uint64_t tval) 304 { 305 xmlNodePtr node; 306 xmlChar *lpath = NULL; 307 sa_handle_impl_t handle; 308 309 /* Have to have a handle or else we weren't initialized. */ 310 handle = get_handle_for_root(root); 311 if (handle == NULL) 312 return; 313 314 for (node = root->xmlChildrenNode; node != NULL; 315 node = node->next) { 316 if (xmlStrcmp(node->name, (xmlChar *)"legacy") == 0) { 317 /* a possible legacy node for this path */ 318 lpath = xmlGetProp(node, (xmlChar *)"path"); 319 if (lpath != NULL && 320 xmlStrcmp(lpath, (xmlChar *)path) == 0) { 321 xmlFree(lpath); 322 break; 323 } 324 if (lpath != NULL) 325 xmlFree(lpath); 326 } 327 } 328 if (node == NULL) { 329 /* need to create the first legacy timestamp node */ 330 node = xmlNewChild(root, NULL, (xmlChar *)"legacy", NULL); 331 } 332 if (node != NULL) { 333 char tstring[32]; 334 int ret; 335 336 (void) snprintf(tstring, sizeof (tstring), "%lld", tval); 337 xmlSetProp(node, (xmlChar *)"timestamp", (xmlChar *)tstring); 338 xmlSetProp(node, (xmlChar *)"path", (xmlChar *)path); 339 /* now commit to SMF */ 340 ret = sa_get_instance(handle->scfhandle, "default"); 341 if (ret == SA_OK) { 342 ret = sa_start_transaction(handle->scfhandle, 343 "operation"); 344 if (ret == SA_OK) { 345 ret = sa_set_property(handle->scfhandle, 346 "legacy-timestamp", tstring); 347 if (ret == SA_OK) { 348 (void) sa_end_transaction( 349 handle->scfhandle); 350 } else { 351 sa_abort_transaction(handle->scfhandle); 352 } 353 } 354 } 355 } 356 } 357 358 /* 359 * is_shared(share) 360 * 361 * determine if the specified share is currently shared or not. 362 */ 363 static int 364 is_shared(sa_share_t share) 365 { 366 char *shared; 367 int result = 0; /* assume not */ 368 369 shared = sa_get_share_attr(share, "shared"); 370 if (shared != NULL) { 371 if (strcmp(shared, "true") == 0) 372 result = 1; 373 sa_free_attr_string(shared); 374 } 375 return (result); 376 } 377 378 /* 379 * checksubdirgroup(group, newpath, strictness) 380 * 381 * check all the specified newpath against all the paths in the 382 * group. This is a helper function for checksubdir to make it easier 383 * to also check ZFS subgroups. 384 * The strictness values mean: 385 * SA_CHECK_NORMAL == only check newpath against shares that are active 386 * SA_CHECK_STRICT == check newpath against both active shares and those 387 * stored in the repository 388 */ 389 static int 390 checksubdirgroup(sa_group_t group, char *newpath, int strictness) 391 { 392 sa_share_t share; 393 char *path; 394 int issub = SA_OK; 395 396 for (share = sa_get_share(group, NULL); share != NULL; 397 share = sa_get_next_share(share)) { 398 /* 399 * The original behavior of share never checked 400 * against the permanent configuration 401 * (/etc/dfs/dfstab). PIT has a number of cases where 402 * it depends on this older behavior even though it 403 * could be considered incorrect. We may tighten this 404 * up in the future. 405 */ 406 if (strictness == SA_CHECK_NORMAL && !is_shared(share)) 407 continue; 408 409 path = sa_get_share_attr(share, "path"); 410 /* 411 * If path is NULL, then a share is in the process of 412 * construction or someone has modified the property 413 * group inappropriately. It should be 414 * ignored. issubdir() comes from the original share 415 * implementation and does the difficult part of 416 * checking subdirectories. 417 */ 418 if (path == NULL) 419 continue; 420 if (newpath != NULL && 421 (strcmp(path, newpath) == 0 || issubdir(newpath, path) || 422 issubdir(path, newpath))) { 423 sa_free_attr_string(path); 424 path = NULL; 425 issub = SA_INVALID_PATH; 426 break; 427 } 428 sa_free_attr_string(path); 429 path = NULL; 430 } 431 return (issub); 432 } 433 434 /* 435 * checksubdir(newpath, strictness) 436 * 437 * checksubdir determines if the specified path (newpath) is a 438 * subdirectory of another share. It calls checksubdirgroup() to do 439 * the complicated work. The strictness parameter determines how 440 * strict a check to make against the path. The strictness values 441 * mean: SA_CHECK_NORMAL == only check newpath against shares that are 442 * active SA_CHECK_STRICT == check newpath against both active shares 443 * and those * stored in the repository 444 */ 445 static int 446 checksubdir(sa_handle_t handle, char *newpath, int strictness) 447 { 448 sa_group_t group; 449 int issub; 450 char *path = NULL; 451 452 for (issub = 0, group = sa_get_group(handle, NULL); 453 group != NULL && !issub; group = sa_get_next_group(group)) { 454 if (sa_group_is_zfs(group)) { 455 sa_group_t subgroup; 456 for (subgroup = sa_get_sub_group(group); 457 subgroup != NULL && !issub; 458 subgroup = sa_get_next_group(subgroup)) 459 issub = checksubdirgroup(subgroup, newpath, 460 strictness); 461 } else { 462 issub = checksubdirgroup(group, newpath, strictness); 463 } 464 } 465 if (path != NULL) 466 sa_free_attr_string(path); 467 return (issub); 468 } 469 470 /* 471 * validpath(path, strictness) 472 * determine if the provided path is valid for a share. It shouldn't 473 * be a sub-dir of an already shared path or the parent directory of a 474 * share path. 475 */ 476 static int 477 validpath(sa_handle_t handle, char *path, int strictness) 478 { 479 int error = SA_OK; 480 struct stat st; 481 sa_share_t share; 482 char *fstype; 483 484 if (*path != '/') 485 return (SA_BAD_PATH); 486 487 if (stat(path, &st) < 0) { 488 error = SA_NO_SUCH_PATH; 489 } else { 490 share = sa_find_share(handle, path); 491 if (share != NULL) 492 error = SA_DUPLICATE_NAME; 493 494 if (error == SA_OK) { 495 /* 496 * check for special case with file system 497 * that might have restrictions. For now, ZFS 498 * is the only case since it has its own idea 499 * of how to configure shares. We do this 500 * before subdir checking since things like 501 * ZFS will do that for us. This should also 502 * be done via plugin interface. 503 */ 504 fstype = sa_fstype(path); 505 if (fstype != NULL && strcmp(fstype, "zfs") == 0) { 506 if (sa_zfs_is_shared(handle, path)) 507 error = SA_INVALID_NAME; 508 } 509 if (fstype != NULL) 510 sa_free_fstype(fstype); 511 } 512 if (error == SA_OK) 513 error = checksubdir(handle, path, strictness); 514 } 515 return (error); 516 } 517 518 /* 519 * check to see if group/share is persistent. 520 */ 521 static int 522 is_persistent(sa_group_t group) 523 { 524 char *type; 525 int persist = 1; 526 527 type = sa_get_group_attr(group, "type"); 528 if (type != NULL && strcmp(type, "transient") == 0) 529 persist = 0; 530 if (type != NULL) 531 sa_free_attr_string(type); 532 return (persist); 533 } 534 535 /* 536 * sa_valid_group_name(name) 537 * 538 * check that the "name" contains only valid characters and otherwise 539 * fits the required naming conventions. Valid names must start with 540 * an alphabetic and the remainder may consist of only alphanumeric 541 * plus the '-' and '_' characters. This name limitation comes from 542 * inherent limitations in SMF. 543 */ 544 545 int 546 sa_valid_group_name(char *name) 547 { 548 int ret = 1; 549 ssize_t len; 550 551 if (name != NULL && isalpha(*name)) { 552 char c; 553 len = strlen(name); 554 if (len < (scf_max_name_len - sizeof ("group:"))) { 555 for (c = *name++; c != '\0' && ret != 0; c = *name++) { 556 if (!isalnum(c) && c != '-' && c != '_') 557 ret = 0; 558 } 559 } else { 560 ret = 0; 561 } 562 } else { 563 ret = 0; 564 } 565 return (ret); 566 } 567 568 569 /* 570 * is_zfs_group(group) 571 * Determine if the specified group is a ZFS sharenfs group 572 */ 573 static int 574 is_zfs_group(sa_group_t group) 575 { 576 int ret = 0; 577 xmlNodePtr parent; 578 xmlChar *zfs; 579 580 if (strcmp((char *)((xmlNodePtr)group)->name, "share") == 0) 581 parent = (xmlNodePtr)sa_get_parent_group(group); 582 else 583 parent = (xmlNodePtr)group; 584 zfs = xmlGetProp(parent, (xmlChar *)"zfs"); 585 if (zfs != NULL) { 586 xmlFree(zfs); 587 ret = 1; 588 } 589 return (ret); 590 } 591 592 /* 593 * sa_optionset_name(optionset, oname, len, id) 594 * return the SMF name for the optionset. If id is not NULL, it 595 * will have the GUID value for a share and should be used 596 * instead of the keyword "optionset" which is used for 597 * groups. If the optionset doesn't have a protocol type 598 * associated with it, "default" is used. This shouldn't happen 599 * at this point but may be desirable in the future if there are 600 * protocol independent properties added. The name is returned in 601 * oname. 602 */ 603 604 static int 605 sa_optionset_name(sa_optionset_t optionset, char *oname, size_t len, char *id) 606 { 607 char *proto; 608 609 if (id == NULL) 610 id = "optionset"; 611 612 proto = sa_get_optionset_attr(optionset, "type"); 613 len = snprintf(oname, len, "%s_%s", id, proto ? proto : "default"); 614 615 if (proto != NULL) 616 sa_free_attr_string(proto); 617 return (len); 618 } 619 620 /* 621 * sa_security_name(optionset, oname, len, id) 622 * 623 * return the SMF name for the security. If id is not NULL, it will 624 * have the GUID value for a share and should be used instead of the 625 * keyword "optionset" which is used for groups. If the optionset 626 * doesn't have a protocol type associated with it, "default" is 627 * used. This shouldn't happen at this point but may be desirable in 628 * the future if there are protocol independent properties added. The 629 * name is returned in oname. The security type is also encoded into 630 * the name. In the future, this wil *be handled a bit differently. 631 */ 632 633 static int 634 sa_security_name(sa_security_t security, char *oname, size_t len, char *id) 635 { 636 char *proto; 637 char *sectype; 638 639 if (id == NULL) 640 id = "optionset"; 641 642 proto = sa_get_security_attr(security, "type"); 643 sectype = sa_get_security_attr(security, "sectype"); 644 len = snprintf(oname, len, "%s_%s_%s", id, proto ? proto : "default", 645 sectype ? sectype : "default"); 646 if (proto != NULL) 647 sa_free_attr_string(proto); 648 if (sectype != NULL) 649 sa_free_attr_string(sectype); 650 return (len); 651 } 652 653 /* 654 * verifydefgroupopts(handle) 655 * 656 * Make sure a "default" group exists and has default protocols enabled. 657 */ 658 static void 659 verifydefgroupopts(sa_handle_t handle) 660 { 661 sa_group_t defgrp; 662 sa_optionset_t opt; 663 defgrp = sa_get_group(handle, "default"); 664 if (defgrp != NULL) { 665 opt = sa_get_optionset(defgrp, NULL); 666 /* 667 * NFS is the default for default group 668 */ 669 if (opt == NULL) 670 opt = sa_create_optionset(defgrp, "nfs"); 671 } 672 } 673 674 /* 675 * sa_init(init_service) 676 * Initialize the API 677 * find all the shared objects 678 * init the tables with all objects 679 * read in the current configuration 680 */ 681 682 #define GETPROP(prop) scf_simple_prop_next_astring(prop) 683 #define CHECKTSTAMP(st, tval) stat(SA_LEGACY_DFSTAB, &st) >= 0 && \ 684 tval != TSTAMP(st.st_ctim) 685 686 sa_handle_t 687 sa_init(int init_service) 688 { 689 struct stat st; 690 int legacy = 0; 691 uint64_t tval = 0; 692 int lockfd; 693 sigset_t old; 694 int updatelegacy = B_FALSE; 695 scf_simple_prop_t *prop; 696 sa_handle_impl_t handle; 697 int err; 698 699 handle = calloc(sizeof (struct sa_handle_impl), 1); 700 701 if (handle != NULL) { 702 /* get protocol specific structures */ 703 (void) proto_plugin_init(); 704 if (init_service & SA_INIT_SHARE_API) { 705 /* 706 * initialize access into libzfs. We use this 707 * when collecting info about ZFS datasets and 708 * shares. 709 */ 710 if (sa_zfs_init(handle) == B_FALSE) { 711 free(handle); 712 (void) proto_plugin_fini(); 713 return (NULL); 714 } 715 /* 716 * since we want to use SMF, initialize an svc handle 717 * and find out what is there. 718 */ 719 handle->scfhandle = sa_scf_init(handle); 720 if (handle->scfhandle != NULL) { 721 /* 722 * Need to lock the extraction of the 723 * configuration if the dfstab file has 724 * changed. Lock everything now and release if 725 * not needed. Use a file that isn't being 726 * manipulated by other parts of the system in 727 * order to not interfere with locking. Using 728 * dfstab doesn't work. 729 */ 730 sablocksigs(&old); 731 lockfd = open(DFS_LOCK_FILE, O_RDWR); 732 if (lockfd >= 0) { 733 extern int errno; 734 errno = 0; 735 (void) lockf(lockfd, F_LOCK, 0); 736 /* 737 * Check whether we are going to need 738 * to merge any dfstab changes. This 739 * is done by comparing the value of 740 * legacy-timestamp with the current 741 * st_ctim of the file. If they are 742 * different, an update is needed and 743 * the file must remain locked until 744 * the merge is done in order to 745 * prevent multiple startups from 746 * changing the SMF repository at the 747 * same time. The first to get the 748 * lock will make any changes before 749 * the others can read the repository. 750 */ 751 prop = scf_simple_prop_get 752 (handle->scfhandle->handle, 753 (const char *)SA_SVC_FMRI_BASE 754 ":default", "operation", 755 "legacy-timestamp"); 756 if (prop != NULL) { 757 char *i64; 758 i64 = GETPROP(prop); 759 if (i64 != NULL) 760 tval = strtoull(i64, 761 NULL, 0); 762 if (CHECKTSTAMP(st, tval)) 763 updatelegacy = B_TRUE; 764 scf_simple_prop_free(prop); 765 } else { 766 /* 767 * We haven't set the 768 * timestamp before so do it. 769 */ 770 updatelegacy = B_TRUE; 771 } 772 } 773 if (updatelegacy == B_FALSE) { 774 /* Don't need the lock anymore */ 775 (void) lockf(lockfd, F_ULOCK, 0); 776 (void) close(lockfd); 777 } 778 779 /* 780 * It is essential that the document tree and 781 * the internal list of roots to handles be 782 * setup before anything that might try to 783 * create a new object is called. The document 784 * tree is the combination of handle->doc and 785 * handle->tree. This allows searches, 786 * etc. when all you have is an object in the 787 * tree. 788 */ 789 handle->doc = xmlNewDoc((xmlChar *)"1.0"); 790 handle->tree = xmlNewNode(NULL, 791 (xmlChar *)"sharecfg"); 792 if (handle->doc != NULL && 793 handle->tree != NULL) { 794 xmlDocSetRootElement(handle->doc, 795 handle->tree); 796 err = add_handle_for_root(handle->tree, 797 handle); 798 if (err == SA_OK) 799 err = sa_get_config( 800 handle->scfhandle, 801 handle->tree, handle); 802 } else { 803 if (handle->doc != NULL) 804 xmlFreeDoc(handle->doc); 805 if (handle->tree != NULL) 806 xmlFreeNode(handle->tree); 807 err = SA_NO_MEMORY; 808 } 809 810 saunblocksigs(&old); 811 812 if (err != SA_OK) { 813 /* 814 * If we couldn't add the tree handle 815 * to the list, then things are going 816 * to fail badly. Might as well undo 817 * everything now and fail the 818 * sa_init(). 819 */ 820 sa_fini(handle); 821 return (NULL); 822 } 823 824 if (tval == 0) { 825 /* 826 * first time so make sure 827 * default is setup 828 */ 829 verifydefgroupopts(handle); 830 } 831 832 if (updatelegacy == B_TRUE) { 833 sablocksigs(&old); 834 getlegacyconfig((sa_handle_t)handle, 835 SA_LEGACY_DFSTAB, &handle->tree); 836 if (stat(SA_LEGACY_DFSTAB, &st) >= 0) 837 set_legacy_timestamp(handle->tree, 838 SA_LEGACY_DFSTAB, 839 TSTAMP(st.st_ctim)); 840 saunblocksigs(&old); 841 /* Safe to unlock now to allow others to run */ 842 (void) lockf(lockfd, F_ULOCK, 0); 843 (void) close(lockfd); 844 } 845 legacy |= sa_get_zfs_shares(handle, "zfs"); 846 legacy |= gettransients(handle, &handle->tree); 847 } 848 } 849 } 850 return ((sa_handle_t)handle); 851 } 852 853 /* 854 * sa_fini(handle) 855 * Uninitialize the API structures including the configuration 856 * data structures and ZFS related data. 857 */ 858 859 void 860 sa_fini(sa_handle_t handle) 861 { 862 sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; 863 864 if (impl_handle != NULL) { 865 /* 866 * Free the config trees and any other data structures 867 * used in the handle. 868 */ 869 if (impl_handle->doc != NULL) 870 xmlFreeDoc(impl_handle->doc); 871 sa_scf_fini(impl_handle->scfhandle); 872 sa_zfs_fini(impl_handle); 873 874 /* Remove and free the entry in the global list. */ 875 remove_handle_for_root(impl_handle->tree); 876 877 /* Make sure we free the handle */ 878 free(impl_handle); 879 880 /* 881 * If this was the last handle to release, unload the 882 * plugins that were loaded. 883 */ 884 if (sa_global_handles == NULL) 885 (void) proto_plugin_fini(); 886 887 } 888 } 889 890 /* 891 * sa_get_protocols(char **protocol) 892 * Get array of protocols that are supported 893 * Returns pointer to an allocated and NULL terminated 894 * array of strings. Caller must free. 895 * This really should be determined dynamically. 896 * If there aren't any defined, return -1. 897 * Use free() to return memory. 898 */ 899 900 int 901 sa_get_protocols(char ***protocols) 902 { 903 int numproto = -1; 904 905 if (protocols != NULL) { 906 struct sa_proto_plugin *plug; 907 for (numproto = 0, plug = sap_proto_list; plug != NULL; 908 plug = plug->plugin_next) { 909 numproto++; 910 } 911 912 *protocols = calloc(numproto + 1, sizeof (char *)); 913 if (*protocols != NULL) { 914 int ret = 0; 915 for (plug = sap_proto_list; plug != NULL; 916 plug = plug->plugin_next) { 917 /* faking for now */ 918 (*protocols)[ret++] = 919 plug->plugin_ops->sa_protocol; 920 } 921 } else { 922 numproto = -1; 923 } 924 } 925 return (numproto); 926 } 927 928 /* 929 * find_group_by_name(node, group) 930 * 931 * search the XML document subtree specified by node to find the group 932 * specified by group. Searching subtree allows subgroups to be 933 * searched for. 934 */ 935 936 static xmlNodePtr 937 find_group_by_name(xmlNodePtr node, xmlChar *group) 938 { 939 xmlChar *name = NULL; 940 941 for (node = node->xmlChildrenNode; node != NULL; 942 node = node->next) { 943 if (xmlStrcmp(node->name, (xmlChar *)"group") == 0) { 944 /* if no groupname, return the first found */ 945 if (group == NULL) 946 break; 947 name = xmlGetProp(node, (xmlChar *)"name"); 948 if (name != NULL && xmlStrcmp(name, group) == 0) 949 break; 950 if (name != NULL) { 951 xmlFree(name); 952 name = NULL; 953 } 954 } 955 } 956 if (name != NULL) 957 xmlFree(name); 958 return (node); 959 } 960 961 /* 962 * sa_get_group(groupname) 963 * Return the "group" specified. If groupname is NULL, 964 * return the first group of the list of groups. 965 */ 966 sa_group_t 967 sa_get_group(sa_handle_t handle, char *groupname) 968 { 969 xmlNodePtr node = NULL; 970 char *subgroup = NULL; 971 char *group = NULL; 972 sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; 973 974 if (impl_handle != NULL && impl_handle->tree != NULL) { 975 if (groupname != NULL) { 976 group = strdup(groupname); 977 subgroup = strchr(group, '/'); 978 if (subgroup != NULL) 979 *subgroup++ = '\0'; 980 } 981 node = find_group_by_name(impl_handle->tree, (xmlChar *)group); 982 /* if a subgroup, find it before returning */ 983 if (subgroup != NULL && node != NULL) 984 node = find_group_by_name(node, (xmlChar *)subgroup); 985 } 986 if (node != NULL && (char *)group != NULL) 987 (void) sa_get_instance(impl_handle->scfhandle, (char *)group); 988 if (group != NULL) 989 free(group); 990 return ((sa_group_t)(node)); 991 } 992 993 /* 994 * sa_get_next_group(group) 995 * Return the "next" group after the specified group from 996 * the internal group list. NULL if there are no more. 997 */ 998 sa_group_t 999 sa_get_next_group(sa_group_t group) 1000 { 1001 xmlNodePtr ngroup = NULL; 1002 if (group != NULL) { 1003 for (ngroup = ((xmlNodePtr)group)->next; ngroup != NULL; 1004 ngroup = ngroup->next) { 1005 if (xmlStrcmp(ngroup->name, (xmlChar *)"group") == 0) 1006 break; 1007 } 1008 } 1009 return ((sa_group_t)ngroup); 1010 } 1011 1012 /* 1013 * sa_get_share(group, sharepath) 1014 * Return the share object for the share specified. The share 1015 * must be in the specified group. Return NULL if not found. 1016 */ 1017 sa_share_t 1018 sa_get_share(sa_group_t group, char *sharepath) 1019 { 1020 xmlNodePtr node = NULL; 1021 xmlChar *path; 1022 1023 /* 1024 * For future scalability, this should end up building a cache 1025 * since it will get called regularly by the mountd and info 1026 * services. 1027 */ 1028 if (group != NULL) { 1029 for (node = ((xmlNodePtr)group)->children; node != NULL; 1030 node = node->next) { 1031 if (xmlStrcmp(node->name, (xmlChar *)"share") == 0) { 1032 if (sharepath == NULL) { 1033 break; 1034 } else { 1035 /* is it the correct share? */ 1036 path = xmlGetProp(node, 1037 (xmlChar *)"path"); 1038 if (path != NULL && 1039 xmlStrcmp(path, 1040 (xmlChar *)sharepath) == 0) { 1041 xmlFree(path); 1042 break; 1043 } 1044 xmlFree(path); 1045 } 1046 } 1047 } 1048 } 1049 return ((sa_share_t)node); 1050 } 1051 1052 /* 1053 * sa_get_next_share(share) 1054 * Return the next share following the specified share 1055 * from the internal list of shares. Returns NULL if there 1056 * are no more shares. The list is relative to the same 1057 * group. 1058 */ 1059 sa_share_t 1060 sa_get_next_share(sa_share_t share) 1061 { 1062 xmlNodePtr node = NULL; 1063 1064 if (share != NULL) { 1065 for (node = ((xmlNodePtr)share)->next; node != NULL; 1066 node = node->next) { 1067 if (xmlStrcmp(node->name, (xmlChar *)"share") == 0) { 1068 break; 1069 } 1070 } 1071 } 1072 return ((sa_share_t)node); 1073 } 1074 1075 /* 1076 * _sa_get_child_node(node, type) 1077 * 1078 * find the child node of the specified node that has "type". This is 1079 * used to implement several internal functions. 1080 */ 1081 1082 static xmlNodePtr 1083 _sa_get_child_node(xmlNodePtr node, xmlChar *type) 1084 { 1085 xmlNodePtr child; 1086 for (child = node->xmlChildrenNode; child != NULL; 1087 child = child->next) 1088 if (xmlStrcmp(child->name, type) == 0) 1089 return (child); 1090 return ((xmlNodePtr)NULL); 1091 } 1092 1093 /* 1094 * find_share(group, path) 1095 * 1096 * Search all the shares in the specified group for one that has the 1097 * specified path. 1098 */ 1099 1100 static sa_share_t 1101 find_share(sa_group_t group, char *sharepath) 1102 { 1103 sa_share_t share; 1104 char *path; 1105 1106 for (share = sa_get_share(group, NULL); share != NULL; 1107 share = sa_get_next_share(share)) { 1108 path = sa_get_share_attr(share, "path"); 1109 if (path != NULL && strcmp(path, sharepath) == 0) { 1110 sa_free_attr_string(path); 1111 break; 1112 } 1113 if (path != NULL) 1114 sa_free_attr_string(path); 1115 } 1116 return (share); 1117 } 1118 1119 /* 1120 * sa_get_sub_group(group) 1121 * 1122 * Get the first sub-group of group. The sa_get_next_group() function 1123 * can be used to get the rest. This is currently only used for ZFS 1124 * sub-groups but could be used to implement a more general mechanism. 1125 */ 1126 1127 sa_group_t 1128 sa_get_sub_group(sa_group_t group) 1129 { 1130 return ((sa_group_t)_sa_get_child_node((xmlNodePtr)group, 1131 (xmlChar *)"group")); 1132 } 1133 1134 /* 1135 * sa_find_share(sharepath) 1136 * Finds a share regardless of group. In the future, this 1137 * function should utilize a cache and hash table of some kind. 1138 * The current assumption is that a path will only be shared 1139 * once. In the future, this may change as implementation of 1140 * resource names comes into being. 1141 */ 1142 sa_share_t 1143 sa_find_share(sa_handle_t handle, char *sharepath) 1144 { 1145 sa_group_t group; 1146 sa_group_t zgroup; 1147 sa_share_t share = NULL; 1148 int done = 0; 1149 1150 for (group = sa_get_group(handle, NULL); group != NULL && !done; 1151 group = sa_get_next_group(group)) { 1152 if (is_zfs_group(group)) { 1153 for (zgroup = 1154 (sa_group_t)_sa_get_child_node((xmlNodePtr)group, 1155 (xmlChar *)"group"); 1156 zgroup != NULL; 1157 zgroup = sa_get_next_group(zgroup)) { 1158 share = find_share(zgroup, sharepath); 1159 if (share != NULL) 1160 break; 1161 } 1162 } else { 1163 share = find_share(group, sharepath); 1164 } 1165 if (share != NULL) 1166 break; 1167 } 1168 return (share); 1169 } 1170 1171 /* 1172 * sa_check_path(group, path, strictness) 1173 * 1174 * check that path is a valid path relative to the group. Currently, 1175 * we are ignoring the group and checking only the NFS rules. Later, 1176 * we may want to use the group to then check against the protocols 1177 * enabled on the group. The strictness values mean: 1178 * SA_CHECK_NORMAL == only check newpath against shares that are active 1179 * SA_CHECK_STRICT == check newpath against both active shares and those 1180 * stored in the repository 1181 */ 1182 1183 int 1184 sa_check_path(sa_group_t group, char *path, int strictness) 1185 { 1186 sa_handle_t handle; 1187 1188 handle = sa_find_group_handle(group); 1189 return (validpath(handle, path, strictness)); 1190 } 1191 1192 /* 1193 * _sa_add_share(group, sharepath, persist, *error) 1194 * 1195 * common code for all types of add_share. sa_add_share() is the 1196 * public API, we also need to be able to do this when parsing legacy 1197 * files and construction of the internal configuration while 1198 * extracting config info from SMF. 1199 */ 1200 1201 sa_share_t 1202 _sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) 1203 { 1204 xmlNodePtr node = NULL; 1205 int err; 1206 1207 err = SA_OK; /* assume success */ 1208 1209 node = xmlNewChild((xmlNodePtr)group, NULL, (xmlChar *)"share", NULL); 1210 if (node != NULL) { 1211 xmlSetProp(node, (xmlChar *)"path", (xmlChar *)sharepath); 1212 xmlSetProp(node, (xmlChar *)"type", 1213 persist ? (xmlChar *)"persist" : (xmlChar *)"transient"); 1214 if (persist != SA_SHARE_TRANSIENT) { 1215 /* 1216 * persistent shares come in two flavors: SMF and 1217 * ZFS. Sort this one out based on target group and 1218 * path type. Currently, only NFS is supported in the 1219 * ZFS group and it is always on. 1220 */ 1221 if (sa_group_is_zfs(group) && 1222 sa_path_is_zfs(sharepath)) { 1223 err = sa_zfs_set_sharenfs(group, sharepath, 1); 1224 } else { 1225 sa_handle_impl_t impl_handle; 1226 impl_handle = 1227 (sa_handle_impl_t)sa_find_group_handle( 1228 group); 1229 if (impl_handle != NULL) { 1230 err = sa_commit_share( 1231 impl_handle->scfhandle, group, 1232 (sa_share_t)node); 1233 } else { 1234 err = SA_SYSTEM_ERR; 1235 } 1236 } 1237 } 1238 if (err == SA_NO_PERMISSION && persist & SA_SHARE_PARSER) { 1239 /* called by the dfstab parser so could be a show */ 1240 err = SA_OK; 1241 } 1242 if (err != SA_OK) { 1243 /* 1244 * we couldn't commit to the repository so undo 1245 * our internal state to reflect reality. 1246 */ 1247 xmlUnlinkNode(node); 1248 xmlFreeNode(node); 1249 node = NULL; 1250 } 1251 } else { 1252 err = SA_NO_MEMORY; 1253 } 1254 if (error != NULL) 1255 *error = err; 1256 return (node); 1257 } 1258 1259 /* 1260 * sa_add_share(group, sharepath, persist, *error) 1261 * 1262 * Add a new share object to the specified group. The share will 1263 * have the specified sharepath and will only be constructed if 1264 * it is a valid path to be shared. NULL is returned on error 1265 * and a detailed error value will be returned via the error 1266 * pointer. 1267 */ 1268 sa_share_t 1269 sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) 1270 { 1271 xmlNodePtr node = NULL; 1272 sa_share_t dup; 1273 int strictness = SA_CHECK_NORMAL; 1274 sa_handle_t handle; 1275 1276 /* 1277 * If the share is to be permanent, use strict checking so a 1278 * bad config doesn't get created. Transient shares only need 1279 * to check against the currently active 1280 * shares. SA_SHARE_PARSER is a modifier used internally to 1281 * indicate that we are being called by the dfstab parser and 1282 * that we need strict checking in all cases. Normally persist 1283 * is in integer value but SA_SHARE_PARSER may be or'd into 1284 * it as an override. 1285 */ 1286 if (persist & SA_SHARE_PARSER || persist == SA_SHARE_PERMANENT) 1287 strictness = SA_CHECK_STRICT; 1288 1289 handle = sa_find_group_handle(group); 1290 1291 if ((dup = sa_find_share(handle, sharepath)) == NULL && 1292 (*error = sa_check_path(group, sharepath, strictness)) == SA_OK) { 1293 node = _sa_add_share(group, sharepath, persist, error); 1294 } 1295 if (dup != NULL) 1296 *error = SA_DUPLICATE_NAME; 1297 1298 return ((sa_share_t)node); 1299 } 1300 1301 /* 1302 * sa_enable_share(share, protocol) 1303 * Enable the specified share to the specified protocol. 1304 * If protocol is NULL, then all protocols. 1305 */ 1306 int 1307 sa_enable_share(sa_share_t share, char *protocol) 1308 { 1309 char *sharepath; 1310 struct stat st; 1311 int err = 0; 1312 1313 sharepath = sa_get_share_attr(share, "path"); 1314 if (stat(sharepath, &st) < 0) { 1315 err = SA_NO_SUCH_PATH; 1316 } else { 1317 /* tell the server about the share */ 1318 if (protocol != NULL) { 1319 /* lookup protocol specific handler */ 1320 err = sa_proto_share(protocol, share); 1321 if (err == SA_OK) 1322 (void) sa_set_share_attr(share, "shared", 1323 "true"); 1324 } else { 1325 /* 1326 * Tell all protocols. Only NFS for now but 1327 * SMB is coming. 1328 */ 1329 err = sa_proto_share("nfs", share); 1330 (void) sa_set_share_attr(share, "shared", "true"); 1331 } 1332 } 1333 if (sharepath != NULL) 1334 sa_free_attr_string(sharepath); 1335 return (err); 1336 } 1337 1338 /* 1339 * sa_disable_share(share, protocol) 1340 * Disable the specified share to the specified protocol. 1341 * If protocol is NULL, then all protocols. 1342 */ 1343 int 1344 sa_disable_share(sa_share_t share, char *protocol) 1345 { 1346 char *path; 1347 char *shared; 1348 int ret = SA_OK; 1349 1350 path = sa_get_share_attr(share, "path"); 1351 shared = sa_get_share_attr(share, "shared"); 1352 1353 if (protocol != NULL) { 1354 ret = sa_proto_unshare(protocol, path); 1355 } else { 1356 /* need to do all protocols */ 1357 ret = sa_proto_unshare("nfs", path); 1358 } 1359 if (ret == SA_OK) 1360 (void) sa_set_share_attr(share, "shared", NULL); 1361 if (path != NULL) 1362 sa_free_attr_string(path); 1363 if (shared != NULL) 1364 sa_free_attr_string(shared); 1365 return (ret); 1366 } 1367 1368 /* 1369 * sa_remove_share(share) 1370 * 1371 * remove the specified share from its containing group. 1372 * Remove from the SMF or ZFS configuration space. 1373 */ 1374 1375 int 1376 sa_remove_share(sa_share_t share) 1377 { 1378 sa_group_t group; 1379 int ret = SA_OK; 1380 char *type; 1381 int transient = 0; 1382 char *groupname; 1383 char *zfs; 1384 1385 type = sa_get_share_attr(share, "type"); 1386 group = sa_get_parent_group(share); 1387 zfs = sa_get_group_attr(group, "zfs"); 1388 groupname = sa_get_group_attr(group, "name"); 1389 if (type != NULL && strcmp(type, "persist") != 0) 1390 transient = 1; 1391 if (type != NULL) 1392 sa_free_attr_string(type); 1393 1394 /* remove the node from its group then free the memory */ 1395 1396 /* 1397 * need to test if "busy" 1398 */ 1399 /* only do SMF action if permanent */ 1400 if (!transient || zfs != NULL) { 1401 /* remove from legacy dfstab as well as possible SMF */ 1402 ret = sa_delete_legacy(share); 1403 if (ret == SA_OK) { 1404 if (!sa_group_is_zfs(group)) { 1405 sa_handle_impl_t impl_handle; 1406 impl_handle = (sa_handle_impl_t) 1407 sa_find_group_handle(group); 1408 if (impl_handle != NULL) { 1409 ret = sa_delete_share( 1410 impl_handle->scfhandle, group, 1411 share); 1412 } else { 1413 ret = SA_SYSTEM_ERR; 1414 } 1415 } else { 1416 char *sharepath = sa_get_share_attr(share, 1417 "path"); 1418 if (sharepath != NULL) { 1419 ret = sa_zfs_set_sharenfs(group, 1420 sharepath, 0); 1421 sa_free_attr_string(sharepath); 1422 } 1423 } 1424 } 1425 } 1426 if (groupname != NULL) 1427 sa_free_attr_string(groupname); 1428 if (zfs != NULL) 1429 sa_free_attr_string(zfs); 1430 1431 xmlUnlinkNode((xmlNodePtr)share); 1432 xmlFreeNode((xmlNodePtr)share); 1433 return (ret); 1434 } 1435 1436 /* 1437 * sa_move_share(group, share) 1438 * 1439 * move the specified share to the specified group. Update SMF 1440 * appropriately. 1441 */ 1442 1443 int 1444 sa_move_share(sa_group_t group, sa_share_t share) 1445 { 1446 sa_group_t oldgroup; 1447 int ret = SA_OK; 1448 1449 /* remove the node from its group then free the memory */ 1450 1451 oldgroup = sa_get_parent_group(share); 1452 if (oldgroup != group) { 1453 sa_handle_impl_t impl_handle; 1454 xmlUnlinkNode((xmlNodePtr)share); 1455 /* 1456 * now that the share isn't in its old group, add to 1457 * the new one 1458 */ 1459 xmlAddChild((xmlNodePtr)group, (xmlNodePtr)share); 1460 /* need to deal with SMF */ 1461 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 1462 if (impl_handle != NULL) { 1463 /* 1464 * need to remove from old group first and then add to 1465 * new group. Ideally, we would do the other order but 1466 * need to avoid having the share in two groups at the 1467 * same time. 1468 */ 1469 ret = sa_delete_share(impl_handle->scfhandle, oldgroup, 1470 share); 1471 if (ret == SA_OK) 1472 ret = sa_commit_share(impl_handle->scfhandle, 1473 group, share); 1474 } else { 1475 ret = SA_SYSTEM_ERR; 1476 } 1477 } 1478 return (ret); 1479 } 1480 1481 /* 1482 * sa_get_parent_group(share) 1483 * 1484 * Return the containg group for the share. If a group was actually 1485 * passed in, we don't want a parent so return NULL. 1486 */ 1487 1488 sa_group_t 1489 sa_get_parent_group(sa_share_t share) 1490 { 1491 xmlNodePtr node = NULL; 1492 if (share != NULL) { 1493 node = ((xmlNodePtr)share)->parent; 1494 /* 1495 * make sure parent is a group and not sharecfg since 1496 * we may be cheating and passing in a group. 1497 * Eventually, groups of groups might come into being. 1498 */ 1499 if (node == NULL || 1500 xmlStrcmp(node->name, (xmlChar *)"sharecfg") == 0) 1501 node = NULL; 1502 } 1503 return ((sa_group_t)node); 1504 } 1505 1506 /* 1507 * _sa_create_group(impl_handle, groupname) 1508 * 1509 * Create a group in the document. The caller will need to deal with 1510 * configuration store and activation. 1511 */ 1512 1513 sa_group_t 1514 _sa_create_group(sa_handle_impl_t impl_handle, char *groupname) 1515 { 1516 xmlNodePtr node = NULL; 1517 1518 if (sa_valid_group_name(groupname)) { 1519 node = xmlNewChild(impl_handle->tree, NULL, (xmlChar *)"group", 1520 NULL); 1521 if (node != NULL) { 1522 xmlSetProp(node, (xmlChar *)"name", 1523 (xmlChar *)groupname); 1524 xmlSetProp(node, (xmlChar *)"state", 1525 (xmlChar *)"enabled"); 1526 } 1527 } 1528 return ((sa_group_t)node); 1529 } 1530 1531 /* 1532 * _sa_create_zfs_group(group, groupname) 1533 * 1534 * Create a ZFS subgroup under the specified group. This may 1535 * eventually form the basis of general sub-groups, but is currently 1536 * restricted to ZFS. 1537 */ 1538 sa_group_t 1539 _sa_create_zfs_group(sa_group_t group, char *groupname) 1540 { 1541 xmlNodePtr node = NULL; 1542 1543 node = xmlNewChild((xmlNodePtr)group, NULL, (xmlChar *)"group", NULL); 1544 if (node != NULL) { 1545 xmlSetProp(node, (xmlChar *)"name", (xmlChar *)groupname); 1546 xmlSetProp(node, (xmlChar *)"state", (xmlChar *)"enabled"); 1547 } 1548 1549 return ((sa_group_t)node); 1550 } 1551 1552 /* 1553 * sa_create_group(groupname, *error) 1554 * 1555 * Create a new group with groupname. Need to validate that it is a 1556 * legal name for SMF and the construct the SMF service instance of 1557 * svc:/network/shares/group to implement the group. All necessary 1558 * operational properties must be added to the group at this point 1559 * (via the SMF transaction model). 1560 */ 1561 sa_group_t 1562 sa_create_group(sa_handle_t handle, char *groupname, int *error) 1563 { 1564 xmlNodePtr node = NULL; 1565 sa_group_t group; 1566 int ret; 1567 char rbacstr[SA_STRSIZE]; 1568 sa_handle_impl_t impl_handle = (sa_handle_impl_t)handle; 1569 1570 ret = SA_OK; 1571 1572 if (impl_handle == NULL || impl_handle->scfhandle == NULL) { 1573 ret = SA_SYSTEM_ERR; 1574 goto err; 1575 } 1576 1577 group = sa_get_group(handle, groupname); 1578 if (group != NULL) { 1579 ret = SA_DUPLICATE_NAME; 1580 } else { 1581 if (sa_valid_group_name(groupname)) { 1582 node = xmlNewChild(impl_handle->tree, NULL, 1583 (xmlChar *)"group", NULL); 1584 if (node != NULL) { 1585 xmlSetProp(node, (xmlChar *)"name", 1586 (xmlChar *)groupname); 1587 /* default to the group being enabled */ 1588 xmlSetProp(node, (xmlChar *)"state", 1589 (xmlChar *)"enabled"); 1590 ret = sa_create_instance(impl_handle->scfhandle, 1591 groupname); 1592 if (ret == SA_OK) { 1593 ret = sa_start_transaction( 1594 impl_handle->scfhandle, 1595 "operation"); 1596 } 1597 if (ret == SA_OK) { 1598 ret = sa_set_property( 1599 impl_handle->scfhandle, 1600 "state", "enabled"); 1601 if (ret == SA_OK) { 1602 ret = sa_end_transaction( 1603 impl_handle->scfhandle); 1604 } else { 1605 sa_abort_transaction( 1606 impl_handle->scfhandle); 1607 } 1608 } 1609 if (ret == SA_OK) { 1610 /* initialize the RBAC strings */ 1611 ret = sa_start_transaction( 1612 impl_handle->scfhandle, 1613 "general"); 1614 if (ret == SA_OK) { 1615 (void) snprintf(rbacstr, 1616 sizeof (rbacstr), "%s.%s", 1617 SA_RBAC_MANAGE, groupname); 1618 ret = sa_set_property( 1619 impl_handle->scfhandle, 1620 "action_authorization", 1621 rbacstr); 1622 } 1623 if (ret == SA_OK) { 1624 (void) snprintf(rbacstr, 1625 sizeof (rbacstr), "%s.%s", 1626 SA_RBAC_VALUE, groupname); 1627 ret = sa_set_property( 1628 impl_handle->scfhandle, 1629 "value_authorization", 1630 rbacstr); 1631 } 1632 if (ret == SA_OK) { 1633 ret = sa_end_transaction( 1634 impl_handle->scfhandle); 1635 } else { 1636 sa_abort_transaction( 1637 impl_handle->scfhandle); 1638 } 1639 } 1640 if (ret != SA_OK) { 1641 /* 1642 * Couldn't commit the group 1643 * so we need to undo 1644 * internally. 1645 */ 1646 xmlUnlinkNode(node); 1647 xmlFreeNode(node); 1648 node = NULL; 1649 } 1650 } else { 1651 ret = SA_NO_MEMORY; 1652 } 1653 } else { 1654 ret = SA_INVALID_NAME; 1655 } 1656 } 1657 err: 1658 if (error != NULL) 1659 *error = ret; 1660 return ((sa_group_t)node); 1661 } 1662 1663 /* 1664 * sa_remove_group(group) 1665 * 1666 * Remove the specified group. This deletes from the SMF repository. 1667 * All property groups and properties are removed. 1668 */ 1669 1670 int 1671 sa_remove_group(sa_group_t group) 1672 { 1673 char *name; 1674 int ret = SA_OK; 1675 sa_handle_impl_t impl_handle; 1676 1677 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 1678 if (impl_handle != NULL) { 1679 name = sa_get_group_attr(group, "name"); 1680 if (name != NULL) { 1681 ret = sa_delete_instance(impl_handle->scfhandle, name); 1682 sa_free_attr_string(name); 1683 } 1684 xmlUnlinkNode((xmlNodePtr)group); /* make sure unlinked */ 1685 xmlFreeNode((xmlNodePtr)group); /* now it is gone */ 1686 } else { 1687 ret = SA_SYSTEM_ERR; 1688 } 1689 return (ret); 1690 } 1691 1692 /* 1693 * sa_update_config() 1694 * 1695 * Used to update legacy files that need to be updated in bulk 1696 * Currently, this is a placeholder and will go away in a future 1697 * release. 1698 */ 1699 1700 int 1701 sa_update_config(sa_handle_t handle) 1702 { 1703 /* 1704 * do legacy files first so we can tell when they change. 1705 * This will go away when we start updating individual records 1706 * rather than the whole file. 1707 */ 1708 update_legacy_config(handle); 1709 return (SA_OK); 1710 } 1711 1712 /* 1713 * get_node_attr(node, tag) 1714 * 1715 * Get the speficied tag(attribute) if it exists on the node. This is 1716 * used internally by a number of attribute oriented functions. 1717 */ 1718 1719 static char * 1720 get_node_attr(void *nodehdl, char *tag) 1721 { 1722 xmlNodePtr node = (xmlNodePtr)nodehdl; 1723 xmlChar *name = NULL; 1724 1725 if (node != NULL) 1726 name = xmlGetProp(node, (xmlChar *)tag); 1727 return ((char *)name); 1728 } 1729 1730 /* 1731 * get_node_attr(node, tag) 1732 * 1733 * Set the speficied tag(attribute) to the specified value This is 1734 * used internally by a number of attribute oriented functions. It 1735 * doesn't update the repository, only the internal document state. 1736 */ 1737 1738 void 1739 set_node_attr(void *nodehdl, char *tag, char *value) 1740 { 1741 xmlNodePtr node = (xmlNodePtr)nodehdl; 1742 if (node != NULL && tag != NULL) { 1743 if (value != NULL) 1744 xmlSetProp(node, (xmlChar *)tag, (xmlChar *)value); 1745 else 1746 xmlUnsetProp(node, (xmlChar *)tag); 1747 } 1748 } 1749 1750 /* 1751 * sa_get_group_attr(group, tag) 1752 * 1753 * Get the specied attribute, if defined, for the group. 1754 */ 1755 1756 char * 1757 sa_get_group_attr(sa_group_t group, char *tag) 1758 { 1759 return (get_node_attr((void *)group, tag)); 1760 } 1761 1762 /* 1763 * sa_set_group_attr(group, tag, value) 1764 * 1765 * set the specified tag/attribute on the group using value as its 1766 * value. 1767 * 1768 * This will result in setting the property in the SMF repository as 1769 * well as in the internal document. 1770 */ 1771 1772 int 1773 sa_set_group_attr(sa_group_t group, char *tag, char *value) 1774 { 1775 int ret; 1776 char *groupname; 1777 sa_handle_impl_t impl_handle; 1778 1779 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 1780 if (impl_handle != NULL) { 1781 groupname = sa_get_group_attr(group, "name"); 1782 ret = sa_get_instance(impl_handle->scfhandle, groupname); 1783 if (ret == SA_OK) { 1784 set_node_attr((void *)group, tag, value); 1785 ret = sa_start_transaction(impl_handle->scfhandle, 1786 "operation"); 1787 if (ret == SA_OK) { 1788 ret = sa_set_property(impl_handle->scfhandle, 1789 tag, value); 1790 if (ret == SA_OK) 1791 (void) sa_end_transaction( 1792 impl_handle->scfhandle); 1793 else 1794 sa_abort_transaction( 1795 impl_handle->scfhandle); 1796 } 1797 } 1798 if (groupname != NULL) 1799 sa_free_attr_string(groupname); 1800 } else { 1801 ret = SA_SYSTEM_ERR; 1802 } 1803 return (ret); 1804 } 1805 1806 /* 1807 * sa_get_share_attr(share, tag) 1808 * 1809 * Return the value of the tag/attribute set on the specified 1810 * share. Returns NULL if the tag doesn't exist. 1811 */ 1812 1813 char * 1814 sa_get_share_attr(sa_share_t share, char *tag) 1815 { 1816 return (get_node_attr((void *)share, tag)); 1817 } 1818 1819 /* 1820 * sa_get_resource(group, resource) 1821 * 1822 * Search all the shares in the speified group for a share with a 1823 * resource name matching the one specified. 1824 * 1825 * In the future, it may be advantageous to allow group to be NULL and 1826 * search all groups but that isn't needed at present. 1827 */ 1828 1829 sa_share_t 1830 sa_get_resource(sa_group_t group, char *resource) 1831 { 1832 sa_share_t share = NULL; 1833 char *name = NULL; 1834 1835 if (resource != NULL) { 1836 for (share = sa_get_share(group, NULL); share != NULL; 1837 share = sa_get_next_share(share)) { 1838 name = sa_get_share_attr(share, "resource"); 1839 if (name != NULL) { 1840 if (strcmp(name, resource) == 0) 1841 break; 1842 sa_free_attr_string(name); 1843 name = NULL; 1844 } 1845 } 1846 if (name != NULL) 1847 sa_free_attr_string(name); 1848 } 1849 return ((sa_share_t)share); 1850 } 1851 1852 /* 1853 * _sa_set_share_description(share, description) 1854 * 1855 * Add a description tag with text contents to the specified share. 1856 * A separate XML tag is used rather than a property. 1857 */ 1858 1859 xmlNodePtr 1860 _sa_set_share_description(sa_share_t share, char *content) 1861 { 1862 xmlNodePtr node; 1863 node = xmlNewChild((xmlNodePtr)share, NULL, (xmlChar *)"description", 1864 NULL); 1865 xmlNodeSetContent(node, (xmlChar *)content); 1866 return (node); 1867 } 1868 1869 /* 1870 * sa_set_share_attr(share, tag, value) 1871 * 1872 * Set the share attribute specified by tag to the specified value. In 1873 * the case of "resource", enforce a no duplicates in a group rule. If 1874 * the share is not transient, commit the changes to the repository 1875 * else just update the share internally. 1876 */ 1877 1878 int 1879 sa_set_share_attr(sa_share_t share, char *tag, char *value) 1880 { 1881 sa_group_t group; 1882 sa_share_t resource; 1883 int ret = SA_OK; 1884 1885 group = sa_get_parent_group(share); 1886 1887 /* 1888 * There are some attributes that may have specific 1889 * restrictions on them. Initially, only "resource" has 1890 * special meaning that needs to be checked. Only one instance 1891 * of a resource name may exist within a group. 1892 */ 1893 1894 if (strcmp(tag, "resource") == 0) { 1895 resource = sa_get_resource(group, value); 1896 if (resource != share && resource != NULL) 1897 ret = SA_DUPLICATE_NAME; 1898 } 1899 if (ret == SA_OK) { 1900 set_node_attr((void *)share, tag, value); 1901 if (group != NULL) { 1902 char *type; 1903 /* we can probably optimize this some */ 1904 type = sa_get_share_attr(share, "type"); 1905 if (type == NULL || strcmp(type, "transient") != 0) { 1906 sa_handle_impl_t impl_handle; 1907 impl_handle = 1908 (sa_handle_impl_t)sa_find_group_handle( 1909 group); 1910 if (impl_handle != NULL) { 1911 ret = sa_commit_share( 1912 impl_handle->scfhandle, group, 1913 share); 1914 } else { 1915 ret = SA_SYSTEM_ERR; 1916 } 1917 } 1918 if (type != NULL) 1919 sa_free_attr_string(type); 1920 } 1921 } 1922 return (ret); 1923 } 1924 1925 /* 1926 * sa_get_property_attr(prop, tag) 1927 * 1928 * Get the value of the specified property attribute. Standard 1929 * attributes are "type" and "value". 1930 */ 1931 1932 char * 1933 sa_get_property_attr(sa_property_t prop, char *tag) 1934 { 1935 return (get_node_attr((void *)prop, tag)); 1936 } 1937 1938 /* 1939 * sa_get_optionset_attr(prop, tag) 1940 * 1941 * Get the value of the specified property attribute. Standard 1942 * attribute is "type". 1943 */ 1944 1945 char * 1946 sa_get_optionset_attr(sa_property_t optionset, char *tag) 1947 { 1948 return (get_node_attr((void *)optionset, tag)); 1949 1950 } 1951 1952 /* 1953 * sa_set_optionset_attr(optionset, tag, value) 1954 * 1955 * Set the specified attribute(tag) to the specified value on the 1956 * optionset. 1957 */ 1958 1959 void 1960 sa_set_optionset_attr(sa_group_t optionset, char *tag, char *value) 1961 { 1962 set_node_attr((void *)optionset, tag, value); 1963 } 1964 1965 /* 1966 * sa_free_attr_string(string) 1967 * 1968 * Free the string that was returned in one of the sa_get_*_attr() 1969 * functions. 1970 */ 1971 1972 void 1973 sa_free_attr_string(char *string) 1974 { 1975 xmlFree((xmlChar *)string); 1976 } 1977 1978 /* 1979 * sa_get_optionset(group, proto) 1980 * 1981 * Return the optionset, if it exists, that is associated with the 1982 * specified protocol. 1983 */ 1984 1985 sa_optionset_t 1986 sa_get_optionset(void *group, char *proto) 1987 { 1988 xmlNodePtr node; 1989 xmlChar *value = NULL; 1990 1991 for (node = ((xmlNodePtr)group)->children; node != NULL; 1992 node = node->next) { 1993 if (xmlStrcmp(node->name, (xmlChar *)"optionset") == 0) { 1994 value = xmlGetProp(node, (xmlChar *)"type"); 1995 if (proto != NULL) { 1996 if (value != NULL && 1997 xmlStrcmp(value, (xmlChar *)proto) == 0) { 1998 break; 1999 } 2000 if (value != NULL) { 2001 xmlFree(value); 2002 value = NULL; 2003 } 2004 } else { 2005 break; 2006 } 2007 } 2008 } 2009 if (value != NULL) 2010 xmlFree(value); 2011 return ((sa_optionset_t)node); 2012 } 2013 2014 /* 2015 * sa_get_next_optionset(optionset) 2016 * 2017 * Return the next optionset in the group. NULL if this was the last. 2018 */ 2019 2020 sa_optionset_t 2021 sa_get_next_optionset(sa_optionset_t optionset) 2022 { 2023 xmlNodePtr node; 2024 2025 for (node = ((xmlNodePtr)optionset)->next; node != NULL; 2026 node = node->next) { 2027 if (xmlStrcmp(node->name, (xmlChar *)"optionset") == 0) { 2028 break; 2029 } 2030 } 2031 return ((sa_optionset_t)node); 2032 } 2033 2034 /* 2035 * sa_get_security(group, sectype, proto) 2036 * 2037 * Return the security optionset. The internal name is a hold over 2038 * from the implementation and will be changed before the API is 2039 * finalized. This is really a named optionset that can be negotiated 2040 * as a group of properties (like NFS security options). 2041 */ 2042 2043 sa_security_t 2044 sa_get_security(sa_group_t group, char *sectype, char *proto) 2045 { 2046 xmlNodePtr node; 2047 xmlChar *value = NULL; 2048 2049 for (node = ((xmlNodePtr)group)->children; node != NULL; 2050 node = node->next) { 2051 if (xmlStrcmp(node->name, (xmlChar *)"security") == 0) { 2052 if (proto != NULL) { 2053 value = xmlGetProp(node, (xmlChar *)"type"); 2054 if (value == NULL || 2055 (value != NULL && 2056 xmlStrcmp(value, (xmlChar *)proto) != 0)) { 2057 /* it doesn't match so continue */ 2058 xmlFree(value); 2059 value = NULL; 2060 continue; 2061 } 2062 } 2063 if (value != NULL) { 2064 xmlFree(value); 2065 value = NULL; 2066 } 2067 /* potential match */ 2068 if (sectype != NULL) { 2069 value = xmlGetProp(node, (xmlChar *)"sectype"); 2070 if (value != NULL && 2071 xmlStrcmp(value, (xmlChar *)sectype) == 0) { 2072 break; 2073 } 2074 } else { 2075 break; 2076 } 2077 } 2078 if (value != NULL) { 2079 xmlFree(value); 2080 value = NULL; 2081 } 2082 } 2083 if (value != NULL) 2084 xmlFree(value); 2085 return ((sa_security_t)node); 2086 } 2087 2088 /* 2089 * sa_get_next_security(security) 2090 * 2091 * Get the next security optionset if one exists. 2092 */ 2093 2094 sa_security_t 2095 sa_get_next_security(sa_security_t security) 2096 { 2097 xmlNodePtr node; 2098 2099 for (node = ((xmlNodePtr)security)->next; node != NULL; 2100 node = node->next) { 2101 if (xmlStrcmp(node->name, (xmlChar *)"security") == 0) { 2102 break; 2103 } 2104 } 2105 return ((sa_security_t)node); 2106 } 2107 2108 /* 2109 * sa_get_property(optionset, prop) 2110 * 2111 * Get the property object with the name specified in prop from the 2112 * optionset. 2113 */ 2114 2115 sa_property_t 2116 sa_get_property(sa_optionset_t optionset, char *prop) 2117 { 2118 xmlNodePtr node = (xmlNodePtr)optionset; 2119 xmlChar *value = NULL; 2120 2121 if (optionset == NULL) 2122 return (NULL); 2123 2124 for (node = node->children; node != NULL; 2125 node = node->next) { 2126 if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { 2127 if (prop == NULL) 2128 break; 2129 value = xmlGetProp(node, (xmlChar *)"type"); 2130 if (value != NULL && 2131 xmlStrcmp(value, (xmlChar *)prop) == 0) { 2132 break; 2133 } 2134 if (value != NULL) { 2135 xmlFree(value); 2136 value = NULL; 2137 } 2138 } 2139 } 2140 if (value != NULL) 2141 xmlFree(value); 2142 if (node != NULL && xmlStrcmp(node->name, (xmlChar *)"option") != 0) { 2143 /* 2144 * avoid a non option node -- it is possible to be a 2145 * text node 2146 */ 2147 node = NULL; 2148 } 2149 return ((sa_property_t)node); 2150 } 2151 2152 /* 2153 * sa_get_next_property(property) 2154 * 2155 * Get the next property following the specified property. NULL if 2156 * this was the last. 2157 */ 2158 2159 sa_property_t 2160 sa_get_next_property(sa_property_t property) 2161 { 2162 xmlNodePtr node; 2163 2164 for (node = ((xmlNodePtr)property)->next; node != NULL; 2165 node = node->next) { 2166 if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { 2167 break; 2168 } 2169 } 2170 return ((sa_property_t)node); 2171 } 2172 2173 /* 2174 * sa_set_share_description(share, content) 2175 * 2176 * Set the description of share to content. 2177 */ 2178 2179 int 2180 sa_set_share_description(sa_share_t share, char *content) 2181 { 2182 xmlNodePtr node; 2183 sa_group_t group; 2184 int ret = SA_OK; 2185 2186 for (node = ((xmlNodePtr)share)->children; node != NULL; 2187 node = node->next) { 2188 if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) { 2189 break; 2190 } 2191 } 2192 group = sa_get_parent_group(share); 2193 /* no existing description but want to add */ 2194 if (node == NULL && content != NULL) { 2195 /* add a description */ 2196 node = _sa_set_share_description(share, content); 2197 } else if (node != NULL && content != NULL) { 2198 /* update a description */ 2199 xmlNodeSetContent(node, (xmlChar *)content); 2200 } else if (node != NULL && content == NULL) { 2201 /* remove an existing description */ 2202 xmlUnlinkNode(node); 2203 xmlFreeNode(node); 2204 } 2205 if (group != NULL && is_persistent((sa_group_t)share)) { 2206 sa_handle_impl_t impl_handle; 2207 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 2208 if (impl_handle != NULL) { 2209 ret = sa_commit_share(impl_handle->scfhandle, group, 2210 share); 2211 } else { 2212 ret = SA_SYSTEM_ERR; 2213 } 2214 } 2215 return (ret); 2216 } 2217 2218 /* 2219 * fixproblemchars(string) 2220 * 2221 * don't want any newline or tab characters in the text since these 2222 * could break display of data and legacy file formats. 2223 */ 2224 static void 2225 fixproblemchars(char *str) 2226 { 2227 int c; 2228 for (c = *str; c != '\0'; c = *++str) { 2229 if (c == '\t' || c == '\n') 2230 *str = ' '; 2231 else if (c == '"') 2232 *str = '\''; 2233 } 2234 } 2235 2236 /* 2237 * sa_get_share_description(share) 2238 * 2239 * Return the description text for the specified share if it 2240 * exists. NULL if no description exists. 2241 */ 2242 2243 char * 2244 sa_get_share_description(sa_share_t share) 2245 { 2246 xmlChar *description = NULL; 2247 xmlNodePtr node; 2248 2249 for (node = ((xmlNodePtr)share)->children; node != NULL; 2250 node = node->next) { 2251 if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) { 2252 break; 2253 } 2254 } 2255 if (node != NULL) { 2256 description = xmlNodeGetContent((xmlNodePtr)share); 2257 fixproblemchars((char *)description); 2258 } 2259 return ((char *)description); 2260 } 2261 2262 /* 2263 * sa_free(share_description(description) 2264 * 2265 * Free the description string. 2266 */ 2267 2268 void 2269 sa_free_share_description(char *description) 2270 { 2271 xmlFree((xmlChar *)description); 2272 } 2273 2274 /* 2275 * sa_create_optionset(group, proto) 2276 * 2277 * Create an optionset for the specified protocol in the specied 2278 * group. This is manifested as a property group within SMF. 2279 */ 2280 2281 sa_optionset_t 2282 sa_create_optionset(sa_group_t group, char *proto) 2283 { 2284 sa_optionset_t optionset; 2285 sa_group_t parent = group; 2286 2287 optionset = sa_get_optionset(group, proto); 2288 if (optionset != NULL) { 2289 /* can't have a duplicate protocol */ 2290 optionset = NULL; 2291 } else { 2292 optionset = (sa_optionset_t)xmlNewChild((xmlNodePtr)group, 2293 NULL, (xmlChar *)"optionset", NULL); 2294 /* 2295 * only put to repository if on a group and we were 2296 * able to create an optionset. 2297 */ 2298 if (optionset != NULL) { 2299 char oname[SA_STRSIZE]; 2300 char *groupname; 2301 char *id = NULL; 2302 2303 if (sa_is_share(group)) 2304 parent = sa_get_parent_group((sa_share_t)group); 2305 2306 sa_set_optionset_attr(optionset, "type", proto); 2307 2308 if (sa_is_share(group)) { 2309 id = sa_get_share_attr((sa_share_t)group, "id"); 2310 } 2311 (void) sa_optionset_name(optionset, oname, 2312 sizeof (oname), id); 2313 groupname = sa_get_group_attr(parent, "name"); 2314 if (groupname != NULL && is_persistent(group)) { 2315 sa_handle_impl_t impl_handle; 2316 impl_handle = (sa_handle_impl_t) 2317 sa_find_group_handle(group); 2318 assert(impl_handle != NULL); 2319 if (impl_handle != NULL) { 2320 (void) sa_get_instance( 2321 impl_handle->scfhandle, 2322 groupname); 2323 (void) sa_create_pgroup( 2324 impl_handle->scfhandle, oname); 2325 } 2326 } 2327 if (groupname != NULL) 2328 sa_free_attr_string(groupname); 2329 if (id != NULL) 2330 sa_free_attr_string(id); 2331 } 2332 } 2333 return (optionset); 2334 } 2335 2336 /* 2337 * sa_get_property_parent(property) 2338 * 2339 * Given a property, return the object it is a property of. This will 2340 * be an optionset of some type. 2341 */ 2342 2343 static sa_optionset_t 2344 sa_get_property_parent(sa_property_t property) 2345 { 2346 xmlNodePtr node = NULL; 2347 2348 if (property != NULL) 2349 node = ((xmlNodePtr)property)->parent; 2350 return ((sa_optionset_t)node); 2351 } 2352 2353 /* 2354 * sa_get_optionset_parent(optionset) 2355 * 2356 * Return the parent of the specified optionset. This could be a group 2357 * or a share. 2358 */ 2359 2360 static sa_group_t 2361 sa_get_optionset_parent(sa_optionset_t optionset) 2362 { 2363 xmlNodePtr node = NULL; 2364 2365 if (optionset != NULL) 2366 node = ((xmlNodePtr)optionset)->parent; 2367 return ((sa_group_t)node); 2368 } 2369 2370 /* 2371 * zfs_needs_update(share) 2372 * 2373 * In order to avoid making multiple updates to a ZFS share when 2374 * setting properties, the share attribute "changed" will be set to 2375 * true when a property is added or modifed. When done adding 2376 * properties, we can then detect that an update is needed. We then 2377 * clear the state here to detect additional changes. 2378 */ 2379 2380 static int 2381 zfs_needs_update(sa_share_t share) 2382 { 2383 char *attr; 2384 int result = 0; 2385 2386 attr = sa_get_share_attr(share, "changed"); 2387 if (attr != NULL) { 2388 sa_free_attr_string(attr); 2389 result = 1; 2390 } 2391 set_node_attr((void *)share, "changed", NULL); 2392 return (result); 2393 } 2394 2395 /* 2396 * zfs_set_update(share) 2397 * 2398 * Set the changed attribute of the share to true. 2399 */ 2400 2401 static void 2402 zfs_set_update(sa_share_t share) 2403 { 2404 set_node_attr((void *)share, "changed", "true"); 2405 } 2406 2407 /* 2408 * sa_commit_properties(optionset, clear) 2409 * 2410 * Check if SMF or ZFS config and either update or abort the pending 2411 * changes. 2412 */ 2413 2414 int 2415 sa_commit_properties(sa_optionset_t optionset, int clear) 2416 { 2417 sa_group_t group; 2418 sa_group_t parent; 2419 int zfs = 0; 2420 int needsupdate = 0; 2421 int ret = SA_OK; 2422 sa_handle_impl_t impl_handle; 2423 2424 group = sa_get_optionset_parent(optionset); 2425 if (group != NULL && (sa_is_share(group) || is_zfs_group(group))) { 2426 /* only update ZFS if on a share */ 2427 parent = sa_get_parent_group(group); 2428 zfs++; 2429 if (parent != NULL && is_zfs_group(parent)) 2430 needsupdate = zfs_needs_update(group); 2431 else 2432 zfs = 0; 2433 } 2434 if (zfs) { 2435 if (!clear && needsupdate) 2436 ret = sa_zfs_update((sa_share_t)group); 2437 } else { 2438 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 2439 if (impl_handle != NULL) { 2440 if (clear) { 2441 (void) sa_abort_transaction( 2442 impl_handle->scfhandle); 2443 } else { 2444 ret = sa_end_transaction( 2445 impl_handle->scfhandle); 2446 } 2447 } else { 2448 ret = SA_SYSTEM_ERR; 2449 } 2450 } 2451 return (ret); 2452 } 2453 2454 /* 2455 * sa_destroy_optionset(optionset) 2456 * 2457 * Remove the optionset from its group. Update the repostory to 2458 * reflect this change. 2459 */ 2460 2461 int 2462 sa_destroy_optionset(sa_optionset_t optionset) 2463 { 2464 char name[SA_STRSIZE]; 2465 int len; 2466 int ret; 2467 char *id = NULL; 2468 sa_group_t group; 2469 int ispersist = 1; 2470 2471 /* now delete the prop group */ 2472 group = sa_get_optionset_parent(optionset); 2473 if (group != NULL && sa_is_share(group)) { 2474 ispersist = is_persistent(group); 2475 id = sa_get_share_attr((sa_share_t)group, "id"); 2476 } 2477 if (ispersist) { 2478 sa_handle_impl_t impl_handle; 2479 len = sa_optionset_name(optionset, name, sizeof (name), id); 2480 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 2481 if (impl_handle != NULL) { 2482 if (len > 0) { 2483 ret = sa_delete_pgroup(impl_handle->scfhandle, 2484 name); 2485 } 2486 } else { 2487 ret = SA_SYSTEM_ERR; 2488 } 2489 } 2490 xmlUnlinkNode((xmlNodePtr)optionset); 2491 xmlFreeNode((xmlNodePtr)optionset); 2492 if (id != NULL) 2493 sa_free_attr_string(id); 2494 return (ret); 2495 } 2496 2497 /* private to the implementation */ 2498 int 2499 _sa_remove_optionset(sa_optionset_t optionset) 2500 { 2501 int ret = SA_OK; 2502 2503 xmlUnlinkNode((xmlNodePtr)optionset); 2504 xmlFreeNode((xmlNodePtr)optionset); 2505 return (ret); 2506 } 2507 2508 /* 2509 * sa_create_security(group, sectype, proto) 2510 * 2511 * Create a security optionset (one that has a type name and a 2512 * proto). Security is left over from a pure NFS implementation. The 2513 * naming will change in the future when the API is released. 2514 */ 2515 sa_security_t 2516 sa_create_security(sa_group_t group, char *sectype, char *proto) 2517 { 2518 sa_security_t security; 2519 char *id = NULL; 2520 sa_group_t parent; 2521 char *groupname = NULL; 2522 2523 if (group != NULL && sa_is_share(group)) { 2524 id = sa_get_share_attr((sa_share_t)group, "id"); 2525 parent = sa_get_parent_group(group); 2526 if (parent != NULL) 2527 groupname = sa_get_group_attr(parent, "name"); 2528 } else if (group != NULL) { 2529 groupname = sa_get_group_attr(group, "name"); 2530 } 2531 2532 security = sa_get_security(group, sectype, proto); 2533 if (security != NULL) { 2534 /* can't have a duplicate security option */ 2535 security = NULL; 2536 } else { 2537 security = (sa_security_t)xmlNewChild((xmlNodePtr)group, 2538 NULL, (xmlChar *)"security", NULL); 2539 if (security != NULL) { 2540 char oname[SA_STRSIZE]; 2541 sa_set_security_attr(security, "type", proto); 2542 2543 sa_set_security_attr(security, "sectype", sectype); 2544 (void) sa_security_name(security, oname, 2545 sizeof (oname), id); 2546 if (groupname != NULL && is_persistent(group)) { 2547 sa_handle_impl_t impl_handle; 2548 impl_handle = 2549 (sa_handle_impl_t)sa_find_group_handle( 2550 group); 2551 if (impl_handle != NULL) { 2552 (void) sa_get_instance( 2553 impl_handle->scfhandle, groupname); 2554 (void) sa_create_pgroup( 2555 impl_handle->scfhandle, oname); 2556 } 2557 } 2558 } 2559 } 2560 if (groupname != NULL) 2561 sa_free_attr_string(groupname); 2562 return (security); 2563 } 2564 2565 /* 2566 * sa_destroy_security(security) 2567 * 2568 * Remove the specified optionset from the document and the 2569 * configuration. 2570 */ 2571 2572 int 2573 sa_destroy_security(sa_security_t security) 2574 { 2575 char name[SA_STRSIZE]; 2576 int len; 2577 int ret = SA_OK; 2578 char *id = NULL; 2579 sa_group_t group; 2580 int iszfs = 0; 2581 int ispersist = 1; 2582 2583 group = sa_get_optionset_parent(security); 2584 2585 if (group != NULL) 2586 iszfs = sa_group_is_zfs(group); 2587 2588 if (group != NULL && !iszfs) { 2589 if (sa_is_share(group)) 2590 ispersist = is_persistent(group); 2591 id = sa_get_share_attr((sa_share_t)group, "id"); 2592 } 2593 if (ispersist) { 2594 len = sa_security_name(security, name, sizeof (name), id); 2595 if (!iszfs && len > 0) { 2596 sa_handle_impl_t impl_handle; 2597 impl_handle = 2598 (sa_handle_impl_t)sa_find_group_handle(group); 2599 if (impl_handle != NULL) { 2600 ret = sa_delete_pgroup(impl_handle->scfhandle, 2601 name); 2602 } else { 2603 ret = SA_SYSTEM_ERR; 2604 } 2605 } 2606 } 2607 xmlUnlinkNode((xmlNodePtr)security); 2608 xmlFreeNode((xmlNodePtr)security); 2609 if (iszfs) 2610 ret = sa_zfs_update(group); 2611 if (id != NULL) 2612 sa_free_attr_string(id); 2613 return (ret); 2614 } 2615 2616 /* 2617 * sa_get_security_attr(optionset, tag) 2618 * 2619 * Return the specified attribute value from the optionset. 2620 */ 2621 2622 char * 2623 sa_get_security_attr(sa_property_t optionset, char *tag) 2624 { 2625 return (get_node_attr((void *)optionset, tag)); 2626 2627 } 2628 2629 /* 2630 * sa_set_security_attr(optionset, tag, value) 2631 * 2632 * Set the optioset attribute specied by tag to the specified value. 2633 */ 2634 2635 void 2636 sa_set_security_attr(sa_group_t optionset, char *tag, char *value) 2637 { 2638 set_node_attr((void *)optionset, tag, value); 2639 } 2640 2641 /* 2642 * is_nodetype(node, type) 2643 * 2644 * Check to see if node is of the type specified. 2645 */ 2646 2647 static int 2648 is_nodetype(void *node, char *type) 2649 { 2650 return (strcmp((char *)((xmlNodePtr)node)->name, type) == 0); 2651 } 2652 2653 2654 /* 2655 * add_or_update() 2656 * 2657 * Add or update a property. Pulled out of sa_set_prop_by_prop for 2658 * readability. 2659 */ 2660 static int 2661 add_or_update(scfutilhandle_t *scf_handle, int type, scf_value_t *value, 2662 scf_transaction_entry_t *entry, char *name, char *valstr) 2663 { 2664 int ret = SA_SYSTEM_ERR; 2665 2666 if (value != NULL) { 2667 if (type == SA_PROP_OP_ADD) 2668 ret = scf_transaction_property_new(scf_handle->trans, 2669 entry, name, SCF_TYPE_ASTRING); 2670 else 2671 ret = scf_transaction_property_change(scf_handle->trans, 2672 entry, name, SCF_TYPE_ASTRING); 2673 if (ret == 0) { 2674 ret = scf_value_set_astring(value, valstr); 2675 if (ret == 0) 2676 ret = scf_entry_add_value(entry, value); 2677 if (ret == 0) 2678 return (ret); 2679 scf_value_destroy(value); 2680 } else { 2681 scf_entry_destroy(entry); 2682 } 2683 } 2684 return (SA_SYSTEM_ERR); 2685 } 2686 2687 /* 2688 * sa_set_prop_by_prop(optionset, group, prop, type) 2689 * 2690 * Add/remove/update the specified property prop into the optionset or 2691 * share. If a share, sort out which property group based on GUID. In 2692 * all cases, the appropriate transaction is set (or ZFS share is 2693 * marked as needing an update) 2694 */ 2695 2696 static int 2697 sa_set_prop_by_prop(sa_optionset_t optionset, sa_group_t group, 2698 sa_property_t prop, int type) 2699 { 2700 char *name; 2701 char *valstr; 2702 int ret = SA_OK; 2703 scf_transaction_entry_t *entry; 2704 scf_value_t *value; 2705 int opttype; /* 1 == optionset, 0 == security */ 2706 char *id = NULL; 2707 int iszfs = 0; 2708 int isshare = 0; 2709 sa_group_t parent = NULL; 2710 sa_handle_impl_t impl_handle; 2711 scfutilhandle_t *scf_handle; 2712 2713 if (!is_persistent(group)) { 2714 /* 2715 * if the group/share is not persistent we don't need 2716 * to do anything here 2717 */ 2718 return (SA_OK); 2719 } 2720 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 2721 if (impl_handle == NULL || impl_handle->scfhandle == NULL) 2722 return (SA_SYSTEM_ERR); 2723 scf_handle = impl_handle->scfhandle; 2724 name = sa_get_property_attr(prop, "type"); 2725 valstr = sa_get_property_attr(prop, "value"); 2726 entry = scf_entry_create(scf_handle->handle); 2727 opttype = is_nodetype((void *)optionset, "optionset"); 2728 2729 if (valstr != NULL && entry != NULL) { 2730 if (sa_is_share(group)) { 2731 isshare = 1; 2732 parent = sa_get_parent_group(group); 2733 if (parent != NULL) 2734 iszfs = is_zfs_group(parent); 2735 } else { 2736 iszfs = is_zfs_group(group); 2737 } 2738 if (!iszfs) { 2739 if (scf_handle->trans == NULL) { 2740 char oname[SA_STRSIZE]; 2741 char *groupname = NULL; 2742 if (isshare) { 2743 if (parent != NULL) { 2744 groupname = 2745 sa_get_group_attr(parent, 2746 "name"); 2747 } 2748 id = 2749 sa_get_share_attr((sa_share_t)group, 2750 "id"); 2751 } else { 2752 groupname = sa_get_group_attr(group, 2753 "name"); 2754 } 2755 if (groupname != NULL) { 2756 ret = sa_get_instance(scf_handle, 2757 groupname); 2758 sa_free_attr_string(groupname); 2759 } 2760 if (opttype) 2761 (void) sa_optionset_name(optionset, 2762 oname, sizeof (oname), id); 2763 else 2764 (void) sa_security_name(optionset, 2765 oname, sizeof (oname), id); 2766 ret = sa_start_transaction(scf_handle, oname); 2767 } 2768 if (ret == SA_OK) { 2769 switch (type) { 2770 case SA_PROP_OP_REMOVE: 2771 ret = scf_transaction_property_delete( 2772 scf_handle->trans, entry, name); 2773 break; 2774 case SA_PROP_OP_ADD: 2775 case SA_PROP_OP_UPDATE: 2776 value = scf_value_create( 2777 scf_handle->handle); 2778 ret = add_or_update(scf_handle, type, 2779 value, entry, name, valstr); 2780 break; 2781 } 2782 } 2783 } else { 2784 /* 2785 * ZFS update. The calling function would have updated 2786 * the internal XML structure. Just need to flag it as 2787 * changed for ZFS. 2788 */ 2789 zfs_set_update((sa_share_t)group); 2790 } 2791 } 2792 2793 if (name != NULL) 2794 sa_free_attr_string(name); 2795 if (valstr != NULL) 2796 sa_free_attr_string(valstr); 2797 else if (entry != NULL) 2798 scf_entry_destroy(entry); 2799 2800 if (ret == -1) 2801 ret = SA_SYSTEM_ERR; 2802 2803 return (ret); 2804 } 2805 2806 /* 2807 * sa_create_property(name, value) 2808 * 2809 * Create a new property with the specified name and value. 2810 */ 2811 2812 sa_property_t 2813 sa_create_property(char *name, char *value) 2814 { 2815 xmlNodePtr node; 2816 2817 node = xmlNewNode(NULL, (xmlChar *)"option"); 2818 if (node != NULL) { 2819 xmlSetProp(node, (xmlChar *)"type", (xmlChar *)name); 2820 xmlSetProp(node, (xmlChar *)"value", (xmlChar *)value); 2821 } 2822 return ((sa_property_t)node); 2823 } 2824 2825 /* 2826 * sa_add_property(object, property) 2827 * 2828 * Add the specified property to the object. Issue the appropriate 2829 * transaction or mark a ZFS object as needing an update. 2830 */ 2831 2832 int 2833 sa_add_property(void *object, sa_property_t property) 2834 { 2835 int ret = SA_OK; 2836 sa_group_t parent; 2837 sa_group_t group; 2838 char *proto; 2839 2840 proto = sa_get_optionset_attr(object, "type"); 2841 if (property != NULL) { 2842 if ((ret = sa_valid_property(object, proto, property)) == 2843 SA_OK) { 2844 property = (sa_property_t)xmlAddChild( 2845 (xmlNodePtr)object, (xmlNodePtr)property); 2846 } else { 2847 if (proto != NULL) 2848 sa_free_attr_string(proto); 2849 return (ret); 2850 } 2851 } 2852 2853 if (proto != NULL) 2854 sa_free_attr_string(proto); 2855 2856 parent = sa_get_parent_group(object); 2857 if (!is_persistent(parent)) { 2858 return (ret); 2859 } 2860 2861 if (sa_is_share(parent)) 2862 group = sa_get_parent_group(parent); 2863 else 2864 group = parent; 2865 2866 if (property == NULL) { 2867 ret = SA_NO_MEMORY; 2868 } else { 2869 char oname[SA_STRSIZE]; 2870 2871 if (!is_zfs_group(group)) { 2872 char *id = NULL; 2873 sa_handle_impl_t impl_handle; 2874 scfutilhandle_t *scf_handle; 2875 2876 impl_handle = (sa_handle_impl_t)sa_find_group_handle( 2877 group); 2878 if (impl_handle == NULL || 2879 impl_handle->scfhandle == NULL) 2880 ret = SA_SYSTEM_ERR; 2881 if (ret == SA_OK) { 2882 scf_handle = impl_handle->scfhandle; 2883 if (sa_is_share((sa_group_t)parent)) { 2884 id = sa_get_share_attr( 2885 (sa_share_t)parent, "id"); 2886 } 2887 if (scf_handle->trans == NULL) { 2888 if (is_nodetype(object, "optionset")) { 2889 (void) sa_optionset_name( 2890 (sa_optionset_t)object, 2891 oname, sizeof (oname), id); 2892 } else { 2893 (void) sa_security_name( 2894 (sa_optionset_t)object, 2895 oname, sizeof (oname), id); 2896 } 2897 ret = sa_start_transaction(scf_handle, 2898 oname); 2899 } 2900 if (ret == SA_OK) { 2901 char *name; 2902 char *value; 2903 name = sa_get_property_attr(property, 2904 "type"); 2905 value = sa_get_property_attr(property, 2906 "value"); 2907 if (name != NULL && value != NULL) { 2908 if (scf_handle->scf_state == 2909 SCH_STATE_INIT) { 2910 ret = sa_set_property( 2911 scf_handle, name, 2912 value); 2913 } 2914 } else { 2915 ret = SA_CONFIG_ERR; 2916 } 2917 if (name != NULL) 2918 sa_free_attr_string( 2919 name); 2920 if (value != NULL) 2921 sa_free_attr_string(value); 2922 } 2923 if (id != NULL) 2924 sa_free_attr_string(id); 2925 } 2926 } else { 2927 /* 2928 * ZFS is a special case. We do want 2929 * to allow editing property/security 2930 * lists since we can have a better 2931 * syntax and we also want to keep 2932 * things consistent when possible. 2933 * 2934 * Right now, we defer until the 2935 * sa_commit_properties so we can get 2936 * them all at once. We do need to 2937 * mark the share as "changed" 2938 */ 2939 zfs_set_update((sa_share_t)parent); 2940 } 2941 } 2942 return (ret); 2943 } 2944 2945 /* 2946 * sa_remove_property(property) 2947 * 2948 * Remove the specied property from its containing object. Update the 2949 * repository as appropriate. 2950 */ 2951 2952 int 2953 sa_remove_property(sa_property_t property) 2954 { 2955 int ret = SA_OK; 2956 2957 if (property != NULL) { 2958 sa_optionset_t optionset; 2959 sa_group_t group; 2960 optionset = sa_get_property_parent(property); 2961 if (optionset != NULL) { 2962 group = sa_get_optionset_parent(optionset); 2963 if (group != NULL) { 2964 ret = sa_set_prop_by_prop(optionset, group, 2965 property, SA_PROP_OP_REMOVE); 2966 } 2967 } 2968 xmlUnlinkNode((xmlNodePtr)property); 2969 xmlFreeNode((xmlNodePtr)property); 2970 } else { 2971 ret = SA_NO_SUCH_PROP; 2972 } 2973 return (ret); 2974 } 2975 2976 /* 2977 * sa_update_property(property, value) 2978 * 2979 * Update the specified property to the new value. If value is NULL, 2980 * we currently treat this as a remove. 2981 */ 2982 2983 int 2984 sa_update_property(sa_property_t property, char *value) 2985 { 2986 int ret = SA_OK; 2987 if (value == NULL) { 2988 return (sa_remove_property(property)); 2989 } else { 2990 sa_optionset_t optionset; 2991 sa_group_t group; 2992 set_node_attr((void *)property, "value", value); 2993 optionset = sa_get_property_parent(property); 2994 if (optionset != NULL) { 2995 group = sa_get_optionset_parent(optionset); 2996 if (group != NULL) { 2997 ret = sa_set_prop_by_prop(optionset, group, 2998 property, SA_PROP_OP_UPDATE); 2999 } 3000 } else { 3001 ret = SA_NO_SUCH_PROP; 3002 } 3003 } 3004 return (ret); 3005 } 3006 3007 /* 3008 * sa_get_protocol_property(propset, prop) 3009 * 3010 * Get the specified protocol specific property. These are global to 3011 * the protocol and not specific to a group or share. 3012 */ 3013 3014 sa_property_t 3015 sa_get_protocol_property(sa_protocol_properties_t propset, char *prop) 3016 { 3017 xmlNodePtr node = (xmlNodePtr)propset; 3018 xmlChar *value = NULL; 3019 3020 for (node = node->children; node != NULL; 3021 node = node->next) { 3022 if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { 3023 if (prop == NULL) 3024 break; 3025 value = xmlGetProp(node, (xmlChar *)"type"); 3026 if (value != NULL && 3027 xmlStrcasecmp(value, (xmlChar *)prop) == 0) { 3028 break; 3029 } 3030 if (value != NULL) { 3031 xmlFree(value); 3032 value = NULL; 3033 } 3034 } 3035 } 3036 if (value != NULL) 3037 xmlFree(value); 3038 if (node != NULL && xmlStrcmp(node->name, (xmlChar *)"option") != 0) { 3039 /* 3040 * avoid a non option node -- it is possible to be a 3041 * text node 3042 */ 3043 node = NULL; 3044 } 3045 return ((sa_property_t)node); 3046 } 3047 3048 /* 3049 * sa_get_next_protocol_property(prop) 3050 * 3051 * Get the next protocol specific property in the list. 3052 */ 3053 3054 sa_property_t 3055 sa_get_next_protocol_property(sa_property_t prop) 3056 { 3057 xmlNodePtr node; 3058 3059 for (node = ((xmlNodePtr)prop)->next; node != NULL; 3060 node = node->next) { 3061 if (xmlStrcmp(node->name, (xmlChar *)"option") == 0) { 3062 break; 3063 } 3064 } 3065 return ((sa_property_t)node); 3066 } 3067 3068 /* 3069 * sa_set_protocol_property(prop, value) 3070 * 3071 * Set the specified property to have the new value. The protocol 3072 * specific plugin will then be called to update the property. 3073 */ 3074 3075 int 3076 sa_set_protocol_property(sa_property_t prop, char *value) 3077 { 3078 sa_protocol_properties_t propset; 3079 char *proto; 3080 int ret = SA_INVALID_PROTOCOL; 3081 3082 propset = ((xmlNodePtr)prop)->parent; 3083 if (propset != NULL) { 3084 proto = sa_get_optionset_attr(propset, "type"); 3085 if (proto != NULL) { 3086 set_node_attr((xmlNodePtr)prop, "value", value); 3087 ret = sa_proto_set_property(proto, prop); 3088 sa_free_attr_string(proto); 3089 } 3090 } 3091 return (ret); 3092 } 3093 3094 /* 3095 * sa_add_protocol_property(propset, prop) 3096 * 3097 * Add a new property to the protocol sepcific property set. 3098 */ 3099 3100 int 3101 sa_add_protocol_property(sa_protocol_properties_t propset, sa_property_t prop) 3102 { 3103 xmlNodePtr node; 3104 3105 /* should check for legitimacy */ 3106 node = xmlAddChild((xmlNodePtr)propset, (xmlNodePtr)prop); 3107 if (node != NULL) 3108 return (SA_OK); 3109 return (SA_NO_MEMORY); 3110 } 3111 3112 /* 3113 * sa_create_protocol_properties(proto) 3114 * 3115 * Create a protocol specifity property set. 3116 */ 3117 3118 sa_protocol_properties_t 3119 sa_create_protocol_properties(char *proto) 3120 { 3121 xmlNodePtr node; 3122 3123 node = xmlNewNode(NULL, (xmlChar *)"propertyset"); 3124 if (node != NULL) 3125 xmlSetProp(node, (xmlChar *)"type", (xmlChar *)proto); 3126 return (node); 3127 } 3128