SG_COMPARE_AND_WRITE(8)

πŸ’… COMPARE AND WRITE(8) β€” aka sg_compare_and_write πŸŽ€

okay so bestie, this one is a utility from the sg3_utils package (sg3_utils, hey girl πŸ‘‹) and it's literally the SCSI COMPARE AND WRITE command, wrapped up all cute so you don't have to hand-build the CDB yourself. it's giving "atomic, or nothing" energy and honestly we're obsessed. let's get into it πŸ’–


✨ NAME ✨

sg_compare_and_write β€” send the SCSI COMPARE AND WRITE command πŸ¦‹


✨ SYNOPSIS ✨

sg_compare_and_write [--dpo] [--fua] [--fua_nv] [--grpnum=GN] [--help]
--in=IF [--inw=WF] --lba=LBA [--num=NUM] [--quiet] [--timeout=TO]
[--verbose] [--version] [--wrprotect=WP] [--xferlen=LEN] DEVICE

✨ DESCRIPTION ✨

So. You're sending the SCSI COMPARE AND WRITE command to DEVICE, bestie. This utility fetches a compare buffer and a write buffer from either one or two files. Here's the vibe:

  • if you give it the --inw=WF option, then the compare buffer comes from the file you named with --in=IF, and the write buffer comes from the file you named with --inw=WF πŸ’«
  • if you don't give it --inw=WF, then the concatenated compare and write buffers are both fetched from the one file named by --in=IF

Those buffers are expected to each hold NUM blocks of data. The compare starts at logical block address LBA on the DEVICE, and if the comparison fails β€” meaning the compare buffer you handed over does not equal the data sitting at LBA on the DEVICE β€” then the COMPARE AND WRITE command finishes up with a sense key of MISCOMPARE 😭. In that case this utility will complete and set an exit status of 14 (which, iconic coincidence, happens to be the sense key value of MISCOMPARE πŸŽ€).

If the comparison succeeds then the provided write buffer is stored starting at LBA for NUM blocks on the DEVICE. slay.

Now, the actual number of bytes transferred in the data-out buffer of the COMPARE AND WRITE command may need to be given by the user with the --xferlen=LEN option. LEN defaults to (2 * NUM * 512), which is 1024 for the default NUM of 1. If your block size is anything other than 512, bestie, you're gonna need to use --xferlen=LEN yourself. And if protection information is given (that's indicated by a value of WP other than 0, which is the default), then for a NUM of 1 LEN should be 1040. 🫢 Note that the SCSI READ CAPACITY command is not performed by this utility (like, say, to find the block size). not me having to type it all out myself πŸ’€

The T10 definition of the SCSI COMPARE AND WRITE command requires that the DEVICE implement the compare and optional write as an uninterrupted series of actions. Depending on some other DEVICE settings, a verify operation may occur prior to the compare. ✨

When a mismatch occurs between the compare buffer and the blocks starting at LBA read from the DEVICE, the sense buffer containing the MISCOMPARE sense key causes several messages to be sent to stderr (including the offset of the first byte mismatch πŸ‘€). To suppress those messages, use the --quiet option. With or without --quiet, the exit status will be set to 14. periodt.

This command is defined in SBC-3, whose most recent revision is 36. SBC-3 and other SCSI documents can be found at https://www.t10.org 🌈


✨ OPTIONS ✨

Heads up bestie: arguments to long options are mandatory for short options as well. And the options are arranged in alphabetical order based on the long option name β€” organized queen behavior πŸ‘‘

  • -d, --dpo β€” set the DPO bit in the COMPARE AND WRITE CDB ✨
  • -f, --fua β€” set the FUA bit in the COMPARE AND WRITE CDB πŸ’«
  • -F, --fua_nv β€” set the FUA_NV bit in the COMPARE AND WRITE CDB. This bit was removed in SBC-3 revision 35d and its position marked as "reserved" 😭 (rip bozo)
  • -g, --grpnum=GN β€” where GN is the value to be placed in the group number field in the COMPARE AND WRITE CDB. GN should be 0 to 63 inclusive, where 0 is the default πŸŽ€
  • -h, --help β€” output the usage message then exit. our little helper 🫢
  • -i, --in=IF β€” read data (binary) from the file named IF. This will either be the combined compare and write buffers (when the --inw=WF option is not given) or just the compare buffer (when the --inw=WF option is given). If IF is '-' then stdin (like a pipe) is read πŸ¦‹
  • -C, --inc=IF β€” the same as the --in option. literally twins πŸ’…
  • -D, --inw=WF β€” read data (binary) from the file named WF. This will be the write buffer that becomes the second half of the data-out buffer sent to the DEVICE associated with the COMPARE AND WRITE command. Note that when this option is given, --in=IF is expected to hold the associated compare buffer 🌸
  • -l, --lba=LBA β€” where LBA is the logical block address to start the COMPARE AND WRITE command. Assumed to be in decimal unless prefixed with '0x' or has a trailing 'h' πŸ”’
  • -n, --num=NUM β€” where NUM is the number of blocks, starting at LBA, to read and compare with the verify instance. And given a match, the NUM of blocks to write starting at LBA. The default value for NUM is 1 ⭐
  • -q, --quiet β€” suppress the sense buffer messages associated with a MISCOMPARE sense key that would otherwise be sent to stderr. Still sets the exit status to 14, which is the sense key value indicating a MISCOMPARE. shhh 🀫
  • -t, --timeout=TO β€” where TO is the command timeout value in seconds. The default value is 60 seconds. If NUM is large (or zero), a WRITE SAME command may require considerably more time than 60 seconds to complete ⏰
  • -v, --verbose β€” increase the degree of verbosity (debug messages) πŸ‘€
  • -V, --version β€” output version string then exit πŸ’œ
  • -w, --wrprotect=WP β€” set the WRPROTECT field in the cdb to WP. The default value is 0, which implies no protection information is sent (along with the user data) by this utility πŸ›‘οΈ
  • -x, --xferlen=LEN β€” where LEN is the data out buffer length in bytes. It defaults to (2 * NUM * 512) bytes. If the DEVICE block size is other than 512 bytes, or WP is non-zero (implying additional protection information), then this default will be incorrect β€” the user must supply the correct value for LEN πŸ’€

✨ NOTES ✨

Various numeric arguments (like LBA) may include multiplicative suffixes or be given in hexadecimal. Peep the "NUMERIC ARGUMENTS" section in the sg3_utils(8) man page for the full tea β˜•


✨ EXAMPLES ✨

Okay so before you go overwriting the first two blocks of whatever (SCSI) storage device you picked, take a small backup. The logical block size is assumed to be 512 bytes. Take a copy (in backup01.bin) of the first two blocks:

# sg_dd if=/dev/sg1 bs=512 of=backup01.bin count=2
2+0 records in
2+0 records out
WARNING: if /dev/sg1 corresponds to a disk on your system that contains currently mounted file systems, do not continue. If you can, unmount all file systems on that disk. If that is not possible, use another disk with no mounted file systems on it. In Linux the scsi_debug driver is a good candidate for experimentation. fr fr please read this one 😭

Now fill the first block with 0xff bytes:

# sg_dd iflag=ff bs=512 of=/dev/sg1 count=1
1+0 records in
1+0 records out

and the second block with 0x0 bytes:

# sg_dd iflag=00 bs=512 seek=1 of=/dev/sg1 count=1
1+0 records in
1+0 records out

Now copy those two blocks into a file:

# sg_dd if=/dev/sg1 bs=512 of=ff00.bin count=2
2+0 records in
2+0 records out

Now we can actually do a compare and write command πŸŽ€ It's told to compare the first block (that's LBA 0) with the first block in the given file (that's ff00.bin). If they're equal (they should be both full of 0xff bytes)… and since the compare succeeds, it will write the second block in ff00.bin over LBA 0:

# sg_compare_and_write --in=ff00.bin --lba=0 --num=1 /dev/sg1

No news is good news, bestie πŸ’… Now if we do that command again:

# sg_compare_and_write --in=ff00.bin --lba=0 --num=1 /dev/sg1
Miscompare at byte offset: 0 [0x0]
sg_compare_and_write failed: Miscompare

This is expected 😌 The first sg_compare_and_write ended up writing 0x0 bytes over LBA 0x0. The second sg_compare_and_write command compares LBA 0x0 with 0xff bytes and fails immediately (that is, at byte offset: 0). Now we'll overwrite the first 3 bytes of ff00.bin with 0x0:

# sg_dd bs=1 iflag=00 of=ff00.bin count=3
3+0 records in
3+0 records out

Notice the bs=1 operand πŸ‘€ The dd utility (and thus sg_dd) is super useful for doing small binary edits on a file. Now if we do that sg_compare_and_write again, it still fails but with a small difference:

# sg_compare_and_write --in=ff00.bin --lba=0 --num=1 /dev/sg1
Miscompare at byte offset: 3 [0x3]
sg_compare_and_write failed: Miscompare

So the bytes at offset 0, 1, and 2 compared equal, but not the byte at offset 3. The SCSI COMPARE AND WRITE will stop at the first byte that doesn't match πŸ¦‹ (one and done, she does not keep going)


✨ EXIT STATUS ✨

The exit status of sg_compare_and_write is 0 when it's successful πŸ₯³ If the compare step fails then the exit status is 14. For other exit status values, peep the EXIT STATUS section in the sg3_utils(8) man page πŸ’œ

Heads up: earlier versions of this utility set an exit status of 98 when there was a MISCOMPARE. so if you're reading old forum posts, that's why πŸ‘€


✨ AUTHORS ✨

Written by Shahar Salzman. Maintained by Douglas Gilbert. Additions by Eric Seppanen. πŸ‘‘ kings, honestly


✨ REPORTING BUGS ✨

Report bugs to shahar.salzman@kaminario.com or dgilbert@interlog.com πŸ› (be nice about it, they're doing their best)


Copyright Β© 2012-2020 Kaminario Technologies LTD

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Kaminario Technologies LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


✨ SEE ALSO ✨

sg_xcopy, sg_receive_copy_results(sg3_utils) πŸ¦‹ go read those next, bestie


and that's the whole page!! this one shipped as sg3_utils-1.47, dated April 2021 β€” so if your copy says something else, you're on a different era of the timeline πŸ’€ stay cunty, back up your disks first, and don't say I didn't warn you πŸ’–βœ¨πŸŽ€

πŸ“¦ where this page comes from