1/* $NetBSD: readdir.c,v 1.3 2015/01/17 17:46:31 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997-2014 Erez Zadok 5 * Copyright (c) 1990 Jan-Simon Pendry 6 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine 7 * Copyright (c) 1990 The Regents of the University of California. 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to Berkeley by 11 * Jan-Simon Pendry at Imperial College, London. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. Neither the name of the University nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 * 37 * 38 * File: am-utils/amd/readdir.c 39 * 40 */ 41 42 43#ifdef HAVE_CONFIG_H 44# include <config.h> 45#endif /* HAVE_CONFIG_H */ 46#include <am_defs.h> 47#include <amd.h> 48 49 50/**************************************************************************** 51 *** MACROS *** 52 ****************************************************************************/ 53#define DOT_DOT_COOKIE (u_int) 1 54#define MAX_CHAIN 2048 55 56/**************************************************************************** 57 *** FORWARD DEFINITIONS *** 58 ****************************************************************************/ 59static int key_already_in_chain(char *keyname, const nfsentry *chain); 60static nfsentry *make_entry_chain(am_node *mp, const nfsentry *current_chain, int fully_browsable); 61static int amfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable); 62 63static const u_int dotdotcookie = DOT_DOT_COOKIE; 64 65/**************************************************************************** 66 *** FUNCTIONS *** 67 ****************************************************************************/ 68/* 69 * Was: NEW_TOPLVL_READDIR 70 * Search a chain for an entry with some name. 71 * -Erez Zadok <ezk@cs.columbia.edu> 72 */ 73static int 74key_already_in_chain(char *keyname, const nfsentry *chain) 75{ 76 const nfsentry *tmpchain = chain; 77 78 while (tmpchain) { 79 if (keyname && tmpchain->ne_name && STREQ(keyname, tmpchain->ne_name)) 80 return 1; 81 tmpchain = tmpchain->ne_nextentry; 82 } 83 84 return 0; 85} 86 87 88/* 89 * Create a chain of entries which are not linked. 90 * -Erez Zadok <ezk@cs.columbia.edu> 91 */ 92static nfsentry * 93make_entry_chain(am_node *mp, const nfsentry *current_chain, int fully_browsable) 94{ 95 static u_int last_cookie = (u_int) 2; /* monotonically increasing */ 96 static nfsentry chain[MAX_CHAIN]; 97 static int max_entries = MAX_CHAIN; 98 char *key; 99 int num_entries = 0, i; 100 u_int preflen = 0; 101 nfsentry *retval = (nfsentry *) NULL; 102 mntfs *mf; 103 mnt_map *mmp; 104 105 if (!mp) { 106 plog(XLOG_DEBUG, "make_entry_chain: mp is (NULL)"); 107 return retval; 108 } 109 mf = mp->am_al->al_mnt; 110 if (!mf) { 111 plog(XLOG_DEBUG, "make_entry_chain: mp->am_al->al_mnt is (NULL)"); 112 return retval; 113 } 114 mmp = (mnt_map *) mf->mf_private; 115 if (!mmp) { 116 plog(XLOG_DEBUG, "make_entry_chain: mp->am_al->al_mnt->mf_private is (NULL)"); 117 return retval; 118 } 119 120 if (mp->am_pref) 121 preflen = strlen(mp->am_pref); 122 123 /* iterate over keys */ 124 for (i = 0; i < NKVHASH; i++) { 125 kv *k; 126 for (k = mmp->kvhash[i]; k ; k = k->next) { 127 128 /* 129 * Skip unwanted entries which are either not real entries or 130 * very difficult to interpret (wildcards...) This test needs 131 * lots of improvement. Any takers? 132 */ 133 key = k->key; 134 if (!key) 135 continue; 136 137 /* Skip '/defaults' */ 138 if (STREQ(key, "/defaults")) 139 continue; 140 141 /* Skip '*' */ 142 if (!fully_browsable && strchr(key, '*')) 143 continue; 144 145 /* 146 * If the map has a prefix-string then check if the key starts with 147 * this string, and if it does, skip over this prefix. If it has a 148 * prefix and it doesn't match the start of the key, skip it. 149 */ 150 if (preflen) { 151 if (preflen > strlen(key)) 152 continue; 153 if (!NSTREQ(key, mp->am_pref, preflen)) 154 continue; 155 key += preflen; 156 } 157 158 /* no more '/' are allowed, unless browsable_dirs=full was used */ 159 if (!fully_browsable && strchr(key, '/')) 160 continue; 161 162 /* no duplicates allowed */ 163 if (key_already_in_chain(key, current_chain)) 164 continue; 165 166 /* fill in a cell and link the entry */ 167 if (num_entries >= max_entries) { 168 /* out of space */ 169 plog(XLOG_DEBUG, "make_entry_chain: no more space in chain"); 170 if (num_entries > 0) { 171 chain[num_entries - 1].ne_nextentry = NULL; 172 retval = &chain[0]; 173 } 174 return retval; 175 } 176 177 /* we have space. put entry in next cell */ 178 ++last_cookie; 179 chain[num_entries].ne_fileid = last_cookie; 180 (void)memcpy(chain[num_entries].ne_cookie, &last_cookie, 181 sizeof(last_cookie)); 182 chain[num_entries].ne_name = key; 183 if (num_entries < max_entries - 1) { /* link to next one */ 184 chain[num_entries].ne_nextentry = &chain[num_entries + 1]; 185 } 186 ++num_entries; 187 } /* end of "while (k)" */ 188 } /* end of "for (i ... NKVHASH ..." */ 189 190 /* terminate chain */ 191 if (num_entries > 0) { 192 chain[num_entries - 1].ne_nextentry = NULL; 193 retval = &chain[0]; 194 } 195 196 return retval; 197} 198 199 200 201/* This one is called only if map is browsable */ 202static int 203amfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable) 204{ 205 u_int gen = *(u_int *) cookie; 206 int chain_length, i; 207 static nfsentry *te, *te_next; 208 static int j; 209 210 dp->dl_eof = FALSE; /* assume readdir not done */ 211 212 if (amuDebug(D_READDIR)) 213 plog(XLOG_DEBUG, "amfs_readdir_browsable gen=%u, count=%d", 214 gen, count); 215 216 if (gen == 0) { 217 /* 218 * In the default instance (which is used to start a search) we return 219 * "." and "..". 220 * 221 * This assumes that the count is big enough to allow both "." and ".." 222 * to be returned in a single packet. If it isn't (which would be 223 * fairly unbelievable) then tough. 224 */ 225 dlog("%s: default search", __func__); 226 /* 227 * Check for enough room. This is extremely approximate but is more 228 * than enough space. Really need 2 times: 229 * 4byte fileid 230 * 4byte cookie 231 * 4byte name length 232 * 4byte name 233 * plus the dirlist structure */ 234 if (count < (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp)))) 235 return EINVAL; 236 237 /* 238 * compute # of entries to send in this chain. 239 * heuristics: 128 bytes per entry. 240 * This is too much probably, but it seems to work better because 241 * of the re-entrant nature of nfs_readdir, and esp. on systems 242 * like OpenBSD 2.2. 243 */ 244 chain_length = count / 128; 245 246 /* reset static state counters */ 247 te = te_next = NULL; 248 249 dp->dl_entries = ep; 250 251 /* construct "." */ 252 ep[0].ne_fileid = mp->am_gen; 253 ep[0].ne_name = "."; 254 ep[0].ne_nextentry = &ep[1]; 255 (void)memset(ep[0].ne_cookie, 0, sizeof(u_int)); 256 257 /* construct ".." */ 258 if (mp->am_parent) 259 ep[1].ne_fileid = mp->am_parent->am_gen; 260 else 261 ep[1].ne_fileid = mp->am_gen; 262 263 ep[1].ne_name = ".."; 264 ep[1].ne_nextentry = NULL; 265 (void)memcpy(ep[1].ne_cookie, &dotdotcookie, sizeof(dotdotcookie)); 266 267 /* 268 * If map is browsable, call a function make_entry_chain() to construct 269 * a linked list of unmounted keys, and return it. Then link the chain 270 * to the regular list. Get the chain only once, but return 271 * chunks of it each time. 272 */ 273 te = make_entry_chain(mp, dp->dl_entries, fully_browsable); 274 if (!te) 275 return 0; 276 if (amuDebug(D_READDIR)) { 277 nfsentry *ne; 278 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 279 plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name); 280 } 281 282 /* return only "chain_length" entries */ 283 te_next = te; 284 for (i=1; i<chain_length; ++i) { 285 te_next = te_next->ne_nextentry; 286 if (!te_next) 287 break; 288 } 289 if (te_next) { 290 nfsentry *te_saved = te_next->ne_nextentry; 291 te_next->ne_nextentry = NULL; /* terminate "te" chain */ 292 te_next = te_saved; /* save rest of "te" for next iteration */ 293 dp->dl_eof = FALSE; /* tell readdir there's more */ 294 } else { 295 dp->dl_eof = TRUE; /* tell readdir that's it */ 296 } 297 ep[1].ne_nextentry = te; /* append this chunk of "te" chain */ 298 if (amuDebug(D_READDIR)) { 299 nfsentry *ne; 300 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 301 plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->ne_name); 302 for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) { 303 u_int cookie; 304 (void)memcpy(&cookie, ne->ne_cookie, sizeof(cookie)); 305 plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%d ck=%d", 306 j++, ne->ne_name, ne->ne_fileid, cookie); 307 } 308 plog(XLOG_DEBUG, "EOF is %d", dp->dl_eof); 309 } 310 return 0; 311 } /* end of "if (gen == 0)" statement */ 312 313 dlog("%s: real child", __func__); 314 315 if (gen == DOT_DOT_COOKIE) { 316 dlog("%s: End of readdir in %s", __func__, mp->am_path); 317 dp->dl_eof = TRUE; 318 dp->dl_entries = NULL; 319 return 0; 320 } 321 322 /* 323 * If browsable directories, then continue serving readdir() with another 324 * chunk of entries, starting from where we left off (when gen was equal 325 * to 0). Once again, assume last chunk served to readdir. 326 */ 327 dp->dl_eof = TRUE; 328 dp->dl_entries = ep; 329 330 te = te_next; /* reset 'te' from last saved te_next */ 331 if (!te) { /* another indicator of end of readdir */ 332 dp->dl_entries = NULL; 333 return 0; 334 } 335 /* 336 * compute # of entries to send in this chain. 337 * heuristics: 128 bytes per entry. 338 */ 339 chain_length = count / 128; 340 341 /* return only "chain_length" entries */ 342 for (i = 1; i < chain_length; ++i) { 343 te_next = te_next->ne_nextentry; 344 if (!te_next) 345 break; 346 } 347 if (te_next) { 348 nfsentry *te_saved = te_next->ne_nextentry; 349 te_next->ne_nextentry = NULL; /* terminate "te" chain */ 350 te_next = te_saved; /* save rest of "te" for next iteration */ 351 dp->dl_eof = FALSE; /* tell readdir there's more */ 352 } 353 ep = te; /* send next chunk of "te" chain */ 354 dp->dl_entries = ep; 355 if (amuDebug(D_READDIR)) { 356 nfsentry *ne; 357 plog(XLOG_DEBUG, "dl_entries=%p, te_next=%p, dl_eof=%d", 358 dp->dl_entries, te_next, dp->dl_eof); 359 for (ne = te; ne; ne = ne->ne_nextentry) 360 plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->ne_name); 361 } 362 return 0; 363} 364 365static int 366amfs_readdir(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count) 367{ 368 u_int gen = *(u_int *) cookie; 369 am_node *xp; 370 371 dp->dl_eof = FALSE; /* assume readdir not done */ 372 373 /* when gen is 0, we start reading from the beginning of the directory */ 374 if (gen == 0) { 375 /* 376 * In the default instance (which is used to start a search) we return 377 * "." and "..". 378 * 379 * This assumes that the count is big enough to allow both "." and ".." 380 * to be returned in a single packet. If it isn't (which would be 381 * fairly unbelievable) then tough. 382 */ 383 dlog("%s: default search", __func__); 384 /* 385 * Check for enough room. This is extremely approximate but is more 386 * than enough space. Really need 2 times: 387 * 4byte fileid 388 * 4byte cookie 389 * 4byte name length 390 * 4byte name 391 * plus the dirlist structure */ 392#define NEEDROOM (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp))) 393 if (count < NEEDROOM) { 394 dlog("%s: not enough room %u < %zu", __func__, count, NEEDROOM); 395 return EINVAL; 396 } 397 398 xp = next_nonerror_node(mp->am_child); 399 dp->dl_entries = ep; 400 401 /* construct "." */ 402 ep[0].ne_fileid = mp->am_gen; 403 ep[0].ne_name = "."; 404 ep[0].ne_nextentry = &ep[1]; 405 (void)memset(ep[0].ne_cookie, 0, sizeof(u_int)); 406 407 /* construct ".." */ 408 if (mp->am_parent) 409 ep[1].ne_fileid = mp->am_parent->am_gen; 410 else 411 ep[1].ne_fileid = mp->am_gen; 412 ep[1].ne_name = ".."; 413 ep[1].ne_nextentry = NULL; 414 (void)memcpy(ep[1].ne_cookie, (xp ? &xp->am_gen : &dotdotcookie), 415 sizeof(dotdotcookie)); 416 417 if (!xp) 418 dp->dl_eof = TRUE; /* by default assume readdir done */ 419 420 if (amuDebug(D_READDIR)) { 421 nfsentry *ne; 422 int j; 423 for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) { 424 u_int cookie; 425 (void)memcpy(&cookie, ne->ne_cookie, sizeof(cookie)); 426 plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%d ck=%d", 427 j++, ne->ne_name, ne->ne_fileid, cookie); 428 } 429 } 430 return 0; 431 } 432 dlog("%s: real child", __func__); 433 434 if (gen == DOT_DOT_COOKIE) { 435 dlog("%s: End of readdir in %s", __func__, mp->am_path); 436 dp->dl_eof = TRUE; 437 dp->dl_entries = NULL; 438 if (amuDebug(D_READDIR)) 439 plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n"); 440 return 0; 441 } 442 443 /* non-browsable directories code */ 444 xp = mp->am_child; 445 while (xp && xp->am_gen != gen) 446 xp = xp->am_osib; 447 448 if (xp) { 449 int nbytes = count / 2; /* conservative */ 450 int todo = MAX_READDIR_ENTRIES; 451 452 dp->dl_entries = ep; 453 do { 454 am_node *xp_next = next_nonerror_node(xp->am_osib); 455 456 if (xp_next) { 457 (void)memcpy(ep->ne_cookie, &xp_next->am_gen, sizeof(xp_next->am_gen)); 458 } else { 459 (void)memcpy(ep->ne_cookie, &dotdotcookie, sizeof(dotdotcookie)); 460 dp->dl_eof = TRUE; 461 } 462 463 ep->ne_fileid = xp->am_gen; 464 ep->ne_name = xp->am_name; 465 nbytes -= sizeof(*ep) + 1; 466 if (xp->am_name) 467 nbytes -= strlen(xp->am_name); 468 469 xp = xp_next; 470 471 if (nbytes > 0 && !dp->dl_eof && todo > 1) { 472 ep->ne_nextentry = ep + 1; 473 ep++; 474 --todo; 475 } else { 476 todo = 0; 477 } 478 } while (todo > 0); 479 480 ep->ne_nextentry = NULL; 481 482 if (amuDebug(D_READDIR)) { 483 nfsentry *ne; 484 int j; 485 for (j=0,ne=ep; ne; ne=ne->ne_nextentry) { 486 u_int cookie; 487 (void)memcpy(&cookie, ne->ne_cookie, sizeof(cookie)); 488 plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%d ck=%d", 489 j++, ne->ne_name, ne->ne_fileid, cookie); 490 } 491 } 492 return 0; 493 } 494 return ESTALE; 495} 496 497/* 498 * Search a chain for an entry with some name. 499 */ 500static int 501key_already_in_chain3(char *keyname, const am_entry3 *chain) 502{ 503 const am_entry3 *tmpchain = chain; 504 505 while (tmpchain) { 506 if (keyname && tmpchain->name && STREQ(keyname, tmpchain->name)) 507 return 1; 508 tmpchain = tmpchain->nextentry; 509 } 510 511 return 0; 512} 513 514/* 515 * Create a chain of entries which are not linked. 516 */ 517static am_entry3 * 518make_entry_chain3(am_node *mp, const am_entry3 *current_chain, int fully_browsable) 519{ 520 static uint64 last_cookie = (uint64) 2; /* monotonically increasing */ 521 static am_entry3 chain[MAX_CHAIN]; 522 static int max_entries = MAX_CHAIN; 523 char *key; 524 int num_entries = 0, i; 525 u_int preflen = 0; 526 am_entry3 *retval = (am_entry3 *) NULL; 527 mntfs *mf; 528 mnt_map *mmp; 529 530 if (!mp) { 531 plog(XLOG_DEBUG, "make_entry_chain3: mp is (NULL)"); 532 return retval; 533 } 534 mf = mp->am_al->al_mnt; 535 if (!mf) { 536 plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt is (NULL)"); 537 return retval; 538 } 539 mmp = (mnt_map *) mf->mf_private; 540 if (!mmp) { 541 plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt->mf_private is (NULL)"); 542 return retval; 543 } 544 545 if (mp->am_pref) 546 preflen = strlen(mp->am_pref); 547 548 /* iterate over keys */ 549 for (i = 0; i < NKVHASH; i++) { 550 kv *k; 551 for (k = mmp->kvhash[i]; k ; k = k->next) { 552 553 /* 554 * Skip unwanted entries which are either not real entries or 555 * very difficult to interpret (wildcards...) This test needs 556 * lots of improvement. Any takers? 557 */ 558 key = k->key; 559 if (!key) 560 continue; 561 562 /* Skip '/defaults' */ 563 if (STREQ(key, "/defaults")) 564 continue; 565 566 /* Skip '*' */ 567 if (!fully_browsable && strchr(key, '*')) 568 continue; 569 570 /* 571 * If the map has a prefix-string then check if the key starts with 572 * this string, and if it does, skip over this prefix. If it has a 573 * prefix and it doesn't match the start of the key, skip it. 574 */ 575 if (preflen) { 576 if (preflen > strlen(key)) 577 continue; 578 if (!NSTREQ(key, mp->am_pref, preflen)) 579 continue; 580 key += preflen; 581 } 582 583 /* no more '/' are allowed, unless browsable_dirs=full was used */ 584 if (!fully_browsable && strchr(key, '/')) 585 continue; 586 587 /* no duplicates allowed */ 588 if (key_already_in_chain3(key, current_chain)) 589 continue; 590 591 /* fill in a cell and link the entry */ 592 if (num_entries >= max_entries) { 593 /* out of space */ 594 plog(XLOG_DEBUG, "make_entry_chain3: no more space in chain"); 595 if (num_entries > 0) { 596 chain[num_entries - 1].nextentry = NULL; 597 retval = &chain[0]; 598 } 599 return retval; 600 } 601 602 /* we have space. put entry in next cell */ 603 ++last_cookie; 604 chain[num_entries].fileid = last_cookie; 605 chain[num_entries].cookie = last_cookie; 606 chain[num_entries].name = key; 607 if (num_entries < max_entries - 1) { /* link to next one */ 608 chain[num_entries].nextentry = &chain[num_entries + 1]; 609 } 610 ++num_entries; 611 } /* end of "while (k)" */ 612 } /* end of "for (i ... NKVHASH ..." */ 613 614 /* terminate chain */ 615 if (num_entries > 0) { 616 chain[num_entries - 1].nextentry = NULL; 617 retval = &chain[0]; 618 } 619 620 return retval; 621} 622 623static size_t needroom3(void) 624{ 625 /* 626 * Check for enough room. This is extremely approximate but should 627 * be enough space. Really need 2 times: 628 * (8byte fileid 629 * 8byte cookie 630 * 8byte name pointer 631 * 8byte next entry addres) = sizeof(am_entry3) 632 * 2byte name + 1byte terminator 633 * plus the size of the am_dirlist3 structure */ 634 return ((2 * ((sizeof(am_entry3) + sizeof("..") + 1))) + sizeof(am_dirlist3)); 635} 636 637/* This one is called only if map is browsable */ 638static int 639amfs_readdir3_browsable(am_node *mp, voidp cookie, 640 am_dirlist3 *dp, am_entry3 *ep, u_int count, 641 int fully_browsable) 642{ 643 uint64 gen = *(uint64 *) cookie; 644 int chain_length, i; 645 static am_entry3 *te, *te_next; 646 static int j; 647 648 dp->eof = FALSE; /* assume readdir not done */ 649 650 if (amuDebug(D_READDIR)) 651 plog(XLOG_DEBUG, "%s: gen=%llu, count=%d", __func__, 652 (unsigned long long)gen, count); 653 654 if (gen == 0) { 655 size_t needed = needroom3(); 656 /* 657 * In the default instance (which is used to start a search) we return 658 * "." and "..". 659 * 660 * This assumes that the count is big enough to allow both "." and ".." 661 * to be returned in a single packet. If it isn't (which would be 662 * fairly unbelievable) then tough. 663 */ 664 dlog("%s: default search", __func__); 665 666 if (count < needed) { 667 dlog("%s: not enough room %u < %zu", __func__, count, needed); 668 return EINVAL; 669 } 670 671 /* 672 * compute # of entries to send in this chain. 673 * heuristics: 128 bytes per entry. 674 * This is too much probably, but it seems to work better because 675 * of the re-entrant nature of nfs_readdir, and esp. on systems 676 * like OpenBSD 2.2. 677 */ 678 chain_length = count / 128; 679 680 /* reset static state counters */ 681 te = te_next = NULL; 682 683 dp->entries = ep; 684 685 /* construct "." */ 686 ep[0].fileid = mp->am_gen; 687 ep[0].name = "."; 688 ep[0].nextentry = &ep[1]; 689 ep[0].cookie = 0; 690 691 /* construct ".." */ 692 if (mp->am_parent) 693 ep[1].fileid = mp->am_parent->am_gen; 694 else 695 ep[1].fileid = mp->am_gen; 696 697 ep[1].name = ".."; 698 ep[1].nextentry = NULL; 699 ep[1].cookie = dotdotcookie; 700 701 /* 702 * If map is browsable, call a function make_entry_chain() to construct 703 * a linked list of unmounted keys, and return it. Then link the chain 704 * to the regular list. Get the chain only once, but return 705 * chunks of it each time. 706 */ 707 te = make_entry_chain3(mp, dp->entries, fully_browsable); 708 if (!te) 709 return 0; 710 if (amuDebug(D_READDIR)) { 711 am_entry3 *ne; 712 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 713 plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name); 714 } 715 716 /* return only "chain_length" entries */ 717 te_next = te; 718 for (i=1; i<chain_length; ++i) { 719 te_next = te_next->nextentry; 720 if (!te_next) 721 break; 722 } 723 if (te_next) { 724 am_entry3 *te_saved = te_next->nextentry; 725 te_next->nextentry = NULL; /* terminate "te" chain */ 726 te_next = te_saved; /* save rest of "te" for next iteration */ 727 dp->eof = FALSE; /* tell readdir there's more */ 728 } else { 729 dp->eof = TRUE; /* tell readdir that's it */ 730 } 731 ep[1].nextentry = te; /* append this chunk of "te" chain */ 732 if (amuDebug(D_READDIR)) { 733 am_entry3 *ne; 734 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 735 plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->name); 736 for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) { 737 plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%llu ck=%llu", 738 j++, ne->name, (unsigned long long)ne->fileid, 739 (unsigned long long)ne->cookie); 740 } 741 plog(XLOG_DEBUG, "EOF is %d", dp->eof); 742 } 743 return 0; 744 } /* end of "if (gen == 0)" statement */ 745 746 dlog("%s: real child", __func__); 747 748 if (gen == DOT_DOT_COOKIE) { 749 dlog("%s: End of readdir in %s", __func__, mp->am_path); 750 dp->eof = TRUE; 751 dp->entries = NULL; 752 return 0; 753 } 754 755 /* 756 * If browsable directories, then continue serving readdir() with another 757 * chunk of entries, starting from where we left off (when gen was equal 758 * to 0). Once again, assume last chunk served to readdir. 759 */ 760 dp->eof = TRUE; 761 dp->entries = ep; 762 763 te = te_next; /* reset 'te' from last saved te_next */ 764 if (!te) { /* another indicator of end of readdir */ 765 dp->entries = NULL; 766 return 0; 767 } 768 /* 769 * compute # of entries to send in this chain. 770 * heuristics: 128 bytes per entry. 771 */ 772 chain_length = count / 128; 773 774 /* return only "chain_length" entries */ 775 for (i = 1; i < chain_length; ++i) { 776 te_next = te_next->nextentry; 777 if (!te_next) 778 break; 779 } 780 if (te_next) { 781 am_entry3 *te_saved = te_next->nextentry; 782 te_next->nextentry = NULL; /* terminate "te" chain */ 783 te_next = te_saved; /* save rest of "te" for next iteration */ 784 dp->eof = FALSE; /* tell readdir there's more */ 785 } 786 ep = te; /* send next chunk of "te" chain */ 787 dp->entries = ep; 788 if (amuDebug(D_READDIR)) { 789 am_entry3 *ne; 790 plog(XLOG_DEBUG, 791 "entries=%p, te_next=%p, eof=%d", dp->entries, te_next, dp->eof); 792 for (ne = te; ne; ne = ne->nextentry) 793 plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->name); 794 } 795 return 0; 796} 797 798static int 799amfs_readdir3(am_node *mp, voidp cookie, 800 am_dirlist3 *dp, am_entry3 *ep, u_int count) 801{ 802 uint64 gen = *(uint64 *) cookie; 803 am_node *xp; 804 805 if (amuDebug(D_READDIR)) 806 plog(XLOG_DEBUG, "%s: gen=%llu, count=%d", __func__, 807 (unsigned long long)gen, count); 808 809 dp->eof = FALSE; /* assume readdir not done */ 810 811 /* when gen is 0, we start reading from the beginning of the directory */ 812 if (gen == 0) { 813 size_t needed = needroom3(); 814 /* 815 * In the default instance (which is used to start a search) we return 816 * "." and "..". 817 * 818 * This assumes that the count is big enough to allow both "." and ".." 819 * to be returned in a single packet. If it isn't (which would be 820 * fairly unbelievable) then tough. 821 */ 822 dlog("%s: default search", __func__); 823 824 if (count < needed) { 825 dlog("%s: not enough room %u < %zu", __func__, count, needed); 826 return EINVAL; 827 } 828 829 xp = next_nonerror_node(mp->am_child); 830 dp->entries = ep; 831 832 /* construct "." */ 833 ep[0].fileid = mp->am_gen; 834 ep[0].name = "."; 835 ep[0].cookie = 0; 836 ep[0].nextentry = &ep[1]; 837 838 /* construct ".." */ 839 if (mp->am_parent) 840 ep[1].fileid = mp->am_parent->am_gen; 841 else 842 ep[1].fileid = mp->am_gen; 843 ep[1].name = ".."; 844 ep[1].nextentry = NULL; 845 ep[1].cookie = (xp ? xp->am_gen : dotdotcookie); 846 847 if (!xp) 848 dp->eof = TRUE; /* by default assume readdir done */ 849 850 if (amuDebug(D_READDIR)) { 851 am_entry3 *ne; 852 int j; 853 for (j = 0, ne = ep; ne; ne = ne->nextentry) { 854 plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%llu ck=%llu", 855 j++, ne->name, (unsigned long long)ne->fileid, 856 (unsigned long long)ne->cookie); 857 } 858 } 859 return 0; 860 } 861 dlog("%s: real child", __func__); 862 863 if (gen == (uint64) DOT_DOT_COOKIE) { 864 dlog("%s: End of readdir in %s", __func__, mp->am_path); 865 dp->eof = TRUE; 866 dp->entries = NULL; 867 if (amuDebug(D_READDIR)) 868 plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n"); 869 return 0; 870 } 871 872 /* non-browsable directories code */ 873 xp = mp->am_child; 874 while (xp && xp->am_gen != gen) 875 xp = xp->am_osib; 876 877 if (xp) { 878 int nbytes = count / 2; /* conservative */ 879 int todo = MAX_READDIR_ENTRIES; 880 881 dp->entries = ep; 882 do { 883 am_node *xp_next = next_nonerror_node(xp->am_osib); 884 885 if (xp_next) { 886 ep->cookie = xp_next->am_gen; 887 } else { 888 ep->cookie = (uint64) dotdotcookie; 889 dp->eof = TRUE; 890 } 891 892 ep->fileid = xp->am_gen; 893 ep->name = xp->am_name; 894 nbytes -= sizeof(*ep) + 1; 895 if (xp->am_name) 896 nbytes -= strlen(xp->am_name); 897 898 xp = xp_next; 899 900 if (nbytes > 0 && !dp->dl_eof && todo > 1) { 901 ep->nextentry = ep + 1; 902 ep++; 903 --todo; 904 } else { 905 todo = 0; 906 } 907 } while (todo > 0); 908 909 ep->nextentry = NULL; 910 911 if (amuDebug(D_READDIR)) { 912 am_entry3 *ne; 913 int j; 914 for (j = 0, ne = ep; ne; ne = ne->nextentry) { 915 plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%llu ck=%llu", 916 j++, ne->name, (unsigned long long)ne->fileid, 917 (unsigned long long)ne->cookie); 918 } 919 } 920 return 0; 921 } 922 return ESTALE; 923} 924 925/* 926 * This readdir function which call a special version of it that allows 927 * browsing if browsable_dirs=yes was set on the map. 928 */ 929int 930amfs_generic_readdir(am_node *mp, voidp cookie, voidp dp, voidp ep, u_int count) 931{ 932 int browsable, full; 933 934 /* check if map is browsable */ 935 browsable = 0; 936 if (mp->am_al->al_mnt && mp->am_al->al_mnt->mf_mopts) { 937 mntent_t mnt; 938 mnt.mnt_opts = mp->am_al->al_mnt->mf_mopts; 939 if (amu_hasmntopt(&mnt, "fullybrowsable")) 940 browsable = 2; 941 else if (amu_hasmntopt(&mnt, "browsable")) 942 browsable = 1; 943 } 944 full = (browsable == 2); 945 946 if (nfs_dispatcher == nfs_program_2) { 947 if (browsable) 948 return amfs_readdir_browsable(mp, cookie, dp, ep, count, full); 949 else 950 return amfs_readdir(mp, cookie, dp, ep, count); 951 } else { 952 if (browsable) 953 return amfs_readdir3_browsable(mp, cookie, dp, ep, count, full); 954 else 955 return amfs_readdir3(mp, cookie, dp, ep, count); 956 } 957} 958