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