1/* 2 * Copyright (c) 1997-2014 Erez Zadok 3 * Copyright (c) 1990 Jan-Simon Pendry 4 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine 5 * Copyright (c) 1990 The Regents of the University of California. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Jan-Simon Pendry at Imperial College, London. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * 36 * File: am-utils/amd/readdir.c 37 * 38 */ 39 40 41#include <stdint.h> 42#ifdef HAVE_CONFIG_H 43# include <config.h> 44#endif /* HAVE_CONFIG_H */ 45#include <am_defs.h> 46#include <amd.h> 47 48 49/**************************************************************************** 50 *** MACROS *** 51 ****************************************************************************/ 52#define DOT_DOT_COOKIE (u_int) 1 53#define MAX_CHAIN 2048 54 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 *) (uintptr_t) 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 *) (uintptr_t) 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, am_cookie3 cookie, 640 am_dirlist3 *dp, am_entry3 *ep, u_int count, 641 int fully_browsable) 642{ 643 uint64 gen = *(uint64 *) (uintptr_t) 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, "amfs_readdir3_browsable gen=%lu, count=%d", (long unsigned) gen, count); 652 653 if (gen == 0) { 654 size_t needed = needroom3(); 655 /* 656 * In the default instance (which is used to start a search) we return 657 * "." and "..". 658 * 659 * This assumes that the count is big enough to allow both "." and ".." 660 * to be returned in a single packet. If it isn't (which would be 661 * fairly unbelievable) then tough. 662 */ 663 dlog("%s: default search", __func__); 664 665 if (count < needed) { 666 dlog("%s: not enough room %u < %zu", __func__, count, needed); 667 return EINVAL; 668 } 669 670 /* 671 * compute # of entries to send in this chain. 672 * heuristics: 128 bytes per entry. 673 * This is too much probably, but it seems to work better because 674 * of the re-entrant nature of nfs_readdir, and esp. on systems 675 * like OpenBSD 2.2. 676 */ 677 chain_length = count / 128; 678 679 /* reset static state counters */ 680 te = te_next = NULL; 681 682 dp->entries = ep; 683 684 /* construct "." */ 685 ep[0].fileid = mp->am_gen; 686 ep[0].name = "."; 687 ep[0].nextentry = &ep[1]; 688 ep[0].cookie = 0; 689 690 /* construct ".." */ 691 if (mp->am_parent) 692 ep[1].fileid = mp->am_parent->am_gen; 693 else 694 ep[1].fileid = mp->am_gen; 695 696 ep[1].name = ".."; 697 ep[1].nextentry = NULL; 698 ep[1].cookie = dotdotcookie; 699 700 /* 701 * If map is browsable, call a function make_entry_chain() to construct 702 * a linked list of unmounted keys, and return it. Then link the chain 703 * to the regular list. Get the chain only once, but return 704 * chunks of it each time. 705 */ 706 te = make_entry_chain3(mp, dp->entries, fully_browsable); 707 if (!te) 708 return 0; 709 if (amuDebug(D_READDIR)) { 710 am_entry3 *ne; 711 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 712 plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name); 713 } 714 715 /* return only "chain_length" entries */ 716 te_next = te; 717 for (i=1; i<chain_length; ++i) { 718 te_next = te_next->nextentry; 719 if (!te_next) 720 break; 721 } 722 if (te_next) { 723 am_entry3 *te_saved = te_next->nextentry; 724 te_next->nextentry = NULL; /* terminate "te" chain */ 725 te_next = te_saved; /* save rest of "te" for next iteration */ 726 dp->eof = FALSE; /* tell readdir there's more */ 727 } else { 728 dp->eof = TRUE; /* tell readdir that's it */ 729 } 730 ep[1].nextentry = te; /* append this chunk of "te" chain */ 731 if (amuDebug(D_READDIR)) { 732 am_entry3 *ne; 733 for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 734 plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->name); 735 for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) { 736 plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%lu ck=%lu", 737 j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 738 } 739 plog(XLOG_DEBUG, "EOF is %d", dp->eof); 740 } 741 return 0; 742 } /* end of "if (gen == 0)" statement */ 743 744 dlog("%s: real child", __func__); 745 746 if (gen == DOT_DOT_COOKIE) { 747 dlog("%s: End of readdir in %s", __func__, mp->am_path); 748 dp->eof = TRUE; 749 dp->entries = NULL; 750 return 0; 751 } 752 753 /* 754 * If browsable directories, then continue serving readdir() with another 755 * chunk of entries, starting from where we left off (when gen was equal 756 * to 0). Once again, assume last chunk served to readdir. 757 */ 758 dp->eof = TRUE; 759 dp->entries = ep; 760 761 te = te_next; /* reset 'te' from last saved te_next */ 762 if (!te) { /* another indicator of end of readdir */ 763 dp->entries = NULL; 764 return 0; 765 } 766 /* 767 * compute # of entries to send in this chain. 768 * heuristics: 128 bytes per entry. 769 */ 770 chain_length = count / 128; 771 772 /* return only "chain_length" entries */ 773 for (i = 1; i < chain_length; ++i) { 774 te_next = te_next->nextentry; 775 if (!te_next) 776 break; 777 } 778 if (te_next) { 779 am_entry3 *te_saved = te_next->nextentry; 780 te_next->nextentry = NULL; /* terminate "te" chain */ 781 te_next = te_saved; /* save rest of "te" for next iteration */ 782 dp->eof = FALSE; /* tell readdir there's more */ 783 } 784 ep = te; /* send next chunk of "te" chain */ 785 dp->entries = ep; 786 if (amuDebug(D_READDIR)) { 787 am_entry3 *ne; 788 plog(XLOG_DEBUG, 789 "entries=%p, te_next=%p, eof=%d", dp->entries, te_next, dp->eof); 790 for (ne = te; ne; ne = ne->nextentry) 791 plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->name); 792 } 793 return 0; 794} 795 796static int 797amfs_readdir3(am_node *mp, am_cookie3 cookie, 798 am_dirlist3 *dp, am_entry3 *ep, u_int count) 799{ 800 uint64 gen = *(uint64 *) (uintptr_t) cookie; 801 am_node *xp; 802 803 if (amuDebug(D_READDIR)) 804 plog(XLOG_DEBUG, "amfs_readdir3 gen=%lu, count=%d", (long unsigned) gen, count); 805 806 dp->eof = FALSE; /* assume readdir not done */ 807 808 /* when gen is 0, we start reading from the beginning of the directory */ 809 if (gen == 0) { 810 size_t needed = needroom3(); 811 /* 812 * In the default instance (which is used to start a search) we return 813 * "." and "..". 814 * 815 * This assumes that the count is big enough to allow both "." and ".." 816 * to be returned in a single packet. If it isn't (which would be 817 * fairly unbelievable) then tough. 818 */ 819 dlog("%s: default search", __func__); 820 821 if (count < needed) { 822 dlog("%s: not enough room %u < %zu", __func__, count, needed); 823 return EINVAL; 824 } 825 826 xp = next_nonerror_node(mp->am_child); 827 dp->entries = ep; 828 829 /* construct "." */ 830 ep[0].fileid = mp->am_gen; 831 ep[0].name = "."; 832 ep[0].cookie = 0; 833 ep[0].nextentry = &ep[1]; 834 835 /* construct ".." */ 836 if (mp->am_parent) 837 ep[1].fileid = mp->am_parent->am_gen; 838 else 839 ep[1].fileid = mp->am_gen; 840 ep[1].name = ".."; 841 ep[1].nextentry = NULL; 842 ep[1].cookie = (xp ? xp->am_gen : dotdotcookie); 843 844 if (!xp) 845 dp->eof = TRUE; /* by default assume readdir done */ 846 847 if (amuDebug(D_READDIR)) { 848 am_entry3 *ne; 849 int j; 850 for (j = 0, ne = ep; ne; ne = ne->nextentry) { 851 plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%lu ck=%lu", 852 j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 853 } 854 } 855 return 0; 856 } 857 dlog("%s: real child", __func__); 858 859 if (gen == (uint64) DOT_DOT_COOKIE) { 860 dlog("%s: End of readdir in %s", __func__, mp->am_path); 861 dp->eof = TRUE; 862 dp->entries = NULL; 863 if (amuDebug(D_READDIR)) 864 plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n"); 865 return 0; 866 } 867 868 /* non-browsable directories code */ 869 xp = mp->am_child; 870 while (xp && xp->am_gen != gen) 871 xp = xp->am_osib; 872 873 if (xp) { 874 int nbytes = count / 2; /* conservative */ 875 int todo = MAX_READDIR_ENTRIES; 876 877 dp->entries = ep; 878 do { 879 am_node *xp_next = next_nonerror_node(xp->am_osib); 880 881 if (xp_next) { 882 ep->cookie = xp_next->am_gen; 883 } else { 884 ep->cookie = (uint64) dotdotcookie; 885 dp->eof = TRUE; 886 } 887 888 ep->fileid = xp->am_gen; 889 ep->name = xp->am_name; 890 nbytes -= sizeof(*ep) + 1; 891 if (xp->am_name) 892 nbytes -= strlen(xp->am_name); 893 894 xp = xp_next; 895 896 if (nbytes > 0 && !dp->dl_eof && todo > 1) { 897 ep->nextentry = ep + 1; 898 ep++; 899 --todo; 900 } else { 901 todo = 0; 902 } 903 } while (todo > 0); 904 905 ep->nextentry = NULL; 906 907 if (amuDebug(D_READDIR)) { 908 am_entry3 *ne; 909 int j; 910 for (j = 0, ne = ep; ne; ne = ne->nextentry) { 911 plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%lu ck=%lu", 912 j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 913 } 914 } 915 return 0; 916 } 917 return ESTALE; 918} 919 920/* 921 * This readdir function which call a special version of it that allows 922 * browsing if browsable_dirs=yes was set on the map. 923 */ 924int 925amfs_generic_readdir(am_node *mp, voidp cookie, voidp dp, voidp ep, u_int count) 926{ 927 int browsable, full; 928 929 /* check if map is browsable */ 930 browsable = 0; 931 if (mp->am_al->al_mnt && mp->am_al->al_mnt->mf_mopts) { 932 mntent_t mnt; 933 mnt.mnt_opts = mp->am_al->al_mnt->mf_mopts; 934 if (amu_hasmntopt(&mnt, "fullybrowsable")) 935 browsable = 2; 936 else if (amu_hasmntopt(&mnt, "browsable")) 937 browsable = 1; 938 } 939 full = (browsable == 2); 940 941 if (nfs_dispatcher == nfs_program_2) { 942 if (browsable) 943 return amfs_readdir_browsable(mp, cookie, dp, ep, count, full); 944 else 945 return amfs_readdir(mp, cookie, dp, ep, count); 946 } else { 947 if (browsable) 948 return amfs_readdir3_browsable(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count, full); 949 else 950 return amfs_readdir3(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count); 951 } 952} 953