NetSurf
fs_backing_store.c
Go to the documentation of this file.
1/*
2 * Copyright 2014 Vincent Sanders <vince@netsurf-browser.org>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * \file
21 * Low-level resource cache persistent storage implementation.
22 *
23 * file based backing store.
24 *
25 * \todo Consider improving eviction sorting to include objects size
26 * and remaining lifetime and other cost metrics.
27 *
28 * \todo Implement mmap retrieval where supported.
29 *
30 * \todo Implement static retrieval for metadata objects as their heap
31 * lifetime is typically very short, though this may be obsoleted
32 * by a small object storage strategy.
33 *
34 */
35
36#include <unistd.h>
37#include <string.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <fcntl.h>
41#include <errno.h>
42#include <time.h>
43#include <stdlib.h>
44#include <nsutils/unistd.h>
45
46#include "netsurf/inttypes.h"
47#include "utils/filepath.h"
48#include "utils/file.h"
49#include "utils/nsurl.h"
50#include "utils/log.h"
51#include "utils/messages.h"
52#include "utils/hashmap.h"
54#include "netsurf/misc.h"
55
57
58/** Backing store file format version */
59#define CONTROL_VERSION 202
60
61/**
62 * Number of milliseconds after a update before control data
63 * maintenance is performed
64 */
65#define CONTROL_MAINT_TIME 10000
66
67/** Filename of serialised entries */
68#define ENTRIES_FNAME "entries"
69
70/** Filename of block file index */
71#define BLOCKS_FNAME "blocks"
72
73/** log2 block data address length (64k) */
74#define BLOCK_ADDR_LEN 16
75
76/** log2 number of entries per block file(1024) */
77#define BLOCK_ENTRY_COUNT 10
78
79/** log2 number of data block files */
80#define BLOCK_FILE_COUNT (BLOCK_ADDR_LEN - BLOCK_ENTRY_COUNT)
81
82/** log2 size of data blocks (8k) */
83#define BLOCK_DATA_SIZE 13
84
85/** log2 size of metadata blocks (8k) */
86#define BLOCK_META_SIZE 13
87
88/** length in bytes of a block files use map */
89#define BLOCK_USE_MAP_SIZE (1 << (BLOCK_ENTRY_COUNT - 3))
90
91/**
92 * The type used as a binary identifier for each entry derived from
93 * the URL. A larger identifier will have fewer collisions but
94 * requires proportionately more storage.
95 */
96typedef uint32_t entry_ident_t;
97
98/**
99 * The type used to store block file index values. If this is changed
100 * it will affect the entry storage/alignment and BLOCK_ADDR_LEN must
101 * also be updated.
102 */
103typedef uint16_t block_index_t;
104
105
106/**
107 * Entry element index values.
108 */
110 ENTRY_ELEM_DATA = 0, /**< entry element is data */
111 ENTRY_ELEM_META = 1, /**< entry element is metadata */
112 ENTRY_ELEM_COUNT = 2, /**< count of elements on an entry */
113};
114
115/**
116 * flags that indicate what additional information is contained within
117 * an entry element.
118 */
120 /** store not managing any allocation on entry */
122 /** entry data allocation is on heap */
124 /** entry data allocation is mmaped */
126 /** entry data allocation is in small object pool */
128};
129
130
132 /** entry is normal */
134 /** entry has been invalidated but something still holding a reference */
136};
137
138/**
139 * Backing store entry element.
140 *
141 * An element keeps data about:
142 * - the current memory allocation
143 * - the number of outstanding references to the memory
144 * - the size of the element data
145 * - flags controlling how the memory and element are handled
146 *
147 * @note Order is important to avoid excessive structure packing overhead.
148 */
150 uint8_t* data; /**< data allocated */
151 uint32_t size; /**< size of entry element on disc */
152 block_index_t block; /**< small object data block */
153 uint8_t ref; /**< element data reference count */
154 uint8_t flags; /**< entry flags */
155};
156
157/**
158 * Backing store object index entry.
159 *
160 * An entry in the backing store contains two elements for the actual
161 * data and the metadata. The two elements are treated identically for
162 * storage lifetime but as a collective whole for expiration and
163 * indexing.
164 *
165 * @note Order is important to avoid excessive structure packing overhead.
166 */
168 nsurl *url; /**< The URL for this entry */
169 int64_t last_used; /**< UNIX time the entry was last used */
170 uint16_t use_count; /**< number of times this entry has been accessed */
171 uint8_t flags; /**< entry flags */
172 /** Entry element (data or meta) specific information */
174};
175
176/**
177 * Small block file.
178 */
180 /** file descriptor of the block file */
181 int fd;
182 /** map of used and unused entries within the block file */
184};
185
186/**
187 * log2 of block size.
188 */
189static const unsigned int log2_block_size[ENTRY_ELEM_COUNT] = {
190 BLOCK_DATA_SIZE, /**< Data block size */
191 BLOCK_META_SIZE /**< Metadata block size */
192};
193
194/**
195 * Parameters controlling the backing store.
196 */
198 /* store config */
199 char *path; /**< The path to the backing store */
200 size_t limit; /**< The backing store upper bound target size */
201 size_t hysteresis; /**< The hysteresis around the target size */
202
203 /**
204 * The cache object hash
205 */
207
208 /** flag indicating if the entries have been made persistent
209 * since they were last changed.
210 */
212
213 /** small block indexes */
215
216 /** flag indicating if the block file use maps have been made
217 * persistent since they were last changed.
218 */
220
221 /** flag indicating if a block file has been opened for update
222 * since maintenance was previously done.
223 */
225
226
227 /* stats */
228 uint64_t total_alloc; /**< total size of all allocated storage. */
229
230 size_t hit_count; /**< number of cache hits */
231 uint64_t hit_size; /**< size of storage served */
232 size_t miss_count; /**< number of cache misses */
233
234};
235
236/**
237 * Global storage state.
238 *
239 * @todo Investigate if there is a way to have a context rather than
240 * use a global.
241 */
243
244/* Entries hashmap parameters
245 *
246 * Our hashmap has nsurl keys and store_entry values
247 */
248
249static bool
250entries_hashmap_key_eq(void *key1, void *key2)
251{
252 return nsurl_compare((nsurl *)key1, (nsurl *)key2, NSURL_COMPLETE);
253}
254
255static void *
257{
258 struct store_entry *ent = calloc(1, sizeof(struct store_entry));
259 if (ent != NULL) {
260 ent->url = nsurl_ref(key);
261 }
262 return ent;
263}
264
265static void
267{
268 struct store_entry *ent = value;
269 /** \todo Do we need to do any disk cleanup here? if so, meep! */
270 nsurl_unref(ent->url);
271 free(ent);
272}
273
276 .key_destroy = (hashmap_key_destroy_t)nsurl_unref,
277 .key_hash = (hashmap_key_hash_t)nsurl_hash,
278 .key_eq = entries_hashmap_key_eq,
279 .value_alloc = entries_hashmap_value_alloc,
280 .value_destroy = entries_hashmap_value_destroy,
281};
282
283/**
284 * Generate a filename for an object.
285 *
286 * this generates the filename for an object on disc. It is necessary
287 * for this to generate a filename which conforms to the limitations
288 * of all the filesystems the cache can be placed upon.
289 *
290 * From http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
291 * the relevant subset is:
292 * - path elements no longer than 8 characters
293 * - acceptable characters are A-Z, 0-9
294 * - short total path lengths (255 or less)
295 * - no more than 77 entries per directory (6bits worth)
296 *
297 * The short total path lengths mean the encoding must represent as
298 * much data as possible in the least number of characters.
299 *
300 * To achieve all these goals we use RFC4648 base32 encoding which
301 * packs 5bits into each character of the filename. To represent a 32
302 * bit ident this requires a total path length of between 17 and 22
303 * bytes (including directory separators) BA/BB/BC/BD/BE/ABCDEFG
304 *
305 * @note Version 1.00 of the cache implementation used base64 to
306 * encode this, however that did not meet the requirement for only
307 * using uppercase characters.
308 *
309 * @note Versions prior to 1.30 only packed 5 bits per directory level
310 * A/B/C/D/E/F/ABCDEFG which only required 19 characters to represent
311 * but resulted in requiring an extra level of directory which is less
312 * desirable than the three extra characters using six bits.
313 *
314 * @param state The store state to use.
315 * @param ident The identifier to use.
316 * @param elem_idx The element index.
317 * @return The filename string or NULL on allocation error.
318 */
319static char *
321 entry_ident_t ident,
322 int elem_idx)
323{
324 char *fname = NULL;
325 uint8_t b32u_i[8]; /* base32 encoded ident */
326 const uint8_t *b32u_d[6]; /* base32 ident as separate components */
327
328 /* directories used to separate elements */
329 const char *base_dir_table[] = {
330 "d", "m", "dblk", "mblk"
331 };
332
333 /* RFC4648 base32 encoding table (six bits) */
334 const uint8_t encoding_table[64][3] = {
335 { 'A', 0, 0 }, { 'B', 0, 0 }, /* 0 */
336 { 'C', 0, 0 }, { 'D', 0, 0 }, /* 2 */
337 { 'E', 0, 0 }, { 'F', 0, 0 }, /* 4 */
338 { 'G', 0, 0 }, { 'H', 0, 0 }, /* 6 */
339 { 'I', 0, 0 }, { 'J', 0, 0 }, /* 8 */
340 { 'K', 0, 0 }, { 'L', 0, 0 }, /* 10 */
341 { 'M', 0, 0 }, { 'N', 0, 0 }, /* 12 */
342 { 'O', 0, 0 }, { 'P', 0, 0 }, /* 14 */
343 { 'Q', 0, 0 }, { 'R', 0, 0 }, /* 16 */
344 { 'S', 0, 0 }, { 'T', 0, 0 }, /* 18 */
345 { 'U', 0, 0 }, { 'V', 0, 0 }, /* 20 */
346 { 'W', 0, 0 }, { 'X', 0, 0 }, /* 22 */
347 { 'Y', 0, 0 }, { 'Z', 0, 0 }, /* 24 */
348 { '2', 0, 0 }, { '3', 0, 0 }, /* 26 */
349 { '4', 0, 0 }, { '5', 0, 0 }, /* 28 */
350 { '6', 0, 0 }, { '7', 0, 0 }, /* 30 */
351 { 'B', 'A', 0 }, { 'B', 'B', 0 }, /* 32 */
352 { 'B', 'C', 0 }, { 'B', 'D', 0 }, /* 34 */
353 { 'B', 'E', 0 }, { 'B', 'F', 0 }, /* 36 */
354 { 'B', 'G', 0 }, { 'B', 'H', 0 }, /* 38 */
355 { 'B', 'I', 0 }, { 'B', 'J', 0 }, /* 40 */
356 { 'B', 'K', 0 }, { 'B', 'L', 0 }, /* 42 */
357 { 'B', 'M', 0 }, { 'B', 'N', 0 }, /* 44 */
358 { 'B', 'O', 0 }, { 'B', 'P', 0 }, /* 46 */
359 { 'B', 'Q', 0 }, { 'B', 'R', 0 }, /* 48 */
360 { 'B', 'S', 0 }, { 'B', 'T', 0 }, /* 50 */
361 { 'B', 'U', 0 }, { 'B', 'V', 0 }, /* 52 */
362 { 'B', 'W', 0 }, { 'B', 'X', 0 }, /* 54 */
363 { 'B', 'Y', 0 }, { 'B', 'Z', 0 }, /* 56 */
364 { 'B', '2', 0 }, { 'B', '3', 0 }, /* 58 */
365 { 'B', '4', 0 }, { 'B', '5', 0 }, /* 60 */
366 { 'B', '6', 0 }, { 'B', '7', 0 } /* 62 */
367 };
368
369 /* base32 encode ident */
370 b32u_i[0] = encoding_table[(ident ) & 0x1f][0];
371 b32u_i[1] = encoding_table[(ident >> 5) & 0x1f][0];
372 b32u_i[2] = encoding_table[(ident >> 10) & 0x1f][0];
373 b32u_i[3] = encoding_table[(ident >> 15) & 0x1f][0];
374 b32u_i[4] = encoding_table[(ident >> 20) & 0x1f][0];
375 b32u_i[5] = encoding_table[(ident >> 25) & 0x1f][0];
376 b32u_i[6] = encoding_table[(ident >> 30) & 0x1f][0];
377 b32u_i[7] = 0; /* null terminate ident string */
378
379 /* base32 encode directory separators */
380 b32u_d[0] = (uint8_t*)base_dir_table[elem_idx];
381 b32u_d[1] = &encoding_table[(ident ) & 0x3f][0];
382 b32u_d[2] = &encoding_table[(ident >> 6) & 0x3f][0];
383 b32u_d[3] = &encoding_table[(ident >> 12) & 0x3f][0];
384 b32u_d[4] = &encoding_table[(ident >> 18) & 0x3f][0];
385 b32u_d[5] = &encoding_table[(ident >> 24) & 0x3f][0];
386
387 switch (elem_idx) {
388 case ENTRY_ELEM_DATA:
389 case ENTRY_ELEM_META:
390 netsurf_mkpath(&fname, NULL, 8,
391 state->path, b32u_d[0], b32u_d[1], b32u_d[2],
392 b32u_d[3], b32u_d[4], b32u_d[5], b32u_i);
393 break;
394
397 netsurf_mkpath(&fname, NULL, 3,
398 state->path, b32u_d[0], b32u_d[1]);
399 break;
400
401 default:
402 assert("bad element index" == NULL);
403 break;
404 }
405
406 return fname;
407}
408
409/**
410 * invalidate an element of an entry
411 *
412 * @param state The store state to use.
413 * @param bse The entry to invalidate.
414 * @param elem_idx The element index to invalidate.
415 * @return NSERROR_OK on success or error code on failure.
416 */
417static nserror
419 struct store_entry *bse,
420 int elem_idx)
421{
422 if (bse->elem[elem_idx].block != 0) {
423 block_index_t bf;
424 block_index_t bi;
425
426 /* block file block resides in */
427 bf = (bse->elem[elem_idx].block >> BLOCK_ENTRY_COUNT) &
428 ((1 << BLOCK_FILE_COUNT) - 1);
429
430 /* block index in file */
431 bi = bse->elem[elem_idx].block & ((1U << BLOCK_ENTRY_COUNT) -1);
432
433 /* clear bit in use map */
434 state->blocks[elem_idx][bf].use_map[bi >> 3] &= ~(1U << (bi & 7));
435 } else {
436 char *fname;
437
438 /* unlink the file from disc */
439 fname = store_fname(state, nsurl_hash(bse->url), elem_idx);
440 if (fname == NULL) {
441 return NSERROR_NOMEM;
442 }
443 unlink(fname);
444 free(fname);
445 }
446
447 state->total_alloc -= bse->elem[elem_idx].size;
448
449 return NSERROR_OK;
450}
451
452/**
453 * Remove the entry and files associated with an identifier.
454 *
455 * @param state The store state to use.
456 * @param bse The entry to invalidate.
457 * @return NSERROR_OK on success or error code on failure.
458 */
459static nserror
460invalidate_entry(struct store_state *state, struct store_entry *bse)
461{
462 nserror ret;
463
464 /* mark entry as invalid */
466
467 /* check if the entry has storage already allocated */
468 if (((bse->elem[ENTRY_ELEM_DATA].flags &
470 ((bse->elem[ENTRY_ELEM_META].flags &
472 /*
473 * This entry cannot be immediately removed as it has
474 * associated allocation so wait for allocation release.
475 */
476 NSLOG(netsurf, DEBUG,
477 "invalidating entry with referenced allocation");
478 return NSERROR_OK;
479 }
480
481 NSLOG(netsurf, VERBOSE, "Removing entry for %s", nsurl_access(bse->url));
482
483 ret = invalidate_element(state, bse, ENTRY_ELEM_META);
484 if (ret != NSERROR_OK) {
485 NSLOG(netsurf, ERROR, "Error invalidating metadata element");
486 }
487
488 ret = invalidate_element(state, bse, ENTRY_ELEM_DATA);
489 if (ret != NSERROR_OK) {
490 NSLOG(netsurf, ERROR, "Error invalidating data element");
491 }
492
493 /* As our final act we remove bse from the cache */
494 hashmap_remove(state->entries, bse->url);
495 /* From now, bse is invalid memory */
496
497 return NSERROR_OK;
498}
499
500
501/**
502 * Quick sort comparison.
503 */
504static int compar(const void *va, const void *vb)
505{
506 const struct store_entry *a = *(const struct store_entry **)va;
507 const struct store_entry *b = *(const struct store_entry **)vb;
508
509 /* consider the allocation flags - if an entry has an
510 * allocation it is considered more valuable as it cannot be
511 * freed.
512 */
515 return -1;
516 } else if ((a->elem[ENTRY_ELEM_DATA].flags != ENTRY_ELEM_FLAG_NONE) &&
518 return 1;
519 }
520
523 return -1;
524 } else if ((a->elem[ENTRY_ELEM_META].flags != ENTRY_ELEM_FLAG_NONE) &&
526 return 1;
527 }
528
529 if (a->use_count < b->use_count) {
530 return -1;
531 } else if (a->use_count > b->use_count) {
532 return 1;
533 }
534 /* use count is the same - now consider last use time */
535
536 if (a->last_used < b->last_used) {
537 return -1;
538 } else if (a->last_used > b->last_used) {
539 return 1;
540 }
541
542 /* they are the same */
543 return 0;
544}
545
546typedef struct {
548 size_t ent_count;
550
551/**
552 * Iterator for gathering entries to compute eviction order
553 */
554static bool
555entry_eviction_iterator_cb(void *key, void *value, void *ctx)
556{
557 eviction_state_t *estate = ctx;
558 struct store_entry *ent = value;
559 estate->elist[estate->ent_count++] = ent;
560 return false;
561}
562
563/**
564 * Evict entries from backing store as per configuration.
565 *
566 * Entries are evicted to ensure the cache remains within the
567 * configured limits on size and number of entries.
568 *
569 * The approach is to check if the cache limits have been exceeded and
570 * if so build and sort list of entries to evict. The list is sorted
571 * by use count and then by age, so oldest object with least number of uses
572 * get evicted first.
573 *
574 * @param state The store state to use.
575 * @return NSERROR_OK on success or error code on failure.
576 */
577static nserror store_evict(struct store_state *state)
578{
579 size_t ent = 0;
580 size_t removed = 0; /* size of removed entries */
581 nserror ret = NSERROR_OK;
582 size_t old_count;
583 eviction_state_t estate;
584
585 /* check if the cache has exceeded configured limit */
586 if (state->total_alloc < state->limit) {
587 /* cache within limits */
588 return NSERROR_OK;
589 }
590
591 NSLOG(netsurf, INFO,
592 "Evicting entries to reduce %"PRIu64" by %"PRIsizet,
593 state->total_alloc,
594 state->hysteresis);
595
596 /* allocate storage for the list */
597 old_count = hashmap_count(state->entries);
598 estate.ent_count = 0;
599 estate.elist = malloc(sizeof(struct state_entry*) * old_count);
600 if (estate.elist == NULL) {
601 return NSERROR_NOMEM;
602 }
603
604 if (hashmap_iterate(state->entries, entry_eviction_iterator_cb, &estate)) {
605 NSLOG(netsurf, WARNING, "Unexpected termination of eviction iterator");
606 free(estate.elist);
607 return NSERROR_UNKNOWN;
608 }
609
610 if (old_count != estate.ent_count) {
611 NSLOG(netsurf, WARNING, "Incorrect entry count after eviction iterator");
612 free(estate.elist);
613 return NSERROR_UNKNOWN;
614 }
615
616 qsort(estate.elist, estate.ent_count, sizeof(struct state_entry*), compar);
617
618 /* evict entries in listed order */
619 removed = 0;
620 for (ent = 0; ent < estate.ent_count; ent++) {
621 struct store_entry *bse = estate.elist[ent];
622
623 removed += bse->elem[ENTRY_ELEM_DATA].size;
624 removed += bse->elem[ENTRY_ELEM_META].size;
625
626 ret = invalidate_entry(state, bse);
627 if (ret != NSERROR_OK) {
628 break;
629 }
630
631 if (removed > state->hysteresis) {
632 break;
633 }
634 }
635
636 free(estate.elist);
637
638 NSLOG(netsurf, INFO,
639 "removed %"PRIsizet" in %"PRIsizet" entries, %"PRIu64" remaining in %"PRIsizet" entries",
640 removed, ent, state->total_alloc, old_count - ent);
641
642 return ret;
643}
644
645/**
646 * Write a single store entry to disk
647 *
648 * To serialise a single store entry for now we write out a 32bit int
649 * which is the length of the url, then that many bytes of the url.
650 * Then we write out the full store entry struct as-is, which includes
651 * a useless nsurl pointer.
652 */
653static nserror
654write_entry(struct store_entry *ent, int fd)
655{
656 uint32_t len = strlen(nsurl_access(ent->url));
657 if (write(fd, &len, sizeof(len)) != sizeof(len))
658 return NSERROR_SAVE_FAILED;
659 if (write(fd, nsurl_access(ent->url), len) != (ssize_t)len)
660 return NSERROR_SAVE_FAILED;
661 if (write(fd, ent, sizeof(*ent)) != sizeof(*ent))
662 return NSERROR_SAVE_FAILED;
663
664 return NSERROR_OK;
665}
666
667typedef struct {
668 int fd;
669 size_t written;
671
672/**
673 * Callback for iterating the entries hashmap
674 */
675static bool
676write_entry_iterator(void *key, void *value, void *ctx)
677{
678 /* We ignore the key */
679 struct store_entry *ent = value;
680 write_entry_iteration_state *state = ctx;
681 state->written++;
682 /* We stop early if we fail to write this entry */
683 return write_entry(ent, state->fd) != NSERROR_OK;
684}
685
686/**
687 * Write filesystem entries to file.
688 *
689 * Serialise entry index out to storage.
690 *
691 * @param state The backing store state to serialise.
692 * @return NSERROR_OK on success or error code on failure.
693 */
694static nserror write_entries(struct store_state *state)
695{
696 char *tname = NULL; /* temporary file name for atomic replace */
697 char *fname = NULL; /* target filename */
699 nserror ret;
700
701 memset(&weistate, 0, sizeof(weistate));
702
703 if (state->entries_dirty == false) {
704 /* entries have not been updated since last write */
705 return NSERROR_OK;
706 }
707
708 ret = netsurf_mkpath(&tname, NULL, 2, state->path, "t"ENTRIES_FNAME);
709 if (ret != NSERROR_OK) {
710 return ret;
711 }
712
713 weistate.fd = open(tname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
714 if (weistate.fd == -1) {
715 free(tname);
716 return NSERROR_SAVE_FAILED;
717 }
718
719 if (hashmap_iterate(state->entries, write_entry_iterator, &weistate)) {
720 /* The iteration ended early, so we failed */
721 close(weistate.fd);
722 unlink(tname);
723 free(tname);
724 return NSERROR_SAVE_FAILED;
725 }
726
727 close(weistate.fd);
728
729 ret = netsurf_mkpath(&fname, NULL, 2, state->path, ENTRIES_FNAME);
730 if (ret != NSERROR_OK) {
731 unlink(tname);
732 free(tname);
733 return ret;
734 }
735
736 /* remove() call is to handle non-POSIX rename() implementations */
737 (void)remove(fname);
738 if (rename(tname, fname) != 0) {
739 unlink(tname);
740 free(tname);
741 free(fname);
742 return NSERROR_SAVE_FAILED;
743 }
744
745 NSLOG(netsurf, INFO, "Wrote out %"PRIsizet" entries", weistate.written);
746
747 return NSERROR_OK;
748}
749
750/**
751 * Write block file use map to file.
752 *
753 * Serialise block file use map out to storage.
754 *
755 * \param state The backing store state to serialise.
756 * \return NSERROR_OK on success or error code on failure.
757 */
758static nserror write_blocks(struct store_state *state)
759{
760 int fd;
761 char *tname = NULL; /* temporary file name for atomic replace */
762 char *fname = NULL; /* target filename */
763 size_t blocks_size;
764 size_t written = 0;
765 size_t wr;
766 nserror ret;
767 int bfidx; /* block file index */
768 int elem_idx;
769
770 if (state->blocks_dirty == false) {
771 /* blocks use maps have not been updated since last write */
772 return NSERROR_OK;
773 }
774
775 ret = netsurf_mkpath(&tname, NULL, 2, state->path, "t"BLOCKS_FNAME);
776 if (ret != NSERROR_OK) {
777 return ret;
778 }
779
780 fd = open(tname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
781 if (fd == -1) {
782 free(tname);
783 return NSERROR_SAVE_FAILED;
784 }
785
787
788 for (elem_idx = 0; elem_idx < ENTRY_ELEM_COUNT; elem_idx++) {
789 for (bfidx = 0; bfidx < BLOCK_FILE_COUNT; bfidx++) {
790 wr = write(fd,
791 &state->blocks[elem_idx][bfidx].use_map[0],
793 if (wr != BLOCK_USE_MAP_SIZE) {
794 NSLOG(netsurf, DEBUG,
795 "writing block file %d use index on file number %d failed",
796 elem_idx,
797 bfidx);
798 goto wr_err;
799 }
800 written += wr;
801 }
802 }
803wr_err:
804 close(fd);
805
806 /* check all data was written */
807 if (written != blocks_size) {
808 unlink(tname);
809 free(tname);
810 return NSERROR_SAVE_FAILED;
811 }
812
813 ret = netsurf_mkpath(&fname, NULL, 2, state->path, BLOCKS_FNAME);
814 if (ret != NSERROR_OK) {
815 unlink(tname);
816 free(tname);
817 return ret;
818 }
819
820 /* remove() call is to handle non-POSIX rename() implementations */
821 (void)remove(fname);
822 if (rename(tname, fname) != 0) {
823 unlink(tname);
824 free(tname);
825 free(fname);
826 return NSERROR_SAVE_FAILED;
827 }
828
829 return NSERROR_OK;
830}
831
832/**
833 * Ensures block files are of the correct extent
834 *
835 * block files have their extent set to their maximum size to ensure
836 * subsequent reads and writes do not need to extend the file and are
837 * therefore faster.
838 *
839 * \param state The backing store state to set block extent for.
840 * \return NSERROR_OK on success or error code on failure.
841 */
843{
844 int bfidx; /* block file index */
845 int elem_idx;
846 int ftr;
847
848 if (state->blocks_opened == false) {
849 /* no blocks have been opened since last write */
850 return NSERROR_OK;
851 }
852
853 NSLOG(netsurf, DEBUG, "Starting");
854 for (elem_idx = 0; elem_idx < ENTRY_ELEM_COUNT; elem_idx++) {
855 for (bfidx = 0; bfidx < BLOCK_FILE_COUNT; bfidx++) {
856 if (state->blocks[elem_idx][bfidx].fd != -1) {
857 /* ensure block file is correct extent */
858 ftr = ftruncate(state->blocks[elem_idx][bfidx].fd, 1U << (log2_block_size[elem_idx] + BLOCK_ENTRY_COUNT));
859 if (ftr == -1) {
860 NSLOG(netsurf, ERROR,
861 "Truncate failed errno:%d",
862 errno);
863 }
864 }
865 }
866 }
867 NSLOG(netsurf, DEBUG, "Complete");
868
869 state->blocks_opened = false;
870
871 return NSERROR_OK;
872}
873
874/**
875 * maintenance of control structures.
876 *
877 * callback scheduled when control data has been update. Currently
878 * this is for when the entries table is dirty and requires
879 * serialising.
880 *
881 * \param s store state to maintain.
882 */
883static void control_maintenance(void *s)
884{
885 struct store_state *state = s;
886
887 write_entries(state);
888 write_blocks(state);
889 set_block_extents(state);
890}
891
892
893/**
894 * Lookup a backing store entry in the entry table from a url.
895 *
896 * This finds the store entry associated with the given
897 * key. Additionally if an entry is found it updates the usage data
898 * about the entry.
899 *
900 * @param state The store state to use.
901 * @param url The value used as the unique key to search entries for.
902 * @param bse Pointer used to return value.
903 * @return NSERROR_OK and bse updated on success or NSERROR_NOT_FOUND
904 * if no entry corresponds to the url.
905 */
906static nserror
907get_store_entry(struct store_state *state, nsurl *url, struct store_entry **bse)
908{
909 struct store_entry *ent;
910
911 ent = hashmap_lookup(state->entries, url);
912
913 if (ent == NULL) {
914 return NSERROR_NOT_FOUND;
915 }
916
917 *bse = ent;
918
919 ent->last_used = time(NULL);
920 ent->use_count++;
921
922 state->entries_dirty = true;
923
925
926 return NSERROR_OK;
927}
928
929/**
930 * Find next available small block.
931 */
932static block_index_t alloc_block(struct store_state *state, int elem_idx)
933{
934 int bf;
935 int idx;
936 int bit;
937 uint8_t *map;
938
939 for (bf = 0; bf < BLOCK_FILE_COUNT; bf++) {
940 map = &state->blocks[elem_idx][bf].use_map[0];
941
942 for (idx = 0; idx < BLOCK_USE_MAP_SIZE; idx++) {
943 if (*(map + idx) != 0xff) {
944 /* located an unused block */
945 for (bit = 0; bit < 8;bit++) {
946 if (((*(map + idx)) & (1U << bit)) == 0) {
947 /* mark block as used */
948 *(map + idx) |= 1U << bit;
949 state->blocks_dirty = true;
950 return (((bf * BLOCK_USE_MAP_SIZE) + idx) * 8) + bit;
951 }
952 }
953 }
954 }
955 }
956
957 return 0;
958}
959
960/**
961 * Set a backing store entry in the entry table from a url.
962 *
963 * This creates a backing store entry in the entry table for a url.
964 *
965 * @param state The store state to use.
966 * @param url The value used as the unique key to search entries for.
967 * @param elem_idx The index of the entry element to use.
968 * @param data The data to store
969 * @param datalen The length of data in \a data
970 * @param bse Pointer used to return value.
971 * @return NSERROR_OK and \a bse updated on success or NSERROR_NOT_FOUND
972 * if no entry corresponds to the url.
973 */
974static nserror
976 nsurl *url,
977 int elem_idx,
978 uint8_t *data,
979 const size_t datalen,
980 struct store_entry **bse)
981{
982 struct store_entry *se;
983 nserror ret;
984 struct store_entry_element *elem;
985
986 NSLOG(netsurf, DEBUG, "url:%s", nsurl_access(url));
987
988 /* evict entries as required and ensure there is at least one
989 * new entry available.
990 */
991 ret = store_evict(state);
992 if (ret != NSERROR_OK) {
993 return ret;
994 }
995
996 se = hashmap_lookup(state->entries, url);
997 if (se == NULL) {
998 se = hashmap_insert(state->entries, url);
999 }
1000 if (se == NULL) {
1001 return NSERROR_NOMEM;
1002 }
1003
1004 /* the entry element */
1005 elem = &se->elem[elem_idx];
1006
1007 /* check if the element has storage already allocated */
1008 if ((elem->flags & (ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0) {
1009 /* this entry cannot be removed as it has associated
1010 * allocation.
1011 */
1012 NSLOG(netsurf, ERROR,
1013 "attempt to overwrite entry with in use data");
1014 return NSERROR_PERMISSION;
1015 }
1016
1017 /* set the common entry data */
1018 se->use_count = 1;
1019 se->last_used = time(NULL);
1020
1021 /* store the data in the element */
1022 elem->flags |= ENTRY_ELEM_FLAG_HEAP;
1023 elem->data = data;
1024 elem->ref = 1;
1025
1026 /* account for size of entry element */
1027 state->total_alloc -= elem->size;
1028 elem->size = datalen;
1029 state->total_alloc += elem->size;
1030
1031 /* if the element will fit in a small block attempt to allocate one */
1032 if (elem->size <= (1U << log2_block_size[elem_idx])) {
1033 elem->block = alloc_block(state, elem_idx);
1034 }
1035
1036 /* ensure control maintenance scheduled. */
1037 state->entries_dirty = true;
1039
1040 *bse = se;
1041
1042 return NSERROR_OK;
1043}
1044
1045
1046/**
1047 * Open a file using a store ident.
1048 *
1049 * @param state The store state to use.
1050 * @param ident The identifier to open file for.
1051 * @param elem_idx The element within the store entry to open. The
1052 * value should be be one of the values in the
1053 * store_entry_elem_idx enum. Additionally it may have
1054 * ENTRY_ELEM_COUNT added to it to indicate block file
1055 * names.
1056 * @param openflags The flags used with the open call.
1057 * @return An fd from the open call or -1 on error.
1058 */
1059static int
1061 entry_ident_t ident,
1062 int elem_idx,
1063 int openflags)
1064{
1065 char *fname;
1066 nserror ret;
1067 int fd;
1068
1069 fname = store_fname(state, ident, elem_idx);
1070 if (fname == NULL) {
1071 NSLOG(netsurf, ERROR, "filename error");
1072 return -1;
1073 }
1074
1075 /* ensure all path elements to file exist if creating file */
1076 if (openflags & O_CREAT) {
1077 ret = netsurf_mkdir_all(fname);
1078 if (ret != NSERROR_OK) {
1079 NSLOG(netsurf, WARNING,
1080 "file path \"%s\" could not be created", fname);
1081 free(fname);
1082 return -1;
1083 }
1084 }
1085
1086 NSLOG(netsurf, DEBUG, "opening %s", fname);
1087 fd = open(fname, openflags, S_IRUSR | S_IWUSR);
1088
1089 free(fname);
1090
1091 return fd;
1092}
1093
1094
1095/**
1096 * Unlink entries file
1097 *
1098 * @param state The backing store state.
1099 * @return NSERROR_OK on success or error code on failure.
1100 */
1101static nserror
1103{
1104 char *fname = NULL;
1105 nserror ret;
1106
1107 ret = netsurf_mkpath(&fname, NULL, 2, state->path, ENTRIES_FNAME);
1108 if (ret != NSERROR_OK) {
1109 return ret;
1110 }
1111
1112 unlink(fname);
1113
1114 free(fname);
1115 return NSERROR_OK;
1116}
1117
1118/**
1119 * Read description entries into memory.
1120 *
1121 * @param state The backing store state to put the loaded entries in.
1122 * @return NSERROR_OK on success or error code on faliure.
1123 */
1124static nserror
1126{
1127 char *fname = NULL;
1128 char *url;
1129 nsurl *nsurl;
1130 nserror ret;
1131 size_t read_entries = 0;
1132 struct store_entry *ent;
1133 int fd;
1134
1135 ret = netsurf_mkpath(&fname, NULL, 2, state->path, ENTRIES_FNAME);
1136 if (ret != NSERROR_OK) {
1137 return ret;
1138 }
1139
1141 if (state->entries == NULL) {
1142 free(fname);
1143 return NSERROR_NOMEM;
1144 }
1145
1146 fd = open(fname, O_RDWR);
1147 if (fd != -1) {
1148 uint32_t urllen;
1149 while (read(fd, &urllen, sizeof(urllen)) == sizeof(urllen)) {
1150 url = calloc(1, urllen+1);
1151 if (url == NULL) {
1152 close(fd);
1153 free(fname);
1154 return NSERROR_NOMEM;
1155 }
1156 if (read(fd, url, urllen) != (ssize_t)urllen) {
1157 free(url);
1158 close(fd);
1159 free(fname);
1160 return NSERROR_INIT_FAILED;
1161 }
1162 ret = nsurl_create(url, &nsurl);
1163 if (ret != NSERROR_OK) {
1164 free(url);
1165 close(fd);
1166 free(fname);
1167 return ret;
1168 }
1169 free(url);
1170 /* We have to be careful here about nsurl refs */
1171 ent = hashmap_insert(state->entries, nsurl);
1172 if (ent == NULL) {
1174 close(fd);
1175 free(fname);
1176 return NSERROR_NOMEM;
1177 }
1178 /* At this point, ent actually owns a ref of nsurl */
1179 if (read(fd, ent, sizeof(*ent)) != sizeof(*ent)) {
1180 /* The read failed, so reset the ptr */
1181 ent->url = nsurl; /* It already had a ref */
1183 close(fd);
1184 free(fname);
1185 return NSERROR_INIT_FAILED;
1186 }
1187 ent->url = nsurl; /* It already owns a ref */
1189 NSLOG(netsurf, DEBUG, "Successfully read entry for %s", nsurl_access(ent->url));
1190 read_entries++;
1191 /* Note the size allocation */
1192 state->total_alloc += ent->elem[ENTRY_ELEM_DATA].size;
1193 state->total_alloc += ent->elem[ENTRY_ELEM_META].size;
1194 /* And ensure we don't pretend to have this in memory yet */
1197
1198 }
1199 close(fd);
1200 }
1201
1202 NSLOG(netsurf, INFO, "Read %"PRIsizet" entries from cache", read_entries);
1203
1204 free(fname);
1205 return NSERROR_OK;
1206}
1207
1208
1209/**
1210 * Read block file usage bitmaps.
1211 *
1212 * @param state The backing store state to put the loaded entries in.
1213 * @return NSERROR_OK on success or error code on failure.
1214 */
1215static nserror
1217{
1218 int bfidx; /* block file index */
1219 int elem_idx;
1220 int fd;
1221 ssize_t rd;
1222 char *fname = NULL;
1223 nserror ret;
1224
1225 ret = netsurf_mkpath(&fname, NULL, 2, state->path, BLOCKS_FNAME);
1226 if (ret != NSERROR_OK) {
1227 return ret;
1228 }
1229
1230 NSLOG(netsurf, INFO, "Initialising block use map from %s", fname);
1231
1232 fd = open(fname, O_RDWR);
1233 free(fname);
1234 if (fd != -1) {
1235 /* initialise block file use array */
1236 for (elem_idx = 0; elem_idx < ENTRY_ELEM_COUNT; elem_idx++) {
1237 for (bfidx = 0; bfidx < BLOCK_FILE_COUNT; bfidx++) {
1238 rd = read(fd,
1239 &state->blocks[elem_idx][bfidx].use_map[0],
1241 if (rd <= 0) {
1242 NSLOG(netsurf, ERROR,
1243 "reading block file %d use index on file number %d failed",
1244 elem_idx,
1245 bfidx);
1246 goto rd_err;
1247 }
1248 }
1249 }
1250 rd_err:
1251 close(fd);
1252
1253 } else {
1254 NSLOG(netsurf, INFO, "Initialising block use map to defaults");
1255 /* ensure block 0 (invalid sentinel) is skipped */
1256 state->blocks[ENTRY_ELEM_DATA][0].use_map[0] = 1;
1257 state->blocks[ENTRY_ELEM_META][0].use_map[0] = 1;
1258 }
1259
1260 /* initialise block file file descriptors */
1261 for (bfidx = 0; bfidx < BLOCK_FILE_COUNT; bfidx++) {
1262 state->blocks[ENTRY_ELEM_DATA][bfidx].fd = -1;
1263 state->blocks[ENTRY_ELEM_META][bfidx].fd = -1;
1264 }
1265
1266 return NSERROR_OK;
1267}
1268
1269/**
1270 * Write the cache tag file.
1271 *
1272 * @param state The cache state.
1273 * @return NSERROR_OK on success or error code on failure.
1274 */
1275static nserror
1277{
1278 FILE *fcachetag;
1279 nserror ret;
1280 char *fname = NULL;
1281
1282 ret = netsurf_mkpath(&fname, NULL, 2, state->path, "CACHEDIR.TAG");
1283 if (ret != NSERROR_OK) {
1284 return ret;
1285 }
1286
1287 fcachetag = fopen(fname, "wb");
1288
1289 free(fname);
1290
1291 if (fcachetag == NULL) {
1292 return NSERROR_NOT_FOUND;
1293 }
1294
1295 fprintf(fcachetag,
1296 "Signature: 8a477f597d28d172789f06886806bc55\n"
1297 "# This file is a cache directory tag created by NetSurf.\n"
1298 "# For information about cache directory tags, see:\n"
1299 "# http://www.brynosaurus.com/cachedir/\n");
1300
1301 fclose(fcachetag);
1302
1303 return NSERROR_OK;
1304}
1305
1306/**
1307 * Write the control file for the current state.
1308 *
1309 * @param state The state to write to the control file.
1310 * @return NSERROR_OK on success or error code on failure.
1311 */
1312static nserror
1314{
1315 FILE *fcontrol;
1316 nserror ret;
1317 char *fname = NULL;
1318
1319 ret = netsurf_mkpath(&fname, NULL, 2, state->path, "control");
1320 if (ret != NSERROR_OK) {
1321 return ret;
1322 }
1323
1324 NSLOG(netsurf, INFO, "writing control file \"%s\"", fname);
1325
1326 ret = netsurf_mkdir_all(fname);
1327 if (ret != NSERROR_OK) {
1328 free(fname);
1329 return ret;
1330 }
1331
1332 fcontrol = fopen(fname, "wb");
1333
1334 free(fname);
1335
1336 if (fcontrol == NULL) {
1337 return NSERROR_NOT_FOUND;
1338 }
1339
1340 fprintf(fcontrol, "%u%c", CONTROL_VERSION, 0);
1341
1342 fclose(fcontrol);
1343
1344 return NSERROR_OK;
1345}
1346
1347
1348/**
1349 * Read and parse the control file.
1350 *
1351 * @param state The state to read from the control file.
1352 * @return NSERROR_OK on success or error code on failure.
1353 */
1354static nserror
1356{
1357 nserror ret;
1358 FILE *fcontrol;
1359 unsigned int ctrlversion;
1360 char *fname = NULL;
1361
1362 ret = netsurf_mkpath(&fname, NULL, 2, state->path, "control");
1363 if (ret != NSERROR_OK) {
1364 return ret;
1365 }
1366
1367 NSLOG(netsurf, INFO, "opening control file \"%s\"", fname);
1368
1369 fcontrol = fopen(fname, "rb");
1370
1371 free(fname);
1372
1373 if (fcontrol == NULL) {
1374 /* unable to open control file */
1375 if (errno == ENOENT) {
1376 return NSERROR_NOT_FOUND;
1377 } else {
1378 return NSERROR_INIT_FAILED;
1379 }
1380 }
1381
1382 /* read control and setup new state */
1383
1384 /* first line is version */
1385 if (fscanf(fcontrol, "%u", &ctrlversion) != 1) {
1386 goto control_error;
1387 }
1388
1389 if (ctrlversion != CONTROL_VERSION) {
1390 goto control_error;
1391 }
1392
1393 if (fgetc(fcontrol) != 0) {
1394 goto control_error;
1395 }
1396
1397 fclose(fcontrol);
1398
1399 return NSERROR_OK;
1400
1401control_error: /* problem with the control file */
1402
1403 fclose(fcontrol);
1404
1405 return NSERROR_INIT_FAILED;
1406}
1407
1408
1409
1410
1411/* Functions exported in the backing store table */
1412
1413/**
1414 * Initialise the backing store.
1415 *
1416 * @param parameters to configure backing store.
1417 * @return NSERROR_OK on success or error code on failure.
1418 */
1419static nserror
1420initialise(const struct llcache_store_parameters *parameters)
1421{
1422 struct store_state *newstate;
1423 nserror ret;
1424
1425 /* check backing store is not already initialised */
1426 if (storestate != NULL) {
1427 return NSERROR_INIT_FAILED;
1428 }
1429
1430 /* if we are not allowed any space simply give up on init */
1431 if (parameters->limit == 0) {
1432 return NSERROR_OK;
1433 }
1434
1435 /* if the path to the cache directory is not set do not init */
1436 if (parameters->path == NULL) {
1437 return NSERROR_OK;
1438 }
1439
1440 /* allocate new store state and set defaults */
1441 newstate = calloc(1, sizeof(struct store_state));
1442 if (newstate == NULL) {
1443 return NSERROR_NOMEM;
1444 }
1445
1446 newstate->path = strdup(parameters->path);
1447 newstate->limit = parameters->limit;
1448 newstate->hysteresis = parameters->hysteresis;
1449
1450 /* read store control and create new if required */
1451 ret = read_control(newstate);
1452 if (ret != NSERROR_OK) {
1453 if (ret == NSERROR_NOT_FOUND) {
1454 NSLOG(netsurf, INFO, "cache control file not found, making fresh");
1455 } else {
1456 NSLOG(netsurf, ERROR, "read control failed %s",
1458 ret = netsurf_recursive_rm(newstate->path);
1459 if (ret != NSERROR_OK) {
1460 NSLOG(netsurf, WARNING, "Error `%s` while removing `%s`",
1461 messages_get_errorcode(ret), newstate->path);
1462 NSLOG(netsurf, WARNING, "Unable to clean up partial cache state.");
1463 NSLOG(netsurf, WARNING, "Funky behaviour may ensue.");
1464 } else {
1465 NSLOG(netsurf, INFO, "Successfully removed old cache from `%s`",
1466 newstate->path);
1467 }
1468 }
1469 ret = write_control(newstate);
1470 if (ret == NSERROR_OK) {
1471 unlink_entries(newstate);
1472 write_cache_tag(newstate);
1473 }
1474 }
1475 if (ret != NSERROR_OK) {
1476 /* that went well obviously */
1477 free(newstate->path);
1478 free(newstate);
1479 return ret;
1480 }
1481
1482 /* read filesystem entries */
1483 ret = read_entries(newstate);
1484 if (ret != NSERROR_OK) {
1485 /* that went well obviously */
1486 free(newstate->path);
1487 free(newstate);
1488 return ret;
1489 }
1490
1491 /* read blocks */
1492 ret = read_blocks(newstate);
1493 if (ret != NSERROR_OK) {
1494 /* oh dear */
1495 hashmap_destroy(newstate->entries);
1496 free(newstate->path);
1497 free(newstate);
1498 return ret;
1499 }
1500
1501 storestate = newstate;
1502
1503 NSLOG(netsurf, INFO, "FS backing store init successful");
1504
1505 NSLOG(netsurf, INFO,
1506 "path:%s limit:%"PRIsizet" hyst:%"PRIsizet,
1507 newstate->path,
1508 newstate->limit,
1509 newstate->hysteresis);
1510 NSLOG(netsurf, INFO, "Using %"PRIu64"/%"PRIsizet,
1511 newstate->total_alloc, newstate->limit);
1512
1513 return NSERROR_OK;
1514}
1515
1516
1517/**
1518 * Finalise the backing store.
1519 *
1520 * \todo This will cause the backing store to leak any outstanding memory
1521 * allocations. This will probably best be done by a global use count.
1522 *
1523 * @return NSERROR_OK on success.
1524 */
1525static nserror
1527{
1528 int bf; /* block file index */
1529 unsigned int op_count;
1530
1531 if (storestate != NULL) {
1535
1536 /* ensure all block files are closed */
1537 for (bf = 0; bf < BLOCK_FILE_COUNT; bf++) {
1538 if (storestate->blocks[ENTRY_ELEM_DATA][bf].fd != -1) {
1539 close(storestate->blocks[ENTRY_ELEM_DATA][bf].fd);
1540 }
1541 if (storestate->blocks[ENTRY_ELEM_META][bf].fd != -1) {
1542 close(storestate->blocks[ENTRY_ELEM_META][bf].fd);
1543 }
1544 }
1545
1547
1548 /* avoid division by zero */
1549 if (op_count > 0) {
1550 NSLOG(netsurf, INFO,
1551 "Cache total/hit/miss/fail (counts) %d/%"PRIsizet"/%"PRIsizet"/%d (100%%/%"PRIsizet"%%/%"PRIsizet"%%/%d%%)",
1552 op_count,
1555 0,
1556 (storestate->hit_count * 100) / op_count,
1557 (storestate->miss_count * 100) / op_count,
1558 0);
1559 }
1560
1562 free(storestate->path);
1563 free(storestate);
1564 storestate = NULL;
1565 }
1566 return NSERROR_OK;
1567}
1568
1569
1570/**
1571 * Write an element of an entry to backing storage in a small block file.
1572 *
1573 * \param state The backing store state to use.
1574 * \param bse The entry to store
1575 * \param elem_idx The element index within the entry.
1576 * \return NSERROR_OK on success or error code.
1577 */
1579 struct store_entry *bse,
1580 int elem_idx)
1581{
1582 block_index_t bf = (bse->elem[elem_idx].block >> BLOCK_ENTRY_COUNT) &
1583 ((1 << BLOCK_FILE_COUNT) - 1); /* block file block resides in */
1584 block_index_t bi = bse->elem[elem_idx].block & ((1U << BLOCK_ENTRY_COUNT) -1); /* block index in file */
1585 ssize_t wr;
1586 off_t offst;
1587
1588 /* ensure the block file fd is good */
1589 if (state->blocks[elem_idx][bf].fd == -1) {
1590 state->blocks[elem_idx][bf].fd = store_open(state, bf,
1591 elem_idx + ENTRY_ELEM_COUNT, O_CREAT | O_RDWR);
1592 if (state->blocks[elem_idx][bf].fd == -1) {
1593 NSLOG(netsurf, ERROR, "Open failed errno %d", errno);
1594 return NSERROR_SAVE_FAILED;
1595 }
1596
1597 /* flag that a block file has been opened */
1598 state->blocks_opened = true;
1599 }
1600
1601 offst = (unsigned int)bi << log2_block_size[elem_idx];
1602
1603 wr = nsu_pwrite(state->blocks[elem_idx][bf].fd,
1604 bse->elem[elem_idx].data,
1605 bse->elem[elem_idx].size,
1606 offst);
1607 if (wr != (ssize_t)bse->elem[elem_idx].size) {
1608 NSLOG(netsurf, ERROR,
1609 "Write failed %"PRIssizet" of %"PRId32" bytes from %p at %"PRIsizet" block %"PRIu16" errno %d",
1610 wr,
1611 bse->elem[elem_idx].size,
1612 bse->elem[elem_idx].data,
1613 (size_t)offst,
1614 bse->elem[elem_idx].block,
1615 errno);
1616 return NSERROR_SAVE_FAILED;
1617 }
1618
1619 NSLOG(netsurf, INFO,
1620 "Wrote %"PRIssizet" bytes from %p at %"PRIsizet" block %d", wr,
1621 bse->elem[elem_idx].data, (size_t)offst,
1622 bse->elem[elem_idx].block);
1623
1624 return NSERROR_OK;
1625}
1626
1627/**
1628 * Write an element of an entry to backing storage as an individual file.
1629 *
1630 * \param state The backing store state to use.
1631 * \param bse The entry to store
1632 * \param elem_idx The element index within the entry.
1633 * \return NSERROR_OK on success or error code.
1634 */
1636 struct store_entry *bse,
1637 int elem_idx)
1638{
1639 ssize_t wr;
1640 int fd;
1641 int err;
1642
1643 fd = store_open(state, nsurl_hash(bse->url), elem_idx, O_CREAT | O_WRONLY);
1644 if (fd < 0) {
1645 perror("");
1646 NSLOG(netsurf, ERROR, "Open failed %d errno %d", fd, errno);
1647 return NSERROR_SAVE_FAILED;
1648 }
1649
1650 wr = write(fd, bse->elem[elem_idx].data, bse->elem[elem_idx].size);
1651 err = errno; /* close can change errno */
1652
1653 close(fd);
1654 if (wr != (ssize_t)bse->elem[elem_idx].size) {
1655 NSLOG(netsurf, ERROR,
1656 "Write failed %"PRIssizet" of %"PRId32" bytes from %p errno %d",
1657 wr,
1658 bse->elem[elem_idx].size,
1659 bse->elem[elem_idx].data,
1660 err);
1661
1662 /** @todo Delete the file? */
1663 return NSERROR_SAVE_FAILED;
1664 }
1665
1666 NSLOG(netsurf, VERBOSE, "Wrote %"PRIssizet" bytes from %p", wr,
1667 bse->elem[elem_idx].data);
1668
1669 return NSERROR_OK;
1670}
1671
1672/**
1673 * Place an object in the backing store.
1674 *
1675 * takes ownership of the heap block passed in.
1676 *
1677 * @param url The url is used as the unique primary key for the data.
1678 * @param bsflags The flags to control how the object is stored.
1679 * @param data The objects source data.
1680 * @param datalen The length of the \a data.
1681 * @return NSERROR_OK on success or error code on failure.
1682 */
1683static nserror
1685 enum backing_store_flags bsflags,
1686 uint8_t *data,
1687 const size_t datalen)
1688{
1689 nserror ret;
1690 struct store_entry *bse;
1691 int elem_idx;
1692
1693 /* check backing store is initialised */
1694 if (storestate == NULL) {
1695 return NSERROR_INIT_FAILED;
1696 }
1697
1698 /* calculate the entry element index */
1699 if ((bsflags & BACKING_STORE_META) != 0) {
1700 elem_idx = ENTRY_ELEM_META;
1701 } else {
1702 elem_idx = ENTRY_ELEM_DATA;
1703 }
1704
1705 /* set the store entry up */
1706 ret = set_store_entry(storestate, url, elem_idx, data, datalen, &bse);
1707 if (ret != NSERROR_OK) {
1708 NSLOG(netsurf, ERROR, "store entry setting failed");
1709 return ret;
1710 }
1711
1712 if (bse->elem[elem_idx].block != 0) {
1713 /* small block storage */
1714 ret = store_write_block(storestate, bse, elem_idx);
1715 } else {
1716 /* separate file in backing store */
1717 ret = store_write_file(storestate, bse, elem_idx);
1718 }
1719
1720 return ret;
1721}
1722
1723/**
1724 * release any allocation for an entry
1725 */
1727{
1728 if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) {
1729 elem->ref--;
1730 if (elem->ref == 0) {
1731 NSLOG(netsurf, DEEPDEBUG, "freeing %p", elem->data);
1732 free(elem->data);
1733 elem->flags &= ~ENTRY_ELEM_FLAG_HEAP;
1734 }
1735 }
1736 return NSERROR_OK;
1737}
1738
1739
1740/**
1741 * Read an element of an entry from a small block file in the backing storage.
1742 *
1743 * \param state The backing store state to use.
1744 * \param bse The entry to read.
1745 * \param elem_idx The element index within the entry.
1746 * \return NSERROR_OK on success or error code.
1747 */
1749 struct store_entry *bse,
1750 int elem_idx)
1751{
1752 block_index_t bf = (bse->elem[elem_idx].block >> BLOCK_ENTRY_COUNT) &
1753 ((1 << BLOCK_FILE_COUNT) - 1); /* block file block resides in */
1754 block_index_t bi = bse->elem[elem_idx].block & ((1 << BLOCK_ENTRY_COUNT) -1); /* block index in file */
1755 ssize_t rd;
1756 off_t offst;
1757
1758 /* ensure the block file fd is good */
1759 if (state->blocks[elem_idx][bf].fd == -1) {
1760 state->blocks[elem_idx][bf].fd = store_open(state, bf,
1761 elem_idx + ENTRY_ELEM_COUNT, O_CREAT | O_RDWR);
1762 if (state->blocks[elem_idx][bf].fd == -1) {
1763 NSLOG(netsurf, ERROR, "Open failed errno %d", errno);
1764 return NSERROR_SAVE_FAILED;
1765 }
1766
1767 /* flag that a block file has been opened */
1768 state->blocks_opened = true;
1769 }
1770
1771 offst = (unsigned int)bi << log2_block_size[elem_idx];
1772
1773 rd = nsu_pread(state->blocks[elem_idx][bf].fd,
1774 bse->elem[elem_idx].data,
1775 bse->elem[elem_idx].size,
1776 offst);
1777 if (rd != (ssize_t)bse->elem[elem_idx].size) {
1778 NSLOG(netsurf, ERROR,
1779 "Failed reading %"PRIssizet" of %"PRId32" bytes into %p from %"PRIsizet" block %"PRIu16" errno %d",
1780 rd,
1781 bse->elem[elem_idx].size,
1782 bse->elem[elem_idx].data,
1783 (size_t)offst,
1784 bse->elem[elem_idx].block,
1785 errno);
1786 return NSERROR_SAVE_FAILED;
1787 }
1788
1789 NSLOG(netsurf, DEEPDEBUG,
1790 "Read %"PRIssizet" bytes into %p from %"PRIsizet" block %d", rd,
1791 bse->elem[elem_idx].data, (size_t)offst,
1792 bse->elem[elem_idx].block);
1793
1794 return NSERROR_OK;
1795}
1796
1797/**
1798 * Read an element of an entry from an individual file in the backing storage.
1799 *
1800 * \param state The backing store state to use.
1801 * \param bse The entry to read.
1802 * \param elem_idx The element index within the entry.
1803 * \return NSERROR_OK on success or error code.
1804 */
1806 struct store_entry *bse,
1807 int elem_idx)
1808{
1809 int fd;
1810 ssize_t rd; /* return from read */
1811 int ret = NSERROR_OK;
1812 size_t tot = 0; /* total size */
1813
1814 /* separate file in backing store */
1815 fd = store_open(storestate, nsurl_hash(bse->url), elem_idx, O_RDONLY);
1816 if (fd < 0) {
1817 NSLOG(netsurf, ERROR, "Open failed %d errno %d", fd, errno);
1818 /** @todo should this invalidate the entry? */
1819 return NSERROR_NOT_FOUND;
1820 }
1821
1822 while (tot < bse->elem[elem_idx].size) {
1823 rd = read(fd,
1824 bse->elem[elem_idx].data + tot,
1825 bse->elem[elem_idx].size - tot);
1826 if (rd <= 0) {
1827 NSLOG(netsurf, ERROR,
1828 "read error returned %"PRIssizet" errno %d",
1829 rd,
1830 errno);
1831 ret = NSERROR_NOT_FOUND;
1832 break;
1833 }
1834 tot += rd;
1835 }
1836
1837 close(fd);
1838
1839 NSLOG(netsurf, DEEPDEBUG, "Read %"PRIsizet" bytes into %p", tot,
1840 bse->elem[elem_idx].data);
1841
1842 return ret;
1843}
1844
1845/**
1846 * Retrieve an object from the backing store.
1847 *
1848 * @param[in] url The url is used as the unique primary key for the data.
1849 * @param[in] bsflags The flags to control how the object is retrieved.
1850 * @param[out] data_out The objects data.
1851 * @param[out] datalen_out The length of the \a data retrieved.
1852 * @return NSERROR_OK on success or error code on failure.
1853 */
1854static nserror
1856 enum backing_store_flags bsflags,
1857 uint8_t **data_out,
1858 size_t *datalen_out)
1859{
1860 nserror ret;
1861 struct store_entry *bse;
1862 struct store_entry_element *elem;
1863 int elem_idx;
1864
1865 /* check backing store is initialised */
1866 if (storestate == NULL) {
1867 return NSERROR_INIT_FAILED;
1868 }
1869
1870 /* fetch store entry */
1871 ret = get_store_entry(storestate, url, &bse);
1872 if (ret != NSERROR_OK) {
1873 NSLOG(netsurf, DEBUG, "Entry for %s not found", nsurl_access(url));
1875 return ret;
1876 }
1878
1879 NSLOG(netsurf, DEBUG, "retrieving cache data for url:%s",
1880 nsurl_access(url));
1881
1882 /* calculate the entry element index */
1883 if ((bsflags & BACKING_STORE_META) != 0) {
1884 elem_idx = ENTRY_ELEM_META;
1885 } else {
1886 elem_idx = ENTRY_ELEM_DATA;
1887 }
1888 elem = &bse->elem[elem_idx];
1889
1890 /* if an allocation already exists return it */
1891 if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) {
1892 /* use the existing allocation and bump the ref count. */
1893 elem->ref++;
1894
1895 NSLOG(netsurf, DEEPDEBUG,
1896 "Using existing entry (%p) allocation %p refs:%d", bse,
1897 elem->data, elem->ref);
1898
1899 } else {
1900 /* allocate from the heap */
1901 elem->data = malloc(elem->size);
1902 if (elem->data == NULL) {
1903 NSLOG(netsurf, ERROR,
1904 "Failed to create new heap allocation");
1905 return NSERROR_NOMEM;
1906 }
1907 NSLOG(netsurf, DEEPDEBUG, "Created new heap allocation %p",
1908 elem->data);
1909
1910 /* mark the entry as having a valid heap allocation */
1911 elem->flags |= ENTRY_ELEM_FLAG_HEAP;
1912 elem->ref = 1;
1913
1914 /* fill the new block */
1915 if (elem->block != 0) {
1916 ret = store_read_block(storestate, bse, elem_idx);
1917 } else {
1918 ret = store_read_file(storestate, bse, elem_idx);
1919 }
1920 }
1921
1922 /* free the allocation if there is a read error */
1923 if (ret != NSERROR_OK) {
1924 entry_release_alloc(elem);
1925 } else {
1926 /* update stats and setup return pointers */
1927 storestate->hit_size += elem->size;
1928
1929 *data_out = elem->data;
1930 *datalen_out = elem->size;
1931 }
1932
1933 return ret;
1934}
1935
1936
1937/**
1938 * release a previously fetched or stored memory object.
1939 *
1940 * @param[in] url The url is used as the unique primary key to invalidate.
1941 * @param[in] bsflags The flags to control how the object data is released.
1942 * @return NSERROR_OK on success or error code on failure.
1943 */
1944static nserror release(nsurl *url, enum backing_store_flags bsflags)
1945{
1946 nserror ret;
1947 struct store_entry *bse;
1948 struct store_entry_element *elem;
1949
1950 /* check backing store is initialised */
1951 if (storestate == NULL) {
1952 return NSERROR_INIT_FAILED;
1953 }
1954
1955 ret = get_store_entry(storestate, url, &bse);
1956 if (ret != NSERROR_OK) {
1957 NSLOG(netsurf, WARNING, "entry not found");
1958 return ret;
1959 }
1960
1961 /* the entry element */
1962 if ((bsflags & BACKING_STORE_META) != 0) {
1963 elem = &bse->elem[ENTRY_ELEM_META];
1964 } else {
1965 elem = &bse->elem[ENTRY_ELEM_DATA];
1966 }
1967
1968 ret = entry_release_alloc(elem);
1969
1970 /* if the entry has previously been invalidated but had
1971 * allocation it must be invalidated fully now the allocation
1972 * has been released.
1973 */
1974 if ((ret == NSERROR_OK) &&
1975 ((bse->flags & ENTRY_FLAGS_INVALID) != 0)) {
1976 ret = invalidate_entry(storestate, bse);
1977 }
1978
1979 return ret;
1980}
1981
1982
1983/**
1984 * Invalidate a source object from the backing store.
1985 *
1986 * The entry (if present in the backing store) must no longer
1987 * be returned as a result to the fetch or meta operations.
1988 *
1989 * @param url The url is used as the unique primary key to invalidate.
1990 * @return NSERROR_OK on success or error code on failure.
1991 */
1992static nserror
1994{
1995 nserror ret;
1996 struct store_entry *bse;
1997
1998 /* check backing store is initialised */
1999 if (storestate == NULL) {
2000 return NSERROR_INIT_FAILED;
2001 }
2002
2003 ret = get_store_entry(storestate, url, &bse);
2004 if (ret != NSERROR_OK) {
2005 return ret;
2006 }
2007
2008 return invalidate_entry(storestate, bse);
2009}
2010
2011
2014 .finalise = finalise,
2015 .store = store,
2016 .fetch = fetch,
2017 .invalidate = invalidate,
2018 .release = release,
2019};
2020
Low-level source data cache backing store interface.
backing_store_flags
storage control flags
Definition: backing_store.h:30
@ BACKING_STORE_META
data is metadata
Definition: backing_store.h:34
nserror
Enumeration of error codes.
Definition: errors.h:29
@ NSERROR_PERMISSION
Permission error.
Definition: errors.h:58
@ NSERROR_SAVE_FAILED
Failed to save data.
Definition: errors.h:36
@ NSERROR_NOT_FOUND
Requested item not found.
Definition: errors.h:34
@ NSERROR_INIT_FAILED
Initialisation failed.
Definition: errors.h:38
@ NSERROR_UNKNOWN
Unknown error - DO NOT USE.
Definition: errors.h:31
@ NSERROR_NOMEM
Memory exhaustion.
Definition: errors.h:32
@ NSERROR_OK
No error.
Definition: errors.h:30
Utility routines to obtain paths to file resources.
static void * entries_hashmap_value_alloc(void *key)
store_entry_flags
@ ENTRY_FLAGS_NONE
entry is normal
@ ENTRY_FLAGS_INVALID
entry has been invalidated but something still holding a reference
static nserror initialise(const struct llcache_store_parameters *parameters)
Initialise the backing store.
#define BLOCK_META_SIZE
log2 size of metadata blocks (8k)
#define CONTROL_VERSION
Backing store file format version.
static nserror store_read_block(struct store_state *state, struct store_entry *bse, int elem_idx)
Read an element of an entry from a small block file in the backing storage.
#define BLOCK_USE_MAP_SIZE
length in bytes of a block files use map
uint32_t entry_ident_t
The type used as a binary identifier for each entry derived from the URL.
store_entry_elem_flags
flags that indicate what additional information is contained within an entry element.
@ ENTRY_ELEM_FLAG_MMAP
entry data allocation is mmaped
@ ENTRY_ELEM_FLAG_NONE
store not managing any allocation on entry
@ ENTRY_ELEM_FLAG_SMALL
entry data allocation is in small object pool
@ ENTRY_ELEM_FLAG_HEAP
entry data allocation is on heap
static nserror unlink_entries(struct store_state *state)
Unlink entries file.
static nserror set_store_entry(struct store_state *state, nsurl *url, int elem_idx, uint8_t *data, const size_t datalen, struct store_entry **bse)
Set a backing store entry in the entry table from a url.
static nserror invalidate_entry(struct store_state *state, struct store_entry *bse)
Remove the entry and files associated with an identifier.
uint16_t block_index_t
The type used to store block file index values.
static nserror release(nsurl *url, enum backing_store_flags bsflags)
release a previously fetched or stored memory object.
static nserror store(nsurl *url, enum backing_store_flags bsflags, uint8_t *data, const size_t datalen)
Place an object in the backing store.
#define ENTRIES_FNAME
Filename of serialised entries.
static nserror write_entry(struct store_entry *ent, int fd)
Write a single store entry to disk.
static nserror store_read_file(struct store_state *state, struct store_entry *bse, int elem_idx)
Read an element of an entry from an individual file in the backing storage.
#define BLOCK_DATA_SIZE
log2 size of data blocks (8k)
static nserror read_blocks(struct store_state *state)
Read block file usage bitmaps.
static nserror store_write_block(struct store_state *state, struct store_entry *bse, int elem_idx)
Write an element of an entry to backing storage in a small block file.
static bool entry_eviction_iterator_cb(void *key, void *value, void *ctx)
Iterator for gathering entries to compute eviction order.
static nserror get_store_entry(struct store_state *state, nsurl *url, struct store_entry **bse)
Lookup a backing store entry in the entry table from a url.
static block_index_t alloc_block(struct store_state *state, int elem_idx)
Find next available small block.
static nserror read_entries(struct store_state *state)
Read description entries into memory.
static const unsigned int log2_block_size[ENTRY_ELEM_COUNT]
log2 of block size.
static int compar(const void *va, const void *vb)
Quick sort comparison.
static void control_maintenance(void *s)
maintenance of control structures.
static nserror read_control(struct store_state *state)
Read and parse the control file.
static nserror write_cache_tag(struct store_state *state)
Write the cache tag file.
#define BLOCK_ENTRY_COUNT
log2 number of entries per block file(1024)
#define BLOCK_FILE_COUNT
log2 number of data block files
static bool entries_hashmap_key_eq(void *key1, void *key2)
struct store_state * storestate
Global storage state.
static bool write_entry_iterator(void *key, void *value, void *ctx)
Callback for iterating the entries hashmap.
static nserror entry_release_alloc(struct store_entry_element *elem)
release any allocation for an entry
static char * store_fname(struct store_state *state, entry_ident_t ident, int elem_idx)
Generate a filename for an object.
#define BLOCKS_FNAME
Filename of block file index.
store_entry_elem_idx
Entry element index values.
@ ENTRY_ELEM_META
entry element is metadata
@ ENTRY_ELEM_COUNT
count of elements on an entry
@ ENTRY_ELEM_DATA
entry element is data
static nserror finalise(void)
Finalise the backing store.
static hashmap_parameters_t entries_hashmap_parameters
static nserror store_evict(struct store_state *state)
Evict entries from backing store as per configuration.
#define CONTROL_MAINT_TIME
Number of milliseconds after a update before control data maintenance is performed.
static struct gui_llcache_table llcache_table
static nserror store_write_file(struct store_state *state, struct store_entry *bse, int elem_idx)
Write an element of an entry to backing storage as an individual file.
static nserror write_blocks(struct store_state *state)
Write block file use map to file.
static nserror set_block_extents(struct store_state *state)
Ensures block files are of the correct extent.
static nserror fetch(nsurl *url, enum backing_store_flags bsflags, uint8_t **data_out, size_t *datalen_out)
Retrieve an object from the backing store.
static nserror write_control(struct store_state *state)
Write the control file for the current state.
static void entries_hashmap_value_destroy(void *value)
struct gui_llcache_table * filesystem_llcache_table
static nserror write_entries(struct store_state *state)
Write filesystem entries to file.
static nserror invalidate_element(struct store_state *state, struct store_entry *bse, int elem_idx)
invalidate an element of an entry
static nserror invalidate(nsurl *url)
Invalidate a source object from the backing store.
static int store_open(struct store_state *state, entry_ident_t ident, int elem_idx, int openflags)
Open a file using a store ident.
struct netsurf_table * guit
The global interface table.
Definition: gui_factory.c:49
Interface to core interface table.
bool hashmap_remove(hashmap_t *hashmap, void *key)
Remove an entry from the hashmap.
Definition: hashmap.c:206
hashmap_t * hashmap_create(hashmap_parameters_t *params)
Create a hashmap.
Definition: hashmap.c:67
size_t hashmap_count(hashmap_t *hashmap)
Get the number of entries in this map.
Definition: hashmap.c:252
void * hashmap_lookup(hashmap_t *hashmap, void *key)
Look up a key in a hashmap.
Definition: hashmap.c:113
void * hashmap_insert(hashmap_t *hashmap, void *key)
Create an entry in a hashmap.
Definition: hashmap.c:131
bool hashmap_iterate(hashmap_t *hashmap, hashmap_iteration_cb_t cb, void *ctx)
Iterate the hashmap.
Definition: hashmap.c:233
void hashmap_destroy(hashmap_t *hashmap)
Destroy a hashmap.
Definition: hashmap.c:91
void(* hashmap_key_destroy_t)(void *)
Key destructor function type.
Definition: hashmap.h:42
void *(* hashmap_key_clone_t)(void *)
Key cloning function type.
Definition: hashmap.h:37
uint32_t(* hashmap_key_hash_t)(void *)
Key hashing function type.
Definition: hashmap.h:47
Interface to platform-specific miscellaneous browser operation table.
Netsurf additional integer type formatting macros.
#define PRIssizet
c99 standard printf formatting for ssize_t type
Definition: inttypes.h:60
#define PRIsizet
c99 standard printf formatting for size_t type
Definition: inttypes.h:53
#define PRIu64
Definition: inttypes.h:38
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
const char * messages_get_errorcode(nserror code)
lookup of a message by errorcode from the standard Messages hash.
Definition: messages.c:248
Localised message support (interface).
NetSurf URL handling (interface).
bool nsurl_compare(const nsurl *url1, const nsurl *url2, nsurl_component parts)
Compare two URLs.
nserror nsurl_create(const char *const url_s, nsurl **url)
Create a NetSurf URL object from a URL string.
void nsurl_unref(nsurl *url)
Drop a reference to a NetSurf URL object.
uint32_t nsurl_hash(const nsurl *url)
Get a URL's hash value.
const char * nsurl_access(const nsurl *url)
Access a NetSurf URL object as a string.
nsurl * nsurl_ref(nsurl *url)
Increment the reference count to a NetSurf URL object.
@ NSURL_COMPLETE
Definition: nsurl.h:54
struct nsurl nsurl
NetSurf URL object.
Definition: nsurl.h:31
Interface to utility string handling.
Small block file.
uint8_t use_map[BLOCK_USE_MAP_SIZE]
map of used and unused entries within the block file
int fd
file descriptor of the block file
struct store_entry ** elist
low level cache backing store operation table
Definition: backing_store.h:44
nserror(* initialise)(const struct llcache_store_parameters *parameters)
Initialise the backing store.
Definition: backing_store.h:51
nserror(* schedule)(int t, void(*callback)(void *p), void *p)
Schedule a callback.
Definition: misc.h:58
Parameters for hashmaps.
Definition: hashmap.h:77
hashmap_key_clone_t key_clone
A function which when called will clone a key and give ownership of the returned object to the hashma...
Definition: hashmap.h:82
The content of a hashmap.
Definition: hashmap.c:43
Parameters to configure the low level cache backing store.
Definition: llcache.h:119
size_t hysteresis
The hysteresis around the target size.
Definition: llcache.h:123
size_t limit
The backing store upper bound target size.
Definition: llcache.h:122
const char * path
The path to the backing store.
Definition: llcache.h:120
struct gui_misc_table * misc
Browser table.
Definition: gui_table.h:57
Backing store entry element.
uint8_t flags
entry flags
block_index_t block
small object data block
uint8_t * data
data allocated
uint8_t ref
element data reference count
uint32_t size
size of entry element on disc
Backing store object index entry.
uint16_t use_count
number of times this entry has been accessed
uint8_t flags
entry flags
struct store_entry_element elem[ENTRY_ELEM_COUNT]
Entry element (data or meta) specific information.
nsurl * url
The URL for this entry.
int64_t last_used
UNIX time the entry was last used.
Parameters controlling the backing store.
bool blocks_opened
flag indicating if a block file has been opened for update since maintenance was previously done.
uint64_t total_alloc
total size of all allocated storage.
bool blocks_dirty
flag indicating if the block file use maps have been made persistent since they were last changed.
uint64_t hit_size
size of storage served
hashmap_t * entries
The cache object hash.
char * path
The path to the backing store.
struct block_file blocks[ENTRY_ELEM_COUNT][BLOCK_FILE_COUNT]
small block indexes
size_t hysteresis
The hysteresis around the target size.
size_t limit
The backing store upper bound target size.
bool entries_dirty
flag indicating if the entries have been made persistent since they were last changed.
size_t hit_count
number of cache hits
size_t miss_count
number of cache misses
int fd
size_t written
Interface to time operations.
nserror netsurf_mkdir_all(const char *fname)
Ensure that all directory elements needed to store a filename exist.
Definition: file.c:313
nserror netsurf_mkpath(char **str, size_t *size, size_t nelm,...)
Generate a path from one or more component elemnts.
Definition: file.c:288
nserror netsurf_recursive_rm(const char *path)
Recursively remove a directory.
Definition: file.c:320
Default operations table for files.