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