Skip to content

nbdkit-qdl-plugin small cleanups & documentation rework - #310

Open
obbardc wants to merge 10 commits into
linux-msm:masterfrom
obbardc:wip/obbardc/nbd-bits
Open

obbardc wants to merge 10 commits into
linux-msm:masterfrom
obbardc:wip/obbardc/nbd-bits

Conversation

@obbardc

@obbardc obbardc commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

When updating the Debian packaging to ship the nbdkit-qdl-plugin, I tried the built plugin with an RB3gen2 and the documentation under doc/nbd.md.

I found the documentation quite hard to follow, so I reworked it and while I was there I added some small quality-of-life changes to the plugin implementation along the way. The main one is the cleaner timeout when connecting to the device.

There are also some minimal future changes I'd like to add in later follow-ups, if you are interested, including:

  • add a parameter to allow nbd plugin to reset firehose into a different mode on exit (e.g. edl/off/don't reset). This was mainly useful for when I was testing the plugin, e.g you don't want the device to reboot into the image after it exits, and want to reset back into EDL so you can run the plugin again. This depends on another pending change I have to add a qdl firehose-reset <mode> command to qdl; I will open that PR shortly.
  • document why the plugin can't connect to device at startup
  • allow matching devices based on serial number
  • debug device serial number on connect and expose serial number (and other metadata) to the NBD device so userspace can read it
  • unit-tests for nbdkit-qdl-plugin in CI. Not sure how this can be done really; possibly could even do some full device-in-the-loop testing but I think that's probably far too much. Ideas welcome.

Let me know if you think any of these are not worth implementing.

The Linux CI job now builds the plugin with -Dnbdkit=enabled. The .block_size callback needs nbdkit 1.30 or later, so it is only registered when building against a new enough nbdkit (Ubuntu 22.04 ships 1.24); on older versions the plugin still builds but keeps nbdkit's default block size advertisement.

Changes since v1:

  • Rebased onto master; kept both the new .load callback and .after_fork.
  • github: use the github: prefix and configure with -Dnbdkit=enabled on the Linux job.
  • nbd: guard .block_size behind NBDKIT_VERSION >= 1.30 to fix the Ubuntu 22.04 build.
  • docs: fix typos, add a text fence and note that the block_size_* lines need nbdkit 1.30+.
  • Fix "LIN" and "nbdkigt" typos in commit messages.

@obbardc
obbardc requested a review from a team as a code owner August 25, 2026 19:11
@obbardc
obbardc force-pushed the wip/obbardc/nbd-bits branch from 93b5291 to 3e67fd7 Compare August 25, 2026 19:17
Comment thread src/nbdkit-qdl-plugin.c Resolved
@obbardc
obbardc force-pushed the wip/obbardc/nbd-bits branch 3 times, most recently from 931eff3 to ff2a9b3 Compare August 25, 2026 19:47

@igoropaniuk igoropaniuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really nice cleanup, thanks!

Just some nits: 0d30cc4 subject says "LIN sector size" (should be LUN), c3b47b1 body has "nbdkigt"

Could you also rebase onto the latest master? When rebasing: master now has a .load callback that sets the progname and forces ux_set_color_mode(UX_COLOR_NEVER). Please keep both .load and .after_fork. With colour forced off, redirecting stdout here won't leak ANSI escapes into the nbdkit log, which is good IMO.

Comment thread .github/workflows/build.yml Resolved
Comment thread src/nbdkit-qdl-plugin.c Resolved
Comment thread docs/nbd.md Resolved
Comment thread docs/nbd.md Resolved
Comment thread docs/nbd.md Resolved
Comment thread docs/nbd.md Resolved
The nbdkit plugin is an optional build target: meson only configures it
when the nbdkit pkg-config file is present: on the CI runner it was
silently skipped and the plugin sources were never compiled.

Install nbdkit-plugin-dev on the Linux job so the plugin is build
tested and kept from bit-rotting alongside the rest of the tree. Also
configure with -Dnbdkit=enabled so the job fails loudly if the plugin
is ever skipped again, e.g. because the dependency goes missing.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The plugin currently defaults to LUN 1 when no LUN is configured. On
typical Qualcomm UFS partition layouts, LUN 1 contains bootloader
partitions while LUN 0 contains the userspace/HLOS/Linux partitions
that users are more likely to wish to target using the plugin.

Devices with eMMC storage may expose only LUN 0 so defaulting to LUN 1
can select a non-existent LUN. The plugin documentation already uses
LUN 0 in its examples which makes the current default inconsistent with
the documented usage.

Switch the default to LUN 0, which is typically the userarea on UFS and
the only LUN available on most eMMC devices.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Currently the LUN parameter is parsed through atoi() which has no
reliable error reporting. For example, "lun=abc" is silently parsed as
LUN 0 instead of being rejected.

There is no range check for the parsed LUN either; values outside of
the valid LUN range (0-7 inclusive) are passed to firehose and fails
much later: once the device has connected, booted into firehose and the
LUN metadata is queried.

Instead, use nbdkit_parse_int() to reject malformed values and integers
outside the range representable by an int. Separately check that the
requested LUN is within the maximum of eight physical LUNs supported by
UFS devices.

Firehose still detects when a LUN within that range is not provisioned
on the device. For example, requesting LUN 6 on a device provisioned
with only five LUNs fails cleanly in firehose with:

    error: failed to query size of LUN 6

Malformed and out-of-range values now fail during plugin configuration
at startup, while device-specific LUN availability checks are handled
by firehose.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Currently the debug parameter accepts only "true" and "1" as true and
silently treats all other values as false. As a result, values such as
"debug=yes" and "debug=info" are accepted but just end up being treated
as false without any user feedback.

Instead, use nbdkit_parse_bool() to parse the value. This accepts the
boolean spellings documented by nbdkit, such as "1", "true", "t", "yes",
"y", "on" (case-insensitive), along with their false equivalents and
reports invalid values as parsing errors.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Enabling the debug parameter correctly enables qdl_debug and therefore
debug logging in the qdl library, but those messages are dropped when
qdl is used through the nbd plugin.

qdl emits debug messages through ux_*() which write to stdout. The
exception is ux_err() which writes to stderr. As a result qdl debug
messages disappear while qdl error messages emitted through ux_err()
remain visible.

nbdkit redirects stdout to /dev/null once configuration is complete,
both in foreground mode and when running as a daemon. This prevents
nbdkit plugins from interfering with modes where nbdkit use
stdin/stdout to communicate with clients.

When debugging is enabled, restore stdout from stderr in .after_fork;
the first callback invoked after this redirection. This makes qdl's
ux_*() messages go to the same destination as nbdkit's (and the
plugin's) own debug output.

This still requires "nbdkit -f" since a daemonised server also
redirects stderr to /dev/null.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
When the first client connects to the daemon, the nbdkit plugin
callback invokes qdl_open(), which calls usb_open(). usb_open() loops
until a device appears. If no device is ever connected, it leaves
nbdkit stuck indefinitely and eventually causes the daemon to crash.

This is undesirable for a server: a client connecting before the device
is attached can block indefinitely, the usleep() between probes cannot
be interrupted during shutdown and the wait is only reported through
ux_info(), which may not be visible through nbdkit.

Instead, build a custom wait and timeout loop around usb_open_once(),
the single scan-and-open operation used by usb_open(). This moves the
wait policy into the plugin where it can expose a deadline through a
new timeout parameter, sleep with nbdkit_nanosleep() so an nbdkit
shutdown can interrupt the wait and report failures through
nbdkit_error().

Report when a device is present but cannot be opened separately since
when nbdkit runs as a service user this can indicate permission
problems or another process locking the device rather than an absent
device.

The timeout defaults to 60 seconds, while timeout=0 preserves the
previous wait-forever behaviour. A timeout only fails the current
client connection: the next client which opens retries accessing the
device, so nbdkit can remain running until the device is attached.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The plugin currently does not report which LUN it is serving or the
geometry reported by firehose. If the exported size looks wrong,
diagnosing the problem requires checking the export from the client
side and comparing it with the expected device layout.

Log the LUN, sector size and sector count after connecting to the
device. This makes it clear which storage geometry the plugin is
exporting and also provides the sector size needed when comparing or
configuring block sizes on NBD clients.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The plugin currently does not validate the sector size reported by
firehose before using it to serve the export. Firehose only probes
512-byte and 4096-byte sectors; any other value means the geometry was
not established correctly.

Continuing with such a value could leave the export unusable: .pread
and .pwrite align every request against the reported sector size,
causing all I/O to be rejected as unaligned. A sector size of 0 is
worse, since the alignment checks would divide by zero.

Reject unsupported sector sizes during setup and report the offending
value. This causes the connection to fail immediately with a clear
nbdkit error instead of surfacing later as unexplained I/O failures.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The plugin currently reports only the total export size, so nbdkit
advertises its default minimum block size of 512 bytes. On a UFS LUN
with 4096-byte sectors, a client may issue a legal request aligned to
512 bytes, even though firehose can only access whole device sectors.

Such requests are rejected by .pread and .pwrite with EINVAL so the
client sees an I/O error for a request which the export advertises as
valid.

Implement .block_size and advertise the device sector size as both the
minimum and preferred block size, allowing clients which honour these
properties to align requests correctly before issuing I/O. nbdkit
requires these values to be powers of two, which is guaranteed by the
sector size validation performed during setup.

Unfortunately, not all NBD clients obey the advertised block size. For
example, nbd-client relies on the user to specify the block size
explicitly and defaults to 512 byte sectors when unset. The values are
nevertheless exposed by the NBD protocol and can be queried with
nbdinfo, for example:

    block_size_minimum: 4096 (4K)
    block_size_preferred: 4096 (4K)
    block_size_maximum: 4294967295

This gives users a way to determine the device sector size before
configuring clients which require it to be supplied manually.

The .block_size callback was only added in nbdkit 1.30, so register it
only when building against 1.30 or later. Older versions, such as the
nbdkit 1.24 shipped in Ubuntu 22.04, still build the plugin but keep
advertising nbdkit's default block size.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The nbd "how-to" guide is quite hard to follow for a first-time reader
(e.g. I wrote this change based on my notes from following the
documentation for the first time knowing little about NBD and nbdkit).

Rework it into a numbered walkthrough that covers the whole session,
from installing packages to detaching the client.

The most important addition is the sector size. nbd-client always
assumes 512-byte sectors and does not honour the block size the
server advertises; attaching a UFS LUN without "-b 4096" produces
confusing read issues on the client without any clear error message
(e.g. device partition table not read properly). Document how to read
the sector size from either the nbdkit debug log or nbdinfo and use
"-b 4096" when calling nbd-client in the example, along with details
on what the value should be and how to read it from the device.

Also correct the build dependency; only the nbdkit plugin development
files are needed to build the plugin (nbdkit-plugin-dev on Debian),
not the nbdkit runtime and list the client-side packages needed to
follow the example. Describe more precisely when the device is
contacted: on the first client connection, staying in firehose mode
until nbdkit exits and then resets the device.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
@obbardc
obbardc force-pushed the wip/obbardc/nbd-bits branch from ff2a9b3 to bd8b542 Compare November 13, 2026 21:40
@obbardc
obbardc requested a review from igoropaniuk November 13, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants