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