Copy SCSI Data Safely with sg_dd
Reach for sg_dd when plain dd cannot talk to a SCSI generic device, or when you need SCSI-aware copying rather than a raw byte stream. You will end up with a checked sg_dd command for a known file, plus a safer pattern for imaging a real device. The examples use the locally installed sg3-utils package version 1.46-3ubuntu4, whose sg_dd reports version 6.25 (20210326).
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes for a file-based test. Device imaging takes much longer and needs an accurate destination, a maintenance window and enough storage. You need a shell and sg3-utils. Reading a device or writing a file is normally unprivileged, but device permissions often mean the real copy needs elevated access.
1. Confirm the installed command
Check the binary and version before copying anything. This is read-only and needs no sudo:
$ command -v sg_dd
/usr/bin/sg_dd
$ sg_dd --version
sg_dd: version: 6.25 20210326
$ dpkg-query -W -f='${Package} ${Version}\n' sg3-utils
sg3-utils 1.46-3ubuntu4
sg_dd uses dd-like operands such as if=, of=, bs= and count=, written as bare arguments rather than dashed options. Run sg_dd --help if your installed version differs from this guide.
2. Choose the block size and scope
For SCSI command I/O, bs must be the device's actual logical block size. The default is 512 bytes: usually right for disks, usually wrong for optical media, where 2048 bytes is the norm. Unlike ordinary dd, sg_dd will not silently accept an arbitrary multiple as a valid SCSI block size.
Set count explicitly whenever the source is a device and you know the range you want. It counts blocks, so bs=512 count=2048 requests 1 MiB. Leave it out and sg_dd tries to derive a device length, but a normal file is never probed for its size, so a missing count can stop the command before any copy happens at all.
Checkpoint
Write down the source, destination, logical block size and block count before touching the copy step. A wrong destination can destroy data, and no clever command-line spelling undoes an overwrite.
3. Test the syntax without copying
--dry-run parses and prepares the command but skips the actual copy. It still opens the named files to work out their lengths, so use real paths and do not mistake this for a full permission test.
$ sg_dd --dry-run if=/path/to/input.img of=/path/to/output.img bs=512 count=2048
sg_dd: dry-run, bypassing actual copy
The wording of that line can vary by build. What matters is the exit status:
$ printf 'exit status: %s\n' "$?"
exit status: 0
An unknown operand, invalid block size or missing file shows up here, so fix it before going further. Do not add conv=sync expecting GNU dd behaviour: sg_dd just ignores it. Its supported conversions are narrower: sparse, nocreat, noerror and notrunc.
4. Copy a bounded file range
For a normal file, this copies 1 MiB from the start of the input to the start of the output:
$ sg_dd if=/path/to/input.img of=/path/to/output.img bs=512 count=2048
2048+0 records in
2048+0 records out
The record summary goes to standard error, so the output stays usable even if you route standard output to your destination. Exact wording depends on the build. Verify the result with a byte count and, where it makes sense, a byte-for-byte comparison:
$ stat -c '%n %s bytes' /path/to/input.img /path/to/output.img
/path/to/input.img 1048576 bytes
/path/to/output.img 1048576 bytes
$ cmp --bytes=1048576 /path/to/input.img /path/to/output.img
$ printf 'exit status: %s\n' "$?"
exit status: 0
If the output file already exists, sg_dd overwrites it from the start but does not truncate it by default, which can leave old trailing bytes beyond the copied range. Use a fresh destination, remove the old file only after checking your backup, or set the intended final length explicitly. oflag=append appends to a regular file and cannot be combined with seek=.
5. Image a device with a guarded command
Do not go further if you have not positively identified the devices. Source and destination are easy to swap by accident, and a device write is destructive. Before you start:
- Unmount anything that must not change while the read is happening.
- Record stable device identifiers rather than trusting a name that can shift on reboot.
- Check the SCSI generic mapping with
sg_mapor/proc/scsi/scsi. - Skip tape entirely.
sg_dd's SCSI READ and WRITE commands target disks and optical media, not tape drives.
For a disk with 512-byte logical blocks, a bounded image command looks like this:
$ sudo sg_dd if=/dev/sgX of=/safe/path/disk.img \
bs=512 count=BLOCKS iflag=coe oflag=nocreat time=1 --progress
Replace /dev/sgX, /safe/path/disk.img and BLOCKS with values you have independently checked. iflag=coe continues through certain SCSI read errors and fills in recovered or zero data using the utility's recovery path, which is not the same as a guarantee that the damaged data is correct. oflag=nocreat refuses to create a missing output file, a useful guard when the destination should already exist: create an appropriately sized destination deliberately before relying on it.
--progress reports progress every two minutes and prints a final completed line; repeat the flag for one-minute or 30-second reports. time=1 prints transfer duration and throughput to standard error. Neither option checks the contents of the image.
6. Use direct or SCSI I/O only when you need it
A block device normally uses buffered Unix reads and writes. iflag=direct or oflag=direct requests O_DIRECT; iflag=sgio or oflag=sgio sends SCSI commands through SG_IO. The oddest one is blk_sgio=1, which applies SCSI I/O to block devices on supported systems but also ignores partition information and hits the underlying device directly. That is a nasty surprise waiting to happen: a command naming /dev/sda3 can read the whole underlying disk once this mode is on.
Do not bolt these flags on as performance folklore. Confirm the device type and the I/O path you actually need first. Direct I/O can fall back to indirect I/O for an sg device, and the command reports that at completion. An ATA disk is not automatically a SCSI target just because it shows up as a block device.
7. Verify an SCSI-capable destination
--verify swaps WRITE commands for SCSI VERIFY commands. It needs of= pointing at an sg device, or a block device opened with oflag=sgio. It is not a general comparison mode for two regular files.
$ sudo sg_dd if=/dev/sgX of=/dev/sgY bs=512 count=BLOCKS --verify
$ printf 'exit status: %s\n' "$?"
exit status: 0
A non-zero status or a miscompare means equality was not established. Without oflag=coe, the first miscompare stops the whole operation. For regular files, use cmp or a checksum instead. Keep the source and the image around until verification finishes; deleting the only copy is the one step you cannot take back.
8. Diagnose a failed or interrupted transfer
All informative, warning and error output goes to standard error. Add --verbose, or repeat it as -vv, when you need more detail, though higher verbosity can dump a lot of SCSI command output. A zero exit status means the utility completed, but with error-continuation settings turned on, individual SCSI command failures do not necessarily flip that status to non-zero.
Interrupting a long transfer prints remaining-block and record information before the normal signal action. SIGUSR1 asks for that same progress information without stopping the copy. If a transfer does stop, preserve the partial output, record the exact command used and inspect both source and destination before deciding whether to resume with skip= and seek=. Both count bs-sized blocks, so calculate carefully.
Done means
- Version checked: the installed
sg_ddversion and the logical block size are both known. - Dry run passed with
--dry-runbefore any real transfer. - Endpoints verified independently: source, destination, block count and direction all checked by hand.
- Copy checked for size and, where it matters, with
cmp. - Imaging bounded: an explicit scope, an existing destination guard and a recovery plan were all in place.
- Verify used correctly:
--verifyonly against an SCSI-capable destination, never as a stand-in for file comparison.