Skip to content

Commit

Permalink
f2fs: clean up post-read processing
Browse files Browse the repository at this point in the history
Rework the post-read processing logic to be much easier to understand.

At least one bug is fixed by this: if an I/O error occurred when reading
from disk, decryption and verity would be performed on the uninitialized
data, causing misleading messages in the kernel log.

Signed-off-by: Eric Biggers <[email protected]>
Reviewed-by: Chao Yu <[email protected]>
Signed-off-by: Jaegeuk Kim <[email protected]>
  • Loading branch information
ebiggers authored and Jaegeuk Kim committed Jan 27, 2021
1 parent cf74040 commit 7f59b27
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 264 deletions.
149 changes: 108 additions & 41 deletions fs/f2fs/compress.c
Original file line number Diff line number Diff line change
Expand Up @@ -756,38 +756,27 @@ static int f2fs_compress_pages(struct compress_ctx *cc)
return ret;
}

void f2fs_decompress_pages(struct bio *bio, struct page *page, bool verity)
static void f2fs_decompress_cluster(struct decompress_io_ctx *dic)
{
struct decompress_io_ctx *dic =
(struct decompress_io_ctx *)page_private(page);
struct f2fs_sb_info *sbi = F2FS_I_SB(dic->inode);
struct f2fs_inode_info *fi= F2FS_I(dic->inode);
struct f2fs_inode_info *fi = F2FS_I(dic->inode);
const struct f2fs_compress_ops *cops =
f2fs_cops[fi->i_compress_algorithm];
int ret;
int i;

dec_page_count(sbi, F2FS_RD_DATA);

if (bio->bi_status || PageError(page))
dic->failed = true;

if (atomic_dec_return(&dic->pending_pages))
return;

trace_f2fs_decompress_pages_start(dic->inode, dic->cluster_idx,
dic->cluster_size, fi->i_compress_algorithm);

/* submit partial compressed pages */
if (dic->failed) {
ret = -EIO;
goto out_free_dic;
goto out_end_io;
}

dic->tpages = page_array_alloc(dic->inode, dic->cluster_size);
if (!dic->tpages) {
ret = -ENOMEM;
goto out_free_dic;
goto out_end_io;
}

for (i = 0; i < dic->cluster_size; i++) {
Expand All @@ -799,20 +788,20 @@ void f2fs_decompress_pages(struct bio *bio, struct page *page, bool verity)
dic->tpages[i] = f2fs_compress_alloc_page();
if (!dic->tpages[i]) {
ret = -ENOMEM;
goto out_free_dic;
goto out_end_io;
}
}

if (cops->init_decompress_ctx) {
ret = cops->init_decompress_ctx(dic);
if (ret)
goto out_free_dic;
goto out_end_io;
}

dic->rbuf = f2fs_vmap(dic->tpages, dic->cluster_size);
if (!dic->rbuf) {
ret = -ENOMEM;
goto destroy_decompress_ctx;
goto out_destroy_decompress_ctx;
}

dic->cbuf = f2fs_vmap(dic->cpages, dic->nr_cpages);
Expand Down Expand Up @@ -851,18 +840,34 @@ void f2fs_decompress_pages(struct bio *bio, struct page *page, bool verity)
vm_unmap_ram(dic->cbuf, dic->nr_cpages);
out_vunmap_rbuf:
vm_unmap_ram(dic->rbuf, dic->cluster_size);
destroy_decompress_ctx:
out_destroy_decompress_ctx:
if (cops->destroy_decompress_ctx)
cops->destroy_decompress_ctx(dic);
out_free_dic:
if (!verity)
f2fs_decompress_end_io(dic->rpages, dic->cluster_size,
ret, false);

out_end_io:
trace_f2fs_decompress_pages_end(dic->inode, dic->cluster_idx,
dic->clen, ret);
if (!verity)
f2fs_free_dic(dic);
f2fs_decompress_end_io(dic, ret);
}

/*
* This is called when a page of a compressed cluster has been read from disk
* (or failed to be read from disk). It checks whether this page was the last
* page being waited on in the cluster, and if so, it decompresses the cluster
* (or in the case of a failure, cleans up without actually decompressing).
*/
void f2fs_end_read_compressed_page(struct page *page, bool failed)
{
struct decompress_io_ctx *dic =
(struct decompress_io_ctx *)page_private(page);
struct f2fs_sb_info *sbi = F2FS_I_SB(dic->inode);

dec_page_count(sbi, F2FS_RD_DATA);

if (failed)
WRITE_ONCE(dic->failed, true);

if (atomic_dec_and_test(&dic->remaining_pages))
f2fs_decompress_cluster(dic);
}

static bool is_page_in_cluster(struct compress_ctx *cc, pgoff_t index)
Expand Down Expand Up @@ -1529,6 +1534,8 @@ int f2fs_write_multi_pages(struct compress_ctx *cc,
return err;
}

static void f2fs_free_dic(struct decompress_io_ctx *dic);

struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
{
struct decompress_io_ctx *dic;
Expand All @@ -1547,12 +1554,14 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)

dic->magic = F2FS_COMPRESSED_PAGE_MAGIC;
dic->inode = cc->inode;
atomic_set(&dic->pending_pages, cc->nr_cpages);
atomic_set(&dic->remaining_pages, cc->nr_cpages);
dic->cluster_idx = cc->cluster_idx;
dic->cluster_size = cc->cluster_size;
dic->log_cluster_size = cc->log_cluster_size;
dic->nr_cpages = cc->nr_cpages;
refcount_set(&dic->refcnt, 1);
dic->failed = false;
dic->need_verity = f2fs_need_verity(cc->inode, start_idx);

for (i = 0; i < dic->cluster_size; i++)
dic->rpages[i] = cc->rpages[i];
Expand Down Expand Up @@ -1581,7 +1590,7 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
return ERR_PTR(-ENOMEM);
}

void f2fs_free_dic(struct decompress_io_ctx *dic)
static void f2fs_free_dic(struct decompress_io_ctx *dic)
{
int i;

Expand Down Expand Up @@ -1609,30 +1618,88 @@ void f2fs_free_dic(struct decompress_io_ctx *dic)
kmem_cache_free(dic_entry_slab, dic);
}

void f2fs_decompress_end_io(struct page **rpages,
unsigned int cluster_size, bool err, bool verity)
static void f2fs_put_dic(struct decompress_io_ctx *dic)
{
if (refcount_dec_and_test(&dic->refcnt))
f2fs_free_dic(dic);
}

/*
* Update and unlock the cluster's pagecache pages, and release the reference to
* the decompress_io_ctx that was being held for I/O completion.
*/
static void __f2fs_decompress_end_io(struct decompress_io_ctx *dic, bool failed)
{
int i;

for (i = 0; i < cluster_size; i++) {
struct page *rpage = rpages[i];
for (i = 0; i < dic->cluster_size; i++) {
struct page *rpage = dic->rpages[i];

if (!rpage)
continue;

if (err || PageError(rpage))
goto clear_uptodate;

if (!verity || fsverity_verify_page(rpage)) {
/* PG_error was set if verity failed. */
if (failed || PageError(rpage)) {
ClearPageUptodate(rpage);
/* will re-read again later */
ClearPageError(rpage);
} else {
SetPageUptodate(rpage);
goto unlock;
}
clear_uptodate:
ClearPageUptodate(rpage);
ClearPageError(rpage);
unlock:
unlock_page(rpage);
}

f2fs_put_dic(dic);
}

static void f2fs_verify_cluster(struct work_struct *work)
{
struct decompress_io_ctx *dic =
container_of(work, struct decompress_io_ctx, verity_work);
int i;

/* Verify the cluster's decompressed pages with fs-verity. */
for (i = 0; i < dic->cluster_size; i++) {
struct page *rpage = dic->rpages[i];

if (rpage && !fsverity_verify_page(rpage))
SetPageError(rpage);
}

__f2fs_decompress_end_io(dic, false);
}

/*
* This is called when a compressed cluster has been decompressed
* (or failed to be read and/or decompressed).
*/
void f2fs_decompress_end_io(struct decompress_io_ctx *dic, bool failed)
{
if (!failed && dic->need_verity) {
/*
* Note that to avoid deadlocks, the verity work can't be done
* on the decompression workqueue. This is because verifying
* the data pages can involve reading metadata pages from the
* file, and these metadata pages may be compressed.
*/
INIT_WORK(&dic->verity_work, f2fs_verify_cluster);
fsverity_enqueue_verify_work(&dic->verity_work);
} else {
__f2fs_decompress_end_io(dic, failed);
}
}

/*
* Put a reference to a compressed page's decompress_io_ctx.
*
* This is called when the page is no longer needed and can be freed.
*/
void f2fs_put_page_dic(struct page *page)
{
struct decompress_io_ctx *dic =
(struct decompress_io_ctx *)page_private(page);

f2fs_put_dic(dic);
}

int f2fs_init_page_array_cache(struct f2fs_sb_info *sbi)
Expand Down
Loading

0 comments on commit 7f59b27

Please sign in to comment.