A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 3274 lines 92 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2005 by Miika Pekkarinen 11 * Copyright (C) 2014 by Michael Sevakis 12 * 13 * This program is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU General Public License 15 * as published by the Free Software Foundation; either version 2 16 * of the License, or (at your option) any later version. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ****************************************************************************/ 22#include "config.h" 23#include <stdio.h> 24#include <errno.h> 25#include "string-extra.h" 26#include <stdbool.h> 27#include <stdlib.h> 28#include "debug.h" 29#include "system.h" 30#include "logf.h" 31#include "fileobj_mgr.h" 32#include "pathfuncs.h" 33#include "dircache.h" 34#include "thread.h" 35#include "kernel.h" 36#include "usb.h" 37#include "file.h" 38#include "core_alloc.h" 39#include "dir.h" 40#include "storage.h" 41#include "audio.h" 42#include "rbpaths.h" 43#include "linked_list.h" 44#include "crc32.h" 45 46/** 47 * Cache memory layout: 48 * x - array of struct dircache_entry 49 * r - reserved buffer 50 * d - name buffer for the name entry of the struct dircache_entry 51 * 0 - zero bytes to assist free name block sentinel scanning (not 0xfe or 0xff) 52 * |xxxxxx|rrrrrrrrr|0|dddddd|0| 53 * 54 * Subsequent x are allocated from the front, d are allocated from the back, 55 * using the reserve buffer for entries added after initial scan. 56 * 57 * After a while the cache may look like: 58 * |xxxxxxxx|rrrrr|0|dddddddd|0| 59 * 60 * After a reboot, the reserve buffer is restored in it's size, so that the 61 * total allocation size grows: 62 * |xxxxxxxx|rrrrrrrrr|0|dddddddd|0| 63 * 64 * 65 * Cache structure: 66 * Format is memory position independent and uses only indexes as links. The 67 * buffer pointers are offset back by one entry to make the array 1-based so 68 * that an index of 0 may be considered an analog of a NULL pointer. 69 * 70 * Entry elements are linked together analagously to the filesystem directory 71 * structure with minor variations that are helpful to the cache's algorithms. 72 * Each volume has a special root structure in the dircache structure, not an 73 * entry in the cache, comprising a forest of volume trees which facilitates 74 * mounting or dismounting of specified volumes on the fly. 75 * 76 * Indexes identifying a volume are computed as: index = -volume - 1 77 * Returning the volume from these indexes is thus: volume = -index - 1 78 * Such indexes are used in root binding and as the 'up' index for an entry 79 * who's parent is the root directory. 80 * 81 * Open files list: 82 * When dircache is made it is the maintainer of the main volume open files 83 * lists, even when it is off. Any files open before dircache is enabled or 84 * initialized must be bound to cache entries by the scan and build operation. 85 * It maintains these lists in a special way. 86 * 87 * Queued (unresolved) bindings are at the back and resolved at the front. 88 * A pointer to the first of each kind of binding is provided to skip to help 89 * iterating one sublist or another. 90 * 91 * r0->r1->r2->q0->q1->q2->NULL 92 * ^resolved0 ^queued0 93 */ 94 95#ifdef DIRCACHE_NATIVE 96#define dircache_lock() file_internal_lock_WRITER() 97#define dircache_unlock() file_internal_unlock_WRITER() 98 99/* scan and build parameter data */ 100struct sab_component; 101struct sab 102{ 103 struct filestr_base stream; /* scan directory stream */ 104 struct file_base_info info; /* scanned entry info */ 105 bool volatile quit; /* halt all scanning */ 106 struct sab_component *stackend; /* end of stack pointer */ 107 struct sab_component *top; /* current top of stack */ 108 struct sab_component 109 { 110 int volatile idx; /* cache index of directory */ 111 int *downp; /* pointer to ce->down */ 112 int *volatile prevp; /* previous item accessed */ 113 } stack[]; /* "recursion" stack */ 114}; 115 116#else /* !DIRCACHE_NATIVE */ 117 118#error need locking scheme 119#define FILESYS_WRITER_LOCK() 120#define FILESYS_WRITER_UNLOCK() 121 122struct sab_component 123{ 124}; 125 126struct sab 127{ 128#ifdef HAVE_MUTLIVOLUME 129 int volume; 130#endif /* HAVE_MULTIVOLUME */ 131 char path[MAX_PATH]; 132 unsigned int append; 133}; 134#endif /* DIRCACHE_NATIVE */ 135 136enum 137{ 138 FRONTIER_SETTLED = 0x0, /* dir entry contents are complete */ 139 FRONTIER_NEW = 0x1, /* dir entry contents are in-progress */ 140 FRONTIER_ZONED = 0x2, /* frontier entry permanent mark (very sticky!) */ 141 FRONTIER_RENEW = 0x4, /* override FRONTIER_ZONED sticky (not stored) */ 142}; 143 144enum 145{ 146 DCM_BUILD, /* build a volume */ 147 DCM_PROCEED, /* merged DCM_BUILD messages */ 148 DCM_FIRST = DCM_BUILD, 149 DCM_LAST = DCM_PROCEED, 150}; 151 152#define MAX_TINYNAME sizeof (uint32_t) 153#define NAMELEN_ADJ (MAX_TINYNAME+1) 154#define DC_MAX_NAME (UINT8_MAX+NAMELEN_ADJ) 155#define CE_NAMESIZE(len) ((len)+NAMELEN_ADJ) 156#define NAMESIZE_CE(size) ((size)-NAMELEN_ADJ) 157 158/* Throw some warnings if about the limits if things may not work */ 159#if MAX_COMPNAME > UINT8_MAX+5 160#warning Need more than 8 bits in name length bitfield 161#endif 162 163#if DIRCACHE_LIMIT > ((1 << 24)-1+5) 164#warning Names may not be addressable with 24 bits 165#endif 166 167/* data structure used by cache entries */ 168struct dircache_entry 169{ 170 int next; /* next at same level */ 171 union { 172 int down; /* first at child level (if directory) */ 173 file_size_t filesize; /* size of file in bytes (if file) */ 174 }; 175 int up; /* parent index (-volume-1 if root) */ 176 union { 177 struct { 178 uint32_t name : 24; /* indirect storage (.tinyname == 0) */ 179 uint32_t namelen : 8; /* length of name - (MAX_TINYNAME+1) */ 180 }; 181 unsigned char namebuf[MAX_TINYNAME]; /* direct storage (.tinyname == 1) */ 182 }; 183 uint32_t direntry : 16; /* entry # in parent - max 0xffff */ 184 uint32_t direntries : 5; /* # of entries used - max 21 */ 185 uint32_t tinyname : 1; /* if == 1, name fits in .namebuf */ 186 uint32_t frontier : 2; /* (FRONTIER_* bitflags) */ 187 uint32_t attr : 8; /* entry file attributes */ 188#ifdef DIRCACHE_NATIVE 189 long firstcluster; /* first file cluster - max 0x0ffffff4 */ 190 uint16_t wrtdate; /* FAT write date */ 191 uint16_t wrttime; /* FAT write time */ 192#else 193 time_t mtime; /* file last-modified time */ 194#endif 195 dc_serial_t serialnum; /* entry serial number */ 196}; 197 198/* spare us some tedium */ 199#define ENTRYSIZE (sizeof (struct dircache_entry)) 200 201/* thread and kernel stuff */ 202static struct event_queue dircache_queue SHAREDBSS_ATTR; 203static uintptr_t dircache_stack[DIRCACHE_STACK_SIZE / sizeof (uintptr_t)]; 204static const char dircache_thread_name[] = "dircache"; 205 206/* struct that is both used during run time and for persistent storage */ 207static struct dircache 208{ 209 /* cache-wide data */ 210 int free_list; /* first index of free entry list */ 211 size_t size; /* total size of data (including holes) */ 212 size_t sizeused; /* bytes of .size bytes actually used */ 213 union { 214 unsigned int numentries; /* entry count (including holes) */ 215#ifdef HAVE_EEPROM_SETTINGS 216 size_t sizeentries; /* used when persisting */ 217#endif 218 }; 219 int names; /* index of first name in name block */ 220 size_t sizenames; /* size of all names (including holes) */ 221 size_t namesfree; /* amount of wasted name space */ 222 int nextnamefree; /* hint of next free name in buffer */ 223 /* per-volume data */ 224 struct dircache_volume /* per volume cache data */ 225 { 226 uint32_t status : 2; /* cache status of this volume */ 227 uint32_t frontier : 2; /* (FRONTIER_* bitflags) */ 228 dc_serial_t serialnum; /* dircache serial number of root */ 229 int root_down; /* index of first entry of volume root */ 230 union { 231 long start_tick; /* when did scan start (scanning) */ 232 long build_ticks; /* how long to build volume? (ready) */ 233 }; 234 } dcvol[NUM_VOLUMES]; 235 /* these remain unchanged between cache resets */ 236 size_t last_size; /* last reported size at boot */ 237 size_t reserve_used; /* reserved used at last build */ 238 dc_serial_t last_serialnum; /* last serialnumber generated */ 239} dircache; 240 241/* struct that is used only for the cache in main memory */ 242static struct dircache_runinfo 243{ 244 /* cache setting and build info */ 245 int suspended; /* dircache suspend count */ 246 bool enabled; /* dircache master enable switch */ 247 unsigned int thread_id; /* current/last thread id */ 248 bool thread_done; /* thread has exited */ 249 /* cache buffer info */ 250 int handle; /* buflib buffer handle */ 251 size_t bufsize; /* size of buflib allocation - 1 */ 252 union { 253 void *p; /* address of buffer - ENTRYSIZE */ 254 struct dircache_entry *pentry; /* alias of .p to assist entry resolution */ 255 unsigned char *pname; /* alias of .p to assist name resolution */ 256 }; 257 struct buflib_callbacks ops; /* buflib ops callbacks */ 258 /* per-volume data */ 259 struct dircache_runinfo_volume 260 { 261 struct file_base_binding *resolved0; /* first resolved binding in list */ 262 struct file_base_binding *queued0; /* first queued binding in list */ 263 struct sab *sabp; /* if building, struct sab in use */ 264 } dcrivol[NUM_VOLUMES]; 265} dircache_runinfo; 266 267#define BINDING_NEXT(bindp) \ 268 ((struct file_base_binding *)(bindp)->node.next) 269 270#define FOR_EACH_BINDING(start, p) \ 271 for (struct file_base_binding *p = (start); p; p = BINDING_NEXT(p)) 272 273#define FOR_EACH_CACHE_ENTRY(ce) \ 274 for (struct dircache_entry *ce = &dircache_runinfo.pentry[1], \ 275 *_ceend = ce + dircache.numentries; \ 276 ce < _ceend; ce++) if (ce->serialnum) 277 278#define FOR_EACH_SAB_COMP(sabp, p) \ 279 for (struct sab_component *p = (sabp)->top; p < (sabp)->stackend; p++) 280 281/* "overloaded" macros to get volume structures */ 282#define DCVOL_i(i) (&dircache.dcvol[i]) 283#define DCVOL_volume(volume) (&dircache.dcvol[volume]) 284#define DCVOL_infop(infop) (&dircache.dcvol[BASEINFO_VOL(infop)]) 285#define DCVOL_dirinfop(dirinfop) (&dircache.dcvol[BASEINFO_VOL(dirinfop)]) 286#define DCVOL(x) DCVOL_##x(x) 287 288#define DCRIVOL_i(i) (&dircache_runinfo.dcrivol[i]) 289#define DCRIVOL_infop(infop) (&dircache_runinfo.dcrivol[BASEINFO_VOL(infop)]) 290#define DCRIVOL_bindp(bindp) (&dircache_runinfo.dcrivol[BASEBINDING_VOL(bindp)]) 291#define DCRIVOL(x) DCRIVOL_##x(x) 292 293/* reserve over 75% full? */ 294#define DIRCACHE_STUFFED(reserve_used) \ 295 ((reserve_used) > 3*DIRCACHE_RESERVE / 4) 296 297#ifdef HAVE_EEPROM_SETTINGS 298/** 299 * remove the snapshot file 300 */ 301static int remove_dircache_file(void) 302{ 303 return remove(DIRCACHE_FILE); 304} 305 306/** 307 * open or create the snapshot file 308 */ 309static int open_dircache_file(int oflag) 310{ 311 return open(DIRCACHE_FILE, oflag, 0666); 312} 313#endif /* HAVE_EEPROM_SETTINGS */ 314 315#ifdef DIRCACHE_DUMPSTER 316/** 317 * clean up the memory allocation to make viewing in a hex editor more friendly 318 * and highlight problems 319 */ 320static inline void dumpster_clean_buffer(void *p, size_t size) 321{ 322 memset(p, 0xAA, size); 323} 324#endif /* DIRCACHE_DUMPSTER */ 325 326/** 327 * relocate the cache when the buffer has moved 328 */ 329static int move_callback(int handle, void *current, void *new) 330{ 331 (void)handle; (void)current; 332 dircache_runinfo.p = new - ENTRYSIZE; 333 return BUFLIB_CB_OK; 334} 335 336 337/** Open file bindings management **/ 338 339/* compare the basic file information and return 'true' if they are logically 340 equivalent or the same item, else return 'false' if not */ 341static inline bool binding_compare(const struct file_base_info *infop1, 342 const struct file_base_info *infop2) 343{ 344#ifdef DIRCACHE_NATIVE 345 return fat_file_is_same(&infop1->fatfile, &infop2->fatfile); 346#else 347 #error hey watch it! 348#endif 349} 350 351/** 352 * bind a file to the cache; "queued" or "resolved" depending upon whether or 353 * not it has entry information 354 */ 355static void binding_open(struct file_base_binding *bindp) 356{ 357 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); 358 if (bindp->info.dcfile.serialnum) 359 { 360 /* already resolved */ 361 dcrivolp->resolved0 = bindp; 362 file_binding_insert_first(bindp); 363 } 364 else 365 { 366 if (dcrivolp->queued0 == NULL) 367 dcrivolp->queued0 = bindp; 368 369 file_binding_insert_last(bindp); 370 } 371} 372 373/** 374 * remove a binding from the cache 375 */ 376static void binding_close(struct file_base_binding *bindp) 377{ 378 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); 379 380 if (bindp == dcrivolp->queued0) 381 dcrivolp->queued0 = BINDING_NEXT(bindp); 382 else if (bindp == dcrivolp->resolved0) 383 { 384 struct file_base_binding *nextp = BINDING_NEXT(bindp); 385 dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp; 386 } 387 388 file_binding_remove(bindp); 389 /* no need to reset it */ 390} 391 392/** 393 * resolve a queued binding with the information from the given source file 394 */ 395static void binding_resolve(const struct file_base_info *infop) 396{ 397 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); 398 399 /* quickly check the queued list to see if it's there */ 400 struct file_base_binding *prevp = NULL; 401 FOR_EACH_BINDING(dcrivolp->queued0, p) 402 { 403 if (!binding_compare(infop, &p->info)) 404 { 405 prevp = p; 406 continue; 407 } 408 409 if (p == dcrivolp->queued0) 410 { 411 dcrivolp->queued0 = BINDING_NEXT(p); 412 if (dcrivolp->resolved0 == NULL) 413 dcrivolp->resolved0 = p; 414 } 415 else 416 { 417 file_binding_remove_next(prevp, p); 418 file_binding_insert_first(p); 419 dcrivolp->resolved0 = p; 420 } 421 422 /* srcinfop may be the actual one */ 423 if (&p->info != infop) 424 p->info.dcfile = infop->dcfile; 425 426 break; 427 } 428} 429 430/** 431 * dissolve a resolved binding on its volume 432 */ 433static void binding_dissolve(struct file_base_binding *prevp, 434 struct file_base_binding *bindp) 435{ 436 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); 437 438 if (bindp == dcrivolp->resolved0) 439 { 440 struct file_base_binding *nextp = BINDING_NEXT(bindp); 441 dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp; 442 } 443 444 if (dcrivolp->queued0 == NULL) 445 dcrivolp->queued0 = bindp; 446 447 file_binding_remove_next(prevp, bindp); 448 file_binding_insert_last(bindp); 449 450 dircache_dcfile_init(&bindp->info.dcfile); 451} 452 453/** 454 * dissolve all resolved bindings on a given volume 455 */ 456static void binding_dissolve_volume(struct dircache_runinfo_volume *dcrivolp) 457{ 458 if (!dcrivolp->resolved0) 459 return; 460 461 FOR_EACH_BINDING(dcrivolp->resolved0, p) 462 { 463 if (p == dcrivolp->queued0) 464 break; 465 466 dircache_dcfile_init(&p->info.dcfile); 467 } 468 469 dcrivolp->queued0 = dcrivolp->resolved0; 470 dcrivolp->resolved0 = NULL; 471} 472 473 474/** Dircache buffer management **/ 475 476/** 477 * allocate the cache's memory block 478 */ 479static int alloc_cache(size_t size) 480{ 481 /* pad with one extra-- see alloc_name() and free_name() */ 482 return core_alloc_ex(size + 1, &dircache_runinfo.ops); 483} 484 485/** 486 * put the allocation in dircache control 487 */ 488static void set_buffer(int handle, size_t size) 489{ 490 void *p = core_get_data(handle); 491 492#ifdef DIRCACHE_DUMPSTER 493 dumpster_clean_buffer(p, size); 494#endif /* DIRCACHE_DUMPSTER */ 495 496 /* set it up as a 1-based array */ 497 dircache_runinfo.p = p - ENTRYSIZE; 498 499 if (dircache_runinfo.handle != handle) 500 { 501 /* new buffer */ 502 dircache_runinfo.handle = handle; 503 dircache_runinfo.bufsize = size; 504 dircache.names = size + ENTRYSIZE; 505 dircache_runinfo.pname[dircache.names - 1] = 0; 506 dircache_runinfo.pname[dircache.names ] = 0; 507 } 508} 509 510/** 511 * remove the allocation from dircache control and return the handle 512 * Note that dircache must not be using the buffer! 513 */ 514static int reset_buffer(void) 515{ 516 int handle = dircache_runinfo.handle; 517 if (handle > 0) 518 { 519 /* don't mind .p; buffer presence is determined by the following: */ 520 dircache_runinfo.handle = 0; 521 dircache_runinfo.bufsize = 0; 522 } 523 524 return handle; 525} 526 527/** 528 * return the number of bytes remaining in the buffer 529 */ 530static size_t dircache_buf_remaining(void) 531{ 532 if (!dircache_runinfo.handle) 533 return 0; 534 535 return dircache_runinfo.bufsize - dircache.size; 536} 537 538/** 539 * return the amount of reserve space used 540 */ 541static size_t reserve_buf_used(void) 542{ 543 size_t remaining = dircache_buf_remaining(); 544 return (remaining < DIRCACHE_RESERVE) ? 545 DIRCACHE_RESERVE - remaining : 0; 546} 547 548 549/** Internal cache structure control functions **/ 550 551/** 552 * generate the next serial number in the sequence 553 */ 554static dc_serial_t next_serialnum(void) 555{ 556 dc_serial_t serialnum = MAX(dircache.last_serialnum + 1, 1); 557 dircache.last_serialnum = serialnum; 558 return serialnum; 559} 560 561/** 562 * return the dircache volume pointer for the special index 563 */ 564static struct dircache_volume * get_idx_dcvolp(int idx) 565{ 566 if (idx >= 0) 567 return NULL; 568 569 return &dircache.dcvol[IF_MV_VOL(-idx - 1)]; 570} 571 572/** 573 * return the cache entry referenced by idx (NULL if outside buffer) 574 */ 575static struct dircache_entry * get_entry(int idx) 576{ 577 if (idx <= 0 || (unsigned int)idx > dircache.numentries) 578 return NULL; 579 580 return &dircache_runinfo.pentry[idx]; 581} 582 583/** 584 * return the index of the cache entry (0 if outside buffer) 585 */ 586static int get_index(const struct dircache_entry *ce) 587{ 588 if (!PTR_IN_ARRAY(dircache_runinfo.pentry + 1, ce, 589 dircache.numentries + 1)) 590 { 591 return 0; 592 } 593 594 return ce - dircache_runinfo.pentry; 595} 596 597/** 598 * return the frontier flags for the index 599 */ 600static uint32_t get_frontier(int idx) 601{ 602 if (idx == 0) 603 return UINT32_MAX; 604 else if (idx > 0) 605 return get_entry(idx)->frontier; 606 else /* idx < 0 */ 607 return get_idx_dcvolp(idx)->frontier; 608} 609 610/** 611 * return the sublist down pointer for the sublist that contains entry 'idx' 612 */ 613static int * get_downidxp(int idx) 614{ 615 /* NOTE: 'idx' must refer to a directory or the result is undefined */ 616 if (idx == 0 || idx < -NUM_VOLUMES) 617 return NULL; 618 619 if (idx > 0) 620 { 621 /* a normal entry */ 622 struct dircache_entry *ce = get_entry(idx); 623 return ce ? &ce->down : NULL; 624 } 625 else 626 { 627 /* a volume root */ 628 return &get_idx_dcvolp(idx)->root_down; 629 } 630} 631 632/** 633 * return a pointer to the index referencing the cache entry that 'idx' 634 * references 635 */ 636static int * get_previdxp(int idx) 637{ 638 struct dircache_entry *ce = get_entry(idx); 639 640 int *prevp = get_downidxp(ce->up); 641 if (!prevp) 642 return NULL; 643 644 while (1) 645 { 646 int next = *prevp; 647 if (!next || next == idx) 648 break; 649 650 prevp = &get_entry(next)->next; 651 } 652 653 return prevp; 654} 655 656/** 657 * if required, adjust the lists and directory read of any scan and build in 658 * progress 659 */ 660static void sab_sync_scan(struct sab *sabp, int *prevp, int *nextp) 661{ 662 struct sab_component *abovep = NULL; 663 FOR_EACH_SAB_COMP(sabp, p) 664 { 665 if (nextp != p->prevp) 666 { 667 abovep = p; 668 continue; 669 } 670 671 /* removing an item being scanned; set the component position to the 672 entry before this */ 673 p->prevp = prevp; 674 675 if (p == sabp->top) 676 { 677 /* removed at item in the directory who's immediate contents are 678 being scanned */ 679 if (prevp == p->downp) 680 { 681 /* was first item; rewind it */ 682 dircache_rewinddir_internal(&sabp->info); 683 } 684 else 685 { 686 struct dircache_entry *ceprev = 687 container_of(prevp, struct dircache_entry, next); 688 #ifdef DIRCACHE_NATIVE 689 sabp->info.fatfile.e.entry = ceprev->direntry; 690 sabp->info.fatfile.e.entries = ceprev->direntries; 691 #endif 692 } 693 } 694 else if (abovep) 695 { 696 /* the directory being scanned or a parent of it has been removed; 697 abort its build or cache traversal */ 698 abovep->idx = 0; 699 } 700 701 break; 702 } 703} 704 705/** 706 * get a pointer to an allocated name given a cache index 707 */ 708static inline unsigned char * get_name(int nameidx) 709{ 710 return &dircache_runinfo.pname[nameidx]; 711} 712 713/** 714 * get the cache buffer index of the given name 715 */ 716static inline int get_nameidx(const unsigned char *pname) 717{ 718 return pname - dircache_runinfo.pname; 719} 720 721/** 722 * copy the entry's name to a buffer (which assumed to be of sufficient size) 723 */ 724static void entry_name_copy(char *dst, const struct dircache_entry *ce) 725{ 726 if (LIKELY(!ce->tinyname)) 727 { 728 strmemcpy(dst, get_name(ce->name), CE_NAMESIZE(ce->namelen)); 729 return; 730 } 731 732 const unsigned char *src = ce->namebuf; 733 size_t len = 0; 734 while (len++ < MAX_TINYNAME && *src) 735 *dst++ = *src++; 736 737 *dst = '\0'; 738} 739 740/** 741 * set the namesfree hint to a new position 742 */ 743static void set_namesfree_hint(const unsigned char *hintp) 744{ 745 int hintidx = get_nameidx(hintp); 746 747 if (hintidx >= (int)(dircache.names + dircache.sizenames)) 748 hintidx = dircache.names; 749 750 dircache.nextnamefree = hintidx; 751} 752 753/** 754 * allocate a buffer to use for a new name 755 */ 756static int alloc_name(size_t size) 757{ 758 int nameidx = 0; 759 760 if (dircache.namesfree >= size) 761 { 762 /* scan for a free gap starting at the hint point - first fit */ 763 unsigned char * const start = get_name(dircache.nextnamefree); 764 unsigned char * const bufend = get_name(dircache.names + dircache.sizenames); 765 unsigned char *p = start; 766 unsigned char *end = bufend; 767 768 while (1) 769 { 770 if ((size_t)(bufend - p) >= size && (p = memchr(p, 0xff, end - p))) 771 { 772 /* found a sentinel; see if there are enough in a row */ 773 unsigned char *q = p + size - 1; 774 775 /* check end byte and every MAX_TINYNAME+1 bytes from the end; 776 the minimum-length indirectly allocated string that could be 777 in between must have at least one character at one of those 778 locations */ 779 while (q > p && *q == 0xff) 780 q -= MAX_TINYNAME+1; 781 782 if (q <= p) 783 { 784 nameidx = get_nameidx(p); 785 break; 786 } 787 788 p += size; 789 } 790 else 791 { 792 if (end == start) 793 break; /* exhausted */ 794 795 /* wrap */ 796 end = start; 797 p = get_name(dircache.names); 798 799 if (p == end) 800 break; /* initial hint was at names start */ 801 } 802 } 803 804 if (nameidx) 805 { 806 unsigned char *q = p + size; 807 if (q[0] == 0xff && q[MAX_TINYNAME] != 0xff) 808 { 809 /* if only a tiny block remains after buffer, claim it and 810 hide it from scans since it's too small for indirect 811 allocation */ 812 do 813 { 814 *q = 0xfe; 815 size++; 816 } 817 while (*++q == 0xff); 818 } 819 820 dircache.namesfree -= size; 821 dircache.sizeused += size; 822 set_namesfree_hint(p + size); 823 } 824 } 825 826 if (!nameidx) 827 { 828 /* no sufficiently long free gaps; allocate anew */ 829 if (dircache_buf_remaining() <= size) 830 { 831 dircache.last_size = 0; 832 return 0; 833 } 834 835 dircache.names -= size; 836 dircache.sizenames += size; 837 nameidx = dircache.names; 838 dircache.size += size; 839 dircache.sizeused += size; 840 *get_name(dircache.names - 1) = 0; 841 } 842 843 return nameidx; 844} 845 846/** 847 * mark a name as free and note that its bytes are available 848 */ 849static void free_name(int nameidx, size_t size) 850{ 851 unsigned char *beg = get_name(nameidx); 852 unsigned char *end = beg + size; 853 854 /* merge with any adjacent tiny blocks */ 855 while (beg[-1] == 0xfe) 856 --beg; 857 858 while (end[0] == 0xfe) 859 ++end; 860 861 size = end - beg; 862 memset(beg, 0xff, size); 863 dircache.namesfree += size; 864 dircache.sizeused -= size; 865 set_namesfree_hint(beg); 866} 867 868/** 869 * allocate and assign a name to the entry 870 */ 871static int entry_assign_name(struct dircache_entry *ce, 872 const unsigned char *name, size_t size) 873{ 874 unsigned char *copyto; 875 876 if (size <= MAX_TINYNAME) 877 { 878 copyto = ce->namebuf; 879 880 if (size < MAX_TINYNAME) 881 copyto[size] = '\0'; 882 883 ce->tinyname = 1; 884 } 885 else 886 { 887 if (size > DC_MAX_NAME) 888 return -ENAMETOOLONG; 889 890 int nameidx = alloc_name(size); 891 if (!nameidx) 892 return -ENOSPC; 893 894 copyto = get_name(nameidx); 895 896 ce->tinyname = 0; 897 ce->name = nameidx; 898 ce->namelen = NAMESIZE_CE(size); 899 } 900 901 memcpy(copyto, name, size); 902 return 0; 903} 904 905/** 906 * free the name for the entry 907 */ 908static void entry_unassign_name(struct dircache_entry *ce) 909{ 910 if (ce->tinyname) 911 return; 912 913 free_name(ce->name, CE_NAMESIZE(ce->namelen)); 914 ce->tinyname = 1; 915} 916 917/** 918 * assign a new name to the entry 919 */ 920static int entry_reassign_name(struct dircache_entry *ce, 921 const unsigned char *newname) 922{ 923 size_t oldlen = ce->tinyname ? 0 : CE_NAMESIZE(ce->namelen); 924 size_t newlen = strlen(newname); 925 926 if (oldlen == newlen || (oldlen == 0 && newlen <= MAX_TINYNAME)) 927 { 928 char *p = mempcpy(oldlen == 0 ? ce->namebuf : get_name(ce->name), 929 newname, newlen); 930 if (newlen < MAX_TINYNAME) 931 *p = '\0'; 932 return 0; 933 } 934 935 /* needs a new name allocation; if the new name fits in the freed block, 936 it will use it immediately without a lengthy search */ 937 entry_unassign_name(ce); 938 return entry_assign_name(ce, newname, newlen); 939} 940 941/** 942 * allocate a dircache_entry from memory using freed ones if available 943 */ 944static int alloc_entry(struct dircache_entry **res) 945{ 946 struct dircache_entry *ce; 947 int idx = dircache.free_list; 948 949 if (idx) 950 { 951 /* reuse a freed entry */ 952 ce = get_entry(idx); 953 dircache.free_list = ce->next; 954 } 955 else if (dircache_buf_remaining() > ENTRYSIZE) 956 { 957 /* allocate a new one */ 958 idx = ++dircache.numentries; 959 dircache.size += ENTRYSIZE; 960 ce = get_entry(idx); 961 } 962 else 963 { 964 dircache.last_size = 0; 965 *res = NULL; 966 return -ENOSPC; 967 } 968 969 dircache.sizeused += ENTRYSIZE; 970 971 ce->next = 0; 972 ce->up = 0; 973 ce->down = 0; 974 ce->tinyname = 1; 975 ce->frontier = FRONTIER_SETTLED; 976 ce->serialnum = next_serialnum(); 977 978 *res = ce; 979 return idx; 980} 981 982/** 983 * free an entry's allocations in the cache; must not be linked to anything 984 * by this time (orphan!) 985 */ 986static void free_orphan_entry(struct dircache_runinfo_volume *dcrivolp, 987 struct dircache_entry *ce, int idx) 988{ 989 if (dcrivolp) 990 { 991 /* was an established entry; find any associated resolved binding and 992 dissolve it; bindings are kept strictly synchronized with changes 993 to the storage so a simple serial number comparison is sufficient */ 994 struct file_base_binding *prevp = NULL; 995 FOR_EACH_BINDING(dcrivolp->resolved0, p) 996 { 997 if (p == dcrivolp->queued0) 998 break; 999 1000 if (ce->serialnum == p->info.dcfile.serialnum) 1001 { 1002 binding_dissolve(prevp, p); 1003 break; 1004 } 1005 1006 prevp = p; 1007 } 1008 } 1009 1010 entry_unassign_name(ce); 1011 1012 /* no serialnum says "it's free" (for cache-wide iterators) */ 1013 ce->serialnum = 0; 1014 1015 /* add to free list */ 1016 ce->next = dircache.free_list; 1017 dircache.free_list = idx; 1018 dircache.sizeused -= ENTRYSIZE; 1019} 1020 1021/** 1022 * allocates a new entry of with the name specified by 'basename' 1023 */ 1024static int create_entry(const char *basename, 1025 struct dircache_entry **res) 1026{ 1027 int idx = alloc_entry(res); 1028 1029 if (idx > 0) 1030 { 1031 int rc = entry_assign_name(*res, basename, strlen(basename)); 1032 if (rc < 0) 1033 { 1034 free_orphan_entry(NULL, *res, idx); 1035 idx = rc; 1036 } 1037 } 1038 1039 if (idx == -ENOSPC) 1040 logf("size limit reached"); 1041 1042 return idx; 1043} 1044 1045/** 1046 * unlink the entry at *prevp and adjust the scanner if needed 1047 */ 1048static void remove_entry(struct dircache_runinfo_volume *dcrivolp, 1049 struct dircache_entry *ce, int *prevp) 1050{ 1051 /* unlink it from its list */ 1052 *prevp = ce->next; 1053 1054 if (dcrivolp) 1055 { 1056 /* adjust scanner iterator if needed */ 1057 struct sab *sabp = dcrivolp->sabp; 1058 if (sabp) 1059 sab_sync_scan(sabp, prevp, &ce->next); 1060 } 1061} 1062 1063/** 1064 * free the entire subtree in the referenced parent down index 1065 */ 1066static void free_subentries(struct dircache_runinfo_volume *dcrivolp, int *downp) 1067{ 1068 while (1) 1069 { 1070 int idx = *downp; 1071 struct dircache_entry *ce = get_entry(idx); 1072 if (!ce) 1073 break; 1074 1075 if ((ce->attr & ATTR_DIRECTORY) && ce->down) 1076 free_subentries(dcrivolp, &ce->down); 1077 1078 remove_entry(dcrivolp, ce, downp); 1079 free_orphan_entry(dcrivolp, ce, idx); 1080 } 1081} 1082 1083/** 1084 * free the specified file entry and its children 1085 */ 1086static void free_file_entry(struct file_base_info *infop) 1087{ 1088 int idx = infop->dcfile.idx; 1089 if (idx <= 0) 1090 return; /* can't remove a root/invalid */ 1091 1092 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); 1093 1094 struct dircache_entry *ce = get_entry(idx); 1095 if ((ce->attr & ATTR_DIRECTORY) && ce->down) 1096 { 1097 /* gonna get all this contents (normally the "." and "..") */ 1098 free_subentries(dcrivolp, &ce->down); 1099 } 1100 1101 remove_entry(dcrivolp, ce, get_previdxp(idx)); 1102 free_orphan_entry(dcrivolp, ce, idx); 1103} 1104 1105/** 1106 * insert the new entry into the parent, sorted into position 1107 */ 1108static void insert_file_entry(struct file_base_info *dirinfop, 1109 struct dircache_entry *ce) 1110{ 1111 /* DIRCACHE_NATIVE: the entires are sorted into the spot it would be on 1112 * the storage medium based upon the directory entry number, in-progress 1113 * scans will catch it or miss it in just the same way they would if 1114 * directly scanning the disk. If this is behind an init scan, it gets 1115 * added anyway; if in front of it, then scanning will compare what it 1116 * finds in order to avoid adding a duplicate. 1117 * 1118 * All others: the order of entries of the host filesystem is not known so 1119 * this must be placed at the end so that a build scan won't miss it and 1120 * add a duplicate since it will be comparing any entries it finds in front 1121 * of it. 1122 */ 1123 int diridx = dirinfop->dcfile.idx; 1124 int *nextp = get_downidxp(diridx); 1125 1126 while (8675309) 1127 { 1128 struct dircache_entry *nextce = get_entry(*nextp); 1129 if (!nextce) 1130 break; 1131 1132#ifdef DIRCACHE_NATIVE 1133 if (nextce->direntry > ce->direntry) 1134 break; 1135 1136 /* now, nothing should be equal to ours or that is a bug since it 1137 would already exist (and it shouldn't because it's just been 1138 created or moved) */ 1139#endif /* DIRCACHE_NATIVE */ 1140 1141 nextp = &nextce->next; 1142 } 1143 1144 ce->up = diridx; 1145 ce->next = *nextp; 1146 *nextp = get_index(ce); 1147} 1148 1149/** 1150 * unlink the entry from its parent and return its pointer to the caller 1151 */ 1152static struct dircache_entry * remove_file_entry(struct file_base_info *infop) 1153{ 1154 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); 1155 struct dircache_entry *ce = get_entry(infop->dcfile.idx); 1156 remove_entry(dcrivolp, ce, get_previdxp(infop->dcfile.idx)); 1157 return ce; 1158} 1159 1160/** 1161 * set the frontier indicator for the given cache index 1162 */ 1163static void establish_frontier(int idx, uint32_t code) 1164{ 1165 if (idx < 0) 1166 { 1167 int volume = IF_MV_VOL(-idx - 1); 1168 uint32_t val = dircache.dcvol[volume].frontier; 1169 if (code & FRONTIER_RENEW) 1170 val &= ~FRONTIER_ZONED; 1171 dircache.dcvol[volume].frontier = code | (val & FRONTIER_ZONED); 1172 } 1173 else if (idx > 0) 1174 { 1175 struct dircache_entry *ce = get_entry(idx); 1176 uint32_t val = ce->frontier; 1177 if (code & FRONTIER_RENEW) 1178 val &= ~FRONTIER_ZONED; 1179 ce->frontier = code | (val & FRONTIER_ZONED); 1180 } 1181} 1182 1183/** 1184 * remove all messages from the queue, responding to anyone waiting 1185 */ 1186static void clear_dircache_queue(void) 1187{ 1188 struct queue_event ev; 1189 1190 while (1) 1191 { 1192 queue_wait_w_tmo(&dircache_queue, &ev, 0); 1193 if (ev.id == SYS_TIMEOUT) 1194 break; 1195 1196 /* respond to any synchronous build queries; since we're already 1197 building and thusly allocated, any additional requests can be 1198 processed async */ 1199 if (ev.id == DCM_BUILD) 1200 { 1201 int *rcp = (int *)ev.data; 1202 if (rcp) 1203 *rcp = 0; 1204 } 1205 } 1206} 1207 1208/** 1209 * service dircache_queue during a scan and build 1210 */ 1211static void process_events(void) 1212{ 1213 yield(); 1214 1215 /* only count externally generated commands */ 1216 if (!queue_peek_ex(&dircache_queue, NULL, 0, QPEEK_FILTER1(DCM_BUILD))) 1217 return; 1218 1219 clear_dircache_queue(); 1220 1221 /* this reminds us to keep moving after we're done here; a volume we passed 1222 up earlier could have been mounted and need refreshing; it just condenses 1223 a slew of requests into one and isn't mistaken for an externally generated 1224 command */ 1225 queue_post(&dircache_queue, DCM_PROCEED, 0); 1226} 1227 1228#if defined (DIRCACHE_NATIVE) 1229/** 1230 * scan and build the contents of a subdirectory 1231 */ 1232static void sab_process_sub(struct sab *sabp) 1233{ 1234 struct fat_direntry *const fatentp = get_dir_fatent(); 1235 struct filestr_base *const streamp = &sabp->stream; 1236 struct file_base_info *const infop = &sabp->info; 1237 1238 int idx = infop->dcfile.idx; 1239 int *downp = get_downidxp(idx); 1240 if (!downp) 1241 return; 1242 1243 while (1) 1244 { 1245 struct sab_component *compp = --sabp->top; 1246 compp->idx = idx; 1247 compp->downp = downp; 1248 compp->prevp = downp; 1249 1250 /* open directory stream */ 1251 filestr_base_init(streamp); 1252 fileobj_fileop_open(streamp, infop, FO_DIRECTORY); 1253 fat_rewind(&streamp->fatstr); 1254 uncached_rewinddir_internal(infop); 1255 1256 const long dircluster = streamp->infop->fatfile.firstcluster; 1257 1258 /* first pass: read directory */ 1259 while (1) 1260 { 1261 if (sabp->stack + 1 < sabp->stackend) 1262 { 1263 /* release control and process queued events */ 1264 dircache_unlock(); 1265 process_events(); 1266 dircache_lock(); 1267 1268 if (sabp->quit || !compp->idx) 1269 break; 1270 } 1271 /* else an immediate-contents directory scan */ 1272 1273 int rc = uncached_readdir_internal(streamp, infop, fatentp); 1274 if (rc <= 0) 1275 { 1276 if (rc < 0) 1277 sabp->quit = true; 1278 else 1279 compp->prevp = downp; /* rewind list */ 1280 1281 break; 1282 } 1283 1284 struct dircache_entry *ce; 1285 int prev = *compp->prevp; 1286 1287 if (prev) 1288 { 1289 /* there are entries ahead of us; they will be what was just 1290 read or something to be subsequently read; if it belongs 1291 ahead of this one, insert a new entry before it; if it's 1292 the entry just scanned, do nothing further and continue 1293 with the next */ 1294 ce = get_entry(prev); 1295 if (ce->direntry == infop->fatfile.e.entry) 1296 { 1297 compp->prevp = &ce->next; 1298 continue; /* already there */ 1299 } 1300 } 1301 1302 int idx = create_entry(fatentp->name, &ce); 1303 if (idx <= 0) 1304 { 1305 if (idx == -ENAMETOOLONG) 1306 { 1307 /* not fatal; just don't include it */ 1308 establish_frontier(compp->idx, FRONTIER_ZONED); 1309 continue; 1310 } 1311 1312 sabp->quit = true; 1313 break; 1314 } 1315 1316 /* link it in */ 1317 ce->up = compp->idx; 1318 ce->next = prev; 1319 *compp->prevp = idx; 1320 compp->prevp = &ce->next; 1321 1322 if (!(fatentp->attr & ATTR_DIRECTORY)) 1323 ce->filesize = fatentp->filesize; 1324 else if (!is_dotdir_name(fatentp->name)) 1325 ce->frontier = FRONTIER_NEW; /* this needs scanning */ 1326 1327 /* copy remaining FS info */ 1328 ce->direntry = infop->fatfile.e.entry; 1329 ce->direntries = infop->fatfile.e.entries; 1330 ce->attr = fatentp->attr; 1331 ce->firstcluster = fatentp->firstcluster; 1332 ce->wrtdate = fatentp->wrtdate; 1333 ce->wrttime = fatentp->wrttime; 1334 1335 /* resolve queued user bindings */ 1336 infop->fatfile.firstcluster = fatentp->firstcluster; 1337 infop->fatfile.dircluster = dircluster; 1338 infop->dcfile.idx = idx; 1339 infop->dcfile.serialnum = ce->serialnum; 1340 binding_resolve(infop); 1341 } /* end while */ 1342 1343 close_stream_internal(streamp); 1344 1345 if (sabp->quit) 1346 return; 1347 1348 establish_frontier(compp->idx, FRONTIER_SETTLED); 1349 1350 /* second pass: "recurse!" */ 1351 struct dircache_entry *ce = NULL; 1352 1353 while (1) 1354 { 1355 idx = compp->idx && compp > sabp->stack ? *compp->prevp : 0; 1356 if (idx) 1357 { 1358 ce = get_entry(idx); 1359 compp->prevp = &ce->next; 1360 1361 if (ce->frontier != FRONTIER_SETTLED) 1362 break; 1363 } 1364 else 1365 { 1366 /* directory completed or removed/deepest level */ 1367 compp = ++sabp->top; 1368 if (compp >= sabp->stackend) 1369 return; /* scan completed/initial directory removed */ 1370 } 1371 } 1372 1373 /* even if it got zoned from outside it is about to be scanned in 1374 its entirety and may be considered new again */ 1375 ce->frontier = FRONTIER_NEW; 1376 downp = &ce->down; 1377 1378 /* set up info for next open 1379 * IF_MV: "volume" was set when scan began */ 1380 infop->fatfile.firstcluster = ce->firstcluster; 1381 infop->fatfile.dircluster = dircluster; 1382 infop->fatfile.e.entry = ce->direntry; 1383 infop->fatfile.e.entries = ce->direntries; 1384 infop->dcfile.idx = idx; 1385 infop->dcfile.serialnum = ce->serialnum; 1386 } /* end while */ 1387} 1388 1389/** 1390 * scan and build the contents of a directory or volume root 1391 */ 1392static void sab_process_dir(struct file_base_info *infop, bool issab) 1393{ 1394 /* infop should have been fully opened meaning that all its parent 1395 directory information is filled in and intact; the binding information 1396 should also filled in beforehand */ 1397 1398 /* allocate the stack right now to the max demand */ 1399 struct dirsab 1400 { 1401 struct sab sab; 1402 struct sab_component stack[issab ? DIRCACHE_MAX_DEPTH : 1]; 1403 } dirsab; 1404 struct sab *sabp = &dirsab.sab; 1405 1406 sabp->quit = false; 1407 sabp->stackend = &sabp->stack[ARRAYLEN(dirsab.stack)]; 1408 sabp->top = sabp->stackend; 1409 sabp->info = *infop; 1410 1411 if (issab) 1412 DCRIVOL(infop)->sabp = sabp; 1413 1414 establish_frontier(infop->dcfile.idx, FRONTIER_NEW | FRONTIER_RENEW); 1415 sab_process_sub(sabp); 1416 1417 if (issab) 1418 DCRIVOL(infop)->sabp = NULL; 1419} 1420 1421/** 1422 * scan and build the entire tree for a volume 1423 */ 1424static void sab_process_volume(struct dircache_volume *dcvolp) 1425{ 1426 int rc; 1427 1428 int volume = IF_MV_VOL(dcvolp - dircache.dcvol); 1429 int idx = -volume - 1; 1430 1431 logf("dircache - building volume %d", volume); 1432 1433 /* gather everything sab_process_dir() needs in order to begin a scan */ 1434 struct file_base_info info; 1435 rc = fat_open_rootdir(IF_MV(volume,) &info.fatfile); 1436 if (rc < 0) 1437 { 1438 /* probably not mounted */ 1439 logf("SAB - no root %d: %d", volume, rc); 1440 establish_frontier(idx, FRONTIER_NEW); 1441 return; 1442 } 1443 1444 info.dcfile.idx = idx; 1445 info.dcfile.serialnum = dcvolp->serialnum; 1446 binding_resolve(&info); 1447 sab_process_dir(&info, true); 1448} 1449 1450/** 1451 * this function is the back end to the public API's like readdir() 1452 */ 1453int dircache_readdir_dirent(struct filestr_base *stream, 1454 struct dirscan_info *scanp, 1455 struct DIRENT *entry) 1456{ 1457 struct file_base_info *dirinfop = stream->infop; 1458 1459 if (!dirinfop->dcfile.serialnum) 1460 goto read_uncached; /* no parent cached => no entries cached */ 1461 1462 struct dircache_volume *dcvolp = DCVOL(dirinfop); 1463 1464 int diridx = dirinfop->dcfile.idx; 1465 unsigned int frontier = diridx <= 0 ? dcvolp->frontier : 1466 get_entry(diridx)->frontier; 1467 1468 /* if not settled, just readthrough; no binding information is needed for 1469 this; if it becomes settled, we'll get scan->dcfile caught up and do 1470 subsequent reads with the cache */ 1471 if (frontier != FRONTIER_SETTLED) 1472 goto read_uncached; 1473 1474 int idx = scanp->dcscan.idx; 1475 struct dircache_entry *ce; 1476 1477 unsigned int direntry = scanp->fatscan.entry; 1478 while (1) 1479 { 1480 if (idx == 0 || direntry == FAT_DIRSCAN_RW_VAL) /* rewound? */ 1481 { 1482 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; 1483 break; 1484 } 1485 1486 /* validate last entry scanned; it might have been replaced between 1487 calls or not there at all any more; if so, start the cache reader 1488 at the beginning and fast-forward to the correct point as indicated 1489 by the FS scanner */ 1490 ce = get_entry(idx); 1491 if (ce && ce->serialnum == scanp->dcscan.serialnum) 1492 { 1493 idx = ce->next; 1494 break; 1495 } 1496 1497 idx = 0; 1498 } 1499 1500 while (1) 1501 { 1502 ce = get_entry(idx); 1503 if (!ce) 1504 { 1505 empty_dirent(entry); 1506 scanp->fatscan.entries = 0; 1507 return 0; /* end of dir */ 1508 } 1509 1510 if (ce->direntry > direntry || direntry == FAT_DIRSCAN_RW_VAL) 1511 break; /* cache reader is caught up to FS scan */ 1512 1513 idx = ce->next; 1514 } 1515 1516 /* basic dirent information */ 1517 entry_name_copy(entry->d_name, ce); 1518 entry->info.attr = ce->attr; 1519 entry->info.size = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize; 1520 entry->info.wrtdate = ce->wrtdate; 1521 entry->info.wrttime = ce->wrttime; 1522 1523 /* FS scan information */ 1524 scanp->fatscan.entry = ce->direntry; 1525 scanp->fatscan.entries = ce->direntries; 1526 1527 /* dircache scan information */ 1528 scanp->dcscan.idx = idx; 1529 scanp->dcscan.serialnum = ce->serialnum; 1530 1531 /* return whether this needs decoding */ 1532 int rc = ce->direntries == 1 ? 2 : 1; 1533 1534 yield(); 1535 return rc; 1536 1537read_uncached: 1538 dircache_dcfile_init(&scanp->dcscan); 1539 return uncached_readdir_dirent(stream, scanp, entry); 1540} 1541 1542/** 1543 * rewind the directory scan cursor 1544 */ 1545void dircache_rewinddir_dirent(struct dirscan_info *scanp) 1546{ 1547 uncached_rewinddir_dirent(scanp); 1548 dircache_dcfile_init(&scanp->dcscan); 1549} 1550 1551/** 1552 * this function is the back end to file API internal scanning, which requires 1553 * much more detail about the directory entries; this is allowed to make 1554 * assumptions about cache state because the cache will not be altered during 1555 * the scan process; an additional important property of internal scanning is 1556 * that any available binding information is not ignored even when a scan 1557 * directory is frontier zoned. 1558 */ 1559int dircache_readdir_internal(struct filestr_base *stream, 1560 struct file_base_info *infop, 1561 struct fat_direntry *fatent) 1562{ 1563 /* call with writer exclusion */ 1564 struct file_base_info *dirinfop = stream->infop; 1565 struct dircache_volume *dcvolp = DCVOL(dirinfop); 1566 1567 /* assume binding "not found" */ 1568 infop->dcfile.serialnum = 0; 1569 1570 /* is parent cached? if not, readthrough because nothing is here yet */ 1571 if (!dirinfop->dcfile.serialnum) 1572 { 1573 if (stream->flags & FF_CACHEONLY) 1574 goto read_eod; 1575 1576 return uncached_readdir_internal(stream, infop, fatent); 1577 } 1578 1579 int diridx = dirinfop->dcfile.idx; 1580 unsigned int frontier = diridx < 0 ? 1581 dcvolp->frontier : get_entry(diridx)->frontier; 1582 1583 int idx = infop->dcfile.idx; 1584 if (idx == 0) /* rewound? */ 1585 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; 1586 else 1587 idx = get_entry(idx)->next; 1588 1589 struct dircache_entry *ce = get_entry(idx); 1590 1591 if (frontier != FRONTIER_SETTLED && !(stream->flags & FF_CACHEONLY)) 1592 { 1593 /* the directory being read is reported to be incompletely cached; 1594 readthrough and if the entry exists, return it with its binding 1595 information; otherwise return the uncached read result while 1596 maintaining the last index */ 1597 int rc = uncached_readdir_internal(stream, infop, fatent); 1598 if (rc <= 0 || !ce || ce->direntry > infop->fatfile.e.entry) 1599 return rc; 1600 1601 /* entry matches next one to read */ 1602 } 1603 else if (!ce) 1604 { 1605 /* end of dir */ 1606 goto read_eod; 1607 } 1608 1609 /* FS entry information that we maintain */ 1610 entry_name_copy(fatent->name, ce); 1611 fatent->shortname[0] = '\0'; 1612 fatent->attr = ce->attr; 1613 /* file code file scanning does not need time information */ 1614 fatent->filesize = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize; 1615 fatent->firstcluster = ce->firstcluster; 1616 1617 /* FS entry directory information */ 1618 infop->fatfile.e.entry = ce->direntry; 1619 infop->fatfile.e.entries = ce->direntries; 1620 1621 /* dircache file binding information */ 1622 infop->dcfile.idx = idx; 1623 infop->dcfile.serialnum = ce->serialnum; 1624 1625 /* return whether this needs decoding */ 1626 int rc = ce->direntries == 1 ? 2 : 1; 1627 1628 if (frontier == FRONTIER_SETTLED) 1629 { 1630 static long next_yield; 1631 if (TIME_AFTER(current_tick, next_yield)) 1632 { 1633 yield(); 1634 next_yield = current_tick + HZ/50; 1635 } 1636 } 1637 1638 return rc; 1639 1640read_eod: 1641 fat_empty_fat_direntry(fatent); 1642 infop->fatfile.e.entries = 0; 1643 return 0; 1644} 1645 1646/** 1647 * rewind the scan position for an internal scan 1648 */ 1649void dircache_rewinddir_internal(struct file_base_info *infop) 1650{ 1651 uncached_rewinddir_internal(infop); 1652 dircache_dcfile_init(&infop->dcfile); 1653} 1654 1655#else /* !DIRCACHE_NATIVE (for all others) */ 1656 1657##################### 1658/* we require access to the host functions */ 1659#undef opendir 1660#undef readdir 1661#undef closedir 1662#undef rewinddir 1663 1664static char sab_path[MAX_PATH]; 1665 1666static int sab_process_dir(struct dircache_entry *ce) 1667{ 1668 struct dirent_uncached *entry; 1669 struct dircache_entry *first_ce = ce; 1670 DIR *dir = opendir(sab_path); 1671 if(dir == NULL) 1672 { 1673 logf("Failed to opendir_uncached(%s)", sab_path); 1674 return -1; 1675 } 1676 1677 while (1) 1678 { 1679 if (!(entry = readdir(dir))) 1680 break; 1681 1682 if (IS_DOTDIR_NAME(entry->d_name)) 1683 { 1684 /* add "." and ".." */ 1685 ce->info.attribute = ATTR_DIRECTORY; 1686 ce->info.size = 0; 1687 ce->down = entry->d_name[1] == '\0' ? first_ce : first_ce->up; 1688 strcpy(ce->dot_d_name, entry->d_name); 1689 continue; 1690 } 1691 1692 size_t size = strlen(entry->d_name) + 1; 1693 ce->d_name = (d_names_start -= size); 1694 ce->info = entry->info; 1695 1696 strcpy(ce->d_name, entry->d_name); 1697 dircache_size += size; 1698 1699 if(entry->info.attribute & ATTR_DIRECTORY) 1700 { 1701 dircache_gen_down(ce, ce); 1702 if(ce->down == NULL) 1703 { 1704 closedir_uncached(dir); 1705 return -1; 1706 } 1707 /* save current paths size */ 1708 int pathpos = strlen(sab_path); 1709 /* append entry */ 1710 strmemccpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos); 1711 strmemccpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1); 1712 1713 int rc = sab_process_dir(ce->down); 1714 /* restore path */ 1715 sab_path[pathpos] = '\0'; 1716 1717 if(rc < 0) 1718 { 1719 closedir_uncached(dir); 1720 return rc; 1721 } 1722 } 1723 1724 ce = dircache_gen_entry(ce); 1725 if (ce == NULL) 1726 return -5; 1727 1728 yield(); 1729 } 1730 1731 closedir_uncached(dir); 1732 return 1; 1733} 1734 1735static int sab_process_volume(IF_MV(int volume,) struct dircache_entry *ce) 1736{ 1737 memset(ce, 0, sizeof(struct dircache_entry)); 1738 strmemccpy(sab_path, "/", sizeof sab_path); 1739 return sab_process_dir(ce); 1740} 1741 1742int dircache_readdir_r(struct dircache_dirscan *dir, struct DIRENT *result) 1743{ 1744 if (dircache_state != DIRCACHE_READY) 1745 return readdir_r(dir->###########3, result, &result); 1746 1747 bool first = dir->dcinfo.scanidx == REWIND_INDEX; 1748 struct dircache_entry *ce = get_entry(first ? dir->dcinfo.index : 1749 dir->dcinfo.scanidx); 1750 1751 ce = first ? ce->down : ce->next; 1752 1753 if (ce == NULL) 1754 return 0; 1755 1756 dir->scanidx = ce - dircache_root; 1757 1758 strmemccpy(result->d_name, ce->d_name, sizeof (result->d_name)); 1759 result->info = ce->dirinfo; 1760 1761 return 1; 1762} 1763 1764#endif /* DIRCACHE_* */ 1765 1766/** 1767 * reset the cache for the specified volume 1768 */ 1769static void reset_volume(IF_MV_NONVOID(int volume)) 1770{ 1771 FOR_EACH_VOLUME(volume, i) 1772 { 1773 struct dircache_volume *dcvolp = DCVOL(i); 1774 1775 if (dcvolp->status == DIRCACHE_IDLE) 1776 continue; /* idle => nothing happening there */ 1777 1778 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(i); 1779 1780 /* stop any scan and build on this one */ 1781 if (dcrivolp->sabp) 1782 dcrivolp->sabp->quit = true; 1783 1784 #ifdef HAVE_MULTIVOLUME 1785 /* if this call is for all volumes, subsequent code will just reset 1786 the cache memory usage and the freeing of individual entries may 1787 be skipped */ 1788 if (volume >= 0) 1789 free_subentries(NULL, &dcvolp->root_down); 1790 else 1791 #endif 1792 binding_dissolve_volume(dcrivolp); 1793 1794 /* set it back to unscanned */ 1795 dcvolp->status = DIRCACHE_IDLE; 1796 dcvolp->frontier = FRONTIER_NEW; 1797 dcvolp->root_down = 0; 1798 dcvolp->build_ticks = 0; 1799 dcvolp->serialnum = 0; 1800 } 1801} 1802 1803/** 1804 * reset the entire cache state for all volumes 1805 */ 1806static void reset_cache(void) 1807{ 1808 if (!dircache_runinfo.handle) 1809 return; /* no buffer => nothing cached */ 1810 1811 /* blast all the volumes */ 1812 reset_volume(IF_MV(-1)); 1813 1814#ifdef DIRCACHE_DUMPSTER 1815 dumpster_clean_buffer(dircache_runinfo.p + ENTRYSIZE, 1816 dircache_runinfo.bufsize); 1817#endif /* DIRCACHE_DUMPSTER */ 1818 1819 /* reset the memory */ 1820 dircache.free_list = 0; 1821 dircache.size = 0; 1822 dircache.sizeused = 0; 1823 dircache.numentries = 0; 1824 dircache.names = dircache_runinfo.bufsize + ENTRYSIZE; 1825 dircache.sizenames = 0; 1826 dircache.namesfree = 0; 1827 dircache.nextnamefree = 0; 1828 *get_name(dircache.names - 1) = 0; 1829 /* dircache.last_serialnum stays */ 1830 /* dircache.reserve_used stays */ 1831 /* dircache.last_size stays */ 1832} 1833 1834/** 1835 * checks each "idle" volume and builds it 1836 */ 1837static void build_volumes(void) 1838{ 1839 core_pin(dircache_runinfo.handle); 1840 1841 for (int i = 0; i < NUM_VOLUMES; i++) 1842 { 1843 /* this does reader locking but we already own that */ 1844 if (!volume_ismounted(IF_MV(i))) 1845 continue; 1846 1847 struct dircache_volume *dcvolp = DCVOL(i); 1848 1849 /* can't already be "scanning" because that's us; doesn't retry 1850 "ready" volumes */ 1851 if (dcvolp->status == DIRCACHE_READY) 1852 continue; 1853 1854 /* measure how long it takes to build the cache for each volume */ 1855 if (!dcvolp->serialnum) 1856 dcvolp->serialnum = next_serialnum(); 1857 1858 dcvolp->status = DIRCACHE_SCANNING; 1859 dcvolp->start_tick = current_tick; 1860 1861 sab_process_volume(dcvolp); 1862 1863 if (dircache_runinfo.suspended) 1864 break; 1865 1866 /* whatever happened, it's ready unless reset */ 1867 dcvolp->build_ticks = current_tick - dcvolp->start_tick; 1868 dcvolp->status = DIRCACHE_READY; 1869 } 1870 1871 size_t reserve_used = reserve_buf_used(); 1872 if (reserve_used > dircache.reserve_used) 1873 dircache.reserve_used = reserve_used; 1874 1875 if (DIRCACHE_STUFFED(reserve_used)) 1876 dircache.last_size = 0; /* reset */ 1877 else if (dircache.size > dircache.last_size) 1878 dircache.last_size = dircache.size; /* grow */ 1879 else if (!dircache_runinfo.suspended && 1880 dircache.last_size - dircache.size > DIRCACHE_RESERVE) 1881 dircache.last_size = dircache.size; /* shrink if not suspended */ 1882 1883 logf("Done, %ld KiB used", dircache.size / 1024); 1884 1885 if (dircache_runinfo.handle > 0) /* dircache may have been disabled */ 1886 core_unpin(dircache_runinfo.handle); 1887} 1888 1889/** 1890 * allocate buffer and return whether or not a synchronous build should take 1891 * place; if 'realloced' is NULL, it's just a query about what will happen 1892 * 1893 * Note this must be called with the dircache_lock() active. 1894 */ 1895static int prepare_build(bool *realloced) 1896{ 1897 /* called holding dircache lock */ 1898 size_t size = dircache.last_size; 1899 1900#ifdef HAVE_EEPROM_SETTINGS 1901 if (realloced) 1902 { 1903 dircache_unlock(); 1904 remove_dircache_file(); 1905 dircache_lock(); 1906 1907 if (dircache_runinfo.suspended) 1908 return -1; 1909 } 1910#endif /* HAVE_EEPROM_SETTINGS */ 1911 1912 bool stuffed = DIRCACHE_STUFFED(dircache.reserve_used); 1913 if (dircache_runinfo.bufsize > size && !stuffed) 1914 { 1915 if (realloced) 1916 *realloced = false; 1917 1918 return 0; /* start a transparent rebuild */ 1919 } 1920 1921 int syncbuild = size > 0 && !stuffed ? 0 : 1; 1922 1923 if (!realloced) 1924 return syncbuild; 1925 1926 if (syncbuild) 1927 { 1928 /* start a non-transparent rebuild */ 1929 /* we'll use the entire audiobuf to allocate the dircache */ 1930 size = audio_buffer_available() + dircache_runinfo.bufsize; 1931 /* try to allocate at least the min and no more than the limit */ 1932 size = MAX(DIRCACHE_MIN, MIN(size, DIRCACHE_LIMIT)); 1933 } 1934 else 1935 { 1936 /* start a transparent rebuild */ 1937 size = MAX(size, DIRCACHE_RESERVE) + DIRCACHE_RESERVE*2; 1938 } 1939 1940 *realloced = true; 1941 reset_cache(); 1942 1943 int handle = reset_buffer(); 1944 dircache_unlock(); /* release lock held by caller */ 1945 1946 core_free(handle); 1947 1948 handle = alloc_cache(size); 1949 1950 dircache_lock(); /* reacquire lock */ 1951 1952 if (dircache_runinfo.suspended && handle > 0) 1953 { 1954 /* if we got suspended, don't keep this huge buffer around */ 1955 dircache_unlock(); 1956 core_free(handle); 1957 handle = 0; 1958 dircache_lock(); 1959 } 1960 1961 if (handle <= 0) 1962 return -1; 1963 1964 set_buffer(handle, size); 1965 1966 return syncbuild; 1967} 1968 1969/** 1970 * compact the dircache buffer after a successful full build 1971 */ 1972static void compact_cache(void) 1973{ 1974 /* called holding dircache lock */ 1975 if (dircache_runinfo.suspended) 1976 return; 1977 1978 void *p = dircache_runinfo.p + ENTRYSIZE; 1979 size_t leadsize = dircache.numentries * ENTRYSIZE + DIRCACHE_RESERVE; 1980 1981 void *dst = p + leadsize; 1982 void *src = get_name(dircache.names - 1); 1983 if (dst >= src) 1984 return; /* cache got bigger than expected; never mind that */ 1985 1986 /* slide the names up in memory */ 1987 memmove(dst, src, dircache.sizenames + 2); 1988 1989 /* fix up name indexes */ 1990 ptrdiff_t offset = dst - src; 1991 1992 FOR_EACH_CACHE_ENTRY(ce) 1993 { 1994 if (!ce->tinyname) 1995 ce->name += offset; 1996 } 1997 1998 dircache.names += offset; 1999 2000 /* assumes beelzelib doesn't do things like calling callbacks or changing 2001 the pointer as a result of the shrink operation; it doesn't as of now 2002 but if it ever does that may very well cause deadlock problems since 2003 we're holding filesystem locks */ 2004 size_t newsize = leadsize + dircache.sizenames + 1; 2005 core_shrink(dircache_runinfo.handle, p, newsize + 1); 2006 dircache_runinfo.bufsize = newsize; 2007 dircache.reserve_used = 0; 2008} 2009 2010/** 2011 * internal thread that controls cache building; exits when no more requests 2012 * are pending or the cache is suspended 2013 */ 2014static void dircache_thread(void) 2015{ 2016 struct queue_event ev; 2017 2018 /* calls made within the loop reopen the lock */ 2019 dircache_lock(); 2020 2021 while (1) 2022 { 2023 queue_wait_w_tmo(&dircache_queue, &ev, 0); 2024 if (ev.id == SYS_TIMEOUT || dircache_runinfo.suspended) 2025 { 2026 /* nothing left to do/suspended */ 2027 if (dircache_runinfo.suspended) 2028 clear_dircache_queue(); 2029 dircache_runinfo.thread_done = true; 2030 break; 2031 } 2032 2033 /* background-only builds are not allowed if a synchronous build is 2034 required first; test what needs to be done and if it checks out, 2035 do it for real below */ 2036 int *rcp = (int *)ev.data; 2037 2038 if (!rcp && prepare_build(NULL) > 0) 2039 continue; 2040 2041 bool realloced; 2042 int rc = prepare_build(&realloced); 2043 if (rcp) 2044 *rcp = rc; 2045 2046 if (rc < 0) 2047 continue; 2048 2049 trigger_cpu_boost(); 2050 build_volumes(); 2051 2052 /* if it was reallocated, compact it */ 2053 if (realloced) 2054 compact_cache(); 2055 } 2056 2057 dircache_unlock(); 2058} 2059 2060/** 2061 * post a scan and build message to the thread, starting it if required 2062 */ 2063static bool dircache_thread_post(int volatile *rcp) 2064{ 2065 if (dircache_runinfo.thread_done) 2066 { 2067 /* mustn't recreate until it exits so that the stack isn't reused */ 2068 thread_wait(dircache_runinfo.thread_id); 2069 dircache_runinfo.thread_done = false; 2070 dircache_runinfo.thread_id = create_thread( 2071 dircache_thread, dircache_stack, sizeof (dircache_stack), 0, 2072 dircache_thread_name IF_PRIO(, PRIORITY_BACKGROUND) 2073 IF_COP(, CPU)); 2074 } 2075 2076 bool started = dircache_runinfo.thread_id != 0; 2077 2078 if (started) 2079 queue_post(&dircache_queue, DCM_BUILD, (intptr_t)rcp); 2080 2081 return started; 2082} 2083 2084/** 2085 * wait for the dircache thread to finish building; intended for waiting for a 2086 * non-transparent build to finish when dircache_resume() returns > 0 2087 */ 2088void dircache_wait(void) 2089{ 2090 thread_wait(dircache_runinfo.thread_id); 2091} 2092 2093/** 2094 * call after mounting a volume or all volumes 2095 */ 2096void dircache_mount(void) 2097{ 2098 /* call with writer exclusion */ 2099 if (dircache_runinfo.suspended) 2100 return; 2101 2102 dircache_thread_post(NULL); 2103} 2104 2105/** 2106 * call after unmounting a volume; specifying < 0 for all or >= 0 for the 2107 * specific one 2108 */ 2109void dircache_unmount(IF_MV_NONVOID(int volume)) 2110{ 2111 /* call with writer exclusion */ 2112 if (dircache_runinfo.suspended) 2113 return; 2114 2115#ifdef HAVE_MULTIVOLUME 2116 if (volume >= 0) 2117 reset_volume(volume); 2118 else 2119#endif /* HAVE_MULTIVOLUME */ 2120 reset_cache(); 2121} 2122 2123/* backend to dircache_suspend() and dircache_disable() */ 2124static void dircache_suspend_internal(bool freeit) 2125{ 2126 if (dircache_runinfo.suspended++ > 0 && 2127 (!freeit || dircache_runinfo.handle <= 0)) 2128 return; 2129 2130 unsigned int thread_id = dircache_runinfo.thread_id; 2131 2132 reset_cache(); 2133 clear_dircache_queue(); 2134 2135 /* grab the buffer away into our control; the cache won't need it now */ 2136 int handle = 0; 2137 if (freeit) 2138 handle = reset_buffer(); 2139 2140 dircache_unlock(); 2141 2142 core_free(handle); 2143 2144 thread_wait(thread_id); 2145 2146 dircache_lock(); 2147} 2148 2149/* backend to dircache_resume() and dircache_enable() */ 2150static int dircache_resume_internal(bool build_now) 2151{ 2152 int volatile rc = 0; 2153 2154 if (dircache_runinfo.suspended == 0 || --dircache_runinfo.suspended == 0) 2155 rc = build_now && dircache_runinfo.enabled ? 1 : 0; 2156 2157 if (rc) 2158 rc = dircache_thread_post(&rc) ? INT_MIN : -1; 2159 2160 if (rc == INT_MIN) 2161 { 2162 dircache_unlock(); 2163 2164 while (rc == INT_MIN) /* poll for response */ 2165 sleep(0); 2166 2167 dircache_lock(); 2168 } 2169 2170 return rc < 0 ? rc * 10 - 2 : rc; 2171} 2172 2173/** 2174 * service to dircache_enable() and dircache_load(); "build_now" starts a build 2175 * immediately if the cache was not enabled 2176 */ 2177static int dircache_enable_internal(bool build_now) 2178{ 2179 int rc = 0; 2180 2181 if (!dircache_runinfo.enabled) 2182 { 2183 dircache_runinfo.enabled = true; 2184 rc = dircache_resume_internal(build_now); 2185 } 2186 2187 return rc; 2188} 2189 2190/** 2191 * service to dircache_disable() 2192 */ 2193static void dircache_disable_internal(void) 2194{ 2195 if (dircache_runinfo.enabled) 2196 { 2197 dircache_runinfo.enabled = false; 2198 dircache_suspend_internal(true); 2199 } 2200} 2201 2202/** 2203 * disables dircache without freeing the buffer (so it can be re-enabled 2204 * afterwards with dircache_resume(); usually called when accepting an USB 2205 * connection 2206 */ 2207void dircache_suspend(void) 2208{ 2209 dircache_lock(); 2210 dircache_suspend_internal(false); 2211 dircache_unlock(); 2212} 2213 2214/** 2215 * re-enables the dircache if previously suspended by dircache_suspend 2216 * or dircache_steal_buffer(), re-using the already allocated buffer if 2217 * available 2218 * 2219 * returns: 0 if the background build is started or dircache is still 2220 * suspended 2221 * > 0 if the build is non-background 2222 * < 0 upon failure 2223 */ 2224int dircache_resume(void) 2225{ 2226 dircache_lock(); 2227 int rc = dircache_resume_internal(true); 2228 dircache_unlock(); 2229 return rc; 2230} 2231 2232/** 2233 * as dircache_resume() but globally enables it; called by settings and init 2234 */ 2235int dircache_enable(void) 2236{ 2237 dircache_lock(); 2238 int rc = dircache_enable_internal(true); 2239 dircache_unlock(); 2240 return rc; 2241} 2242 2243/** 2244 * as dircache_suspend() but also frees the buffer; usually called on shutdown 2245 * or when deactivated 2246 */ 2247void dircache_disable(void) 2248{ 2249 dircache_lock(); 2250 dircache_disable_internal(); 2251 dircache_unlock(); 2252} 2253 2254/** 2255 * have dircache give up its allocation; call dircache_resume() to restart it 2256 */ 2257void dircache_free_buffer(void) 2258{ 2259 dircache_lock(); 2260 dircache_suspend_internal(true); 2261 dircache_unlock(); 2262} 2263 2264 2265/** Dircache live updating **/ 2266 2267/** 2268 * obtain binding information for the file's root volume; this is the starting 2269 * point for internal path parsing and binding 2270 */ 2271void dircache_get_rootinfo(struct file_base_info *infop) 2272{ 2273 int volume = BASEINFO_VOL(infop); 2274 struct dircache_volume *dcvolp = DCVOL(volume); 2275 2276 if (dcvolp->serialnum) 2277 { 2278 /* root has a binding */ 2279 infop->dcfile.idx = -volume - 1; 2280 infop->dcfile.serialnum = dcvolp->serialnum; 2281 } 2282 else 2283 { 2284 /* root is idle */ 2285 dircache_dcfile_init(&infop->dcfile); 2286 } 2287} 2288 2289/** 2290 * called by file code when the first reference to a file or directory is 2291 * opened 2292 */ 2293void dircache_bind_file(struct file_base_binding *bindp) 2294{ 2295 /* requires write exclusion */ 2296 logf("dc open: %u", (unsigned int)bindp->info.dcfile.serialnum); 2297 binding_open(bindp); 2298} 2299 2300/** 2301 * called by file code when the last reference to a file or directory is 2302 * closed 2303 */ 2304void dircache_unbind_file(struct file_base_binding *bindp) 2305{ 2306 /* requires write exclusion */ 2307 logf("dc close: %u", (unsigned int)bindp->info.dcfile.serialnum); 2308 binding_close(bindp); 2309} 2310 2311/** 2312 * called by file code when a file is newly created 2313 */ 2314void dircache_fileop_create(struct file_base_info *dirinfop, 2315 struct file_base_binding *bindp, 2316 const char *basename, 2317 const struct dirinfo_native *dinp) 2318{ 2319 /* requires write exclusion */ 2320 logf("dc create: %u \"%s\"", 2321 (unsigned int)bindp->info.dcfile.serialnum, basename); 2322 2323 if (!dirinfop->dcfile.serialnum) 2324 { 2325 /* no parent binding => no child binding */ 2326 return; 2327 } 2328 2329 struct dircache_entry *ce; 2330 int idx = create_entry(basename, &ce); 2331 if (idx <= 0) 2332 { 2333 /* failed allocation; parent cache contents are not complete */ 2334 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); 2335 return; 2336 } 2337 2338 struct file_base_info *infop = &bindp->info; 2339 2340#ifdef DIRCACHE_NATIVE 2341 ce->firstcluster = infop->fatfile.firstcluster; 2342 ce->direntry = infop->fatfile.e.entry; 2343 ce->direntries = infop->fatfile.e.entries; 2344 ce->wrtdate = dinp->wrtdate; 2345 ce->wrttime = dinp->wrttime; 2346#else 2347 ce->mtime = dinp->mtime; 2348#endif 2349 ce->attr = dinp->attr; 2350 if (!(dinp->attr & ATTR_DIRECTORY)) 2351 ce->filesize = dinp->size; 2352 2353 insert_file_entry(dirinfop, ce); 2354 2355 /* file binding will have been queued when it was opened; just resolve */ 2356 infop->dcfile.idx = idx; 2357 infop->dcfile.serialnum = ce->serialnum; 2358 binding_resolve(infop); 2359 2360 if ((dinp->attr & ATTR_DIRECTORY) && !is_dotdir_name(basename)) 2361 { 2362 /* scan-in the contents of the new directory at this level only */ 2363 core_pin(dircache_runinfo.handle); 2364 sab_process_dir(infop, false); 2365 core_unpin(dircache_runinfo.handle); 2366 } 2367} 2368 2369/** 2370 * called by file code when a file or directory is removed 2371 */ 2372void dircache_fileop_remove(struct file_base_binding *bindp) 2373{ 2374 /* requires write exclusion */ 2375 logf("dc remove: %u\n", (unsigned int)bindp->info.dcfile.serialnum); 2376 2377 if (!bindp->info.dcfile.serialnum) 2378 return; /* no binding yet */ 2379 2380 free_file_entry(&bindp->info); 2381 2382 /* if binding was resolved; it should now be queued via above call */ 2383} 2384 2385/** 2386 * called by file code when a file is renamed 2387 */ 2388void dircache_fileop_rename(struct file_base_info *dirinfop, 2389 struct file_base_binding *bindp, 2390 const char *basename) 2391{ 2392 /* requires write exclusion */ 2393 logf("dc rename: %u \"%s\"", 2394 (unsigned int)bindp->info.dcfile.serialnum, basename); 2395 2396 if (!dirinfop->dcfile.serialnum) 2397 { 2398 /* new parent directory not cached; there is nowhere to put it so 2399 nuke it */ 2400 if (bindp->info.dcfile.serialnum) 2401 free_file_entry(&bindp->info); 2402 /* else no entry anyway */ 2403 2404 return; 2405 } 2406 2407 if (!bindp->info.dcfile.serialnum) 2408 { 2409 /* binding not resolved on the old file but it's going into a resolved 2410 parent which means the parent would be missing an entry in the cache; 2411 downgrade the parent */ 2412 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); 2413 return; 2414 } 2415 2416 /* unlink the entry but keep it; it needs to be re-sorted since the 2417 underlying FS probably changed the order */ 2418 struct dircache_entry *ce = remove_file_entry(&bindp->info); 2419 2420#ifdef DIRCACHE_NATIVE 2421 /* update other name-related information before inserting */ 2422 ce->direntry = bindp->info.fatfile.e.entry; 2423 ce->direntries = bindp->info.fatfile.e.entries; 2424#endif 2425 2426 /* place it into its new home */ 2427 insert_file_entry(dirinfop, ce); 2428 2429 /* lastly, update the entry name itself */ 2430 if (entry_reassign_name(ce, basename) == 0) 2431 { 2432 /* it's not really the same one now so re-stamp it */ 2433 dc_serial_t serialnum = next_serialnum(); 2434 ce->serialnum = serialnum; 2435 bindp->info.dcfile.serialnum = serialnum; 2436 } 2437 else 2438 { 2439 /* it cannot be kept around without a valid name */ 2440 free_file_entry(&bindp->info); 2441 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); 2442 } 2443} 2444 2445/** 2446 * called by file code to synchronize file entry information 2447 */ 2448void dircache_fileop_sync(struct file_base_binding *bindp, 2449 const struct dirinfo_native *dinp) 2450{ 2451 /* requires write exclusion */ 2452 struct file_base_info *infop = &bindp->info; 2453 logf("dc sync: %u\n", (unsigned int)infop->dcfile.serialnum); 2454 2455 if (!infop->dcfile.serialnum) 2456 return; /* binding unresolved */ 2457 2458 struct dircache_entry *ce = get_entry(infop->dcfile.idx); 2459 if (!ce) 2460 { 2461 logf(" bad index %d", infop->dcfile.idx); 2462 return; /* a root (should never be called for this) */ 2463 } 2464 2465#ifdef DIRCACHE_NATIVE 2466 ce->firstcluster = infop->fatfile.firstcluster; 2467 ce->wrtdate = dinp->wrtdate; 2468 ce->wrttime = dinp->wrttime; 2469#else 2470 ce->mtime = dinp->mtime; 2471#endif 2472 ce->attr = dinp->attr; 2473 if (!(dinp->attr & ATTR_DIRECTORY)) 2474 ce->filesize = dinp->size; 2475} 2476 2477 2478/** Dircache paths and files **/ 2479 2480/** 2481 * helper for returning a path and serial hash represented by an index 2482 */ 2483struct get_path_sub_data 2484{ 2485 char *buf; 2486 size_t size; 2487 dc_serial_t serialhash; 2488}; 2489 2490static ssize_t get_path_sub(int idx, struct get_path_sub_data *data) 2491{ 2492 if (idx == 0) 2493 return -1; /* entry is an orphan split from any root */ 2494 2495 ssize_t len; 2496 char *cename; 2497 2498 if (idx > 0) 2499 { 2500 struct dircache_entry *ce = get_entry(idx); 2501 2502 data->serialhash = dc_hash_serialnum(ce->serialnum, data->serialhash); 2503 2504 /* go all the way up then move back down from the root */ 2505 len = get_path_sub(ce->up, data) - 1; 2506 if (len < 0) 2507 return -2; 2508 2509 cename = alloca(DC_MAX_NAME + 1); 2510 entry_name_copy(cename, ce); 2511 } 2512 else /* idx < 0 */ 2513 { 2514 len = 0; 2515 cename = ""; 2516 2517 #ifdef HAVE_MULTIVOLUME 2518 /* prepend the volume specifier */ 2519 int volume = IF_MV_VOL(-idx - 1); 2520 cename = alloca(VOL_MAX_LEN+1); 2521 get_volume_name(volume, cename); 2522 #endif /* HAVE_MULTIVOLUME */ 2523 2524 data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, 2525 data->serialhash); 2526 } 2527 2528 return len + path_append(data->buf + len, PA_SEP_HARD, cename, 2529 data->size > (size_t)len ? data->size - len : 0); 2530} 2531 2532/** 2533 * validate the file's entry/binding serial number 2534 * the dircache file's serial number must match the indexed entry's or the 2535 * file reference is stale 2536 */ 2537static int check_file_serialnum(const struct dircache_file *dcfilep) 2538{ 2539 int idx = dcfilep->idx; 2540 2541 if (idx == 0 || idx < -NUM_VOLUMES) 2542 return -EBADF; 2543 2544 dc_serial_t serialnum = dcfilep->serialnum; 2545 2546 if (serialnum == 0) 2547 return -EBADF; 2548 2549 dc_serial_t s; 2550 2551 if (idx > 0) 2552 { 2553 struct dircache_entry *ce = get_entry(idx); 2554 if (!ce || !(s = ce->serialnum)) 2555 return -EBADF; 2556 } 2557 else /* idx < 0 */ 2558 { 2559 struct dircache_volume *dcvolp = get_idx_dcvolp(idx); 2560 if (!(s = dcvolp->serialnum)) 2561 return -EBADF; 2562 } 2563 2564 if (serialnum != s) 2565 return -EBADF; 2566 2567 return 0; 2568} 2569 2570/** 2571 * Obtain the hash of the serial numbers of the canonical path, index to root 2572 */ 2573static dc_serial_t get_file_serialhash(const struct dircache_file *dcfilep) 2574{ 2575 int idx = dcfilep->idx; 2576 2577 dc_serial_t h = DC_SERHASH_START; 2578 2579 while (idx > 0) 2580 { 2581 struct dircache_entry *ce = get_entry(idx); 2582 h = dc_hash_serialnum(ce->serialnum, h); 2583 idx = ce->up; 2584 } 2585 2586 if (idx < 0) 2587 h = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, h); 2588 2589 return h; 2590} 2591 2592/** 2593 * Initialize the fileref 2594 */ 2595void dircache_fileref_init(struct dircache_fileref *dcfrefp) 2596{ 2597 dircache_dcfile_init(&dcfrefp->dcfile); 2598 dcfrefp->serialhash = DC_SERHASH_START; 2599} 2600 2601/** 2602 * usermode function to construct a full absolute path from dircache into the 2603 * given buffer given the dircache file info 2604 * 2605 * returns: 2606 * success - the length of the string, not including the trailing null or the 2607 * buffer length required if the buffer is too small (return is >= 2608 * size) 2609 * failure - a negative value 2610 * 2611 * errors: 2612 * EBADF - Bad file number 2613 * EFAULT - Bad address 2614 * ENOENT - No such file or directory 2615 */ 2616ssize_t dircache_get_fileref_path(const struct dircache_fileref *dcfrefp, char *buf, 2617 size_t size) 2618{ 2619 ssize_t rc; 2620 2621 if (!dcfrefp) 2622 FILE_ERROR_RETURN(EFAULT, -1); 2623 2624 /* if missing buffer space, still return what's needed a la strlcpy */ 2625 if (!buf) 2626 size = 0; 2627 else if (size) 2628 *buf = '\0'; 2629 2630 dircache_lock(); 2631 2632 /* first and foremost, there must be a cache and the serial number must 2633 check out */ 2634 if (!dircache_runinfo.handle) 2635 FILE_ERROR(EBADF, -2); 2636 2637 rc = check_file_serialnum(&dcfrefp->dcfile); 2638 if (rc < 0) 2639 FILE_ERROR(-rc, -3); 2640 2641 struct get_path_sub_data data = 2642 { 2643 .buf = buf, 2644 .size = size, 2645 .serialhash = DC_SERHASH_START, 2646 }; 2647 2648 rc = get_path_sub(dcfrefp->dcfile.idx, &data); 2649 if (rc < 0) 2650 FILE_ERROR(ENOENT, rc * 10 - 4); 2651 2652 if (data.serialhash != dcfrefp->serialhash) 2653 FILE_ERROR(ENOENT, -5); 2654 2655file_error: 2656 dircache_unlock(); 2657 return rc; 2658} 2659 2660/** 2661 * Test a path to various levels of rigor and optionally return dircache file 2662 * info for the given path. 2663 * 2664 * If the file reference is used, it is checked first and the path is checked 2665 * only if all specified file reference checks fail. 2666 * 2667 * returns: 2668 * success: 0 = not cached (very weak) 2669 * 1 = serial number checks out for the reference (weak) 2670 * 2 = serial number and hash check out for the reference (medium) 2671 * 3 = path is valid; reference updated if specified (strong) 2672 * failure: a negative value 2673 * if file definitely doesn't exist (errno = ENOENT) 2674 * other error 2675 * 2676 * errors (including but not limited to): 2677 * EFAULT - Bad address 2678 * EINVAL - Invalid argument 2679 * ENAMETOOLONG - File or path name too long 2680 * ENOENT - No such file or directory 2681 * ENOTDIR - Not a directory 2682 */ 2683int dircache_search(unsigned int flags, struct dircache_fileref *dcfrefp, 2684 const char *path) 2685{ 2686 if (!(flags & (DCS_FILEREF | DCS_CACHED_PATH))) 2687 FILE_ERROR_RETURN(EINVAL, -1); /* search nothing? */ 2688 2689 if (!dcfrefp && (flags & (DCS_FILEREF | DCS_UPDATE_FILEREF))) 2690 FILE_ERROR_RETURN(EFAULT, -2); /* bad! */ 2691 2692 int rc = 0; 2693 2694 dircache_lock(); 2695 2696 /* -- File reference search -- */ 2697 if (!dircache_runinfo.handle) 2698 ; /* cache not enabled; not cached */ 2699 else if (!(flags & DCS_FILEREF)) 2700 ; /* don't use fileref */ 2701 else if (check_file_serialnum(&dcfrefp->dcfile) < 0) 2702 ; /* serial number bad */ 2703 else if (!(flags & _DCS_VERIFY_FLAG)) 2704 rc = 1; /* only check idx and serialnum */ 2705 else if (get_file_serialhash(&dcfrefp->dcfile) == dcfrefp->serialhash) 2706 rc = 2; /* reference is most likely still valid */ 2707 2708 /* -- Path cache and storage search -- */ 2709 if (rc > 0) 2710 ; /* rc > 0 */ /* found by file reference */ 2711 else if (!(flags & DCS_CACHED_PATH)) 2712 ; /* rc = 0 */ /* reference bad/unused and no path */ 2713 else 2714 { /* rc = 0 */ /* check path with cache and/or storage */ 2715 struct path_component_info compinfo; 2716 struct filestr_base stream; 2717 unsigned int ffcache = (flags & _DCS_STORAGE_FLAG) ? 0 : FF_CACHEONLY; 2718 int err = errno; 2719 int rc2 = open_stream_internal(path, ffcache | FF_ANYTYPE | FF_PROBE | 2720 FF_INFO | FF_PARENTINFO, &stream, 2721 &compinfo); 2722 if (rc2 <= 0) 2723 { 2724 if (ffcache == 0) 2725 { 2726 /* checked storage too: absent for sure */ 2727 FILE_ERROR(rc2 ? ERRNO : ENOENT, rc2 * 10 - 5); 2728 } 2729 2730 if (rc2 < 0) 2731 { 2732 /* no base info available */ 2733 if (errno != ENOENT) 2734 FILE_ERROR(ERRNO, rc2 * 10 - 6); 2735 2736 /* only cache; something didn't exist: indecisive */ 2737 errno = err; 2738 FILE_ERROR(ERRNO, RC); /* rc = 0 */ 2739 } 2740 2741 struct dircache_file *dcfp = &compinfo.parentinfo.dcfile; 2742 if (get_frontier(dcfp->idx) == FRONTIER_SETTLED) 2743 FILE_ERROR(ENOENT, -7); /* parent not a frontier; absent */ 2744 /* else checked only cache; parent is incomplete: indecisive */ 2745 } 2746 else 2747 { 2748 struct dircache_file *dcfp = &compinfo.info.dcfile; 2749 if (dcfp->serialnum != 0) 2750 { 2751 /* found by path in the cache afterall */ 2752 if (flags & DCS_UPDATE_FILEREF) 2753 { 2754 dcfrefp->dcfile = *dcfp; 2755 dcfrefp->serialhash = get_file_serialhash(dcfp); 2756 } 2757 2758 rc = 3; 2759 } 2760 } 2761 } 2762 2763file_error: 2764 if (rc <= 0 && (flags & DCS_UPDATE_FILEREF)) 2765 dircache_fileref_init(dcfrefp); 2766 2767 dircache_unlock(); 2768 return rc; 2769} 2770 2771/** 2772 * Compare dircache file references (no validity check is made) 2773 * 2774 * returns: 0 - no match 2775 * 1 - indexes match 2776 * 2 - serial numbers match 2777 * 3 - serial and hashes match 2778 */ 2779int dircache_fileref_cmp(const struct dircache_fileref *dcfrefp1, 2780 const struct dircache_fileref *dcfrefp2) 2781{ 2782 int cmp = 0; 2783 2784 if (dcfrefp1->dcfile.idx == dcfrefp2->dcfile.idx) 2785 { 2786 cmp++; 2787 if (dcfrefp1->dcfile.serialnum == dcfrefp2->dcfile.serialnum) 2788 { 2789 cmp++; 2790 if (dcfrefp1->serialhash == dcfrefp2->serialhash) 2791 cmp++; 2792 } 2793 } 2794 2795 return cmp; 2796} 2797 2798/** Debug screen/info stuff **/ 2799 2800/** 2801 * return cache state parameters 2802 */ 2803void dircache_get_info(struct dircache_info *info) 2804{ 2805 static const char * const status_descriptions[] = 2806 { 2807 [DIRCACHE_IDLE] = "Idle", 2808 [DIRCACHE_SCANNING] = "Scanning", 2809 [DIRCACHE_READY] = "Ready", 2810 }; 2811 2812 if (!info) 2813 return; 2814 2815 dircache_lock(); 2816 2817 enum dircache_status status = DIRCACHE_IDLE; 2818 info->build_ticks = 0; 2819 2820 FOR_EACH_VOLUME(-1, volume) 2821 { 2822 struct dircache_volume *dcvolp = DCVOL(volume); 2823 enum dircache_status volstatus = dcvolp->status; 2824 2825 switch (volstatus) 2826 { 2827 case DIRCACHE_SCANNING: 2828 /* if any one is scanning then overall status is "scanning" */ 2829 status = volstatus; 2830 2831 /* sum the time the scanning has taken so far */ 2832 info->build_ticks += current_tick - dcvolp->start_tick; 2833 break; 2834 case DIRCACHE_READY: 2835 /* if all the rest are idle and at least one is ready, then 2836 status is "ready". */ 2837 if (status == DIRCACHE_IDLE) 2838 status = DIRCACHE_READY; 2839 2840 /* sum the build ticks of all "ready" volumes */ 2841 info->build_ticks += dcvolp->build_ticks; 2842 break; 2843 case DIRCACHE_IDLE: 2844 /* if all are idle; then the whole cache is "idle" */ 2845 break; 2846 } 2847 } 2848 2849 info->status = status; 2850 info->statusdesc = status_descriptions[status]; 2851 info->last_size = dircache.last_size; 2852 info->size_limit = DIRCACHE_LIMIT; 2853 info->reserve = DIRCACHE_RESERVE; 2854 2855 /* report usage only if there is something ready or being built */ 2856 if (status != DIRCACHE_IDLE) 2857 { 2858 info->size = dircache.size; 2859 info->sizeused = dircache.sizeused; 2860 info->reserve_used = reserve_buf_used(); 2861 info->entry_count = dircache.numentries; 2862 } 2863 else 2864 { 2865 info->size = 0; 2866 info->sizeused = 0; 2867 info->reserve_used = 0; 2868 info->entry_count = 0; 2869 } 2870 2871 dircache_unlock(); 2872} 2873 2874#ifdef DIRCACHE_DUMPSTER 2875/** 2876 * dump RAW binary of buffer and CSV of all valid paths and volumes to disk 2877 */ 2878void dircache_dump(void) 2879{ 2880 /* open both now so they're in the cache */ 2881 int fdbin = open(DIRCACHE_DUMPSTER_BIN, O_WRONLY|O_CREAT|O_TRUNC, 0666); 2882 int fdcsv = open(DIRCACHE_DUMPSTER_CSV, O_WRONLY|O_CREAT|O_TRUNC, 0666); 2883 if (fdbin < 0 || fdcsv < 0) 2884 { 2885 close(fdbin); 2886 return; 2887 } 2888 2889 trigger_cpu_boost(); 2890 dircache_lock(); 2891 2892 if (dircache_runinfo.handle) 2893 { 2894 core_pin(dircache_runinfo.handle); 2895 2896 /* bin */ 2897 write(fdbin, dircache_runinfo.p + ENTRYSIZE, 2898 dircache_runinfo.bufsize + 1); 2899 2900 /* CSV */ 2901 fdprintf(fdcsv, "\"Index\",\"Serialnum\",\"Serialhash\"," 2902 "\"Path\",\"Frontier\"," 2903 "\"Attribute\",\"File Size\"," 2904 "\"Mod Date\",\"Mod Time\"\n"); 2905 2906 FOR_EACH_VOLUME(-1, volume) 2907 { 2908 struct dircache_volume *dcvolp = DCVOL(volume); 2909 if (dcvolp->status == DIRCACHE_IDLE) 2910 continue; 2911 2912 #ifdef HAVE_MULTIVOLUME 2913 char name[VOL_MAX_LEN+1]; 2914 get_volume_name(volume, name); 2915 #endif 2916 fdprintf(fdcsv, 2917 "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT "," 2918 "\"%c" IF_MV("%s") "\",%u," 2919 "0x%08X,0," 2920 "\"\",\"\"\n", 2921 -volume-1, dcvolp->serialnum, 2922 dc_hash_serialnum(dcvolp->serialnum, DC_SERHASH_START), 2923 PATH_SEPCH, IF_MV(name,) dcvolp->frontier, 2924 ATTR_DIRECTORY | ATTR_VOLUME); 2925 } 2926 2927 FOR_EACH_CACHE_ENTRY(ce) 2928 { 2929 #ifdef DIRCACHE_NATIVE 2930 time_t mtime = dostime_mktime(ce->wrtdate, ce->wrttime); 2931 #else 2932 time_t mtime = ce->mtime; 2933 #endif 2934 struct tm tm; 2935 gmtime_r(&mtime, &tm); 2936 2937 char buf[DC_MAX_NAME + 2]; 2938 *buf = '\0'; 2939 int idx = get_index(ce); 2940 2941 struct get_path_sub_data data = 2942 { 2943 .buf = buf, 2944 .size = sizeof (buf), 2945 .serialhash = DC_SERHASH_START, 2946 }; 2947 2948 get_path_sub(idx, &data); 2949 2950 fdprintf(fdcsv, 2951 "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT "," 2952 "\"%s\",%u," 2953 "0x%08X,%lu," 2954 "%04d/%02d/%02d," 2955 "%02d:%02d:%02d\n", 2956 idx, ce->serialnum, data.serialhash, 2957 buf, ce->frontier, 2958 ce->attr, (ce->attr & ATTR_DIRECTORY) ? 2959 0ul : (unsigned long)ce->filesize, 2960 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 2961 tm.tm_hour, tm.tm_min, tm.tm_sec); 2962 } 2963 2964 core_unpin(dircache_runinfo.handle); 2965 } 2966 2967 dircache_unlock(); 2968 cancel_cpu_boost(); 2969 2970 close(fdbin); 2971 close(fdcsv); 2972} 2973#endif /* DIRCACHE_DUMPSTER */ 2974 2975 2976/** Misc. stuff **/ 2977 2978/** 2979 * set the dircache file to initial values 2980 */ 2981void dircache_dcfile_init(struct dircache_file *dcfilep) 2982{ 2983 dcfilep->idx = 0; 2984 dcfilep->serialnum = 0; 2985} 2986 2987#ifdef HAVE_EEPROM_SETTINGS 2988 2989#ifdef HAVE_HOTSWAP 2990/* NOTE: This is hazardous to the filesystem of any sort of removable 2991 storage unless it may be determined that the filesystem from save 2992 to load is identical. If it's not possible to do so in a timely 2993 manner, it's not worth persisting the cache. */ 2994 #warning "Don't do this; you'll find the consequences unpleasant." 2995#endif 2996 2997/* dircache persistence file header magic */ 2998#define DIRCACHE_MAGIC 0x00d0c0a1 2999 3000/* dircache persistence file header */ 3001struct dircache_maindata 3002{ 3003 uint32_t magic; /* DIRCACHE_MAGIC */ 3004 struct dircache dircache; /* metadata of the cache! */ 3005 uint32_t datacrc; /* CRC32 of data */ 3006 uint32_t hdrcrc; /* CRC32 of header through datacrc */ 3007} __attribute__((packed, aligned (4))); 3008 3009/** 3010 * verify that the clean status is A-ok 3011 */ 3012static bool dircache_is_clean(bool saving) 3013{ 3014 if (saving) 3015 return dircache.dcvol[0].status == DIRCACHE_READY; 3016 else 3017 { 3018 return dircache.dcvol[0].status == DIRCACHE_IDLE && 3019 !dircache_runinfo.enabled; 3020 } 3021} 3022 3023/** 3024 * function to load the internal cache structure from disk to initialize 3025 * the dircache really fast with little disk access. 3026 */ 3027int INIT_ATTR dircache_load(void) 3028{ 3029 logf("Loading directory cache"); 3030 int fd = open_dircache_file(O_RDONLY); 3031 if (fd < 0) 3032 return -1; 3033 3034 int rc = -1; 3035 3036 ssize_t size; 3037 struct dircache_maindata maindata; 3038 uint32_t crc; 3039 int handle = 0; 3040 bool hasbuffer = false; 3041 3042 size = sizeof (maindata); 3043 if (read(fd, &maindata, size) != size) 3044 { 3045 logf("dircache: header read failed"); 3046 goto error_nolock; 3047 } 3048 3049 /* sanity check the header */ 3050 if (maindata.magic != DIRCACHE_MAGIC) 3051 { 3052 logf("dircache: invalid header magic"); 3053 goto error_nolock; 3054 } 3055 3056 crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc), 3057 0xffffffff); 3058 if (crc != maindata.hdrcrc) 3059 { 3060 logf("dircache: invalid header CRC32"); 3061 goto error_nolock; 3062 } 3063 3064 if (maindata.dircache.size != 3065 maindata.dircache.sizeentries + maindata.dircache.sizenames || 3066 ALIGN_DOWN(maindata.dircache.size, ENTRYSIZE) != maindata.dircache.size || 3067 filesize(fd) - sizeof (maindata) != maindata.dircache.size) 3068 { 3069 logf("dircache: file header error"); 3070 goto error_nolock; 3071 } 3072 3073 /* allocate so that exactly the reserve size remains */ 3074 size_t bufsize = maindata.dircache.size + DIRCACHE_RESERVE + 1; 3075 handle = alloc_cache(bufsize); 3076 if (handle <= 0) 3077 { 3078 logf("dircache: failed load allocation"); 3079 goto error_nolock; 3080 } 3081 3082 dircache_lock(); 3083 3084 if (!dircache_is_clean(false)) 3085 goto error; 3086 3087 /* from this point on, we're actually dealing with the cache in RAM */ 3088 dircache = maindata.dircache; 3089 3090 set_buffer(handle, bufsize); 3091 core_pin(handle); 3092 hasbuffer = true; 3093 3094 /* convert back to in-RAM representation */ 3095 dircache.numentries = maindata.dircache.sizeentries / ENTRYSIZE; 3096 3097 /* read the dircache file into memory; start with the entries */ 3098 size = maindata.dircache.sizeentries; 3099 if (read(fd, dircache_runinfo.pentry + 1, size) != size) 3100 { 3101 logf("dircache read failed #1"); 3102 goto error; 3103 } 3104 3105 crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff); 3106 3107 /* continue with the names; fix up indexes to them if needed */ 3108 dircache.names -= maindata.dircache.sizenames; 3109 *get_name(dircache.names - 1) = 0; 3110 3111 size = maindata.dircache.sizenames; 3112 if (read(fd, get_name(dircache.names), size) != size) 3113 { 3114 logf("dircache read failed #2"); 3115 goto error; 3116 } 3117 3118 crc = crc_32(get_name(dircache.names), size, crc); 3119 if (crc != maindata.datacrc) 3120 { 3121 logf("dircache: data failed CRC32"); 3122 goto error; 3123 } 3124 3125 /* only names will be changed in relative position so fix up those 3126 references */ 3127 ssize_t offset = dircache.names - maindata.dircache.names; 3128 if (offset != 0) 3129 { 3130 /* nothing should be open besides the dircache file itself therefore 3131 no bindings need be resolved; the cache will have its own entry 3132 but that should get cleaned up when removing the file */ 3133 FOR_EACH_CACHE_ENTRY(ce) 3134 { 3135 if (!ce->tinyname) 3136 ce->name += offset; 3137 } 3138 } 3139 3140 dircache.reserve_used = 0; 3141 3142 /* enable the cache but do not try to build it */ 3143 dircache_enable_internal(false); 3144 3145 /* cache successfully loaded */ 3146 core_unpin(handle); 3147 logf("Done, %ld KiB used", dircache.size / 1024); 3148 rc = 0; 3149error: 3150 if (rc < 0 && hasbuffer) 3151 reset_buffer(); 3152 3153 dircache_unlock(); 3154 3155error_nolock: 3156 if (rc < 0) 3157 core_free(handle); 3158 3159 if (fd >= 0) 3160 close(fd); 3161 3162 remove_dircache_file(); 3163 return rc; 3164} 3165 3166/** 3167 * function to save the internal cache stucture to disk for fast loading 3168 * on boot 3169 */ 3170int dircache_save(void) 3171{ 3172 logf("Saving directory cache"); 3173 3174 int fd = open_dircache_file(O_WRONLY|O_CREAT|O_TRUNC|O_APPEND); 3175 if (fd < 0) 3176 return -1; 3177 3178 /* it seems the handle *must* exist if this function is called */ 3179 dircache_lock(); 3180 core_pin(dircache_runinfo.handle); 3181 3182 int rc = -1; 3183 3184 if (!dircache_is_clean(true)) 3185 goto error; 3186 3187 /* save the header structure along with the cache metadata */ 3188 ssize_t size; 3189 uint32_t crc; 3190 struct dircache_maindata maindata = 3191 { 3192 .magic = DIRCACHE_MAGIC, 3193 .dircache = dircache, 3194 }; 3195 3196 /* store the size since it better detects an invalid header */ 3197 maindata.dircache.sizeentries = maindata.dircache.numentries * ENTRYSIZE; 3198 3199 /* write the template header */ 3200 size = sizeof (maindata); 3201 if (write(fd, &maindata, size) != size) 3202 { 3203 logf("dircache: write failed #1"); 3204 goto error; 3205 } 3206 3207 /* write the dircache entries */ 3208 size = maindata.dircache.sizeentries; 3209 if (write(fd, dircache_runinfo.pentry + 1, size) != size) 3210 { 3211 logf("dircache: write failed #2"); 3212 goto error; 3213 } 3214 3215 crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff); 3216 3217 /* continue with the names */ 3218 size = maindata.dircache.sizenames; 3219 if (write(fd, get_name(dircache.names), size) != size) 3220 { 3221 logf("dircache: write failed #3"); 3222 goto error; 3223 } 3224 3225 crc = crc_32(get_name(dircache.names), size, crc); 3226 maindata.datacrc = crc; 3227 3228 /* rewrite the header with CRC info */ 3229 if (lseek(fd, 0, SEEK_SET) != 0) 3230 { 3231 logf("dircache: seek failed"); 3232 goto error; 3233 } 3234 3235 crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc), 3236 0xffffffff); 3237 maindata.hdrcrc = crc; 3238 3239 if (write(fd, &maindata, sizeof (maindata)) != sizeof (maindata)) 3240 { 3241 logf("dircache: write failed #4"); 3242 goto error; 3243 } 3244 3245 /* as of now, no changes to the volumes should be allowed at all since 3246 that makes what was saved completely invalid */ 3247 rc = 0; 3248error: 3249 core_unpin(dircache_runinfo.handle); 3250 dircache_unlock(); 3251 3252 if (rc < 0) 3253 remove_dircache_file(); 3254 3255 close(fd); 3256 return rc; 3257} 3258#endif /* HAVE_EEPROM_SETTINGS */ 3259 3260/** 3261 * main one-time initialization function that must be called before any other 3262 * operations within the dircache 3263 */ 3264void dircache_init(size_t last_size) 3265{ 3266 queue_init(&dircache_queue, false); 3267 3268 dircache.last_size = MIN(last_size, DIRCACHE_LIMIT); 3269 3270 struct dircache_runinfo *dcrip = &dircache_runinfo; 3271 dcrip->suspended = 1; 3272 dcrip->thread_done = true; 3273 dcrip->ops.move_callback = move_callback; 3274}