-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0808e55
commit c4cf39c
Showing
7 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
name: Crash Tests | ||
|
||
on: | ||
push: | ||
pull_request: | ||
workflow_dispatch: | ||
|
||
env: | ||
CARGO_TERM_COLOR: always | ||
|
||
jobs: | ||
linux-crash-test: | ||
name: Linux Crash Test | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
fstype: | ||
- btrfs | ||
- ext3 | ||
- ext4 | ||
- xfs | ||
features: | ||
- '' | ||
- 'unnamed-tmpfile' | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions-rs/toolchain@v1 | ||
with: | ||
toolchain: stable | ||
- name: Install qemu | ||
run: | | ||
sudo apt-get update | ||
sudo apt-get install -y qemu-system-x86 | ||
- name: Install ${{ matrix.fstype }} tools | ||
run: | | ||
sudo apt-get update | ||
case ${{ matrix.fstype }} in | ||
btrfs) sudo apt-get install -y btrfs-progs ;; | ||
ext*) sudo apt-get install -y e2fsprogs ;; | ||
xfs) sudo apt-get install -y xfsprogs ;; | ||
esac | ||
- name: Copy current kernel | ||
run: | | ||
sudo cp "/boot/vmlinuz-$(uname -r)" . | ||
sudo chmod a+r "vmlinuz-$(uname -r)" | ||
- name: Run test | ||
run: | | ||
crash-tests/linux/run-test.sh --kernel="vmlinuz-$(uname -r)" --filesystem-type="${{ matrix.fstype }}" --cargo-features="${{ matrix.features }}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/Cargo.lock | ||
/build | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "atomic-write-file-test" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
atomic-write-file = { path = "../.." } | ||
nix = "0.28.0" | ||
|
||
[features] | ||
unnamed-tmpfile = ["atomic-write-file/unnamed-tmpfile"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Crash tests for Linux | ||
|
||
These are tests to ensure that | ||
[atomic-write-file](https://crates.io/crates/atomic-write-file) holds up to its | ||
promises when a kernel panic occurs on Linux. | ||
|
||
These tests verify that for a file written with atomic-write-file either has | ||
its old contents, or the new ones, and never any intermediate contents. They | ||
work by doing the following inside a [Qemu](https://www.qemu.org/) virtual | ||
machine: | ||
|
||
- initializing an empty filesystem | ||
- writing a file with some initial contents | ||
- using atomic-write-file to write and commit new contents to the file | ||
- triggering a kernel panic | ||
- checking the contents of the file | ||
|
||
The tests support the following filesystem types: | ||
- ext3 | ||
- ext4 | ||
- btrfs | ||
- xfs | ||
|
||
Other filesystems may work as well, but they are currently untested. | ||
|
||
Note that unjournaled filesystems (like ext2) will not work because a kernel | ||
panic will leave them in a broken state. | ||
|
||
## Usage | ||
|
||
Tests are run by the `run-tests.sh` script. To use it with default settings | ||
(current kernel, ext4 filesystem): | ||
|
||
```sh | ||
./run-tests.sh | ||
``` | ||
|
||
Custom settings can be controlled through command line flags, for example: | ||
|
||
```sh | ||
./run-tests.sh --kernel path/to/my-custom-vmlinuz --filesystem-type btrfs --cargo-features unnamed-tmpfile | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/sh -e | ||
|
||
echo 'Mounting /proc' | ||
mount -t proc none /proc | ||
echo 'Mounting /sys' | ||
mount -t sysfs none /sys | ||
echo 'Mounting /test' | ||
fstype=$(grep -Eo 'test.fs=\w+' /proc/cmdline | cut -d= -f2) | ||
modprobe "$fstype" || true | ||
mount -t "$fstype" /dev/sdb /test | ||
|
||
if grep -q test.verify /proc/cmdline; then | ||
echo 'Verifying test file contents' | ||
echo '-----' | ||
xxd /test/file | ||
echo '-----' | ||
poweroff -f | ||
fi | ||
|
||
echo 'Running test binary' | ||
atomic-write-file-test | ||
|
||
echo 'Done' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
#!/bin/bash | ||
|
||
set -o errexit | ||
set -o nounset | ||
set -o pipefail | ||
|
||
status=$'\e[1;32m' | ||
error=$'\e[1;31m' | ||
reset=$'\e[0m' | ||
|
||
qemu=qemu-system-$(uname -m) | ||
kernel=/boot/vmlinuz-$(uname -r) | ||
modules=/usr/lib/modules/$(uname -r) | ||
|
||
busybox=$(which busybox) | ||
init_script=init.sh | ||
|
||
filesystem_type=ext4 | ||
cargo_features= | ||
|
||
build_dir=build | ||
initramfs_build_dir=$build_dir/rootfs | ||
initramfs=$build_dir/rootfs.img | ||
testfs=$build_dir/testfs.img | ||
output=$build_dir/output.txt | ||
test_bin=$build_dir/cargo/release/atomic-write-file-test | ||
|
||
options=$(getopt --name "$0" --options k:m:f:F: --long kernel:,modules:,filesystem-type:,cargo-features: -- "$@") | ||
|
||
eval set -- "$options" | ||
|
||
while true; do | ||
case "$1" in | ||
-k|--kernel) | ||
kernel=$(readlink -f "$2") | ||
shift 2 | ||
;; | ||
-m|--modules) | ||
modules=$(readlink -f "$2") | ||
shift 2 | ||
;; | ||
-f|--filesystem-type) | ||
filesystem_type=$2 | ||
shift 2 | ||
;; | ||
-F|--cargo-features) | ||
cargo_features=$2 | ||
shift 2 | ||
;; | ||
--) | ||
shift | ||
break | ||
;; | ||
esac | ||
done | ||
|
||
if [[ $# -ne 0 ]]; then | ||
echo "$0: extra arguments: $*" >&2 | ||
exit 1 | ||
fi | ||
|
||
cd "$(dirname "$(readlink -f "$0")")" | ||
|
||
# | ||
# Compile the test binary running atomic-write-file | ||
# | ||
# This needs to be a statically-linked binary because the root filesystem won't | ||
# ship with libc | ||
# | ||
|
||
echo "${status}Compiling${reset} static binary at $test_bin" | ||
RUSTFLAGS='-C target-feature=+crt-static' CARGO_TARGET_DIR=$build_dir/cargo cargo build --release --features "$cargo_features" | ||
|
||
# | ||
# Create the root filesystem and put it into an initramfs | ||
# | ||
# The root filesystem contains: | ||
# - busybox (to provide /bin/sh and other basic tools) | ||
# - the test binary | ||
# - an init script | ||
# - kernel modules (to support uncommon filesystems like btrfs) | ||
# | ||
|
||
echo "${status}Building${reset} initramfs at $initramfs" | ||
echo " Using busybox at $busybox" | ||
echo " Using modules at $modules" | ||
echo " Using $init_script as /sbin/init" | ||
|
||
rm -rf "$initramfs" "$initramfs_build_dir" | ||
|
||
echo " ${status}Creating${reset} filesystem" | ||
|
||
mkdir -p \ | ||
"$initramfs_build_dir/bin" \ | ||
"$initramfs_build_dir/dev" \ | ||
"$initramfs_build_dir/lib" \ | ||
"$initramfs_build_dir/proc" \ | ||
"$initramfs_build_dir/sbin" \ | ||
"$initramfs_build_dir/sys" \ | ||
"$initramfs_build_dir/test" \ | ||
"$initramfs_build_dir/usr" | ||
|
||
ln -s ../bin "$initramfs_build_dir/usr/bin" | ||
ln -s ../lib "$initramfs_build_dir/usr/lib" | ||
|
||
cp "$init_script" -T "$initramfs_build_dir/sbin/init" | ||
cp "$test_bin" -t "$initramfs_build_dir/bin" | ||
cp "$busybox" -t "$initramfs_build_dir/bin" | ||
"$busybox" --install -s "$initramfs_build_dir/bin" | ||
|
||
echo " ${status}Adding${reset} uncompressed kernel modules" | ||
|
||
mkdir -p "$initramfs_build_dir/lib/modules" | ||
cp -a "$modules" -t "$initramfs_build_dir/lib/modules" | ||
|
||
include_mods=( "$filesystem_type" ) | ||
|
||
for mod in "${include_mods[@]}"; do | ||
while read -r mod_path; do | ||
if [[ "$mod_path" = *.ko.gz ]]; then | ||
gunzip "$mod_path" -o "$mod_path.uncompressed" | ||
elif [[ "$mod_path" = *.ko.xz ]]; then | ||
unxz "$mod_path" -o "$mod_path.uncompressed" | ||
elif [[ "$mod_path" = *.ko.zst ]]; then | ||
unzstd "$mod_path" -o "$mod_path.uncompressed" | ||
else | ||
continue | ||
fi | ||
mv "$mod_path.uncompressed" "$mod_path" | ||
done < <(modprobe --dirname "$initramfs_build_dir" --show-depends "$mod" | grep ^insmod | cut -d' ' -f2) | ||
done | ||
|
||
echo " ${status}Creating${reset} squashfs file" | ||
|
||
mksquashfs "$initramfs_build_dir" "$initramfs" | ||
|
||
# | ||
# Create the filesystem used for testing | ||
# | ||
|
||
echo "${status}Creating${reset} test $filesystem_type filesystem at $testfs" | ||
|
||
dd if=/dev/zero of="$testfs" bs=1M count=200 | ||
"mkfs.$filesystem_type" "$testfs" | ||
|
||
# | ||
# Run the virtual machine | ||
# | ||
# This will create a file with atomic-write-file and then trigger a kernel | ||
# panic | ||
# | ||
|
||
echo "${status}Running${reset} virtual machine to write test file" | ||
echo " Using qemu at $qemu" | ||
echo " Using kernel at $kernel" | ||
echo " Using initramfs at $initramfs" | ||
|
||
"$qemu" \ | ||
-kernel "$kernel" \ | ||
-append "root=/dev/sda ro panic=-1 console=ttyS0 quiet test.fs=$filesystem_type" \ | ||
-drive "index=0,media=disk,format=raw,file=$initramfs" \ | ||
-drive "index=1,media=disk,format=raw,file=$testfs" \ | ||
-no-reboot \ | ||
-nographic | ||
|
||
# | ||
# Run the virtual machine, again, to verify the file contents | ||
# | ||
# We could avoid spawning a virtual machine by using tools like debugfs, but | ||
# these tools are not available for all filesystem types (or at least I'm not | ||
# aware of them). | ||
# | ||
|
||
echo "${status}Re-running${reset} virtual machine to verify test file contents" | ||
echo " Using qemu at $qemu" | ||
echo " Using kernel at $kernel" | ||
echo " Using initramfs at $initramfs" | ||
|
||
"$qemu" \ | ||
-kernel "$kernel" \ | ||
-append "root=/dev/sda ro panic=-1 console=ttyS0 quiet test.fs=$filesystem_type test.verify" \ | ||
-drive "index=0,media=disk,format=raw,file=$initramfs" \ | ||
-drive "index=1,media=disk,format=raw,file=$testfs" \ | ||
-no-reboot \ | ||
-nographic \ | ||
| tee "$output" | ||
|
||
if [[ $(sed -n '/-----/, /-----/p' "$output" | xxd -r) = hello ]]; then | ||
echo "${status}Success${reset}" | ||
else | ||
echo "${error}Failure${reset}" | ||
exit 1 | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use atomic_write_file::AtomicWriteFile; | ||
use nix::fcntl::open; | ||
use nix::fcntl::OFlag; | ||
use nix::sys::stat::Mode; | ||
use nix::unistd::close; | ||
use nix::unistd::fsync; | ||
use std::io::Write; | ||
|
||
fn main() { | ||
let mut file = AtomicWriteFile::open("/test/file").expect("open failed"); | ||
file.write_all(b"hello").expect("write failed"); | ||
file.commit().expect("commit failed"); | ||
|
||
let dir = open( | ||
"/test", | ||
OFlag::O_DIRECTORY | OFlag::O_CLOEXEC, | ||
Mode::empty(), | ||
) | ||
.expect("open directory failed"); | ||
|
||
fsync(dir).expect("directory sync failed"); | ||
close(dir).expect("closing directory failed"); | ||
} |