ext4: test journal checkpoint ioctl
authorLeah Rumancik <leah.rumancik@gmail.com>
Mon, 7 Jun 2021 21:55:09 +0000 (21:55 +0000)
committerEryu Guan <guaneryu@gmail.com>
Sun, 13 Jun 2021 15:39:50 +0000 (23:39 +0800)
Test for commit "ext4: add ioctl EXT4_IOC_CHECKPOINT". Tests journal
checkpointing and journal erasing via EXT4_IOC_CHECKPOINT with flag
EXT4_IOC_CHECKPOINT_FLAG_ZEROOUT set.

Signed-off-by: Leah Rumancik <leah.rumancik@gmail.com>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
.gitignore
src/Makefile
src/checkpoint_journal.c [new file with mode: 0644]
tests/ext4/050 [new file with mode: 0755]
tests/ext4/050.out [new file with mode: 0644]
tests/ext4/group

index c62c1556f3a94a795c2b6eb11cc0c010ca634b52..d3194e76a779cb0d571d97d438bc8685237fff27 100644 (file)
@@ -61,6 +61,7 @@ tags
 /src/bulkstat_null_ocount
 /src/bulkstat_unlink_test
 /src/bulkstat_unlink_test_modified
+/src/checkpoint_journal
 /src/chprojid_fail
 /src/cloner
 /src/dbtest
index 1279e4b99d62ce6de9ed284a4baa75e9ba49af67..5c76aa93db5c0bc003519b80d183a9abfbaefd3b 100644 (file)
@@ -18,7 +18,7 @@ TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
        t_ext4_dax_journal_corruption t_ext4_dax_inline_corruption \
        t_ofd_locks t_mmap_collision mmap-write-concurrent \
        t_get_file_time t_create_short_dirs t_create_long_dirs t_enospc \
-       t_mmap_writev_overlap
+       t_mmap_writev_overlap checkpoint_journal
 
 LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
        preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \
diff --git a/src/checkpoint_journal.c b/src/checkpoint_journal.c
new file mode 100644 (file)
index 0000000..a4a6b69
--- /dev/null
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Google
+ * All Rights Reserved.
+ *
+ * checkpoint_journal.c
+ *
+ * Flush journal log and checkpoint journal for ext4 file system and
+ * optionally, issue discard or zeroout for the journal log blocks.
+ *
+ * Arguments:
+ * 1) mount point for device
+ * 2) flags (optional)
+ *     set --erase=discard to enable discarding journal blocks
+ *     set --erase=zeroout to enable zero-filling journal blocks
+ *     set --dry-run flag to only perform input checking
+ */
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <linux/fs.h>
+#include <getopt.h>
+
+#if !defined(EXT4_IOC_CHECKPOINT)
+#define EXT4_IOC_CHECKPOINT    _IOW('f', 43, __u32)
+#endif
+
+#if !defined(EXT4_IOC_CHECKPOINT_FLAG_DISCARD)
+#define EXT4_IOC_CHECKPOINT_FLAG_DISCARD               1
+#define EXT4_IOC_CHECKPOINT_FLAG_ZEROOUT               2
+#define EXT4_IOC_CHECKPOINT_FLAG_DRY_RUN               4
+#endif
+
+int main(int argc, char** argv) {
+       int fd, c, ret = 0, option_index = 0;
+       char* rpath;
+       unsigned int flags = 0;
+
+       static struct option long_options[] =
+       {
+               {"dry-run", no_argument, 0, 'd'},
+               {"erase", required_argument, 0, 'e'},
+               {0, 0, 0, 0}
+       };
+
+       /* get optional flags */
+       while ((c = getopt_long(argc, argv, "de:", long_options,
+                               &option_index)) != -1) {
+               switch (c) {
+               case 'd':
+                       flags |= EXT4_IOC_CHECKPOINT_FLAG_DRY_RUN;
+                       break;
+               case 'e':
+                       if (strcmp(optarg, "discard") == 0) {
+                               flags |= EXT4_IOC_CHECKPOINT_FLAG_DISCARD;
+                       } else if (strcmp(optarg, "zeroout") == 0) {
+                               flags |= EXT4_IOC_CHECKPOINT_FLAG_ZEROOUT;
+                       } else {
+                               fprintf(stderr, "Error: invalid erase option\n");
+                               return 1;
+                       }
+                       break;
+               default:
+                       return 1;
+               }
+       }
+
+       if (optind != argc - 1) {
+               fprintf(stderr, "Error: invalid number of arguments\n");
+               return 1;
+       }
+
+       /* get fd to file system */
+       rpath = realpath(argv[optind], NULL);
+       fd = open(rpath, O_RDONLY);
+       free(rpath);
+
+       if (fd == -1) {
+               fprintf(stderr, "Error: unable to open device %s: %s\n",
+                       argv[optind], strerror(errno));
+               return 1;
+       }
+
+       ret = ioctl(fd, EXT4_IOC_CHECKPOINT, &flags);
+
+       if (ret)
+               fprintf(stderr, "checkpoint ioctl returned error: %s\n", strerror(errno));
+
+       close(fd);
+       return ret;
+}
+
diff --git a/tests/ext4/050 b/tests/ext4/050
new file mode 100755 (executable)
index 0000000..c56623a
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Google, Inc. All Rights Reserved.
+#
+# FS QA Test No. 050
+#
+# Test checkpoint and zeroout of journal via ioctl EXT4_IOC_CHECKPOINT
+#
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+status=1       # failure is the default!
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs ext4
+
+_require_scratch
+_require_command "$DEBUGFS_PROG" debugfs
+
+checkpoint_journal=$here/src/checkpoint_journal
+_require_test_program "checkpoint_journal"
+
+# convert output from stat<journal_inode> to list of block numbers
+get_journal_extents() {
+       inode_info=$($DEBUGFS_PROG $SCRATCH_DEV -R "stat <8>" 2>> $seqres.full)
+       echo -e "\nJournal info:" >> $seqres.full
+       echo "$inode_info" >> $seqres.full
+
+       extents_line=$(echo "$inode_info" | awk '/EXTENTS:/{ print NR; exit }')
+       get_extents=$(echo "$inode_info" | sed -n "$(($extents_line + 1))"p)
+
+       # get just the physical block numbers
+       get_extents=$(echo "$get_extents" |  perl -pe 's|\(.*?\):||g' | sed -e 's/, /\n/g' | perl -pe 's|(\d+)-(\d+)|\1 \2|g')
+
+       echo "$get_extents"
+}
+
+
+# checks all extents are zero'd out except for the superblock
+# arg 1: extents (output of get_journal_extents())
+check_extents() {
+       echo -e "\nChecking extents:" >> $seqres.full
+       echo "$1" >> $seqres.full
+
+       super_block="true"
+       echo "$1" | while IFS= read line; do
+               start_block=$(echo $line | cut -f1 -d' ')
+               end_block=$(echo $line | cut -f2 -d' ' -s)
+
+               # if first block of journal, shouldn't be wiped
+               if [ "$super_block" == "true" ]; then
+                       super_block="false"
+
+                       #if super block only block in this extent, skip extent
+                       if [ -z "$end_block" ]; then
+                               continue;
+                       fi
+                       start_block=$(($start_block + 1))
+               fi
+
+               if [ ! -z "$end_block" ]; then
+                       blocks=$(($end_block - $start_block + 1))
+               else
+                       blocks=1
+               fi
+
+               check=$(od $SCRATCH_DEV --skip-bytes=$(($start_block * $blocksize)) --read-bytes=$(($blocks * $blocksize)) -An -v | sed -e 's/[0 \t\n\r]//g')
+
+               [ ! -z "$check" ] && echo "error" && break
+       done
+}
+
+testdir="${SCRATCH_MNT}/testdir"
+
+_scratch_mkfs_sized $((64 * 1024 * 1024)) >> $seqres.full 2>&1
+_require_metadata_journaling $SCRATCH_DEV
+_scratch_mount >> $seqres.full 2>&1
+blocksize=$(_get_block_size $SCRATCH_MNT)
+mkdir $testdir
+
+# check if ioctl present, skip test if not present
+$checkpoint_journal $SCRATCH_MNT --dry-run || _notrun "journal checkpoint ioctl not present on device"
+
+# create some files to add some entries to journal
+for i in {1..100}; do
+       echo > $testdir/$i
+done
+
+# make sure these files get to the journal
+sync --file-system $testdir/1
+
+# call ioctl to checkpoint and zero-fill journal blocks
+$checkpoint_journal $SCRATCH_MNT --erase=zeroout || _fail "ioctl returned error"
+
+extents=$(get_journal_extents)
+
+# check journal blocks zeroed out
+ret=$(check_extents "$extents")
+[ "$ret" = "error" ] && _fail "Journal was not zero-filled"
+
+_scratch_unmount >> $seqres.full 2>&1
+
+echo "Silence is golden"
+
+status=0
+exit
diff --git a/tests/ext4/050.out b/tests/ext4/050.out
new file mode 100644 (file)
index 0000000..73794d8
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 050
+Silence is golden
index c246feb8e08b177d72689c60fe8a260eda32d8a9..16eee914a1fec4508e31f9935dfb6450166103a5 100644 (file)
@@ -52,6 +52,7 @@
 047 auto quick dax
 048 auto quick dir
 049 auto quick
+050 auto ioctl quick
 271 auto rw quick
 301 aio auto ioctl rw stress defrag
 302 aio auto ioctl rw stress defrag