Skip to content

Instantly share code, notes, and snippets.

@joseivanlopez
Last active August 3, 2026 12:01
Show Gist options
  • Select an option

  • Save joseivanlopez/92cff2148be02c464be8d0839e13ac26 to your computer and use it in GitHub Desktop.

Select an option

Save joseivanlopez/92cff2148be02c464be8d0839e13ac26 to your computer and use it in GitHub Desktop.

Analysis: is there an authoritative list of "data transports" for Linux?

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.

TL;DR

  1. 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 TRTYPE values, in Linux pcie / 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, udev ID_BUS, udev ID_PATH.
  2. 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.
  3. 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 (same ahci/*ata* proc_name check, 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.
  4. 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), Xen xvd*, 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), so transport: fc / iscsi never matches the very devices where FC/iSCSI matter most.
    • iSCSI detection breaks for targets not named iqn.* (libstorage-ng matches the prefix iqn, but RFC 3720/7143 also allows eui. and naa.) → those disks report unknown.
    • srp (SCSI RDMA, InfiniBand) is detected by lsscsi but dropped by libstorage-ng.
    • sata vs ata is a driver-name guess; a SATA disk behind a SAS HBA reports sas.
  5. 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.

1. What libstorage-ng actually does

1.1 Probing (storage/Devices/DiskImpl.cc, Disk::Impl::probe_pass_1a)

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.

1.2 The enum (storage/Devices/Disk.h)

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).

1.3 The lsscsi mapping (storage/SystemInfo/CmdLsscsi.cc, the code the reviewer linked)

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() computes const 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 disk are considered (if (type != "disk") continue;).
  • lsscsi's srp: and pseudo_0 (scsi_debug) prefixes are not mappedUNKNOWN.
  • TCP, RDMA and LOOP can never come from here; they only come from nvme list-subsys.

1.4 The NVMe mapping (storage/SystemInfo/CmdNvme.cc)

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.


2. How lsscsi decides (and why it is a heuristic)

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 reports sas (check 1 wins) — which some users would not expect.

  • ata vs sata is 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 whose proc_name is not ahci and does not contain ata (virtio-scsi, megaraid_sas, mpt3sas, many RAID HBAs) is not reported as sata at 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).


3. What is standardized

3.1 SCSI: T10 SAM protocol identifiers

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.

3.2 NVMe: a genuinely authoritative list

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.

3.3 Kernel SCSI transport classes

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).

3.4 De-facto userspace lists

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.


4. Concrete defects found in the current detection

Ordered by impact on the new Agama search condition.

4.1 Whole device classes always report no transport

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.

4.2 srp is detected and thrown away

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).

4.3 iSCSI detection misses non-iqn target names

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.

4.4 FCoE detection is a substring match

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.

4.5 ata vs sata

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.

4.6 Naming collisions that will confuse users

  • fc now means two different things: SCSI FCP (from lsscsi) and NVMe-over-Fibre-Channel (from nvme-cli). A search for fc matches both. Probably desirable, but undocumented.
  • loop is the NVMe loopback target (nvmet-loop, essentially a test/dev configuration), not /dev/loopN loop devices. A user reading the schema will assume the latter. High confusion risk.
  • pcie in practice means "local NVMe" (a SCSI-over-PCIe/SOP device would also land here, but those are essentially nonexistent).
  • spi is the SCSI Parallel Interface, not the SPI bus of /sys/class/spi_master.

4.7 Semantics of a negated condition

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".

4.8 Parsing fragility / dead code

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.


6. Answer to the reviewer's question

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/sata and fc/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-iqn iSCSI 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.


7. Recommendations for Agama

7.1 Do not change the schema enum

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.

7.2 Document the semantics in the schema ($defs/transportValue)

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 (loop is the nvmet loopback target, not /dev/loopN); fc covers both SCSI FCP and NVMe-over-FC;
  • ata vs sata is 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 driver is more reliable — cross-reference bsc#1176140.

7.3 Consider making "no transport" expressible

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:

  1. Add a presence shortcut to the leaf ({ "transport": "none" } / "any"), consistent with how filesystem and partitions already work. Cleanest, and it does not put a fake value in the enum.
  2. Add unknown to transportValue and let the matcher compare against DataTransport::UNKNOWN / a device that has no transport attribute.

Either is a follow-up iteration, not a blocker for this PR.

7.4 Upstream bug reports (libstorage-ng)

  1. 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.
  2. srp: rows are dropped; add Transport::SRP (T10 protocol id 4h).
  3. 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.
  4. Transport is only available on Disk; consider deriving it for Multipath (and possibly DmRaid/MdRaid) from the parent disks when they all agree.
  5. CmdLsscsi computes supports_json_option() and ignores it; switching to lsscsi --json would remove the column-layout parsing and the two replace_all fixups.
  6. Minor: CmdNvmeListSubsys::get_transport takes paths[0] for multi-path subsystems (already a TODO upstream).

7.5 Do not reimplement detection in Agama

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.


Appendix: sources consulted

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 --transportsata:…, lsblk -o TRANsata, proc_nameahci, by-path → pci-0000:00:17.0-ata-3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment