]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/dircache.c
Use st_ino in dircache for files
[netatalk.git] / etc / afpd / dircache.c
1 /*
2   Copyright (c) 2010 Frank Lahm <franklahm@gmail.com>
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif /* HAVE_CONFIG_H */
18
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <assert.h>
24 #include <time.h>
25
26 #include <atalk/util.h>
27 #include <atalk/cnid.h>
28 #include <atalk/logger.h>
29 #include <atalk/volume.h>
30 #include <atalk/directory.h>
31 #include <atalk/queue.h>
32 #include <atalk/bstrlib.h>
33 #include <atalk/bstradd.h>
34
35 #include "dircache.h"
36 #include "directory.h"
37 #include "hash.h"
38 #include "globals.h"
39
40 /*
41  * Directory Cache
42  * ===============
43  *
44  * Cache files and directories in a LRU cache.
45  *
46  * The directory cache caches directories and files(!). The main reason for having the cache
47  * is avoiding recursive walks up the path, querying the CNID database each time, when
48  * we have to calculate the location of eg directory with CNID 30, which is located in a dir with
49  * CNID 25, next CNID 20 and then CNID 2 (the volume root as per AFP spec).
50  * If all these dirs where in the cache, each database look up can be avoided. Additionally there's
51  * the element "fullpath" in struct dir, which is used to avoid the recursion in any case. Wheneveer
52  * a struct dir is initialized, the fullpath to the directory is stored there.
53  *
54  * In order to speed up the CNID query for files too, which eg happens when a directory is enumerated,
55  * files are stored too in the dircache. In order to differentiate between files and dirs, we set
56  * the flag DIRF_ISFILE in struct dir.d_flags for files.
57  *
58  * The most frequent codepatch that leads to caching is directory enumeration (cf enumerate.c):
59  * - if a element is a directory:
60  *   (1) the cache is searched by dircache_search_by_name()
61  *   (2) if it wasn't found a new struct dir is created and cached both from within dir_add()
62  * - for files the caching happens a little bit down the call chain:
63  *   (3) first getfilparams() is called, which calls
64  *   (4) getmetadata() where the cache is searched with dircache_search_by_name()
65  *   (5) if the element is not found
66  *   (6) get_id() queries the CNID from the database
67  *   (7) then a struct dir is initialized via dir_new() (note the fullpath arg is NULL)
68  *   (8) finally added to the cache with dircache_add()
69  * (2) of course does contain the steps 6,7 and 8.
70  *
71  * The dircache is a LRU cache, whenever it fills up we call dircache_evict internally which removes
72  * DIRCACHE_FREE_QUANTUM elements from the cache.
73  *
74  * There is only one cache for all volumes, so of course we use the volume id in hashing calculations.
75  *
76  * In order to avoid cache poisoning, we store the cached entries st_ctime from stat in
77  * struct dir.ctime_dircache. Later when we search the cache we compare the stored
78  * value with the result of a fresh stat. If the times differ, we remove the cached
79  * entry and return "no entry found in cache".
80  * A elements ctime changes when
81  *   1) the element is renamed
82  *      (we loose the cached entry here, but it will expire when the cache fills)
83  *   2) its a directory and an object has been created therein
84  *   3) the element is deleted and recreated under the same name
85  * Using ctime leads to cache eviction in case 2) where it wouldn't be necessary, because
86  * the dir itself (name, CNID, ...) hasn't changed, but there's no other way.
87  *
88  * Indexes
89  * =======
90  *
91  * The maximum dircache size is:
92  * max(DEFAULT_MAX_DIRCACHE_SIZE, min(size, MAX_POSSIBLE_DIRCACHE_SIZE)).
93  * It is a hashtable which we use to store "struct dir"s in. If the cache get full, oldest
94  * entries are evicted in chunks of DIRCACHE_FREE.
95  *
96  * We have/need two indexes:
97  * - a DID/name index on the main dircache, another hashtable
98  * - a queue index on the dircache, for evicting the oldest entries
99  *
100  * Debugging
101  * =========
102  *
103  * Sending SIGINT to a afpd child causes it to dump the dircache to a file "/tmp/dircache.PID".
104  */
105
106 /********************************************************
107  * Local funcs and variables
108  ********************************************************/
109
110 /*****************************
111  *       the dircache        */
112
113 static hash_t       *dircache;        /* The actual cache */
114 static unsigned int dircache_maxsize; /* cache maximum size */
115
116 static struct dircache_stat {
117     unsigned long long lookups;
118     unsigned long long hits;
119     unsigned long long misses;
120     unsigned long long added;
121     unsigned long long removed;
122     unsigned long long expunged;
123     unsigned long long evicted;
124 } dircache_stat;
125
126 /* FNV 1a */
127 static hash_val_t hash_vid_did(const void *key)
128 {
129     const struct dir *k = (const struct dir *)key;
130     hash_val_t hash = 2166136261;
131
132     hash ^= k->d_vid >> 8;
133     hash *= 16777619;
134     hash ^= k->d_vid;
135     hash *= 16777619;
136
137     hash ^= k->d_did >> 24;
138     hash *= 16777619;
139     hash ^= (k->d_did >> 16) & 0xff;
140     hash *= 16777619;
141     hash ^= (k->d_did >> 8) & 0xff;
142     hash *= 16777619;
143     hash ^= (k->d_did >> 0) & 0xff;
144     hash *= 16777619;
145
146     return hash;
147 }
148
149 static int hash_comp_vid_did(const void *key1, const void *key2)
150 {
151     const struct dir *k1 = key1;
152     const struct dir *k2 = key2;
153
154     return !(k1->d_did == k2->d_did && k1->d_vid == k2->d_vid);
155 }
156
157 /**************************************************
158  * DID/name index on dircache (another hashtable) */
159
160 static hash_t *index_didname;
161
162 #undef get16bits
163 #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)    \
164     || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
165 #define get16bits(d) (*((const uint16_t *) (d)))
166 #endif
167
168 #if !defined (get16bits)
169 #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)    \
170                       +(uint32_t)(((const uint8_t *)(d))[0]) )
171 #endif
172
173 static hash_val_t hash_didname(const void *p)
174 {
175     const struct dir *key = (const struct dir *)p;
176     const unsigned char *data = key->d_u_name->data;
177     int len = key->d_u_name->slen;
178     hash_val_t hash = key->d_pdid + key->d_vid;
179     hash_val_t tmp;
180
181     int rem = len & 3;
182     len >>= 2;
183
184     /* Main loop */
185     for (;len > 0; len--) {
186         hash  += get16bits (data);
187         tmp    = (get16bits (data+2) << 11) ^ hash;
188         hash   = (hash << 16) ^ tmp;
189         data  += 2*sizeof (uint16_t);
190         hash  += hash >> 11;
191     }
192
193     /* Handle end cases */
194     switch (rem) {
195     case 3: hash += get16bits (data);
196         hash ^= hash << 16;
197         hash ^= data[sizeof (uint16_t)] << 18;
198         hash += hash >> 11;
199         break;
200     case 2: hash += get16bits (data);
201         hash ^= hash << 11;
202         hash += hash >> 17;
203         break;
204     case 1: hash += *data;
205         hash ^= hash << 10;
206         hash += hash >> 1;
207     }
208
209     /* Force "avalanching" of final 127 bits */
210     hash ^= hash << 3;
211     hash += hash >> 5;
212     hash ^= hash << 4;
213     hash += hash >> 17;
214     hash ^= hash << 25;
215     hash += hash >> 6;
216
217     return hash;
218 }
219
220 static int hash_comp_didname(const void *k1, const void *k2)
221 {
222     const struct dir *key1 = (const struct dir *)k1;
223     const struct dir *key2 = (const struct dir *)k2;
224
225     return ! (key1->d_vid == key2->d_vid
226               && key1->d_pdid == key2->d_pdid
227               && (bstrcmp(key1->d_u_name, key2->d_u_name) == 0) );
228 }
229
230 /***************************
231  * queue index on dircache */
232
233 static q_t *index_queue;    /* the index itself */
234 static unsigned long queue_count;
235
236 /*!
237  * @brief Remove a fixed number of (oldest) entries from the cache and indexes
238  *
239  * The default is to remove the 256 oldest entries from the cache.
240  * 1. Get the oldest entry
241  * 2. If it's in use ie open forks reference it or it's curdir requeue it,
242  *    dont remove it
243  * 3. Remove the dir from the main cache and the didname index
244  * 4. Free the struct dir structure and all its members
245  */
246 static void dircache_evict(void)
247 {
248     int i = DIRCACHE_FREE_QUANTUM;
249     struct dir *dir;
250
251     LOG(log_debug, logtype_afpd, "dircache: {starting cache eviction}");
252
253     while (i--) {
254         if ((dir = (struct dir *)dequeue(index_queue)) == NULL) { /* 1 */
255             dircache_dump();
256             AFP_PANIC("dircache_evict");
257         }
258         queue_count--;
259
260         if (curdir == dir) {                          /* 2 */
261             if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
262                 dircache_dump();
263                 AFP_PANIC("dircache_evict");
264             }
265             queue_count++;
266             continue;
267         }
268
269         dircache_remove(NULL, dir, DIRCACHE | DIDNAME_INDEX); /* 3 */
270         dir_free(dir);                                        /* 4 */
271     }
272
273     AFP_ASSERT(queue_count == dircache->hash_nodecount);
274     dircache_stat.evicted += DIRCACHE_FREE_QUANTUM;
275     LOG(log_debug, logtype_afpd, "dircache: {finished cache eviction}");
276 }
277
278
279 /********************************************************
280  * Interface
281  ********************************************************/
282
283 /*!
284  * @brief Search the dircache via a CNID for a directory
285  *
286  * Found cache entries are expunged if both the parent directory st_ctime and the objects
287  * st_ctime are modified.
288  * This func builds on the fact, that all our code only ever needs to and does search
289  * the dircache by CNID expecting directories to be returned, but not files.
290  * Thus
291  * (1) if we find a file for a given CNID we
292  *     (1a) remove it from the cache
293  *     (1b) return NULL indicating nothing found
294  * (2) we can then use d_fullpath to stat the directory
295  *
296  * @param vol      (r) pointer to struct vol
297  * @param cnid     (r) CNID of the directory to search
298  *
299  * @returns            Pointer to struct dir if found, else NULL
300  */
301 struct dir *dircache_search_by_did(const struct vol *vol, cnid_t cnid)
302 {
303     struct dir *cdir = NULL;
304     struct dir key;
305     struct stat st;
306     hnode_t *hn;
307
308     AFP_ASSERT(vol);
309     AFP_ASSERT(ntohl(cnid) >= CNID_START);
310
311     dircache_stat.lookups++;
312     key.d_vid = vol->v_vid;
313     key.d_did = cnid;
314     if ((hn = hash_lookup(dircache, &key)))
315         cdir = hnode_get(hn);
316
317     if (cdir) {
318         if (cdir->d_flags & DIRF_ISFILE) { /* (1) */
319             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not a directory:\"%s\"}",
320                 ntohl(cnid), cfrombstr(cdir->d_u_name));
321             (void)dir_remove(vol, cdir); /* (1a) */
322             dircache_stat.expunged++;
323             return NULL;        /* (1b) */
324
325         }
326         if (lstat(cfrombstr(cdir->d_fullpath), &st) != 0) {
327             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {missing:\"%s\"}",
328                 ntohl(cnid), cfrombstr(cdir->d_fullpath));
329             (void)dir_remove(vol, cdir);
330             dircache_stat.expunged++;
331             return NULL;
332         }
333         if (cdir->dcache_ctime != st.st_ctime) {
334             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {modified:\"%s\"}",
335                 ntohl(cnid), cfrombstr(cdir->d_u_name));
336             (void)dir_remove(vol, cdir);
337             dircache_stat.expunged++;
338             return NULL;
339         }
340         LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {cached: path:\"%s\"}",
341             ntohl(cnid), cfrombstr(cdir->d_fullpath));
342         dircache_stat.hits++;
343     } else {
344         LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not in cache}", ntohl(cnid));
345         dircache_stat.misses++;
346     }
347     
348     return cdir;
349 }
350
351 /*!
352  * @brief Search the cache via did/name hashtable
353  *
354  * Found cache entries are expunged if both the parent directory st_ctime and the objects
355  * st_ctime are modified.
356  *
357  * @param vol      (r) volume
358  * @param dir      (r) directory
359  * @param name     (r) name (server side encoding)
360  * @parma len      (r) strlen of name
361  *
362  * @returns pointer to struct dir if found in cache, else NULL
363  */
364 struct dir *dircache_search_by_name(const struct vol *vol,
365                                     const struct dir *dir,
366                                     char *name,
367                                     int len)
368 {
369     struct dir *cdir = NULL;
370     struct dir key;
371     struct stat st;
372
373     hnode_t *hn;
374     static_bstring uname = {-1, len, (unsigned char *)name};
375
376     AFP_ASSERT(vol);
377     AFP_ASSERT(dir);
378     AFP_ASSERT(name);
379     AFP_ASSERT(len == strlen(name));
380     AFP_ASSERT(len < 256);
381
382     dircache_stat.lookups++;
383     LOG(log_debug, logtype_afpd, "dircache_search_by_name(did:%u, \"%s\")",
384         ntohl(dir->d_did), name);
385
386     if (dir->d_did != DIRDID_ROOT_PARENT) {
387         key.d_vid = vol->v_vid;
388         key.d_pdid = dir->d_did;
389         key.d_u_name = &uname;
390
391         if ((hn = hash_lookup(index_didname, &key)))
392             cdir = hnode_get(hn);
393     }
394
395     if (cdir) {
396         if (lstat(cfrombstr(cdir->d_fullpath), &st) != 0) {
397             LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {missing:\"%s\"}",
398                 ntohl(dir->d_did), name, cfrombstr(cdir->d_fullpath));
399             (void)dir_remove(vol, cdir);
400             dircache_stat.expunged++;
401             return NULL;
402         }
403
404         /* Remove modified directories (precaution, probably not necessary) and file */
405         if (
406             (!(cdir->d_flags & DIRF_ISFILE) && (cdir->dcache_ctime != st.st_ctime))
407             ||
408             ((cdir->d_flags & DIRF_ISFILE) && (cdir->dcache_ino != st.st_ino))
409            ) {
410             LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {modified}",
411                 ntohl(dir->d_did), name);
412             (void)dir_remove(vol, cdir);
413             dircache_stat.expunged++;
414             return NULL;
415         }
416         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {found in cache}",
417             ntohl(dir->d_did), name);
418         dircache_stat.hits++;
419     } else {
420         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {not in cache}",
421             ntohl(dir->d_did), name);
422         dircache_stat.misses++;
423     }
424
425     return cdir;
426 }
427
428 /*!
429  * @brief create struct dir from struct path
430  *
431  * Add a struct dir to the cache and its indexes.
432  *
433  * @param dir   (r) pointer to parrent directory
434  *
435  * @returns 0 on success, -1 on error which should result in an abort
436  */
437 int dircache_add(const struct vol *vol,
438                  struct dir *dir)
439 {
440     struct dir *cdir = NULL;
441     struct dir key;
442     hnode_t *hn;
443
444     AFP_ASSERT(dir);
445     AFP_ASSERT(ntohl(dir->d_pdid) >= 2);
446     AFP_ASSERT(ntohl(dir->d_did) >= CNID_START);
447     AFP_ASSERT(dir->d_u_name);
448     AFP_ASSERT(dir->d_vid);
449     AFP_ASSERT(dircache->hash_nodecount <= dircache_maxsize);
450
451     /* Check if cache is full */
452     if (dircache->hash_nodecount == dircache_maxsize)
453         dircache_evict();
454
455     /* 
456      * Make sure we don't add duplicates
457      */
458
459     /* Search primary cache by CNID */
460     key.d_vid = dir->d_vid;
461     key.d_did = dir->d_did;
462     if ((hn = hash_lookup(dircache, &key))) {
463         /* Found an entry with the same CNID, delete it */
464         dir_remove(vol, hnode_get(hn));
465         dircache_stat.expunged++;
466     }
467     key.d_vid = vol->v_vid;
468     key.d_pdid = dir->d_did;
469     key.d_u_name = dir->d_u_name;
470     if ((hn = hash_lookup(index_didname, &key))) {
471         /* Found an entry with the same DID/name, delete it */
472         dir_remove(vol, hnode_get(hn));
473         dircache_stat.expunged++;
474     }
475
476     /* Add it to the main dircache */
477     if (hash_alloc_insert(dircache, dir, dir) == 0) {
478         dircache_dump();
479         exit(EXITERR_SYS);
480     }
481
482     /* Add it to the did/name index */
483     if (hash_alloc_insert(index_didname, dir, dir) == 0) {
484         dircache_dump();
485         exit(EXITERR_SYS);
486     }
487
488     /* Add it to the fifo queue index */
489     if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
490         dircache_dump();
491         exit(EXITERR_SYS);
492     } else {
493         queue_count++;
494     }
495
496     dircache_stat.added++;
497     LOG(log_debug, logtype_afpd, "dircache(did:%u,'%s'): {added}",
498         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
499
500    AFP_ASSERT(queue_count == index_didname->hash_nodecount 
501            && queue_count == dircache->hash_nodecount);
502
503     return 0;
504 }
505
506 /*!
507   * @brief Remove an entry from the dircache
508   *
509   * Callers outside of dircache.c should call this with
510   * flags = QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE.
511   */
512 void dircache_remove(const struct vol *vol _U_, struct dir *dir, int flags)
513 {
514     hnode_t *hn;
515
516     AFP_ASSERT(dir);
517     AFP_ASSERT((flags & ~(QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE)) == 0);
518
519     if (flags & QUEUE_INDEX) {
520         /* remove it from the queue index */
521         dequeue(dir->qidx_node->prev); /* this effectively deletes the dequeued node */
522         queue_count--;
523     }
524
525     if (flags & DIDNAME_INDEX) {
526         if ((hn = hash_lookup(index_didname, dir)) == NULL) {
527             LOG(log_error, logtype_afpd, "dircache_remove(%u,\"%s\"): not in didname index", 
528                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
529             dircache_dump();
530             AFP_PANIC("dircache_remove");
531         }
532         hash_delete_free(index_didname, hn);
533     }
534
535     if (flags & DIRCACHE) {
536         if ((hn = hash_lookup(dircache, dir)) == NULL) {
537             LOG(log_error, logtype_afpd, "dircache_remove(%u,\"%s\"): not in dircache", 
538                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
539             dircache_dump();
540             AFP_PANIC("dircache_remove");
541         }
542         hash_delete_free(dircache, hn);
543     }
544
545     LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {removed}",
546         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
547
548     dircache_stat.removed++;
549     AFP_ASSERT(queue_count == index_didname->hash_nodecount 
550                && queue_count == dircache->hash_nodecount);
551 }
552
553 /*!
554  * @brief Initialize the dircache and indexes
555  *
556  * This is called in child afpd initialisation. The maximum cache size will be
557  * max(DEFAULT_MAX_DIRCACHE_SIZE, min(size, MAX_POSSIBLE_DIRCACHE_SIZE)).
558  * It initializes a hashtable which we use to store a directory cache in.
559  * It also initializes two indexes:
560  * - a DID/name index on the main dircache
561  * - a queue index on the dircache
562  *
563  * @param size   (r) requested maximum size from afpd.conf
564  *
565  * @return 0 on success, -1 on error
566  */
567 int dircache_init(int reqsize)
568 {
569     dircache_maxsize = DEFAULT_MAX_DIRCACHE_SIZE;
570
571     /* Initialize the main dircache */
572     if (reqsize > DEFAULT_MAX_DIRCACHE_SIZE && reqsize < MAX_POSSIBLE_DIRCACHE_SIZE) {
573         while ((dircache_maxsize < MAX_POSSIBLE_DIRCACHE_SIZE) && (dircache_maxsize < reqsize))
574                dircache_maxsize *= 2;
575     }
576     if ((dircache = hash_create(dircache_maxsize, hash_comp_vid_did, hash_vid_did)) == NULL)
577         return -1;
578     
579     LOG(log_debug, logtype_afpd, "dircache_init: done. max dircache size: %u", dircache_maxsize);
580
581     /* Initialize did/name index hashtable */
582     if ((index_didname = hash_create(dircache_maxsize, hash_comp_didname, hash_didname)) == NULL)
583         return -1;
584
585     /* Initialize index queue */
586     if ((index_queue = queue_init()) == NULL)
587         return -1;
588     else
589         queue_count = 0;
590
591     /* Initialize index queue */
592     if ((invalid_dircache_entries = queue_init()) == NULL)
593         return -1;
594
595     /* As long as directory.c hasn't got its own initializer call, we do it for it */
596     rootParent.d_did = DIRDID_ROOT_PARENT;
597     rootParent.d_fullpath = bfromcstr("ROOT_PARENT");
598     rootParent.d_m_name = bfromcstr("ROOT_PARENT");
599     rootParent.d_u_name = rootParent.d_m_name;
600
601     return 0;
602 }
603
604 /*!
605  * Log dircache statistics
606  */
607 void log_dircache_stat(void)
608 {
609     LOG(log_info, logtype_afpd, "dircache statistics: "
610         "entries: %lu, lookups: %llu, hits: %llu, misses: %llu, added: %llu, removed: %llu, expunged: %llu, evicted: %llu",
611         queue_count,
612         dircache_stat.lookups,
613         dircache_stat.hits,
614         dircache_stat.misses,
615         dircache_stat.added,
616         dircache_stat.removed,
617         dircache_stat.expunged,
618         dircache_stat.evicted);
619 }
620
621 /*!
622  * @brief Dump dircache to /tmp/dircache.PID
623  */
624 void dircache_dump(void)
625 {
626     char tmpnam[64];
627     FILE *dump;
628     qnode_t *n = index_queue->next;
629     hnode_t *hn;
630     hscan_t hs;
631     const struct dir *dir;
632     int i;
633
634     LOG(log_warning, logtype_afpd, "Dumping directory cache...");
635
636     sprintf(tmpnam, "/tmp/dircache.%u", getpid());
637     if ((dump = fopen(tmpnam, "w+")) == NULL) {
638         LOG(log_error, logtype_afpd, "dircache_dump: %s", strerror(errno));
639         return;
640     }
641     setbuf(dump, NULL);
642
643     fprintf(dump, "Number of cache entries in LRU queue: %lu\n", queue_count);
644     fprintf(dump, "Configured maximum cache size: %u\n\n", dircache_maxsize);
645
646     fprintf(dump, "Primary CNID index:\n");
647     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
648     fprintf(dump, "====================================================================\n");
649     hash_scan_begin(&hs, dircache);
650     i = 1;
651     while ((hn = hash_scan_next(&hs))) {
652         dir = hnode_get(hn);
653         fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
654                 i++,
655                 ntohs(dir->d_vid),
656                 ntohl(dir->d_pdid),
657                 ntohl(dir->d_did),
658                 dir->d_flags & DIRF_ISFILE ? "f" : "d",
659                 cfrombstr(dir->d_fullpath));
660     }
661
662     fprintf(dump, "\nSecondary DID/name index:\n");
663     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
664     fprintf(dump, "====================================================================\n");
665     hash_scan_begin(&hs, index_didname);
666     i = 1;
667     while ((hn = hash_scan_next(&hs))) {
668         dir = hnode_get(hn);
669         fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
670                 i++,
671                 ntohs(dir->d_vid),
672                 ntohl(dir->d_pdid),
673                 ntohl(dir->d_did),
674                 dir->d_flags & DIRF_ISFILE ? "f" : "d",
675                 cfrombstr(dir->d_fullpath));
676     }
677
678     fprintf(dump, "\nLRU Queue:\n");
679     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
680     fprintf(dump, "====================================================================\n");
681
682     for (i = 1; i <= queue_count; i++) {
683         if (n == index_queue)
684             break;
685         dir = (struct dir *)n->data;
686         fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
687                 i,
688                 ntohs(dir->d_vid),
689                 ntohl(dir->d_pdid),
690                 ntohl(dir->d_did),
691                 dir->d_flags & DIRF_ISFILE ? "f" : "d",
692                 cfrombstr(dir->d_fullpath));
693         n = n->next;
694     }
695
696     fprintf(dump, "\n");
697     fflush(dump);
698     return;
699 }