mtio
Safe Rust bindings for the Linux SCSI tape driver — ioctl(2) interface to
/dev/nst* tape devices, with an in-memory mock for unit testing.
Features
- Full
MTIOCTOPcoverage — rewind, seek to EOD, forward/backward space over filemarks and records, write filemarks, set block size, load/unload, lock/unlock door, physical erase from current position to EOT. - Drive status and position —
MTIOCGET(flags, file number, block number, block size, drive type) andMTIOCPOS(absolute logical block position). Tapetrait — a single trait implemented by bothTapeDeviceandMockTape, so all higher-level logic can be written and tested against the mock without any hardware present.MockTape— in-memory tape simulation backed byVec<Vec<u8>>, with correct filemark, overwrite, and write-protection semantics. Available via themockfeature flag or automatically in#[cfg(test)]contexts. Targets variable-length block mode (the Linuxstdriver default); see block mode notes below.StatusFlags— typed bitmask formt_gstat, with named constants and predicate methods (is_bot(),is_write_protected(),is_eod(), …) matching theGMT_*macros inlinux/mtio.h.- Linux only —
TapeDeviceand the raw ioctl bindings are compiled only ontarget_os = "linux". TheTapetrait andMockTapecompile on all platforms, so the rest of the workspace can be developed on macOS or Windows.
Installation
[]
= { = "https://github.com/dc-ja/mtio-rs" }
# To use MockTape in tests of a downstream crate:
[]
= { = "https://github.com/dc-ja/mtio-rs", = ["mock"] }
Requires Rust 1.74+ and Linux.
Usage
Writing to tape
use ;
use Write;
use Path;
Reading from tape
use ;
use Read;
use Path;
Querying drive status
use ;
use Path;
Erasing tape
Two levels of erasure are available:
Truncation — every write (data record or filemark) causes the drive firmware to update its End-of-Data (EOD) marker to immediately follow the last written item. Everything beyond that point becomes inaccessible: normal I/O stops at EOD regardless of what is magnetically recorded past it. Writing a filemark at a chosen position is therefore the conventional way to mark the end of an archive at that position; the firmware EOD update is what makes prior data unreachable, not the filemark count itself. The magnetically recorded data past the new EOD is not destroyed — it simply cannot be reached by normal means. Fast, minimal wear.
Physical erase — erase(long_erase) issues MTERASE. With
long_erase = true, the drive's erase head traverses the full remaining tape,
permanently destroying all data from the current position to EOT (slow,
high-wear). With long_erase = false, only an EOD marker is written at the
current position; prior data is left magnetically intact but unreachable (fast,
low-wear). Both forms are irreversible. Neither is a cryptographic secure erase.
use ;
use Path;
Testing with MockTape
[!IMPORTANT]
MockTapedoes not emulate each and every aspect of a tape drive. Apart from the obvious (instant operation rather than delay due to tape travel), there are differences like the assumption that a tape is always present, a focus on variable block mode with only partial support for fixed block mode, and no auto-rewind after writing operations.
use ;
use ;
Any function that accepts &mut impl Tape works with both TapeDevice and
MockTape without modification.
API overview
All methods below are part of the [Tape] trait and are available on both
TapeDevice and MockTape.
| Method | ioctl / operation | Description |
|---|---|---|
rewind() |
MTREW |
Seek to BOT |
seek_to_eod() |
MTEOM |
Seek to end of recorded data |
space_filemarks(n) |
MTFSF / MTBSF |
Space ±n filemarks |
space_records(n) |
MTFSR / MTBSR |
Space ±n records |
write_filemarks(n) |
MTWEOF |
Write n filemarks |
seek_block(n) |
MTSEEK |
Seek to logical block n (≤ i32::MAX) |
set_block_size(n) |
MTSETBLK |
Set fixed block size (0 = variable) |
load() |
MTLOAD |
SCSI LOAD |
unload() |
MTUNLOAD |
SCSI UNLOAD / eject |
lock() |
MTLOCK |
Lock drive door |
unlock() |
MTUNLOCK |
Unlock drive door |
status() |
MTIOCGET |
Read drive status and flags |
position() |
MTIOCPOS |
Read absolute logical block position |
erase(long_erase) |
MTERASE |
Physically erase from current position to EOT |
TapeDevice-only
The following method is only available on TapeDevice, not through the Tape
trait, and has no MockTape equivalent.
| Method | ioctl / operation | Description |
|---|---|---|
raw_op(op, count) |
MTIOCTOP |
Issue any MTIOCTOP operation by code directly. Use the MT* constants exported from this crate. |
Block mode
The Linux st driver supports two block modes, selected via set_block_size
(MTSETBLK):
Variable-length mode (block_size = 0, the default) — each write(2)
call produces one tape record of whatever size is passed. On read(2), the
drive returns exactly one record; the read buffer must be at least as large as
the record or the read fails with ENOMEM. This is the Linux st driver
default and is used unless an explicit set_block_size call overrides it.
Fixed block mode (block_size > 0) — every record on tape is exactly
block_size bytes. All read(2) and write(2) buffers must be multiples of
block_size; misaligned I/O fails with EINVAL. The block size is physically
encoded in the tape format, so a tape written in fixed mode must be read with a
matching block size.
TapeDevice
TapeDevice passes set_block_size directly to the drive via MTSETBLK. The
current block size is available as TapeStatus::block_size after a status()
call (decoded from the mt_dsreg field of struct mtget).
MockTape
MockTape targets variable-length mode as its primary use case, matching the
st driver default. Fixed block mode is partially supported:
| Behaviour | Supported |
|---|---|
set_block_size stored and reported via status() |
Yes |
Write alignment enforced (EINVAL for non-multiples) |
Yes |
Read alignment enforced (EINVAL for non-multiples) |
Yes |
space_records steps by block_size bytes |
Yes |
Per-record read boundary enforcement (ENOMEM for undersized buffers in variable mode) |
No — MockTape always does a byte-stream short read |
Development notes
Running tests
AI assistance
The initial API design, ioctl constant verification, and documentation were developed with assistance from Claude (Anthropic). All code was reviewed and the final implementation decisions were made by the project author.
License
MIT OR Apache-2.0