Copy Blocks Between SCSI Devices with sg_xcopy

sg_xcopy copies a chosen range of logical blocks between two SCSI block devices, offloading the transfer to the devices themselves. Allow 20 to 30 minutes for a first run, longer if you must identify the correct LUNs. The copy itself can be much faster than reading and writing through the host, but that depends on the devices and their copy manager.

This guide describes sg_xcopy from sg3-utils 1.46-3ubuntu4, installed on this machine. Its local manpage is labelled sg3_utils 1.45 and documents the LID1 XCOPY interface. Check the command on your own host before relying on version-specific output.

Warning: An incorrect of= can overwrite a disk. Treat every device path as untrusted until you have checked it. Do not test these examples against a mounted filesystem, a production LUN or a device containing data that has no verified backup.

1. Confirm the installed interface

These are ordinary, read-only checks that need no elevated privileges:

$ command -v sg_xcopy
/usr/bin/sg_xcopy
$ sg_xcopy --version
sg_xcopy: 0.71 20200510
$ dpkg-query -W -f='${Package} ${Version}\n' sg3-utils
sg3-utils 1.46-3ubuntu4

The syntax resembles dd, but it is not a general file copier. The installed utility implements XCOPY for block-to-block transfers, so if= and of= must identify SCSI block devices. The manpage says that regular files are not supported as the source and destination for the implemented transfer.

Checkpoint: If sg_xcopy --version or the package query reports something different, read that host's sg_xcopy(8) before using the examples.

2. Identify the source and destination

Use your storage inventory, multipath configuration and change record to establish the exact source and target paths. You may need elevated privileges for inventory commands or for opening a device, but do not add sudo automatically: it does not make an ambiguous path safe.

Set shell variables only after checking both paths. The values below are placeholders, not devices to paste unchanged:

$ SRC=/dev/disk/by-id/REPLACE_WITH_SOURCE_ID
$ DST=/dev/disk/by-id/REPLACE_WITH_DESTINATION_ID
$ printf 'source: '; readlink -f -- "$SRC"
$ printf 'destination: '; readlink -f -- "$DST"
source: /dev/REPLACE_SOURCE
destination: /dev/REPLACE_DESTINATION

Stop if the resolved paths are the same, if either link is missing, or if the destination contains data you have not approved for replacement. The command opens an existing output without truncating it, then overwrites from the start unless you use oflag=append or seek=. That still means destructive writes to a device.

3. Choose the block range and block size

$ BS=512
$ COUNT=2048
$ printf 'planned bytes: %s\n' "$((BS * COUNT))"
planned bytes: 1048576

If you omit count=, sg_xcopy tries to derive a device-sized count from SCSI READ CAPACITY. That is convenient for a whole-device operation but a poor default for a first test. It cannot derive a count for every file-like input, and a missing count can stop the copy before it starts.

For a range inside a logical unit, skip= selects the source start block and seek= selects the destination start block, measured in bs=-sized blocks. The tool does not account for partitions: a path such as /dev/sdc2 is treated as the whole logical unit starting at LBA 0. Calculate offsets yourself from a trusted partition layout, and do not assume a partition path protects the rest of the disk.

4. Run the approved XCOPY

Run this only during the approved maintenance window, with both endpoints unmounted or otherwise quiesced as required by your storage design. The command below copies 2048 blocks from block zero on the source to block zero on the destination:

# sg_xcopy if="$SRC" of="$DST" bs="$BS" count="$COUNT" list_id=2 dc=1 time=1
sg_xcopy: if=/dev/REPLACE_SOURCE skip=0 of=/dev/REPLACE_DESTINATION seek=0 count=2048
sg_xcopy: 2048 blocks, 1 command

5. Check the copy result

A successful sg_xcopy exit status is useful, but query the copy manager as well when the device supports copy results. The manpage supplies sg_copy_results for this purpose:

$ sg_copy_results --status --list_id=2 "$DST"
Receive copy results (copy status):
    Held data discarded: Yes
    Copy manager status: Operation completed without errors
    Segments processed: 1
    Transfer count units: 0
    Transfer count: 0

Exact spacing and fields vary by device. The useful checkpoint is a completed status with no device error, not a particular transfer-count value. If the XCOPY command fails, save the complete stderr output and the copy-results response before retrying. A retry can repeat or overwrite part of the requested range.

If source and destination logical block sizes differ, residual data must be handled deliberately. Without suitable cat=1 or oflag=pad/iflag=pad settings, the command can fail with an inexact-segment error. Do not add those flags by trial and error: confirm the segment and padding requirements in the device documentation first.

6. Understand the XCOPY endpoint

By default, the XCOPY command is sent to of=, the destination. --on_src sends it to if= instead; iflag=xflag has the same effect. If the command line does not make the choice unambiguous, the environment variables XCOPY_TO_SRC and XCOPY_TO_DST can select one endpoint, but only one should exist. Avoid relying on ambient environment variables in automation. Use --on_dst or --on_src in the command itself when the endpoint matters.

The fco=1 option requests fast-copy-only behaviour. If the device cannot perform the transfer by a faster technique, it should return a copy-aborted condition instead of silently falling back to ordinary reads and writes. Use it only when that failure behaviour is wanted.

Done means