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 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * autod_readdir.c 28 */ 29 30 #pragma ident "%Z%%M% %I% %E% SMI" 31 32 #include <stdio.h> 33 #include <ctype.h> 34 #include <string.h> 35 #include <syslog.h> 36 #include <sys/types.h> 37 #include <sys/param.h> 38 #include <errno.h> 39 #include <pwd.h> 40 #include <locale.h> 41 #include <stdlib.h> 42 #include <unistd.h> 43 #include <assert.h> 44 #include <fcntl.h> 45 #include "automount.h" 46 47 static void build_dir_entry_list(struct autofs_rddir_cache *rdcp, 48 struct dir_entry *list); 49 static int autofs_rddir_cache_enter(char *map, ulong_t bucket_size, 50 struct autofs_rddir_cache **rdcpp); 51 int autofs_rddir_cache_lookup(char *map, struct autofs_rddir_cache **rdcpp); 52 static int autofs_rddir_cache_delete(struct autofs_rddir_cache *rdcp); 53 static int create_dirents(struct autofs_rddir_cache *rdcp, ulong_t offset, 54 autofs_rddirres *res); 55 struct dir_entry *rddir_entry_lookup(char *name, struct dir_entry *list); 56 static void free_offset_tbl(struct off_tbl *head); 57 static void free_dir_list(struct dir_entry *head); 58 59 #define OFFSET_BUCKET_SIZE 100 60 61 rwlock_t autofs_rddir_cache_lock; /* readdir cache lock */ 62 struct autofs_rddir_cache *rddir_head; /* readdir cache head */ 63 64 int 65 do_readdir(autofs_rddirargs *rda, autofs_rddirres *rd) 66 { 67 struct dir_entry *list = NULL, *l; 68 struct autofs_rddir_cache *rdcp = NULL; 69 int error; 70 int cache_time = RDDIR_CACHE_TIME; 71 72 if (automountd_nobrowse) { 73 /* 74 * Browsability was disabled return an empty list. 75 */ 76 rd->rd_status = AUTOFS_OK; 77 rd->rd_rddir.rddir_size = 0; 78 rd->rd_rddir.rddir_eof = 1; 79 rd->rd_rddir.rddir_entries = NULL; 80 81 return (0); 82 } 83 84 rw_rdlock(&autofs_rddir_cache_lock); 85 error = autofs_rddir_cache_lookup(rda->rda_map, &rdcp); 86 if (error) { 87 rw_unlock(&autofs_rddir_cache_lock); 88 rw_wrlock(&autofs_rddir_cache_lock); 89 error = autofs_rddir_cache_lookup(rda->rda_map, &rdcp); 90 if (error) { 91 if (trace > 2) 92 trace_prt(1, 93 "map %s not found, adding...\n", rda->rda_map); 94 /* 95 * entry doesn't exist, add it. 96 */ 97 error = autofs_rddir_cache_enter(rda->rda_map, 98 OFFSET_BUCKET_SIZE, &rdcp); 99 } 100 } 101 rw_unlock(&autofs_rddir_cache_lock); 102 103 if (error) 104 return (error); 105 106 assert(rdcp != NULL); 107 assert(rdcp->in_use); 108 109 if (!rdcp->full) { 110 rw_wrlock(&rdcp->rwlock); 111 if (!rdcp->full) { 112 /* 113 * cache entry hasn't been filled up, do it now. 114 */ 115 char *stack[STACKSIZ]; 116 char **stkptr; 117 118 /* 119 * Initialize the stack of open files 120 * for this thread 121 */ 122 stack_op(INIT, NULL, stack, &stkptr); 123 (void) getmapkeys(rda->rda_map, &list, &error, 124 &cache_time, stack, &stkptr, rda->uid); 125 if (!error) 126 build_dir_entry_list(rdcp, list); 127 else if (list) { 128 free_dir_list(list); 129 list = NULL; 130 } 131 } 132 } else 133 rw_rdlock(&rdcp->rwlock); 134 135 rd->rd_bufsize = rda->rda_count; 136 if (!error) { 137 error = create_dirents(rdcp, rda->rda_offset, rd); 138 if (error) { 139 if (rdcp->offtp) { 140 free_offset_tbl(rdcp->offtp); 141 rdcp->offtp = NULL; 142 } 143 if (rdcp->entp) { 144 free_dir_list(rdcp->entp); 145 rdcp->entp = NULL; 146 } 147 rdcp->full = 0; 148 list = NULL; 149 } 150 } 151 152 if (trace > 2) { 153 /* 154 * print this list only once 155 */ 156 for (l = list; l != NULL; l = l->next) 157 trace_prt(0, "%s\n", l->name); 158 trace_prt(0, "\n"); 159 } 160 161 if (!error) { 162 rd->rd_status = AUTOFS_OK; 163 if (cache_time) { 164 /* 165 * keep list of entries for up to 166 * 'cache_time' seconds 167 */ 168 rdcp->ttl = time((time_t *)NULL) + cache_time; 169 } else { 170 /* 171 * the underlying name service indicated not 172 * to cache contents. 173 */ 174 if (rdcp->offtp) { 175 free_offset_tbl(rdcp->offtp); 176 rdcp->offtp = NULL; 177 } 178 if (rdcp->entp) { 179 free_dir_list(rdcp->entp); 180 rdcp->entp = NULL; 181 } 182 rdcp->full = 0; 183 } 184 } else { 185 /* 186 * return an empty list 187 */ 188 rd->rd_rddir.rddir_size = 0; 189 rd->rd_rddir.rddir_eof = 1; 190 rd->rd_rddir.rddir_entries = NULL; 191 192 /* 193 * Invalidate cache and set error 194 */ 195 switch (error) { 196 case ENOENT: 197 rd->rd_status = AUTOFS_NOENT; 198 break; 199 case ENOMEM: 200 rd->rd_status = AUTOFS_NOMEM; 201 break; 202 default: 203 rd->rd_status = AUTOFS_ECOMM; 204 } 205 } 206 rw_unlock(&rdcp->rwlock); 207 208 mutex_lock(&rdcp->lock); 209 rdcp->in_use--; 210 mutex_unlock(&rdcp->lock); 211 212 assert(rdcp->in_use >= 0); 213 214 return (error); 215 } 216 217 #define roundtoint(x) (((x) + sizeof (int) - 1) & ~(sizeof (int) - 1)) 218 #define DIRENT64_RECLEN(namelen) \ 219 (((int)(((dirent64_t *)0)->d_name) + 1 + (namelen) + 7) & ~ 7) 220 221 static int 222 create_dirents( 223 struct autofs_rddir_cache *rdcp, 224 ulong_t offset, 225 autofs_rddirres *res) 226 { 227 uint_t total_bytes_wanted; 228 int bufsize; 229 ushort_t this_reclen; 230 int outcount = 0; 231 int namelen; 232 struct dir_entry *list = NULL, *l, *nl; 233 struct dirent64 *dp; 234 char *outbuf; 235 struct off_tbl *offtp, *next = NULL; 236 int this_bucket = 0; 237 int error = 0; 238 int x = 0, y = 0; 239 240 assert(RW_LOCK_HELD(&rdcp->rwlock)); 241 for (offtp = rdcp->offtp; offtp != NULL; offtp = next) { 242 x++; 243 next = offtp->next; 244 this_bucket = (next == NULL); 245 if (!this_bucket) 246 this_bucket = (offset < next->offset); 247 if (this_bucket) { 248 /* 249 * has to be in this bucket 250 */ 251 assert(offset >= offtp->offset); 252 list = offtp->first; 253 break; 254 } 255 /* 256 * loop to look in next bucket 257 */ 258 } 259 260 for (l = list; l != NULL && l->offset < offset; l = l->next) 261 y++; 262 263 if (l == NULL) { 264 /* 265 * reached end of directory 266 */ 267 error = 0; 268 goto empty; 269 } 270 271 if (trace > 2) 272 trace_prt(1, "%s: offset searches (%d, %d)\n", rdcp->map, x, y); 273 274 total_bytes_wanted = res->rd_bufsize; 275 bufsize = total_bytes_wanted + sizeof (struct dirent64); 276 outbuf = malloc(bufsize); 277 if (outbuf == NULL) { 278 syslog(LOG_ERR, "memory allocation error\n"); 279 error = ENOMEM; 280 goto empty; 281 } 282 memset(outbuf, 0, bufsize); 283 /* LINTED pointer alignment */ 284 dp = (struct dirent64 *)outbuf; 285 286 while (l) { 287 nl = l->next; 288 namelen = strlen(l->name); 289 this_reclen = DIRENT64_RECLEN(namelen); 290 if (outcount + this_reclen > total_bytes_wanted) { 291 break; 292 } 293 dp->d_ino = (ino64_t)l->nodeid; 294 if (nl) { 295 /* 296 * get the next elements offset 297 */ 298 dp->d_off = (off64_t)nl->offset; 299 } else { 300 /* 301 * This is the last element 302 * make offset one plus the current. 303 */ 304 dp->d_off = (off64_t)l->offset + 1; 305 } 306 (void) strcpy(dp->d_name, l->name); 307 dp->d_reclen = (ushort_t)this_reclen; 308 outcount += dp->d_reclen; 309 dp = (struct dirent64 *)((int)dp + dp->d_reclen); 310 assert(outcount <= total_bytes_wanted); 311 l = l->next; 312 } 313 314 res->rd_rddir.rddir_size = (long)outcount; 315 if (outcount > 0) { 316 /* 317 * have some entries 318 */ 319 res->rd_rddir.rddir_eof = (l == NULL); 320 /* LINTED pointer alignment */ 321 res->rd_rddir.rddir_entries = (struct dirent64 *)outbuf; 322 error = 0; 323 } else { 324 /* 325 * total_bytes_wanted is not large enough for one 326 * directory entry 327 */ 328 res->rd_rddir.rddir_eof = 0; 329 res->rd_rddir.rddir_entries = NULL; 330 free(outbuf); 331 error = EIO; 332 } 333 return (error); 334 335 empty: 336 res->rd_rddir.rddir_size = 0L; 337 res->rd_rddir.rddir_eof = TRUE; 338 res->rd_rddir.rddir_entries = NULL; 339 return (error); 340 } 341 342 343 /* 344 * add new entry to cache for 'map' 345 */ 346 static int 347 autofs_rddir_cache_enter( 348 char *map, 349 ulong_t bucket_size, 350 struct autofs_rddir_cache **rdcpp) 351 { 352 struct autofs_rddir_cache *p; 353 assert(RW_LOCK_HELD(&autofs_rddir_cache_lock)); 354 355 /* 356 * Add to front of the list at this time 357 */ 358 p = (struct autofs_rddir_cache *)malloc(sizeof (*p)); 359 if (p == NULL) { 360 syslog(LOG_ERR, 361 "autofs_rddir_cache_enter: memory allocation failed\n"); 362 return (ENOMEM); 363 } 364 memset((char *)p, 0, sizeof (*p)); 365 366 p->map = malloc(strlen(map) + 1); 367 if (p->map == NULL) { 368 syslog(LOG_ERR, 369 "autofs_rddir_cache_enter: memory allocation failed\n"); 370 free(p); 371 return (ENOMEM); 372 } 373 strcpy(p->map, map); 374 375 p->bucket_size = bucket_size; 376 /* 377 * no need to grab mutex lock since I haven't yet made the 378 * node visible to the list 379 */ 380 p->in_use = 1; 381 (void) rwlock_init(&p->rwlock, USYNC_THREAD, NULL); 382 (void) mutex_init(&p->lock, USYNC_THREAD, NULL); 383 384 if (rddir_head == NULL) 385 rddir_head = p; 386 else { 387 p->next = rddir_head; 388 rddir_head = p; 389 } 390 *rdcpp = p; 391 392 return (0); 393 } 394 395 /* 396 * find 'map' in readdir cache 397 */ 398 int 399 autofs_rddir_cache_lookup(char *map, struct autofs_rddir_cache **rdcpp) 400 { 401 struct autofs_rddir_cache *p; 402 403 assert(RW_LOCK_HELD(&autofs_rddir_cache_lock)); 404 for (p = rddir_head; p != NULL; p = p->next) { 405 if (strcmp(p->map, map) == 0) { 406 /* 407 * found matching entry 408 */ 409 *rdcpp = p; 410 mutex_lock(&p->lock); 411 p->in_use++; 412 mutex_unlock(&p->lock); 413 return (0); 414 } 415 } 416 /* 417 * didn't find entry 418 */ 419 return (ENOENT); 420 } 421 422 /* 423 * free the offset table 424 */ 425 static void 426 free_offset_tbl(struct off_tbl *head) 427 { 428 struct off_tbl *p, *next = NULL; 429 430 for (p = head; p != NULL; p = next) { 431 next = p->next; 432 free(p); 433 } 434 } 435 436 /* 437 * free the directory entries 438 */ 439 static void 440 free_dir_list(struct dir_entry *head) 441 { 442 struct dir_entry *p, *next = NULL; 443 444 for (p = head; p != NULL; p = next) { 445 next = p->next; 446 assert(p->name); 447 free(p->name); 448 free(p); 449 } 450 } 451 452 static void 453 autofs_rddir_cache_entry_free(struct autofs_rddir_cache *p) 454 { 455 assert(RW_LOCK_HELD(&autofs_rddir_cache_lock)); 456 assert(!p->in_use); 457 if (p->map) 458 free(p->map); 459 if (p->offtp) 460 free_offset_tbl(p->offtp); 461 if (p->entp) 462 free_dir_list(p->entp); 463 free(p); 464 } 465 466 /* 467 * Remove entry from the rddircache 468 * the caller must own the autofs_rddir_cache_lock. 469 */ 470 static int 471 autofs_rddir_cache_delete(struct autofs_rddir_cache *rdcp) 472 { 473 struct autofs_rddir_cache *p, *prev; 474 475 assert(RW_LOCK_HELD(&autofs_rddir_cache_lock)); 476 /* 477 * Search cache for entry 478 */ 479 prev = NULL; 480 for (p = rddir_head; p != NULL; p = p->next) { 481 if (p == rdcp) { 482 /* 483 * entry found, remove from list if not in use 484 */ 485 if (p->in_use) 486 return (EBUSY); 487 if (prev) 488 prev->next = p->next; 489 else 490 rddir_head = p->next; 491 autofs_rddir_cache_entry_free(p); 492 return (0); 493 } 494 prev = p; 495 } 496 syslog(LOG_ERR, "Couldn't find entry %x in cache\n", p); 497 return (ENOENT); 498 } 499 500 /* 501 * Return entry that matches name, NULL otherwise. 502 * Assumes the readers lock for this list has been grabed. 503 */ 504 struct dir_entry * 505 rddir_entry_lookup(char *name, struct dir_entry *list) 506 { 507 return (btree_lookup(list, name)); 508 } 509 510 static void 511 build_dir_entry_list(struct autofs_rddir_cache *rdcp, struct dir_entry *list) 512 { 513 struct dir_entry *p; 514 ulong_t offset = AUTOFS_DAEMONCOOKIE, offset_list = AUTOFS_DAEMONCOOKIE; 515 struct off_tbl *offtp, *last = NULL; 516 ino_t inonum = 4; 517 518 assert(RW_LOCK_HELD(&rdcp->rwlock)); 519 assert(rdcp->entp == NULL); 520 rdcp->entp = list; 521 for (p = list; p != NULL; p = p->next) { 522 p->nodeid = inonum; 523 p->offset = offset; 524 if (offset >= offset_list) { 525 /* 526 * add node to index table 527 */ 528 offtp = (struct off_tbl *) 529 malloc(sizeof (struct off_tbl)); 530 if (offtp != NULL) { 531 offtp->offset = offset; 532 offtp->first = p; 533 offtp->next = NULL; 534 offset_list += rdcp->bucket_size; 535 } else { 536 syslog(LOG_ERR, 537 "WARNING: build_dir_entry_list: could not add offset to index table\n"); 538 continue; 539 } 540 /* 541 * add to cache 542 */ 543 if (rdcp->offtp == NULL) 544 rdcp->offtp = offtp; 545 else 546 last->next = offtp; 547 last = offtp; 548 } 549 offset++; 550 inonum += 2; /* use even numbers in daemon */ 551 } 552 rdcp->full = 1; 553 } 554 555 mutex_t cleanup_lock; 556 cond_t cleanup_start_cv; 557 cond_t cleanup_done_cv; 558 559 /* 560 * cache cleanup thread starting point 561 */ 562 void 563 cache_cleanup(void) 564 { 565 timestruc_t reltime; 566 struct autofs_rddir_cache *p, *next = NULL; 567 int error; 568 569 mutex_init(&cleanup_lock, USYNC_THREAD, NULL); 570 cond_init(&cleanup_start_cv, USYNC_THREAD, NULL); 571 cond_init(&cleanup_done_cv, USYNC_THREAD, NULL); 572 573 mutex_lock(&cleanup_lock); 574 for (;;) { 575 reltime.tv_sec = RDDIR_CACHE_TIME/2; 576 reltime.tv_nsec = 0; 577 578 /* 579 * delay RDDIR_CACHE_TIME seconds, or until some other thread 580 * requests that I cleanup the caches 581 */ 582 if (error = cond_reltimedwait( 583 &cleanup_start_cv, &cleanup_lock, &reltime)) { 584 if (error != ETIME) { 585 if (trace > 1) 586 trace_prt(1, 587 "cleanup thread wakeup (%d)\n", error); 588 continue; 589 } 590 } 591 mutex_unlock(&cleanup_lock); 592 593 /* 594 * Perform the cache cleanup 595 */ 596 rw_wrlock(&autofs_rddir_cache_lock); 597 for (p = rddir_head; p != NULL; p = next) { 598 next = p->next; 599 if (p->in_use > 0) { 600 /* 601 * cache entry busy, skip it 602 */ 603 if (trace > 1) { 604 trace_prt(1, 605 "%s cache in use\n", p->map); 606 } 607 continue; 608 } 609 /* 610 * Cache entry is not in use, and nobody can grab a 611 * new reference since I'm holding the 612 * autofs_rddir_cache_lock 613 */ 614 615 /* 616 * error will be zero if some thread signaled us asking 617 * that the caches be freed. In such case, free caches 618 * even if they're still valid and nobody is referencing 619 * them at this time. Otherwise, free caches only 620 * if their time to live (ttl) has expired. 621 */ 622 if (error == ETIME && (p->ttl > time((time_t *)NULL))) { 623 /* 624 * Scheduled cache cleanup, if cache is still 625 * valid don't free. 626 */ 627 if (trace > 1) { 628 trace_prt(1, 629 "%s cache still valid\n", p->map); 630 } 631 continue; 632 } 633 if (trace > 1) 634 trace_prt(1, "%s freeing cache\n", p->map); 635 assert(!p->in_use); 636 error = autofs_rddir_cache_delete(p); 637 assert(!error); 638 } 639 rw_unlock(&autofs_rddir_cache_lock); 640 641 /* 642 * wakeup the thread/threads waiting for the 643 * cleanup to finish 644 */ 645 mutex_lock(&cleanup_lock); 646 cond_broadcast(&cleanup_done_cv); 647 } 648 /* NOTREACHED */ 649 } 650