diff --git a/content/backing_store.h b/content/backing_store.h index 2b986cf52..2a90c8762 100644 --- a/content/backing_store.h +++ b/content/backing_store.h @@ -31,8 +31,6 @@ enum backing_store_flags { BACKING_STORE_NONE = 0, /** data is metadata */ BACKING_STORE_META = 1, - /** backing store will handle allocation. */ - BACKING_STORE_ALLOC = 2 }; /** low level cache backing store operation table @@ -61,12 +59,13 @@ struct gui_llcache_table { * Place an object in the backing store. * * The object is placed in the persistent store and may be - * retrieved. If the BACKING_STORE_ALLOC flag is used the - * backing store will take a reference to the passed data, - * subsequently the caller should explicitly release the - * allocation using the release method and not free the data - * itself. An additional effect of this is that the persistent - * storage may not have been completely written on return. + * retrieved with the fetch method. + * The backing store will take a reference to the + * passed data, subsequently the caller should explicitly + * release the allocation using the release method and not + * free the data itself. + * The caller may not assume that the persistent storage has + * been completely written on return. * * @param[in] url The url is used as the unique primary key for the data. * @param[in] flags The flags to control how the obejct is stored. @@ -80,9 +79,13 @@ struct gui_llcache_table { /** * Retrive an object from the backing store. * - * If the BACKING_STORE_ALLOC flag is set the returned memory - * is managed by the backing store and should be freed by - * calling the release method. + * The caller may provide a buffer in \a data and a buffer + * length in \a datalen. Alternatively the backing store will + * allocate its own buffer if \a data is NULL, this memory is + * managed by the backing store. + * The caller must assume nothing about the backing store + * allocated buffers and the storage and *must* be freed by + * calling the release method. * * @param[in] url The url is used as the unique primary key for the data. * @param[in] flags The flags to control how the object is retrived. @@ -90,9 +93,18 @@ struct gui_llcache_table { * @param[in,out] datalen The length of the \a data retrieved. * @return NSERROR_OK on success or error code on faliure. */ - nserror (*fetch)(struct nsurl *url, enum backing_store_flags *flags, + nserror (*fetch)(struct nsurl *url, enum backing_store_flags flags, uint8_t **data, size_t *datalen); + /** + * release a previously fetched or stored memory object. + * + * @param url The url is used as the unique primary key to invalidate. + * @param[in] flags The flags to control how the object data is released. + * @return NSERROR_OK on success or error code on faliure. + */ + nserror (*release)(struct nsurl *url, enum backing_store_flags flags); + /** * Invalidate a source object from the backing store. * @@ -106,19 +118,6 @@ struct gui_llcache_table { */ nserror (*invalidate)(struct nsurl *url); - /** - * release a previously fetched or stored memory object. - * - * if the BACKING_STORE_ALLOC flag was used with the fetch or - * store operation for this url the returned storage is - * unreferenced. When the reference count drops to zero the - * storage is released. - * - * @param url The url is used as the unique primary key to invalidate. - * @param[in] flags The flags to control how the object data is released. - * @return NSERROR_OK on success or error code on faliure. - */ - nserror (*release)(struct nsurl *url, enum backing_store_flags flags); }; extern struct gui_llcache_table* null_llcache_table; diff --git a/content/fs_backing_store.c b/content/fs_backing_store.c index fdac343ff..d743139ca 100644 --- a/content/fs_backing_store.c +++ b/content/fs_backing_store.c @@ -56,7 +56,7 @@ #define DEFAULT_ENTRY_SIZE 16 /** Backing store file format version */ -#define CONTROL_VERSION 110 +#define CONTROL_VERSION 120 /** Number of milliseconds after a update before control data maintinance is performed */ #define CONTROL_MAINT_TIME 10000 @@ -73,21 +73,6 @@ /** Filename of serialised entries */ #define ENTRIES_FNAME "entries" -/** - * flags that indicate what additional information is contained within - * an entry. - */ -enum store_entry_flags { - /** entry is not managing the allocation */ - STORE_ENTRY_FLAG_NONE = 0, - /** entry allocation is on heap */ - STORE_ENTRY_FLAG_HEAP = 1, - /** entry allocation is mmaped */ - STORE_ENTRY_FLAG_MMAP = 2, - /** entry allocation is in small object pool */ - STORE_ENTRY_FLAG_SMALL = 4, -}; - /** * The type used to store index values refering to store entries. Care * must be taken with this type as it is used to build address to @@ -103,20 +88,70 @@ typedef uint16_t entry_index_t; */ typedef uint32_t entry_ident_t; +/** + * Entry extension index values. + */ +enum store_entry_elem_idx { + ENTRY_ELEM_DATA = 0, /**< entry element is data */ + ENTRY_ELEM_META = 1, /**< entry element is metadata */ + ENTRY_ELEM_COUNT = 2, /**< count of elements on an entry */ +}; + +/** + * flags that indicate what additional information is contained within + * an entry element. + */ +enum store_entry_elem_flags { + /** store not managing any allocation on entry */ + ENTRY_ELEM_FLAG_NONE = 0, + /** entry data allocation is on heap */ + ENTRY_ELEM_FLAG_HEAP = 0x1, + /** entry data allocation is mmaped */ + ENTRY_ELEM_FLAG_MMAP = 0x2, + /** entry data allocation is in small object pool */ + ENTRY_ELEM_FLAG_SMALL = 0x4, +}; + + +/** + * Backing store entry element. + * + * @note Order is important to avoid excessive structure packing overhead. + */ +struct store_entry_element { + uint32_t size; /**< size of entry element on disc */ + union { + struct { + uint8_t* data; /**< data allocated on heap */ + uint8_t ref; /**< reference count */ + } heap; + struct { + uint8_t* data; /**< data is from an mmapping */ + uint8_t ref; /**< reference count */ + } map; + struct { + uint16_t block; /**< small object data block */ + } small; + }; + uint8_t flags; /* extension flags */ +}; + /** * Backing store object index entry. * - * @note Order is important to avoid structure packing overhead. + * An entry in the backing store contains two elements for the actual + * data and the metadata. The two elements are treated identically for + * storage lifetime but as a collective whole for expiration and + * indexing. + * + * @note Order is important to avoid excessive structure packing overhead. */ struct store_entry { int64_t last_used; /**< unix time the entry was last used */ entry_ident_t ident; /**< entry identifier */ - uint32_t data_alloc; /**< currently allocated size of data on disc */ - uint32_t meta_alloc; /**< currently allocated size of metadata on disc */ uint16_t use_count; /**< number of times this entry has been accessed */ - uint16_t flags; /**< entry flags (unused) */ - uint16_t data_block; /**< small object data block entry (unused) */ - uint16_t meta_block; /**< small object meta block entry (unused) */ + /** Entry element (data or meta) specific information */ + struct store_entry_element elem[ENTRY_ELEM_COUNT]; }; /** @@ -195,6 +230,18 @@ remove_store_entry(struct store_state *state, return NSERROR_NOT_FOUND; } + /* check if the entry has storage already allocated */ + if (((state->entries[sei].elem[ENTRY_ELEM_DATA].flags & + (ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0) || + ((state->entries[sei].elem[ENTRY_ELEM_META].flags & + (ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0)) { + /* this entry cannot be removed as it has associated + * allocation. + */ + LOG(("attempt to remove entry with in use data")); + return NSERROR_PERMISSION; + } + /* sei is entry to be removed, we swap it to the end of the * table so there are no gaps and the returned entry is held * in storage with reasonable lifetime. @@ -204,8 +251,8 @@ remove_store_entry(struct store_state *state, BS_ENTRY_INDEX(ident, state) = 0; /* global allocation accounting */ - state->total_alloc -= state->entries[sei].data_alloc; - state->total_alloc -= state->entries[sei].meta_alloc; + state->total_alloc -= state->entries[sei].elem[ENTRY_ELEM_DATA].size; + state->total_alloc -= state->entries[sei].elem[ENTRY_ELEM_META].size; state->last_entry--; @@ -396,6 +443,26 @@ static int compar(const void *va, const void *vb) const struct store_entry *a = &BS_ENTRY(*(entry_ident_t *)va, storestate); const struct store_entry *b = &BS_ENTRY(*(entry_ident_t *)vb, storestate); + /* consider the allocation flags - if an entry has an + * allocation it is considered more valuble as it cannot be + * freed. + */ + if ((a->elem[ENTRY_ELEM_DATA].flags == ENTRY_ELEM_FLAG_NONE) && + (b->elem[ENTRY_ELEM_DATA].flags != ENTRY_ELEM_FLAG_NONE)) { + return -1; + } else if ((a->elem[ENTRY_ELEM_DATA].flags != ENTRY_ELEM_FLAG_NONE) && + (b->elem[ENTRY_ELEM_DATA].flags == ENTRY_ELEM_FLAG_NONE)) { + return 1; + } + + if ((a->elem[ENTRY_ELEM_META].flags == ENTRY_ELEM_FLAG_NONE) && + (b->elem[ENTRY_ELEM_META].flags != ENTRY_ELEM_FLAG_NONE)) { + return -1; + } else if ((a->elem[ENTRY_ELEM_META].flags != ENTRY_ELEM_FLAG_NONE) && + (b->elem[ENTRY_ELEM_META].flags == ENTRY_ELEM_FLAG_NONE)) { + return 1; + } + if (a->use_count < b->use_count) { return -1; } else if (a->use_count > b->use_count) { @@ -463,8 +530,8 @@ static nserror store_evict(struct store_state *state) removed = 0; for (ent = 0; ent < ent_count; ent++) { - removed += BS_ENTRY(elist[ent], state).data_alloc; - removed += BS_ENTRY(elist[ent], state).meta_alloc; + removed += BS_ENTRY(elist[ent], state).elem[ENTRY_ELEM_DATA].size; + removed += BS_ENTRY(elist[ent], state).elem[ENTRY_ELEM_META].size; ret = unlink_ident(state, elist[ent]); if (ret != NSERROR_OK) { @@ -591,6 +658,7 @@ get_store_entry(struct store_state *state, nsurl *url, struct store_entry **bse) sei = BS_ENTRY_INDEX(ident, state); if (sei == 0) { + LOG(("Failed to find ident 0x%x in index", ident)); return NSERROR_NOT_FOUND; } @@ -639,7 +707,7 @@ set_store_entry(struct store_state *state, entry_index_t sei; /* store entry index */ struct store_entry *se; nserror ret; - bool isrep; /* is the store repalcing an existing entry or not */ + struct store_entry_element *elem; LOG(("url:%s", nsurl_access(url))); @@ -654,52 +722,59 @@ set_store_entry(struct store_state *state, /* use the url hash as the entry identifier */ ident = nsurl_hash(url); + /* get the entry index from the ident */ sei = BS_ENTRY_INDEX(ident, state); - - /** @todo Should this deal with cache eviction? */ - if (sei == 0) { /* allocating the next available entry */ sei = state->last_entry; state->last_entry++; BS_ENTRY_INDEX(ident, state) = sei; - isrep = false; - } else { - /* updating or replacing existing entry */ - /** @todo should we be checking the entry ident - * matches the url. Thats a collision in the address - * mapping right? and is it important? - */ - isrep = true; + + /* clear the new entry */ + memset(&state->entries[sei], 0, sizeof(struct store_entry)); } + /** @todo should we be checking the entry ident matches the + * url. Thats a collision in the address mapping right? and is + * it important? + */ + + /* the entry */ se = &state->entries[sei]; + /* the entry element */ + if ((flags & BACKING_STORE_META) != 0) { + elem = &se->elem[ENTRY_ELEM_META]; + } else { + elem = &se->elem[ENTRY_ELEM_DATA]; + } + + /* check if the element has storage already allocated */ + if ((elem->flags & (ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0) { + /* this entry cannot be removed as it has associated + * allocation. + */ + LOG(("attempt to remove entry with in use data")); + return NSERROR_PERMISSION; + } + + /* set the common entry data */ se->ident = ident; - se->flags = STORE_ENTRY_FLAG_NONE; se->use_count = 1; se->last_used = time(NULL); - /* account for allocation */ - if ((flags & BACKING_STORE_META) != 0) { - if (isrep) { - state->total_alloc -= se->meta_alloc; - } else { - se->data_alloc = 0; - } - se->meta_alloc = datalen; - } else { - if (isrep) { - state->total_alloc -= se->data_alloc; - } else { - se->meta_alloc = 0; - } - se->data_alloc = datalen; - } - state->total_alloc += datalen; + /* store the data in the element */ + elem->flags |= ENTRY_ELEM_FLAG_HEAP; + elem->heap.data = data; + elem->heap.ref = 1; + /* account for size of entry element */ + state->total_alloc -= elem->size; + elem->size = datalen; + state->total_alloc += elem->size; + + /* ensure control maintinance scheduled. */ state->entries_dirty = true; - guit->browser->schedule(CONTROL_MAINT_TIME, control_maintinance, state); *bse = se; @@ -793,8 +868,8 @@ build_entrymap(struct store_state *state) BS_ENTRY_INDEX(state->entries[eloop].ident, state) = eloop; /* account for the storage space */ - state->total_alloc += state->entries[eloop].data_alloc + - state->entries[eloop].meta_alloc; + state->total_alloc += state->entries[eloop].elem[ENTRY_ELEM_DATA].size; + state->total_alloc += state->entries[eloop].elem[ENTRY_ELEM_META].size; } return NSERROR_OK; @@ -1113,7 +1188,7 @@ initialise(const struct llcache_store_parameters *parameters) LOG(("FS backing store init successful")); LOG(("path:%s limit:%d hyst:%d addr:%d entries:%d", newstate->path, newstate->limit, newstate->hysteresis, newstate->ident_bits, newstate->entry_bits)); - LOG(("Using %d/%d", newstate->total_alloc, newstate->limit)); + LOG(("Using %lld/%lld", newstate->total_alloc, newstate->limit)); return NSERROR_OK; } @@ -1151,6 +1226,8 @@ finalise(void) /** * Place an object in the backing store. * + * takes ownership of the heap block passed in. + * * @param url The url is used as the unique primary key for the data. * @param flags The flags to control how the object is stored. * @param data The objects source data. @@ -1200,6 +1277,22 @@ store(nsurl *url, return NSERROR_OK; } +/** + * release any allocation for an entry + */ +static nserror entry_release_alloc(struct store_entry_element *elem) +{ + if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) { + elem->heap.ref--; + if (elem->heap.ref == 0) { + LOG(("freeing %p", elem->heap.data)); + free(elem->heap.data); + elem->flags &= ~ENTRY_ELEM_FLAG_HEAP; + } + } + return NSERROR_OK; +} + /** * Retrive an object from the backing store. * @@ -1211,12 +1304,13 @@ store(nsurl *url, */ static nserror fetch(nsurl *url, - enum backing_store_flags *flags, + enum backing_store_flags bsflags, uint8_t **data_out, size_t *datalen_out) { nserror ret; struct store_entry *bse; + struct store_entry_element *elem; uint8_t *data; size_t datalen; int fd; @@ -1237,36 +1331,53 @@ fetch(nsurl *url, LOG(("retriving cache file for url:%s", nsurl_access(url))); - fd = store_open(storestate, bse->ident, *flags, O_RDONLY); + fd = store_open(storestate, bse->ident, bsflags, O_RDONLY); if (fd < 0) { LOG(("Open failed")); /** @todo should this invalidate the entry? */ return NSERROR_NOT_FOUND; } + /* the entry element */ + if ((bsflags & BACKING_STORE_META) != 0) { + elem = &bse->elem[ENTRY_ELEM_META]; + } else { + elem = &bse->elem[ENTRY_ELEM_DATA]; + } + data = *data_out; datalen = *datalen_out; + /** @todo should this check datalen is sufficient? */ /* need to deal with buffers */ if (data == NULL) { - if (datalen == 0) { - /* caller did not know the files length */ - if (((*flags) & BACKING_STORE_META) != 0) { - datalen = bse->meta_alloc; - } else { - datalen = bse->data_alloc; + if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) { + /* a heap allocation already exists. Return + * that allocation and bump our ref count. + */ + data = elem->heap.data; + elem->heap.ref++; + datalen = elem->size; + LOG(("Using existing heap allocation %p", elem->heap.data)); + } else { + datalen = elem->size; + data = malloc(elem->size); + if (data == NULL) { + close(fd); + return NSERROR_NOMEM; } - } - data = malloc(datalen); - if (data == NULL) { - close(fd); - return NSERROR_NOMEM; + /* store allocated buffer so track ownership */ + elem->flags |= ENTRY_ELEM_FLAG_HEAP; + elem->heap.data = data; + elem->heap.ref = 1; + LOG(("Creating new heap allocation %p", elem->heap.data)); } + } else if (datalen == 0) { + /* caller provided a buffer but no length bad parameter */ + return NSERROR_BAD_PARAMETER; } - /** @todo should this check datalen is sufficient */ - LOG(("Reading %d bytes into %p from file", datalen, data)); /** @todo this read should be an a loop */ @@ -1275,7 +1386,7 @@ fetch(nsurl *url, LOG(("read returned %d", rd)); close(fd); if ((*data_out) == NULL) { - free(data); + entry_release_alloc(elem); } return NSERROR_NOT_FOUND; } @@ -1291,6 +1402,41 @@ fetch(nsurl *url, } +/** + * release a previously fetched or stored memory object. + * + * @param url The url is used as the unique primary key to invalidate. + * @param[in] flags The flags to control how the object data is released. + * @return NSERROR_OK on success or error code on faliure. + */ +static nserror release(nsurl *url, enum backing_store_flags bsflags) +{ + nserror ret; + struct store_entry *bse; + struct store_entry_element *elem; + + /* check backing store is initialised */ + if (storestate == NULL) { + return NSERROR_INIT_FAILED; + } + + ret = get_store_entry(storestate, url, &bse); + if (ret != NSERROR_OK) { + LOG(("entry not found")); + return ret; + } + + /* the entry element */ + if ((bsflags & BACKING_STORE_META) != 0) { + elem = &bse->elem[ENTRY_ELEM_META]; + } else { + elem = &bse->elem[ENTRY_ELEM_DATA]; + } + + return entry_release_alloc(elem); +} + + /** * Invalidate a source object from the backing store. * @@ -1314,23 +1460,6 @@ invalidate(nsurl *url) } -/** - * release a previously fetched or stored memory object. - * - * if the BACKING_STORE_ALLOC flag was used with the fetch or - * store operation for this url the returned storage is - * unreferenced. When the reference count drops to zero the - * storage is released. - * - * @param url The url is used as the unique primary key to invalidate. - * @param[in] flags The flags to control how the object data is released. - * @return NSERROR_OK on success or error code on faliure. - */ -static nserror release(nsurl *url, enum backing_store_flags flags) -{ - return NSERROR_NOT_FOUND; -} - static struct gui_llcache_table llcache_table = { .initialise = initialise, .finalise = finalise, diff --git a/content/llcache.c b/content/llcache.c index c076d5dd4..3fed59d9a 100644 --- a/content/llcache.c +++ b/content/llcache.c @@ -150,7 +150,6 @@ typedef struct { /** Current status of objects data */ typedef enum { LLCACHE_STATE_RAM = 0, /**< source data is stored in RAM only */ - LLCACHE_STATE_MMAP, /**< source data is mmaped (implies on disc too) */ LLCACHE_STATE_DISC, /**< source data is stored on disc */ } llcache_store_state; @@ -1076,8 +1075,6 @@ llcache_object_remove_from_list(llcache_object *object, llcache_object **list) */ static nserror llcache_persist_retrieve(llcache_object *object) { - enum backing_store_flags flags = BACKING_STORE_NONE; - /* ensure the source data is present if necessary */ if ((object->source_data != NULL) || (object->store_state != LLCACHE_STATE_DISC)) { @@ -1089,7 +1086,7 @@ static nserror llcache_persist_retrieve(llcache_object *object) /* Source data for the object may be in the persiatant store */ return guit->llcache->fetch(object->url, - &flags, + BACKING_STORE_NONE, &object->source_data, &object->source_len); } @@ -1246,13 +1243,12 @@ llcache_process_metadata(llcache_object *object) int lnsize; size_t num_headers; size_t hloop; - enum backing_store_flags flags = BACKING_STORE_META; LOG(("Retriving metadata")); /* attempt to retrieve object metadata from the backing store */ res = guit->llcache->fetch(object->url, - &flags, + BACKING_STORE_META, &metadata, &metadatalen); if (res != NSERROR_OK) { @@ -1273,14 +1269,14 @@ llcache_process_metadata(llcache_object *object) res = nsurl_create(ln, &metadataurl); if (res != NSERROR_OK) { - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); return res; } if (nsurl_compare(object->url, metadataurl, NSURL_COMPLETE) != true) { /* backing store returned the wrong object for the * request. This may occour if the backing store had - * a collision in its stoage method. We cope with this + * a collision in its storage method. We cope with this * by simply skipping caching of this object. */ @@ -1290,7 +1286,7 @@ llcache_process_metadata(llcache_object *object) nsurl_unref(metadataurl); - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); return NSERROR_BAD_URL; } @@ -1349,12 +1345,12 @@ llcache_process_metadata(llcache_object *object) res = llcache_fetch_process_header(object, (uint8_t *)ln, lnsize); if (res != NSERROR_OK) { - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); return res; } } - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); /* object stored in backing store */ object->store_state = LLCACHE_STATE_DISC; @@ -1363,7 +1359,8 @@ llcache_process_metadata(llcache_object *object) format_error: LOG(("metadata error on line %d\n", line)); - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); + return NSERROR_INVALID; } @@ -1454,7 +1451,7 @@ llcache_object_retrieve_from_cache(nsurl *url, * pull from persistant store. */ if (newest == NULL) { - LLCACHE_LOG(("No viable object found in cache")); + LLCACHE_LOG(("No viable object found in llcache")); error = llcache_object_new(url, &obj); if (error != NSERROR_OK) @@ -1498,7 +1495,7 @@ llcache_object_retrieve_from_cache(nsurl *url, /* retrival of source data from persistant store * failed, destroy cache object and fall though to - * cache miss to re-retch + * cache miss to re-fetch */ LLCACHE_LOG(("Persistant retrival failed for %p", newest)); @@ -2255,7 +2252,7 @@ write_backing_store(struct llcache_object *object, size_t *written_out) BACKING_STORE_META, metadata, metadatasize); - free(metadata); + guit->llcache->release(object->url, BACKING_STORE_META); if (ret != NSERROR_OK) { /* There has been an error putting the metadata in the * backing store. Ensure the data object is invalidated. @@ -2903,7 +2900,7 @@ void llcache_clean(bool purge) (object->fetch.fetch == NULL) && (object->fetch.outstanding_query == false) && (remaining_lifetime <= 0)) { - /* object is stale */ + /* object is stale */ LLCACHE_LOG(("discarding stale cacheable object with no users or pending fetches (%p) %s", object, nsurl_access(object->url))); llcache_object_remove_from_list(object, @@ -2942,7 +2939,8 @@ void llcache_clean(bool purge) (object->fetch.fetch == NULL) && (object->fetch.outstanding_query == false) && (object->store_state == LLCACHE_STATE_DISC)) { - free(object->source_data); + guit->llcache->release(object->url, BACKING_STORE_NONE); + object->source_data = NULL; llcache_size -= object->source_len; @@ -2955,7 +2953,7 @@ void llcache_clean(bool purge) /* Fresh cacheable objects with no users, no pending fetches * and pushed to persistant store while the cache exceeds - * the configured size. Efectively just the object metadata. + * the configured size. Efectively just the llcache object metadata. */ for (object = llcache->cached_objects; ((limit < llcache_size) && (object != NULL)); @@ -2983,7 +2981,7 @@ void llcache_clean(bool purge) } /* Fresh cacheable objects with no users or pending fetches - * while the cache exceeds the configured size. These are the + * while the cache exceeds the configured size. These are the * most valuble objects as replacing them is a full network * fetch */ diff --git a/content/no_backing_store.c b/content/no_backing_store.c index 917d68a11..527518513 100644 --- a/content/no_backing_store.c +++ b/content/no_backing_store.c @@ -45,7 +45,7 @@ static nserror store(nsurl *url, } static nserror fetch(nsurl *url, - enum backing_store_flags *flags, + enum backing_store_flags flags, uint8_t **data_out, size_t *datalen_out) {