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 #include <libzfs.h> 30 #include <string.h> 31 #include <libshare.h> 32 #include "libshare_impl.h" 33 #include <libintl.h> 34 35 extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *); 36 extern sa_group_t _sa_create_zfs_group(sa_group_t, char *); 37 extern char *sa_fstype(char *); 38 extern void set_node_attr(void *, char *, char *); 39 extern int sa_is_share(void *); 40 41 /* 42 * File system specific code for ZFS. The original code was stolen 43 * from the "zfs" command and modified to better suit this library's 44 * usage. 45 */ 46 47 typedef struct get_all_cbdata { 48 zfs_handle_t **cb_handles; 49 size_t cb_alloc; 50 size_t cb_used; 51 } get_all_cbdata_t; 52 53 /* 54 * sa_zfs_init(impl_handle) 55 * 56 * Initialize an access handle into libzfs. The handle needs to stay 57 * around until sa_zfs_fini() in order to maintain the cache of 58 * mounts. 59 */ 60 61 void 62 sa_zfs_init(sa_handle_impl_t impl_handle) 63 { 64 impl_handle->zfs_libhandle = libzfs_init(); 65 libzfs_print_on_error(impl_handle->zfs_libhandle, B_TRUE); 66 } 67 68 /* 69 * sa_zfs_fini(impl_handle) 70 * 71 * cleanup data structures and the libzfs handle used for accessing 72 * zfs file share info. 73 */ 74 75 void 76 sa_zfs_fini(sa_handle_impl_t impl_handle) 77 { 78 if (impl_handle->zfs_libhandle != NULL) { 79 libzfs_fini(impl_handle->zfs_libhandle); 80 impl_handle->zfs_libhandle = NULL; 81 if (impl_handle->zfs_list != NULL) { 82 /* 83 * contents of zfs_list were already freed by the call to 84 * libzfs_fini(). 85 */ 86 free(impl_handle->zfs_list); 87 impl_handle->zfs_list = NULL; 88 impl_handle->zfs_list_count = 0; 89 } 90 } 91 } 92 93 /* 94 * get_one_filesystem(zfs_handle_t, data) 95 * 96 * an interator function called while iterating through the ZFS 97 * root. It accumulates into an array of file system handles that can 98 * be used to derive info about those file systems. 99 */ 100 101 static int 102 get_one_filesystem(zfs_handle_t *zhp, void *data) 103 { 104 get_all_cbdata_t *cbp = data; 105 106 /* 107 * Skip any zvols 108 */ 109 if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { 110 zfs_close(zhp); 111 return (0); 112 } 113 114 if (cbp->cb_alloc == cbp->cb_used) { 115 zfs_handle_t **handles; 116 117 if (cbp->cb_alloc == 0) 118 cbp->cb_alloc = 64; 119 else 120 cbp->cb_alloc *= 2; 121 122 handles = calloc(1, cbp->cb_alloc * sizeof (void *)); 123 if (handles == NULL) { 124 return (0); 125 } 126 127 if (cbp->cb_handles) { 128 (void) memcpy(handles, cbp->cb_handles, 129 cbp->cb_used * sizeof (void *)); 130 free(cbp->cb_handles); 131 } 132 133 cbp->cb_handles = handles; 134 } 135 136 cbp->cb_handles[cbp->cb_used++] = zhp; 137 138 return (zfs_iter_filesystems(zhp, get_one_filesystem, data)); 139 } 140 141 /* 142 * get_all_filesystems(zfs_handle_t ***fslist, size_t *count) 143 * 144 * iterate through all ZFS file systems starting at the root. Returns 145 * a count and an array of handle pointers. Allocating is only done 146 * once. The caller does not need to free since it will be done at 147 * sa_zfs_fini() time. 148 */ 149 150 static void 151 get_all_filesystems(sa_handle_impl_t impl_handle, 152 zfs_handle_t ***fslist, size_t *count) 153 { 154 get_all_cbdata_t cb = { 0 }; 155 156 if (impl_handle->zfs_list != NULL) { 157 *fslist = impl_handle->zfs_list; 158 *count = impl_handle->zfs_list_count; 159 return; 160 } 161 162 (void) zfs_iter_root(impl_handle->zfs_libhandle, 163 get_one_filesystem, &cb); 164 165 impl_handle->zfs_list = *fslist = cb.cb_handles; 166 impl_handle->zfs_list_count = *count = cb.cb_used; 167 } 168 169 /* 170 * mountpoint_compare(a, b) 171 * 172 * compares the mountpoint on two zfs file systems handles. 173 * returns values following strcmp() model. 174 */ 175 176 static int 177 mountpoint_compare(const void *a, const void *b) 178 { 179 zfs_handle_t **za = (zfs_handle_t **)a; 180 zfs_handle_t **zb = (zfs_handle_t **)b; 181 char mounta[MAXPATHLEN]; 182 char mountb[MAXPATHLEN]; 183 184 verify(zfs_prop_get(*za, ZFS_PROP_MOUNTPOINT, mounta, 185 sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0); 186 verify(zfs_prop_get(*zb, ZFS_PROP_MOUNTPOINT, mountb, 187 sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0); 188 189 return (strcmp(mounta, mountb)); 190 } 191 192 /* 193 * get_zfs_dataset(impl_handle, path) 194 * 195 * get the name of the ZFS dataset the path is equivalent to. The 196 * dataset name is used for get/set of ZFS properties since libzfs 197 * requires a dataset to do a zfs_open(). 198 */ 199 200 static char * 201 get_zfs_dataset(sa_handle_impl_t impl_handle, char *path) 202 { 203 size_t i, count = 0; 204 char *dataset = NULL; 205 zfs_handle_t **zlist; 206 char mountpoint[ZFS_MAXPROPLEN]; 207 char canmount[ZFS_MAXPROPLEN]; 208 209 get_all_filesystems(impl_handle, &zlist, &count); 210 qsort(zlist, count, sizeof (void *), mountpoint_compare); 211 for (i = 0; i < count; i++) { 212 /* must have a mountpoint */ 213 if (zfs_prop_get(zlist[i], ZFS_PROP_MOUNTPOINT, mountpoint, 214 sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { 215 /* no mountpoint */ 216 continue; 217 } 218 219 /* mountpoint must be a path */ 220 if (strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) == 0 || 221 strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) 222 continue; 223 224 /* canmount must be set */ 225 canmount[0] = '\0'; 226 if (!zfs_prop_get(zlist[i], ZFS_PROP_CANMOUNT, canmount, 227 sizeof (canmount), NULL, NULL, 0, B_FALSE) != 0 || 228 strcmp(canmount, "off") == 0) 229 continue; 230 231 /* 232 * have a mountable handle but want to skip those marked none 233 * and legacy 234 */ 235 if (strcmp(mountpoint, path) == 0) { 236 dataset = (char *)zfs_get_name(zlist[i]); 237 break; 238 } 239 240 } 241 242 if (dataset != NULL) { 243 dataset = strdup(dataset); 244 } 245 return (dataset); 246 } 247 248 /* 249 * get_zfs_property(dataset, property) 250 * 251 * Get the file system property specified from the ZFS dataset. 252 */ 253 254 static char * 255 get_zfs_property(char *dataset, zfs_prop_t property) 256 { 257 zfs_handle_t *handle = NULL; 258 char shareopts[ZFS_MAXPROPLEN]; 259 libzfs_handle_t *libhandle; 260 261 libhandle = libzfs_init(); 262 if (libhandle != NULL) { 263 handle = zfs_open(libhandle, dataset, ZFS_TYPE_FILESYSTEM); 264 if (handle != NULL) { 265 if (zfs_prop_get(handle, property, shareopts, 266 sizeof (shareopts), NULL, NULL, 0, 267 B_FALSE) == 0) { 268 zfs_close(handle); 269 libzfs_fini(libhandle); 270 return (strdup(shareopts)); 271 } 272 zfs_close(handle); 273 } 274 libzfs_fini(libhandle); 275 } 276 return (NULL); 277 } 278 279 /* 280 * sa_zfs_is_shared(handle, path) 281 * 282 * Check to see if the ZFS path provided has the sharenfs option set 283 * or not. 284 */ 285 286 int 287 sa_zfs_is_shared(sa_handle_t sahandle, char *path) 288 { 289 int ret = 0; 290 char *dataset; 291 zfs_handle_t *handle = NULL; 292 char shareopts[ZFS_MAXPROPLEN]; 293 libzfs_handle_t *libhandle; 294 295 dataset = get_zfs_dataset((sa_handle_t)sahandle, path); 296 if (dataset != NULL) { 297 libhandle = libzfs_init(); 298 if (libhandle != NULL) { 299 handle = zfs_open(libhandle, dataset, ZFS_TYPE_FILESYSTEM); 300 if (handle != NULL) { 301 if (zfs_prop_get(handle, ZFS_PROP_SHARENFS, shareopts, 302 sizeof (shareopts), NULL, NULL, 0, 303 B_FALSE) == 0 && 304 strcmp(shareopts, "off") != 0) 305 ret = 1; /* it is shared */ 306 zfs_close(handle); 307 } 308 libzfs_fini(libhandle); 309 } 310 free(dataset); 311 } 312 return (ret); 313 } 314 315 /* 316 * find_or_create_group(groupname, proto, *err) 317 * 318 * While walking the ZFS tree, we need to add shares to a defined 319 * group. If the group doesn't exist, create it first, making sure it 320 * is marked as a ZFS group. 321 * 322 * Note that all ZFS shares are in a subgroup of the top level group 323 * called "zfs". 324 */ 325 326 static sa_group_t 327 find_or_create_group(sa_handle_t handle, char *groupname, char *proto, int *err) 328 { 329 sa_group_t group; 330 sa_optionset_t optionset; 331 int ret = SA_OK; 332 333 /* 334 * we check to see if the "zfs" group exists. Since this 335 * should be the top level group, we don't want the 336 * parent. This is to make sure the zfs group has been created 337 * and to created if it hasn't been. 338 */ 339 group = sa_get_group(handle, groupname); 340 if (group == NULL) { 341 group = sa_create_group(handle, groupname, &ret); 342 343 /* make sure this is flagged as a ZFS group */ 344 if (group != NULL) 345 ret = sa_set_group_attr(group, "zfs", "true"); 346 } 347 if (group != NULL) { 348 if (proto != NULL) { 349 optionset = sa_get_optionset(group, proto); 350 if (optionset == NULL) { 351 optionset = sa_create_optionset(group, proto); 352 } else { 353 char **protolist; 354 int numprotos, i; 355 numprotos = sa_get_protocols(&protolist); 356 for (i = 0; i < numprotos; i++) { 357 optionset = sa_create_optionset(group, protolist[i]); 358 } 359 if (protolist != NULL) 360 free(protolist); 361 } 362 } 363 } 364 if (err != NULL) 365 *err = ret; 366 return (group); 367 } 368 369 /* 370 * find_or_create_zfs_subgroup(groupname, optstring, *err) 371 * 372 * ZFS shares will be in a subgroup of the "zfs" master group. This 373 * function looks to see if the groupname exists and returns it if it 374 * does or else creates a new one with the specified name and returns 375 * that. The "zfs" group will exist before we get here, but we make 376 * sure just in case. 377 * 378 * err must be a valid pointer. 379 */ 380 381 static sa_group_t 382 find_or_create_zfs_subgroup(sa_handle_t handle, char *groupname, 383 char *optstring, int *err) 384 { 385 sa_group_t group = NULL; 386 sa_group_t zfs; 387 char *name; 388 char *options; 389 390 /* start with the top-level "zfs" group */ 391 zfs = sa_get_group(handle, "zfs"); 392 *err = SA_OK; 393 if (zfs != NULL) { 394 for (group = sa_get_sub_group(zfs); group != NULL; 395 group = sa_get_next_group(group)) { 396 name = sa_get_group_attr(group, "name"); 397 if (name != NULL && strcmp(name, groupname) == 0) { 398 /* have the group so break out of here */ 399 sa_free_attr_string(name); 400 break; 401 } 402 if (name != NULL) 403 sa_free_attr_string(name); 404 } 405 406 if (group == NULL) { 407 /* need to create the sub-group since it doesn't exist */ 408 group = _sa_create_zfs_group(zfs, groupname); 409 if (group != NULL) { 410 set_node_attr(group, "zfs", "true"); 411 } 412 if (strcmp(optstring, "on") == 0) 413 optstring = "rw"; 414 if (group != NULL) { 415 options = strdup(optstring); 416 if (options != NULL) { 417 *err = sa_parse_legacy_options(group, options, "nfs"); 418 free(options); 419 } else { 420 *err = SA_NO_MEMORY; 421 } 422 } 423 } 424 } 425 return (group); 426 } 427 428 /* 429 * sa_get_zfs_shares(handle, groupname) 430 * 431 * Walk the mnttab for all zfs mounts and determine which are 432 * shared. Find or create the appropriate group/sub-group to contain 433 * the shares. 434 * 435 * All shares are in a sub-group that will hold the properties. This 436 * allows representing the inherited property model. 437 */ 438 439 int 440 sa_get_zfs_shares(sa_handle_t handle, char *groupname) 441 { 442 sa_group_t group; 443 sa_group_t zfsgroup; 444 int legacy = 0; 445 int err; 446 zfs_handle_t **zlist; 447 char shareopts[ZFS_MAXPROPLEN]; 448 sa_share_t share; 449 zfs_source_t source; 450 char sourcestr[ZFS_MAXPROPLEN]; 451 char mountpoint[ZFS_MAXPROPLEN]; 452 char *options; 453 size_t count = 0, i; 454 libzfs_handle_t *zfs_libhandle; 455 456 /* 457 * If we can't access libzfs, don't bother doing anything. 458 */ 459 zfs_libhandle = ((sa_handle_impl_t)handle)->zfs_libhandle; 460 if (zfs_libhandle == NULL) 461 return (SA_SYSTEM_ERR); 462 463 zfsgroup = find_or_create_group(handle, groupname, "nfs", &err); 464 if (zfsgroup != NULL) { 465 /* 466 * need to walk the mounted ZFS pools and datasets to 467 * find shares that are possible. 468 */ 469 get_all_filesystems((sa_handle_impl_t)handle, &zlist, &count); 470 qsort(zlist, count, sizeof (void *), mountpoint_compare); 471 472 group = zfsgroup; 473 for (i = 0; i < count; i++) { 474 char *dataset; 475 476 source = ZFS_SRC_ALL; 477 if (zfs_prop_get(zlist[i], ZFS_PROP_MOUNTPOINT, mountpoint, 478 sizeof (mountpoint), NULL, NULL, 0, 479 B_FALSE) != 0) { 480 /* no mountpoint */ 481 continue; 482 } 483 484 /* 485 * zfs_get_name value must not be freed. It is just a 486 * pointer to a value in the handle. 487 */ 488 if ((dataset = (char *)zfs_get_name(zlist[i])) == NULL) 489 continue; 490 491 /* 492 * only deal with "mounted" file systems since 493 * unmounted file systems can't actually be shared. 494 */ 495 496 if (!zfs_is_mounted(zlist[i], NULL)) 497 continue; 498 499 if (zfs_prop_get(zlist[i], ZFS_PROP_SHARENFS, shareopts, 500 sizeof (shareopts), &source, sourcestr, 501 ZFS_MAXPROPLEN, 502 B_FALSE) == 0 && 503 strcmp(shareopts, "off") != 0) { 504 /* it is shared so add to list */ 505 share = sa_find_share(handle, mountpoint); 506 err = SA_OK; 507 if (share != NULL) { 508 /* 509 * A zfs file system had been shared 510 * through traditional methods 511 * (share/dfstab or added to a non-zfs 512 * group. Now it has been added to a 513 * ZFS group via the zfs 514 * command. Remove from previous 515 * config and setup with current 516 * options. 517 */ 518 err = sa_remove_share(share); 519 share = NULL; 520 } 521 if (err == SA_OK) { 522 if (source & ZFS_SRC_INHERITED) { 523 int doshopt = 0; 524 /* 525 * Need to find the "real" parent 526 * sub-group. It may not be mounted, but it 527 * was identified in the "sourcestr" 528 * variable. The real parent not mounted can 529 * occur if "canmount=off and sharenfs=on". 530 */ 531 group = find_or_create_zfs_subgroup(handle, 532 sourcestr, 533 shareopts, &doshopt); 534 if (group != NULL) { 535 share = _sa_add_share(group, mountpoint, 536 SA_SHARE_TRANSIENT, 537 &err); 538 /* 539 * some options may only be on 540 * shares. If the opt string 541 * contains one of those, we 542 * put it just on the share. 543 */ 544 if (share != NULL && 545 doshopt == SA_PROP_SHARE_ONLY) { 546 options = strdup(shareopts); 547 if (options != NULL) { 548 err = sa_parse_legacy_options(share, 549 options, "nfs"); 550 free(options); 551 } 552 } 553 } else { 554 err = SA_NO_MEMORY; 555 } 556 } else { 557 group = _sa_create_zfs_group(zfsgroup, dataset); 558 if (group == NULL) { 559 static int err = 0; 560 /* 561 * there is a problem, but we can't do 562 * anything about it at this point so 563 * we issue a warning an move on. 564 */ 565 if (err == 0) { 566 /* only print error once */ 567 (void) fprintf(stderr, 568 dgettext(TEXT_DOMAIN, 569 "Cannot create ZFS subgroup " 570 "during initialization:" 571 " %s\n"), 572 sa_errorstr(SA_SYSTEM_ERR)); 573 err = 1; 574 } 575 continue; 576 } 577 set_node_attr(group, "zfs", "true"); 578 share = _sa_add_share(group, mountpoint, 579 SA_SHARE_TRANSIENT, &err); 580 if (err == SA_OK) { 581 if (strcmp(shareopts, "on") != 0) { 582 options = strdup(shareopts); 583 if (options != NULL) { 584 err = sa_parse_legacy_options(group, 585 options, 586 "nfs"); 587 free(options); 588 } 589 if (err == SA_PROP_SHARE_ONLY) { 590 /* 591 * Same as above, some 592 * properties may only be on 593 * shares, but due to the ZFS 594 * sub-groups being 595 * artificial, we sometimes 596 * get this and have to deal 597 * with it. We do it by 598 * attempting to put it on the 599 * share. 600 */ 601 options = strdup(shareopts); 602 if (options != NULL) 603 err = sa_parse_legacy_options( 604 share, 605 options, 606 "nfs"); 607 free(options); 608 } 609 /* unmark the share's changed state */ 610 set_node_attr(share, "changed", NULL); 611 } 612 } 613 } 614 } 615 } 616 } 617 } 618 /* 619 * Don't need to free the "zlist" variable since it is only a 620 * pointer to a cached value that will be freed when 621 * sa_fini() is called. 622 */ 623 return (legacy); 624 } 625 626 #define COMMAND "/usr/sbin/zfs" 627 628 /* 629 * sa_zfs_set_sharenfs(group, path, on) 630 * 631 * Update the "sharenfs" property on the path. If on is true, then set 632 * to the properties on the group or "on" if no properties are 633 * defined. Set to "off" if on is false. 634 */ 635 636 int 637 sa_zfs_set_sharenfs(sa_group_t group, char *path, int on) 638 { 639 int ret = SA_NOT_IMPLEMENTED; 640 char *command; 641 642 command = malloc(ZFS_MAXPROPLEN * 2); 643 if (command != NULL) { 644 char *opts = NULL; 645 char *dataset = NULL; 646 FILE *pfile; 647 sa_handle_impl_t impl_handle; 648 /* for now, NFS is always available for "zfs" */ 649 if (on) { 650 opts = sa_proto_legacy_format("nfs", group, 1); 651 if (opts != NULL && strlen(opts) == 0) { 652 free(opts); 653 opts = strdup("on"); 654 } 655 } 656 657 impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); 658 assert(impl_handle != NULL); 659 if (impl_handle != NULL) 660 dataset = get_zfs_dataset(impl_handle, path); 661 else 662 ret = SA_SYSTEM_ERR; 663 664 if (dataset != NULL) { 665 (void) snprintf(command, ZFS_MAXPROPLEN * 2, 666 "%s set sharenfs=\"%s\" %s", COMMAND, 667 opts != NULL ? opts : "off", 668 dataset); 669 pfile = popen(command, "r"); 670 if (pfile != NULL) { 671 ret = pclose(pfile); 672 if (ret != 0) 673 ret = SA_SYSTEM_ERR; 674 } 675 } 676 if (opts != NULL) 677 free(opts); 678 if (dataset != NULL) 679 free(dataset); 680 free(command); 681 } 682 return (ret); 683 } 684 685 /* 686 * sa_zfs_update(group) 687 * 688 * call back to ZFS to update the share if necessary. 689 * Don't do it if it isn't a real change. 690 */ 691 int 692 sa_zfs_update(sa_group_t group) 693 { 694 sa_optionset_t protopt; 695 sa_group_t parent; 696 char *command; 697 char *optstring; 698 int ret = SA_OK; 699 int doupdate = 0; 700 FILE *pfile; 701 702 if (sa_is_share(group)) 703 parent = sa_get_parent_group(group); 704 else 705 parent = group; 706 707 if (parent != NULL) { 708 command = malloc(ZFS_MAXPROPLEN * 2); 709 if (command == NULL) 710 return (SA_NO_MEMORY); 711 712 *command = '\0'; 713 for (protopt = sa_get_optionset(parent, NULL); protopt != NULL; 714 protopt = sa_get_next_optionset(protopt)) { 715 716 char *proto = sa_get_optionset_attr(protopt, "type"); 717 char *path; 718 char *dataset = NULL; 719 char *zfsopts = NULL; 720 721 if (sa_is_share(group)) { 722 path = sa_get_share_attr((sa_share_t)group, "path"); 723 if (path != NULL) { 724 sa_handle_impl_t impl_handle; 725 726 impl_handle = sa_find_group_handle(group); 727 if (impl_handle != NULL) 728 dataset = get_zfs_dataset(impl_handle, path); 729 else 730 ret = SA_SYSTEM_ERR; 731 732 sa_free_attr_string(path); 733 } 734 } else { 735 dataset = sa_get_group_attr(group, "name"); 736 } 737 /* update only when there is an optstring found */ 738 doupdate = 0; 739 if (proto != NULL && dataset != NULL) { 740 optstring = sa_proto_legacy_format(proto, group, 1); 741 zfsopts = get_zfs_property(dataset, ZFS_PROP_SHARENFS); 742 743 if (optstring != NULL && zfsopts != NULL) { 744 if (strcmp(optstring, zfsopts) != 0) 745 doupdate++; 746 } 747 748 if (doupdate) { 749 if (optstring != NULL && strlen(optstring) > 0) { 750 (void) snprintf(command, ZFS_MAXPROPLEN * 2, 751 "%s set sharenfs=%s %s", COMMAND, 752 optstring, dataset); 753 } else { 754 (void) snprintf(command, ZFS_MAXPROPLEN * 2, 755 "%s set sharenfs=on %s", COMMAND, 756 dataset); 757 } 758 pfile = popen(command, "r"); 759 if (pfile != NULL) 760 ret = pclose(pfile); 761 switch (ret) { 762 default: 763 case 1: 764 ret = SA_SYSTEM_ERR; 765 break; 766 case 2: 767 ret = SA_SYNTAX_ERR; 768 break; 769 case 0: 770 break; 771 } 772 } 773 if (optstring != NULL) { 774 free(optstring); 775 } 776 if (zfsopts != NULL) 777 free(zfsopts); 778 } 779 if (proto != NULL) 780 sa_free_attr_string(proto); 781 if (dataset != NULL) 782 free(dataset); 783 } 784 free(command); 785 } 786 return (ret); 787 } 788 789 /* 790 * sa_group_is_zfs(group) 791 * 792 * Given the group, determine if the zfs attribute is set. 793 */ 794 795 int 796 sa_group_is_zfs(sa_group_t group) 797 { 798 char *zfs; 799 int ret = 0; 800 801 zfs = sa_get_group_attr(group, "zfs"); 802 if (zfs != NULL) { 803 ret = 1; 804 sa_free_attr_string(zfs); 805 } 806 return (ret); 807 } 808 809 /* 810 * sa_path_is_zfs(path) 811 * 812 * Check to see if the file system path represents is of type "zfs". 813 */ 814 815 int 816 sa_path_is_zfs(char *path) 817 { 818 char *fstype; 819 int ret = 0; 820 821 fstype = sa_fstype(path); 822 if (fstype != NULL && strcmp(fstype, "zfs") == 0) { 823 ret = 1; 824 } 825 if (fstype != NULL) 826 sa_free_fstype(fstype); 827 return (ret); 828 } 829