1#include <unistd.h> 2#include <stdlib.h> 3#include "config.h" 4#include "cunit/cunit.h" 5#include "imap/quota.h" 6#include "xmalloc.h" 7#include "retry.h" 8#include "imap/global.h" 9#include "imap/imap_err.h" 10#include "cyrusdb.h" 11#include "libcyr_cfg.h" 12#include "libconfig.h" 13#include "hash.h" 14 15#define DBDIR "test-mb-dbdir" 16#define QUOTAROOT "user.smurf" 17#define QUOTAROOT_NONEXISTANT "no-such-quotaroot" 18 19static char *backend = CUNIT_PARAM("quotalegacy,skiplist"); 20 21static void test_names(void) 22{ 23 int r; 24 25 CU_ASSERT_STRING_EQUAL(quota_names[QUOTA_STORAGE], 26 "STORAGE"); 27 r = quota_name_to_resource("STORAGE"); 28 CU_ASSERT_EQUAL(r, QUOTA_STORAGE); 29 r = quota_name_to_resource("storage"); 30 CU_ASSERT_EQUAL(r, QUOTA_STORAGE); 31 r = quota_name_to_resource("StOrAge"); 32 CU_ASSERT_EQUAL(r, QUOTA_STORAGE); 33 34 CU_ASSERT_STRING_EQUAL(quota_names[QUOTA_ANNOTSTORAGE], 35 "X-ANNOTATION-STORAGE"); 36 r = quota_name_to_resource("X-ANNOTATION-STORAGE"); 37 CU_ASSERT_EQUAL(r, QUOTA_ANNOTSTORAGE); 38 r = quota_name_to_resource("x-annotation-storage"); 39 CU_ASSERT_EQUAL(r, QUOTA_ANNOTSTORAGE); 40 r = quota_name_to_resource("X-AnNotAtiOn-StOragE"); 41 CU_ASSERT_EQUAL(r, QUOTA_ANNOTSTORAGE); 42 43 r = quota_name_to_resource("nonesuch"); 44 CU_ASSERT_EQUAL(r, -1); 45 46} 47 48/* 49 * Trying to read quota for a quotaroot which is NULL, an empty string, 50 * or a string which is not found in the database. 51 */ 52static void test_read_no_root(void) 53{ 54 struct quota q; 55 struct txn *txn = NULL; 56 int r; 57 58 q.root = NULL; 59 r = quota_read(&q, NULL, 0); 60 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 61 62 q.root = ""; 63 r = quota_read(&q, NULL, 0); 64 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 65 66 /* Also, for NULL or "" no txn is started */ 67 q.root = NULL; 68 r = quota_read(&q, &txn, 1); 69 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 70 CU_ASSERT_PTR_NULL(txn); 71 72 q.root = ""; 73 r = quota_read(&q, &txn, 1); 74 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 75 CU_ASSERT_PTR_NULL(txn); 76 77 q.root = QUOTAROOT_NONEXISTANT; 78 r = quota_read(&q, NULL, 0); 79 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 80} 81 82static void test_write_no_root(void) 83{ 84 struct quota q; 85 struct txn *txn = NULL; 86 int r; 87 88 q.root = NULL; 89 r = quota_write(&q, NULL); 90 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 91 92 q.root = ""; 93 r = quota_write(&q, NULL); 94 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 95 96 /* Also, for NULL or "" no txn is started */ 97 q.root = NULL; 98 r = quota_write(&q, &txn); 99 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 100 CU_ASSERT_PTR_NULL(txn); 101 102 q.root = ""; 103 r = quota_write(&q, &txn); 104 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 105 CU_ASSERT_PTR_NULL(txn); 106} 107 108 109static void test_read_write(void) 110{ 111 struct quota q; 112 struct quota q2; 113 struct txn *txn = NULL; 114 struct txn *oldtxn = NULL; 115 int res; 116 int r; 117 118 memset(&q, 0, sizeof(q)); 119 q.root = QUOTAROOT; 120 121 r = quota_read(&q, &txn, 1); 122 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 123 /* we went to the db and started a transaction */ 124 CU_ASSERT_PTR_NOT_NULL(txn); 125 126 q.useds[QUOTA_STORAGE] = 12345; 127 q.limits[QUOTA_STORAGE] = 678; 128 q.useds[QUOTA_MESSAGE] = 2345; 129 q.limits[QUOTA_MESSAGE] = 78; 130 q.useds[QUOTA_ANNOTSTORAGE] = 345; 131 q.limits[QUOTA_ANNOTSTORAGE] = 8; 132 133 oldtxn = txn; 134 r = quota_write(&q, &txn); 135 CU_ASSERT_EQUAL(r, 0); 136 CU_ASSERT_PTR_NOT_NULL(txn); 137 CU_ASSERT_PTR_EQUAL(oldtxn, txn); 138 139 /* reading in the same txn gets the new values */ 140 memset(&q2, 0, sizeof(q2)); 141 q2.root = QUOTAROOT; 142 r = quota_read(&q2, &txn, 0); 143 CU_ASSERT_EQUAL(r, 0); 144 for (res = 0; res < QUOTA_NUMRESOURCES; res++) { 145 CU_ASSERT_EQUAL(q2.useds[res], q.useds[res]); 146 CU_ASSERT_EQUAL(q2.limits[res], q.limits[res]); 147 } 148 149 /* commit the txn */ 150 quota_commit(&txn); 151 CU_ASSERT_PTR_NULL(txn); 152 153 /* reading in a new txn gets the new values */ 154 memset(&q2, 0, sizeof(q2)); 155 q2.root = QUOTAROOT; 156 r = quota_read(&q2, &txn, 0); 157 CU_ASSERT_EQUAL(r, 0); 158 CU_ASSERT_PTR_NOT_NULL(txn); 159 for (res = 0; res < QUOTA_NUMRESOURCES; res++) { 160 CU_ASSERT_EQUAL(q2.useds[res], q.useds[res]); 161 CU_ASSERT_EQUAL(q2.limits[res], q.limits[res]); 162 } 163 164 quota_commit(&txn); 165 CU_ASSERT_PTR_NULL(txn); 166} 167 168static void test_abort(void) 169{ 170 struct quota q; 171 struct quota q2; 172 struct txn *txn = NULL; 173 struct txn *oldtxn = NULL; 174 int r; 175 176 memset(&q, 0, sizeof(q)); 177 q.root = QUOTAROOT; 178 179 r = quota_read(&q, &txn, 1); 180 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 181 /* we went to the db and started a transaction */ 182 CU_ASSERT_PTR_NOT_NULL(txn); 183 184 q.useds[QUOTA_STORAGE] = 12345; 185 q.limits[QUOTA_STORAGE] = 678; 186 187 oldtxn = txn; 188 r = quota_write(&q, &txn); 189 CU_ASSERT_EQUAL(r, 0); 190 CU_ASSERT_PTR_NOT_NULL(txn); 191 CU_ASSERT_PTR_EQUAL(oldtxn, txn); 192 193 /* reading in the same txn gets the new values */ 194 memset(&q2, 0, sizeof(q2)); 195 q2.root = QUOTAROOT; 196 r = quota_read(&q2, &txn, 0); 197 CU_ASSERT_EQUAL(r, 0); 198 CU_ASSERT_EQUAL(q2.useds[QUOTA_STORAGE], q.useds[QUOTA_STORAGE]); 199 CU_ASSERT_EQUAL(q2.limits[QUOTA_STORAGE], q.limits[QUOTA_STORAGE]); 200 201 /* abort the txn */ 202 quota_abort(&txn); 203 CU_ASSERT_PTR_NULL(txn); 204 205 /* reading in a new txn gets no result */ 206 memset(&q2, 0, sizeof(q2)); 207 q2.root = QUOTAROOT; 208 r = quota_read(&q2, &txn, 0); 209 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 210 CU_ASSERT_PTR_NOT_NULL(txn); 211 212 quota_commit(&txn); 213 CU_ASSERT_PTR_NULL(txn); 214} 215 216static void test_abort2(void) 217{ 218 struct quota q; 219 struct quota oldq; 220 struct quota q2; 221 struct txn *txn = NULL; 222 int r; 223 224 memset(&q, 0, sizeof(q)); 225 q.root = QUOTAROOT; 226 227 r = quota_read(&q, &txn, 1); 228 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 229 /* we went to the db and started a transaction */ 230 CU_ASSERT_PTR_NOT_NULL(txn); 231 232 q.useds[QUOTA_STORAGE] = 12345; 233 q.limits[QUOTA_STORAGE] = 678; 234 235 r = quota_write(&q, &txn); 236 CU_ASSERT_EQUAL(r, 0); 237 CU_ASSERT_PTR_NOT_NULL(txn); 238 239 /* commit some old values */ 240 quota_commit(&txn); 241 CU_ASSERT_PTR_NULL(txn); 242 243 /* write some new values */ 244 oldq = q; 245 q.useds[QUOTA_STORAGE] = 23456; 246 q.limits[QUOTA_STORAGE] = 789; 247 r = quota_write(&q, &txn); 248 CU_ASSERT_EQUAL(r, 0); 249 250 /* reading in the same txn gets the new values */ 251 memset(&q2, 0, sizeof(q2)); 252 q2.root = QUOTAROOT; 253 r = quota_read(&q2, &txn, 0); 254 CU_ASSERT_EQUAL(r, 0); 255 CU_ASSERT_EQUAL(q2.useds[QUOTA_STORAGE], q.useds[QUOTA_STORAGE]); 256 CU_ASSERT_EQUAL(q2.limits[QUOTA_STORAGE], q.limits[QUOTA_STORAGE]); 257 258 /* abort the txn */ 259 quota_abort(&txn); 260 CU_ASSERT_PTR_NULL(txn); 261 262 /* reading in a new txn gets old values */ 263 memset(&q2, 0, sizeof(q2)); 264 q2.root = QUOTAROOT; 265 r = quota_read(&q2, &txn, 0); 266 CU_ASSERT_EQUAL(r, 0); 267 CU_ASSERT_PTR_NOT_NULL(txn); 268 CU_ASSERT_EQUAL(q2.useds[QUOTA_STORAGE], oldq.useds[QUOTA_STORAGE]); 269 CU_ASSERT_EQUAL(q2.limits[QUOTA_STORAGE], oldq.limits[QUOTA_STORAGE]); 270 271 quota_commit(&txn); 272 CU_ASSERT_PTR_NULL(txn); 273} 274 275static void test_check_use(void) 276{ 277 struct quota q; 278 unsigned int i; 279 int r; 280 281 memset(&q, 0, sizeof(q)); 282 q.root = QUOTAROOT; 283 q.limits[QUOTA_STORAGE] = 100; 284 285 for (i = 1 ; i <= 63 ; i++) 286 { 287 quota_t diff = (1ULL<<i)-1; 288 r = quota_check(&q, QUOTA_STORAGE, diff); 289 CU_ASSERT_EQUAL(r, (diff > 100*1024 ? IMAP_QUOTA_EXCEEDED : 0)); 290 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 291 } 292 293 r = quota_check(&q, QUOTA_STORAGE, 10*1024); 294 CU_ASSERT_EQUAL(r, 0); 295 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 296 297 quota_use(&q, QUOTA_STORAGE, 10*1024); 298 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 10*1024); 299 300 r = quota_check(&q, QUOTA_STORAGE, 80*1024); 301 CU_ASSERT_EQUAL(r, 0); 302 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 10*1024); 303 304 quota_use(&q, QUOTA_STORAGE, 80*1024); 305 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], (10+80)*1024); 306 307 r = quota_check(&q, QUOTA_STORAGE, 15*1024); 308 CU_ASSERT_EQUAL(r, IMAP_QUOTA_EXCEEDED); 309 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], (10+80)*1024); 310 311 /* test the zero limit */ 312 memset(&q, 0, sizeof(q)); 313 q.root = QUOTAROOT; 314 q.limits[QUOTA_STORAGE] = 0; 315 316 r = quota_check(&q, QUOTA_STORAGE, 15*1024); 317 CU_ASSERT_EQUAL(r, IMAP_QUOTA_EXCEEDED); 318 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 319 320 r = quota_check(&q, QUOTA_STORAGE, 1); 321 CU_ASSERT_EQUAL(r, IMAP_QUOTA_EXCEEDED); 322 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 323 324 /* test the special limit QUOTA_UNLIMITED */ 325 memset(&q, 0, sizeof(q)); 326 q.root = QUOTAROOT; 327 q.limits[QUOTA_STORAGE] = QUOTA_UNLIMITED; 328 329 for (i = 1 ; i <= 63 ; i++) 330 { 331 quota_t diff = (1ULL<<i)-1; 332 r = quota_check(&q, QUOTA_STORAGE, diff); 333 CU_ASSERT_EQUAL(r, 0); 334 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 335 } 336 337 /* test negative diffs in quota_check */ 338 memset(&q, 0, sizeof(q)); 339 q.root = QUOTAROOT; 340 q.useds[QUOTA_STORAGE] = 80*1024; /* used 80 KiB */ 341 q.limits[QUOTA_STORAGE] = 100; /* limit 100 KiB */ 342 343 r = quota_check(&q, QUOTA_STORAGE, -1); 344 CU_ASSERT_EQUAL(r, 0); 345 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 80*1024); 346 347 r = quota_check(&q, QUOTA_STORAGE, -10*1024); 348 CU_ASSERT_EQUAL(r, 0); 349 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 80*1024); 350 351 r = quota_check(&q, QUOTA_STORAGE, -80*1024); 352 CU_ASSERT_EQUAL(r, 0); 353 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 80*1024); 354 355 r = quota_check(&q, QUOTA_STORAGE, -1000*1024); 356 CU_ASSERT_EQUAL(r, 0); 357 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 80*1024); 358 359 /* test negative diffs in quota_use */ 360 memset(&q, 0, sizeof(q)); 361 q.root = QUOTAROOT; 362 q.useds[QUOTA_STORAGE] = 80*1024; /* used 80 KiB */ 363 q.limits[QUOTA_STORAGE] = 100; /* limit 100 KiB */ 364 365 quota_use(&q, QUOTA_STORAGE, -1); 366 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 80*1024-1); 367 368 q.useds[QUOTA_STORAGE] = 80*1024; /* used 80 KiB */ 369 quota_use(&q, QUOTA_STORAGE, -10*1024); 370 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 70*1024); 371 372 q.useds[QUOTA_STORAGE] = 80*1024; /* used 80 KiB */ 373 quota_use(&q, QUOTA_STORAGE, -80*1024); 374 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); 375 376 /* test underflow in quota_use */ 377 CU_SYSLOG_MATCH("Quota underflow"); 378 q.useds[QUOTA_STORAGE] = 80*1024; /* used 80 KiB */ 379 quota_use(&q, QUOTA_STORAGE, -1000*1024); 380 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 0); /* clamped */ 381 CU_ASSERT_SYSLOG(/*all*/0, 1); /* whined */ 382} 383 384static void test_check_overquota(void) 385{ 386 struct quota q; 387 388 memset(&q, 0, sizeof(q)); 389 q.root = QUOTAROOT; 390 q.limits[QUOTA_STORAGE] = 100; 391 q.limits[QUOTA_MESSAGE] = 2; 392 393 /* test not overquota */ 394 quota_use(&q, QUOTA_STORAGE, 10*1024); 395 quota_use(&q, QUOTA_MESSAGE, 1); 396 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 10*1024); 397 CU_ASSERT_EQUAL(q.useds[QUOTA_MESSAGE], 1); 398 CU_ASSERT_EQUAL(0, quota_is_overquota(&q, QUOTA_STORAGE, NULL)); 399 CU_ASSERT_EQUAL(0, quota_is_overquota(&q, QUOTA_MESSAGE, NULL)); 400 401 /* test now overquota */ 402 quota_use(&q, QUOTA_STORAGE, 90*1024); 403 quota_use(&q, QUOTA_MESSAGE, 1); 404 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 100*1024); 405 CU_ASSERT_EQUAL(q.useds[QUOTA_MESSAGE], 2); 406 CU_ASSERT_EQUAL(1, quota_is_overquota(&q, QUOTA_STORAGE, NULL)); 407 CU_ASSERT_EQUAL(1, quota_is_overquota(&q, QUOTA_MESSAGE, NULL)); 408 409 /* test under quota */ 410 quota_use(&q, QUOTA_STORAGE, -10*1024); 411 quota_use(&q, QUOTA_MESSAGE, -1); 412 CU_ASSERT_EQUAL(q.useds[QUOTA_STORAGE], 90*1024); 413 CU_ASSERT_EQUAL(q.useds[QUOTA_MESSAGE], 1); 414 CU_ASSERT_EQUAL(0, quota_is_overquota(&q, QUOTA_STORAGE, NULL)); 415 CU_ASSERT_EQUAL(0, quota_is_overquota(&q, QUOTA_MESSAGE, NULL)); 416} 417 418static void test_update_useds(void) 419{ 420 struct quota q; 421 struct quota q2; 422 struct txn *txn = NULL; 423 int res; 424 quota_t quota_diff[QUOTA_NUMRESOURCES]; 425 int r; 426 427 memset(&q, 0, sizeof(q)); 428 q.root = QUOTAROOT; 429 memset(quota_diff, 0, sizeof(quota_diff)); 430 431 /* updating a NULL or empty or non-existant root returns the error */ 432 quota_diff[QUOTA_STORAGE] = 10*1024; 433 quota_diff[QUOTA_MESSAGE] = 2; 434 quota_diff[QUOTA_ANNOTSTORAGE] = 1*1024; 435 r = quota_update_useds(NULL, quota_diff, NULL); 436 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 437 438 r = quota_update_useds("", quota_diff, NULL); 439 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 440 441 r = quota_update_useds(QUOTAROOT_NONEXISTANT, quota_diff, NULL); 442 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 443 444 /* set limits */ 445 q.limits[QUOTA_STORAGE] = 100; /* limit storage to 100 KiB */ 446 q.limits[QUOTA_MESSAGE] = 20; /* limit messages to 20 */ 447 q.limits[QUOTA_ANNOTSTORAGE] = 10; /* limit annotations storage to 10 KiB */ 448 r = quota_write(&q, &txn); 449 CU_ASSERT_EQUAL(r, 0); 450 CU_ASSERT_PTR_NOT_NULL(txn); 451 quota_commit(&txn); 452 CU_ASSERT_PTR_NULL(txn); 453 454#define TESTCASE(d0, d1, d2, e0, e1, e2) { \ 455 static const quota_t diff[QUOTA_NUMRESOURCES] = { d0, d1, d2 }; \ 456 static const quota_t expused[QUOTA_NUMRESOURCES] = { e0, e1, e2 }; \ 457 r = quota_update_useds(QUOTAROOT, diff, NULL); \ 458 CU_ASSERT_EQUAL(r, 0); \ 459 memset(&q2, 0, sizeof(q2)); \ 460 q2.root = QUOTAROOT; \ 461 r = quota_read(&q2, NULL, 0); \ 462 CU_ASSERT_EQUAL(r, 0); \ 463 for (res = 0; res < QUOTA_NUMRESOURCES; res++) { \ 464 CU_ASSERT_EQUAL(q2.useds[res], expused[res]); \ 465 CU_ASSERT_EQUAL(q2.limits[res], q.limits[res]) \ 466 } \ 467} 468 469 /* updating a root which has a record, succeeds */ 470 TESTCASE(10*1024, 2, 1*1024, 471 10*1024, 2, 1*1024); 472 473 /* updating some more adds to the used value */ 474 TESTCASE(80*1024, 16, 8*1024, 475 90*1024, 18, 9*1024); 476 477 /* updating with a zero diff does not change the used value */ 478 TESTCASE(0, 0, 0, 479 90*1024, 18, 9*1024); 480 481 /* quota_update_useds() does not enforce the limit */ 482 TESTCASE(20*1024, 4, 2*1024, 483 110*1024, /* used 110 KiB limit 100 KiB */ 484 22, /* used 22 limit 20 */ 485 11*1024); /* used 11 KiB limit 10 KiB */ 486 487 /* updating with a negative value */ 488 TESTCASE(-70*1024, -14, -7*1024, 489 40*1024, 8, 4*1024); 490 491 /* underflow is prevented */ 492 TESTCASE(-50*1024, -10, -5*1024, 493 0, 0, 0); 494 495 /* XXX we call quota_update_useds() with a NULL mailbox, which 496 * XXX will crash in some circumstances (see comment inline), but 497 * XXX our tests don't crash... which means we're missing tests 498 * XXX for the codepath that depends on mboxname! 499 * XXX https://github.com/cyrusimap/cyrus-imapd/issues/2808 500 */ 501 502#undef TESTCASE 503} 504 505static void test_delete(void) 506{ 507 struct quota q; 508 struct quota q2; 509 struct txn *txn = NULL; 510 int r; 511 512 /* first, deleteroot() behaviour with nothing in the db */ 513 r = quota_deleteroot(NULL); 514 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 515 516 r = quota_deleteroot(""); 517 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 518 519 r = quota_deleteroot(QUOTAROOT_NONEXISTANT); 520 CU_ASSERT_EQUAL(r, 0); 521 522 r = quota_deleteroot(QUOTAROOT); 523 CU_ASSERT_EQUAL(r, 0); 524 525 526 /* add a record to the db and check it's there */ 527 memset(&q, 0, sizeof(q)); 528 q.root = QUOTAROOT; 529 q.useds[QUOTA_STORAGE] = 12345; 530 q.limits[QUOTA_STORAGE] = 678; 531 532 r = quota_write(&q, &txn); 533 CU_ASSERT_EQUAL(r, 0); 534 CU_ASSERT_PTR_NOT_NULL(txn); 535 536 quota_commit(&txn); 537 CU_ASSERT_PTR_NULL(txn); 538 539 memset(&q2, 0, sizeof(q2)); 540 q2.root = QUOTAROOT; 541 r = quota_read(&q2, NULL, 0); 542 CU_ASSERT_EQUAL(r, 0); 543 CU_ASSERT_EQUAL(q2.useds[QUOTA_STORAGE], q.useds[QUOTA_STORAGE]); 544 CU_ASSERT_EQUAL(q2.limits[QUOTA_STORAGE], q.limits[QUOTA_STORAGE]); 545 546 /* check behaviour with a record in the db */ 547 r = quota_deleteroot(NULL); 548 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 549 550 r = quota_deleteroot(""); 551 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 552 553 r = quota_deleteroot(QUOTAROOT_NONEXISTANT); 554 CU_ASSERT_EQUAL(r, 0); 555 556 r = quota_deleteroot(QUOTAROOT); 557 CU_ASSERT_EQUAL(r, 0); 558 559 /* record should now be gone */ 560 memset(&q2, 0, sizeof(q2)); 561 q2.root = QUOTAROOT; 562 r = quota_read(&q2, NULL, 0); 563 CU_ASSERT_EQUAL(r, IMAP_QUOTAROOT_NONEXISTENT); 564} 565 566#if 0 567static void count_cb(const char *key __attribute__((unused)), 568 void *data __attribute__((unused)), 569 void *rock) 570{ 571 unsigned int *np = (unsigned int *)rock; 572 (*np)++; 573} 574 575static unsigned int hash_count(hash_table *ht) 576{ 577 unsigned int n = 0; 578 hash_enumerate(ht, count_cb, &n); 579 return n; 580} 581 582static const char *nth_quotaroot(unsigned int n) 583{ 584 static char buf[100]; 585 static const char * const ones[10] = { 586 ".around", ".defend", ".failure", ".develop", ".nader", 587 ".fuels", ".mixtec", ".bunting", ".energy", ".orlons" }; 588 static const char * const tens[10] = { 589 "", ".blob", ".flakier", ".freda", ".garbs", 590 ".debug", ".ava", ".dumbing", ".addend", ".apaches" }; 591 static const char * const hundreds[10] = { 592 "", ".volcker", ".genies", ".spiro", ".alone", 593 ".drawer", ".eighth", ".micheal", ".coheres", ".garrick" }; 594 static const char * const thousands[10] = { 595 "", ".epilogs", ".cue", ".cahoots", ".decking", 596 ".gypsum", ".gratis", ".dimple", ".pedro", ".fading" }; 597 598 snprintf(buf, sizeof(buf), "user%s%s%s%s", 599 thousands[(n / 1000) % 10], 600 hundreds[(n / 100) % 10], 601 tens[(n / 10) % 10], 602 ones[n % 10]); 603 return buf; 604} 605 606static quota_t nth_used(unsigned int n) 607{ 608 quota_t u = n; 609 610 if (n % 7 == 0) 611 u *= 1023; 612 else if (n % 17 == 0) 613 u |= (u << 53); 614 return u; 615} 616 617static int nth_limit(unsigned int n) 618{ 619 return n * 1024 * 1023; 620} 621 622static int found_cb(struct quota *q, void *rock) 623{ 624 hash_table *exphash = (hash_table *)rock; 625 struct quota *expected = hash_lookup(q->root, exphash); 626 627 CU_ASSERT_PTR_NOT_NULL(expected); 628 CU_ASSERT_STRING_EQUAL(q->root, expected->root); 629 CU_ASSERT_EQUAL(q->useds[QUOTA_STORAGE], 630 expected->useds[QUOTA_STORAGE]); 631 CU_ASSERT_EQUAL(q->limits[QUOTA_STORAGE], 632 expected->limits[QUOTA_STORAGE]); 633 634 hash_del(q->root, exphash); 635 return 0; 636} 637 638#define FOREACH_PRECONDITION(condition, expcount) \ 639{ \ 640 unsigned int nsubset = 0; \ 641 unsigned int i; \ 642 for (i = 0 ; i <= MAXN ; i++) { \ 643 if ((condition)) { \ 644 hash_insert(expected[i].root, &expected[i], &exphash); \ 645 nsubset++; \ 646 } \ 647 } \ 648 CU_ASSERT_EQUAL(nsubset, expcount); \ 649 CU_ASSERT_EQUAL(hash_count(&exphash), nsubset); \ 650} 651 652#define FOREACH_POSTCONDITION() \ 653 CU_ASSERT_EQUAL(r, 0); \ 654 CU_ASSERT_EQUAL(hash_count(&exphash), 0); 655 656#define FOREACH_TEST(prefix, condition, expcount) \ 657 FOREACH_PRECONDITION(condition, expcount); \ 658 r = quota_foreach(prefix, found_cb, &exphash, NULL); \ 659 FOREACH_POSTCONDITION() 660 661static void notest_foreach(void) 662{ 663 struct quota q; 664 struct quota q2; 665 struct txn *txn = NULL; 666 unsigned int n; 667 struct quota *expected; 668 hash_table exphash = HASH_TABLE_INITIALIZER; 669#define MAXN 4095 670 int r; 671 672 expected = (struct quota *)xzmalloc((MAXN+1) * sizeof(struct quota)); 673 for (n = 0 ; n <= MAXN ; n++) { 674 expected[n].root = xstrdup(nth_quotaroot(n)); 675 expected[n].useds[QUOTA_STORAGE] = nth_used(n); 676 expected[n].limits[QUOTA_STORAGE] = nth_limit(n); 677 } 678 construct_hash_table(&exphash, (MAXN+1)*4, 0); 679 680 /* add records to the db */ 681 for (n = 0 ; n <= MAXN ; n++) 682 { 683 q = expected[n]; 684 r = quota_write(&q, &txn); 685 CU_ASSERT_EQUAL(r, 0); 686 CU_ASSERT_PTR_NOT_NULL(txn); 687 } 688 689 quota_commit(&txn); 690 CU_ASSERT_PTR_NULL(txn); 691 692 /* check the records all made it */ 693 for (n = 0 ; n <= MAXN ; n++) 694 { 695 memset(&q2, 0, sizeof(q2)); 696 q2.root = nth_quotaroot(n); 697 r = quota_read(&q2, NULL, 0); 698 CU_ASSERT_EQUAL(r, 0); 699 CU_ASSERT_EQUAL(q2.useds[QUOTA_STORAGE], 700 expected[n].useds[QUOTA_STORAGE]); 701 CU_ASSERT_EQUAL(q2.limits[QUOTA_STORAGE], 702 expected[n].limits[QUOTA_STORAGE]); 703 } 704 705 /* prefix=NULL: iterate all the records */ 706 FOREACH_TEST(NULL, 1, MAXN+1); 707 708 /* prefix="": iterate all the records */ 709 FOREACH_TEST("", 1, MAXN+1); 710 711 /* prefix=a common prefix: iterate all the records */ 712 FOREACH_TEST("user.", 1, MAXN+1); 713 714 /* prefix=an uncommon prefix: iterate some of the records */ 715 FOREACH_TEST("user.epilogs", i / 1000 == 1, 1000); 716 717 /* delete records one by one, checking that foreach 718 * walks over the expected number at each point */ 719 for (n = 0 ; n <= MAXN ; n++) { 720 r = quota_deleteroot(nth_quotaroot(n)); 721 CU_ASSERT_EQUAL(r, 0); 722 723 if (n && n % 301 == 0) { 724 FOREACH_TEST("user.", i > n, MAXN-n); 725 } 726 } 727 728 free_hash_table(&exphash, NULL); 729 for (n = 0 ; n <= MAXN ; n++) 730 free((char *)expected[n].root); 731 free(expected); 732#undef MAXN 733} 734#endif 735 736/* 737 * TODO: should test for quota_foreach() iteration order, 738 * with and without IMAPOPT_IMPROVED_MBOXLIST_SORT set. 739 * There is code that depends on it, e.g. for quota -f. 740 */ 741 742 743/* Note that quota_findroot() returns 0 (not found) 744 * or 1 (found) and never an error code. */ 745#define TESTCASE(in, exp) \ 746{ \ 747 const char *expected = (exp); \ 748 int r; \ 749 char res[1024]; \ 750 r = quota_findroot(res, sizeof(res), (in)); \ 751 if (expected) { \ 752 CU_ASSERT_EQUAL(r, 1); \ 753 CU_ASSERT_STRING_EQUAL(res, expected); \ 754 } \ 755 else { \ 756 CU_ASSERT_EQUAL(r, 0); \ 757 /* contents of res[] not defined */ \ 758 } \ 759} 760 761static void test_findroot(void) 762{ 763 struct quota q; 764 struct txn *txn = NULL; 765 int r; 766 767 memset(&q, 0, sizeof(q)); 768 q.useds[QUOTA_STORAGE] = 123; 769 q.limits[QUOTA_STORAGE] = 456; 770 771 /* 772 * behaviour when the database is empty: 773 * there is no root to be found 774 */ 775 TESTCASE("user.foo.bar.baz", NULL); 776 TESTCASE("user.foo.quux", NULL); 777 TESTCASE("user.foo", NULL); 778 TESTCASE("user.foonly", NULL); 779 TESTCASE("user.fo", NULL); 780 TESTCASE("user.smeg", NULL); 781 TESTCASE("user.smeg.fridge", NULL); 782 TESTCASE("user.farnarkle", NULL); 783 784 /* add some db entries, but not the "user." */ 785 q.root = "user.foo"; 786 r = quota_write(&q, &txn); 787 CU_ASSERT_EQUAL(r, 0); 788 789 q.root = "user.smeg"; 790 r = quota_write(&q, &txn); 791 CU_ASSERT_EQUAL(r, 0); 792 793 CU_ASSERT_PTR_NOT_NULL(txn); 794 quota_commit(&txn); 795 CU_ASSERT_PTR_NULL(txn); 796 797 /* 798 * behaviour with these new entries 799 */ 800 /* "user.foo" and its subfolders match the "user.foo" root */ 801 TESTCASE("user.foo.bar.baz", "user.foo"); 802 TESTCASE("user.foo.quux", "user.foo"); 803 TESTCASE("user.foo", "user.foo"); 804 /* "user.foonly" doesn't match "user.foo" despite the leading 805 * 8 characters being the same */ 806 TESTCASE("user.foonly", NULL); 807 /* "user.fo" doesn't match "user.foo" despite the leading 808 * 7 characters being the same */ 809 TESTCASE("user.fo", NULL); 810 /* "user.smeg" and its subfolders matches the "user.smeg" root */ 811 TESTCASE("user.smeg", "user.smeg"); 812 TESTCASE("user.smeg.fridge", "user.smeg"); 813 /* no root matches at all for "user.farnarkle" */ 814 TESTCASE("user.farnarkle", NULL); 815 816 /* add a catch-all "user" record */ 817 q.root = "user"; 818 r = quota_write(&q, &txn); 819 CU_ASSERT_EQUAL(r, 0); 820 821 CU_ASSERT_PTR_NOT_NULL(txn); 822 quota_commit(&txn); 823 CU_ASSERT_PTR_NULL(txn); 824 825 /* 826 * behaviour with these new entries 827 */ 828 /* "user.foo" unchanged */ 829 TESTCASE("user.foo.bar.baz", "user.foo"); 830 TESTCASE("user.foo.quux", "user.foo"); 831 TESTCASE("user.foo", "user.foo"); 832 /* "user.foonly" matches the catch-all */ 833 TESTCASE("user.foonly", "user"); 834 /* "user.fo" ditto */ 835 TESTCASE("user.fo", "user"); 836 /* "user.smeg" unchanged */ 837 TESTCASE("user.smeg", "user.smeg"); 838 TESTCASE("user.smeg.fridge", "user.smeg"); 839 /* "user.farnarkle" matches the catch-all */ 840 TESTCASE("user.farnarkle", "user"); 841} 842 843static void test_findroot_virtdomains(void) 844{ 845 struct quota q; 846 struct txn *txn = NULL; 847 int r; 848 849 config_virtdomains = IMAP_ENUM_VIRTDOMAINS_ON; 850 /* this shouldn't matter, quota_findroot() doesn't use it */ 851 config_defdomain = "smaak.nl"; 852 853 memset(&q, 0, sizeof(q)); 854 q.useds[QUOTA_STORAGE] = 123; 855 q.limits[QUOTA_STORAGE] = 456; 856 857 /* 858 * behaviour when the database is empty: 859 * there is no root to be found 860 */ 861 TESTCASE("bloggs.com!user.foo.bar.baz", NULL); 862 TESTCASE("bloggs.com!user.foo.quux", NULL); 863 TESTCASE("bloggs.com!user.foo", NULL); 864 TESTCASE("fnarp.org!user.foo.bar.baz", NULL); 865 TESTCASE("fnarp.org!user.foo.quux", NULL); 866 TESTCASE("fnarp.org!user.foo", NULL); 867 TESTCASE("user.foo.bar.baz", NULL); 868 TESTCASE("user.foo.quux", NULL); 869 TESTCASE("user.foo", NULL); 870 TESTCASE("bloggs.com!user.smeg", NULL); 871 TESTCASE("bloggs.com!user.smeg.fridge", NULL); 872 TESTCASE("fnarp.org!user.smeg", NULL); 873 TESTCASE("fnarp.org!user.smeg.fridge", NULL); 874 TESTCASE("user.smeg", NULL); 875 TESTCASE("user.smeg.fridge", NULL); 876 TESTCASE("bloggs.com!user.farnarkle", NULL); 877 TESTCASE("fnarp.org!user.farnarkle", NULL); 878 TESTCASE("user.farnarkle", NULL); 879 880 /* add some db entries for bloggs.com, but not fnarp.org, 881 * not the default domain, and not the "user." */ 882 q.root = "bloggs.com!user.foo"; 883 r = quota_write(&q, &txn); 884 CU_ASSERT_EQUAL(r, 0); 885 886 q.root = "bloggs.com!user.smeg"; 887 r = quota_write(&q, &txn); 888 CU_ASSERT_EQUAL(r, 0); 889 890 CU_ASSERT_PTR_NOT_NULL(txn); 891 quota_commit(&txn); 892 CU_ASSERT_PTR_NULL(txn); 893 894 /* 895 * behaviour with these new entries 896 */ 897 /* foo@bloggs.com's home and its subfolders match 898 * the "bloggs.com!user.foo" root */ 899 TESTCASE("bloggs.com!user.foo.bar.baz", "bloggs.com!user.foo"); 900 TESTCASE("bloggs.com!user.foo.quux", "bloggs.com!user.foo"); 901 TESTCASE("bloggs.com!user.foo", "bloggs.com!user.foo"); 902 /* foo@fnarp.org's home and its subfolders don't match anything */ 903 TESTCASE("fnarp.org!user.foo.bar.baz", NULL); 904 TESTCASE("fnarp.org!user.foo.quux", NULL); 905 TESTCASE("fnarp.org!user.foo", NULL); 906 /* foo's home and its subfolders don't match anything */ 907 TESTCASE("user.foo.bar.baz", NULL); 908 TESTCASE("user.foo.quux", NULL); 909 TESTCASE("user.foo", NULL); 910 /* smeg@bloggs.com's home and its subfolders match 911 * the "bloggs.com!user.smeg" root */ 912 TESTCASE("bloggs.com!user.smeg", "bloggs.com!user.smeg"); 913 TESTCASE("bloggs.com!user.smeg.fridge", "bloggs.com!user.smeg"); 914 /* smeg@fnarp.org's home and its subfolders don't match anything */ 915 TESTCASE("fnarp.org!user.smeg", NULL); 916 TESTCASE("fnarp.org!user.smeg.fridge", NULL); 917 /* smeg's home and its subfolders don't match anything */ 918 TESTCASE("user.smeg", NULL); 919 TESTCASE("user.smeg.fridge", NULL); 920 /* no root matches at all for "user.farnarkle" in any domain */ 921 TESTCASE("bloggs.com!user.farnarkle", NULL); 922 TESTCASE("fnarp.org!user.farnarkle", NULL); 923 TESTCASE("user.farnarkle", NULL); 924 925 /* add a catch-all "bloggs.com" record */ 926 q.root = "bloggs.com!"; 927 r = quota_write(&q, &txn); 928 CU_ASSERT_EQUAL(r, 0); 929 930 CU_ASSERT_PTR_NOT_NULL(txn); 931 quota_commit(&txn); 932 CU_ASSERT_PTR_NULL(txn); 933 934 /* 935 * behaviour with these new entries 936 */ 937 /* foo@bloggs.com unchanged */ 938 TESTCASE("bloggs.com!user.foo.bar.baz", "bloggs.com!user.foo"); 939 TESTCASE("bloggs.com!user.foo.quux", "bloggs.com!user.foo"); 940 TESTCASE("bloggs.com!user.foo", "bloggs.com!user.foo"); 941 /* foo@fnarp.org's unchanged */ 942 TESTCASE("fnarp.org!user.foo.bar.baz", NULL); 943 TESTCASE("fnarp.org!user.foo.quux", NULL); 944 TESTCASE("fnarp.org!user.foo", NULL); 945 /* foo unchanged */ 946 TESTCASE("user.foo.bar.baz", NULL); 947 TESTCASE("user.foo.quux", NULL); 948 TESTCASE("user.foo", NULL); 949 /* smeg@bloggs.com unchanged */ 950 TESTCASE("bloggs.com!user.smeg", "bloggs.com!user.smeg"); 951 TESTCASE("bloggs.com!user.smeg.fridge", "bloggs.com!user.smeg"); 952 /* smeg@fnarp.org unchanged */ 953 TESTCASE("fnarp.org!user.smeg", NULL); 954 TESTCASE("fnarp.org!user.smeg.fridge", NULL); 955 /* smeg unchanged */ 956 TESTCASE("user.smeg", NULL); 957 TESTCASE("user.smeg.fridge", NULL); 958 /* farnarkle@bloggs.com matches the bloggs.com catch-all */ 959 TESTCASE("bloggs.com!user.farnarkle", "bloggs.com!"); 960 /* farnarkle at other domains unchanged */ 961 TESTCASE("fnarp.org!user.farnarkle", NULL); 962 TESTCASE("user.farnarkle", NULL); 963 964 /* add a catch-all "fnarp.org!user" record */ 965 q.root = "fnarp.org!user"; 966 r = quota_write(&q, &txn); 967 CU_ASSERT_EQUAL(r, 0); 968 969 CU_ASSERT_PTR_NOT_NULL(txn); 970 quota_commit(&txn); 971 CU_ASSERT_PTR_NULL(txn); 972 973 /* 974 * behaviour with these new entries 975 */ 976 /* foo@bloggs.com unchanged */ 977 TESTCASE("bloggs.com!user.foo.bar.baz", "bloggs.com!user.foo"); 978 TESTCASE("bloggs.com!user.foo.quux", "bloggs.com!user.foo"); 979 TESTCASE("bloggs.com!user.foo", "bloggs.com!user.foo"); 980 /* foo@fnarp.org's matches the fnarp.org catch-all */ 981 TESTCASE("fnarp.org!user.foo.bar.baz", "fnarp.org!user"); 982 TESTCASE("fnarp.org!user.foo.quux", "fnarp.org!user"); 983 TESTCASE("fnarp.org!user.foo", "fnarp.org!user"); 984 /* foo unchanged */ 985 TESTCASE("user.foo.bar.baz", NULL); 986 TESTCASE("user.foo.quux", NULL); 987 TESTCASE("user.foo", NULL); 988 /* smeg@bloggs.com unchanged */ 989 TESTCASE("bloggs.com!user.smeg", "bloggs.com!user.smeg"); 990 TESTCASE("bloggs.com!user.smeg.fridge", "bloggs.com!user.smeg"); 991 /* smeg@fnarp.org matches the fnarp.org catch-all */ 992 TESTCASE("fnarp.org!user.smeg", "fnarp.org!user"); 993 TESTCASE("fnarp.org!user.smeg.fridge", "fnarp.org!user"); 994 /* smeg unchanged */ 995 TESTCASE("user.smeg", NULL); 996 TESTCASE("user.smeg.fridge", NULL); 997 /* farnarkle@bloggs.com matches the bloggs.com catch-all */ 998 TESTCASE("bloggs.com!user.farnarkle", "bloggs.com!"); 999 /* farnarkle@fnarp.org matches the fnarp.org catch-all */ 1000 TESTCASE("fnarp.org!user.farnarkle", "fnarp.org!user"); 1001 /* farnarkle in the default domain unchanged */ 1002 TESTCASE("user.farnarkle", NULL); 1003} 1004#undef TESTCASE 1005 1006static void config_read_string(const char *s) 1007{ 1008 char *fname = xstrdup("/tmp/cyrus-cunit-configXXXXXX"); 1009 int fd = mkstemp(fname); 1010 retry_write(fd, s, strlen(s)); 1011 config_reset(); 1012 config_read(fname, 0); 1013 unlink(fname); 1014 free(fname); 1015 close(fd); 1016} 1017 1018static int set_up(void) 1019{ 1020 int r; 1021 const char * const *d; 1022 static const char * const dirs[] = { 1023 DBDIR, 1024 DBDIR"/db", 1025 NULL 1026 }; 1027 1028 r = system("rm -rf " DBDIR); 1029 if (r) 1030 return r; 1031 1032 for (d = dirs ; *d ; d++) { 1033 r = mkdir(*d, 0777); 1034 if (r < 0) { 1035 int e = errno; 1036 perror(*d); 1037 return e; 1038 } 1039 } 1040 1041 libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, DBDIR); 1042 config_read_string( 1043 "configdirectory: "DBDIR"/conf\n" 1044 ); 1045 1046 cyrusdb_init(); 1047 config_quota_db = "skiplist"; 1048 1049 quotadb_init(0); 1050 quotadb_open(NULL); 1051 1052 return 0; 1053} 1054 1055static int tear_down(void) 1056{ 1057 int r; 1058 1059 quotadb_close(); 1060 quotadb_done(); 1061 cyrusdb_done(); 1062 config_quota_db = NULL; 1063 config_reset(); 1064 1065 r = system("rm -rf " DBDIR); 1066 if (r) r = -1; 1067 1068 return r; 1069} 1070/* vim: set ft=c: */ 1071