Date: 2026-08-03. All statements below were checked against the current upstream sources (libstorage-ng master, lsscsi master, util-linux master, torvalds/linux master, systemd main, sg3_utils master), against the local system (openSUSE, lsscsi 0.32) and against the local checkouts of yast2-storage-ng and libstorage-ng.
- There is no authoritative list of "data transports" for Linux. Neither the kernel nor any
standards body defines the concept "transport of a block device". What exists is:
- a standardized list for SCSI: the T10 SAM protocol identifiers (FCP, SPI, SSA, SBP, SRP, iSCSI, SAS/SPL, ADT, ATA/ACS, UAS, SOP, PCIe);
- a standardized + kernel-enforced list for NVMe: the NVMe-oF
TRTYPEvalues, in Linuxpcie/rdma/fc/tcp/loop; - a set of kernel SCSI transport classes (
fc,iscsi,sas,spi,srp), which is what the kernel actually models as a transport; - several de-facto userspace lists that do not agree with each other:
lsscsi --transport,lsblk TRAN, udevID_BUS, udevID_PATH.
- libstorage-ng's enum is a pragmatic union of the lsscsi list and the NVMe list. Every value it exposes maps to something real in one of the standardized lists; the enum itself is not invented. In that sense the review comment's fear ("an opinionated value from an opinionated list") is only half true: the list is defensible, the detection is where the problems are.
- The detection mechanism is state of the art for Linux, but state of the art is heuristic.
lsblk, the util-linux reference implementation, uses exactly the same sysfs heuristics (sameahci/*ata*proc_namecheck, same" over "check for FCoE). lsscsi's own source calls the ATA/SATA check "crude check: driver name". So there is nothing better to switch to today; switching to udev/lsblk-style detection would change which devices are covered, not the fundamental reliability. - The real risks for Agama are coverage gaps and two specific bugs, not the enum:
- virtio disks (
vd*) always report no transport — very relevant, most Agama test/QA and many production installs are VMs. Same for SD/MMC, DASD (s390), Xenxvd*, cciss, nbd, pmem. - multipath, MD RAID, BIOS RAID and DASD drives have no transport at all in libstorage-ng
(the attribute only exists on
Disk), sotransport: fc/iscsinever matches the very devices where FC/iSCSI matter most. - iSCSI detection breaks for targets not named
iqn.*(libstorage-ng matches the prefixiqn, but RFC 3720/7143 also allowseui.andnaa.) → those disks report unknown. srp(SCSI RDMA, InfiniBand) is detected by lsscsi but dropped by libstorage-ng.satavsatais a driver-name guess; a SATA disk behind a SAS HBA reportssas.
- virtio disks (
- Recommendation: keep the enum as is, document the semantics and the gaps in the JSON schema, consider making "no transport detected" expressible, and file the concrete upstream bugs. Detail in §7.
if (is_nvme()) // → nvme list-subsys (nvme-cli)
transport = cmd_nvme_list_subsys.get_transport(get_name(), system_info);
else if (is_pmem() || is_brd() || is_nbd())
; // "no proper transport"
else // → lsscsi --transport
if (system_info.getCmdLsscsi().get_entry(get_name(), entry))
transport = entry.transport;So there are two independent detection paths and one explicit no-op path:
| device | source | binary | package requirement |
|---|---|---|---|
NVMe (nvme*) |
nvme list-subsys JSON |
/usr/sbin/nvme |
nvme-cli |
| pmem / brd / nbd | none, stays UNKNOWN |
— | — |
| everything else | lsscsi --transport text output |
/usr/bin/lsscsi |
Requires: lsscsi >= 0.26 in libstorage-ng.spec |
The attribute lives on Disk only. Multipath, DmRaid, MdRaid, Dasd and StrayBlkDevice
do not have it (this was already verified in the matcher session: they do not even respond to
#transport in y2storage).
If lsscsi is missing or fails, SystemCmd is created with DoThrow; the exception is caught one
level up in probe_disks() and turned into a "Probing disk %s failed" error — i.e. a missing
lsscsi degrades the whole probing of that disk, not just the transport.
enum class Transport {
UNKNOWN, SBP, ATA, FC, ISCSI, SAS, SATA, SPI, USB, FCOE, PCIE, TCP, RDMA, LOOP
};13 values + UNKNOWN. This is exactly the list Agama exposes in
rust/share/storage.schema.json ($defs/transportValue), minus unknown (excluded on purpose).
boost::replace_all(tmp, " usb: ", " usb:"); // normalize two ambiguous layouts
boost::replace_all(tmp, " pcie 0x", " pcie:0x");
...
if (starts_with(transport, "sbp:")) entry.transport = Transport::SBP;
else if (starts_with(transport, "ata:")) entry.transport = Transport::ATA;
else if (starts_with(transport, "fc:")) entry.transport = Transport::FC;
else if (starts_with(transport, "fcoe:")) entry.transport = Transport::FCOE;
else if (starts_with(transport, "iqn")) entry.transport = Transport::ISCSI; // ← see §4.3
else if (starts_with(transport, "sas:")) entry.transport = Transport::SAS;
else if (starts_with(transport, "sata:")) entry.transport = Transport::SATA;
else if (starts_with(transport, "spi:")) entry.transport = Transport::SPI;
else if (starts_with(transport, "usb:")) entry.transport = Transport::USB;
else if (starts_with(transport, "pcie:")) entry.transport = Transport::PCIE;Observations:
- It is string parsing of a human-oriented CLI output with fixed column positions
(
extractNthWord(2, …)), plus two ad-hoc fixups for the layouts where lsscsi prints a space instead of a colon. CmdLsscsi::CmdLsscsi()computesconst bool json = CmdLsscsiVersion::supports_json_option();and never uses it — the JSON parsing path (lsscsi ≥ 0.33 supports--json) is prepared but not implemented. Worth reporting upstream; it would remove the layout fragility.- Only rows whose type is
diskare considered (if (type != "disk") continue;). - lsscsi's
srp:andpseudo_0(scsi_debug) prefixes are not mapped →UNKNOWN. TCP,RDMAandLOOPcan never come from here; they only come fromnvme list-subsys.
Reads the Transport field of nvme list-subsys --output-format=json and maps
pcie/fc/tcp/rdma/loop to PCIE/FC/TCP/RDMA/LOOP, logging "unknown NVMe
transport" otherwise. There is a TODO for subsystems with several paths using different
transports (it takes paths[0]).
This half is solid: those five strings are exactly the kernel's nvme_ctrl_ops.name values,
see §3.2.
transport_sdev_tport() / transport_h_init() in src/lsscsi.c walk sysfs in a fixed order
and return the first match:
| order | check | sysfs evidence | result |
|---|---|---|---|
| 1 | SAS host | /sys/class/sas_host/hostN |
sas:<sas_address> |
| 2 | SPI host | /sys/class/spi_host/hostN |
spi:<target> |
| 3 | FC host | /sys/class/fc_host/hostN + symbolic_name contains " over " |
fcoe: else fc: |
| 4 | SRP host | /sys/class/srp_host/hostN |
srp: (dropped by libstorage-ng) |
| 5 | SAS class / SBP | .../sas_device, ieee1394_id |
sas: / sbp: |
| 6 | iSCSI | /sys/class/iscsi_host/hostN/device |
<target name>,t,0x<tpgt> |
| 7 | USB | sysfs path contains a USB device | usb:<usb path> |
| 8 | ATA/SATA | scsi_host/hostN/proc_name == ahci, or starts with sata, or contains ata |
sata: / ata: |
| 9 | scsi_debug | path contains pseudo_0 |
pseudo_0 (dropped) |
Two consequences that matter for a user-facing search condition:
-
The reported transport is the transport of the initiator/host adapter, resolved in that fixed order. A SATA disk in a USB enclosure reports
usb(check 7 wins over check 8) — which is what a user expects. A SATA disk plugged into a SAS HBA reportssas(check 1 wins) — which some users would not expect. -
atavssatais a guess based on the driver name. lsscsi's own comment:/* ATA or SATA device, crude check: driver name */and in its header:
#define TRANSPORT_ATA 8 /* probably PATA, could be SATA */. So a SATA disk driven by anything whoseproc_nameis notahciand does not containata(virtio-scsi,megaraid_sas,mpt3sas, many RAID HBAs) is not reported assataat all.
util-linux does the very same thing. lsblk-cmd/lsblk.c:get_transport():
if (sysfs_blkdev_scsi_host_is(sysfs, "spi")) trans = "spi";
else if (sysfs_blkdev_scsi_host_is(sysfs, "fc")) trans = strstr(attr," over ") ? "fcoe":"fc";
else if (… "sas" || has_attribute("sas_device")) trans = "sas";
else if (has_attribute("ieee1394_id")) trans = "sbp";
else if (… "iscsi") trans = "iscsi";
else if (sysfs_blkdev_scsi_path_contains(sysfs,"usb")) trans = "usb";
else if (… "scsi") { proc_name: ahci|sata* → "sata"; *ata* → "ata"; }
else if (startswith(name,"nvme")) trans = "nvme";
else if (startswith(name,"vd")) trans = "virtio";
else if (startswith(name,"mmcblk")) trans = "mmc";Identical heuristics, plus three name-based fallbacks (nvme, virtio, mmc) that libstorage-ng
lacks, and less detail for NVMe (everything is nvme, no tcp/rdma/fc distinction).
Verified on the local machine: /dev/sda is a SATA SSD, lsscsi --transport prints
sata:500a0751155452d8, lsblk -o TRAN prints sata, proc_name is ahci, and udev's by-path
link is pci-0000:00:17.0-ata-3 (udev does not distinguish SATA from PATA either).
The closest thing to an authoritative list. Values 0h–Fh of the PROTOCOL IDENTIFIER field
(SAM-5/SAM-6, also used in SPC VPD page 83h designators). Verbatim from
sg3_utils/lib/sg_lib_data.c:sg_lib_transport_proto_strs[]:
| id | protocol | libstorage-ng value |
|---|---|---|
| 0h | Fibre Channel Protocol for SCSI (FCP-5) | fc (and fcoe, which is FCP over Ethernet) |
| 1h | SCSI Parallel Interface (SPI-5) (obsolete in SPC-5) | spi |
| 2h | Serial Storage Architecture (SSA-S3P) | — (dead technology) |
| 3h | Serial Bus Protocol, IEEE 1394 (SBP-3) | sbp |
| 4h | SCSI RDMA Protocol (SRP) | missing |
| 5h | Internet SCSI (iSCSI) | iscsi |
| 6h | Serial Attached SCSI Protocol (SPL-4) | sas |
| 7h | Automation/Drive Interface Transport (ADT-2) | — (tape libraries) |
| 8h | AT Attachment Interface (ACS-2) | ata + sata (the standard does not split them) |
| 9h | USB Attached SCSI (UAS-2) | usb |
| Ah | SCSI over PCI Express (SOP) | — |
| Bh | PCIe | pcie |
| Ch–Eh | reserved | — |
| Fh | No specific protocol | ≈ unknown |
So 11 of the 13 libstorage-ng values are recognisable T10 protocols or NVMe transports; the two
that are not standard distinctions are the ata/sata split (a Linux-ism) and the fc/fcoe
split (FCoE is FCP over an Ethernet-based FC fabric; T10 sees one protocol).
Is it usable as a detection mechanism? Barely: the protocol identifier is only exposed per
designator in VPD page 83h (/sys/class/scsi_device/H:C:T:L/device/vpd_pg83, decoded by
sg_vpd -p di / sg_inq --id, and by the sg3_utils udev rules that produce SCSI_IDENT_*), it is
optional (the PIV bit may be unset), it is absent for non-SCSI devices, and it cannot distinguish
SATA from PATA nor FC from FCoE. It is a good vocabulary, not a good probe.
include/linux/nvme.h:
enum { NVMF_TRTYPE_PCI = 0, NVMF_TRTYPE_RDMA = 1, NVMF_TRTYPE_FC = 2,
NVMF_TRTYPE_TCP = 3, NVMF_TRTYPE_LOOP = 254 };and the corresponding driver names (nvme_ctrl_ops.name), verified in
drivers/nvme/host/{pci,rdma,fc,tcp}.c and drivers/nvme/target/loop.c: pcie, rdma, fc,
tcp, loop. The kernel exports them per controller in /sys/class/nvme/nvmeX/transport, and
that is exactly what nvme list-subsys reports. Defined by the NVMe-oF specification (TRTYPE),
closed set, stable.
libstorage-ng's NVMe half therefore needs no improvement: it consumes the authoritative value.
drivers/scsi/scsi_transport_{fc,iscsi,sas,spi,srp}.c — five classes, exposed as
/sys/class/{fc,iscsi,sas,spi,srp}_host etc. This is the only place where the kernel itself uses
the word "transport" for SCSI, and it is precisely what lsscsi and lsblk probe. Note what is not
a transport class and therefore can only be inferred: ATA/SATA (libata's SCSI emulation),
USB (usb-storage/uas), SBP (firewire-sbp2), FCoE (uses fc_host).
| source | list |
|---|---|
| libstorage-ng / Agama | sbp, ata, fc, iscsi, sas, sata, spi, usb, fcoe, pcie, tcp, rdma, loop |
lsscsi (--transport) |
spi, fc, fcoe, sas, sas-class, iscsi, sbp, usb, ata, sata, srp, pcie, pseudo_0 |
lsblk (TRAN) |
spi, fc, fcoe, sas, sbp, iscsi, usb, sata, ata, nvme, virtio, mmc |
udev ID_BUS |
ata, scsi, usb, ieee1394, cciss (set by 60-persistent-storage.rules + sg3_utils rules) |
udev ID_PATH prefixes (udev-builtin-path_id.c) |
ata-, sas-, sas-exp, fc-, ip-…-iscsi-, usb-, ieee1394-, scsi-, cciss-, vmbus-, acpi-, ap-, bcma-, nvme-, ccw- |
Four different lists, four different granularities. That is the empirical answer to the review comment: no authoritative list exists; libstorage-ng's is neither better nor worse founded than util-linux's, and it is the most detailed one for NVMe.
Ordered by impact on the new Agama search condition.
| device | libstorage-ng | lsblk | why |
|---|---|---|---|
virtio-blk (vda) |
unknown | virtio |
not a SCSI device, lsscsi does not list it |
SD/eMMC (mmcblk*) |
unknown | mmc |
idem |
DASD (s390, dasd*) |
unknown (no attribute at all) | — | Dasd is not a Disk |
Xen PV (xvd*), cciss, rbd, zvol |
unknown | mostly — | idem |
| pmem / brd / nbd | unknown (explicit) | — | "no proper transport" by design |
| multipath, MD RAID, BIOS RAID | no attribute | per-member | transport only exists on Disk |
The virtio case is the most relevant one for Agama: in a VM using virtio-blk, transport is simply
never usable. The multipath case is arguably worse: an FC or iSCSI SAN LUN reaching the system
through several paths is exposed by Agama as a multipath drive, and that drive has no
transport, so { "transport": "fc" } will not match it even though the underlying disks would.
lsscsi prints srp:<gid> for SCSI-over-RDMA (InfiniBand) disks; CmdLsscsi::parse has no branch
for it, so the disk ends as UNKNOWN. Trivial upstream fix (the enum has no SRP value either, so
it needs both).
libstorage-ng detects iSCSI with boost::starts_with(transport, "iqn") because lsscsi prints the
target name in the transport column. But iSCSI names are defined by RFC 3720/7143 to use one of
three formats: iqn., eui. and naa.. A target using EUI-64 or NAA naming (some arrays and
most software targets can be configured that way) produces a row starting with eui. / naa.,
which falls through all branches → UNKNOWN.
Note that the same disk is correctly identified as iSCSI by lsblk (which checks
/sys/class/iscsi_host) and by udev (ip-…-iscsi-… path). So this is a libstorage-ng bug, not a
Linux limitation.
Both lsscsi and lsblk decide FCoE by looking for the literal " over " inside
/sys/class/fc_host/hostN/symbolic_name (a free-form vendor string). Fragile by construction, and
FCoE is being removed from several drivers. transport: "fcoe" should be considered best-effort.
As described in §2: driver-name heuristic, unreliable outside AHCI. A user wanting "any locally
attached disk that is not USB/network" cannot express it as sata — they would need
or: [sata, ata, sas, spi, pcie], and even that misses virtio.
fcnow means two different things: SCSI FCP (from lsscsi) and NVMe-over-Fibre-Channel (from nvme-cli). A search forfcmatches both. Probably desirable, but undocumented.loopis the NVMe loopback target (nvmet-loop, essentially a test/dev configuration), not/dev/loopNloop devices. A user reading the schema will assume the latter. High confusion risk.pciein practice means "local NVMe" (a SCSI-over-PCIe/SOP device would also land here, but those are essentially nonexistent).spiis the SCSI Parallel Interface, not the SPI bus of/sys/class/spi_master.
unknown was deliberately excluded from the schema, and the matcher returns false for a device
whose transport is unknown or absent. Therefore { "not": { "transport": "usb" } } matches every
device with an undetermined transport (virtio disks, multipath, DASD…). Given §4.1 that is a
large set. It is defensible, but it must be documented; and the user currently has no way to write
"the transport could not be determined".
Text parsing of lsscsi --transport with word indexes and two replace_all fixups; the JSON code
path is detected (supports_json_option(), lsscsi ≥ 0.33) and the result discarded. Also,
nvme list-subsys multi-path subsystems take paths[0] (upstream TODO) — an NVMe namespace
reachable over both fc and tcp reports whichever path comes first.
It would be good if we could find some kind of authoritative list of "data transports" for Linux and make sure what libstorage-ng does is actually the proper detection mechanism instead of an opinionated value from an opinionated list of possible transports.
- Authoritative list: it does not exist. "Transport of a block device" is not a kernel concept.
The two standardized enumerations that do exist (T10 SAM protocol identifiers for SCSI; NVMe-oF
TRTYPE for NVMe) cover the two families separately, and libstorage-ng's 13 values map onto them
almost exactly (§3.1). The only non-standard distinctions are
ata/sataandfc/fcoe, both of which are long-standing Linux conventions shared with util-linux. So the list is opinionated in the sense that somebody had to unify two vocabularies, but it is not arbitrary. - Detection mechanism: it is the standard Linux approach, and it is heuristic by nature. lsscsi and lsblk implement the same sysfs probes; there is no third, better implementation. For NVMe, libstorage-ng consumes the authoritative kernel value and is actually more precise than lsblk. For SCSI-attached devices it is as precise as anything available.
- Where libstorage-ng is genuinely weaker than the alternatives is coverage
(virtio/mmc are trivially detectable and lsblk detects them; SRP is detected by lsscsi and
dropped; non-
iqniSCSI targets are missed) and scope (no transport for multipath/RAID/DASD). Those are fixable bugs, not conceptual problems.
Conclusion: the search condition is fine to ship, but its documentation must be honest about what the value means and when it is absent, and we should push the fixable parts upstream.
The 13 values are what libstorage-ng can deliver, they are what the reported transport field of
the devices API already exposes, and they map to real standardized protocols. Inventing an Agama
list (or aliasing to lsblk's) would only add a translation layer over the same heuristics.
Short description covering:
- the value is the transport of the host adapter path the kernel used, best-effort;
pcie= local NVMe;tcp/rdma/loop= NVMe over Fabrics (loopis the nvmet loopback target, not/dev/loopN);fccovers both SCSI FCP and NVMe-over-FC;atavssatais derived from the driver name and is unreliable outside AHCI;- devices with an undetermined transport (virtio, SD/MMC, DASD, multipath, MD/BIOS RAID, pmem, nbd) never match any transport condition, and therefore always match a negated one;
- for network-attached storage, combining with
driveris more reliable — cross-reference bsc#1176140.
Today unknown is excluded from the enum by design, so { "transport": … } cannot express
"undetermined". Given §4.1 this is a real use case ("give me the virtio disk in this VM" currently
has no transport-based answer). Two options, in order of preference:
- Add a presence shortcut to the leaf (
{ "transport": "none" }/"any"), consistent with howfilesystemandpartitionsalready work. Cleanest, and it does not put a fake value in the enum. - Add
unknowntotransportValueand let the matcher compare againstDataTransport::UNKNOWN/ a device that has no transport attribute.
Either is a follow-up iteration, not a blocker for this PR.
- iSCSI targets named
eui.*/naa.*are not detected (CmdLsscsi.cc,starts_with("iqn")). Suggested fix: detect iSCSI from the host class (/sys/class/iscsi_host/hostN) like lsblk does, or accept the three RFC 3720 prefixes. srp:rows are dropped; addTransport::SRP(T10 protocol id 4h).- virtio and mmc are never detected; lsblk's name-based fallback (
vd*→ virtio,mmcblk*→ mmc) would cover the vast majority of VM installs. Needs two new enum values. - Transport is only available on
Disk; consider deriving it forMultipath(and possiblyDmRaid/MdRaid) from the parent disks when they all agree. CmdLsscsicomputessupports_json_option()and ignores it; switching tolsscsi --jsonwould remove the column-layout parsing and the tworeplace_allfixups.- Minor:
CmdNvmeListSubsys::get_transporttakespaths[0]for multi-path subsystems (already aTODOupstream).
Reading sysfs/udev ourselves (via ID_PATH, /sys/class/nvme/*/transport, lsblk's algorithm)
would duplicate libstorage-ng's job and drift from the value we already report in the devices API.
The right place for every fix above is libstorage-ng.
| what | where |
|---|---|
| Transport enum | libstorage-ng/storage/Devices/Disk.h:37 |
| Probing logic | libstorage-ng/storage/Devices/DiskImpl.cc, Disk::Impl::probe_pass_1a |
| lsscsi parsing | libstorage-ng/storage/SystemInfo/CmdLsscsi.cc (the linked L78) |
| NVMe parsing | libstorage-ng/storage/SystemInfo/CmdNvme.cc |
| Runtime dependency | libstorage-ng/package/libstorage-ng.spec:87 (Requires: lsscsi >= 0.26) |
| lsscsi detection | doug-gilbert/lsscsi:src/lsscsi.c, transport_h_init() / transport_sdev_tport(), TRANSPORT_* defines at L81-94 |
| lsblk detection | util-linux/lsblk-cmd/lsblk.c:509, get_transport() |
| Kernel SCSI transport classes | linux/drivers/scsi/scsi_transport_{fc,iscsi,sas,spi,srp}.c |
| NVMe transport types | linux/include/linux/nvme.h (NVMF_TRTYPE_*), drivers/nvme/host/{pci,rdma,fc,tcp}.c, drivers/nvme/target/loop.c |
| T10 protocol identifiers | sg3_utils/lib/sg_lib_data.c:1645 (sg_lib_transport_proto_strs[]) |
| udev path prefixes | systemd/src/udev/udev-builtin-path_id.c |
udev ID_BUS |
/usr/lib/udev/rules.d/60-persistent-storage.rules, 55-scsi-sg3_id.rules |
| bsc#1176140 follow-up | yast2-storage-ng commit 2c6aaae3, src/lib/y2storage/disk.rb (SYSTEMD_REMOTE_DRIVERS), changelog 4.3.46 |
| Agama side | rust/share/storage.schema.json ($defs/transportValue), service/lib/agama/storage/devicegraph_conversions/to_json_conversions/drive.rb#drive_transport, config_solvers/search_matchers/with_transport.rb |
| Local checks | openSUSE, lsscsi 0.32, /dev/sda SATA/AHCI: lsscsi --transport → sata:…, lsblk -o TRAN → sata, proc_name → ahci, by-path → pci-0000:00:17.0-ata-3 |