Add O_DIRECT support for encrypted files.
By default, fscrypt does not support O_DIRECT because it needs
pagecache pages to proceed.
With Lustre, we can make use of pages being used for sending RPCs.
They can be twisted so that they have a proper mapping and index,
suitable for encryption/decryption.
One of the benefits of O_DIRECT support for encrypted files is that
we get support for mirroring at the same time.
Test-Parameters: testlist=sanity-sec envdefinitions=ONLY="36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 52" clientdistro=el8.1 fstype=ldiskfs mdscount=2 mdtcount=4
Test-Parameters: testlist=sanity-sec envdefinitions=ONLY="36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 52" clientdistro=el8.1 fstype=zfs mdscount=2 mdtcount=4
Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Change-Id: I12f61c44b55f3a454f38016200f81eb735ab8f18
Reviewed-on: https://review.whamcloud.com/38967
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Wang Shilong <wshilong@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
may be used to overwrite the source files but isn't guaranteed to be
effective on all filesystems and storage devices.
-- Direct I/O is not supported on encrypted files. Attempts to use
- direct I/O on such files will fall back to buffered I/O.
-
- The fallocate operations FALLOC_FL_COLLAPSE_RANGE,
FALLOC_FL_INSERT_RANGE, and FALLOC_FL_ZERO_RANGE are not supported
on encrypted files and will fail with EOPNOTSUPP.
attr->ia_valid & ATTR_SIZE) {
xvalid |= OP_XVALID_FLAGS;
flags = LUSTRE_ENCRYPT_FL;
- if (attr->ia_size & ~PAGE_MASK) {
+ /* Call to ll_io_zero_page is not necessary if
+ * truncating on PAGE_SIZE boundary, because
+ * whole pages will be wiped.
+ * In case of Direct IO, all we need is to set
+ * new size.
+ */
+ if (attr->ia_size & ~PAGE_MASK &&
+ !(attr->ia_valid & ATTR_FILE &&
+ attr->ia_file->f_flags & O_DIRECT)) {
pgoff_t offset =
attr->ia_size & (PAGE_SIZE - 1);
int io_pages = 0;
size_t page_size = cl_page_size(obj);
int i;
+ pgoff_t index = offset >> PAGE_SHIFT;
ssize_t rc = 0;
ENTRY;
}
page->cp_sync_io = anchor;
+ if (inode && IS_ENCRYPTED(inode)) {
+ struct page *vmpage = cl_page_vmpage(page);
+
+ /* In case of Direct IO on encrypted file, we need to
+ * set the correct page index, and add a reference to
+ * the mapping. This is required by llcrypt to proceed
+ * to encryption/decryption, because each block is
+ * encrypted independently, and each block's IV is set
+ * to the logical block number within the file.
+ * This is safe because we know these pages are private
+ * to the thread doing the Direct IO, and despite
+ * setting a mapping on the pages, cached lookups will
+ * not find them.
+ * Set PageChecked to detect special case of Direct IO
+ * in osc_brw_fini_request().
+ * Reference to the mapping and PageChecked flag are
+ * removed in cl_aio_end().
+ */
+ vmpage->index = index++;
+ vmpage->mapping = inode->i_mapping;
+ SetPageChecked(vmpage);
+ }
cl_2queue_add(queue, page);
/*
* Set page clip to tell transfer formation engine
loff_t file_offset = iocb->ki_pos;
struct vvp_io *vio;
- /* if file is encrypted, return 0 so that we fall back to buffered IO */
- if (IS_ENCRYPTED(inode))
- return 0;
-
/* Check EOF by ourselves */
if (rw == READ && file_offset >= i_size_read(inode))
return 0;
#include <lustre_fid.h>
#include <cl_object.h>
#include "cl_internal.h"
+#include <libcfs/crypto/llcrypt.h>
/*****************************************************************************
*
/* release pages */
while (aio->cda_pages.pl_nr > 0) {
struct cl_page *page = cl_page_list_first(&aio->cda_pages);
+ struct page *vmpage = cl_page_vmpage(page);
+ struct inode *inode = vmpage ? page2inode(vmpage) : NULL;
cl_page_get(page);
+ /* We end up here in case of Direct IO only. For encrypted file,
+ * mapping was set on pages in ll_direct_rw_pages(), so it has
+ * to be cleared now before page cleanup.
+ * PageChecked flag was also set there, so we clean up here.
+ */
+ if (inode && IS_ENCRYPTED(inode)) {
+ vmpage->mapping = NULL;
+ ClearPageChecked(vmpage);
+ }
cl_page_list_del(env, &aio->cda_pages, page);
cl_page_delete(env, page);
cl_page_put(env, page);
int i;
for (i = 0; i < page_count; i++) {
- if (pga[i]->pg->mapping)
+ if (!pga[i]->pg->mapping)
/* bounce pages are unmapped */
- continue;
- if (pga[i]->flag & OBD_BRW_SYNC)
- /* sync transfer cannot have encrypted pages */
- continue;
- llcrypt_finalize_bounce_page(&pga[i]->pg);
+ llcrypt_finalize_bounce_page(&pga[i]->pg);
pga[i]->count -= pga[i]->bp_count_diff;
pga[i]->off += pga[i]->bp_off_diff;
}
pg->bp_off_diff = pg->off & ~PAGE_MASK;
pg->off = pg->off & PAGE_MASK;
}
+ } else if (opc == OST_READ && inode && IS_ENCRYPTED(inode)) {
+ for (i = 0; i < page_count; i++) {
+ struct brw_page *pg = pga[i];
+
+ /* count/off are forced to cover the whole page so that
+ * all encrypted data is stored on the OST, so adjust
+ * bp_{count,off}_diff for the size of the clear text.
+ */
+ pg->bp_count_diff = PAGE_SIZE - pg->count;
+ pg->count = PAGE_SIZE;
+ pg->bp_off_diff = pg->off & ~PAGE_MASK;
+ pg->off = pg->off & PAGE_MASK;
+ }
}
for (niocount = i = 1; i < page_count; i++) {
req_capsule_set_size(pill, &RMF_NIOBUF_REMOTE, RCL_CLIENT,
niocount * sizeof(*niobuf));
- for (i = 0; i < page_count; i++)
+ for (i = 0; i < page_count; i++) {
short_io_size += pga[i]->count;
+ if (!inode || !IS_ENCRYPTED(inode)) {
+ pga[i]->bp_count_diff = 0;
+ pga[i]->bp_off_diff = 0;
+ }
+ }
/* Check if read/write is small enough to be a short io. */
if (short_io_size > cli->cl_max_short_io_bytes || niocount > 1 ||
continue;
}
+ /* The page is already locked when we arrive here,
+ * except when we deal with a twisted page for
+ * specific Direct IO support, in which case
+ * PageChecked flag is set on page.
+ */
+ if (PageChecked(pg->pg))
+ lock_page(pg->pg);
rc = llcrypt_decrypt_pagecache_blocks(pg->pg,
PAGE_SIZE, 0);
+ if (PageChecked(pg->pg))
+ unlock_page(pg->pg);
if (rc)
GOTO(out, rc);
}
test_44() {
local testfile=$DIR/$tdir/$tfile
+ local tmpfile=$TMP/abc
+ local resfile=$TMP/resfile
+ local respage
$LCTL get_param mdc.*.import | grep -q client_encryption ||
skip "client encryption not supported"
which vmtouch || skip "This test needs vmtouch utility"
- # Direct I/O is not supported on encrypted files.
- # Attempts to use direct I/O on such files should fall back to
- # buffered I/O.
+ # Direct I/O is now supported on encrypted files.
stack_trap cleanup_for_enc_tests EXIT
setup_for_enc_tests
- # write a page in file with O_DIRECT
$LFS setstripe -c1 -i0 $testfile
- dd if=/dev/urandom of=$testfile bs=4096 count=1 conv=fsync oflag=direct
+ dd if=/dev/urandom of=$tmpfile bs=8192 count=1 conv=fsync
+ dd if=$tmpfile of=$testfile bs=8192 count=1 oflag=direct ||
+ error "could not write to file with O_DIRECT (1)"
respage=$(vmtouch $testfile | awk '/Resident\ Pages:/ {print $3}')
- [ "$respage" == "1/1" ] ||
- error "write to enc file did not fall back to buffered IO"
+ [ "$respage" == "0/2" ] ||
+ error "write to enc file fell back to buffered IO"
- cancel_lru_locks osc ; cancel_lru_locks mdc
+ cancel_lru_locks
- dd if=$testfile of=/dev/null bs=4096 count=1 iflag=direct
+ dd if=$testfile of=$resfile bs=8192 count=1 iflag=direct ||
+ error "could not read from file with O_DIRECT (1)"
respage=$(vmtouch $testfile | awk '/Resident\ Pages:/ {print $3}')
- [ "$respage" == "1/1" ] ||
- error "write to enc file did not fall back to buffered IO"
+ [ "$respage" == "0/2" ] ||
+ error "read from enc file fell back to buffered IO"
+
+ cmp -bl $tmpfile $resfile ||
+ error "file $testfile is corrupted (1)"
+
+ rm -f $resfile
+
+ $TRUNCATE $tmpfile 4096
+ dd if=$tmpfile of=$testfile bs=4096 count=1 seek=13 oflag=direct ||
+ error "could not write to file with O_DIRECT (2)"
+
+ cancel_lru_locks
+
+ dd if=$testfile of=$resfile bs=4096 count=1 skip=13 iflag=direct ||
+ error "could not read from file with O_DIRECT (2)"
+ cmp -bl $tmpfile $resfile ||
+ error "file $testfile is corrupted (2)"
+
+ rm -f $testfile $resfile
+ $LFS setstripe -c1 -i0 $testfile
+
+ $TRUNCATE $tmpfile 2043
+ cp $tmpfile $testfile
+
+ cancel_lru_locks
+
+ dd if=$testfile of=$resfile bs=4096 count=1 iflag=direct ||
+ error "could not read from file with O_DIRECT (3)"
+ cmp -bl $tmpfile $resfile ||
+ error "file $testfile is corrupted (3)"
+
+ rm -f $tmpfile $resfile
}
run_test 44 "encrypted file access semantics: direct IO"
}
run_test 51 "FS capabilities ==============="
+test_52() {
+ local testfile=$DIR/$tdir/$tfile
+ local tmpfile=$TMP/$tfile
+ local mirror1=$TMP/$tfile.mirror1
+ local mirror2=$TMP/$tfile.mirror2
+
+ $LCTL get_param mdc.*.import | grep -q client_encryption ||
+ skip "client encryption not supported"
+
+ mount.lustre --help |& grep -q "test_dummy_encryption:" ||
+ skip "need dummy encryption support"
+
+ [[ $OSTCOUNT -lt 2 ]] && skip_env "needs >= 2 OSTs"
+
+ stack_trap cleanup_for_enc_tests EXIT
+ setup_for_enc_tests
+
+ dd if=/dev/urandom of=$tmpfile bs=5000 count=1 conv=fsync
+
+ $LFS mirror create -N -i0 -N -i1 $testfile ||
+ error "could not create mirror"
+
+ dd if=$tmpfile of=$testfile bs=5000 count=1 conv=fsync ||
+ error "could not write to $testfile"
+
+ $LFS mirror resync $testfile ||
+ error "could not resync mirror"
+
+ $LFS mirror verify -v $testfile ||
+ error "verify mirror failed"
+
+ $LFS mirror read -N 1 -o $mirror1 $testfile ||
+ error "could not read from mirror 1"
+
+ cmp -bl $tmpfile $mirror1 ||
+ error "mirror 1 is corrupted"
+
+ $LFS mirror read -N 2 -o $mirror2 $testfile ||
+ error "could not read from mirror 2"
+
+ cmp -bl $tmpfile $mirror2 ||
+ error "mirror 2 is corrupted"
+
+ tr '\0' '2' < /dev/zero |
+ dd of=$tmpfile bs=9000 count=1 conv=fsync
+
+ $LFS mirror write -N 1 -i $tmpfile $testfile ||
+ error "could not write to mirror 1"
+
+ $LFS mirror verify -v $testfile &&
+ error "mirrors should be different"
+
+ rm -f $tmpfile $mirror1 $mirror2
+}
+run_test 52 "Mirrored encrypted file"
+
log "cleanup: ======================================================"
sec_unsetup() {