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 = (u_int) last_cookie;
180      *(u_int *) chain[num_entries].ne_cookie = (u_int) last_cookie;
181      chain[num_entries].ne_name = key;
182      if (num_entries < max_entries - 1) {	/* link to next one */
183	chain[num_entries].ne_nextentry = &chain[num_entries + 1];
184      }
185      ++num_entries;
186    } /* end of "while (k)" */
187  } /* end of "for (i ... NKVHASH ..." */
188
189  /* terminate chain */
190  if (num_entries > 0) {
191    chain[num_entries - 1].ne_nextentry = NULL;
192    retval = &chain[0];
193  }
194
195  return retval;
196}
197
198
199
200/* This one is called only if map is browsable */
201static int
202amfs_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count, int fully_browsable)
203{
204  u_int gen = *(u_int *) (uintptr_t) cookie;
205  int chain_length, i;
206  static nfsentry *te, *te_next;
207  static int j;
208
209  dp->dl_eof = FALSE;		/* assume readdir not done */
210
211  if (amuDebug(D_READDIR))
212    plog(XLOG_DEBUG, "amfs_readdir_browsable gen=%u, count=%d",
213	 gen, count);
214
215  if (gen == 0) {
216    /*
217     * In the default instance (which is used to start a search) we return
218     * "." and "..".
219     *
220     * This assumes that the count is big enough to allow both "." and ".."
221     * to be returned in a single packet.  If it isn't (which would be
222     * fairly unbelievable) then tough.
223     */
224    dlog("%s: default search", __func__);
225    /*
226     * Check for enough room.  This is extremely approximate but is more
227     * than enough space.  Really need 2 times:
228     *      4byte fileid
229     *      4byte cookie
230     *      4byte name length
231     *      4byte name
232     * plus the dirlist structure */
233    if (count < (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp))))
234      return EINVAL;
235
236    /*
237     * compute # of entries to send in this chain.
238     * heuristics: 128 bytes per entry.
239     * This is too much probably, but it seems to work better because
240     * of the re-entrant nature of nfs_readdir, and esp. on systems
241     * like OpenBSD 2.2.
242     */
243    chain_length = count / 128;
244
245    /* reset static state counters */
246    te = te_next = NULL;
247
248    dp->dl_entries = ep;
249
250    /* construct "." */
251    ep[0].ne_fileid = mp->am_gen;
252    ep[0].ne_name = ".";
253    ep[0].ne_nextentry = &ep[1];
254    *(u_int *) ep[0].ne_cookie = 0;
255
256    /* construct ".." */
257    if (mp->am_parent)
258      ep[1].ne_fileid = mp->am_parent->am_gen;
259    else
260      ep[1].ne_fileid = mp->am_gen;
261
262    ep[1].ne_name = "..";
263    ep[1].ne_nextentry = NULL;
264    (void)memcpy(ep[1].ne_cookie, &dotdotcookie, sizeof(dotdotcookie));
265
266    /*
267     * If map is browsable, call a function make_entry_chain() to construct
268     * a linked list of unmounted keys, and return it.  Then link the chain
269     * to the regular list.  Get the chain only once, but return
270     * chunks of it each time.
271     */
272    te = make_entry_chain(mp, dp->dl_entries, fully_browsable);
273    if (!te)
274      return 0;
275    if (amuDebug(D_READDIR)) {
276      nfsentry *ne;
277      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
278	plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name);
279    }
280
281    /* return only "chain_length" entries */
282    te_next = te;
283    for (i=1; i<chain_length; ++i) {
284      te_next = te_next->ne_nextentry;
285      if (!te_next)
286	break;
287    }
288    if (te_next) {
289      nfsentry *te_saved = te_next->ne_nextentry;
290      te_next->ne_nextentry = NULL; /* terminate "te" chain */
291      te_next = te_saved;	/* save rest of "te" for next iteration */
292      dp->dl_eof = FALSE;	/* tell readdir there's more */
293    } else {
294      dp->dl_eof = TRUE;	/* tell readdir that's it */
295    }
296    ep[1].ne_nextentry = te;	/* append this chunk of "te" chain */
297    if (amuDebug(D_READDIR)) {
298      nfsentry *ne;
299      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
300	plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->ne_name);
301      for (j = 0, ne = ep; ne; ne = ne->ne_nextentry)
302	plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%d ck=%d",
303	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
304      plog(XLOG_DEBUG, "EOF is %d", dp->dl_eof);
305    }
306    return 0;
307  } /* end of "if (gen == 0)" statement */
308
309  dlog("%s: real child", __func__);
310
311  if (gen == DOT_DOT_COOKIE) {
312    dlog("%s: End of readdir in %s", __func__, mp->am_path);
313    dp->dl_eof = TRUE;
314    dp->dl_entries = NULL;
315    return 0;
316  }
317
318  /*
319   * If browsable directories, then continue serving readdir() with another
320   * chunk of entries, starting from where we left off (when gen was equal
321   * to 0).  Once again, assume last chunk served to readdir.
322   */
323  dp->dl_eof = TRUE;
324  dp->dl_entries = ep;
325
326  te = te_next;			/* reset 'te' from last saved te_next */
327  if (!te) {			/* another indicator of end of readdir */
328    dp->dl_entries = NULL;
329    return 0;
330  }
331  /*
332   * compute # of entries to send in this chain.
333   * heuristics: 128 bytes per entry.
334   */
335  chain_length = count / 128;
336
337  /* return only "chain_length" entries */
338  for (i = 1; i < chain_length; ++i) {
339    te_next = te_next->ne_nextentry;
340    if (!te_next)
341      break;
342  }
343  if (te_next) {
344    nfsentry *te_saved = te_next->ne_nextentry;
345    te_next->ne_nextentry = NULL; /* terminate "te" chain */
346    te_next = te_saved;		/* save rest of "te" for next iteration */
347    dp->dl_eof = FALSE;		/* tell readdir there's more */
348  }
349  ep = te;			/* send next chunk of "te" chain */
350  dp->dl_entries = ep;
351  if (amuDebug(D_READDIR)) {
352    nfsentry *ne;
353    plog(XLOG_DEBUG, "dl_entries=%p, te_next=%p, dl_eof=%d",
354	 dp->dl_entries, te_next, dp->dl_eof);
355    for (ne = te; ne; ne = ne->ne_nextentry)
356      plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->ne_name);
357  }
358  return 0;
359}
360
361static int
362amfs_readdir(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, u_int count)
363{
364  u_int gen = *(u_int *) (uintptr_t) cookie;
365  am_node *xp;
366
367  dp->dl_eof = FALSE;		/* assume readdir not done */
368
369  /* when gen is 0, we start reading from the beginning of the directory */
370  if (gen == 0) {
371    /*
372     * In the default instance (which is used to start a search) we return
373     * "." and "..".
374     *
375     * This assumes that the count is big enough to allow both "." and ".."
376     * to be returned in a single packet.  If it isn't (which would be
377     * fairly unbelievable) then tough.
378     */
379    dlog("%s: default search", __func__);
380    /*
381     * Check for enough room.  This is extremely approximate but is more
382     * than enough space.  Really need 2 times:
383     *      4byte fileid
384     *      4byte cookie
385     *      4byte name length
386     *      4byte name
387     * plus the dirlist structure */
388#define NEEDROOM (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp)))
389    if (count < NEEDROOM) {
390      dlog("%s: not enough room %u < %zu", __func__, count, NEEDROOM);
391      return EINVAL;
392    }
393
394    xp = next_nonerror_node(mp->am_child);
395    dp->dl_entries = ep;
396
397    /* construct "." */
398    ep[0].ne_fileid = mp->am_gen;
399    ep[0].ne_name = ".";
400    ep[0].ne_nextentry = &ep[1];
401    *(u_int *) ep[0].ne_cookie = 0;
402
403    /* construct ".." */
404    if (mp->am_parent)
405      ep[1].ne_fileid = mp->am_parent->am_gen;
406    else
407      ep[1].ne_fileid = mp->am_gen;
408    ep[1].ne_name = "..";
409    ep[1].ne_nextentry = NULL;
410    (void)memcpy(ep[1].ne_cookie, (xp ? &xp->am_gen : &dotdotcookie),
411      sizeof(dotdotcookie));
412
413    if (!xp)
414      dp->dl_eof = TRUE;	/* by default assume readdir done */
415
416    if (amuDebug(D_READDIR)) {
417      nfsentry *ne;
418      int j;
419      for (j = 0, ne = ep; ne; ne = ne->ne_nextentry)
420	plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%d ck=%d",
421	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
422    }
423    return 0;
424  }
425  dlog("%s: real child", __func__);
426
427  if (gen == DOT_DOT_COOKIE) {
428    dlog("%s: End of readdir in %s", __func__, mp->am_path);
429    dp->dl_eof = TRUE;
430    dp->dl_entries = NULL;
431    if (amuDebug(D_READDIR))
432      plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n");
433    return 0;
434  }
435
436  /* non-browsable directories code */
437  xp = mp->am_child;
438  while (xp && xp->am_gen != gen)
439    xp = xp->am_osib;
440
441  if (xp) {
442    int nbytes = count / 2;	/* conservative */
443    int todo = MAX_READDIR_ENTRIES;
444
445    dp->dl_entries = ep;
446    do {
447      am_node *xp_next = next_nonerror_node(xp->am_osib);
448
449      if (xp_next) {
450	*(u_int *) ep->ne_cookie = xp_next->am_gen;
451      } else {
452	*(u_int *) ep->ne_cookie = DOT_DOT_COOKIE;
453	dp->dl_eof = TRUE;
454      }
455
456      ep->ne_fileid = xp->am_gen;
457      ep->ne_name = xp->am_name;
458      nbytes -= sizeof(*ep) + 1;
459      if (xp->am_name)
460	nbytes -= strlen(xp->am_name);
461
462      xp = xp_next;
463
464      if (nbytes > 0 && !dp->dl_eof && todo > 1) {
465	ep->ne_nextentry = ep + 1;
466	ep++;
467	--todo;
468      } else {
469	todo = 0;
470      }
471    } while (todo > 0);
472
473    ep->ne_nextentry = NULL;
474
475    if (amuDebug(D_READDIR)) {
476      nfsentry *ne;
477      int j;
478      for (j=0,ne=ep; ne; ne=ne->ne_nextentry)
479	plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%d ck=%d",
480	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
481    }
482    return 0;
483  }
484  return ESTALE;
485}
486
487/*
488 * Search a chain for an entry with some name.
489 */
490static int
491key_already_in_chain3(char *keyname, const am_entry3 *chain)
492{
493  const am_entry3 *tmpchain = chain;
494
495  while (tmpchain) {
496    if (keyname && tmpchain->name && STREQ(keyname, tmpchain->name))
497        return 1;
498    tmpchain = tmpchain->nextentry;
499  }
500
501  return 0;
502}
503
504/*
505 * Create a chain of entries which are not linked.
506 */
507static am_entry3 *
508make_entry_chain3(am_node *mp, const am_entry3 *current_chain, int fully_browsable)
509{
510  static uint64 last_cookie = (uint64) 2;	/* monotonically increasing */
511  static am_entry3 chain[MAX_CHAIN];
512  static int max_entries = MAX_CHAIN;
513  char *key;
514  int num_entries = 0, i;
515  u_int preflen = 0;
516  am_entry3 *retval = (am_entry3 *) NULL;
517  mntfs *mf;
518  mnt_map *mmp;
519
520  if (!mp) {
521    plog(XLOG_DEBUG, "make_entry_chain3: mp is (NULL)");
522    return retval;
523  }
524  mf = mp->am_al->al_mnt;
525  if (!mf) {
526    plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt is (NULL)");
527    return retval;
528  }
529  mmp = (mnt_map *) mf->mf_private;
530  if (!mmp) {
531    plog(XLOG_DEBUG, "make_entry_chain3: mp->am_al->al_mnt->mf_private is (NULL)");
532    return retval;
533  }
534
535  if (mp->am_pref)
536    preflen = strlen(mp->am_pref);
537
538  /* iterate over keys */
539  for (i = 0; i < NKVHASH; i++) {
540    kv *k;
541    for (k = mmp->kvhash[i]; k ; k = k->next) {
542
543      /*
544       * Skip unwanted entries which are either not real entries or
545       * very difficult to interpret (wildcards...)  This test needs
546       * lots of improvement.  Any takers?
547       */
548      key = k->key;
549      if (!key)
550	continue;
551
552      /* Skip '/defaults' */
553      if (STREQ(key, "/defaults"))
554	continue;
555
556      /* Skip '*' */
557      if (!fully_browsable && strchr(key, '*'))
558	continue;
559
560      /*
561       * If the map has a prefix-string then check if the key starts with
562       * this string, and if it does, skip over this prefix.  If it has a
563       * prefix and it doesn't match the start of the key, skip it.
564       */
565      if (preflen) {
566	if (preflen > strlen(key))
567	  continue;
568	if (!NSTREQ(key, mp->am_pref, preflen))
569	  continue;
570	key += preflen;
571      }
572
573      /* no more '/' are allowed, unless browsable_dirs=full was used */
574      if (!fully_browsable && strchr(key, '/'))
575	continue;
576
577      /* no duplicates allowed */
578      if (key_already_in_chain3(key, current_chain))
579	continue;
580
581      /* fill in a cell and link the entry */
582      if (num_entries >= max_entries) {
583	/* out of space */
584	plog(XLOG_DEBUG, "make_entry_chain3: no more space in chain");
585	if (num_entries > 0) {
586	  chain[num_entries - 1].nextentry = NULL;
587	  retval = &chain[0];
588	}
589	return retval;
590      }
591
592      /* we have space.  put entry in next cell */
593      ++last_cookie;
594      chain[num_entries].fileid = last_cookie;
595      chain[num_entries].cookie = last_cookie;
596      chain[num_entries].name = key;
597      if (num_entries < max_entries - 1) {	/* link to next one */
598	chain[num_entries].nextentry = &chain[num_entries + 1];
599      }
600      ++num_entries;
601    } /* end of "while (k)" */
602  } /* end of "for (i ... NKVHASH ..." */
603
604  /* terminate chain */
605  if (num_entries > 0) {
606    chain[num_entries - 1].nextentry = NULL;
607    retval = &chain[0];
608  }
609
610  return retval;
611}
612
613static size_t needroom3(void)
614{
615  /*
616   * Check for enough room.  This is extremely approximate but should
617   * be enough space.  Really need 2 times:
618   *      (8byte fileid
619   *      8byte cookie
620   *      8byte name pointer
621   *      8byte next entry addres) = sizeof(am_entry3)
622   *      2byte name + 1byte terminator
623   * plus the size of the am_dirlist3 structure */
624  return ((2 * ((sizeof(am_entry3) + sizeof("..") + 1))) + sizeof(am_dirlist3));
625}
626
627/* This one is called only if map is browsable */
628static int
629amfs_readdir3_browsable(am_node *mp, am_cookie3 cookie,
630			am_dirlist3 *dp, am_entry3 *ep, u_int count,
631			int fully_browsable)
632{
633  uint64 gen = *(uint64 *) (uintptr_t) cookie;
634  int chain_length, i;
635  static am_entry3 *te, *te_next;
636  static int j;
637
638  dp->eof = FALSE;		/* assume readdir not done */
639
640  if (amuDebug(D_READDIR))
641    plog(XLOG_DEBUG, "amfs_readdir3_browsable gen=%lu, count=%d", (long unsigned) gen, count);
642
643  if (gen == 0) {
644    size_t needed = needroom3();
645    /*
646     * In the default instance (which is used to start a search) we return
647     * "." and "..".
648     *
649     * This assumes that the count is big enough to allow both "." and ".."
650     * to be returned in a single packet.  If it isn't (which would be
651     * fairly unbelievable) then tough.
652     */
653    dlog("%s: default search", __func__);
654
655    if (count < needed) {
656      dlog("%s: not enough room %u < %zu", __func__, count, needed);
657      return EINVAL;
658    }
659
660    /*
661     * compute # of entries to send in this chain.
662     * heuristics: 128 bytes per entry.
663     * This is too much probably, but it seems to work better because
664     * of the re-entrant nature of nfs_readdir, and esp. on systems
665     * like OpenBSD 2.2.
666     */
667    chain_length = count / 128;
668
669    /* reset static state counters */
670    te = te_next = NULL;
671
672    dp->entries = ep;
673
674    /* construct "." */
675    ep[0].fileid = mp->am_gen;
676    ep[0].name = ".";
677    ep[0].nextentry = &ep[1];
678    ep[0].cookie = 0;
679
680    /* construct ".." */
681    if (mp->am_parent)
682      ep[1].fileid = mp->am_parent->am_gen;
683    else
684      ep[1].fileid = mp->am_gen;
685
686    ep[1].name = "..";
687    ep[1].nextentry = NULL;
688    ep[1].cookie = dotdotcookie;
689
690    /*
691     * If map is browsable, call a function make_entry_chain() to construct
692     * a linked list of unmounted keys, and return it.  Then link the chain
693     * to the regular list.  Get the chain only once, but return
694     * chunks of it each time.
695     */
696    te = make_entry_chain3(mp, dp->entries, fully_browsable);
697    if (!te)
698      return 0;
699    if (amuDebug(D_READDIR)) {
700      am_entry3 *ne;
701      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
702	plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name);
703    }
704
705    /* return only "chain_length" entries */
706    te_next = te;
707    for (i=1; i<chain_length; ++i) {
708      te_next = te_next->nextentry;
709      if (!te_next)
710	break;
711    }
712    if (te_next) {
713      am_entry3 *te_saved = te_next->nextentry;
714      te_next->nextentry = NULL; /* terminate "te" chain */
715      te_next = te_saved;	 /* save rest of "te" for next iteration */
716      dp->eof = FALSE;		 /* tell readdir there's more */
717    } else {
718      dp->eof = TRUE;		 /* tell readdir that's it */
719    }
720    ep[1].nextentry = te;	 /* append this chunk of "te" chain */
721    if (amuDebug(D_READDIR)) {
722      am_entry3 *ne;
723      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
724	plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->name);
725      for (j = 0, ne = ep; ne; ne = ne->ne_nextentry) {
726	plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%lu ck=%lu",
727	     j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie);
728      }
729      plog(XLOG_DEBUG, "EOF is %d", dp->eof);
730    }
731    return 0;
732  } /* end of "if (gen == 0)" statement */
733
734  dlog("%s: real child", __func__);
735
736  if (gen == DOT_DOT_COOKIE) {
737    dlog("%s: End of readdir in %s", __func__, mp->am_path);
738    dp->eof = TRUE;
739    dp->entries = NULL;
740    return 0;
741  }
742
743  /*
744   * If browsable directories, then continue serving readdir() with another
745   * chunk of entries, starting from where we left off (when gen was equal
746   * to 0).  Once again, assume last chunk served to readdir.
747   */
748  dp->eof = TRUE;
749  dp->entries = ep;
750
751  te = te_next;			/* reset 'te' from last saved te_next */
752  if (!te) {			/* another indicator of end of readdir */
753    dp->entries = NULL;
754    return 0;
755  }
756  /*
757   * compute # of entries to send in this chain.
758   * heuristics: 128 bytes per entry.
759   */
760  chain_length = count / 128;
761
762  /* return only "chain_length" entries */
763  for (i = 1; i < chain_length; ++i) {
764    te_next = te_next->nextentry;
765    if (!te_next)
766      break;
767  }
768  if (te_next) {
769    am_entry3 *te_saved = te_next->nextentry;
770    te_next->nextentry = NULL; /* terminate "te" chain */
771    te_next = te_saved;		/* save rest of "te" for next iteration */
772    dp->eof = FALSE;		/* tell readdir there's more */
773  }
774  ep = te;			/* send next chunk of "te" chain */
775  dp->entries = ep;
776  if (amuDebug(D_READDIR)) {
777    am_entry3 *ne;
778    plog(XLOG_DEBUG,
779	 "entries=%p, te_next=%p, eof=%d", dp->entries, te_next, dp->eof);
780    for (ne = te; ne; ne = ne->nextentry)
781      plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->name);
782  }
783  return 0;
784}
785
786static int
787amfs_readdir3(am_node *mp, am_cookie3 cookie,
788	      am_dirlist3 *dp, am_entry3 *ep, u_int count)
789{
790  uint64 gen = *(uint64 *) (uintptr_t) cookie;
791  am_node *xp;
792
793  if (amuDebug(D_READDIR))
794    plog(XLOG_DEBUG, "amfs_readdir3 gen=%lu, count=%d", (long unsigned) gen, count);
795
796  dp->eof = FALSE;		/* assume readdir not done */
797
798  /* when gen is 0, we start reading from the beginning of the directory */
799  if (gen == 0) {
800    size_t needed = needroom3();
801    /*
802     * In the default instance (which is used to start a search) we return
803     * "." and "..".
804     *
805     * This assumes that the count is big enough to allow both "." and ".."
806     * to be returned in a single packet.  If it isn't (which would be
807     * fairly unbelievable) then tough.
808     */
809    dlog("%s: default search", __func__);
810
811    if (count < needed) {
812      dlog("%s: not enough room %u < %zu", __func__, count, needed);
813      return EINVAL;
814    }
815
816    xp = next_nonerror_node(mp->am_child);
817    dp->entries = ep;
818
819    /* construct "." */
820    ep[0].fileid = mp->am_gen;
821    ep[0].name = ".";
822    ep[0].cookie = 0;
823    ep[0].nextentry = &ep[1];
824
825    /* construct ".." */
826    if (mp->am_parent)
827      ep[1].fileid = mp->am_parent->am_gen;
828    else
829      ep[1].fileid = mp->am_gen;
830    ep[1].name = "..";
831    ep[1].nextentry = NULL;
832    ep[1].cookie = (xp ? xp->am_gen : dotdotcookie);
833
834    if (!xp)
835      dp->eof = TRUE;	/* by default assume readdir done */
836
837    if (amuDebug(D_READDIR)) {
838      am_entry3 *ne;
839      int j;
840      for (j = 0, ne = ep; ne; ne = ne->nextentry) {
841	plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%lu ck=%lu",
842	     j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie);
843      }
844    }
845    return 0;
846  }
847  dlog("%s: real child", __func__);
848
849  if (gen == (uint64) DOT_DOT_COOKIE) {
850    dlog("%s: End of readdir in %s", __func__, mp->am_path);
851    dp->eof = TRUE;
852    dp->entries = NULL;
853    if (amuDebug(D_READDIR))
854      plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n");
855    return 0;
856  }
857
858  /* non-browsable directories code */
859  xp = mp->am_child;
860  while (xp && xp->am_gen != gen)
861    xp = xp->am_osib;
862
863  if (xp) {
864    int nbytes = count / 2;	/* conservative */
865    int todo = MAX_READDIR_ENTRIES;
866
867    dp->entries = ep;
868    do {
869      am_node *xp_next = next_nonerror_node(xp->am_osib);
870
871      if (xp_next) {
872        ep->cookie = xp_next->am_gen;
873      } else {
874	ep->cookie = (uint64) dotdotcookie;
875	dp->eof = TRUE;
876      }
877
878      ep->fileid = xp->am_gen;
879      ep->name = xp->am_name;
880      nbytes -= sizeof(*ep) + 1;
881      if (xp->am_name)
882	nbytes -= strlen(xp->am_name);
883
884      xp = xp_next;
885
886      if (nbytes > 0 && !dp->dl_eof && todo > 1) {
887	ep->nextentry = ep + 1;
888	ep++;
889	--todo;
890      } else {
891	todo = 0;
892      }
893    } while (todo > 0);
894
895    ep->nextentry = NULL;
896
897    if (amuDebug(D_READDIR)) {
898      am_entry3 *ne;
899      int j;
900      for (j = 0, ne = ep; ne; ne = ne->nextentry) {
901	plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%lu ck=%lu",
902	     j++, ne->name, (long unsigned) ne->fileid, (long unsigned) ne->cookie);
903      }
904    }
905    return 0;
906  }
907  return ESTALE;
908}
909
910/*
911 * This readdir function which call a special version of it that allows
912 * browsing if browsable_dirs=yes was set on the map.
913 */
914int
915amfs_generic_readdir(am_node *mp, voidp cookie, voidp dp, voidp ep, u_int count)
916{
917  int browsable, full;
918
919  /* check if map is browsable */
920  browsable = 0;
921  if (mp->am_al->al_mnt && mp->am_al->al_mnt->mf_mopts) {
922    mntent_t mnt;
923    mnt.mnt_opts = mp->am_al->al_mnt->mf_mopts;
924    if (amu_hasmntopt(&mnt, "fullybrowsable"))
925      browsable = 2;
926    else if (amu_hasmntopt(&mnt, "browsable"))
927      browsable = 1;
928  }
929  full = (browsable == 2);
930
931  if (nfs_dispatcher == nfs_program_2) {
932    if (browsable)
933      return amfs_readdir_browsable(mp, cookie, dp, ep, count, full);
934    else
935      return amfs_readdir(mp, cookie, dp, ep, count);
936  } else {
937    if (browsable)
938      return amfs_readdir3_browsable(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count, full);
939    else
940      return amfs_readdir3(mp, (am_cookie3) (uintptr_t) cookie, dp, ep, count);
941  }
942}
943