/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <attr.h>
#include <unistd.h>
#include <libuutil.h>
#include <libzfs.h>
#include <assert.h>
#include <stddef.h>
#include <strings.h>
#include <errno.h>
#include <synch.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smb_idmap.h>
#include <mlsvc.h>
/*
* smb_quota subsystem interface - mlsvc.h
* ---------------------------------------
* Management of the smb_quota_fs_list (see below).
* smb_quota_init
* smb_quota_fini
* smb_quota_add_fs
* smb_quota_remove_fs
*
* smb_quota public interface - libmlsvc.h
* ---------------------------------------
* Handling of requests to query and set quota data on a filesystem.
* smb_quota_free - delete the quota list created in smb_quota_query
*/
/*
* Querying user & group quotas - smb_quota_query
*
* In order to fulfill the quota query requests that can be received
* from clients, it is required that the quota data can be provided in
* a well defined and consistent order, and that a request can specify
* at which quota entry to begin the query.
*
* Quota Tree
* Since the file system does not support the above, an avl tree is
* populated with the file system's user and group quota data, and
* then used to provide the data to respond to query requests. The
* avl tree is indexed by the SID.
* Each node of the avl tree is an smb_quota_t structure.
*
* Quota List
* There is a list of avl trees, one per file system.
* Each node in the list is an smb_quota_tree_t structure.
* The list is created via a call to smb_quota_init() when the library
* is initialized, and destroyed via a call to smb_quota_fini() when
* the library is fini'd.
*
* An avl tree for a specific file system is created and added to the
* list via a call to smb_quota_add_fs() when the file system is shared,
* and removed from the list via a call to smb_quota_remove_fs() when
* the file system is unshared.
*
* An avl tree is (re)populated, if required, whenever a quota request
* (EXCLUDING a resume request) is received for its filesystem. The
* avl tree is considered to be expired (needs to be repopulated) if
* either of the following have occurred since it was last (re)populated:
* - SMB_QUOTA_REFRESH seconds have elapsed OR
* - a quota set operation has been performed on its file system
*
* In order to perform a smb_quota_query/set operation on a file system
* the appropriate quota tree must be identified and locked via a call
* to smb_quota_tree_lookup(), The quota tree is locked (qt_locked == B_TRUE)
* until the caller releases it via a call to smb_quota_tree_release().
*/
/*
* smb_quota_tree_t
* Represents an avl tree of user quotas for a file system.
*
* qt_refcnt - a count of the number of users of the tree.
* qt_refcnt is also incremented and decremented when the tree is
* added to and removed from the quota list.
* The tree cannot be deleted until this count is zero.
*
* qt_sharecnt - a count of the shares of the file system which the
* tree represents. smb_quota_remove_fs() cannot remove the tree from
* removed from the quota list until this count is zero.
*
* qt_locked - B_TRUE if someone is currently using the tree, in
* which case a lookup will wait for the tree to become available.
*/
typedef struct smb_quota_tree {
char *qt_path;
/*
* smb_quota_fs_list
* list of quota trees; one per shared file system.
*/
/*
* smb_quota_zfs_handle_t
* handle to zfs library and dataset
*/
typedef struct smb_quota_zfs_handle {
/*
* smb_quota_zfs_arg_t
* arg passed to zfs callback when querying quota properties
*/
typedef struct smb_quota_zfs_arg {
static void smb_quota_add_ctrldir(const char *);
static void smb_quota_remove_ctrldir(const char *);
static smb_quota_tree_t *smb_quota_tree_create(const char *);
static void smb_quota_tree_delete(smb_quota_tree_t *);
static smb_quota_tree_t *smb_quota_tree_lookup(const char *);
static void smb_quota_tree_release(smb_quota_tree_t *);
static int smb_quota_sid_cmp(const void *, const void *);
static void smb_quota_tree_set_expired(smb_quota_tree_t *);
static void smb_quota_zfs_fini(smb_quota_zfs_handle_t *);
/*
* In order to display the quota properties tab, windows clients
* check for the existence of the quota control file.
*/
/*
* Note: this line needs to have the same format as what acl_totext() returns.
*/
/*
* smb_quota_init
* Initialize the list to hold the quota trees.
*/
void
smb_quota_init(void)
{
(void) mutex_lock(&smb_quota_list_mutex);
if (!smb_quota_list_init) {
}
(void) mutex_unlock(&smb_quota_list_mutex);
}
/*
* smb_quota_fini
*
* Wait for each quota tree to not be in use (qt_refcnt == 1)
* then remove it from the list and delete it.
*/
void
smb_quota_fini(void)
{
(void) mutex_lock(&smb_quota_list_mutex);
if (!smb_quota_list_init) {
(void) mutex_unlock(&smb_quota_list_mutex);
return;
}
(void) cond_broadcast(&smb_quota_list_condvar);
while (!list_is_empty(&smb_quota_fs_list)) {
if (remove) {
}
if (remove)
qtree = qtree_next;
}
if (!list_is_empty(&smb_quota_fs_list)) {
"quota shutdown timeout expired");
break;
}
}
}
if (list_is_empty(&smb_quota_fs_list)) {
}
(void) mutex_unlock(&smb_quota_list_mutex);
}
/*
* smb_quota_add_fs
*
* If there is not a quota tree representing the specified path,
* create one and add it to the list.
*/
void
{
(void) mutex_lock(&smb_quota_list_mutex);
if (!smb_quota_list_init || smb_quota_shutdown) {
(void) mutex_unlock(&smb_quota_list_mutex);
return;
}
++qtree->qt_sharecnt;
break;
}
}
if (qtree)
}
if (qtree)
(void) mutex_unlock(&smb_quota_list_mutex);
}
/*
* smb_quota_remove_fs
*
* If this is the last share that the quota tree represents
* (qtree->qt_sharecnt == 0) remove the qtree from the list.
* (qtree->qt_refcnt == 0).
*/
void
{
(void) mutex_lock(&smb_quota_list_mutex);
if (!smb_quota_list_init || smb_quota_shutdown) {
(void) mutex_unlock(&smb_quota_list_mutex);
return;
}
--qtree->qt_sharecnt;
if (qtree->qt_sharecnt == 0) {
}
if (delete)
break;
}
}
(void) mutex_unlock(&smb_quota_list_mutex);
}
/*
* smb_quota_query
*
* Request->qq_query_op determines whether to get quota entries
* for the specified SIDs (smb_quota_query_list) OR to get all
* quota entries, optionally starting at a specified SID.
*
* Returns NT_STATUS codes.
*/
{
return (NT_STATUS_INVALID_PARAMETER);
/* If NOT resuming a previous query all, refresh qtree if required */
if (status != NT_STATUS_SUCCESS) {
return (status);
}
}
switch (query_op) {
case SMB_QUOTA_QUERY_SIDLIST:
break;
case SMB_QUOTA_QUERY_STARTSID:
case SMB_QUOTA_QUERY_ALL:
break;
default:
break;
}
return (status);
}
/*
* smb_quota_set
*
* Set the list of quota entries.
*/
{
return (NT_STATUS_INVALID_PARAMETER);
return (status);
}
/*
* smb_quota_free
*
* This method frees quota entries.
*/
void
{
}
}
/*
* smb_quota_query_all
*
* Query quotas sequentially from tree, optionally starting at a
* specified sid. If request->qq_single is TRUE only one quota
* should be returned, otherwise up to request->qq_max_quota
* should be returned.
*
* SMB_QUOTA_QUERY_STARTSID
* The query should start at the startsid, the first sid in
* request->qq_sid_list.
*
* SMQ_QUOTA_QUERY_ALL
* If request->qq_restart the query should restart at the start
* of the avl tree. Otherwise the first sid in request->qq_sid_list
* is the resume sid and the query should start at the tree entry
* after the one it refers to.
*
* Returns NT_STATUS codes.
*/
static uint32_t
{
/* find starting sid */
return (NT_STATUS_INVALID_PARAMETER);
} else if (request->qq_restart) {
return (NT_STATUS_NO_MORE_ENTRIES);
} else {
return (NT_STATUS_INVALID_PARAMETER);
return (NT_STATUS_NO_MORE_ENTRIES);
}
count = 0;
while (quota) {
break;
return (NT_STATUS_NO_MEMORY);
++count;
}
return (NT_STATUS_SUCCESS);
}
/*
* smb_quota_query_list
*
* Iterate through request sid list querying the avl tree for each.
* Insert an entry in the reply quota list for each sid.
* For any sid that cannot be found in the avl tree, the reply
* quota list entry should contain zeros.
*/
static uint32_t
{
while (sid) {
return (NT_STATUS_NO_MEMORY);
if (quota) {
} else {
}
}
return (NT_STATUS_SUCCESS);
}
/*
* smb_quota_zfs_set_quotas
*
* This method sets the list of quota entries.
*
* A quota list or threshold value of SMB_QUOTA_UNLIMITED means that
* the user / group does not have a quota limit. In ZFS this maps to
* 0 (none).
* A quota list or threshold value of (SMB_QUOTA_UNLIMITED - 1) means
* that the user / group quota should be removed. In ZFS this maps to
* 0 (none).
*/
static uint32_t
{
if (status != NT_STATUS_SUCCESS)
return (status);
while (quota) {
}
switch (sidtype) {
case SidTypeUser:
typestr = "userquota";
break;
case SidTypeWellKnownGroup:
case SidTypeGroup:
case SidTypeAlias:
typestr = "groupquota";
break;
default:
continue;
}
!(IDMAP_ID_IS_EPHEMERAL(id))) {
} else {
}
errno = 0;
break;
}
}
return (status);
}
/*
* smb_quota_sidtype
*
* Determine the type of the sid. If the sid exists in
* the qtree get its type from there, otherwise do an
* lsa_lookup_sid().
*/
static uint32_t
{
if (quota)
}
}
return (sidtype);
}
/*
* smb_quota_getid
*
*/
static int
{
int rc = 0;
int idtype;
return (-1);
switch (sidtype) {
case SidTypeUser:
break;
case SidTypeWellKnownGroup:
case SidTypeGroup:
case SidTypeAlias:
break;
default:
rc = -1;
break;
}
if (rc == 0)
return (rc);
}
/*
* smb_quota_tree_lookup
*
* Find the quota tree in smb_quota_fs_list.
*
* If the tree is found but is locked, waits for it to become available.
* If the tree is available, locks it and returns it.
* Otherwise, returns NULL.
*/
static smb_quota_tree_t *
{
(void) mutex_lock(&smb_quota_list_mutex);
if (!smb_quota_list_init || smb_quota_shutdown) {
(void) mutex_unlock(&smb_quota_list_mutex);
return (NULL);
}
continue;
}
(void) cond_wait(&smb_quota_list_condvar,
continue;
}
break;
};
(void) mutex_unlock(&smb_quota_list_mutex);
return (qtree);
}
/*
* smb_quota_tree_release
*/
static void
{
(void) mutex_lock(&smb_quota_list_mutex);
if (delete)
(void) cond_broadcast(&smb_quota_list_condvar);
(void) mutex_unlock(&smb_quota_list_mutex);
}
/*
* smb_quota_tree_match
*
* Determine if qtree represents the file system identified by path
*/
static boolean_t
{
}
/*
* smb_quota_tree_create
*
* Create and initialize an smb_quota_tree_t structure
*/
static smb_quota_tree_t *
{
return (NULL);
return (NULL);
}
qtree->qt_timestamp = 0;
return (qtree);
}
/*
* smb_quota_tree_delete
*
* Free and delete the smb_quota_tree_t structure.
* qtree must have no users (refcnt == 0).
*/
static void
{
}
/*
* smb_quota_sid_cmp
*
* Comparision function for nodes in an AVL tree which holds quota
* entries indexed by SID.
*/
static int
{
int ret;
if (ret > 0)
return (1);
if (ret < 0)
return (-1);
return (0);
}
/*
* smb_quota_tree_populate
*
* If the quota tree needs to be (re)populated:
* - delete the qtree's contents
* - repopulate the qtree from zfs
* - set the qtree's timestamp.
*/
static uint32_t
{
void *node;
if (!smb_quota_tree_expired(qtree))
return (NT_STATUS_SUCCESS);
if (status != NT_STATUS_SUCCESS)
return (status);
return (NT_STATUS_SUCCESS);
}
static boolean_t
{
}
static void
{
qtree->qt_timestamp = 0;
}
/*
* smb_quota_zfs_get_quotas
*
* Get user and group quotas from ZFS and use them to
* populate the quota tree.
*/
static uint32_t
{
if (status != NT_STATUS_SUCCESS)
return (status);
for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
smb_quota_zfs_callback, &arg) != 0) {
break;
}
}
return (status);
}
/*
* smb_quota_zfs_callback
*
* Find or create a node in the avl tree (arg->qa_avl) that matches
* the SID derived from domain and rid. If no domain is specified,
* lookup the sid (smb_quota_sidstr()).
* Populate the node.
* The property type (arg->qa_prop) determines which property 'space'
* refers to.
*/
static int
{
return (0);
} else {
}
return (NT_STATUS_NO_MEMORY);
}
switch (qprop) {
case ZFS_PROP_USERUSED:
break;
case ZFS_PROP_GROUPUSED:
break;
case ZFS_PROP_USERQUOTA:
break;
case ZFS_PROP_GROUPQUOTA:
break;
default:
break;
}
return (0);
}
/*
* smb_quota_sidstr
*
* Use idmap to get the sid for the specified id and return
* the string version of the sid in sidstr.
* sidstr must be a buffer of at least SMB_SID_STRSZ.
*/
static int
{
int idtype;
switch (qprop) {
case ZFS_PROP_USERUSED:
case ZFS_PROP_USERQUOTA:
break;
case ZFS_PROP_GROUPUSED:
case ZFS_PROP_GROUPQUOTA:
break;
default:
return (-1);
}
return (-1);
return (0);
}
/*
* smb_quota_zfs_init
*
* Initialize zfs library and dataset handles
*/
static uint32_t
{
return (NT_STATUS_INVALID_PARAMETER);
return (NT_STATUS_INTERNAL_ERROR);
return (NT_STATUS_ACCESS_DENIED);
}
return (NT_STATUS_SUCCESS);
}
/*
* smb_quota_zfs_fini
*
* Close zfs library and dataset handles
*/
static void
{
}
/*
* smb_quota_add_ctrldir
*
* In order to display the quota properties tab, windows clients
* check for the existence of the quota control file, created
* here as follows:
* - Create SMB_QUOTA_CNTRL_DIR directory (with A_HIDDEN & A_SYSTEM
* attributes).
* - Create the SMB_QUOTA_CNTRL_FILE file (with extended attribute
* SMB_QUOTA_CNTRL_INDEX_XATTR) in the SMB_QUOTA_CNTRL_DIR directory.
* - Set the acl of SMB_QUOTA_CNTRL_FILE file to SMB_QUOTA_CNTRL_PERM.
*/
static void
{
return;
if (qdir_created)
return;
}
if (qdir_created)
return;
}
if (qdir_created)
return;
}
/*
* Before setting attr or acl we check if the they have already been
* set to what we want. If so we could be dealing with a received
* snapshot and setting these is not needed.
*/
if (!prop_hidden || !prop_sys) {
if ((nvlist_add_boolean_value(
if (qdir_created)
return;
}
}
}
if (qdir_created)
return;
}
}
0640);
if (afd == -1) {
if (qdir_created)
return;
}
if (qdir_created)
return;
}
if (qdir_created)
return;
}
if (qdir_created)
return;
}
if (qdir_created)
return;
}
}
}
/*
* smb_quota_remove_ctrldir
*
* Remove SMB_QUOTA_CNTRL_FILE and SMB_QUOTA_CNTRL_DIR.
*/
static void
{
}