sg_compare_and_write only writes a block after comparing it first, a genuinely useful conditional write, not a file-copy tool in a SCSI costume. This walks through preparing the comparison and replacement buffers, running the write, and reading a MISCOMPARE correctly instead of retrying blindly. Allow 15 to 30 minutes for a prepared test device, plus time to confirm its block size. The examples assume the device is disposable or otherwise fully under your control.
This guide follows the installed sg3-utils 1.46 package and its sg_compare_and_write 1.29 binary. The command sends the SCSI COMPARE AND WRITE operation through a device node, but it does not discover the block size with READ CAPACITY for you, so establish that separately before preparing any buffers:
$ command -v sg_compare_and_write
/usr/bin/sg_compare_and_write
$ sg_compare_and_write --version
sg_compare_and_write: version: 1.29 20200509
$ dpkg-query -W -f='${Package} ${Version}\n' sg3-utils
sg3-utils 1.46-3ubuntu4
Package and program versions can differ on other distributions. Keep the local help output with your operational notes, especially if a script depends on an exact option spelling.
Identify a SCSI generic device such as /dev/sg1, but do not assume it represents an unused disk just because it's generic. A generic node can sit in front of storage with mounted filesystems or valuable data on it. Stop before the write if you cannot prove the target is safe.
Use your normal inventory tools to map the generic node to real hardware. Inspect device names and mounts without changing anything first:
$ ls -l /dev/sg*
$ findmnt
$ lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS
All ordinary inspection so far. Compare-and-write itself normally needs access to the device node, which may require elevated privileges. Use sudo only after checking the path and target:
$ sudo sg_compare_and_write --help
Usage: sg_compare_and_write [--dpo] [--fua] ... DEVICE
A safer laboratory target is a SCSI device created specifically for testing, such as one backed by the Linux scsi_debug driver. Do not experiment against a mounted production disk.
With the default --num=1 and a 512-byte block size, the input needs two 512-byte regions: the first is the comparison buffer, the second the replacement data. The command compares the first region against the target block and writes the second only after a match.
Create the files in a new working directory. This example fills a comparison block with hexadecimal 0x41 and a replacement block with 0x42, both local files, not device writes:
$ mkdir -p "$HOME/sg-compare-test"
$ cd "$HOME/sg-compare-test"
$ dd if=/dev/zero bs=512 count=1 status=none | tr '\0' 'A' > compare.bin
$ dd if=/dev/zero bs=512 count=1 status=none | tr '\0' 'B' > write.bin
$ wc -c compare.bin write.bin
512 compare.bin
512 write.bin
For a single input file, concatenate the comparison and write buffers in that exact order:
$ cat compare.bin write.bin > compare-and-write.bin
$ wc -c compare-and-write.bin
1024 compare-and-write.bin
Check the size before you go anywhere near the device. A short or reversed file changes what data gets sent, and can produce a mismatch or an unintended write.
Use the separate-file form first, since it keeps the two roles obvious. Replace /dev/sg1 with the exact device you verified, and 0 with the intended logical block address. This command is destructive if the comparison succeeds:
$ sudo sg_compare_and_write \
--in=compare.bin --inw=write.bin \
--lba=0 --num=1 /dev/sg1
Success normally produces no output and returns status 0. Capture the status immediately:
$ printf 'exit status: %s\n' "$?"
exit status: 0
The device compares and, on a match, writes as one uninterrupted sequence, which is exactly why this command suits a narrow storage update. It does not make an incorrect device path safe, though, so confirm the LBA and buffers before every run.
If the target block does not equal compare.bin, the command must not write write.bin. The installed utility reports the first differing byte and exits with status 14, the SCSI MISCOMPARE sense-key value:
$ sudo sg_compare_and_write \
--in=compare.bin --inw=write.bin \
--lba=0 --num=1 /dev/sg1
Miscompare at byte offset: 0 [0x0]
sg_compare_and_write failed: Miscompare
$ printf 'exit status: %s\n' "$?"
exit status: 14
Treat status 14 as a useful concurrency or expectation check, not permission to keep retrying. Another process, a different starting value, or simply a wrong comparison file can all explain it. Inspect the situation and prepare a fresh comparison buffer if the target has legitimately changed. --quiet suppresses the diagnostic messages but still returns 14, so scripts must check the status regardless.
The equivalent single-file invocation reads the first half of compare-and-write.bin as comparison data and the second half as write data:
$ sudo sg_compare_and_write \
--in=compare-and-write.bin \
--lba=0 --num=1 /dev/sg1
$ printf 'exit status: %s\n' "$?"
exit status: 0
Do not mix the two forms by accident. With --inw present, --in is only the comparison buffer and --inw the write buffer; without it, --in must hold the concatenated pair. A dash can stand in for --in to read binary data from standard input, but a plain file is easier to audit for a one-off change.
The default transfer length is 2 * NUM * 512 bytes, correct only when the device uses 512-byte blocks and no protection information is in play. The utility never queries the block size itself, so a device with 4096-byte blocks needs matching buffers and an explicit transfer length.
For one 4096-byte block, each buffer is 4096 bytes, the combined input is 8192 bytes, and --xferlen=8192 describes the total data-out buffer:
$ wc -c compare-4k.bin write-4k.bin compare-and-write-4k.bin
4096 compare-4k.bin
4096 write-4k.bin
8192 compare-and-write-4k.bin
$ sudo sg_compare_and_write \
--in=compare-and-write-4k.bin \
--lba=0 --num=1 --xferlen=8192 /dev/sg1
Calculate the value as two buffers multiplied by the number of blocks and the actual block size. A non-zero --wrprotect adds bytes to the transfer for protection information, so do not reuse a plain-data length; supply the device-specific value and matching buffer contents only once you've verified the protection format.
--timeout changes the command timeout from its 60-second default. Use it only when the expected operation needs longer and your maintenance window allows it. --fua sets the Force Unit Access bit, --dpo the Disable Page Out bit: device-cache and command-hint decisions, not general safety switches.
--fua_nv is particularly unsuitable as a casual addition: the local manual notes that its bit was removed in SBC-3 revision 35d and is now reserved. Leave it unset unless the target specification and compatibility requirements explicitly call for it, and likewise, do not add --wrprotect or a group number just because an example happens to show them.
sg_compare_and_write and sg3-utils versions.--xferlen for non-512-byte blocks or protection information.