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