Home | History | Annotate | Line # | Download | only in amd
      1 /*	$NetBSD: readdir.c,v 1.4 2015/01/18 16:37:05 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  ****************************************************************************/
     59 static int key_already_in_chain(char *keyname, const nfsentry *chain);
     60 static nfsentry *make_entry_chain(am_node *mp, const nfsentry *current_chain, int fully_browsable);
     61 static int amfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable);
     62 
     63 static 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 (at) cs.columbia.edu>
     72  */
     73 static int
     74 key_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 (at) cs.columbia.edu>
     91  */
     92 static nfsentry *
     93 make_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 */
    202 static int
    203 amfs_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 
    365 static int
    366 amfs_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  */
    500 static int
    501 key_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  */
    517 static am_entry3 *
    518 make_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 
    623 static 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 */
    638 static int
    639 amfs_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 
    798 static int
    799 amfs_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  */
    929 int
    930 amfs_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