From d02fc5f386664d19c9d791cd60e10c207ad7614c Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 06:03:41 +0100 Subject: [PATCH 1/7] Provide a detailed description of design considerations and future plans --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/README.md b/README.md index 6dfebd5..94f7535 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,14 @@ Args: -V, --version Print version ``` +We also provide a manpage, which targets OpenBSD-level quality. Incorrect or +outdated information is a bug. + ## Testing +For testing, there is a shell script "test.sh" included. It sets up a block dev +and writes (and verifies) a bunch of nonsense to it. It boils down to: + ``` dd if=/dev/zero of=./disk.img bs=1M count=1024 losetup -fP ./disk.img @@ -30,6 +36,75 @@ losetup -a losedup -d /dev/loop0 ``` +Writes to a regular files is not currently in scope, although it would simplify +testing. + +## Design Considerations + +Most of the sanity checking is currently highly Linux specific. We should +prefer general/posix solutions that reach **at least** FreeBSD, preferably +OpenBSD and Solaris as well. FreeBSD did implement procfs, but its a Linux-ism +and it has since been deprecated. I would prefer not to turn this rather simple +code into macro-mozaic, as i've seen other similar projects do. After all, this +is just a juiced-up dd-rewrite at its core. + +**Apple products are unsupported.** Im simply not interested in ensuring +compatibility with a walled-garden ecosystem. If *you* are, we can change that. + +At the time of writing, my FreeBSD server is down for maintenance, which means +all of my development and testing is focused on AMD64, AArch64 and AArch32 +Linux. + +In the setup phase of the program, we can absolutely afford to do lots of +sanity checking via syscalls. Between each block write, we flush the disk +buffers. These flushes are larger (1 MiB when buffer is full) than the sector +size (often 4k or 64k) of the flash, so as far as i know, this is a gentle way +to write flash, and should not incur any significant performance overhead. 1 +MiB is also a multiple of the most common sector sizes. We could write it all +with no flushing, but that would mean the progress indicator will measure +buffered writes, which is useless. + +It should be possible to induce optimal block size, but this ide has not been +explored yet. + +Currently when verifying the written data, we read from both the input file and +the output block device and do a byte-by-byte comparison. A CRC32 is also +calculated in the first pass of the input file read. In the second pass (the +verification stage), we calculate the CRC32 of the block device data and +compare that to our previous result. **This means that we currently use two +separate methods of verification**. The program allocates **two** separate +buffers in the startup phase for comparisons. This will change in the coming +releases, and we will rely only on the CRC. + +De-allocation is handled in the interrupt vector as well as in the 'sad-paths', +but ultimately this program can be regarded as samurai-principled. We try to +handle deallocation, but exit on failure and let the kernel handle the rest. + +The use of a crypto-grade checksumming algorithm was considered, but was +ultimately rejected in favour of a lookup based CRC32. Its simpler, faster and +easier to understand (See: [crc32.h](./crc32.h)). We may include a compile-time +option to disable the lookup table to reduce size for really small targets, but +we speculate that those targets are already satisfied with busybox-dd. + +We also considered using libudev or any of its analogues, to determine the type +of block device (spinning or flash), but my somewhat inconclusive research +indicates that it does not include functionality to determine the medium type +(usb/sd/sata), which is ultimately what i would like to warn the user about. +The libudev library is also Linux specific. + +We also need completion scripts for the most common shells. This includes csh, +bash and zsh. Should be easy enough when we set our minds to it. + +See: + - `grep -nE 'BLK[A-Za-z0-9]+' /usr/include/linux/fs.h` + In particular, we're interested in BLKGETSIZE64 and BLKFLSBUF for now. + +We can read the device info from: + - /sys/class/block/[name]/* +Like: + - /sys/class/block/[name]/device/model + - /sys/class/block/[name]/size + ## Inspiration See: From 5e9245f059b23aa6a4eb9bece595b1ede919abb6 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 06:05:02 +0100 Subject: [PATCH 2/7] Bump the help section in the readme to latest version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94f7535..069db9d 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ This tool verifies your image automatically. The block size is currently set to 1 MiB, which should be enough to avoid trouble. ``` -writeimg v0.2.0, Rev. 5323932-dirty +WriteIMG v0.2.3, Rev. d02fc5f In honor of SwePwnage - the OG disk destroyer Copyright (C) 2026 Imbus, BSD-2-Clause -Build date: 2026-02-07 +Build date: 2026-02-21 Usage: writeimg [-v] -d From febcedc2f3cf6b338662c4042fdf5aeffc544864 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 06:11:40 +0100 Subject: [PATCH 3/7] Provide a more descriptive introduction and a summary of writeimg's features --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 069db9d..0c30f7c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# Simple dd-like image writer with additional security checks. +# WriteIMG Image Writer -This tool verifies your image automatically. The block size is currently set to -1 MiB, which should be enough to avoid trouble. +WriteIMG is a simple dd-like image writer with additional security checks for +Linux and soon FreeBSD. It tells you what you're about to do and warns you +about potential problems before they occur. It also features automatic image +verifications, and uses a suitable block size for fast and gentle writing. + +Say goodbye to fried flash and overwritten bootloaders, no more footguns! ``` WriteIMG v0.2.3, Rev. d02fc5f From 4b853f9c1651cd037b46df9aa4ca4b647fd10438 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 06:17:07 +0100 Subject: [PATCH 4/7] Spelling errors --- README.md | 4 ++-- writeimg.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c30f7c..f864c59 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Args: -v Verify only -d device Target block device -h, --help Print this help message - -n, --noconfirm Do not ask for premission + -n, --noconfirm Do not ask for permission -V, --version Print version ``` @@ -68,7 +68,7 @@ MiB is also a multiple of the most common sector sizes. We could write it all with no flushing, but that would mean the progress indicator will measure buffered writes, which is useless. -It should be possible to induce optimal block size, but this ide has not been +It should be possible to induce optimal block size, but this idea has not been explored yet. Currently when verifying the written data, we read from both the input file and diff --git a/writeimg.c b/writeimg.c index e839930..f0139ad 100644 --- a/writeimg.c +++ b/writeimg.c @@ -72,7 +72,7 @@ const char help[] = " -v Verify only\n" " -d device Target block device\n" " -h, --help Print this help message\n" - " -n, --noconfirm Do not ask for premission\n" + " -n, --noconfirm Do not ask for permission\n" " -V, --version Print version\n" "\0"; // clang-format on From 8601db8811a54717719702effe3bed94bc2ce86f Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 07:24:25 +0100 Subject: [PATCH 5/7] Initial bash completion --- Makefile | 7 +++++++ writeimg_completion_bash.sh | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 writeimg_completion_bash.sh diff --git a/Makefile b/Makefile index eb6a5cd..7663e96 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,15 @@ install: writeimg install -m 0755 $< $(DESTDIR)$(PREFIX)/bin/$< install -m 0644 $<.1 $(DESTDIR)$(PREFIX)/share/man/man1/$<.1 +# Note that this bypasses PREFIX, since +# bash does not source /usr/local by default +install_bash: writeimg_completion_bash.sh + install -d $(DESTDIR)/etc/bash_completion.d + install -m 0644 $< $(DESTDIR)/etc/bash_completion.d/$< + uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/writeimg rm -f $(DESTDIR)$(PREFIX)/share/man/man1/writeimg.1 + rm -f $(DESTDIR)/etc/bash_completion.d/writeimg* .PHONY: clean install uninstall diff --git a/writeimg_completion_bash.sh b/writeimg_completion_bash.sh new file mode 100644 index 0000000..305cf95 --- /dev/null +++ b/writeimg_completion_bash.sh @@ -0,0 +1,25 @@ +_writeimg_completion() { + local cur prev opts + + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + opts="-v -d -h --help -n --noconfirm -V --version" + + # Devices + if [[ "$prev" == "-d" ]]; then + COMPREPLY=( $(compgen -W "$(ls -d /dev/sd* /dev/nvme* /dev/mmcblk* 2>/dev/null)" -- "$cur") ) + return 0 + fi + + # Flags + if [[ "$cur" != -* ]]; then + COMPREPLY=( $(compgen -f -- "$cur") ) + return 0 + fi + + # Files + COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) +} + +complete -F _writeimg_completion writeimg From d90c05ca77f4ddea252cbb0b899098b97747ed81 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 08:05:09 +0100 Subject: [PATCH 6/7] Tests: test.sh does not use sudo internally, now runs under alpine with musl --- test.sh | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/test.sh b/test.sh index 70cfae9..1eb6280 100644 --- a/test.sh +++ b/test.sh @@ -4,13 +4,17 @@ set -e # set -x # For debugging # If you ever mess this up: -# $ sudo mknod /dev/loop-control c 10 237 -# $ sudo chmod 600 /dev/loop-control -# $ sudo chown root:root /dev/loop-control +# $ mknod /dev/loop-control c 10 237 +# $ chmod 600 /dev/loop-control +# $ chown root:root /dev/loop-control # # For cleanup: -# $ sudo find /dev -maxdepth 1 -type b -name 'loop[0-9]*' -exec rm -f {} \; +# $ find /dev -maxdepth 1 -type b -name 'loop[0-9]*' -exec rm -f {} \; +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root. We need permissions to write and setup a loop device." + exit 1 +fi DISKFILE="/tmp/disk.img" BINFILE="/tmp/file.bin" @@ -22,9 +26,9 @@ echo "Using device: ${LOOPDEV}" cleanup() { echo "Cleaning up..." set +e -x - sudo losetup -d ${LOOPDEV} - sudo rm ${LOOPDEV} - sudo rm ${BINFILE} ${DISKFILE} + losetup -d ${LOOPDEV} + rm ${LOOPDEV} + rm ${BINFILE} ${DISKFILE} } trap cleanup EXIT INT TERM @@ -36,28 +40,31 @@ if losetup ${LOOPDEV} >/dev/null 2>&1; then fi if [ ! -f ${DISKFILE} ]; then + echo "Creating ${DISKFILE}" dd if=/dev/zero of=${DISKFILE} bs=1M count=256 fi if [ ! -f ${BINFILE} ]; then + echo "Creating ${BINFILE}" dd if=/dev/urandom of=${BINFILE} bs=1M count=64 fi if [ ! -e ${LOOPDEV} ]; then - sudo losetup ${LOOPDEV} ${DISKFILE} + mknod ${LOOPDEV} b 7 ${LOOPNUM} # busybox needs this + losetup ${LOOPDEV} ${DISKFILE} fi -sudo ./writeimg -nd ${LOOPDEV} ${BINFILE} -sudo ./writeimg -vnd ${LOOPDEV} ${BINFILE} +./writeimg -nd ${LOOPDEV} ${BINFILE} +./writeimg -vnd ${LOOPDEV} ${BINFILE} -sudo ./writeimg -nd ${LOOPDEV} ./writeimg -sudo ./writeimg -vnd ${LOOPDEV} ./writeimg +./writeimg -nd ${LOOPDEV} ./writeimg +./writeimg -vnd ${LOOPDEV} ./writeimg -sudo ./writeimg -nd ${LOOPDEV} ./LICENSE -sudo ./writeimg -vnd ${LOOPDEV} ./LICENSE +./writeimg -nd ${LOOPDEV} ./LICENSE +./writeimg -vnd ${LOOPDEV} ./LICENSE # Redirect this to avoid confusion -! sudo ./writeimg -vnd ${LOOPDEV} ./crc32.h 2>/dev/null +! ./writeimg -vnd ${LOOPDEV} ./crc32.h 2>/dev/null GREEN="\e[32m" RESET="\e[0m" From 1ca6f8dfdf0f62051839ee46d28038570212352e Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Feb 2026 08:06:37 +0100 Subject: [PATCH 7/7] Fix warning in musl related to ioctl Ioctl's in musl take ints, while glibc take unsigned longs. When not using glibc, default to casting the ioctl number to an int. Tests passing. --- writeimg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/writeimg.c b/writeimg.c index f0139ad..2219acb 100644 --- a/writeimg.c +++ b/writeimg.c @@ -278,7 +278,11 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } +#ifdef __GLIBC__ if (ioctl(fd, BLKGETSIZE64, &device_size) < 0) { +#else /* With musl, ioctl's take ints, kheaders provide unsigned longs. Passes tests. */ + if (ioctl(fd, (int)BLKGETSIZE64, &device_size) < 0) { +#endif perror("ioctl"); close(fd); exit(EXIT_FAILURE);