]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/dircache.c
Protect dircache from duplicates
[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 re-use
56  * the element fullpath, which for files is always NULL.
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  * The cache supports locking of struct dir elements through the DIRF_CACHELOCK flag. A dir
100  * locked this way wont ever be removed from the cache, so be careful.
101  *
102  * Debugging
103  * =========
104  *
105  * Sending SIGHUP to a afpd child causes it to dump the dircache to a file "/tmp/dircache.PID".
106  */
107
108 /********************************************************
109  * Local funcs and variables
110  ********************************************************/
111
112 /*****************************
113  *       the dircache        */
114
115 static hash_t       *dircache;        /* The actual cache */
116 static unsigned int dircache_maxsize; /* cache maximum size */
117
118 static struct dircache_stat {
119     unsigned long long lookups;
120     unsigned long long hits;
121     unsigned long long misses;
122     unsigned long long added;
123     unsigned long long removed;
124     unsigned long long expunged;
125     unsigned long long evicted;
126 } dircache_stat;
127
128 /* FNV 1a */
129 static hash_val_t hash_vid_did(const void *key)
130 {
131     const struct dir *k = (const struct dir *)key;
132     hash_val_t hash = 2166136261;
133
134     hash ^= k->d_vid >> 8;
135     hash *= 16777619;
136     hash ^= k->d_vid;
137     hash *= 16777619;
138
139     hash ^= k->d_did >> 24;
140     hash *= 16777619;
141     hash ^= (k->d_did >> 16) & 0xff;
142     hash *= 16777619;
143     hash ^= (k->d_did >> 8) & 0xff;
144     hash *= 16777619;
145     hash ^= (k->d_did >> 0) & 0xff;
146     hash *= 16777619;
147
148     return hash;
149 }
150
151 static int hash_comp_vid_did(const void *key1, const void *key2)
152 {
153     const struct dir *k1 = key1;
154     const struct dir *k2 = key2;
155
156     return !(k1->d_did == k2->d_did && k1->d_vid == k2->d_vid);
157 }
158
159 /**************************************************
160  * DID/name index on dircache (another hashtable) */
161
162 static hash_t *index_didname;
163
164 #undef get16bits
165 #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)    \
166     || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
167 #define get16bits(d) (*((const uint16_t *) (d)))
168 #endif
169
170 #if !defined (get16bits)
171 #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)    \
172                       +(uint32_t)(((const uint8_t *)(d))[0]) )
173 #endif
174
175 static hash_val_t hash_didname(const void *p)
176 {
177     const struct dir *key = (const struct dir *)p;
178     const unsigned char *data = key->d_u_name->data;
179     int len = key->d_u_name->slen;
180     hash_val_t hash = key->d_pdid + key->d_vid;
181     hash_val_t tmp;
182
183     int rem = len & 3;
184     len >>= 2;
185
186     /* Main loop */
187     for (;len > 0; len--) {
188         hash  += get16bits (data);
189         tmp    = (get16bits (data+2) << 11) ^ hash;
190         hash   = (hash << 16) ^ tmp;
191         data  += 2*sizeof (uint16_t);
192         hash  += hash >> 11;
193     }
194
195     /* Handle end cases */
196     switch (rem) {
197     case 3: hash += get16bits (data);
198         hash ^= hash << 16;
199         hash ^= data[sizeof (uint16_t)] << 18;
200         hash += hash >> 11;
201         break;
202     case 2: hash += get16bits (data);
203         hash ^= hash << 11;
204         hash += hash >> 17;
205         break;
206     case 1: hash += *data;
207         hash ^= hash << 10;
208         hash += hash >> 1;
209     }
210
211     /* Force "avalanching" of final 127 bits */
212     hash ^= hash << 3;
213     hash += hash >> 5;
214     hash ^= hash << 4;
215     hash += hash >> 17;
216     hash ^= hash << 25;
217     hash += hash >> 6;
218
219     return hash;
220 }
221
222 static int hash_comp_didname(const void *k1, const void *k2)
223 {
224     const struct dir *key1 = (const struct dir *)k1;
225     const struct dir *key2 = (const struct dir *)k2;
226
227     return ! (key1->d_vid == key2->d_vid
228               && key1->d_pdid == key2->d_pdid
229               && (bstrcmp(key1->d_u_name, key2->d_u_name) == 0) );
230 }
231
232 /***************************
233  * queue index on dircache */
234
235 static q_t *index_queue;    /* the index itself */
236 static unsigned long queue_count;
237
238 /*!
239  * @brief Remove a fixed number of (oldest) entries from the cache and indexes
240  *
241  * The default is to remove the 256 oldest entries from the cache.
242  * 1. Get the oldest entry
243  * 2. If it's in use ie open forks reference it or it's curdir requeue it,
244  *    or it's locked (from catsearch) dont remove it
245  * 3. Remove the dir from the main cache and the didname index
246  * 4. Free the struct dir structure and all its members
247  */
248 static void dircache_evict(void)
249 {
250     int i = DIRCACHE_FREE_QUANTUM;
251     struct dir *dir;
252
253     LOG(log_debug, logtype_afpd, "dircache: {starting cache eviction}");
254
255     while (i--) {
256         if ((dir = (struct dir *)dequeue(index_queue)) == NULL) { /* 1 */
257             dircache_dump();
258             AFP_PANIC("dircache_evict");
259         }
260         queue_count--;
261
262         if (curdir == dir
263             || (dir->d_flags & DIRF_CACHELOCK)) {     /* 2 */
264             if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
265                 dircache_dump();
266                 AFP_PANIC("dircache_evict");
267             }
268             queue_count++;
269             continue;
270         }
271
272         dircache_remove(NULL, dir, DIRCACHE | DIDNAME_INDEX); /* 3 */
273         dir_free(dir);                                        /* 4 */
274     }
275
276     AFP_ASSERT(queue_count == dircache->hash_nodecount);
277     dircache_stat.evicted += DIRCACHE_FREE_QUANTUM;
278     LOG(log_debug, logtype_afpd, "dircache: {finished cache eviction}");
279 }
280
281
282 /********************************************************
283  * Interface
284  ********************************************************/
285
286 /*!
287  * @brief Search the dircache via a CNID for a directory
288  *
289  * Found cache entries are expunged if both the parent directory st_ctime and the objects
290  * st_ctime are modified.
291  * This func builds on the fact, that all our code only ever needs to and does search
292  * the dircache by CNID expecting directories to be returned, but not files.
293  * Thus
294  * (1) if we find a file (d_fullpath == NULL) for a given CNID we
295  *     (1a) remove it from the cache
296  *     (1b) return NULL indicating nothing found
297  * (2) we can then use d_fullpath to stat the directory
298  *
299  * @param vol      (r) pointer to struct vol
300  * @param cnid     (r) CNID of the directory to search
301  *
302  * @returns            Pointer to struct dir if found, else NULL
303  */
304 struct dir *dircache_search_by_did(const struct vol *vol, cnid_t cnid)
305 {
306     struct dir *cdir = NULL;
307     struct dir key;
308     struct stat st;
309     hnode_t *hn;
310
311     AFP_ASSERT(vol);
312     AFP_ASSERT(ntohl(cnid) >= CNID_START);
313
314     dircache_stat.lookups++;
315     key.d_vid = vol->v_vid;
316     key.d_did = cnid;
317     if ((hn = hash_lookup(dircache, &key)))
318         cdir = hnode_get(hn);
319
320     if (cdir) {
321         if (cdir->d_fullpath == NULL) { /* (1) */
322             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not a directory:\"%s\"}",
323                 ntohl(cnid), cfrombstr(cdir->d_u_name));
324             (void)dir_remove(vol, cdir); /* (1a) */
325             dircache_stat.expunged++;
326             return NULL;        /* (1b) */
327
328         }
329         if (lstat(cfrombstr(cdir->d_fullpath), &st) != 0) {
330             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {missing:\"%s\"}",
331                 ntohl(cnid), cfrombstr(cdir->d_fullpath));
332             (void)dir_remove(vol, cdir);
333             dircache_stat.expunged++;
334             return NULL;
335         }
336         if (cdir->ctime_dircache != st.st_ctime) {
337             LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {modified:\"%s\"}",
338                 ntohl(cnid), cfrombstr(cdir->d_u_name));
339             (void)dir_remove(vol, cdir);
340             dircache_stat.expunged++;
341             return NULL;
342         }
343         LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {cached: path:\"%s\"}",
344             ntohl(cnid), cfrombstr(cdir->d_fullpath));
345         dircache_stat.hits++;
346     } else {
347         LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not in cache}", ntohl(cnid));
348         dircache_stat.misses++;
349     }
350     
351     return cdir;
352 }
353
354 /*!
355  * @brief Search the cache via did/name hashtable
356  *
357  * Found cache entries are expunged if both the parent directory st_ctime and the objects
358  * st_ctime are modified.
359  *
360  * @param vol      (r) volume
361  * @param dir      (r) directory
362  * @param name     (r) name (server side encoding)
363  * @parma len      (r) strlen of name
364  * @param ctime    (r) current st_ctime from stat
365  *
366  * @returns pointer to struct dir if found in cache, else NULL
367  */
368 struct dir *dircache_search_by_name(const struct vol *vol,
369                                     const struct dir *dir,
370                                     char *name,
371                                     int len,
372                                     time_t ctime)
373 {
374     struct dir *cdir = NULL;
375     struct dir key;
376
377     hnode_t *hn;
378     static_bstring uname = {-1, len, (unsigned char *)name};
379
380     AFP_ASSERT(vol);
381     AFP_ASSERT(dir);
382     AFP_ASSERT(name);
383     AFP_ASSERT(len == strlen(name));
384     AFP_ASSERT(len < 256);
385
386     dircache_stat.lookups++;
387     LOG(log_debug, logtype_afpd, "dircache_search_by_name(did:%u, \"%s\")",
388         ntohl(dir->d_did), name);
389
390     if (dir->d_did != DIRDID_ROOT_PARENT) {
391         key.d_vid = vol->v_vid;
392         key.d_pdid = dir->d_did;
393         key.d_u_name = &uname;
394
395         if ((hn = hash_lookup(index_didname, &key)))
396             cdir = hnode_get(hn);
397     }
398
399     if (cdir) {
400         if (cdir->ctime_dircache != ctime) {
401             LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {modified}",
402                 ntohl(dir->d_did), name);
403             (void)dir_remove(vol, cdir);
404             dircache_stat.expunged++;
405             return NULL;
406         }
407         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {found in cache}",
408             ntohl(dir->d_did), name);
409         dircache_stat.hits++;
410     } else {
411         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {not in cache}",
412             ntohl(dir->d_did), name);
413         dircache_stat.misses++;
414     }
415
416     return cdir;
417 }
418
419 /*!
420  * @brief create struct dir from struct path
421  *
422  * Add a struct dir to the cache and its indexes.
423  *
424  * @param dir   (r) pointer to parrent directory
425  *
426  * @returns 0 on success, -1 on error which should result in an abort
427  */
428 int dircache_add(const struct vol *vol,
429                  struct dir *dir)
430 {
431     struct dir *cdir = NULL;
432     struct dir key;
433     hnode_t *hn;
434
435     AFP_ASSERT(dir);
436     AFP_ASSERT(ntohl(dir->d_pdid) >= 2);
437     AFP_ASSERT(ntohl(dir->d_did) >= CNID_START);
438     AFP_ASSERT(dir->d_u_name);
439     AFP_ASSERT(dir->d_vid);
440     AFP_ASSERT(dircache->hash_nodecount <= dircache_maxsize);
441
442     /* Check if cache is full */
443     if (dircache->hash_nodecount == dircache_maxsize)
444         dircache_evict();
445
446     /* 
447      * Make sure we don't add duplicates
448      */
449
450     /* Search primary cache by CNID */
451     key.d_vid = dir->d_vid;
452     key.d_did = dir->d_did;
453     if ((hn = hash_lookup(dircache, &key))) {
454         /* Found an entry with the same CNID, delete it */
455         dir_remove(vol, hnode_get(hn));
456         dircache_stat.expunged++;
457     }
458     key.d_vid = vol->v_vid;
459     key.d_pdid = dir->d_did;
460     key.d_u_name = dir->d_u_name;
461     if ((hn = hash_lookup(index_didname, &key))) {
462         /* Found an entry with the same DID/name, delete it */
463         dir_remove(vol, hnode_get(hn));
464         dircache_stat.expunged++;
465     }
466
467     /* Add it to the main dircache */
468     if (hash_alloc_insert(dircache, dir, dir) == 0) {
469         dircache_dump();
470         exit(EXITERR_SYS);
471     }
472
473     /* Add it to the did/name index */
474     if (hash_alloc_insert(index_didname, dir, dir) == 0) {
475         dircache_dump();
476         exit(EXITERR_SYS);
477     }
478
479     /* Add it to the fifo queue index */
480     if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
481         dircache_dump();
482         exit(EXITERR_SYS);
483     } else {
484         queue_count++;
485     }
486
487     dircache_stat.added++;
488     LOG(log_debug, logtype_afpd, "dircache(did:%u,'%s'): {added}",
489         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
490
491    AFP_ASSERT(queue_count == index_didname->hash_nodecount 
492            && queue_count == dircache->hash_nodecount);
493
494     return 0;
495 }
496
497 /*!
498   * @brief Remove an entry from the dircache
499   *
500   * Callers outside of dircache.c should call this with
501   * flags = QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE.
502   */
503 void dircache_remove(const struct vol *vol _U_, struct dir *dir, int flags)
504 {
505     hnode_t *hn;
506
507    AFP_ASSERT(dir);
508    AFP_ASSERT((flags & ~(QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE)) == 0);
509
510     if (dir->d_flags & DIRF_CACHELOCK)
511         return;
512
513     if (flags & QUEUE_INDEX) {
514         /* remove it from the queue index */
515         dequeue(dir->qidx_node->prev); /* this effectively deletes the dequeued node */
516         queue_count--;
517     }
518
519     if (flags & DIDNAME_INDEX) {
520         if ((hn = hash_lookup(index_didname, dir)) == NULL) {
521             LOG(log_error, logtype_default, "dircache_remove(%u,\"%s\"): not in didname index", 
522                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
523             dircache_dump();
524             AFP_PANIC("dircache_remove");
525         }
526         hash_delete_free(index_didname, hn);
527     }
528
529     if (flags & DIRCACHE) {
530         if ((hn = hash_lookup(dircache, dir)) == NULL) {
531             LOG(log_error, logtype_default, "dircache_remove(%u,\"%s\"): not in dircache", 
532                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
533             dircache_dump();
534             AFP_PANIC("dircache_remove");
535         }
536         hash_delete_free(dircache, hn);
537     }
538
539     LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {removed}",
540         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
541
542     dircache_stat.removed++;
543     AFP_ASSERT(queue_count == index_didname->hash_nodecount 
544                && queue_count == dircache->hash_nodecount);
545 }
546
547 /*!
548  * @brief Initialize the dircache and indexes
549  *
550  * This is called in child afpd initialisation. The maximum cache size will be
551  * max(DEFAULT_MAX_DIRCACHE_SIZE, min(size, MAX_POSSIBLE_DIRCACHE_SIZE)).
552  * It initializes a hashtable which we use to store a directory cache in.
553  * It also initializes two indexes:
554  * - a DID/name index on the main dircache
555  * - a queue index on the dircache
556  *
557  * @param size   (r) requested maximum size from afpd.conf
558  *
559  * @return 0 on success, -1 on error
560  */
561 int dircache_init(int reqsize)
562 {
563     dircache_maxsize = DEFAULT_MAX_DIRCACHE_SIZE;
564
565     /* Initialize the main dircache */
566     if (reqsize > DEFAULT_MAX_DIRCACHE_SIZE && reqsize < MAX_POSSIBLE_DIRCACHE_SIZE) {
567         while ((dircache_maxsize < MAX_POSSIBLE_DIRCACHE_SIZE) && (dircache_maxsize < reqsize))
568                dircache_maxsize *= 2;
569     }
570     if ((dircache = hash_create(dircache_maxsize, hash_comp_vid_did, hash_vid_did)) == NULL)
571         return -1;
572     
573     LOG(log_debug, logtype_afpd, "dircache_init: done. max dircache size: %u", dircache_maxsize);
574
575     /* Initialize did/name index hashtable */
576     if ((index_didname = hash_create(dircache_maxsize, hash_comp_didname, hash_didname)) == NULL)
577         return -1;
578
579     /* Initialize index queue */
580     if ((index_queue = queue_init()) == NULL)
581         return -1;
582     else
583         queue_count = 0;
584
585     /* Initialize index queue */
586     if ((invalid_dircache_entries = queue_init()) == NULL)
587         return -1;
588
589     /* As long as directory.c hasn't got its own initializer call, we do it for it */
590     rootParent.d_did = DIRDID_ROOT_PARENT;
591     rootParent.d_fullpath = bfromcstr("ROOT_PARENT");
592     rootParent.d_m_name = bfromcstr("ROOT_PARENT");
593     rootParent.d_u_name = rootParent.d_m_name;
594
595     return 0;
596 }
597
598 /*!
599  * Log dircache statistics
600  */
601 void log_dircache_stat(void)
602 {
603     LOG(log_info, logtype_afpd, "dircache statistics: "
604         "entries: %lu, lookups: %llu, hits: %llu, misses: %llu, added: %llu, removed: %llu, expunged: %llu, evicted: %llu",
605         queue_count,
606         dircache_stat.lookups,
607         dircache_stat.hits,
608         dircache_stat.misses,
609         dircache_stat.added,
610         dircache_stat.removed,
611         dircache_stat.expunged,
612         dircache_stat.evicted);
613 }
614
615 /*!
616  * @brief Dump dircache to /tmp/dircache.PID
617  */
618 void dircache_dump(void)
619 {
620     char tmpnam[64];
621     FILE *dump;
622     qnode_t *n = index_queue->next;
623     hnode_t *hn;
624     hscan_t hs;
625     const struct dir *dir;
626     int i;
627
628     LOG(log_warning, logtype_afpd, "Dumping directory cache...");
629
630     sprintf(tmpnam, "/tmp/dircache.%u", getpid());
631     if ((dump = fopen(tmpnam, "w+")) == NULL) {
632         LOG(log_error, logtype_afpd, "dircache_dump: %s", strerror(errno));
633         return;
634     }
635     setbuf(dump, NULL);
636
637     fprintf(dump, "Number of cache entries in LRU queue: %lu\n", queue_count);
638     fprintf(dump, "Configured maximum cache size: %u\n\n", dircache_maxsize);
639
640     fprintf(dump, "Primary CNID index:\n");
641     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
642     fprintf(dump, "====================================================================\n");
643     hash_scan_begin(&hs, dircache);
644     i = 1;
645     while ((hn = hash_scan_next(&hs))) {
646         dir = hnode_get(hn);
647         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
648                 i++,
649                 ntohs(dir->d_vid),
650                 ntohl(dir->d_pdid),
651                 ntohl(dir->d_did),
652                 dir->d_fullpath ? "d" : "f",
653                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
654                 cfrombstr(dir->d_u_name));
655     }
656
657     fprintf(dump, "\nSecondary DID/name index:\n");
658     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
659     fprintf(dump, "====================================================================\n");
660     hash_scan_begin(&hs, index_didname);
661     i = 1;
662     while ((hn = hash_scan_next(&hs))) {
663         dir = hnode_get(hn);
664         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
665                 i++,
666                 ntohs(dir->d_vid),
667                 ntohl(dir->d_pdid),
668                 ntohl(dir->d_did),
669                 dir->d_fullpath ? "d" : "f",
670                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
671                 cfrombstr(dir->d_u_name));
672     }
673
674     fprintf(dump, "\nLRU Queue:\n");
675     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
676     fprintf(dump, "====================================================================\n");
677
678     for (i = 1; i <= queue_count; i++) {
679         if (n == index_queue)
680             break;
681         dir = (struct dir *)n->data;
682         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
683                 i,
684                 ntohs(dir->d_vid),
685                 ntohl(dir->d_pdid),
686                 ntohl(dir->d_did),
687                 dir->d_fullpath ? "d" : "f",
688                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
689                 cfrombstr(dir->d_u_name));
690         n = n->next;
691     }
692
693     fprintf(dump, "\n");
694     fflush(dump);
695     return;
696 }