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 2018 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 ((zfs_hdl->z_lib = libzfs_init()) == NULL) 1087 return (NT_STATUS_INTERNAL_ERROR); 1088 1089 if (smb_getdataset(zfs_hdl->z_lib, path, dataset, MAXPATHLEN) != 0) { 1090 libzfs_fini(zfs_hdl->z_lib); 1091 return (NT_STATUS_INVALID_PARAMETER); 1092 } 1093 1094 zfs_hdl->z_fs = zfs_open(zfs_hdl->z_lib, dataset, ZFS_TYPE_DATASET); 1095 if (zfs_hdl->z_fs == NULL) { 1096 libzfs_fini(zfs_hdl->z_lib); 1097 return (NT_STATUS_ACCESS_DENIED); 1098 } 1099 1100 return (NT_STATUS_SUCCESS); 1101 } 1102 1103 /* 1104 * smb_quota_zfs_fini 1105 * 1106 * Close zfs library and dataset handles 1107 */ 1108 static void 1109 smb_quota_zfs_fini(smb_quota_zfs_handle_t *zfs_hdl) 1110 { 1111 zfs_close(zfs_hdl->z_fs); 1112 libzfs_fini(zfs_hdl->z_lib); 1113 } 1114 1115 /* 1116 * smb_quota_add_ctrldir 1117 * 1118 * In order to display the quota properties tab, windows clients 1119 * check for the existence of the quota control file, created 1120 * here as follows: 1121 * - Create SMB_QUOTA_CNTRL_DIR directory (with A_HIDDEN & A_SYSTEM 1122 * attributes). 1123 * - Create the SMB_QUOTA_CNTRL_FILE file (with extended attribute 1124 * SMB_QUOTA_CNTRL_INDEX_XATTR) in the SMB_QUOTA_CNTRL_DIR directory. 1125 * - Set the acl of SMB_QUOTA_CNTRL_FILE file to SMB_QUOTA_CNTRL_PERM. 1126 */ 1127 static void 1128 smb_quota_add_ctrldir(const char *path) 1129 { 1130 int newfd, dirfd, afd; 1131 nvlist_t *attr; 1132 char dir[MAXPATHLEN], file[MAXPATHLEN], *acl_text; 1133 acl_t *aclp, *existing_aclp; 1134 boolean_t qdir_created, prop_hidden = B_FALSE, prop_sys = B_FALSE; 1135 struct stat statbuf; 1136 1137 assert(path != NULL); 1138 1139 (void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR); 1140 (void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE); 1141 if ((mkdir(dir, 0750) < 0) && (errno != EEXIST)) 1142 return; 1143 qdir_created = (errno == EEXIST) ? B_FALSE : B_TRUE; 1144 1145 if ((dirfd = open(dir, O_RDONLY)) < 0) { 1146 if (qdir_created) 1147 (void) remove(dir); 1148 return; 1149 } 1150 1151 if (fgetattr(dirfd, XATTR_VIEW_READWRITE, &attr) != 0) { 1152 (void) close(dirfd); 1153 if (qdir_created) 1154 (void) remove(dir); 1155 return; 1156 } 1157 1158 if ((nvlist_lookup_boolean_value(attr, A_HIDDEN, &prop_hidden) != 0) || 1159 (nvlist_lookup_boolean_value(attr, A_SYSTEM, &prop_sys) != 0)) { 1160 nvlist_free(attr); 1161 (void) close(dirfd); 1162 if (qdir_created) 1163 (void) remove(dir); 1164 return; 1165 } 1166 nvlist_free(attr); 1167 1168 /* 1169 * Before setting attr or acl we check if the they have already been 1170 * set to what we want. If so we could be dealing with a received 1171 * snapshot and setting these is not needed. 1172 */ 1173 1174 if (!prop_hidden || !prop_sys) { 1175 if (nvlist_alloc(&attr, NV_UNIQUE_NAME, 0) == 0) { 1176 if ((nvlist_add_boolean_value( 1177 attr, A_HIDDEN, 1) != 0) || 1178 (nvlist_add_boolean_value( 1179 attr, A_SYSTEM, 1) != 0) || 1180 (fsetattr(dirfd, XATTR_VIEW_READWRITE, attr))) { 1181 nvlist_free(attr); 1182 (void) close(dirfd); 1183 if (qdir_created) 1184 (void) remove(dir); 1185 return; 1186 } 1187 } 1188 nvlist_free(attr); 1189 } 1190 1191 (void) close(dirfd); 1192 1193 if (stat(file, &statbuf) != 0) { 1194 if ((newfd = creat(file, 0640)) < 0) { 1195 if (qdir_created) 1196 (void) remove(dir); 1197 return; 1198 } 1199 (void) close(newfd); 1200 } 1201 1202 afd = attropen(file, SMB_QUOTA_CNTRL_INDEX_XATTR, O_RDWR | O_CREAT, 1203 0640); 1204 if (afd == -1) { 1205 (void) unlink(file); 1206 if (qdir_created) 1207 (void) remove(dir); 1208 return; 1209 } 1210 (void) close(afd); 1211 1212 if (acl_get(file, 0, &existing_aclp) == -1) { 1213 (void) unlink(file); 1214 if (qdir_created) 1215 (void) remove(dir); 1216 return; 1217 } 1218 1219 acl_text = acl_totext(existing_aclp, ACL_COMPACT_FMT); 1220 acl_free(existing_aclp); 1221 if (acl_text == NULL) { 1222 (void) unlink(file); 1223 if (qdir_created) 1224 (void) remove(dir); 1225 return; 1226 } 1227 1228 aclp = NULL; 1229 if (strcmp(acl_text, SMB_QUOTA_CNTRL_PERM) != 0) { 1230 if (acl_fromtext(SMB_QUOTA_CNTRL_PERM, &aclp) != 0) { 1231 free(acl_text); 1232 (void) unlink(file); 1233 if (qdir_created) 1234 (void) remove(dir); 1235 return; 1236 } 1237 if (acl_set(file, aclp) == -1) { 1238 free(acl_text); 1239 (void) unlink(file); 1240 if (qdir_created) 1241 (void) remove(dir); 1242 acl_free(aclp); 1243 return; 1244 } 1245 acl_free(aclp); 1246 } 1247 free(acl_text); 1248 } 1249 1250 /* 1251 * smb_quota_remove_ctrldir 1252 * 1253 * Remove SMB_QUOTA_CNTRL_FILE and SMB_QUOTA_CNTRL_DIR. 1254 */ 1255 static void 1256 smb_quota_remove_ctrldir(const char *path) 1257 { 1258 char dir[MAXPATHLEN], file[MAXPATHLEN]; 1259 assert(path); 1260 1261 (void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR); 1262 (void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE); 1263 (void) unlink(file); 1264 (void) remove(dir); 1265 } 1266