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