6023 Quota tab for an SMB share sometimes doesn't work
[unleashed.git] / usr / src / lib / smbsrv / libmlsvc / common / smb_quota.c
blob2fdffde894774711690532f2d1fcd048a3f3e29a
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
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>
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
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
63 * Querying user & group quotas - smb_quota_query
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.
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.
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.
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.
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
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().
103 * smb_quota_tree_t
104 * Represents an avl tree of user quotas for a file system.
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.
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.
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.
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;
130 * smb_quota_fs_list
131 * list of quota trees; one per shared file system.
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 */
142 * smb_quota_zfs_handle_t
143 * handle to zfs library and dataset
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;
151 * smb_quota_zfs_arg_t
152 * arg passed to zfs callback when querying quota properties
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;
159 static void smb_quota_add_ctrldir(const char *);
160 static void smb_quota_remove_ctrldir(const char *);
162 static smb_quota_tree_t *smb_quota_tree_create(const char *);
163 static void smb_quota_tree_delete(smb_quota_tree_t *);
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 *);
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 *);
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 *);
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
192 * In order to display the quota properties tab, windows clients
193 * check for the existence of the quota control file.
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"
199 * Note: this line needs to have the same format as what acl_totext() returns.
201 #define SMB_QUOTA_CNTRL_PERM "everyone@:rw-p--aARWc--s:-------:allow"
204 * smb_quota_init
205 * Initialize the list to hold the quota trees.
207 void
208 smb_quota_init(void)
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;
217 (void) mutex_unlock(&smb_quota_list_mutex);
221 * smb_quota_fini
223 * Wait for each quota tree to not be in use (qt_refcnt == 1)
224 * then remove it from the list and delete it.
226 void
227 smb_quota_fini(void)
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;
235 (void) mutex_lock(&smb_quota_list_mutex);
236 smb_quota_shutdown = B_TRUE;
238 if (!smb_quota_list_init) {
239 (void) mutex_unlock(&smb_quota_list_mutex);
240 return;
243 (void) cond_broadcast(&smb_quota_list_condvar);
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);
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;
256 (void) mutex_unlock(&qtree->qt_mutex);
258 if (remove)
259 smb_quota_tree_delete(qtree);
261 qtree = qtree_next;
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;
274 if (list_is_empty(&smb_quota_fs_list)) {
275 list_destroy(&smb_quota_fs_list);
276 smb_quota_list_init = B_FALSE;
279 (void) mutex_unlock(&smb_quota_list_mutex);
283 * smb_quota_add_fs
285 * If there is not a quota tree representing the specified path,
286 * create one and add it to the list.
288 void
289 smb_quota_add_fs(const char *path)
291 smb_quota_tree_t *qtree;
293 (void) mutex_lock(&smb_quota_list_mutex);
295 if (!smb_quota_list_init || smb_quota_shutdown) {
296 (void) mutex_unlock(&smb_quota_list_mutex);
297 return;
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;
308 qtree = list_next(&smb_quota_fs_list, qtree);
311 if (qtree == NULL) {
312 qtree = smb_quota_tree_create(path);
313 if (qtree)
314 list_insert_head(&smb_quota_fs_list, (void *)qtree);
317 if (qtree)
318 smb_quota_add_ctrldir(path);
320 (void) mutex_unlock(&smb_quota_list_mutex);
324 * smb_quota_remove_fs
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).
331 void
332 smb_quota_remove_fs(const char *path)
334 smb_quota_tree_t *qtree;
335 boolean_t delete = B_FALSE;
337 (void) mutex_lock(&smb_quota_list_mutex);
339 if (!smb_quota_list_init || smb_quota_shutdown) {
340 (void) mutex_unlock(&smb_quota_list_mutex);
341 return;
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);
356 (void) mutex_unlock(&qtree->qt_mutex);
357 if (delete)
358 smb_quota_tree_delete(qtree);
359 break;
361 qtree = list_next(&smb_quota_fs_list, qtree);
363 (void) mutex_unlock(&smb_quota_list_mutex);
367 * smb_quota_query
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.
374 * Returns NT_STATUS codes.
376 uint32_t
377 smb_quota_query(smb_quota_query_t *request, smb_quota_response_t *reply)
379 uint32_t status;
380 smb_quota_tree_t *qtree;
381 smb_quota_query_op_t query_op = request->qq_query_op;
383 list_create(&reply->qr_quota_list, sizeof (smb_quota_t),
384 offsetof(smb_quota_t, q_list_node));
386 qtree = smb_quota_tree_lookup(request->qq_root_path);
387 if (qtree == NULL)
388 return (NT_STATUS_INVALID_PARAMETER);
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);
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;
413 smb_quota_tree_release(qtree);
415 return (status);
419 * smb_quota_set
421 * Set the list of quota entries.
423 uint32_t
424 smb_quota_set(smb_quota_set_t *request)
426 uint32_t status;
427 smb_quota_tree_t *qtree;
429 qtree = smb_quota_tree_lookup(request->qs_root_path);
430 if (qtree == NULL)
431 return (NT_STATUS_INVALID_PARAMETER);
433 status = smb_quota_zfs_set_quotas(qtree, request);
435 smb_quota_tree_set_expired(qtree);
436 smb_quota_tree_release(qtree);
438 return (status);
442 * smb_quota_free
444 * This method frees quota entries.
446 void
447 smb_quota_free(smb_quota_response_t *reply)
449 list_t *list = &reply->qr_quota_list;
450 smb_quota_t *quota;
452 while ((quota = list_head(list)) != NULL) {
453 list_remove(list, quota);
454 free(quota);
457 list_destroy(list);
461 * smb_quota_query_all
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.
468 * SMB_QUOTA_QUERY_STARTSID
469 * The query should start at the startsid, the first sid in
470 * request->qq_sid_list.
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.
478 * Returns NT_STATUS codes.
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)
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;
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);
515 if ((request->qq_single) && (request->qq_max_quota > 1))
516 request->qq_max_quota = 1;
518 quota_list = &reply->qr_quota_list;
519 count = 0;
520 while (quota) {
521 if (count >= request->qq_max_quota)
522 break;
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));
529 list_insert_tail(quota_list, quotal);
530 ++count;
532 quota = AVL_NEXT(avl_tree, quota);
535 return (NT_STATUS_SUCCESS);
539 * smb_quota_query_list
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.
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)
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;
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);
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);
574 list_insert_tail(quota_list, quotal);
575 sid = list_next(sid_list, sid);
578 return (NT_STATUS_SUCCESS);
582 * smb_quota_zfs_set_quotas
584 * This method sets the list of quota entries.
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).
593 static uint32_t
594 smb_quota_zfs_set_quotas(smb_quota_tree_t *qtree, smb_quota_set_t *request)
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;
605 status = smb_quota_zfs_init(request->qs_root_path, &zfs_hdl);
606 if (status != NT_STATUS_SUCCESS)
607 return (status);
609 quota_list = &request->qs_quota_list;
610 quota = list_head(quota_list);
612 while (quota) {
613 if ((quota->q_limit == SMB_QUOTA_UNLIMITED) ||
614 (quota->q_limit == (SMB_QUOTA_UNLIMITED - 1))) {
615 quota->q_limit = 0;
617 (void) snprintf(qlimit, SMB_QUOTA_CMD_LENGTH, "%llu",
618 quota->q_limit);
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;
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);
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;
655 quota = list_next(quota_list, quota);
658 smb_quota_zfs_fini(&zfs_hdl);
659 return (status);
663 * smb_quota_sidtype
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().
669 static uint32_t
670 smb_quota_sidtype(smb_quota_tree_t *qtree, char *sidstr)
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;
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);
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);
689 smb_sid_free(sid);
691 return (sidtype);
695 * smb_quota_getid
697 * Get the user/group id for the sid.
699 static int
700 smb_quota_getid(char *sidstr, uint32_t sidtype, uint32_t *id)
702 int rc = 0;
703 smb_sid_t *sid = NULL;
704 int idtype;
706 sid = smb_sid_fromstr(sidstr);
707 if (sid == NULL)
708 return (-1);
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;
724 if (rc == 0)
725 rc = smb_idmap_getid(sid, id, &idtype);
727 smb_sid_free(sid);
729 return (rc);
733 * smb_quota_tree_lookup
735 * Find the quota tree in smb_quota_fs_list.
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.
741 static smb_quota_tree_t *
742 smb_quota_tree_lookup(const char *path)
744 smb_quota_tree_t *qtree = NULL;
746 assert(path);
747 (void) mutex_lock(&smb_quota_list_mutex);
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);
756 (void) mutex_lock(&qtree->qt_mutex);
757 assert(qtree->qt_refcnt > 0);
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;
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;
773 ++(qtree->qt_refcnt);
774 qtree->qt_locked = B_TRUE;
775 (void) mutex_unlock(&qtree->qt_mutex);
776 break;
779 (void) mutex_unlock(&smb_quota_list_mutex);
780 return (qtree);
784 * smb_quota_tree_release
786 static void
787 smb_quota_tree_release(smb_quota_tree_t *qtree)
789 boolean_t delete;
791 (void) mutex_lock(&qtree->qt_mutex);
792 assert(qtree->qt_locked);
793 assert(qtree->qt_refcnt > 0);
795 --(qtree->qt_refcnt);
796 qtree->qt_locked = B_FALSE;
797 delete = (qtree->qt_refcnt == 0);
798 (void) mutex_unlock(&qtree->qt_mutex);
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);
808 * smb_quota_tree_match
810 * Determine if qtree represents the file system identified by path
812 static boolean_t
813 smb_quota_tree_match(smb_quota_tree_t *qtree, const char *path)
815 return (strncmp(qtree->qt_path, path, MAXPATHLEN) == 0);
819 * smb_quota_tree_create
821 * Create and initialize an smb_quota_tree_t structure
823 static smb_quota_tree_t *
824 smb_quota_tree_create(const char *path)
826 smb_quota_tree_t *qtree;
828 assert(MUTEX_HELD(&smb_quota_list_mutex));
830 qtree = calloc(sizeof (smb_quota_tree_t), 1);
831 if (qtree == NULL)
832 return (NULL);
834 qtree->qt_path = strdup(path);
835 if (qtree->qt_path == NULL) {
836 free(qtree);
837 return (NULL);
840 qtree->qt_timestamp = 0;
841 qtree->qt_locked = B_FALSE;
842 qtree->qt_refcnt = 1;
843 qtree->qt_sharecnt = 1;
845 avl_create(&qtree->qt_avl, smb_quota_sid_cmp,
846 sizeof (smb_quota_t), offsetof(smb_quota_t, q_avl_node));
848 ++smb_quota_tree_cnt;
849 return (qtree);
853 * smb_quota_tree_delete
855 * Free and delete the smb_quota_tree_t structure.
856 * qtree must have no users (refcnt == 0).
858 static void
859 smb_quota_tree_delete(smb_quota_tree_t *qtree)
861 void *cookie = NULL;
862 smb_quota_t *node;
864 assert(MUTEX_HELD(&smb_quota_list_mutex));
865 assert(qtree->qt_refcnt == 0);
867 while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
868 free(node);
869 avl_destroy(&qtree->qt_avl);
871 free(qtree->qt_path);
872 free(qtree);
874 --smb_quota_tree_cnt;
878 * smb_quota_sid_cmp
880 * Comparision function for nodes in an AVL tree which holds quota
881 * entries indexed by SID.
883 static int
884 smb_quota_sid_cmp(const void *l_arg, const void *r_arg)
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;
890 ret = strncasecmp(l_sid, r_sid, SMB_SID_STRSZ);
892 if (ret > 0)
893 return (1);
894 if (ret < 0)
895 return (-1);
896 return (0);
900 * smb_quota_tree_populate
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.
907 static uint32_t
908 smb_quota_tree_populate(smb_quota_tree_t *qtree)
910 void *cookie = NULL;
911 void *node;
912 uint32_t status;
914 assert(qtree->qt_locked);
916 if (!smb_quota_tree_expired(qtree))
917 return (NT_STATUS_SUCCESS);
919 while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
920 free(node);
922 status = smb_quota_zfs_get_quotas(qtree);
923 if (status != NT_STATUS_SUCCESS)
924 return (status);
926 qtree->qt_timestamp = time(NULL);
928 return (NT_STATUS_SUCCESS);
931 static boolean_t
932 smb_quota_tree_expired(smb_quota_tree_t *qtree)
934 time_t tnow = time(NULL);
935 return ((tnow - qtree->qt_timestamp) > SMB_QUOTA_REFRESH);
938 static void
939 smb_quota_tree_set_expired(smb_quota_tree_t *qtree)
941 qtree->qt_timestamp = 0;
945 * smb_quota_zfs_get_quotas
947 * Get user and group quotas from ZFS and use them to
948 * populate the quota tree.
950 static uint32_t
951 smb_quota_zfs_get_quotas(smb_quota_tree_t *qtree)
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;
958 status = smb_quota_zfs_init(qtree->qt_path, &zfs_hdl);
959 if (status != NT_STATUS_SUCCESS)
960 return (status);
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;
972 smb_quota_zfs_fini(&zfs_hdl);
973 return (status);
977 * smb_quota_zfs_callback
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.
986 static int
987 smb_quota_zfs_callback(void *arg, const char *domain, uid_t rid, uint64_t space)
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;
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);
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);
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;
1036 quota->q_thresh = quota->q_limit;
1038 return (0);
1042 * smb_quota_sidstr
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.
1048 static int
1049 smb_quota_sidstr(uint32_t id, zfs_userquota_prop_t qprop, char *sidstr)
1051 int idtype;
1052 smb_sid_t *sid;
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);
1067 if (smb_idmap_getsid(id, idtype, &sid) != IDMAP_SUCCESS)
1068 return (-1);
1070 smb_sid_tostr(sid, sidstr);
1071 smb_sid_free(sid);
1073 return (0);
1077 * smb_quota_zfs_init
1079 * Initialize zfs library and dataset handles
1081 static uint32_t
1082 smb_quota_zfs_init(const char *path, smb_quota_zfs_handle_t *zfs_hdl)
1084 char dataset[MAXPATHLEN];
1086 if (smb_getdataset(path, dataset, MAXPATHLEN) != 0)
1087 return (NT_STATUS_INVALID_PARAMETER);
1089 if ((zfs_hdl->z_lib = libzfs_init()) == NULL)
1090 return (NT_STATUS_INTERNAL_ERROR);
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);
1098 return (NT_STATUS_SUCCESS);
1102 * smb_quota_zfs_fini
1104 * Close zfs library and dataset handles
1106 static void
1107 smb_quota_zfs_fini(smb_quota_zfs_handle_t *zfs_hdl)
1109 zfs_close(zfs_hdl->z_fs);
1110 libzfs_fini(zfs_hdl->z_lib);
1114 * smb_quota_add_ctrldir
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.
1125 static void
1126 smb_quota_add_ctrldir(const char *path)
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;
1135 assert(path != NULL);
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;
1143 if ((dirfd = open(dir, O_RDONLY)) < 0) {
1144 if (qdir_created)
1145 (void) remove(dir);
1146 return;
1149 if (fgetattr(dirfd, XATTR_VIEW_READWRITE, &attr) != 0) {
1150 (void) close(dirfd);
1151 if (qdir_created)
1152 (void) remove(dir);
1153 return;
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;
1164 nvlist_free(attr);
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.
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;
1185 nvlist_free(attr);
1188 (void) close(dirfd);
1190 if (stat(file, &statbuf) != 0) {
1191 if ((newfd = creat(file, 0640)) < 0) {
1192 if (qdir_created)
1193 (void) remove(dir);
1194 return;
1196 (void) close(newfd);
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;
1207 (void) close(afd);
1209 if (acl_get(file, 0, &existing_aclp) == -1) {
1210 (void) unlink(file);
1211 if (qdir_created)
1212 (void) remove(dir);
1213 return;
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;
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;
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;
1242 free(acl_text);
1243 acl_free(aclp);
1247 * smb_quota_remove_ctrldir
1249 * Remove SMB_QUOTA_CNTRL_FILE and SMB_QUOTA_CNTRL_DIR.
1251 static void
1252 smb_quota_remove_ctrldir(const char *path)
1254 char dir[MAXPATHLEN], file[MAXPATHLEN];
1255 assert(path);
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);