ceph: add encryption support to writepage and writepages

Allow writepage to issue encrypted writes. Extend out the requested size
and offset to cover complete blocks, and then encrypt and write them to
the OSDs.

Add the appropriate machinery to write back dirty data with encryption.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Xiubo Li <xiubli@redhat.com>
Reviewed-and-tested-by: Luís Henriques <lhenriques@suse.de>
Reviewed-by: Milind Changire <mchangir@redhat.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
This commit is contained in:
Jeff Layton 2022-08-25 09:31:25 -04:00 committed by Ilya Dryomov
parent 33a5f1709a
commit d55207717d
2 changed files with 98 additions and 21 deletions

View File

@ -565,10 +565,12 @@ static u64 get_writepages_data_length(struct inode *inode,
struct page *page, u64 start) struct page *page, u64 start)
{ {
struct ceph_inode_info *ci = ceph_inode(inode); struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_snap_context *snapc = page_snap_context(page); struct ceph_snap_context *snapc;
struct ceph_cap_snap *capsnap = NULL; struct ceph_cap_snap *capsnap = NULL;
u64 end = i_size_read(inode); u64 end = i_size_read(inode);
u64 ret;
snapc = page_snap_context(ceph_fscrypt_pagecache_page(page));
if (snapc != ci->i_head_snapc) { if (snapc != ci->i_head_snapc) {
bool found = false; bool found = false;
spin_lock(&ci->i_ceph_lock); spin_lock(&ci->i_ceph_lock);
@ -583,9 +585,12 @@ static u64 get_writepages_data_length(struct inode *inode,
spin_unlock(&ci->i_ceph_lock); spin_unlock(&ci->i_ceph_lock);
WARN_ON(!found); WARN_ON(!found);
} }
if (end > page_offset(page) + thp_size(page)) if (end > ceph_fscrypt_page_offset(page) + thp_size(page))
end = page_offset(page) + thp_size(page); end = ceph_fscrypt_page_offset(page) + thp_size(page);
return end > start ? end - start : 0; ret = end > start ? end - start : 0;
if (ret && fscrypt_is_bounce_page(page))
ret = round_up(ret, CEPH_FSCRYPT_BLOCK_SIZE);
return ret;
} }
/* /*
@ -604,10 +609,12 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
loff_t page_off = page_offset(page); loff_t page_off = page_offset(page);
int err; int err;
loff_t len = thp_size(page); loff_t len = thp_size(page);
loff_t wlen;
struct ceph_writeback_ctl ceph_wbc; struct ceph_writeback_ctl ceph_wbc;
struct ceph_osd_client *osdc = &fsc->client->osdc; struct ceph_osd_client *osdc = &fsc->client->osdc;
struct ceph_osd_request *req; struct ceph_osd_request *req;
bool caching = ceph_is_cache_enabled(inode); bool caching = ceph_is_cache_enabled(inode);
struct page *bounce_page = NULL;
dout("writepage %p idx %lu\n", page, page->index); dout("writepage %p idx %lu\n", page, page->index);
@ -643,31 +650,51 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
if (ceph_wbc.i_size < page_off + len) if (ceph_wbc.i_size < page_off + len)
len = ceph_wbc.i_size - page_off; len = ceph_wbc.i_size - page_off;
wlen = IS_ENCRYPTED(inode) ? round_up(len, CEPH_FSCRYPT_BLOCK_SIZE) : len;
dout("writepage %p page %p index %lu on %llu~%llu snapc %p seq %lld\n", dout("writepage %p page %p index %lu on %llu~%llu snapc %p seq %lld\n",
inode, page, page->index, page_off, len, snapc, snapc->seq); inode, page, page->index, page_off, wlen, snapc, snapc->seq);
if (atomic_long_inc_return(&fsc->writeback_count) > if (atomic_long_inc_return(&fsc->writeback_count) >
CONGESTION_ON_THRESH(fsc->mount_options->congestion_kb)) CONGESTION_ON_THRESH(fsc->mount_options->congestion_kb))
fsc->write_congested = true; fsc->write_congested = true;
req = ceph_osdc_new_request(osdc, &ci->i_layout, ceph_vino(inode), page_off, &len, 0, 1, req = ceph_osdc_new_request(osdc, &ci->i_layout, ceph_vino(inode),
CEPH_OSD_OP_WRITE, CEPH_OSD_FLAG_WRITE, snapc, page_off, &wlen, 0, 1, CEPH_OSD_OP_WRITE,
ceph_wbc.truncate_seq, ceph_wbc.truncate_size, CEPH_OSD_FLAG_WRITE, snapc,
true); ceph_wbc.truncate_seq,
ceph_wbc.truncate_size, true);
if (IS_ERR(req)) { if (IS_ERR(req)) {
redirty_page_for_writepage(wbc, page); redirty_page_for_writepage(wbc, page);
return PTR_ERR(req); return PTR_ERR(req);
} }
if (wlen < len)
len = wlen;
set_page_writeback(page); set_page_writeback(page);
if (caching) if (caching)
ceph_set_page_fscache(page); ceph_set_page_fscache(page);
ceph_fscache_write_to_cache(inode, page_off, len, caching); ceph_fscache_write_to_cache(inode, page_off, len, caching);
if (IS_ENCRYPTED(inode)) {
bounce_page = fscrypt_encrypt_pagecache_blocks(page,
CEPH_FSCRYPT_BLOCK_SIZE, 0,
GFP_NOFS);
if (IS_ERR(bounce_page)) {
redirty_page_for_writepage(wbc, page);
end_page_writeback(page);
ceph_osdc_put_request(req);
return PTR_ERR(bounce_page);
}
}
/* it may be a short write due to an object boundary */ /* it may be a short write due to an object boundary */
WARN_ON_ONCE(len > thp_size(page)); WARN_ON_ONCE(len > thp_size(page));
osd_req_op_extent_osd_data_pages(req, 0, &page, len, 0, false, false); osd_req_op_extent_osd_data_pages(req, 0,
dout("writepage %llu~%llu (%llu bytes)\n", page_off, len, len); bounce_page ? &bounce_page : &page, wlen, 0,
false, false);
dout("writepage %llu~%llu (%llu bytes, %sencrypted)\n",
page_off, len, wlen, IS_ENCRYPTED(inode) ? "" : "not ");
req->r_mtime = inode->i_mtime; req->r_mtime = inode->i_mtime;
ceph_osdc_start_request(osdc, req); ceph_osdc_start_request(osdc, req);
@ -675,7 +702,7 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency, ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency,
req->r_end_latency, len, err); req->r_end_latency, len, err);
fscrypt_free_bounce_page(bounce_page);
ceph_osdc_put_request(req); ceph_osdc_put_request(req);
if (err == 0) if (err == 0)
err = len; err = len;
@ -794,6 +821,11 @@ static void writepages_finish(struct ceph_osd_request *req)
total_pages += num_pages; total_pages += num_pages;
for (j = 0; j < num_pages; j++) { for (j = 0; j < num_pages; j++) {
page = osd_data->pages[j]; page = osd_data->pages[j];
if (fscrypt_is_bounce_page(page)) {
page = fscrypt_pagecache_page(page);
fscrypt_free_bounce_page(osd_data->pages[j]);
osd_data->pages[j] = page;
}
BUG_ON(!page); BUG_ON(!page);
WARN_ON(!PageUptodate(page)); WARN_ON(!PageUptodate(page));
@ -1064,9 +1096,28 @@ static int ceph_writepages_start(struct address_space *mapping,
fsc->mount_options->congestion_kb)) fsc->mount_options->congestion_kb))
fsc->write_congested = true; fsc->write_congested = true;
pages[locked_pages++] = page; if (IS_ENCRYPTED(inode)) {
fbatch.folios[i] = NULL; pages[locked_pages] =
fscrypt_encrypt_pagecache_blocks(page,
PAGE_SIZE, 0,
locked_pages ? GFP_NOWAIT : GFP_NOFS);
if (IS_ERR(pages[locked_pages])) {
if (PTR_ERR(pages[locked_pages]) == -EINVAL)
pr_err("%s: inode->i_blkbits=%hhu\n",
__func__, inode->i_blkbits);
/* better not fail on first page! */
BUG_ON(locked_pages == 0);
pages[locked_pages] = NULL;
redirty_page_for_writepage(wbc, page);
unlock_page(page);
break;
}
++locked_pages;
} else {
pages[locked_pages++] = page;
}
fbatch.folios[i] = NULL;
len += thp_size(page); len += thp_size(page);
} }
@ -1094,7 +1145,7 @@ static int ceph_writepages_start(struct address_space *mapping,
} }
new_request: new_request:
offset = page_offset(pages[0]); offset = ceph_fscrypt_page_offset(pages[0]);
len = wsize; len = wsize;
req = ceph_osdc_new_request(&fsc->client->osdc, req = ceph_osdc_new_request(&fsc->client->osdc,
@ -1115,8 +1166,8 @@ static int ceph_writepages_start(struct address_space *mapping,
ceph_wbc.truncate_size, true); ceph_wbc.truncate_size, true);
BUG_ON(IS_ERR(req)); BUG_ON(IS_ERR(req));
} }
BUG_ON(len < page_offset(pages[locked_pages - 1]) + BUG_ON(len < ceph_fscrypt_page_offset(pages[locked_pages - 1]) +
thp_size(page) - offset); thp_size(pages[locked_pages - 1]) - offset);
req->r_callback = writepages_finish; req->r_callback = writepages_finish;
req->r_inode = inode; req->r_inode = inode;
@ -1126,7 +1177,9 @@ static int ceph_writepages_start(struct address_space *mapping,
data_pages = pages; data_pages = pages;
op_idx = 0; op_idx = 0;
for (i = 0; i < locked_pages; i++) { for (i = 0; i < locked_pages; i++) {
u64 cur_offset = page_offset(pages[i]); struct page *page = ceph_fscrypt_pagecache_page(pages[i]);
u64 cur_offset = page_offset(page);
/* /*
* Discontinuity in page range? Ceph can handle that by just passing * Discontinuity in page range? Ceph can handle that by just passing
* multiple extents in the write op. * multiple extents in the write op.
@ -1155,9 +1208,9 @@ static int ceph_writepages_start(struct address_space *mapping,
op_idx++; op_idx++;
} }
set_page_writeback(pages[i]); set_page_writeback(page);
if (caching) if (caching)
ceph_set_page_fscache(pages[i]); ceph_set_page_fscache(page);
len += thp_size(page); len += thp_size(page);
} }
ceph_fscache_write_to_cache(inode, offset, len, caching); ceph_fscache_write_to_cache(inode, offset, len, caching);
@ -1173,8 +1226,16 @@ static int ceph_writepages_start(struct address_space *mapping,
offset); offset);
len = max(len, min_len); len = max(len, min_len);
} }
if (IS_ENCRYPTED(inode))
len = round_up(len, CEPH_FSCRYPT_BLOCK_SIZE);
dout("writepages got pages at %llu~%llu\n", offset, len); dout("writepages got pages at %llu~%llu\n", offset, len);
if (IS_ENCRYPTED(inode) &&
((offset | len) & ~CEPH_FSCRYPT_BLOCK_MASK))
pr_warn("%s: bad encrypted write offset=%lld len=%llu\n",
__func__, offset, len);
osd_req_op_extent_osd_data_pages(req, op_idx, data_pages, len, osd_req_op_extent_osd_data_pages(req, op_idx, data_pages, len,
0, from_pool, false); 0, from_pool, false);
osd_req_op_extent_update(req, op_idx, len); osd_req_op_extent_update(req, op_idx, len);

View File

@ -164,6 +164,12 @@ int ceph_fscrypt_decrypt_extents(struct inode *inode, struct page **page,
u32 ext_cnt); u32 ext_cnt);
int ceph_fscrypt_encrypt_pages(struct inode *inode, struct page **page, u64 off, int ceph_fscrypt_encrypt_pages(struct inode *inode, struct page **page, u64 off,
int len, gfp_t gfp); int len, gfp_t gfp);
static inline struct page *ceph_fscrypt_pagecache_page(struct page *page)
{
return fscrypt_is_bounce_page(page) ? fscrypt_pagecache_page(page) : page;
}
#else /* CONFIG_FS_ENCRYPTION */ #else /* CONFIG_FS_ENCRYPTION */
static inline void ceph_fscrypt_set_ops(struct super_block *sb) static inline void ceph_fscrypt_set_ops(struct super_block *sb)
@ -267,6 +273,16 @@ static inline int ceph_fscrypt_encrypt_pages(struct inode *inode,
{ {
return 0; return 0;
} }
static inline struct page *ceph_fscrypt_pagecache_page(struct page *page)
{
return page;
}
#endif /* CONFIG_FS_ENCRYPTION */ #endif /* CONFIG_FS_ENCRYPTION */
#endif static inline loff_t ceph_fscrypt_page_offset(struct page *page)
{
return page_offset(ceph_fscrypt_pagecache_page(page));
}
#endif /* _CEPH_CRYPTO_H */