]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/dircache.c
Merge branch-2-1
[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.hits++;
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, const struct dir *dir, char *name, int len, time_t ctime)
369 {
370     struct dir *cdir = NULL;
371     struct dir key;
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 (cdir->ctime_dircache != ctime) {
397             LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {modified}",
398                 ntohl(dir->d_did), name);
399             (void)dir_remove(vol, cdir);
400             dircache_stat.expunged++;
401             return NULL;
402         }
403         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {found in cache}",
404             ntohl(dir->d_did), name);
405         dircache_stat.hits++;
406     } else {
407         LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {not in cache}",
408             ntohl(dir->d_did), name);
409         dircache_stat.misses++;
410     }
411
412     return cdir;
413 }
414
415 /*!
416  * @brief create struct dir from struct path
417  *
418  * Add a struct dir to the cache and its indexes.
419  *
420  * @param dir   (r) pointer to parrent directory
421  *
422  * @returns 0 on success, -1 on error which should result in an abort
423  */
424 int dircache_add(struct dir *dir)
425 {
426    AFP_ASSERT(dir);
427    AFP_ASSERT(ntohl(dir->d_pdid) >= 2);
428    AFP_ASSERT(ntohl(dir->d_did) >= CNID_START);
429    AFP_ASSERT(dir->d_u_name);
430    AFP_ASSERT(dir->d_vid);
431    AFP_ASSERT(dircache->hash_nodecount <= dircache_maxsize);
432
433     /* Check if cache is full */
434     if (dircache->hash_nodecount == dircache_maxsize)
435         dircache_evict();
436
437     /* Add it to the main dircache */
438     if (hash_alloc_insert(dircache, dir, dir) == 0) {
439         dircache_dump();
440         exit(EXITERR_SYS);
441     }
442
443     /* Add it to the did/name index */
444     if (hash_alloc_insert(index_didname, dir, dir) == 0) {
445         dircache_dump();
446         exit(EXITERR_SYS);
447     }
448
449     /* Add it to the fifo queue index */
450     if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
451         dircache_dump();
452         exit(EXITERR_SYS);
453     } else {
454         queue_count++;
455     }
456
457     dircache_stat.added++;
458     LOG(log_debug, logtype_afpd, "dircache(did:%u,'%s'): {added}",
459         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
460
461    AFP_ASSERT(queue_count == index_didname->hash_nodecount 
462            && queue_count == dircache->hash_nodecount);
463
464     return 0;
465 }
466
467 /*!
468   * @brief Remove an entry from the dircache
469   *
470   * Callers outside of dircache.c should call this with
471   * flags = QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE.
472   */
473 void dircache_remove(const struct vol *vol _U_, struct dir *dir, int flags)
474 {
475     hnode_t *hn;
476
477    AFP_ASSERT(dir);
478    AFP_ASSERT((flags & ~(QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE)) == 0);
479
480     if (dir->d_flags & DIRF_CACHELOCK)
481         return;
482
483     if (flags & QUEUE_INDEX) {
484         /* remove it from the queue index */
485         dequeue(dir->qidx_node->prev); /* this effectively deletes the dequeued node */
486         queue_count--;
487     }
488
489     if (flags & DIDNAME_INDEX) {
490         if ((hn = hash_lookup(index_didname, dir)) == NULL) {
491             LOG(log_error, logtype_default, "dircache_remove(%u,\"%s\"): not in didname index", 
492                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
493             dircache_dump();
494             AFP_PANIC("dircache_remove");
495         }
496         hash_delete_free(index_didname, hn);
497     }
498
499     if (flags & DIRCACHE) {
500         if ((hn = hash_lookup(dircache, dir)) == NULL) {
501             LOG(log_error, logtype_default, "dircache_remove(%u,\"%s\"): not in dircache", 
502                 ntohl(dir->d_did), cfrombstr(dir->d_u_name));
503             dircache_dump();
504             AFP_PANIC("dircache_remove");
505         }
506         hash_delete_free(dircache, hn);
507     }
508
509     LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {removed}",
510         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
511
512     dircache_stat.removed++;
513     AFP_ASSERT(queue_count == index_didname->hash_nodecount 
514                && queue_count == dircache->hash_nodecount);
515 }
516
517 /*!
518  * @brief Initialize the dircache and indexes
519  *
520  * This is called in child afpd initialisation. The maximum cache size will be
521  * max(DEFAULT_MAX_DIRCACHE_SIZE, min(size, MAX_POSSIBLE_DIRCACHE_SIZE)).
522  * It initializes a hashtable which we use to store a directory cache in.
523  * It also initializes two indexes:
524  * - a DID/name index on the main dircache
525  * - a queue index on the dircache
526  *
527  * @param size   (r) requested maximum size from afpd.conf
528  *
529  * @return 0 on success, -1 on error
530  */
531 int dircache_init(int reqsize)
532 {
533     dircache_maxsize = DEFAULT_MAX_DIRCACHE_SIZE;
534
535     /* Initialize the main dircache */
536     if (reqsize > DEFAULT_MAX_DIRCACHE_SIZE && reqsize < MAX_POSSIBLE_DIRCACHE_SIZE) {
537         while ((dircache_maxsize < MAX_POSSIBLE_DIRCACHE_SIZE) && (dircache_maxsize < reqsize))
538                dircache_maxsize *= 2;
539     }
540     if ((dircache = hash_create(dircache_maxsize, hash_comp_vid_did, hash_vid_did)) == NULL)
541         return -1;
542     
543     LOG(log_debug, logtype_afpd, "dircache_init: done. max dircache size: %u", dircache_maxsize);
544
545     /* Initialize did/name index hashtable */
546     if ((index_didname = hash_create(dircache_maxsize, hash_comp_didname, hash_didname)) == NULL)
547         return -1;
548
549     /* Initialize index queue */
550     if ((index_queue = queue_init()) == NULL)
551         return -1;
552     else
553         queue_count = 0;
554
555     /* As long as directory.c hasn't got its own initializer call, we do it for it */
556     rootParent.d_did = DIRDID_ROOT_PARENT;
557     rootParent.d_fullpath = bfromcstr("ROOT_PARENT");
558     rootParent.d_m_name = bfromcstr("ROOT_PARENT");
559     rootParent.d_u_name = rootParent.d_m_name;
560
561     return 0;
562 }
563
564 /*!
565  * Log dircache statistics
566  */
567 void log_dircache_stat(void)
568 {
569     LOG(log_info, logtype_afpd, "dircache statistics: "
570         "entries: %lu, lookups: %llu, hits: %llu, misses: %llu, added: %llu, removed: %llu, expunged: %llu, evicted: %llu",
571         queue_count,
572         dircache_stat.lookups,
573         dircache_stat.hits,
574         dircache_stat.misses,
575         dircache_stat.added,
576         dircache_stat.removed,
577         dircache_stat.expunged,
578         dircache_stat.evicted);
579 }
580
581 /*!
582  * @brief Dump dircache to /tmp/dircache.PID
583  */
584 void dircache_dump(void)
585 {
586     char tmpnam[64];
587     FILE *dump;
588     qnode_t *n = index_queue->next;
589     hnode_t *hn;
590     hscan_t hs;
591     const struct dir *dir;
592     int i;
593
594     LOG(log_warning, logtype_afpd, "Dumping directory cache...");
595
596     sprintf(tmpnam, "/tmp/dircache.%u", getpid());
597     if ((dump = fopen(tmpnam, "w+")) == NULL) {
598         LOG(log_error, logtype_afpd, "dircache_dump: %s", strerror(errno));
599         return;
600     }
601     setbuf(dump, NULL);
602
603     fprintf(dump, "Number of cache entries in LRU queue: %lu\n", queue_count);
604     fprintf(dump, "Configured maximum cache size: %u\n\n", dircache_maxsize);
605
606     fprintf(dump, "Primary CNID index:\n");
607     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
608     fprintf(dump, "====================================================================\n");
609     hash_scan_begin(&hs, dircache);
610     i = 1;
611     while ((hn = hash_scan_next(&hs))) {
612         dir = hnode_get(hn);
613         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
614                 i++,
615                 ntohs(dir->d_vid),
616                 ntohl(dir->d_pdid),
617                 ntohl(dir->d_did),
618                 dir->d_fullpath ? "d" : "f",
619                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
620                 cfrombstr(dir->d_u_name));
621     }
622
623     fprintf(dump, "\nSecondary DID/name index:\n");
624     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
625     fprintf(dump, "====================================================================\n");
626     hash_scan_begin(&hs, index_didname);
627     i = 1;
628     while ((hn = hash_scan_next(&hs))) {
629         dir = hnode_get(hn);
630         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
631                 i++,
632                 ntohs(dir->d_vid),
633                 ntohl(dir->d_pdid),
634                 ntohl(dir->d_did),
635                 dir->d_fullpath ? "d" : "f",
636                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
637                 cfrombstr(dir->d_u_name));
638     }
639
640     fprintf(dump, "\nLRU Queue:\n");
641     fprintf(dump, "       VID     DID    CNID STAT PATH\n");
642     fprintf(dump, "====================================================================\n");
643
644     for (i = 1; i <= queue_count; i++) {
645         if (n == index_queue)
646             break;
647         dir = (struct dir *)n->data;
648         fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
649                 i,
650                 ntohs(dir->d_vid),
651                 ntohl(dir->d_pdid),
652                 ntohl(dir->d_did),
653                 dir->d_fullpath ? "d" : "f",
654                 (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
655                 cfrombstr(dir->d_u_name));
656         n = n->next;
657     }
658
659     fprintf(dump, "\n");
660     return;
661 }