xref: /netbsd-src/external/bsd/am-utils/dist/amd/readdir.c (revision 8bc49d51dd1abfd6ff03d24c2237ae447507de63)
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@cs.columbia.edu>
72  */
73 static int
key_already_in_chain(char * keyname,const nfsentry * chain)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@cs.columbia.edu>
91  */
92 static nfsentry *
make_entry_chain(am_node * mp,const nfsentry * current_chain,int fully_browsable)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
amfs_readdir_browsable(am_node * mp,nfscookie cookie,nfsdirlist * dp,nfsentry * ep,u_int count,int fully_browsable)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
amfs_readdir(am_node * mp,nfscookie cookie,nfsdirlist * dp,nfsentry * ep,u_int count)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
key_already_in_chain3(char * keyname,const am_entry3 * chain)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 *
make_entry_chain3(am_node * mp,const am_entry3 * current_chain,int fully_browsable)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 
needroom3(void)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
amfs_readdir3_browsable(am_node * mp,voidp cookie,am_dirlist3 * dp,am_entry3 * ep,u_int count,int fully_browsable)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
amfs_readdir3(am_node * mp,voidp cookie,am_dirlist3 * dp,am_entry3 * ep,u_int count)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
amfs_generic_readdir(am_node * mp,voidp cookie,voidp dp,voidp ep,u_int count)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