1174294Sobrien/* 2310490Scy * Copyright (c) 1997-2014 Erez Zadok 3174294Sobrien * Copyright (c) 1990 Jan-Simon Pendry 4174294Sobrien * Copyright (c) 1990 Imperial College of Science, Technology & Medicine 5174294Sobrien * Copyright (c) 1990 The Regents of the University of California. 6174294Sobrien * All rights reserved. 7174294Sobrien * 8174294Sobrien * This code is derived from software contributed to Berkeley by 9174294Sobrien * Jan-Simon Pendry at Imperial College, London. 10174294Sobrien * 11174294Sobrien * Redistribution and use in source and binary forms, with or without 12174294Sobrien * modification, are permitted provided that the following conditions 13174294Sobrien * are met: 14174294Sobrien * 1. Redistributions of source code must retain the above copyright 15174294Sobrien * notice, this list of conditions and the following disclaimer. 16174294Sobrien * 2. Redistributions in binary form must reproduce the above copyright 17174294Sobrien * notice, this list of conditions and the following disclaimer in the 18174294Sobrien * documentation and/or other materials provided with the distribution. 19310490Scy * 3. Neither the name of the University nor the names of its contributors 20174294Sobrien * may be used to endorse or promote products derived from this software 21174294Sobrien * without specific prior written permission. 22174294Sobrien * 23174294Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24174294Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25174294Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26174294Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27174294Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28174294Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29174294Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30174294Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31174294Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32174294Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33174294Sobrien * SUCH DAMAGE. 34174294Sobrien * 35174294Sobrien * 36174294Sobrien * File: am-utils/amd/readdir.c 37174294Sobrien * 38174294Sobrien */ 39174294Sobrien 40174294Sobrien 41310490Scy#include <stdint.h> 42174294Sobrien#ifdef HAVE_CONFIG_H 43174294Sobrien# include <config.h> 44174294Sobrien#endif /* HAVE_CONFIG_H */ 45174294Sobrien#include <am_defs.h> 46174294Sobrien#include <amd.h> 47174294Sobrien 48174294Sobrien 49174294Sobrien/**************************************************************************** 50174294Sobrien *** MACROS *** 51174294Sobrien ****************************************************************************/ 52174294Sobrien#define DOT_DOT_COOKIE (u_int) 1 53174294Sobrien#define MAX_CHAIN 2048 54174294Sobrien 55174294Sobrien 56174294Sobrien/**************************************************************************** 57174294Sobrien *** FORWARD DEFINITIONS *** 58174294Sobrien ****************************************************************************/ 59174294Sobrienstatic int key_already_in_chain(char *keyname, const nfsentry *chain); 60174294Sobrienstatic nfsentry *make_entry_chain(am_node *mp, const nfsentry *current_chain, int fully_browsable); 61174294Sobrienstatic int amfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable); 62174294Sobrien 63310490Scystatic const u_int dotdotcookie = DOT_DOT_COOKIE; 64174294Sobrien 65174294Sobrien/**************************************************************************** 66174294Sobrien *** FUNCTIONS *** 67174294Sobrien ****************************************************************************/ 68174294Sobrien/* 69174294Sobrien * Was: NEW_TOPLVL_READDIR 70174294Sobrien * Search a chain for an entry with some name. 71174294Sobrien * -Erez Zadok <ezk@cs.columbia.edu> 72174294Sobrien */ 73174294Sobrienstatic int 74174294Sobrienkey_already_in_chain(char *keyname, const nfsentry *chain) 75174294Sobrien{ 76174294Sobrien const nfsentry *tmpchain = chain; 77174294Sobrien 78174294Sobrien while (tmpchain) { 79174294Sobrien if (keyname && tmpchain->ne_name && STREQ(keyname, tmpchain->ne_name)) 80174294Sobrien return 1; 81174294Sobrien tmpchain = tmpchain->ne_nextentry; 82174294Sobrien } 83174294Sobrien 84174294Sobrien return 0; 85174294Sobrien} 86174294Sobrien 87174294Sobrien 88174294Sobrien/* 89174294Sobrien * Create a chain of entries which are not linked. 90174294Sobrien * -Erez Zadok <ezk@cs.columbia.edu> 91174294Sobrien */ 92174294Sobrienstatic nfsentry * 93174294Sobrienmake_entry_chain(am_node *mp, const nfsentry *current_chain, int fully_browsable) 94174294Sobrien{ 95174294Sobrien static u_int last_cookie = (u_int) 2; /* monotonically increasing */ 96174294Sobrien static nfsentry chain[MAX_CHAIN]; 97174294Sobrien static int max_entries = MAX_CHAIN; 98174294Sobrien char *key; 99174294Sobrien int num_entries = 0, i; 100174294Sobrien u_int preflen = 0; 101174294Sobrien nfsentry *retval = (nfsentry *) NULL; 102174294Sobrien mntfs *mf; 103174294Sobrien mnt_map *mmp; 104174294Sobrien 105174294Sobrien if (!mp) { 106174294Sobrien plog(XLOG_DEBUG, "make_entry_chain: mp is (NULL)"); 107174294Sobrien return retval; 108174294Sobrien } 109310490Scy mf = mp->am_al->al_mnt; 110174294Sobrien if (!mf) { 111310490Scy plog(XLOG_DEBUG, "make_entry_chain: mp->am_al->al_mnt is (NULL)"); 112174294Sobrien return retval; 113174294Sobrien } 114174294Sobrien mmp = (mnt_map *) mf->mf_private; 115174294Sobrien if (!mmp) { 116310490Scy plog(XLOG_DEBUG, "make_entry_chain: mp->am_al->al_mnt->mf_private is (NULL)"); 117174294Sobrien return retval; 118174294Sobrien } 119174294Sobrien 120174294Sobrien if (mp->am_pref) 121174294Sobrien preflen = strlen(mp->am_pref); 122174294Sobrien 123174294Sobrien /* iterate over keys */ 124174294Sobrien for (i = 0; i < NKVHASH; i++) { 125174294Sobrien kv *k; 126174294Sobrien for (k = mmp->kvhash[i]; k ; k = k->next) { 127174294Sobrien 128174294Sobrien /* 129174294Sobrien * Skip unwanted entries which are either not real entries or 130174294Sobrien * very difficult to interpret (wildcards...) This test needs 131174294Sobrien * lots of improvement. Any takers? 132174294Sobrien */ 133174294Sobrien key = k->key; 134174294Sobrien if (!key) 135174294Sobrien continue; 136174294Sobrien 137174294Sobrien /* Skip '/defaults' */ 138174294Sobrien if (STREQ(key, "/defaults")) 139174294Sobrien continue; 140174294Sobrien 141174294Sobrien /* Skip '*' */ 142174294Sobrien if (!fully_browsable && strchr(key, '*')) 143174294Sobrien continue; 144174294Sobrien 145174294Sobrien /* 146174294Sobrien * If the map has a prefix-string then check if the key starts with 147174294Sobrien * this string, and if it does, skip over this prefix. If it has a 148174294Sobrien * prefix and it doesn't match the start of the key, skip it. 149174294Sobrien */ 150174294Sobrien if (preflen) { 151174294Sobrien if (preflen > strlen(key)) 152174294Sobrien continue; 153174294Sobrien if (!NSTREQ(key, mp->am_pref, preflen)) 154174294Sobrien continue; 155174294Sobrien key += preflen; 156174294Sobrien } 157174294Sobrien 158174294Sobrien /* no more '/' are allowed, unless browsable_dirs=full was used */ 159174294Sobrien if (!fully_browsable && strchr(key, '/')) 160174294Sobrien continue; 161174294Sobrien 162174294Sobrien /* no duplicates allowed */ 163174294Sobrien if (key_already_in_chain(key, current_chain)) 164174294Sobrien continue; 165174294Sobrien 166174294Sobrien /* fill in a cell and link the entry */ 167174294Sobrien if (num_entries >= max_entries) { 168174294Sobrien /* out of space */ 169174294Sobrien plog(XLOG_DEBUG, "make_entry_chain: no more space in chain"); 170174294Sobrien if (num_entries > 0) { 171310490Scy chain[num_entries - 1].ne_nextentry = NULL; 172174294Sobrien retval = &chain[0]; 173174294Sobrien } 174174294Sobrien return retval; 175174294Sobrien } 176174294Sobrien 177174294Sobrien /* we have space. put entry in next cell */ 178174294Sobrien ++last_cookie; 179174294Sobrien chain[num_entries].ne_fileid = (u_int) last_cookie; 180174294Sobrien *(u_int *) chain[num_entries].ne_cookie = (u_int) last_cookie; 181174294Sobrien chain[num_entries].ne_name = key; 182174294Sobrien if (num_entries < max_entries - 1) { /* link to next one */ 183174294Sobrien chain[num_entries].ne_nextentry = &chain[num_entries + 1]; 184174294Sobrien } 185174294Sobrien ++num_entries; 186174294Sobrien } /* end of "while (k)" */ 187174294Sobrien } /* end of "for (i ... NKVHASH ..." */ 188174294Sobrien 189174294Sobrien /* terminate chain */ 190174294Sobrien if (num_entries > 0) { 191310490Scy chain[num_entries - 1].ne_nextentry = NULL; 192174294Sobrien retval = &chain[0]; 193174294Sobrien } 194174294Sobrien 195174294Sobrien return retval; 196174294Sobrien} 197174294Sobrien 198174294Sobrien 199174294Sobrien 200174294Sobrien/* This one is called only if map is browsable */ 201174294Sobrienstatic int 202174294Sobrienamfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable) 203174294Sobrien{ 204310490Scy u_int gen = *(u_int *) (uintptr_t) cookie; 205174294Sobrien int chain_length, i; 206174294Sobrien static nfsentry *te, *te_next; 207174294Sobrien static int j; 208174294Sobrien 209174294Sobrien dp->dl_eof = FALSE; /* assume readdir not done */ 210174294Sobrien 211174294Sobrien if (amuDebug(D_READDIR)) 212174294Sobrien plog(XLOG_DEBUG, "amfs_readdir_browsable gen=%u, count=%d", 213174294Sobrien gen, count); 214174294Sobrien 215174294Sobrien if (gen == 0) { 216174294Sobrien /* 217174294Sobrien * In the default instance (which is used to start a search) we return 218174294Sobrien * "." and "..". 219174294Sobrien * 220174294Sobrien * This assumes that the count is big enough to allow both "." and ".." 221174294Sobrien * to be returned in a single packet. If it isn't (which would be 222174294Sobrien * fairly unbelievable) then tough. 223174294Sobrien */ 224310490Scy dlog("%s: default search", __func__); 225174294Sobrien /* 226174294Sobrien * Check for enough room. This is extremely approximate but is more 227174294Sobrien * than enough space. Really need 2 times: 228174294Sobrien * 4byte fileid 229174294Sobrien * 4byte cookie 230174294Sobrien * 4byte name length 231174294Sobrien * 4byte name 232174294Sobrien * plus the dirlist structure */ 233174294Sobrien if (count < (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp)))) 234174294Sobrien return EINVAL; 235174294Sobrien 236174294Sobrien /* 237174294Sobrien * compute # of entries to send in this chain. 238174294Sobrien * heuristics: 128 bytes per entry. 239174294Sobrien * This is too much probably, but it seems to work better because 240174294Sobrien * of the re-entrant nature of nfs_readdir, and esp. on systems 241174294Sobrien * like OpenBSD 2.2. 242174294Sobrien */ 243174294Sobrien chain_length = count / 128; 244174294Sobrien 245174294Sobrien /* reset static state counters */ 246174294Sobrien te = te_next = NULL; 247174294Sobrien 248174294Sobrien dp->dl_entries = ep; 249174294Sobrien 250174294Sobrien /* construct "." */ 251174294Sobrien ep[0].ne_fileid = mp->am_gen; 252174294Sobrien ep[0].ne_name = "."; 253174294Sobrien ep[0].ne_nextentry = &ep[1]; 254174294Sobrien *(u_int *) ep[0].ne_cookie = 0; 255174294Sobrien 256174294Sobrien /* construct ".." */ 257174294Sobrien if (mp->am_parent) 258174294Sobrien ep[1].ne_fileid = mp->am_parent->am_gen; 259174294Sobrien else 260174294Sobrien ep[1].ne_fileid = mp->am_gen; 261174294Sobrien 262174294Sobrien ep[1].ne_name = ".."; 263310490Scy ep[1].ne_nextentry = NULL; 264310490Scy (void)memcpy(ep[1].ne_cookie, &dotdotcookie, sizeof(dotdotcookie)); 265174294Sobrien 266174294Sobrien /* 267174294Sobrien * If map is browsable, call a function make_entry_chain() to construct 268174294Sobrien * a linked list of unmounted keys, and return it. Then link the chain 269174294Sobrien * to the regular list. Get the chain only once, but return 270174294Sobrien * chunks of it each time. 271174294Sobrien */ 272174294Sobrien te = make_entry_chain(mp, dp->dl_entries, fully_browsable); 273174294Sobrien if (!te) 274174294Sobrien return 0; 275174294Sobrien if (amuDebug(D_READDIR)) { 276174294Sobrien nfsentry *ne; 277174294Sobrien for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 278174294Sobrien plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name); 279174294Sobrien } 280174294Sobrien 281174294Sobrien /* return only "chain_length" entries */ 282174294Sobrien te_next = te; 283174294Sobrien for (i=1; i<chain_length; ++i) { 284174294Sobrien te_next = te_next->ne_nextentry; 285174294Sobrien if (!te_next) 286174294Sobrien break; 287174294Sobrien } 288174294Sobrien if (te_next) { 289174294Sobrien nfsentry *te_saved = te_next->ne_nextentry; 290174294Sobrien te_next->ne_nextentry = NULL; /* terminate "te" chain */ 291174294Sobrien te_next = te_saved; /* save rest of "te" for next iteration */ 292174294Sobrien dp->dl_eof = FALSE; /* tell readdir there's more */ 293174294Sobrien } else { 294174294Sobrien dp->dl_eof = TRUE; /* tell readdir that's it */ 295174294Sobrien } 296174294Sobrien ep[1].ne_nextentry = te; /* append this chunk of "te" chain */ 297174294Sobrien if (amuDebug(D_READDIR)) { 298174294Sobrien nfsentry *ne; 299174294Sobrien for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 300174294Sobrien plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->ne_name); 301174294Sobrien for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) 302174294Sobrien plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%d ck=%d", 303174294Sobrien j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie); 304174294Sobrien plog(XLOG_DEBUG, "EOF is %d", dp->dl_eof); 305174294Sobrien } 306174294Sobrien return 0; 307174294Sobrien } /* end of "if (gen == 0)" statement */ 308174294Sobrien 309310490Scy dlog("%s: real child", __func__); 310174294Sobrien 311174294Sobrien if (gen == DOT_DOT_COOKIE) { 312310490Scy dlog("%s: End of readdir in %s", __func__, mp->am_path); 313174294Sobrien dp->dl_eof = TRUE; 314310490Scy dp->dl_entries = NULL; 315174294Sobrien return 0; 316174294Sobrien } 317174294Sobrien 318174294Sobrien /* 319174294Sobrien * If browsable directories, then continue serving readdir() with another 320174294Sobrien * chunk of entries, starting from where we left off (when gen was equal 321174294Sobrien * to 0). Once again, assume last chunk served to readdir. 322174294Sobrien */ 323174294Sobrien dp->dl_eof = TRUE; 324174294Sobrien dp->dl_entries = ep; 325174294Sobrien 326174294Sobrien te = te_next; /* reset 'te' from last saved te_next */ 327174294Sobrien if (!te) { /* another indicator of end of readdir */ 328310490Scy dp->dl_entries = NULL; 329174294Sobrien return 0; 330174294Sobrien } 331174294Sobrien /* 332174294Sobrien * compute # of entries to send in this chain. 333174294Sobrien * heuristics: 128 bytes per entry. 334174294Sobrien */ 335174294Sobrien chain_length = count / 128; 336174294Sobrien 337174294Sobrien /* return only "chain_length" entries */ 338174294Sobrien for (i = 1; i < chain_length; ++i) { 339174294Sobrien te_next = te_next->ne_nextentry; 340174294Sobrien if (!te_next) 341174294Sobrien break; 342174294Sobrien } 343174294Sobrien if (te_next) { 344174294Sobrien nfsentry *te_saved = te_next->ne_nextentry; 345174294Sobrien te_next->ne_nextentry = NULL; /* terminate "te" chain */ 346174294Sobrien te_next = te_saved; /* save rest of "te" for next iteration */ 347174294Sobrien dp->dl_eof = FALSE; /* tell readdir there's more */ 348174294Sobrien } 349174294Sobrien ep = te; /* send next chunk of "te" chain */ 350174294Sobrien dp->dl_entries = ep; 351174294Sobrien if (amuDebug(D_READDIR)) { 352174294Sobrien nfsentry *ne; 353174294Sobrien plog(XLOG_DEBUG, "dl_entries=%p, te_next=%p, dl_eof=%d", 354174294Sobrien dp->dl_entries, te_next, dp->dl_eof); 355174294Sobrien for (ne = te; ne; ne = ne->ne_nextentry) 356174294Sobrien plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->ne_name); 357174294Sobrien } 358174294Sobrien return 0; 359174294Sobrien} 360174294Sobrien 361310490Scystatic int 362310490Scyamfs_readdir(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count) 363174294Sobrien{ 364310490Scy u_int gen = *(u_int *) (uintptr_t) cookie; 365174294Sobrien am_node *xp; 366174294Sobrien 367174294Sobrien dp->dl_eof = FALSE; /* assume readdir not done */ 368174294Sobrien 369174294Sobrien /* when gen is 0, we start reading from the beginning of the directory */ 370174294Sobrien if (gen == 0) { 371174294Sobrien /* 372174294Sobrien * In the default instance (which is used to start a search) we return 373174294Sobrien * "." and "..". 374174294Sobrien * 375174294Sobrien * This assumes that the count is big enough to allow both "." and ".." 376174294Sobrien * to be returned in a single packet. If it isn't (which would be 377174294Sobrien * fairly unbelievable) then tough. 378174294Sobrien */ 379310490Scy dlog("%s: default search", __func__); 380174294Sobrien /* 381174294Sobrien * Check for enough room. This is extremely approximate but is more 382174294Sobrien * than enough space. Really need 2 times: 383174294Sobrien * 4byte fileid 384174294Sobrien * 4byte cookie 385174294Sobrien * 4byte name length 386174294Sobrien * 4byte name 387174294Sobrien * plus the dirlist structure */ 388310490Scy#define NEEDROOM (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp))) 389310490Scy if (count < NEEDROOM) { 390310490Scy dlog("%s: not enough room %u < %zu", __func__, count, NEEDROOM); 391174294Sobrien return EINVAL; 392310490Scy } 393174294Sobrien 394174294Sobrien xp = next_nonerror_node(mp->am_child); 395174294Sobrien dp->dl_entries = ep; 396174294Sobrien 397174294Sobrien /* construct "." */ 398174294Sobrien ep[0].ne_fileid = mp->am_gen; 399174294Sobrien ep[0].ne_name = "."; 400174294Sobrien ep[0].ne_nextentry = &ep[1]; 401174294Sobrien *(u_int *) ep[0].ne_cookie = 0; 402174294Sobrien 403174294Sobrien /* construct ".." */ 404174294Sobrien if (mp->am_parent) 405174294Sobrien ep[1].ne_fileid = mp->am_parent->am_gen; 406174294Sobrien else 407174294Sobrien ep[1].ne_fileid = mp->am_gen; 408174294Sobrien ep[1].ne_name = ".."; 409310490Scy ep[1].ne_nextentry = NULL; 410310490Scy (void)memcpy(ep[1].ne_cookie, (xp ? &xp->am_gen : &dotdotcookie), 411310490Scy sizeof(dotdotcookie)); 412174294Sobrien 413174294Sobrien if (!xp) 414174294Sobrien dp->dl_eof = TRUE; /* by default assume readdir done */ 415174294Sobrien 416174294Sobrien if (amuDebug(D_READDIR)) { 417174294Sobrien nfsentry *ne; 418174294Sobrien int j; 419174294Sobrien for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) 420174294Sobrien plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%d ck=%d", 421174294Sobrien j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie); 422174294Sobrien } 423174294Sobrien return 0; 424174294Sobrien } 425310490Scy dlog("%s: real child", __func__); 426174294Sobrien 427174294Sobrien if (gen == DOT_DOT_COOKIE) { 428310490Scy dlog("%s: End of readdir in %s", __func__, mp->am_path); 429174294Sobrien dp->dl_eof = TRUE; 430310490Scy dp->dl_entries = NULL; 431174294Sobrien if (amuDebug(D_READDIR)) 432174294Sobrien plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n"); 433174294Sobrien return 0; 434174294Sobrien } 435174294Sobrien 436174294Sobrien /* non-browsable directories code */ 437174294Sobrien xp = mp->am_child; 438174294Sobrien while (xp && xp->am_gen != gen) 439174294Sobrien xp = xp->am_osib; 440174294Sobrien 441174294Sobrien if (xp) { 442174294Sobrien int nbytes = count / 2; /* conservative */ 443174294Sobrien int todo = MAX_READDIR_ENTRIES; 444174294Sobrien 445174294Sobrien dp->dl_entries = ep; 446174294Sobrien do { 447174294Sobrien am_node *xp_next = next_nonerror_node(xp->am_osib); 448174294Sobrien 449174294Sobrien if (xp_next) { 450174294Sobrien *(u_int *) ep->ne_cookie = xp_next->am_gen; 451174294Sobrien } else { 452174294Sobrien *(u_int *) ep->ne_cookie = DOT_DOT_COOKIE; 453174294Sobrien dp->dl_eof = TRUE; 454174294Sobrien } 455174294Sobrien 456174294Sobrien ep->ne_fileid = xp->am_gen; 457174294Sobrien ep->ne_name = xp->am_name; 458174294Sobrien nbytes -= sizeof(*ep) + 1; 459174294Sobrien if (xp->am_name) 460174294Sobrien nbytes -= strlen(xp->am_name); 461174294Sobrien 462174294Sobrien xp = xp_next; 463174294Sobrien 464174294Sobrien if (nbytes > 0 && !dp->dl_eof && todo > 1) { 465174294Sobrien ep->ne_nextentry = ep + 1; 466174294Sobrien ep++; 467174294Sobrien --todo; 468174294Sobrien } else { 469174294Sobrien todo = 0; 470174294Sobrien } 471174294Sobrien } while (todo > 0); 472174294Sobrien 473310490Scy ep->ne_nextentry = NULL; 474174294Sobrien 475174294Sobrien if (amuDebug(D_READDIR)) { 476174294Sobrien nfsentry *ne; 477174294Sobrien int j; 478174294Sobrien for (j=0,ne=ep; ne; ne=ne->ne_nextentry) 479174294Sobrien plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%d ck=%d", 480174294Sobrien j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie); 481174294Sobrien } 482174294Sobrien return 0; 483174294Sobrien } 484174294Sobrien return ESTALE; 485174294Sobrien} 486310490Scy 487310490Scy/* 488310490Scy * Search a chain for an entry with some name. 489310490Scy */ 490310490Scystatic int 491310490Scykey_already_in_chain3(char *keyname, const am_entry3 *chain) 492310490Scy{ 493310490Scy const am_entry3 *tmpchain = chain; 494310490Scy 495310490Scy while (tmpchain) { 496310490Scy if (keyname && tmpchain->name && STREQ(keyname, tmpchain->name)) 497310490Scy return 1; 498310490Scy tmpchain = tmpchain->nextentry; 499310490Scy } 500310490Scy 501310490Scy return 0; 502310490Scy} 503310490Scy 504310490Scy/* 505310490Scy * Create a chain of entries which are not linked. 506310490Scy */ 507310490Scystatic am_entry3 * 508310490Scymake_entry_chain3(am_node *mp, const am_entry3 *current_chain, int fully_browsable) 509310490Scy{ 510310490Scy static uint64 last_cookie = (uint64) 2; /* monotonically increasing */ 511310490Scy static am_entry3 chain[MAX_CHAIN]; 512310490Scy static int max_entries = MAX_CHAIN; 513310490Scy char *key; 514310490Scy int num_entries = 0, i; 515310490Scy u_int preflen = 0; 516310490Scy am_entry3 *retval = (am_entry3 *) NULL; 517310490Scy mntfs *mf; 518310490Scy mnt_map *mmp; 519310490Scy 520310490Scy if (!mp) { 521310490Scy plog(XLOG_DEBUG, "make_entry_chain3: mp is (NULL)"); 522310490Scy return retval; 523310490Scy } 524310490Scy mf = mp->am_al->al_mnt; 525310490Scy if (!mf) { 526310490Scy plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt is (NULL)"); 527310490Scy return retval; 528310490Scy } 529310490Scy mmp = (mnt_map *) mf->mf_private; 530310490Scy if (!mmp) { 531310490Scy plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt->mf_private is (NULL)"); 532310490Scy return retval; 533310490Scy } 534310490Scy 535310490Scy if (mp->am_pref) 536310490Scy preflen = strlen(mp->am_pref); 537310490Scy 538310490Scy /* iterate over keys */ 539310490Scy for (i = 0; i < NKVHASH; i++) { 540310490Scy kv *k; 541310490Scy for (k = mmp->kvhash[i]; k ; k = k->next) { 542310490Scy 543310490Scy /* 544310490Scy * Skip unwanted entries which are either not real entries or 545310490Scy * very difficult to interpret (wildcards...) This test needs 546310490Scy * lots of improvement. Any takers? 547310490Scy */ 548310490Scy key = k->key; 549310490Scy if (!key) 550310490Scy continue; 551310490Scy 552310490Scy /* Skip '/defaults' */ 553310490Scy if (STREQ(key, "/defaults")) 554310490Scy continue; 555310490Scy 556310490Scy /* Skip '*' */ 557310490Scy if (!fully_browsable && strchr(key, '*')) 558310490Scy continue; 559310490Scy 560310490Scy /* 561310490Scy * If the map has a prefix-string then check if the key starts with 562310490Scy * this string, and if it does, skip over this prefix. If it has a 563310490Scy * prefix and it doesn't match the start of the key, skip it. 564310490Scy */ 565310490Scy if (preflen) { 566310490Scy if (preflen > strlen(key)) 567310490Scy continue; 568310490Scy if (!NSTREQ(key, mp->am_pref, preflen)) 569310490Scy continue; 570310490Scy key += preflen; 571310490Scy } 572310490Scy 573310490Scy /* no more '/' are allowed, unless browsable_dirs=full was used */ 574310490Scy if (!fully_browsable && strchr(key, '/')) 575310490Scy continue; 576310490Scy 577310490Scy /* no duplicates allowed */ 578310490Scy if (key_already_in_chain3(key, current_chain)) 579310490Scy continue; 580310490Scy 581310490Scy /* fill in a cell and link the entry */ 582310490Scy if (num_entries >= max_entries) { 583310490Scy /* out of space */ 584310490Scy plog(XLOG_DEBUG, "make_entry_chain3: no more space in chain"); 585310490Scy if (num_entries > 0) { 586310490Scy chain[num_entries - 1].nextentry = NULL; 587310490Scy retval = &chain[0]; 588310490Scy } 589310490Scy return retval; 590310490Scy } 591310490Scy 592310490Scy /* we have space. put entry in next cell */ 593310490Scy ++last_cookie; 594310490Scy chain[num_entries].fileid = last_cookie; 595310490Scy chain[num_entries].cookie = last_cookie; 596310490Scy chain[num_entries].name = key; 597310490Scy if (num_entries < max_entries - 1) { /* link to next one */ 598310490Scy chain[num_entries].nextentry = &chain[num_entries + 1]; 599310490Scy } 600310490Scy ++num_entries; 601310490Scy } /* end of "while (k)" */ 602310490Scy } /* end of "for (i ... NKVHASH ..." */ 603310490Scy 604310490Scy /* terminate chain */ 605310490Scy if (num_entries > 0) { 606310490Scy chain[num_entries - 1].nextentry = NULL; 607310490Scy retval = &chain[0]; 608310490Scy } 609310490Scy 610310490Scy return retval; 611310490Scy} 612310490Scy 613310490Scystatic size_t needroom3(void) 614310490Scy{ 615310490Scy /* 616310490Scy * Check for enough room. This is extremely approximate but should 617310490Scy * be enough space. Really need 2 times: 618310490Scy * (8byte fileid 619310490Scy * 8byte cookie 620310490Scy * 8byte name pointer 621310490Scy * 8byte next entry addres) = sizeof(am_entry3) 622310490Scy * 2byte name + 1byte terminator 623310490Scy * plus the size of the am_dirlist3 structure */ 624310490Scy return ((2 * ((sizeof(am_entry3) + sizeof("..") + 1))) + sizeof(am_dirlist3)); 625310490Scy} 626310490Scy 627310490Scy/* This one is called only if map is browsable */ 628310490Scystatic int 629310490Scyamfs_readdir3_browsable(am_node *mp, am_cookie3 cookie, 630310490Scy am_dirlist3 *dp, am_entry3 *ep, u_int count, 631310490Scy int fully_browsable) 632310490Scy{ 633310490Scy uint64 gen = *(uint64 *) (uintptr_t) cookie; 634310490Scy int chain_length, i; 635310490Scy static am_entry3 *te, *te_next; 636310490Scy static int j; 637310490Scy 638310490Scy dp->eof = FALSE; /* assume readdir not done */ 639310490Scy 640310490Scy if (amuDebug(D_READDIR)) 641310490Scy plog(XLOG_DEBUG, "amfs_readdir3_browsable gen=%lu, count=%d", (long unsigned) gen, count); 642310490Scy 643310490Scy if (gen == 0) { 644310490Scy size_t needed = needroom3(); 645310490Scy /* 646310490Scy * In the default instance (which is used to start a search) we return 647310490Scy * "." and "..". 648310490Scy * 649310490Scy * This assumes that the count is big enough to allow both "." and ".." 650310490Scy * to be returned in a single packet. If it isn't (which would be 651310490Scy * fairly unbelievable) then tough. 652310490Scy */ 653310490Scy dlog("%s: default search", __func__); 654310490Scy 655310490Scy if (count < needed) { 656310490Scy dlog("%s: not enough room %u < %zu", __func__, count, needed); 657310490Scy return EINVAL; 658310490Scy } 659310490Scy 660310490Scy /* 661310490Scy * compute # of entries to send in this chain. 662310490Scy * heuristics: 128 bytes per entry. 663310490Scy * This is too much probably, but it seems to work better because 664310490Scy * of the re-entrant nature of nfs_readdir, and esp. on systems 665310490Scy * like OpenBSD 2.2. 666310490Scy */ 667310490Scy chain_length = count / 128; 668310490Scy 669310490Scy /* reset static state counters */ 670310490Scy te = te_next = NULL; 671310490Scy 672310490Scy dp->entries = ep; 673310490Scy 674310490Scy /* construct "." */ 675310490Scy ep[0].fileid = mp->am_gen; 676310490Scy ep[0].name = "."; 677310490Scy ep[0].nextentry = &ep[1]; 678310490Scy ep[0].cookie = 0; 679310490Scy 680310490Scy /* construct ".." */ 681310490Scy if (mp->am_parent) 682310490Scy ep[1].fileid = mp->am_parent->am_gen; 683310490Scy else 684310490Scy ep[1].fileid = mp->am_gen; 685310490Scy 686310490Scy ep[1].name = ".."; 687310490Scy ep[1].nextentry = NULL; 688310490Scy ep[1].cookie = dotdotcookie; 689310490Scy 690310490Scy /* 691310490Scy * If map is browsable, call a function make_entry_chain() to construct 692310490Scy * a linked list of unmounted keys, and return it. Then link the chain 693310490Scy * to the regular list. Get the chain only once, but return 694310490Scy * chunks of it each time. 695310490Scy */ 696310490Scy te = make_entry_chain3(mp, dp->entries, fully_browsable); 697310490Scy if (!te) 698310490Scy return 0; 699310490Scy if (amuDebug(D_READDIR)) { 700310490Scy am_entry3 *ne; 701310490Scy for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 702310490Scy plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name); 703310490Scy } 704310490Scy 705310490Scy /* return only "chain_length" entries */ 706310490Scy te_next = te; 707310490Scy for (i=1; i<chain_length; ++i) { 708310490Scy te_next = te_next->nextentry; 709310490Scy if (!te_next) 710310490Scy break; 711310490Scy } 712310490Scy if (te_next) { 713310490Scy am_entry3 *te_saved = te_next->nextentry; 714310490Scy te_next->nextentry = NULL; /* terminate "te" chain */ 715310490Scy te_next = te_saved; /* save rest of "te" for next iteration */ 716310490Scy dp->eof = FALSE; /* tell readdir there's more */ 717310490Scy } else { 718310490Scy dp->eof = TRUE; /* tell readdir that's it */ 719310490Scy } 720310490Scy ep[1].nextentry = te; /* append this chunk of "te" chain */ 721310490Scy if (amuDebug(D_READDIR)) { 722310490Scy am_entry3 *ne; 723310490Scy for (j = 0, ne = te; ne; ne = ne->ne_nextentry) 724310490Scy plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->name); 725310490Scy for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) { 726310490Scy plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%lu ck=%lu", 727310490Scy j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 728310490Scy } 729310490Scy plog(XLOG_DEBUG, "EOF is %d", dp->eof); 730310490Scy } 731310490Scy return 0; 732310490Scy } /* end of "if (gen == 0)" statement */ 733310490Scy 734310490Scy dlog("%s: real child", __func__); 735310490Scy 736310490Scy if (gen == DOT_DOT_COOKIE) { 737310490Scy dlog("%s: End of readdir in %s", __func__, mp->am_path); 738310490Scy dp->eof = TRUE; 739310490Scy dp->entries = NULL; 740310490Scy return 0; 741310490Scy } 742310490Scy 743310490Scy /* 744310490Scy * If browsable directories, then continue serving readdir() with another 745310490Scy * chunk of entries, starting from where we left off (when gen was equal 746310490Scy * to 0). Once again, assume last chunk served to readdir. 747310490Scy */ 748310490Scy dp->eof = TRUE; 749310490Scy dp->entries = ep; 750310490Scy 751310490Scy te = te_next; /* reset 'te' from last saved te_next */ 752310490Scy if (!te) { /* another indicator of end of readdir */ 753310490Scy dp->entries = NULL; 754310490Scy return 0; 755310490Scy } 756310490Scy /* 757310490Scy * compute # of entries to send in this chain. 758310490Scy * heuristics: 128 bytes per entry. 759310490Scy */ 760310490Scy chain_length = count / 128; 761310490Scy 762310490Scy /* return only "chain_length" entries */ 763310490Scy for (i = 1; i < chain_length; ++i) { 764310490Scy te_next = te_next->nextentry; 765310490Scy if (!te_next) 766310490Scy break; 767310490Scy } 768310490Scy if (te_next) { 769310490Scy am_entry3 *te_saved = te_next->nextentry; 770310490Scy te_next->nextentry = NULL; /* terminate "te" chain */ 771310490Scy te_next = te_saved; /* save rest of "te" for next iteration */ 772310490Scy dp->eof = FALSE; /* tell readdir there's more */ 773310490Scy } 774310490Scy ep = te; /* send next chunk of "te" chain */ 775310490Scy dp->entries = ep; 776310490Scy if (amuDebug(D_READDIR)) { 777310490Scy am_entry3 *ne; 778310490Scy plog(XLOG_DEBUG, 779310490Scy "entries=%p, te_next=%p, eof=%d", dp->entries, te_next, dp->eof); 780310490Scy for (ne = te; ne; ne = ne->nextentry) 781310490Scy plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->name); 782310490Scy } 783310490Scy return 0; 784310490Scy} 785310490Scy 786310490Scystatic int 787310490Scyamfs_readdir3(am_node *mp, am_cookie3 cookie, 788310490Scy am_dirlist3 *dp, am_entry3 *ep, u_int count) 789310490Scy{ 790310490Scy uint64 gen = *(uint64 *) (uintptr_t) cookie; 791310490Scy am_node *xp; 792310490Scy 793310490Scy if (amuDebug(D_READDIR)) 794310490Scy plog(XLOG_DEBUG, "amfs_readdir3 gen=%lu, count=%d", (long unsigned) gen, count); 795310490Scy 796310490Scy dp->eof = FALSE; /* assume readdir not done */ 797310490Scy 798310490Scy /* when gen is 0, we start reading from the beginning of the directory */ 799310490Scy if (gen == 0) { 800310490Scy size_t needed = needroom3(); 801310490Scy /* 802310490Scy * In the default instance (which is used to start a search) we return 803310490Scy * "." and "..". 804310490Scy * 805310490Scy * This assumes that the count is big enough to allow both "." and ".." 806310490Scy * to be returned in a single packet. If it isn't (which would be 807310490Scy * fairly unbelievable) then tough. 808310490Scy */ 809310490Scy dlog("%s: default search", __func__); 810310490Scy 811310490Scy if (count < needed) { 812310490Scy dlog("%s: not enough room %u < %zu", __func__, count, needed); 813310490Scy return EINVAL; 814310490Scy } 815310490Scy 816310490Scy xp = next_nonerror_node(mp->am_child); 817310490Scy dp->entries = ep; 818310490Scy 819310490Scy /* construct "." */ 820310490Scy ep[0].fileid = mp->am_gen; 821310490Scy ep[0].name = "."; 822310490Scy ep[0].cookie = 0; 823310490Scy ep[0].nextentry = &ep[1]; 824310490Scy 825310490Scy /* construct ".." */ 826310490Scy if (mp->am_parent) 827310490Scy ep[1].fileid = mp->am_parent->am_gen; 828310490Scy else 829310490Scy ep[1].fileid = mp->am_gen; 830310490Scy ep[1].name = ".."; 831310490Scy ep[1].nextentry = NULL; 832310490Scy ep[1].cookie = (xp ? xp->am_gen : dotdotcookie); 833310490Scy 834310490Scy if (!xp) 835310490Scy dp->eof = TRUE; /* by default assume readdir done */ 836310490Scy 837310490Scy if (amuDebug(D_READDIR)) { 838310490Scy am_entry3 *ne; 839310490Scy int j; 840310490Scy for (j = 0, ne = ep; ne; ne = ne->nextentry) { 841310490Scy plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%lu ck=%lu", 842310490Scy j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 843310490Scy } 844310490Scy } 845310490Scy return 0; 846310490Scy } 847310490Scy dlog("%s: real child", __func__); 848310490Scy 849310490Scy if (gen == (uint64) DOT_DOT_COOKIE) { 850310490Scy dlog("%s: End of readdir in %s", __func__, mp->am_path); 851310490Scy dp->eof = TRUE; 852310490Scy dp->entries = NULL; 853310490Scy if (amuDebug(D_READDIR)) 854310490Scy plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n"); 855310490Scy return 0; 856310490Scy } 857310490Scy 858310490Scy /* non-browsable directories code */ 859310490Scy xp = mp->am_child; 860310490Scy while (xp && xp->am_gen != gen) 861310490Scy xp = xp->am_osib; 862310490Scy 863310490Scy if (xp) { 864310490Scy int nbytes = count / 2; /* conservative */ 865310490Scy int todo = MAX_READDIR_ENTRIES; 866310490Scy 867310490Scy dp->entries = ep; 868310490Scy do { 869310490Scy am_node *xp_next = next_nonerror_node(xp->am_osib); 870310490Scy 871310490Scy if (xp_next) { 872310490Scy ep->cookie = xp_next->am_gen; 873310490Scy } else { 874310490Scy ep->cookie = (uint64) dotdotcookie; 875310490Scy dp->eof = TRUE; 876310490Scy } 877310490Scy 878310490Scy ep->fileid = xp->am_gen; 879310490Scy ep->name = xp->am_name; 880310490Scy nbytes -= sizeof(*ep) + 1; 881310490Scy if (xp->am_name) 882310490Scy nbytes -= strlen(xp->am_name); 883310490Scy 884310490Scy xp = xp_next; 885310490Scy 886310490Scy if (nbytes > 0 && !dp->dl_eof && todo > 1) { 887310490Scy ep->nextentry = ep + 1; 888310490Scy ep++; 889310490Scy --todo; 890310490Scy } else { 891310490Scy todo = 0; 892310490Scy } 893310490Scy } while (todo > 0); 894310490Scy 895310490Scy ep->nextentry = NULL; 896310490Scy 897310490Scy if (amuDebug(D_READDIR)) { 898310490Scy am_entry3 *ne; 899310490Scy int j; 900310490Scy for (j = 0, ne = ep; ne; ne = ne->nextentry) { 901310490Scy plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%lu ck=%lu", 902310490Scy j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie); 903310490Scy } 904310490Scy } 905310490Scy return 0; 906310490Scy } 907310490Scy return ESTALE; 908310490Scy} 909310490Scy 910310490Scy/* 911310490Scy * This readdir function which call a special version of it that allows 912310490Scy * browsing if browsable_dirs=yes was set on the map. 913310490Scy */ 914310490Scyint 915310490Scyamfs_generic_readdir(am_node *mp, voidp cookie, voidp dp, voidp ep, u_int count) 916310490Scy{ 917310490Scy int browsable, full; 918310490Scy 919310490Scy /* check if map is browsable */ 920310490Scy browsable = 0; 921310490Scy if (mp->am_al->al_mnt && mp->am_al->al_mnt->mf_mopts) { 922310490Scy mntent_t mnt; 923310490Scy mnt.mnt_opts = mp->am_al->al_mnt->mf_mopts; 924310490Scy if (amu_hasmntopt(&mnt, "fullybrowsable")) 925310490Scy browsable = 2; 926310490Scy else if (amu_hasmntopt(&mnt, "browsable")) 927310490Scy browsable = 1; 928310490Scy } 929310490Scy full = (browsable == 2); 930310490Scy 931310490Scy if (nfs_dispatcher == nfs_program_2) { 932310490Scy if (browsable) 933310490Scy return amfs_readdir_browsable(mp, cookie, dp, ep, count, full); 934310490Scy else 935310490Scy return amfs_readdir(mp, cookie, dp, ep, count); 936310490Scy } else { 937310490Scy if (browsable) 938310490Scy return amfs_readdir3_browsable(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count, full); 939310490Scy else 940310490Scy return amfs_readdir3(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count); 941310490Scy } 942310490Scy} 943