A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}