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