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 */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <fcntl.h>
29#include <attr.h>
30#include <unistd.h>
31#include <libuutil.h>
32#include <libzfs.h>
33#include <assert.h>
34#include <stddef.h>
35#include <strings.h>
36#include <errno.h>
37#include <synch.h>
38#include <smbsrv/smb_xdr.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 */
117typedef 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 */
132static list_t smb_quota_fs_list;
133static boolean_t smb_quota_list_init = B_FALSE;
134static boolean_t smb_quota_shutdown = B_FALSE;
135static mutex_t smb_quota_list_mutex = DEFAULTMUTEX;
136static cond_t smb_quota_list_condvar;
137static uint32_t smb_quota_tree_cnt = 0;
138static int smb_quota_fini_timeout = 1; /* seconds */
139
140/*
141 * smb_quota_zfs_handle_t
142 * handle to zfs library and dataset
143 */
144typedef 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 */
153typedef 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
158static void smb_quota_add_ctrldir(const char *);
159static void smb_quota_remove_ctrldir(const char *);
160
161static smb_quota_tree_t *smb_quota_tree_create(const char *);
162static void smb_quota_tree_delete(smb_quota_tree_t *);
163
164static smb_quota_tree_t *smb_quota_tree_lookup(const char *);
165static void smb_quota_tree_release(smb_quota_tree_t *);
166static boolean_t smb_quota_tree_match(smb_quota_tree_t *, const char *);
167static int smb_quota_sid_cmp(const void *, const void *);
168static uint32_t smb_quota_tree_populate(smb_quota_tree_t *);
169static boolean_t smb_quota_tree_expired(smb_quota_tree_t *);
170static void smb_quota_tree_set_expired(smb_quota_tree_t *);
171
172static uint32_t smb_quota_zfs_init(const char *, smb_quota_zfs_handle_t *);
173static void smb_quota_zfs_fini(smb_quota_zfs_handle_t *);
174static uint32_t smb_quota_zfs_get_quotas(smb_quota_tree_t *);
175static int smb_quota_zfs_callback(void *, const char *, uid_t, uint64_t);
176static uint32_t smb_quota_zfs_set_quotas(smb_quota_tree_t *, smb_quota_set_t *);
177static int smb_quota_sidstr(uint32_t, zfs_userquota_prop_t, char *);
178static uint32_t smb_quota_sidtype(smb_quota_tree_t *, char *);
179static int smb_quota_getid(char *, uint32_t, uint32_t *);
180
181static uint32_t smb_quota_query_all(smb_quota_tree_t *,
182    smb_quota_query_t *, smb_quota_response_t *);
183static 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 */
203void
204smb_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 */
222void
223smb_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 (!list_is_empty(&smb_quota_fs_list)) {
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 (!list_is_empty(&smb_quota_fs_list)) {
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 */
284void
285smb_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 */
327void
328smb_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 */
372uint32_t
373smb_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 */
419uint32_t
420smb_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 */
442void
443smb_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 */
476static uint32_t
477smb_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_ENTRIES);
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_ENTRIES);
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 */
542static uint32_t
543smb_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 */
589static uint32_t
590smb_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 */
665static uint32_t
666smb_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 */
695static int
696smb_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 */
737static smb_quota_tree_t *
738smb_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 */
782static void
783smb_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 */
808static boolean_t
809smb_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 */
819static smb_quota_tree_t *
820smb_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 = calloc(sizeof (smb_quota_tree_t), 1);
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 */
854static void
855smb_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 */
879static int
880smb_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 */
903static uint32_t
904smb_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
927static boolean_t
928smb_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
934static void
935smb_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 */
946static uint32_t
947smb_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 */
982static int
983smb_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 */
1044static int
1045smb_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 */
1077static uint32_t
1078smb_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 */
1102static void
1103smb_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 */
1121static void
1122smb_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 */
1192static void
1193smb_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