|
| 1 | +#!/bin/bash |
| 2 | +# SPDX-License-Identifier: GPL-3.0+ |
| 3 | +# Copyright (C) 2025 Google LLC |
| 4 | +# |
| 5 | +# Test the combination of inline encryption and bio splitting. About the values |
| 6 | +# of the parameters in this test: |
| 7 | +# - The zone size is 4 MiB and is larger than max_sectors_kb. |
| 8 | +# - The maximum bio size supported by the code in block/blk-crypto-fallback.c |
| 9 | +# is BIO_MAX_VECS * PAGE_SIZE or 1 MiB if the page size is 4 KiB. |
| 10 | +# - max_sectors_kb has been set to 512 KiB to cause further bio splitting. |
| 11 | +# |
| 12 | +# Without these two commits, this test triggers an I/O error: |
| 13 | +# - Commit e3290419d9be ("blk-crypto: convert to use |
| 14 | +# bio_submit_split_bioset()"). |
| 15 | +# - Commit b2f5974079d8 ("block: fix ordering of recursive split IO"). |
| 16 | + |
| 17 | +. tests/zbd/rc |
| 18 | +. common/null_blk |
| 19 | + |
| 20 | +DESCRIPTION="test inline encryption and bio splitting" |
| 21 | + |
| 22 | +requires() { |
| 23 | + _have_driver f2fs |
| 24 | + _have_driver null_blk |
| 25 | + _have_program fscrypt |
| 26 | + _have_program getconf |
| 27 | + _have_program mkfs.f2fs |
| 28 | + for o in BLK_INLINE_ENCRYPTION_FALLBACK FS_ENCRYPTION_INLINE_CRYPT; do |
| 29 | + if ! _check_kernel_option "$o"; then |
| 30 | + SKIP_REASONS+=("Kernel option $o has not been enabled") |
| 31 | + fi |
| 32 | + done |
| 33 | + if [ ! -e /etc/fscrypt.conf ]; then |
| 34 | + SKIP_REASONS+=("/etc/fscrypt.conf is missing. 'fscrypt setup' must be run first.") |
| 35 | + fi |
| 36 | +} |
| 37 | + |
| 38 | +# Start block I/O tracing with filter "$1". |
| 39 | +trace_block_io() { |
| 40 | + if [ -e /sys/kernel/tracing/tracing_on ]; then |
| 41 | + ( |
| 42 | + set -e |
| 43 | + cd /sys/kernel/tracing |
| 44 | + lsof -t trace_pipe | xargs -r kill |
| 45 | + echo 1024 > buffer_size_kb |
| 46 | + echo nop > current_tracer |
| 47 | + echo 0 > tracing_on |
| 48 | + echo > trace |
| 49 | + echo 0 > events/enable |
| 50 | + echo 1 > events/block/enable |
| 51 | + echo 0 > events/block/block_dirty_buffer/enable |
| 52 | + echo 0 > events/block/block_plug/enable |
| 53 | + echo 0 > events/block/block_touch_buffer/enable |
| 54 | + echo 0 > events/block/block_unplug/enable |
| 55 | + # Set filter "$1" for all enabled block tracing events. |
| 56 | + grep -lw 1 events/block/*/enable | |
| 57 | + while read -r event_path; do |
| 58 | + filter_path="${event_path%enable}filter" |
| 59 | + echo "$1" > "$filter_path" |
| 60 | + done |
| 61 | + echo 1 > tracing_on |
| 62 | + echo "Tracing has been enabled" >>"$FULL" |
| 63 | + cat trace_pipe > "${FULL%.full}-block-trace.txt" |
| 64 | + ) |
| 65 | + fi |
| 66 | +} |
| 67 | + |
| 68 | +# Wait until trace_block_io() has enabled tracing. |
| 69 | +wait_until_tracing_started() { |
| 70 | + if [ -e /sys/kernel/tracing/tracing_on ]; then |
| 71 | + while [ "$(</sys/kernel/tracing/tracing_on)" = 0 ]; do |
| 72 | + sleep .1 |
| 73 | + done |
| 74 | + fi |
| 75 | +} |
| 76 | + |
| 77 | +# Stop tracing. $1 is the tracing PID. |
| 78 | +stop_tracing() { |
| 79 | + if [ -e /sys/kernel/tracing/tracing_on ]; then |
| 80 | + kill "$1" |
| 81 | + ( |
| 82 | + set -e |
| 83 | + cd /sys/kernel/tracing |
| 84 | + echo 0 > tracing_on |
| 85 | + echo 0 > events/enable |
| 86 | + ) |
| 87 | + fi |
| 88 | +} |
| 89 | + |
| 90 | +# Report block I/O statistics. $1 is the output prefix. $2 and $3 are before and |
| 91 | +# after statistics that come from /sys/class/block/.../stat. |
| 92 | +report_stats() { |
| 93 | + local pfx=$1 before after |
| 94 | + read -r -a before <<<"$2" |
| 95 | + read -r -a after <<<"$3" |
| 96 | + local reads=$((after[0]-before[0])) |
| 97 | + local rmerge=$((after[1]-before[1])) |
| 98 | + local writes=$((after[4]-before[4])) |
| 99 | + local wmerge=$((after[5]-before[5])) |
| 100 | + echo "$pfx reads: $reads rmerge: $rmerge writes: $writes wmerge: $wmerge" |
| 101 | +} |
| 102 | + |
| 103 | +# Convert block device name $1 into a Linux kernel device number. |
| 104 | +devno() { |
| 105 | + IFS=: read -r maj min <"/sys/class/block/$(basename "$1")/dev" |
| 106 | + # From <linux/kdev_t.h>: MINORBITS=20. |
| 107 | + echo $(((maj << 20) + min)) |
| 108 | +} |
| 109 | + |
| 110 | +run_test() { |
| 111 | + # From <linux/bio.h>: BIO_MAX_VECS=256. |
| 112 | + local bio_max_vecs=256 |
| 113 | + |
| 114 | + local page_size |
| 115 | + page_size=$(getconf PAGE_SIZE) |
| 116 | + |
| 117 | + # In bytes. |
| 118 | + local max_inl_encr_bio_size=$((bio_max_vecs * page_size)) |
| 119 | + |
| 120 | + if umount /dev/nullb1 >>"$FULL" 2>&1; then :; fi |
| 121 | + _remove_null_blk_devices |
| 122 | + |
| 123 | + # A small conventional block device for the F2FS metadata. |
| 124 | + local null_blk_params=( |
| 125 | + blocksize=4096 |
| 126 | + discard=1 |
| 127 | + max_sectors=$(((1 << 32) - 1)) |
| 128 | + memory_backed=1 |
| 129 | + size=64 # MiB |
| 130 | + submit_queues=1 |
| 131 | + power=1 |
| 132 | + ) |
| 133 | + _configure_null_blk nullb1 "${null_blk_params[@]}" |
| 134 | + local cdev=/dev/nullb1 |
| 135 | + |
| 136 | + # A larger zoned block device for the data. |
| 137 | + local null_blk_params=( |
| 138 | + blocksize=4096 |
| 139 | + completion_nsec=10000000 # 10 ms |
| 140 | + irqmode=2 |
| 141 | + # Half of the maximum bio size supported by the inline |
| 142 | + # encryption fallback code. |
| 143 | + max_sectors=$((max_inl_encr_bio_size >> 10)) |
| 144 | + memory_backed=1 |
| 145 | + hw_queue_depth=1 |
| 146 | + size=1024 # MiB |
| 147 | + submit_queues=1 |
| 148 | + # Four times the maximum bio size supported by the inline |
| 149 | + # encryption fallback code. |
| 150 | + zone_size="$(((4 * max_inl_encr_bio_size) >> 20))" |
| 151 | + zoned=1 |
| 152 | + power=1 |
| 153 | + ) |
| 154 | + _configure_null_blk nullb2 "${null_blk_params[@]}" |
| 155 | + local zdev_basename=nullb2 |
| 156 | + local zdev=/dev/${zdev_basename} |
| 157 | + local zdev_devno |
| 158 | + zdev_devno=$(devno "$zdev") |
| 159 | + |
| 160 | + { |
| 161 | + ls -ld "${cdev}" "${zdev}" |
| 162 | + echo "${zdev_basename} settings:" |
| 163 | + ( |
| 164 | + cd "/sys/class/block/$zdev_basename/queue" && |
| 165 | + grep -vw 0 ./* |
| 166 | + ) || true |
| 167 | + } >>"${FULL}" 2>&1 |
| 168 | + |
| 169 | + trace_block_io "dev == ${zdev_devno}" & |
| 170 | + echo $! > "${trace_pid_file}" |
| 171 | + wait_until_tracing_started |
| 172 | + |
| 173 | + { |
| 174 | + mkfs.f2fs -q -O encrypt -m "${cdev}" -c "${zdev}" |
| 175 | + mkdir -p "${mount_dir}" |
| 176 | + mount -o inlinecrypt -t f2fs "${cdev}" "${mount_dir}" |
| 177 | + local encrypted_dir="${mount_dir}/encrypted" |
| 178 | + mkdir -p "${encrypted_dir}" |
| 179 | + fscrypt setup "${mount_dir}" </dev/null |
| 180 | + local keyfile=$TMPDIR/keyfile |
| 181 | + dd if=/dev/zero of="$keyfile" bs=32 count=1 status=none |
| 182 | + fscrypt encrypt "${encrypted_dir}" --source=raw_key \ |
| 183 | + --name=protector --key="$keyfile" |
| 184 | + fscrypt status "${encrypted_dir}" |
| 185 | + |
| 186 | + local before after |
| 187 | + read -r -a before <"/sys/class/block/${zdev_basename}/stat" |
| 188 | + echo "Starting IO" |
| 189 | + local cmd="dd if=/dev/zero of=${encrypted_dir}/file bs=64M count=15 conv=fdatasync status=none" |
| 190 | + echo "$cmd" |
| 191 | + } >>"$FULL" 2>&1 |
| 192 | + eval "$cmd" |
| 193 | + { |
| 194 | + ls -ld "${mount_dir}/encrypted/file" |
| 195 | + read -r -a after <"/sys/class/block/${zdev_basename}/stat" |
| 196 | + report_stats "zdev:" "${before[*]}" "${after[*]}" |
| 197 | + } >>"$FULL" 2>&1 |
| 198 | +} |
| 199 | + |
| 200 | +test() { |
| 201 | + echo "Running ${TEST_NAME}" |
| 202 | + |
| 203 | + # Global variables. |
| 204 | + mount_dir="$TMPDIR/mnt" |
| 205 | + trace_pid_file="$TMPDIR/trace_pid" |
| 206 | + |
| 207 | + ( |
| 208 | + set -e |
| 209 | + run_test |
| 210 | + ) |
| 211 | + # shellcheck disable=SC2181 |
| 212 | + (($? != 0)) && fail=true |
| 213 | + |
| 214 | + umount "${mount_dir}" >>"${FULL}" 2>&1 |
| 215 | + local trace_pid |
| 216 | + trace_pid=$(cat "${trace_pid_file}" 2>/dev/null) |
| 217 | + [ -n "${trace_pid}" ] && stop_tracing "${trace_pid}" |
| 218 | + _exit_null_blk |
| 219 | + |
| 220 | + if [ -z "$fail" ]; then |
| 221 | + echo "Test complete" |
| 222 | + else |
| 223 | + echo "Test failed" |
| 224 | + return 1 |
| 225 | + fi |
| 226 | +} |
0 commit comments