| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* Datafile content management |
| * |
| * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| */ |
| |
| #include <linux/mount.h> |
| #include <linux/slab.h> |
| #include <linux/file.h> |
| #include <linux/swap.h> |
| #include <linux/xattr.h> |
| #include "internal.h" |
| |
| static const char cachefiles_xattr_content_map[] = |
| XATTR_USER_PREFIX "CacheFiles.content"; |
| |
| /* |
| * Determine the map size for a granulated object. |
| * |
| * There's one bit per granule. We size it in terms of 8-byte chunks, where a |
| * 64-bit span * 256KiB bytes granules covers 16MiB of file space. At that, |
| * 512B will cover 1GiB. |
| */ |
| static size_t cachefiles_map_size(loff_t i_size) |
| { |
| loff_t size; |
| size_t granules, bits, bytes, map_size; |
| |
| if (i_size <= CACHEFILES_GRAN_SIZE * 64) |
| return 8; |
| |
| size = i_size + CACHEFILES_GRAN_SIZE - 1; |
| granules = size / CACHEFILES_GRAN_SIZE; |
| bits = granules + (64 - 1); |
| bits &= ~(64 - 1); |
| bytes = bits / 8; |
| map_size = roundup_pow_of_two(bytes); |
| _leave(" = %zx [i=%llx g=%zu b=%zu]", map_size, i_size, granules, bits); |
| return map_size; |
| } |
| |
| static bool cachefiles_granule_is_present(struct cachefiles_object *object, |
| size_t granule) |
| { |
| bool res; |
| |
| if (granule / 8 >= object->content_map_size) |
| return false; |
| read_lock_bh(&object->content_map_lock); |
| res = test_bit_le(granule, object->content_map); |
| read_unlock_bh(&object->content_map_lock); |
| return res; |
| } |
| |
| /* |
| * Shape the extent of a single-chunk data object. |
| */ |
| static unsigned int cachefiles_shape_single(struct fscache_object *obj, |
| struct fscache_extent *extent, |
| loff_t i_size, bool for_write) |
| { |
| struct cachefiles_object *object = |
| container_of(obj, struct cachefiles_object, fscache); |
| unsigned int ret; |
| pgoff_t eof; |
| |
| _enter("{%lx,%lx,%lx},%llx,%d", |
| extent->start, extent->block_end, extent->limit, |
| i_size, for_write); |
| |
| extent->dio_block_size = CACHEFILES_DIO_BLOCK_SIZE; |
| |
| if (!for_write && |
| object->content_info == CACHEFILES_CONTENT_SINGLE) { |
| ret = FSCACHE_READ_FROM_CACHE; |
| } else { |
| eof = (i_size + PAGE_SIZE - 1) >> PAGE_SHIFT; |
| |
| extent->start = 0; |
| extent->block_end = eof; |
| extent->limit = eof; |
| ret = FSCACHE_WRITE_TO_CACHE; |
| } |
| |
| _leave(" = %u", ret); |
| return ret; |
| } |
| |
| /* |
| * Determine the size of a data extent in a cache object. |
| * |
| * In cachefiles, a data cache object is divided into granules of 256KiB, each |
| * of which must be written as a whole unit when the cache is being loaded. |
| * Data may be read out piecemeal. |
| * |
| * The extent is resized, but the result will always contain the starting page |
| * from the extent. |
| * |
| * If the granule does not exist in the cachefile, the start may be brought |
| * forward to align with the beginning of a granule boundary, and the end may be |
| * moved either way to align also. The extent will be cut off it it would cross |
| * over the boundary between what's cached and what's not. |
| * |
| * If the starting granule does exist in the cachefile, the extent will be |
| * shortened, if necessary, so that it doesn't cross over into a region that is |
| * not present. |
| * |
| * If the granule does not exist and we cannot cache it for lack of space, the |
| * requested extent is left unaltered. |
| */ |
| unsigned int cachefiles_shape_extent(struct fscache_object *obj, |
| struct fscache_extent *extent, |
| loff_t i_size, bool for_write) |
| { |
| struct cachefiles_object *object = |
| container_of(obj, struct cachefiles_object, fscache); |
| unsigned int ret = 0; |
| pgoff_t start, end, limit, eof, bend; |
| size_t granule; |
| |
| if (object->fscache.cookie->advice & FSCACHE_ADV_SINGLE_CHUNK) |
| return cachefiles_shape_single(obj, extent, i_size, for_write); |
| |
| start = extent->start; |
| end = extent->block_end; |
| limit = extent->limit; |
| _enter("{%lx,%lx,%lx},%llx,%d", start, end, limit, i_size, for_write); |
| |
| granule = start / CACHEFILES_GRAN_PAGES; |
| |
| if (granule / 8 >= object->content_map_size) { |
| cachefiles_expand_content_map(object, i_size); |
| if (granule / 8 >= object->content_map_size) |
| return 0; |
| } |
| |
| if (for_write) { |
| /* Assume that the preparation to write involved preloading any |
| * bits of the cache that weren't to be written and filling any |
| * gaps that didn't end up being written. |
| */ |
| bend = end; |
| ret = FSCACHE_WRITE_TO_CACHE; |
| } else if (cachefiles_granule_is_present(object, granule)) { |
| /* The start of the requested extent is present in the cache - |
| * restrict the returned extent to the maximum length of what's |
| * available. |
| */ |
| bend = round_up(start + 1, CACHEFILES_GRAN_PAGES); |
| while (bend < end) { |
| pgoff_t i = round_up(bend + 1, CACHEFILES_GRAN_PAGES); |
| granule = i / CACHEFILES_GRAN_PAGES; |
| if (!cachefiles_granule_is_present(object, granule)) |
| break; |
| bend = i; |
| } |
| |
| if (bend > end) |
| bend = end; |
| end = bend; |
| ret = FSCACHE_READ_FROM_CACHE; |
| } else { |
| /* Otherwise expand the extent in both directions to cover what |
| * we want for caching purposes. |
| */ |
| start = round_down(start, CACHEFILES_GRAN_PAGES); |
| end = round_up(end, CACHEFILES_GRAN_PAGES); |
| |
| if (limit != ULONG_MAX) { |
| limit = round_down(limit, CACHEFILES_GRAN_PAGES); |
| if (end > limit) { |
| end = limit; |
| if (end <= start) { |
| _leave(" = don't"); |
| return 0; |
| } |
| } |
| } |
| |
| /* But trim to the end of the file and the starting page */ |
| eof = (i_size + PAGE_SIZE - 1) >> PAGE_SHIFT; |
| if (eof <= extent->start) |
| eof = extent->start + 1; |
| if (end > eof) |
| end = eof; |
| |
| if ((start << PAGE_SHIFT) >= object->fscache.cookie->zero_point) { |
| /* The start of the requested extent is beyond the |
| * original EOF of the file on the server - therefore |
| * it's not going to be found on the server. |
| */ |
| bend = round_up(start + 1, CACHEFILES_GRAN_PAGES); |
| end = bend; |
| ret = FSCACHE_FILL_WITH_ZERO; |
| } else { |
| bend = start + CACHEFILES_GRAN_PAGES; |
| if (bend > eof) |
| bend = eof; |
| ret = FSCACHE_WRITE_TO_CACHE; |
| } |
| |
| /* TODO: Check we have space in the cache */ |
| } |
| |
| extent->start = start; |
| extent->block_end = bend; |
| extent->limit = end; |
| extent->dio_block_size = CACHEFILES_DIO_BLOCK_SIZE; |
| |
| _leave(" = %u {%lx,%lx,%lx}", ret, start, bend, end); |
| return ret; |
| } |
| |
| /* |
| * Allocate a new content map. |
| */ |
| u8 *cachefiles_new_content_map(struct cachefiles_object *object, |
| unsigned int *_size) |
| { |
| size_t size; |
| u8 *map = NULL; |
| |
| _enter(""); |
| |
| if (!(object->fscache.cookie->advice & FSCACHE_ADV_SINGLE_CHUNK)) { |
| /* Single-chunk object. The presence or absence of the content |
| * map xattr is sufficient indication. |
| */ |
| *_size = 0; |
| return NULL; |
| } |
| |
| /* Granular object. */ |
| size = cachefiles_map_size(object->fscache.cookie->object_size); |
| map = kzalloc(size, GFP_KERNEL); |
| if (!map) |
| return ERR_PTR(-ENOMEM); |
| *_size = size; |
| return map; |
| } |
| |
| /* |
| * Mark the content map to indicate stored granule. |
| */ |
| void cachefiles_mark_content_map(struct fscache_io_request *req) |
| { |
| struct cachefiles_object *object = |
| container_of(req->object, struct cachefiles_object, fscache); |
| loff_t pos = req->pos; |
| |
| _enter("%llx", pos); |
| |
| read_lock_bh(&object->content_map_lock); |
| |
| if (req->inval_counter != object->fscache.inval_counter) { |
| _debug("inval mark"); |
| } else if (object->fscache.cookie->advice & FSCACHE_ADV_SINGLE_CHUNK) { |
| if (pos == 0) { |
| object->content_info = CACHEFILES_CONTENT_SINGLE; |
| set_bit(FSCACHE_OBJECT_NEEDS_UPDATE, &object->fscache.flags); |
| } |
| } else { |
| pgoff_t granule; |
| loff_t end = pos + req->len; |
| |
| do { |
| granule = pos / CACHEFILES_GRAN_SIZE; |
| if (granule / 8 >= object->content_map_size) |
| break; |
| |
| set_bit_le(granule, object->content_map); |
| object->content_map_changed = true; |
| pos += CACHEFILES_GRAN_SIZE; |
| |
| } while (pos < end); |
| |
| if (object->content_info != CACHEFILES_CONTENT_MAP) { |
| object->content_info = CACHEFILES_CONTENT_MAP; |
| set_bit(FSCACHE_OBJECT_NEEDS_UPDATE, &object->fscache.flags); |
| } |
| } |
| |
| read_unlock_bh(&object->content_map_lock); |
| } |
| |
| /* |
| * Expand the content map to a larger file size. |
| */ |
| void cachefiles_expand_content_map(struct cachefiles_object *object, loff_t i_size) |
| { |
| size_t size; |
| u8 *map, *zap; |
| |
| size = cachefiles_map_size(i_size); |
| |
| _enter("%llx,%lx,%x", i_size, size, object->content_map_size); |
| |
| if (size <= object->content_map_size) |
| return; |
| |
| map = kzalloc(size, GFP_KERNEL); |
| if (!map) |
| return; |
| |
| write_lock_bh(&object->content_map_lock); |
| if (size > object->content_map_size) { |
| zap = object->content_map; |
| memcpy(map, zap, object->content_map_size); |
| object->content_map = map; |
| object->content_map_size = size; |
| } else { |
| zap = map; |
| } |
| write_unlock_bh(&object->content_map_lock); |
| |
| kfree(zap); |
| } |
| |
| /* |
| * Adjust the content map when we shorten a backing object. |
| * |
| * We need to unmark any granules that are going to be discarded. |
| */ |
| void cachefiles_shorten_content_map(struct cachefiles_object *object, |
| loff_t new_size) |
| { |
| struct fscache_cookie *cookie = object->fscache.cookie; |
| size_t granule, tmp, bytes; |
| |
| if (object->fscache.cookie->advice & FSCACHE_ADV_SINGLE_CHUNK) |
| return; |
| |
| write_lock_bh(&object->content_map_lock); |
| |
| if (object->content_info == CACHEFILES_CONTENT_MAP) { |
| if (cookie->zero_point > new_size) |
| cookie->zero_point = new_size; |
| |
| granule = new_size; |
| granule += CACHEFILES_GRAN_SIZE - 1; |
| granule /= CACHEFILES_GRAN_SIZE; |
| |
| tmp = granule; |
| tmp = round_up(granule, 64); |
| bytes = tmp / 8; |
| if (bytes < object->content_map_size) |
| memset(object->content_map + bytes, 0, |
| object->content_map_size - bytes); |
| |
| if (tmp > granule) |
| for (tmp--; tmp > granule; tmp--) |
| clear_bit_le(tmp, object->content_map); |
| } |
| |
| write_unlock_bh(&object->content_map_lock); |
| } |
| |
| /* |
| * Load the content map. |
| */ |
| bool cachefiles_load_content_map(struct cachefiles_object *object) |
| { |
| struct cachefiles_cache *cache = container_of(object->fscache.cache, |
| struct cachefiles_cache, cache); |
| const struct cred *saved_cred; |
| ssize_t got; |
| size_t size; |
| u8 *map = NULL; |
| |
| _enter("c=%08x,%llx", |
| object->fscache.cookie->debug_id, |
| object->fscache.cookie->object_size); |
| |
| object->content_info = CACHEFILES_CONTENT_NO_DATA; |
| if (object->fscache.cookie->advice & FSCACHE_ADV_SINGLE_CHUNK) { |
| /* Single-chunk object. The presence or absence of the content |
| * map xattr is sufficient indication. |
| */ |
| size = 0; |
| } else { |
| /* Granulated object. There's one bit per granule. We size it |
| * in terms of 8-byte chunks, where a 64-bit span * 256KiB |
| * bytes granules covers 16MiB of file space. At that, 512B |
| * will cover 1GiB. |
| */ |
| size = cachefiles_map_size(object->fscache.cookie->object_size); |
| map = kzalloc(size, GFP_KERNEL); |
| if (!map) |
| return false; |
| } |
| |
| cachefiles_begin_secure(cache, &saved_cred); |
| got = vfs_getxattr(object->dentry, cachefiles_xattr_content_map, |
| map, size); |
| cachefiles_end_secure(cache, saved_cred); |
| if (got < 0 && got != -ENODATA) { |
| kfree(map); |
| _leave(" = f [%zd]", got); |
| return false; |
| } |
| |
| if (size == 0) { |
| if (got != -ENODATA) |
| object->content_info = CACHEFILES_CONTENT_SINGLE; |
| _leave(" = t [%zd]", got); |
| } else { |
| object->content_map = map; |
| object->content_map_size = size; |
| object->content_info = CACHEFILES_CONTENT_MAP; |
| _leave(" = t [%zd/%zu %*phN]", got, size, (int)size, map); |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Save the content map. |
| */ |
| void cachefiles_save_content_map(struct cachefiles_object *object) |
| { |
| ssize_t ret; |
| size_t size; |
| u8 *map; |
| |
| _enter("c=%08x", object->fscache.cookie->debug_id); |
| |
| if (object->content_info != CACHEFILES_CONTENT_MAP) |
| return; |
| |
| size = object->content_map_size; |
| map = object->content_map; |
| |
| /* Don't save trailing zeros, but do save at least one byte */ |
| for (; size > 0; size--) |
| if (map[size - 1]) |
| break; |
| |
| ret = vfs_setxattr(object->dentry, cachefiles_xattr_content_map, |
| map, size, 0); |
| if (ret < 0) { |
| cachefiles_io_error_obj(object, "Unable to set xattr"); |
| return; |
| } |
| |
| _leave(" = %zd", ret); |
| } |
| |
| /* |
| * Display object information in proc. |
| */ |
| int cachefiles_display_object(struct seq_file *m, struct fscache_object *_object) |
| { |
| struct cachefiles_object *object = |
| container_of(_object, struct cachefiles_object, fscache); |
| |
| if (object->fscache.cookie->type == FSCACHE_COOKIE_TYPE_INDEX) { |
| if (object->content_info != CACHEFILES_CONTENT_NO_DATA) |
| seq_printf(m, " ???%u???", object->content_info); |
| } else { |
| switch (object->content_info) { |
| case CACHEFILES_CONTENT_NO_DATA: |
| seq_puts(m, " <n>"); |
| break; |
| case CACHEFILES_CONTENT_SINGLE: |
| seq_puts(m, " <s>"); |
| break; |
| case CACHEFILES_CONTENT_ALL: |
| seq_puts(m, " <a>"); |
| break; |
| case CACHEFILES_CONTENT_MAP: |
| read_lock_bh(&object->content_map_lock); |
| if (object->content_map) { |
| seq_printf(m, " %*phN", |
| object->content_map_size, |
| object->content_map); |
| } |
| read_unlock_bh(&object->content_map_lock); |
| break; |
| default: |
| seq_printf(m, " <%u>", object->content_info); |
| break; |
| } |
| } |
| |
| seq_putc(m, '\n'); |
| return 0; |
| } |