xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/smb_quota.c (revision 183ef8a1713ca188e24d970f22c6f9cc333007fd)
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  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <attr.h>
29 #include <unistd.h>
30 #include <libuutil.h>
31 #include <libzfs.h>
32 #include <assert.h>
33 #include <stddef.h>
34 #include <strings.h>
35 #include <errno.h>
36 #include <synch.h>
37 #include <smbsrv/smb_xdr.h>
38 #include <smbsrv/ntstatus.h>
39 #include <smbsrv/libmlsvc.h>
40 #include <smbsrv/smb_idmap.h>
41 #include <mlsvc.h>
42 #include <sys/avl.h>
43 
44 /*
45  * smb_quota subsystem interface - mlsvc.h
46  * ---------------------------------------
47  * Management of the smb_quota_fs_list (see below).
48  * smb_quota_init
49  * smb_quota_fini
50  * smb_quota_add_fs
51  * smb_quota_remove_fs
52  *
53  * smb_quota public interface - libmlsvc.h
54  * ---------------------------------------
55  * Handling of requests to query and set quota data on a filesystem.
56  * smb_quota_query - query user/group quotas on a filesystem
57  * smb_quota_set - set user/group quotas ona filesystem
58  * smb_quota_free - delete the quota list created in smb_quota_query
59  */
60 
61 /*
62  * Querying user & group quotas - smb_quota_query
63  *
64  * In order to fulfill the quota query requests that can be received
65  * from clients, it is required that the quota data can be provided in
66  * a well defined and consistent order, and that a request can specify
67  * at which quota entry to begin the query.
68  *
69  * Quota Tree
70  * Since the file system does not support the above, an avl tree is
71  * populated with the file system's user and group quota data, and
72  * then used to provide the data to respond to query requests. The
73  * avl tree is indexed by the SID.
74  * Each node of the avl tree is an smb_quota_t structure.
75  *
76  * Quota List
77  * There is a list of avl trees, one per file system.
78  * Each node in the list is an smb_quota_tree_t structure.
79  * The list is created via a call to smb_quota_init() when the library
80  * is initialized, and destroyed via a call to smb_quota_fini() when
81  * the library is fini'd.
82  *
83  * An avl tree for a specific file system is created and added to the
84  * list via a call to smb_quota_add_fs() when the file system is shared,
85  * and removed from the list via a call to smb_quota_remove_fs() when
86  * the file system is unshared.
87  *
88  * An avl tree is (re)populated, if required, whenever a quota request
89  * (EXCLUDING a resume request) is received for its filesystem. The
90  * avl tree is considered to be expired (needs to be repopulated) if
91  * either of the following have occurred since it was last (re)populated:
92  * - SMB_QUOTA_REFRESH seconds have elapsed OR
93  * - a quota set operation has been performed on its file system
94  *
95  * In order to perform a smb_quota_query/set operation on a file system
96  * the appropriate quota tree must be identified and locked via a call
97  * to smb_quota_tree_lookup(), The quota tree is locked (qt_locked == B_TRUE)
98  * until the caller releases it via a call to smb_quota_tree_release().
99  */
100 
101 /*
102  * smb_quota_tree_t
103  * Represents an avl tree of user quotas for a file system.
104  *
105  * qt_refcnt - a count of the number of users of the tree.
106  * qt_refcnt is also incremented and decremented when the tree is
107  * added to and removed from the quota list.
108  * The tree cannot be deleted until this count is zero.
109  *
110  * qt_sharecnt - a count of the shares of the file system which the
111  * tree represents.  smb_quota_remove_fs() cannot remove the tree from
112  * removed from the quota list until this count is zero.
113  *
114  * qt_locked - B_TRUE if someone is currently using the tree, in
115  * which case a lookup will wait for the tree to become available.
116  */
117 typedef struct smb_quota_tree {
118 	list_node_t	qt_node;
119 	char		*qt_path;
120 	time_t		qt_timestamp;
121 	uint32_t	qt_refcnt;
122 	uint32_t	qt_sharecnt;
123 	boolean_t	qt_locked;
124 	avl_tree_t	qt_avl;
125 	mutex_t		qt_mutex;
126 }smb_quota_tree_t;
127 
128 /*
129  * smb_quota_fs_list
130  * list of quota trees; one per shared file system.
131  */
132 static list_t smb_quota_fs_list;
133 static boolean_t smb_quota_list_init = B_FALSE;
134 static boolean_t smb_quota_shutdown = B_FALSE;
135 static mutex_t smb_quota_list_mutex;
136 static cond_t smb_quota_list_condvar;
137 static uint32_t smb_quota_tree_cnt = 0;
138 static int smb_quota_fini_timeout = 1; /* seconds */
139 
140 /*
141  * smb_quota_zfs_handle_t
142  * handle to zfs library and dataset
143  */
144 typedef struct smb_quota_zfs_handle {
145 	libzfs_handle_t *z_lib;
146 	zfs_handle_t *z_fs;
147 } smb_quota_zfs_handle_t;
148 
149 /*
150  * smb_quota_zfs_arg_t
151  * arg passed to zfs callback when querying quota properties
152  */
153 typedef struct smb_quota_zfs_arg {
154 	zfs_userquota_prop_t qa_prop;
155 	avl_tree_t *qa_avl;
156 } smb_quota_zfs_arg_t;
157 
158 static void smb_quota_add_ctrldir(const char *);
159 static void smb_quota_remove_ctrldir(const char *);
160 
161 static smb_quota_tree_t *smb_quota_tree_create(const char *);
162 static void smb_quota_tree_delete(smb_quota_tree_t *);
163 
164 static smb_quota_tree_t *smb_quota_tree_lookup(const char *);
165 static void smb_quota_tree_release(smb_quota_tree_t *);
166 static boolean_t smb_quota_tree_match(smb_quota_tree_t *, const char *);
167 static int smb_quota_sid_cmp(const void *, const void *);
168 static uint32_t smb_quota_tree_populate(smb_quota_tree_t *);
169 static boolean_t smb_quota_tree_expired(smb_quota_tree_t *);
170 static void smb_quota_tree_set_expired(smb_quota_tree_t *);
171 
172 static uint32_t smb_quota_zfs_init(const char *, smb_quota_zfs_handle_t *);
173 static void smb_quota_zfs_fini(smb_quota_zfs_handle_t *);
174 static uint32_t smb_quota_zfs_get_quotas(smb_quota_tree_t *);
175 static int smb_quota_zfs_callback(void *, const char *, uid_t, uint64_t);
176 static uint32_t smb_quota_zfs_set_quotas(smb_quota_tree_t *, smb_quota_set_t *);
177 static int smb_quota_sidstr(uint32_t, zfs_userquota_prop_t, char *);
178 static uint32_t smb_quota_sidtype(smb_quota_tree_t *, char *);
179 static int smb_quota_getid(char *, uint32_t, uint32_t *);
180 
181 static uint32_t smb_quota_query_all(smb_quota_tree_t *,
182     smb_quota_query_t *, smb_quota_response_t *);
183 static uint32_t smb_quota_query_list(smb_quota_tree_t *,
184     smb_quota_query_t *, smb_quota_response_t *);
185 
186 #define	SMB_QUOTA_REFRESH		2
187 #define	SMB_QUOTA_CMD_LENGTH		21
188 #define	SMB_QUOTA_CMD_STR_LENGTH	SMB_SID_STRSZ+SMB_QUOTA_CMD_LENGTH
189 
190 /*
191  * In order to display the quota properties tab, windows clients
192  * check for the existence of the quota control file.
193  */
194 #define	SMB_QUOTA_CNTRL_DIR		".$EXTEND"
195 #define	SMB_QUOTA_CNTRL_FILE		"$QUOTA"
196 #define	SMB_QUOTA_CNTRL_INDEX_XATTR	"SUNWsmb:$Q:$INDEX_ALLOCATION"
197 #define	SMB_QUOTA_CNTRL_PERM		"everyone@:rwpaARWc::allow"
198 
199 /*
200  * smb_quota_init
201  * Initialize the list to hold the quota trees.
202  */
203 void
204 smb_quota_init(void)
205 {
206 	(void) mutex_lock(&smb_quota_list_mutex);
207 	if (!smb_quota_list_init) {
208 		list_create(&smb_quota_fs_list, sizeof (smb_quota_tree_t),
209 		    offsetof(smb_quota_tree_t, qt_node));
210 		smb_quota_list_init = B_TRUE;
211 		smb_quota_shutdown = B_FALSE;
212 	}
213 	(void) mutex_unlock(&smb_quota_list_mutex);
214 }
215 
216 /*
217  * smb_quota_fini
218  *
219  * Wait for each quota tree to not be in use (qt_refcnt == 1)
220  * then remove it from the list and delete it.
221  */
222 void
223 smb_quota_fini(void)
224 {
225 	smb_quota_tree_t *qtree, *qtree_next;
226 	boolean_t remove;
227 	struct timespec tswait;
228 	tswait.tv_sec = smb_quota_fini_timeout;
229 	tswait.tv_nsec = 0;
230 
231 	(void) mutex_lock(&smb_quota_list_mutex);
232 	smb_quota_shutdown = B_TRUE;
233 
234 	if (!smb_quota_list_init) {
235 		(void) mutex_unlock(&smb_quota_list_mutex);
236 		return;
237 	}
238 
239 	(void) cond_broadcast(&smb_quota_list_condvar);
240 
241 	while (smb_quota_tree_cnt != 0) {
242 		qtree = list_head(&smb_quota_fs_list);
243 		while (qtree != NULL) {
244 			qtree_next = list_next(&smb_quota_fs_list, qtree);
245 
246 			(void) mutex_lock(&qtree->qt_mutex);
247 			remove = (qtree->qt_refcnt == 1);
248 			if (remove) {
249 				list_remove(&smb_quota_fs_list, qtree);
250 				--qtree->qt_refcnt;
251 			}
252 			(void) mutex_unlock(&qtree->qt_mutex);
253 
254 			if (remove)
255 				smb_quota_tree_delete(qtree);
256 
257 			qtree = qtree_next;
258 		}
259 
260 		if (smb_quota_tree_cnt != 0) {
261 			if (cond_reltimedwait(&smb_quota_list_condvar,
262 			    &smb_quota_list_mutex, &tswait) == ETIME) {
263 				syslog(LOG_WARNING,
264 				    "quota shutdown timeout expired");
265 				break;
266 			}
267 		}
268 	}
269 
270 	if (list_is_empty(&smb_quota_fs_list)) {
271 		list_destroy(&smb_quota_fs_list);
272 		smb_quota_list_init = B_FALSE;
273 	}
274 
275 	(void) mutex_unlock(&smb_quota_list_mutex);
276 }
277 
278 /*
279  * smb_quota_add_fs
280  *
281  * If there is not a quota tree representing the specified path,
282  * create one and add it to the list.
283  */
284 void
285 smb_quota_add_fs(const char *path)
286 {
287 	smb_quota_tree_t *qtree;
288 
289 	(void) mutex_lock(&smb_quota_list_mutex);
290 
291 	if (!smb_quota_list_init || smb_quota_shutdown) {
292 		(void) mutex_unlock(&smb_quota_list_mutex);
293 		return;
294 	}
295 
296 	qtree = list_head(&smb_quota_fs_list);
297 	while (qtree != NULL) {
298 		if (smb_quota_tree_match(qtree, path)) {
299 			(void) mutex_lock(&qtree->qt_mutex);
300 			++qtree->qt_sharecnt;
301 			(void) mutex_unlock(&qtree->qt_mutex);
302 			break;
303 		}
304 		qtree = list_next(&smb_quota_fs_list, qtree);
305 	}
306 
307 	if (qtree == NULL) {
308 		qtree = smb_quota_tree_create(path);
309 		if (qtree)
310 			list_insert_head(&smb_quota_fs_list, (void *)qtree);
311 	}
312 
313 	if (qtree)
314 		smb_quota_add_ctrldir(path);
315 
316 	(void) mutex_unlock(&smb_quota_list_mutex);
317 }
318 
319 /*
320  * smb_quota_remove_fs
321  *
322  * If this is the last share that the quota tree represents
323  * (qtree->qt_sharecnt == 0) remove the qtree from the list.
324  * The qtree will be deleted if/when there is nobody using it
325  * (qtree->qt_refcnt == 0).
326  */
327 void
328 smb_quota_remove_fs(const char *path)
329 {
330 	smb_quota_tree_t *qtree;
331 	boolean_t delete = B_FALSE;
332 
333 	(void) mutex_lock(&smb_quota_list_mutex);
334 
335 	if (!smb_quota_list_init || smb_quota_shutdown) {
336 		(void) mutex_unlock(&smb_quota_list_mutex);
337 		return;
338 	}
339 
340 	qtree = list_head(&smb_quota_fs_list);
341 	while (qtree != NULL) {
342 		assert(qtree->qt_refcnt > 0);
343 		if (smb_quota_tree_match(qtree, path)) {
344 			(void) mutex_lock(&qtree->qt_mutex);
345 			--qtree->qt_sharecnt;
346 			if (qtree->qt_sharecnt == 0) {
347 				list_remove(&smb_quota_fs_list, (void *)qtree);
348 				smb_quota_remove_ctrldir(qtree->qt_path);
349 				--(qtree->qt_refcnt);
350 				delete = (qtree->qt_refcnt == 0);
351 			}
352 			(void) mutex_unlock(&qtree->qt_mutex);
353 			if (delete)
354 				smb_quota_tree_delete(qtree);
355 			break;
356 		}
357 		qtree = list_next(&smb_quota_fs_list, qtree);
358 	}
359 	(void) mutex_unlock(&smb_quota_list_mutex);
360 }
361 
362 /*
363  * smb_quota_query
364  *
365  * Get list of user/group quotas entries.
366  * Request->qq_query_op determines whether to get quota entries
367  * for the specified SIDs (smb_quota_query_list) OR to get all
368  * quota entries, optionally starting at a specified SID.
369  *
370  * Returns NT_STATUS codes.
371  */
372 uint32_t
373 smb_quota_query(smb_quota_query_t *request, smb_quota_response_t *reply)
374 {
375 	uint32_t status;
376 	smb_quota_tree_t *qtree;
377 	smb_quota_query_op_t query_op = request->qq_query_op;
378 
379 	list_create(&reply->qr_quota_list, sizeof (smb_quota_t),
380 	    offsetof(smb_quota_t, q_list_node));
381 
382 	qtree = smb_quota_tree_lookup(request->qq_root_path);
383 	if (qtree == NULL)
384 		return (NT_STATUS_INVALID_PARAMETER);
385 
386 	/* If NOT resuming a previous query all, refresh qtree if required */
387 	if ((query_op != SMB_QUOTA_QUERY_ALL) || (request->qq_restart)) {
388 		status = smb_quota_tree_populate(qtree);
389 		if (status != NT_STATUS_SUCCESS) {
390 			smb_quota_tree_release(qtree);
391 			return (status);
392 		}
393 	}
394 
395 	switch (query_op) {
396 	case SMB_QUOTA_QUERY_SIDLIST:
397 		status = smb_quota_query_list(qtree, request, reply);
398 		break;
399 	case SMB_QUOTA_QUERY_STARTSID:
400 	case SMB_QUOTA_QUERY_ALL:
401 		status = smb_quota_query_all(qtree, request, reply);
402 		break;
403 	case SMB_QUOTA_QUERY_INVALID_OP:
404 	default:
405 		status = NT_STATUS_INVALID_PARAMETER;
406 		break;
407 	}
408 
409 	smb_quota_tree_release(qtree);
410 
411 	return (status);
412 }
413 
414 /*
415  * smb_quota_set
416  *
417  * Set the list of quota entries.
418  */
419 uint32_t
420 smb_quota_set(smb_quota_set_t *request)
421 {
422 	uint32_t status;
423 	smb_quota_tree_t *qtree;
424 
425 	qtree = smb_quota_tree_lookup(request->qs_root_path);
426 	if (qtree == NULL)
427 		return (NT_STATUS_INVALID_PARAMETER);
428 
429 	status = smb_quota_zfs_set_quotas(qtree, request);
430 
431 	smb_quota_tree_set_expired(qtree);
432 	smb_quota_tree_release(qtree);
433 
434 	return (status);
435 }
436 
437 /*
438  * smb_quota_free
439  *
440  * This method frees quota entries.
441  */
442 void
443 smb_quota_free(smb_quota_response_t *reply)
444 {
445 	list_t *list = &reply->qr_quota_list;
446 	smb_quota_t *quota;
447 
448 	while ((quota = list_head(list)) != NULL) {
449 		list_remove(list, quota);
450 		free(quota);
451 	}
452 
453 	list_destroy(list);
454 }
455 
456 /*
457  * smb_quota_query_all
458  *
459  * Query quotas sequentially from tree, optionally starting at a
460  * specified sid. If request->qq_single is TRUE only one quota
461  * should be returned, otherwise up to request->qq_max_quota
462  * should be returned.
463  *
464  * SMB_QUOTA_QUERY_STARTSID
465  * The query should start at the startsid, the first sid in
466  * request->qq_sid_list.
467  *
468  * SMQ_QUOTA_QUERY_ALL
469  * If request->qq_restart the query should restart at the start
470  * of the avl tree. Otherwise the first sid in request->qq_sid_list
471  * is the resume sid and the query should start at the tree entry
472  * after the one it refers to.
473  *
474  * Returns NT_STATUS codes.
475  */
476 static uint32_t
477 smb_quota_query_all(smb_quota_tree_t *qtree, smb_quota_query_t *request,
478     smb_quota_response_t *reply)
479 {
480 	avl_tree_t *avl_tree = &qtree->qt_avl;
481 	avl_index_t where;
482 	list_t *sid_list, *quota_list;
483 	smb_quota_sid_t *sid;
484 	smb_quota_t *quota, *quotal, key;
485 	uint32_t count;
486 
487 	/* find starting sid */
488 	if (request->qq_query_op == SMB_QUOTA_QUERY_STARTSID) {
489 		sid_list = &request->qq_sid_list;
490 		sid = list_head(sid_list);
491 		(void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
492 		quota = avl_find(avl_tree, &key, &where);
493 		if (quota == NULL)
494 			return (NT_STATUS_INVALID_PARAMETER);
495 	} else if (request->qq_restart) {
496 		quota = avl_first(avl_tree);
497 		if (quota == NULL)
498 			return (NT_STATUS_NO_MORE_DATA);
499 	} else {
500 		sid_list = &request->qq_sid_list;
501 		sid = list_head(sid_list);
502 		(void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
503 		quota = avl_find(avl_tree, &key, &where);
504 		if (quota == NULL)
505 			return (NT_STATUS_INVALID_PARAMETER);
506 		quota = AVL_NEXT(avl_tree, quota);
507 		if (quota == NULL)
508 			return (NT_STATUS_NO_MORE_DATA);
509 	}
510 
511 	if ((request->qq_single) && (request->qq_max_quota > 1))
512 		request->qq_max_quota = 1;
513 
514 	quota_list = &reply->qr_quota_list;
515 	count = 0;
516 	while (quota) {
517 		if (count >= request->qq_max_quota)
518 			break;
519 
520 		quotal = malloc(sizeof (smb_quota_t));
521 		if (quotal == NULL)
522 			return (NT_STATUS_NO_MEMORY);
523 		bcopy(quota, quotal, sizeof (smb_quota_t));
524 
525 		list_insert_tail(quota_list, quotal);
526 		++count;
527 
528 		quota = AVL_NEXT(avl_tree, quota);
529 	}
530 
531 	return (NT_STATUS_SUCCESS);
532 }
533 
534 /*
535  * smb_quota_query_list
536  *
537  * Iterate through request sid list querying the avl tree for each.
538  * Insert an entry in the reply quota list for each sid.
539  * For any sid that cannot be found in the avl tree, the reply
540  * quota list entry should contain zeros.
541  */
542 static uint32_t
543 smb_quota_query_list(smb_quota_tree_t *qtree, smb_quota_query_t *request,
544     smb_quota_response_t *reply)
545 {
546 	avl_tree_t *avl_tree = &qtree->qt_avl;
547 	avl_index_t where;
548 	list_t *sid_list, *quota_list;
549 	smb_quota_sid_t *sid;
550 	smb_quota_t *quota, *quotal, key;
551 
552 	quota_list = &reply->qr_quota_list;
553 	sid_list = &request->qq_sid_list;
554 	sid = list_head(sid_list);
555 	while (sid) {
556 		quotal = malloc(sizeof (smb_quota_t));
557 		if (quotal == NULL)
558 			return (NT_STATUS_NO_MEMORY);
559 
560 		(void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
561 		quota = avl_find(avl_tree, &key, &where);
562 		if (quota) {
563 			bcopy(quota, quotal, sizeof (smb_quota_t));
564 		} else {
565 			bzero(quotal, sizeof (smb_quota_t));
566 			(void) strlcpy(quotal->q_sidstr, sid->qs_sidstr,
567 			    SMB_SID_STRSZ);
568 		}
569 
570 		list_insert_tail(quota_list, quotal);
571 		sid = list_next(sid_list, sid);
572 	}
573 
574 	return (NT_STATUS_SUCCESS);
575 }
576 
577 /*
578  * smb_quota_zfs_set_quotas
579  *
580  * This method sets the list of quota entries.
581  *
582  * A quota list or threshold value of SMB_QUOTA_UNLIMITED means that
583  * the user / group does not have a quota limit. In ZFS this maps to
584  * 0 (none).
585  * A quota list or threshold value of (SMB_QUOTA_UNLIMITED - 1) means
586  * that the user / group quota should be removed. In ZFS this maps to
587  * 0 (none).
588  */
589 static uint32_t
590 smb_quota_zfs_set_quotas(smb_quota_tree_t *qtree, smb_quota_set_t *request)
591 {
592 	smb_quota_zfs_handle_t zfs_hdl;
593 	char *typestr, qsetstr[SMB_QUOTA_CMD_STR_LENGTH];
594 	char qlimit[SMB_QUOTA_CMD_LENGTH];
595 	list_t *quota_list;
596 	smb_quota_t *quota;
597 	uint32_t id;
598 	uint32_t status = NT_STATUS_SUCCESS;
599 	uint32_t sidtype;
600 
601 	status = smb_quota_zfs_init(request->qs_root_path, &zfs_hdl);
602 	if (status != NT_STATUS_SUCCESS)
603 		return (status);
604 
605 	quota_list = &request->qs_quota_list;
606 	quota = list_head(quota_list);
607 
608 	while (quota) {
609 		if ((quota->q_limit == SMB_QUOTA_UNLIMITED) ||
610 		    (quota->q_limit == (SMB_QUOTA_UNLIMITED - 1))) {
611 			quota->q_limit = 0;
612 		}
613 		(void) snprintf(qlimit, SMB_QUOTA_CMD_LENGTH, "%llu",
614 		    quota->q_limit);
615 
616 		sidtype = smb_quota_sidtype(qtree, quota->q_sidstr);
617 		switch (sidtype) {
618 		case SidTypeUser:
619 			typestr = "userquota";
620 			break;
621 		case SidTypeWellKnownGroup:
622 		case SidTypeGroup:
623 		case SidTypeAlias:
624 			typestr = "groupquota";
625 			break;
626 		default:
627 			syslog(LOG_WARNING, "Failed to set quota for %s: "
628 			    "%s (%d) not valid for quotas", quota->q_sidstr,
629 			    smb_sid_type2str(sidtype), sidtype);
630 			quota = list_next(quota_list, quota);
631 			continue;
632 		}
633 
634 		if ((smb_quota_getid(quota->q_sidstr, sidtype, &id) == 0) &&
635 		    !(IDMAP_ID_IS_EPHEMERAL(id))) {
636 			(void) snprintf(qsetstr, SMB_QUOTA_CMD_STR_LENGTH,
637 			    "%s@%d", typestr, id);
638 		} else {
639 			(void) snprintf(qsetstr, SMB_QUOTA_CMD_STR_LENGTH,
640 			    "%s@%s", typestr, quota->q_sidstr);
641 		}
642 
643 		errno = 0;
644 		if (zfs_prop_set(zfs_hdl.z_fs, qsetstr, qlimit) != 0) {
645 			syslog(LOG_WARNING, "Failed to set quota for %s: %s",
646 			    quota->q_sidstr, strerror(errno));
647 			status = NT_STATUS_INVALID_PARAMETER;
648 			break;
649 		}
650 
651 		quota = list_next(quota_list, quota);
652 	}
653 
654 	smb_quota_zfs_fini(&zfs_hdl);
655 	return (status);
656 }
657 
658 /*
659  * smb_quota_sidtype
660  *
661  * Determine the type of the sid. If the sid exists in
662  * the qtree get its type from there, otherwise do an
663  * lsa_lookup_sid().
664  */
665 static uint32_t
666 smb_quota_sidtype(smb_quota_tree_t *qtree, char *sidstr)
667 {
668 	smb_quota_t key, *quota;
669 	avl_index_t where;
670 	smb_sid_t *sid = NULL;
671 	smb_account_t ainfo;
672 	uint32_t sidtype = SidTypeUnknown;
673 
674 	(void) strlcpy(key.q_sidstr, sidstr, SMB_SID_STRSZ);
675 	quota = avl_find(&qtree->qt_avl, &key, &where);
676 	if (quota)
677 		return (quota->q_sidtype);
678 
679 	sid = smb_sid_fromstr(sidstr);
680 	if (sid != NULL) {
681 		if (lsa_lookup_sid(sid, &ainfo) == NT_STATUS_SUCCESS) {
682 			sidtype = ainfo.a_type;
683 			smb_account_free(&ainfo);
684 		}
685 		smb_sid_free(sid);
686 	}
687 	return (sidtype);
688 }
689 
690 /*
691  * smb_quota_getid
692  *
693  * Get the user/group id for the sid.
694  */
695 static int
696 smb_quota_getid(char *sidstr, uint32_t sidtype, uint32_t *id)
697 {
698 	int rc = 0;
699 	smb_sid_t *sid = NULL;
700 	int idtype;
701 
702 	sid = smb_sid_fromstr(sidstr);
703 	if (sid == NULL)
704 		return (-1);
705 
706 	switch (sidtype) {
707 	case SidTypeUser:
708 		idtype = SMB_IDMAP_USER;
709 		break;
710 	case SidTypeWellKnownGroup:
711 	case SidTypeGroup:
712 	case SidTypeAlias:
713 		idtype = SMB_IDMAP_GROUP;
714 		break;
715 	default:
716 		rc = -1;
717 		break;
718 	}
719 
720 	if (rc == 0)
721 		rc = smb_idmap_getid(sid, id, &idtype);
722 
723 	smb_sid_free(sid);
724 
725 	return (rc);
726 }
727 
728 /*
729  * smb_quota_tree_lookup
730  *
731  * Find the quota tree in smb_quota_fs_list.
732  *
733  * If the tree is found but is locked, waits for it to become available.
734  * If the tree is available, locks it and returns it.
735  * Otherwise, returns NULL.
736  */
737 static smb_quota_tree_t *
738 smb_quota_tree_lookup(const char *path)
739 {
740 	smb_quota_tree_t *qtree = NULL;
741 
742 	assert(path);
743 	(void) mutex_lock(&smb_quota_list_mutex);
744 
745 	qtree = list_head(&smb_quota_fs_list);
746 	while (qtree != NULL) {
747 		if (!smb_quota_list_init || smb_quota_shutdown) {
748 			(void) mutex_unlock(&smb_quota_list_mutex);
749 			return (NULL);
750 		}
751 
752 		(void) mutex_lock(&qtree->qt_mutex);
753 		assert(qtree->qt_refcnt > 0);
754 
755 		if (!smb_quota_tree_match(qtree, path)) {
756 			(void) mutex_unlock(&qtree->qt_mutex);
757 			qtree = list_next(&smb_quota_fs_list, qtree);
758 			continue;
759 		}
760 
761 		if (qtree->qt_locked) {
762 			(void) mutex_unlock(&qtree->qt_mutex);
763 			(void) cond_wait(&smb_quota_list_condvar,
764 			    &smb_quota_list_mutex);
765 			qtree = list_head(&smb_quota_fs_list);
766 			continue;
767 		}
768 
769 		++(qtree->qt_refcnt);
770 		qtree->qt_locked = B_TRUE;
771 		(void) mutex_unlock(&qtree->qt_mutex);
772 		break;
773 	};
774 
775 	(void) mutex_unlock(&smb_quota_list_mutex);
776 	return (qtree);
777 }
778 
779 /*
780  * smb_quota_tree_release
781  */
782 static void
783 smb_quota_tree_release(smb_quota_tree_t *qtree)
784 {
785 	boolean_t delete;
786 
787 	(void) mutex_lock(&qtree->qt_mutex);
788 	assert(qtree->qt_locked);
789 	assert(qtree->qt_refcnt > 0);
790 
791 	--(qtree->qt_refcnt);
792 	qtree->qt_locked = B_FALSE;
793 	delete = (qtree->qt_refcnt == 0);
794 	(void) mutex_unlock(&qtree->qt_mutex);
795 
796 	(void) mutex_lock(&smb_quota_list_mutex);
797 	if (delete)
798 		smb_quota_tree_delete(qtree);
799 	(void) cond_broadcast(&smb_quota_list_condvar);
800 	(void) mutex_unlock(&smb_quota_list_mutex);
801 }
802 
803 /*
804  * smb_quota_tree_match
805  *
806  * Determine if qtree represents the file system identified by path
807  */
808 static boolean_t
809 smb_quota_tree_match(smb_quota_tree_t *qtree, const char *path)
810 {
811 	return (strncmp(qtree->qt_path, path, MAXPATHLEN) == 0);
812 }
813 
814 /*
815  * smb_quota_tree_create
816  *
817  * Create and initialize an smb_quota_tree_t structure
818  */
819 static smb_quota_tree_t *
820 smb_quota_tree_create(const char *path)
821 {
822 	smb_quota_tree_t *qtree;
823 
824 	assert(MUTEX_HELD(&smb_quota_list_mutex));
825 
826 	qtree = malloc(sizeof (smb_quota_tree_t));
827 	if (qtree == NULL)
828 		return (NULL);
829 
830 	qtree->qt_path = strdup(path);
831 	if (qtree->qt_path == NULL) {
832 		free(qtree);
833 		return (NULL);
834 	}
835 
836 	qtree->qt_timestamp = 0;
837 	qtree->qt_locked = B_FALSE;
838 	qtree->qt_refcnt = 1;
839 	qtree->qt_sharecnt = 1;
840 
841 	avl_create(&qtree->qt_avl, smb_quota_sid_cmp,
842 	    sizeof (smb_quota_t), offsetof(smb_quota_t, q_avl_node));
843 
844 	++smb_quota_tree_cnt;
845 	return (qtree);
846 }
847 
848 /*
849  * smb_quota_tree_delete
850  *
851  * Free and delete the smb_quota_tree_t structure.
852  * qtree must have no users (refcnt == 0).
853  */
854 static void
855 smb_quota_tree_delete(smb_quota_tree_t *qtree)
856 {
857 	void *cookie = NULL;
858 	smb_quota_t *node;
859 
860 	assert(MUTEX_HELD(&smb_quota_list_mutex));
861 	assert(qtree->qt_refcnt == 0);
862 
863 	while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
864 		free(node);
865 	avl_destroy(&qtree->qt_avl);
866 
867 	free(qtree->qt_path);
868 	free(qtree);
869 
870 	--smb_quota_tree_cnt;
871 }
872 
873 /*
874  * smb_quota_sid_cmp
875  *
876  * Comparision function for nodes in an AVL tree which holds quota
877  * entries indexed by SID.
878  */
879 static int
880 smb_quota_sid_cmp(const void *l_arg, const void *r_arg)
881 {
882 	const char *l_sid = ((smb_quota_t *)l_arg)->q_sidstr;
883 	const char *r_sid = ((smb_quota_t *)r_arg)->q_sidstr;
884 	int ret;
885 
886 	ret = strncasecmp(l_sid, r_sid, SMB_SID_STRSZ);
887 
888 	if (ret > 0)
889 		return (1);
890 	if (ret < 0)
891 		return (-1);
892 	return (0);
893 }
894 
895 /*
896  * smb_quota_tree_populate
897  *
898  * If the quota tree needs to be (re)populated:
899  * - delete the qtree's contents
900  * - repopulate the qtree from zfs
901  * - set the qtree's timestamp.
902  */
903 static uint32_t
904 smb_quota_tree_populate(smb_quota_tree_t *qtree)
905 {
906 	void *cookie = NULL;
907 	void *node;
908 	uint32_t status;
909 
910 	assert(qtree->qt_locked);
911 
912 	if (!smb_quota_tree_expired(qtree))
913 		return (NT_STATUS_SUCCESS);
914 
915 	while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
916 		free(node);
917 
918 	status = smb_quota_zfs_get_quotas(qtree);
919 	if (status != NT_STATUS_SUCCESS)
920 		return (status);
921 
922 	qtree->qt_timestamp = time(NULL);
923 
924 	return (NT_STATUS_SUCCESS);
925 }
926 
927 static boolean_t
928 smb_quota_tree_expired(smb_quota_tree_t *qtree)
929 {
930 	time_t tnow = time(NULL);
931 	return ((tnow - qtree->qt_timestamp) > SMB_QUOTA_REFRESH);
932 }
933 
934 static void
935 smb_quota_tree_set_expired(smb_quota_tree_t *qtree)
936 {
937 	qtree->qt_timestamp = 0;
938 }
939 
940 /*
941  * smb_quota_zfs_get_quotas
942  *
943  * Get user and group quotas from ZFS and use them to
944  * populate the quota tree.
945  */
946 static uint32_t
947 smb_quota_zfs_get_quotas(smb_quota_tree_t *qtree)
948 {
949 	smb_quota_zfs_handle_t zfs_hdl;
950 	smb_quota_zfs_arg_t arg;
951 	zfs_userquota_prop_t p;
952 	uint32_t status = NT_STATUS_SUCCESS;
953 
954 	status = smb_quota_zfs_init(qtree->qt_path, &zfs_hdl);
955 	if (status != NT_STATUS_SUCCESS)
956 		return (status);
957 
958 	arg.qa_avl = &qtree->qt_avl;
959 	for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
960 		arg.qa_prop = p;
961 		if (zfs_userspace(zfs_hdl.z_fs, p,
962 		    smb_quota_zfs_callback, &arg) != 0) {
963 			status = NT_STATUS_INTERNAL_ERROR;
964 			break;
965 		}
966 	}
967 
968 	smb_quota_zfs_fini(&zfs_hdl);
969 	return (status);
970 }
971 
972 /*
973  * smb_quota_zfs_callback
974  *
975  * Find or create a node in the avl tree (arg->qa_avl) that matches
976  * the SID derived from domain and rid. If no domain is specified,
977  * lookup the sid (smb_quota_sidstr()).
978  * Populate the node.
979  * The property type (arg->qa_prop) determines which property 'space'
980  * refers to.
981  */
982 static int
983 smb_quota_zfs_callback(void *arg, const char *domain, uid_t rid, uint64_t space)
984 {
985 	smb_quota_zfs_arg_t *qarg = (smb_quota_zfs_arg_t *)arg;
986 	zfs_userquota_prop_t qprop = qarg->qa_prop;
987 	avl_tree_t *avl_tree = qarg->qa_avl;
988 	avl_index_t where;
989 	smb_quota_t *quota, key;
990 
991 	if (domain == NULL || domain[0] == '\0') {
992 		if (smb_quota_sidstr(rid, qprop, key.q_sidstr) != 0)
993 			return (0);
994 	} else {
995 		(void) snprintf(key.q_sidstr, SMB_SID_STRSZ, "%s-%u",
996 		    domain, (uint32_t)rid);
997 	}
998 
999 	quota = avl_find(avl_tree, &key, &where);
1000 	if (quota == NULL) {
1001 		quota = malloc(sizeof (smb_quota_t));
1002 		if (quota == NULL)
1003 			return (NT_STATUS_NO_MEMORY);
1004 		bzero(quota, sizeof (smb_quota_t));
1005 		quota->q_thresh = SMB_QUOTA_UNLIMITED;
1006 		quota->q_limit = SMB_QUOTA_UNLIMITED;
1007 		avl_insert(avl_tree, (void *)quota, where);
1008 		(void) strlcpy(quota->q_sidstr, key.q_sidstr, SMB_SID_STRSZ);
1009 	}
1010 
1011 	switch (qprop) {
1012 	case ZFS_PROP_USERUSED:
1013 		quota->q_sidtype = SidTypeUser;
1014 		quota->q_used = space;
1015 		break;
1016 	case ZFS_PROP_GROUPUSED:
1017 		quota->q_sidtype = SidTypeGroup;
1018 		quota->q_used = space;
1019 		break;
1020 	case ZFS_PROP_USERQUOTA:
1021 		quota->q_sidtype = SidTypeUser;
1022 		quota->q_limit = space;
1023 		break;
1024 	case ZFS_PROP_GROUPQUOTA:
1025 		quota->q_sidtype = SidTypeGroup;
1026 		quota->q_limit = space;
1027 		break;
1028 	default:
1029 		break;
1030 	}
1031 
1032 	quota->q_thresh = quota->q_limit;
1033 
1034 	return (0);
1035 }
1036 
1037 /*
1038  * smb_quota_sidstr
1039  *
1040  * Use idmap to get the sid for the specified id and return
1041  * the string version of the sid in sidstr.
1042  * sidstr must be a buffer of at least SMB_SID_STRSZ.
1043  */
1044 static int
1045 smb_quota_sidstr(uint32_t id, zfs_userquota_prop_t qprop, char *sidstr)
1046 {
1047 	int idtype;
1048 	smb_sid_t *sid;
1049 
1050 	switch (qprop) {
1051 	case ZFS_PROP_USERUSED:
1052 	case ZFS_PROP_USERQUOTA:
1053 		idtype = SMB_IDMAP_USER;
1054 		break;
1055 	case ZFS_PROP_GROUPUSED:
1056 	case ZFS_PROP_GROUPQUOTA:
1057 		idtype = SMB_IDMAP_GROUP;
1058 		break;
1059 	default:
1060 		return (-1);
1061 	}
1062 
1063 	if (smb_idmap_getsid(id, idtype, &sid) != IDMAP_SUCCESS)
1064 		return (-1);
1065 
1066 	smb_sid_tostr(sid, sidstr);
1067 	smb_sid_free(sid);
1068 
1069 	return (0);
1070 }
1071 
1072 /*
1073  * smb_quota_zfs_init
1074  *
1075  * Initialize zfs library and dataset handles
1076  */
1077 static uint32_t
1078 smb_quota_zfs_init(const char *path, smb_quota_zfs_handle_t *zfs_hdl)
1079 {
1080 	char dataset[MAXPATHLEN];
1081 
1082 	if (smb_getdataset(path, dataset, MAXPATHLEN) != 0)
1083 		return (NT_STATUS_INVALID_PARAMETER);
1084 
1085 	if ((zfs_hdl->z_lib = libzfs_init()) == NULL)
1086 		return (NT_STATUS_INTERNAL_ERROR);
1087 
1088 	zfs_hdl->z_fs = zfs_open(zfs_hdl->z_lib, dataset, ZFS_TYPE_DATASET);
1089 	if (zfs_hdl->z_fs == NULL) {
1090 		libzfs_fini(zfs_hdl->z_lib);
1091 		return (NT_STATUS_ACCESS_DENIED);
1092 	}
1093 
1094 	return (NT_STATUS_SUCCESS);
1095 }
1096 
1097 /*
1098  * smb_quota_zfs_fini
1099  *
1100  * Close zfs library and dataset handles
1101  */
1102 static void
1103 smb_quota_zfs_fini(smb_quota_zfs_handle_t *zfs_hdl)
1104 {
1105 	zfs_close(zfs_hdl->z_fs);
1106 	libzfs_fini(zfs_hdl->z_lib);
1107 }
1108 
1109 /*
1110  * smb_quota_add_ctrldir
1111  *
1112  * In order to display the quota properties tab, windows clients
1113  * check for the existence of the quota control file, created
1114  * here as follows:
1115  * - Create SMB_QUOTA_CNTRL_DIR directory (with A_HIDDEN & A_SYSTEM
1116  *   attributes).
1117  * - Create the SMB_QUOTA_CNTRL_FILE file (with extended attribute
1118  *   SMB_QUOTA_CNTRL_INDEX_XATTR) in the SMB_QUOTA_CNTRL_DIR directory.
1119  * - Set the acl of SMB_QUOTA_CNTRL_FILE file to SMB_QUOTA_CNTRL_PERM.
1120  */
1121 static void
1122 smb_quota_add_ctrldir(const char *path)
1123 {
1124 	int newfd, dirfd, afd;
1125 	nvlist_t *request;
1126 	char dir[MAXPATHLEN], file[MAXPATHLEN];
1127 	acl_t *aclp;
1128 	struct stat statbuf;
1129 
1130 	assert(path != NULL);
1131 
1132 	(void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR);
1133 	(void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE);
1134 	if ((mkdir(dir, 0750) < 0) && (errno != EEXIST))
1135 		return;
1136 
1137 	if ((dirfd = open(dir, O_RDONLY)) < 0) {
1138 		(void) remove(dir);
1139 		return;
1140 	}
1141 
1142 	if (nvlist_alloc(&request, NV_UNIQUE_NAME, 0) == 0) {
1143 		if ((nvlist_add_boolean_value(request, A_HIDDEN, 1) != 0) ||
1144 		    (nvlist_add_boolean_value(request, A_SYSTEM, 1) != 0) ||
1145 		    (fsetattr(dirfd, XATTR_VIEW_READWRITE, request))) {
1146 			nvlist_free(request);
1147 			(void) close(dirfd);
1148 			(void) remove(dir);
1149 			return;
1150 		}
1151 	}
1152 	nvlist_free(request);
1153 	(void) close(dirfd);
1154 
1155 	if (stat(file, &statbuf) != 0) {
1156 		if ((newfd = creat(file, 0640)) < 0) {
1157 			(void) remove(dir);
1158 			return;
1159 		}
1160 		(void) close(newfd);
1161 	}
1162 
1163 	afd = attropen(file, SMB_QUOTA_CNTRL_INDEX_XATTR, O_RDWR | O_CREAT,
1164 	    0640);
1165 	if (afd == -1) {
1166 		(void) unlink(file);
1167 		(void) remove(dir);
1168 		return;
1169 	}
1170 	(void) close(afd);
1171 
1172 	if (acl_fromtext(SMB_QUOTA_CNTRL_PERM, &aclp) != 0) {
1173 		(void) unlink(file);
1174 		(void) remove(dir);
1175 		return;
1176 	}
1177 
1178 	if (acl_set(file, aclp) == -1) {
1179 		(void) unlink(file);
1180 		(void) remove(dir);
1181 		acl_free(aclp);
1182 		return;
1183 	}
1184 	acl_free(aclp);
1185 }
1186 
1187 /*
1188  * smb_quota_remove_ctrldir
1189  *
1190  * Remove SMB_QUOTA_CNTRL_FILE and SMB_QUOTA_CNTRL_DIR.
1191  */
1192 static void
1193 smb_quota_remove_ctrldir(const char *path)
1194 {
1195 	char dir[MAXPATHLEN], file[MAXPATHLEN];
1196 	assert(path);
1197 
1198 	(void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR);
1199 	(void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE);
1200 	(void) unlink(file);
1201 	(void) remove(dir);
1202 }
1203