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