NBD(3)
✨ NAME ✨
okay so bestie, welcome to NBD — the OCaml bindings for libnbd 💖 that's the whole NAME section, we ate and left no crumbs. (this is the NBD(3) page btw, iykyk 🫶)
🎀 MODULE 🎀
Module NBD — yes, that's it, that's the module. one module, main character energy, no cap 👑
📚 DOCUMENTATION 📚
Module NBD
: sig end
OCaml bindings for libnbd. fr fr, that's the vibe.
For full documentation see libnbd-ocaml(3) and libnbd(3) — go read those too, they're giving ✨lore✨.
For examples written in OCaml see the libnbd source code ocaml/examples subdirectory 🦋 (yes go look, the examples are lowkey iconic).
💀 the exceptions (they WILL happen)
exception Error of string * Unix.error option— Exception thrown when an API call fails 😭. The string is the error message, and the int is theUnix.error(if available).exception Closed of string— Exception thrown if you call a closed handle. like bestie the handle is gone, you can't sit with it anymore 🙈
🧚 the types
type cookie = int64— it's a cookie. it's anint64. that's the whole post 💅type extent = int64 * int64— Length and flags of an extent inNBD.block_status_64callback.
🌸 the little modules
these are just here, doing their thing, no descriptions given, and honestly? respect ✨
module TLS : sig endmodule SIZE : sig endmodule CMD_FLAG : sig endmodule HANDSHAKE_FLAG : sig endmodule STRICT : sig endmodule ALLOW_TRANSPORT : sig endmodule SHUTDOWN : sig end
⭐ the constants (the docs literally just hand you the name, so here's the name, mwah 😘)
val aio_direction_read : int32val aio_direction_write : int32val aio_direction_both : int32val read_data : int32val read_hole : int32val read_error : int32val namespace_base : stringval context_base_allocation : stringval state_hole : int32val state_zero : int32val namespace_qemu : stringval context_qemu_dirty_bitmap : stringval state_dirty : int32val context_qemu_allocation_depth : stringmodule Buffer : sig end— Persistent buffer used in AIO calls. that's the entire description, she's mysterious and we love that for her 🎀
🔥 THE FUNCTIONS 🔥
val errno_of_unix_error : Unix.error -> int— Return the raw Cerrnocorresponding to aUnix.error. This can be used in callbacks to update theint refparameter. (yes you can touch theint ref, go off king 👑)type t— The libnbd handle, equivalent to the C typestruct nbd_handle *. she's the moment. she's the main character. everything happens through her 💅val create : unit -> t— Create a new libnbd handle. fresh handle, fresh start, new me energy ✨val close : t -> unit— Close a handle.
Handles can also be closed by the garbage collector when they become unreachable. This call is used only if you want to force the handle to close now and reclaim resources immediately. (like when you need it gone gone, not "eventually gone" 😤)
val with_handle : (t -> 'a) -> 'a— Wrapper aroundNBD.create. It calls the function parameter with a newly created handle, and ensures thatNBD.closeis always called even if the function throws an exception.
Use this when it is essential that the handle is closed in order to free up external resources in a timely manner; for example if running the server as a subprocess and you want to ensure that the subprocess is always killed; or if you need to disconnect from the server before continuing with another operation. this one is the responsible friend who makes sure nobody leaves their stuff at the party 🫶
🐛 debug stuff
val set_debug : t -> bool -> unit—NBD.set_debug t debug— set or clear the debug flag.
Set or clear the debug flag. When debugging is enabled, debugging messages from the library are printed to stderr, unless a debugging callback has been defined too (see nbd_set_debug_callback(3)) in which case they are sent to that function. This flag defaults to false on newly created handles, except if "LIBNBD_DEBUG=1" is set in the environment in which case it defaults to true. (sneaky little env var, love that for us 🌈)
val get_debug : t -> bool—NBD.get_debug t— return the state of the debug flag.
Return the state of the debug flag on this handle.
val set_debug_callback : t -> (string -> string -> int) -> unit—NBD.set_debug_callback t debug— set the debug callback.
Set the debug callback. This function is called when the library emits debug messages, when debugging is enabled on a handle. The callback parameters are "user_data" passed to this function, the name of the libnbd function emitting the debug message ("context"), and the message itself ("msg"). If no debug callback is set on a handle then messages are printed on "stderr".
⚠️ the callback should not call "nbd_*" APIs on the same handle since it can be called while holding the handle lock and will cause a deadlock. bestie. do not do it. i am begging 🙏
val clear_debug_callback : t -> unit—NBD.clear_debug_callback t— clear the debug callback.
Remove the debug callback if one was previously associated with the handle (with nbd_set_debug_callback(3)). If no callback was associated this does nothing. no callback? no problem, she simply does not care 💅
📊 stats, we love numbers
val stats_bytes_sent : t -> int64—NBD.stats_bytes_sent t— statistics of bytes sent over connection so far.
Return the number of bytes that the client has sent to the server.
This tracks the plaintext bytes utilized by the NBD protocol; it may differ from the number of bytes actually sent over the connection, particularly when TLS is in use. (so don't @ me when the numbers don't match, TLS is doing its own thing 🔐)
val stats_chunks_sent : t -> int64—NBD.stats_chunks_sent t— statistics of chunks sent over connection so far.
Return the number of chunks that the client has sent to the server, where a chunk is a group of bytes delineated by a magic number that cannot be further subdivided without breaking the protocol.
This number does not necessarily relate to the number of API calls made, nor to the number of TCP packets sent over the connection. three different numbers, three different vibes, do not confuse them 🫠
val stats_bytes_received : t -> int64—NBD.stats_bytes_received t— statistics of bytes received over connection so far.
Return the number of bytes that the client has received from the server.
This tracks the plaintext bytes utilized by the NBD protocol; it may differ from the number of bytes actually received over the connection, particularly when TLS is in use.
val stats_chunks_received : t -> int64—NBD.stats_chunks_received t— statistics of chunks received over connection so far.
Return the number of chunks that the client has received from the server, where a chunk is a group of bytes delineated by a magic number that cannot be further subdivided without breaking the protocol.
This number does not necessarily relate to the number of API calls made, nor to the number of TCP packets received over the connection.
🏷️ name that handle
val set_handle_name : t -> string -> unit—NBD.set_handle_name t handle_name— set the handle name.
Handles have a name which is unique within the current process. The handle name is used in debug output.
Handle names are normally generated automatically and have the form "nbd1", "nbd2", etc., but you can optionally use this call to give the handles a name which is meaningful for your application to make debugging output easier to understand. name your handle something cute, your future self debugging at 2am will thank you 💖
val get_handle_name : t -> string—NBD.get_handle_name t— get the handle name.
Get the name of the handle. If it was previously set by calling nbd_set_handle_name(3) then this returns the name that was set. Otherwise it will return a generic name like "nbd1", "nbd2", etc.
🎒 private data (it's giving emotional baggage but make it uintptr_t)
val set_private_data : t -> int -> int—NBD.set_private_data t private_data— set the per-handle private data.
Handles contain a private data field for applications to use for any purpose. This function sets the value of this field and returns the old value (or 0 if it was not previously set).
When calling libnbd from C, the type of this field is "uintptr_t" so it can be used to store an unsigned integer or a pointer.
In non-C bindings it can be used to store an unsigned integer.
Libnbd does not use or interpret the value at all (except to return it when you call nbd_get_private_data(3)). In particular, if the value is a pointer then it is not freed when the handle is closed. If you want that behaviour, see nbd_add_close_callback(3). so like. libnbd is holding your stuff but she is NOT responsible for it, okay? 😭
val get_private_data : t -> int—NBD.get_private_data t— get the per-handle private data.
Return the value of the private data field set previously by a call to nbd_set_private_data(3) (or 0 if it was not previously set).
val get_handle_size : t -> int—NBD.get_handle_size t— return an estimate of the memory allocated for the handle.
Return an estimate of the number of bytes allocated for the handle. The main use for this is with compacting garbage collectors which can make a more informed decision about when to free the handle with this information. it's giving "how much space do i take up in your life" and honestly relatable 🦋
📤 export names
val set_export_name : t -> string -> unit—NBD.set_export_name t export_name— set the export name.
Some NBD servers can serve multiple disk images ("exports"). The export is picked by the client, by requesting an export name during the negotiation phase. The default export is the empty string "".
Some NBD servers ignore this and serve the same content regardless. This is only relevant when connecting to servers using the newstyle protocol as the oldstyle protocol did not support export names. (oldstyle said "one export, take it or leave it" 💀)
The NBD protocol limits export names to 4096 bytes, but servers may not support the full length. The encoding of export names is always UTF-8.
When option mode is not in use, the export name must be set before beginning a connection. However, when nbd_set_opt_mode(3) has enabled option mode, it is possible to change the export name prior to nbd_opt_go(3). In particular, the use of nbd_opt_list(3) during negotiation can be used to determine a name the server is likely to accept, and nbd_opt_info(3) can be used to learn details about an export before connecting. window shopping but for disk images ✨
This call may be skipped if using nbd_connect_uri(3) to connect to a URI that includes an export name. convenience slay 🎀
val get_export_name : t -> string—NBD.get_export_name t— get the export name.
Get the export name associated with the handle. This is the name that libnbd requests; see nbd_get_canonical_export_name(3) for determining if the server has a different canonical name for the given export (most common when requesting the default export name of an empty string ""). the server might have a government name for it, check that one too 👀
📏 block size requests
val set_request_block_size : t -> bool -> unit—NBD.set_request_block_size t request— control whetherNBD_OPT_GOrequests block size.
By default, when connecting to an export, libnbd requests that the server report any block size restrictions. The NBD protocol states that a server may supply block sizes regardless of whether the client requests them, and libnbd will report those block sizes (see nbd_get_block_size(3)); conversely, if a client does not request block sizes, the server may reject the connection instead of dealing with a client sending unaligned requests. This function makes it possible to test server behavior by emulating older clients. (cosplay as a boomer client, why not 🙈)
Note that even when block size is requested, the server is not obligated to provide any. Furthermore, if block sizes are provided (whether or not the client requested them), libnbd enforces alignment to those sizes unless nbd_set_strict_mode(3) is used to bypass client-side safety checks.
⚠️ fr fr: bypassing those client-side safety checks is a you problem, be careful out there 💀
val get_request_block_size : t -> bool—NBD.get_request_block_size t— see ifNBD_OPT_GOrequests block size.
Return the state of the block size request flag on this handle.
💫 extra details
val set_full_info : t -> bool -> unit—NBD.set_full_info t request— control whetherNBD_OPT_GOrequests extra details.
okay so bestie, picture this: by default, when you connect to an export, libnbd is only asking for the deets it actually needs to service data operations. The NBD protocol says a server can hand over optional info — like a canonical name of the export (peep nbd_get_canonical_export_name(3)) or a description of the export (peep nbd_get_export_description(3)) — but a hint from the client makes it way more likely that this extra info actually shows up. This function controls whether libnbd will provide that hint. 💅
And real talk: even when full info is requested, the server is not obligated to reply with all the information libnbd requested. Same energy: libnbd will ignore any optional server info it hasn’t been taught to recognize yet. Also, the hint to request block sizes is independently controlled via nbd_set_request_block_size(3). Periodt.
get_full_info — see if NBD_OPT_GO requests extra details
val get_full_info : t -> bool
NBD.get_full_info t
Return the state of the full info request flag on this handle. 🫶
get_canonical_export_name — return the canonical export name, if the server has one
val get_canonical_export_name : t -> string
NBD.get_canonical_export_name t
The NBD protocol permits a server to report an optional canonical export name, which may differ from the client’s request (as set by nbd_set_export_name(3) or nbd_connect_uri(3)). This function accesses any name returned by the server; it may be the same as the client request, but is more likely to differ when the client requested a connection to the default export name (an empty string ""). 🎀
Some servers are unlikely to report a canonical name unless the client specifically hinted about wanting it, via nbd_set_full_info(3).
get_export_description — return the export description, if the server has one
val get_export_description : t -> string
NBD.get_export_description t
The NBD protocol permits a server to report an optional export description. This function reports any description returned by the server. 🌸
Some servers are unlikely to report a description unless the client specifically hinted about wanting it, via nbd_set_full_info(3). For qemu-nbd(8), a description is set with -D.
set_tls — enable or require TLS (authentication and encryption)
val set_tls : t -> TLS.t -> unit
NBD.set_tls t tls
Enable or require TLS (authenticated and encrypted connections) to the NBD server. The possible settings are:
"LIBNBD_TLS_DISABLE"— Disable TLS. (The default setting, unless usingnbd_connect_uri(3)with a URI that requires TLS).
This setting is also necessary if you use nbd_set_opt_mode(3) and want to interact in plaintext with a server that implements the NBD protocol’s "SELECTIVETLS" mode, prior to enabling TLS with nbd_opt_starttls(3). Most NBD servers with TLS support prefer the NBD protocol’s "FORCEDTLS" mode, so this sort of manual interaction tends to be useful mainly during integration testing.
"LIBNBD_TLS_ALLOW"— Enable TLS if possible.
This option is insecure (or best effort) in that in some cases it will fall back to an unencrypted and/or unauthenticated connection if TLS could not be established. Use "LIBNBD_TLS_REQUIRE" below if the connection must be encrypted. ⚠️
Some servers will drop the connection if TLS fails so fallback may not be possible.
"LIBNBD_TLS_REQUIRE"— Require an encrypted and authenticated TLS connection. Always fail to connect if the connection is not encrypted and authenticated.
As well as calling this you may also need to supply the path to the certificates directory (nbd_set_tls_certificates(3)), the username (nbd_set_tls_username(3)) and/or the Pre-Shared Keys (PSK) file (nbd_set_tls_psk_file(3)). For now, when using nbd_connect_uri(3), any URI query parameters related to TLS are not handled automatically. Setting the level higher than zero will fail if libnbd was not compiled against gnutls; you can test whether this is the case with nbd_supports_tls(3). 🔥
get_tls — get the TLS request setting
val get_tls : t -> TLS.t
NBD.get_tls t
Get the TLS request setting.
Note: if you want to find out if TLS was actually negotiated on a particular connection, use nbd_get_tls_negotiated(3) instead. 💡
get_tls_negotiated — find out if TLS was negotiated on a connection
val get_tls_negotiated : t -> bool
NBD.get_tls_negotiated t
After connecting you may call this to find out if the connection is using TLS. 🔐
This is normally useful only if you set the TLS request mode to "LIBNBD_TLS_ALLOW" (see nbd_set_tls(3)), because in this mode we try to use TLS but fall back to unencrypted if it was not available. This function will tell you if TLS was negotiated or not.
In "LIBNBD_TLS_REQUIRE" mode (the most secure) the connection would have failed if TLS could not be negotiated. With "LIBNBD_TLS_DISABLE" mode, TLS is not tried automatically; but if the NBD server uses the less-common "SELECTIVETLS" mode, this function reports whether a manual nbd_opt_starttls(3) enabled TLS or if the connection is still plaintext. 🦋
set_tls_certificates — set the path to the TLS certificates directory
val set_tls_certificates : t -> string -> unit
NBD.set_tls_certificates t dir
Set the path to the TLS certificates directory. If not set and TLS is used then a compiled in default is used. For root this is /etc/pki/libnbd/. For non-root this is $HOME/.pki/libnbd and $HOME/.config/pki/libnbd. If none of these directories can be found then the system trusted CAs are used. 📁
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
set_tls_verify_peer — set whether we verify the identity of the server
val set_tls_verify_peer : t -> bool -> unit
NBD.set_tls_verify_peer t verify
Set this flag to control whether libnbd will verify the identity of the server from the server’s certificate and the certificate authority. This defaults to true when connecting to TCP servers using TLS certificate authentication, and false otherwise. 👑
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
get_tls_verify_peer — get whether we verify the identity of the server
val get_tls_verify_peer : t -> bool
NBD.get_tls_verify_peer t
Get the verify peer flag. ✅
set_tls_username — set the TLS username
val set_tls_username : t -> string -> unit
NBD.set_tls_username t username
Set the TLS client username. This is used if authenticating with PSK over TLS is enabled. If not set then the local username is used. 🫶
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
get_tls_username — get the current TLS username
val get_tls_username : t -> string
NBD.get_tls_username t
Get the current TLS username.
set_tls_hostname — set the TLS hostname
val set_tls_hostname : t -> string -> unit
NBD.set_tls_hostname t hostname
Set the TLS server hostname. This is used in preference to the hostname supplied when connecting (eg. through nbd_connect_tcp(3)), or when there is no explicit hostname at all (nbd_connect_unix(3)). It can be useful when you are connecting to a proxy which is forwarding the data to the final server, to specify the name of the final server so that the server’s certificate can be checked. 🌐
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
get_tls_hostname — get the effective TLS hostname
val get_tls_hostname : t -> string
NBD.get_tls_hostname t
Get the TLS server hostname in effect. If not set, this returns the empty string (not an error). 🎀
set_tls_psk_file — set the TLS Pre-Shared Keys (PSK) filename
val set_tls_psk_file : t -> string -> unit
NBD.set_tls_psk_file t filename
Set the TLS Pre-Shared Keys (PSK) filename. This is used if trying to authenticate to the server with a pre-shared key. There is no default so if this is not set then PSK authentication cannot be used to connect to the server. 🔑
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
set_tls_priority — set the TLS priority string
val set_tls_priority : t -> string -> unit
NBD.set_tls_priority t priority
Set the TLS priority string. This controls which ciphers, key exchange methods and MACs are used and in what order. See “Priority strings” in the GnuTLS manual (https://gnutls.org/manual/html_node/Priority-Strings.ht ml). 🧚
The default is picked at compile time, either using ./configure --with-tls-priority=PRIORITY or if that was not set then NORMAL.
It is not recommended to change this unless you know what you are doing. It may be dangerous to set this from an untrusted source, since it may allow you to read some local configuration files, and possibly downgrade TLS security. 💀
This function may be called regardless of whether TLS is supported, but will have no effect unless nbd_set_tls(3) is also used to request or require TLS.
get_tls_priority — get the current TLS priority
val get_tls_priority : t -> string
NBD.get_tls_priority t
Get the current TLS priority.
If nbd_set_tls_priority(3) was not called previously, then this returns the compile time default TLS priority. ✨
set_request_extended_headers — control use of extended headers
val set_request_extended_headers : t -> bool -> unit
NBD.set_request_extended_headers t request
By default, libnbd tries to negotiate extended headers with the server, as this protocol extension permits the use of 64-bit zero, trim, and block status actions. However, for integration testing, it can be useful to clear this flag rather than find a way to alter the server to fail the negotiation request. 🧪
For backwards compatibility, the setting of this knob is ignored if nbd_set_request_structured_replies(3) is also set to false, since the use of extended headers implies structured replies.
get_request_extended_headers — see if extended headers are attempted
val get_request_extended_headers : t -> bool
NBD.get_request_extended_headers t
Return the state of the request extended headers flag on this handle.
Note: if you want to find out if extended headers were actually negotiated on a particular connection, use nbd_get_extended_headers_negotiated(3) instead. 💡
get_extended_headers_negotiated — see if extended headers are in use
val get_extended_headers_negotiated : t -> bool
NBD.get_extended_headers_negotiated t
After connecting you may call this to find out if the connection is using extended headers. Note that this setting is sticky; this can return true even after a second nbd_opt_extended_headers(3) returns false because the server detected a duplicate request. 👑
When extended headers aren't in play, commands are capped at a 32-bit length — even though the libnbd API hands you a 64-bit parameter to express the length. 💀 And even with extended headers, the server can still enforce its own limits, which you can peek at via nbd_get_block_size(3).
✨ Little heads-up, bestie: when extended headers get negotiated, you should prefernbd_block_status_64(3)overnbd_block_status(3)if any of the meta contexts you requested throughnbd_add_meta_context(3)might hand back 64-bit status values. That said — all the well-known meta contexts covered by the current"LIBNBD_CONTEXT_*"constants only ever return 32-bit status. So like, usually you're fine. 🫶
💅 val set_request_structured_replies : t -> bool -> unit
NBD.set_request_structured_replies t request
control use of structured replies
So by default, libnbd tries to negotiate structured replies with the server, because this protocol extension has to be in use before nbd_can_meta_context(3) or nbd_can_df(3) can return true. Like, it's a prerequisite, no cap. 💯
But — for integration testing, it's genuinely useful to clear this flag instead of going and finding some way to alter the server so it fails the negotiation request. Also useful: set this to false before using nbd_set_opt_mode(3) if you want to control when to send nbd_opt_structured_reply(3) during negotiation. ✨
⚠️ Note that setting this knob to false also disables any automatic request for extended headers. Not a drill, it's a package deal.
🌸 val get_request_structured_replies : t -> bool
NBD.get_request_structured_replies t
see if structured replies are attempted
Returns the state of the request structured replies flag on this handle. That's it, that's the whole vibe check. 🎀
💡 Note: if you actually want to know whether structured replies were actually negotiated on a particular connection, use nbd_get_structured_replies_negotiated(3) instead — that's a different question entirely, bestie.
🦋 val get_structured_replies_negotiated : t -> bool
NBD.get_structured_replies_negotiated t
see if structured replies are in use
After you connect, you can call this to find out if the connection is using structured replies. 🎉
Note that this setting is sticky — it can return true even after a second nbd_opt_structured_reply(3) returns false, because the server detected a duplicate request. Iconic behaviour, honestly.
Also note: if the connection negotiates extended headers, this function returns true (since extended headers imply structured replies) even if no explicit request for structured replies was ever attempted. Sneaky but correct. 💜
🍓 val set_request_meta_context : t -> bool -> unit
NBD.set_request_meta_context t request
control whether connect automatically requests meta contexts
This one controls whether the act of connecting to an export will also try to issue NBD_OPT_SET_META_CONTEXT when the server supports structured replies or extended headers and any contexts were registered by nbd_add_meta_context(3). 👑
And which calls count as "connecting"? All the "nbd_connect_*" calls when nbd_set_opt_mode(3) is false, or nbd_opt_go(3) and nbd_opt_info(3) when option mode is enabled. Got it? Got it. ✨
The default setting is true. But the extra step of negotiating meta contexts is not always desirable, lowkey:
- performing both info and go on the same export works without needing to re-negotiate contexts on the second call 🌈
- integration testing of other servers may benefit from manual invocation of
nbd_opt_set_meta_context(3)at other times in the negotiation sequence - and even when using just
nbd_opt_info(3), it can be faster to collect the server's results by relying on the callback function passed tonbd_opt_list_meta_context(3)than a series of post-process calls tonbd_can_meta_context(3)
🙈 Note that this control has no effect if the server doesn't negotiate structured replies or extended headers, or if the client didn't request any contexts vianbd_add_meta_context(3). And heads up — setting this control tofalsemay causenbd_block_status(3)to fail. So like, know what you're doing.
💖 val get_request_meta_context : t -> bool
NBD.get_request_meta_context t
see if connect automatically requests meta contexts
Returns the state of the automatic meta context request flag on this handle. Short and sweet. 🎀
👑 val set_handshake_flags : t -> HANDSHAKE_FLAG.t list -> unit
NBD.set_handshake_flags t flags
control use of handshake flags
By default, libnbd tries to negotiate all possible handshake flags that are also supported by the server — because omitting a handshake flag can straight up prevent you from using other functionality like TLS encryption or structured replies. ✨
However! For integration testing, it can be useful to shrink the set of flags supported by the client, so you can test that a particular server handles all the various clients that were compliant to older versions of the NBD specification. Retro vibes, on purpose. 🦋
The "flags" argument is a bitmask, including zero or more of the following handshake flags:
"LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE"= 1 — the server gracefully handles unknown option requests from the client, rather than just disconnecting. 😭 Without this flag, a client can't safely request to use extensions such as TLS encryption or structured replies, since the request may cause an older server to drop the connection. Rude, but that's the spec."LIBNBD_HANDSHAKE_FLAG_NO_ZEROES"= 2 — if the client is forced to use"NBD_OPT_EXPORT_NAME"instead of the preferred"NBD_OPT_GO", this flag lets the server send fewer all-zero padding bytes over the connection. Less filler, more content. 💅
For convenience, the constant "LIBNBD_HANDSHAKE_FLAG_MASK" is available to describe all flags supported by this build of libnbd. 🫶
🧚 Future NBD extensions may add further flags, which in turn may be enabled by default in newer libnbd. So when you're trying to disable just one specific bit, it is wiser to first call nbd_get_handshake_flags(3) and modify that value, rather than blindly setting a constant value. Don't hardcode, bestie, it never ends well.
⭐ val get_handshake_flags : t -> HANDSHAKE_FLAG.t list
NBD.get_handshake_flags t
see which handshake flags are supported
Returns the state of the handshake flags on this handle. 🌟
When the handle hasn't yet completed a connection (see nbd_aio_is_created(3)), this returns the flags the client is willing to use, provided the server also advertises those flags. After the connection is ready (see nbd_aio_is_ready(3)), this returns the flags that were actually agreed on between the server and client. Two very different moods, iykyk. 💀
And if the NBD protocol defines new handshake flags, then the return value from a newer library version may include bits that were undefined at the time of compilation. Spooky but fine.
🔥 val set_pread_initialize : t -> bool -> unit
NBD.set_pread_initialize t request
control whether libnbd pre-initializes read buffers
Okay so by default, libnbd will pre-initialize the contents of a buffer passed to calls like nbd_pread(3) to all zeroes before checking for any other errors. Why? So that even if a client application passes in an uninitialized buffer and then fails to check for errors, you don't end up with a potential security risk caused by accidentally leaking prior heap contents. 😭
💀 See CVE-2022-0485 in libnbd-security(3) for an example of a security hole in an application built against an earlier version of libnbd that lacked consistent pre-initialization. Not a vibe. Learn from history.
However — for a client application that has audited that an uninitialized buffer is never dereferenced, or which does its own pre-initialization, libnbd's sanitization efforts merely pessimize performance. (Although honestly, the time spent pre-initializing may pale in comparison to time spent waiting on network packets. 🌸)
Calling this function with "request" set to false tells libnbd to skip the buffer initialization step in read commands. 🎀
🌈 val get_pread_initialize : t -> bool
NBD.get_pread_initialize t
see whether libnbd pre-initializes read buffers
Returns whether libnbd performs a pre-initialization of a buffer passed to nbd_pread(3) and similar to all zeroes, as set by nbd_set_pread_initialize(3). 💜
💅 val set_strict_mode : t -> STRICT.t list -> unit
NBD.set_strict_mode t flags
control how strictly to follow NBD protocol
So by default, libnbd tries to detect requests that would trigger undefined behavior in the NBD protocol, and rejects them client side without causing any network traffic, rather than risking undefined server behavior. Responsible queen behaviour. 👑
However, for integration testing, it can be handy to relax the strictness of libnbd, to coerce it into sending such requests over the network — so you can test the robustness of the server in dealing with such traffic. Chaos as a testing strategy, honestly obsessed. 🔥
The "flags" argument is a bitmask, including zero or more of the following strictness flags:
"LIBNBD_STRICT_COMMANDS"= 0x1 — if set, this flag rejects client requests that don't comply with the set of advertised server flags (for example, attempting a write on a read-only server, or attempting to use"LIBNBD_CMD_FLAG_FUA"whennbd_can_fua(3)returned false). If clear, this flag relies on the server to reject unexpected commands. 📨"LIBNBD_STRICT_FLAGS"= 0x2 — if set, this flag rejects client requests that attempt to set a command flag not recognized by libnbd (those outside of"LIBNBD_CMD_FLAG_MASK"), or a flag not normally associated with a command (such as using"LIBNBD_CMD_FLAG_FUA"on a read command). If clear, all flags are sent on to the server — even if sending such a flag may cause the server to change its reply in a manner that confuses libnbd, perhaps causing deadlock or ending the connection. 💀 Yeah. That kind of clear.
Flags that are known by libnbd as associated with a given command (such as "LIBNBD_CMD_FLAG_DF" for nbd_pread_structured(3) gated by nbd_can_df(3)) are controlled by "LIBNBD_STRICT_COMMANDS" instead; and "LIBNBD_CMD_FLAG_PAYLOAD_LEN" is managed automatically by libnbd unless "LIBNBD_STRICT_AUTO_FLAG" is disabled. 🫶
⚠️ Note that the NBD protocol only supports 16 bits of command flags, even though the libnbd API uses "uint32_t"; bits outside of the range permitted by the protocol are always a client-side error. No cap, no exceptions.
"LIBNBD_STRICT_BOUNDS"= 0x4 — if set, this flag rejects client requests that would exceed the export bounds, without sending any traffic to the server. If clear, this flag relies on the server to detect out-of-bounds requests. 🚧"LIBNBD_STRICT_ZERO_SIZE"= 0x8 — if set, this flag rejects client requests with length 0. If clear, this permits zero-length requests to the server, which may produce undefined results. 🙈 Nothing screams "undefined" like a zero-length request, bestie."LIBNBD_STRICT_ALIGN"= 0x10 — if set, and the server provided minimum block sizes (see"LIBNBD_SIZE_MINIMUM"fornbd_get_block_size(3)), this flag rejects client requests that don't have length and offset aligned to the server's minimum requirements — except for an unaligned length that extends to the end of an image with an overall unaligned size. If clear, all unaligned requests are sent to the server, where it's up to the server whether to honor or reject the request. 🎀"LIBNBD_STRICT_PAYLOAD" = 0x20— okay so if this bit is set, the client is like "nope, absolutely not sending that" and refuses to send a command to the server with anything bigger than libnbd's outgoing payload maximum (peep"LIBNBD_SIZE_PAYLOAD"over innbd_get_block_size(3)), and it does this whether or not the server even advertised a block size maximum. If it's clear, then oversize requests up to 64MiB might get attempted — but bestie, requests over 32MiB are lowkey liable to make some servers just disconnect on you. 💀"LIBNBD_STRICT_AUTO_FLAG" = 0x40— if this is set, any command that accepts the"LIBNBD_CMD_FLAG_PAYLOAD_LEN"flag (thinking ofnbd_pwrite(3)andnbd_block_status_filter(3)here) straight-up ignores whether the caller did or didn't pass that flag, and instead sends over the wire whatever value matches the server's expectations based on whether extended headers got negotiated when the connection was made. If it's clear, then you the caller are taking on the whole responsibility for whether the payload length flag is set or clear during the affected command — which, ngl, can be handy during integration testing but is way more likely to lead to undefined behavior. 🙈
For convenience, the constant "LIBNBD_STRICT_MASK" exists to describe all the strictness flags supported by this build of libnbd. Future versions of libnbd may add more flags, and those are likely to be enabled by default for extra client-side filtering. So when you're trying to relax just one specific bit while keeping all the other checks client-side, it's way wiser to first call nbd_get_strict_mode(3) and modify that value rather than blindly setting a constant value. Like, don't hardcode, it's giving chaos. ✨
🌸 get_strict_mode — see which strictness flags are in effect
val get_strict_mode : t -> STRICT.t list
NBD.get_strict_mode t
Return flags indicating which protocol strictness items are being enforced locally by libnbd instead of by the server. Heads up: if you're running a newer library version, the return value may include bits that were undefined back when your code was compiled. 👑
🦋 set_opt_mode — control option mode, for pausing during option negotiation
val set_opt_mode : t -> bool -> unit
NBD.set_opt_mode t enable
Set this flag to true when you want a connection command "nbd_connect_*" to pause for negotiation options instead of just zooming all the way to the ready state — this is for when you're talking to a newstyle server. If you're connecting to an oldstyle server, this setting literally does nothing. 💅
Note that libnbd defaults to attempting"NBD_OPT_STARTTLS","NBD_OPT_EXTENDED_HEADERS", and"NBD_OPT_STRUCTURED_REPLY"before it lets you control the remaining negotiation steps. If you need control over those steps too, first setnbd_set_tls(3)to"LIBNBD_TLS_DISABLE", and setnbd_set_request_extended_headers(3)ornbd_set_request_structured_replies(3)to false, before you even start the connection attempt. 🫶
When option mode is enabled, you get fine-grained control over which options get negotiated — compared to the default where the server negotiates everything on your behalf using the settings you made before starting the connection. To leave the mode and proceed on to the ready state, you must use nbd_opt_go(3) successfully. And a failed nbd_opt_go(3) drops you back into the negotiating state so you can change the export name and try again. You can also use nbd_opt_abort(3) or nbd_shutdown(3) to end the connection without finishing negotiation. 🎀
⭐ get_opt_mode — return whether option mode was enabled
val get_opt_mode : t -> bool
NBD.get_opt_mode t
Returns true if option negotiation mode was enabled on this handle. Simple as that, icon behavior. ✅
🔥 opt_go — end negotiation and move on to using an export
val opt_go : t -> unit
NBD.opt_go t
Request that the server finish negotiation and move on to serving the export previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). This can only be used if nbd_set_opt_mode(3) enabled option mode. 😌
By default, libnbd will automatically request all meta contexts registered by nbd_add_meta_context(3) as part of this call — but you can suppress that with nbd_set_request_meta_context(3), which is especially useful if nbd_opt_set_meta_context(3) was already used earlier in the negotiation sequence. 🧚
If this fails, the server may still be in negotiation, and in that case it's possible to attempt another option, like a different export name — although older servers will instead have just killed the connection. 💀
💜 opt_abort — end negotiation and close the connection
val opt_abort : t -> unit
NBD.opt_abort t
Request that the server finish negotiation, gracefully if possible, and then close the connection. This can only be used if nbd_set_opt_mode(3) enabled option mode. Bye bestie 👋
🌈 opt_starttls — request the server to initiate TLS
val opt_starttls : t -> bool
NBD.opt_starttls t
Request that the server initiate a secure TLS connection, by sending "NBD_OPT_STARTTLS". This can only be used if nbd_set_opt_mode(3) enabled option mode. And also — if you use nbd_set_tls(3) to request anything other than the default of "LIBNBD_TLS_DISABLE", then libnbd will have already attempted a TLS connection before letting you control option negotiation. This command is disabled if nbd_supports_tls(3) reports false. 🔒
This function is mainly useful for integration testing of corner cases in server handling. In particular — and I need you to hear this — misuse of this function coupled with a server that isn't careful about resetting stateful commands such as nbd_opt_structured_reply(3) could result in a security hole (see CVE-2021-3716 against nbdkit, for example). So when security is a concern, you should instead prefer to use nbd_set_tls(3) with "LIBNBD_TLS_REQUIRE" and just let libnbd negotiate TLS automatically, fr fr. 🚨
This function returns true if the server replies with success, false if the server replies with an error, and fails only if the server does not reply (like, for a loss of connection, which can include when the server rejects credentials supplied during the TLS handshake). Note that the NBD protocol documents that requesting TLS after it's already enabled is a client error; most servers will gracefully fail a second request, but that does not downgrade a TLS session that's already been established, as reported by nbd_get_tls_negotiated(3). 🛡️
🍓 opt_extended_headers — request the server to enable extended headers
val opt_extended_headers : t -> bool
NBD.opt_extended_headers t
Request that the server use extended headers, by sending "NBD_OPT_EXTENDED_HEADERS". This can only be used if nbd_set_opt_mode(3) enabled option mode. Also, libnbd defaults to automatically requesting this unless you use nbd_set_request_extended_headers(3) or nbd_set_request_structured_replies(3) prior to connecting. This function is mainly useful for integration testing of corner cases in server handling. 🔬
It returns true if the server replies with success, false if the server replies with an error, and fails only if the server does not reply (loss of connection type beat). Note that some servers fail a second request as redundant; libnbd assumes that once one request has succeeded, then extended headers are supported (as visible by nbd_get_extended_headers_negotiated(3)) regardless of whether later calls to this function return false. And if this function returns true, the use of structured replies is implied. Slay. ✨
🎀 opt_structured_reply — request the server to enable structured replies
val opt_structured_reply : t -> bool
NBD.opt_structured_reply t
Request that the server use structured replies, by sending "NBD_OPT_STRUCTURED_REPLY". This can only be used if nbd_set_opt_mode(3) enabled option mode. And again, libnbd defaults to automatically requesting this unless you use nbd_set_request_structured_replies(3) prior to connecting. Mainly useful for integration testing of corner cases in server handling. 🧪
Returns true if the server replies with success, false if the server replies with an error, and fails only if the server does not reply (such as for a loss of connection). Note that some servers fail a second request as redundant; libnbd assumes that once one request has succeeded, then structured replies are supported (as visible by nbd_get_structured_replies_negotiated(3)) regardless of whether later calls to this function return false. Similarly, a server may fail this request if extended headers are already negotiated, since extended headers take priority. Priorities, bestie. 👑
🦋 opt_list — request the server to list all exports during negotiation
val opt_list : t -> (string -> string -> int) -> int
NBD.opt_list t list
Request that the server list all the exports that it supports. This can only be used if nbd_set_opt_mode(3) enabled option mode. 📋
The "list" function gets called once per advertised export, with any "user_data" passed to this function, and with "name" and "description" supplied by the server. Many servers omit descriptions, in which case "description" will be an empty string. And remember — it is not safe to call nbd_set_export_name(3) from inside the context of the callback function; instead, your code has to copy any "name" you need for later use after this function completes. At present, the return value of the callback is ignored, although a return of -1 should be avoided. Don't do it. 🙅
For convenience, when this function succeeds, it returns the number of exports that were advertised by the server. 🎉
Not all servers understand this request, and even when they do, the server might intentionally send an empty list to avoid being an information leak, may encounter a failure after delivering partial results, or may refuse to answer more than one query per connection in the interest of avoiding negotiation that doesn't resolve. So like, this function may succeed even when no exports are reported, or may fail but still have a non-empty list. Likewise, the NBD protocol doesn't specify an upper bound for the number of exports that might be advertised, so client code should be aware that a server may send a hella lengthy list. 😭
For nbd-server(1) you'll need to allow clients to make list requests by adding allowlist=true to the " generic " section of /etc/nbd-server/config. For qemu-nbd(8), a description is set with -D. 🛠️
💫 opt_info — request the server for information about an export
val opt_info : t -> unit
NBD.opt_info t
Request that the server supply information about the export name previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). This can only be used if nbd_set_opt_mode(3) enabled option mode. Info girl summer 💅
Okay so if it all works out, functions like nbd_is_read_only(3) and nbd_get_size(3) will spill the tea about that export. 💅 And if nbd_set_request_meta_context(3) is set (which it is by default, bestie) and structured replies or extended headers were negotiated, then it's also valid to use nbd_can_meta_context(3) after this call. That said — lowkey it might be more efficient to clear that setting and manually use nbd_opt_list_meta_context(3) with its callback approach to find out which contexts an export actually supports. In general, if nbd_opt_go(3) gets called next, that call will likely succeed with all the details staying the same — but ngl, not all servers guarantee that, so don't @ me.
Not every server even understands this request, and even when it does? The server might still fail it even when a corresponding nbd_opt_go(3) would've said slay. 😭
🌈 opt_list_meta_context — list available meta contexts, using implicit query list
val opt_list_meta_context : t -> (string -> int) -> int
NBD.opt_list_meta_context t context
So you're telling the server: "hey, list me the meta contexts available for this export please 🥺". It's asking about the export that was most recently specified by nbd_set_export_name(3) or nbd_connect_uri(3), and it uses a list of queries built up from prior calls to nbd_add_meta_context(3) (peep nbd_opt_list_meta_context_queries(3) if you'd rather hand over an explicit query list instead). Heads up: this only works if nbd_set_opt_mode(3) turned on option mode. 💖
The NBD protocol lets the client decide how many queries to throw at the server. But rather than taking that list as a parameter here, libnbd just reuses the current list of requested meta contexts as set by nbd_add_meta_context(3) — and you can use nbd_clear_meta_contexts(3) to set up a whole different list of queries. When the list is empty, a server will typically reply with all the contexts it supports; when the list is non-empty, the server replies only with supported contexts that match what the client asked for. ✨
Careful though: a reply from the server might be encoded to represent several feasible contexts within one string, rather than multiple strings per actual context name that would actually succeed duringnbd_opt_go(3). So it's still necessary to usenbd_can_meta_context(3)after connecting to see which contexts are actually supported. iykyk 💀
The "context" function gets called once per server reply, with any "user_data" passed to this function, and with "name" supplied by the server. Remember: it is not safe to call nbd_add_meta_context(3) from within the context of the callback function — no cap. Instead, your code must copy any "name" needed for later use after this function completes. At present, the return value of the callback is ignored, although a return of -1 should be avoided. 🙈
For convenience, when this function succeeds, it returns the number of replies returned by the server. 🎀
Not all servers understand this request, and even when it is understood, the server might intentionally send an empty list because it doesn't support the requested context, or it may encounter a failure after delivering partial results. So this function may succeed even when no contexts are reported, or may fail but have a non-empty list. Likewise, the NBD protocol doesn't specify an upper bound for the number of replies that might be advertised, so client code should be aware that a server may send a lengthy list. 😭
🦋 opt_list_meta_context_queries — list available meta contexts, using explicit query list
val opt_list_meta_context_queries : t -> string list -> (string -> int)
-> int
NBD.opt_list_meta_context_queries t queries context
Same energy as before, but this time you're the one handing over the query list. ✨ Request that the server list available meta contexts associated with the export previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3), using an explicit list of queries given as a parameter (check nbd_opt_list_meta_context(3) if you want to reuse an implicit query list instead). Only usable if nbd_set_opt_mode(3) enabled option mode. 💅
The NBD protocol lets a client decide how many queries to ask the server. For this function, the list is explicit in the "queries" parameter. When the list is empty, a server will typically reply with all contexts it supports; when the list is non-empty, the server replies only with supported contexts matching the client's request. Note that a reply might be encoded to represent several feasible contexts within one string, rather than multiple strings per actual context name that would actually succeed during nbd_opt_go(3) — so it's still necessary to use nbd_can_meta_context(3) after connecting to see which contexts are actually supported. 👑
The "context" function is called once per server reply, with any "user_data" passed to this function, and with "name" supplied by the server. And fr fr: it is not safe to call nbd_add_meta_context(3) from within the context of the callback function. Your code must copy any "name" needed for later use after this function completes. At present the return value of the callback is ignored, although a return of -1 should be avoided. 🙈
For convenience, when this function succeeds, it returns the number of replies returned by the server. 🫶
Not all servers understand this request, and even when it is understood, the server might intentionally send an empty list because it doesn't support the requested context, or may hit a failure after delivering partial results. So yes — this function may succeed even when no contexts are reported, or may fail but have a non-empty list. And the NBD protocol doesn't specify an upper bound for the number of replies that might be advertised, so your client code should know a server may send a lengthy list. 💀
🌸 opt_set_meta_context — select specific meta contexts, using implicit query list
val opt_set_meta_context : t -> (string -> int) -> int
NBD.opt_set_meta_context t context
Now we're selecting instead of just listing. 💖 Request that the server supply all recognized meta contexts registered through prior calls to nbd_add_meta_context(3), together with the export previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). Only usable if nbd_set_opt_mode(3) enabled option mode.
Normally this function is redundant, because nbd_opt_go(3) automatically does the same task if structured replies or extended headers have already been negotiated. But manual control over meta context requests can be useful for fine-grained testing of how a server handles unusual negotiation sequences. 👑 Often, use of this function is coupled with nbd_set_request_meta_context(3) to bypass the automatic context request normally performed by nbd_opt_go(3).
The NBD protocol lets a client decide how many queries to ask the server. Rather than taking that list as a parameter here, libnbd reuses the current list of requested meta contexts as set by nbd_add_meta_context(3); you can use nbd_clear_meta_contexts(3) to set up a different list of queries (see nbd_opt_set_meta_context_queries(3) to pass an explicit list of contexts instead). Since this function is primarily designed for testing servers, libnbd does not prevent the use of this function on an empty list or when nbd_set_request_structured_replies(3) has disabled structured replies — all so you can see how a server behaves. 🧚
The "context" function is called once per server reply, with any "user_data" passed to this function, and with "name" supplied by the server. Additionally, each server name will remain visible through nbd_can_meta_context(3) until the next attempt at nbd_set_export_name(3) or nbd_opt_set_meta_context(3), as well as nbd_opt_go(3) or nbd_opt_info(3) that trigger an automatic meta context request. Remember that it is not safe to call any "nbd_*" APIs from within the context of the callback function. At present, the return value of the callback is ignored, although a return of -1 should be avoided. 🙈
For convenience, when this function succeeds, it returns the number of replies returned by the server. ⭐
Not all servers understand this request, and even when it is understood, the server might intentionally send an empty list because it doesn't support the requested context, or may encounter a failure after delivering partial results. So this function may succeed even when no contexts are reported, or may fail but have a non-empty list. 😭
🎀 opt_set_meta_context_queries — select specific meta contexts, using explicit query list
val opt_set_meta_context_queries : t -> string list -> (string -> int)
-> int
NBD.opt_set_meta_context_queries t queries context
Request that the server supply all recognized meta contexts passed in through "queries", in conjunction with the export previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). Only usable if nbd_set_opt_mode(3) enabled option mode. ✨
Normally this function is redundant, because nbd_opt_go(3) automatically does the same task if structured replies or extended headers have already been negotiated. But manual control over meta context requests can be useful for fine-grained testing of how a server handles unusual negotiation sequences. Often, use of this function is coupled with nbd_set_request_meta_context(3) to bypass the automatic context request normally performed by nbd_opt_go(3). 💅
The NBD protocol lets a client decide how many queries to ask the server. This function takes an explicit list of queries; to instead reuse an implicit list, see nbd_opt_set_meta_context(3). Since this function is primarily designed for testing servers, libnbd does not prevent the use of this function on an empty list or when nbd_set_request_structured_replies(3) has disabled structured replies, in order to see how a server behaves. 🌈
The "context" function is called once per server reply, with any "user_data" passed to this function, and with "name" supplied by the server. Additionally, each server name will remain visible through nbd_can_meta_context(3) until the next attempt at nbd_set_export_name(3) or nbd_opt_set_meta_context(3), as well as nbd_opt_go(3) or nbd_opt_info(3) that trigger an automatic meta context request. Remember that it is not safe to call any "nbd_*" APIs from within the context of the callback function. At present, the return value of the callback is ignored, although a return of -1 should be avoided. 🫶
For convenience, when this function succeeds, it returns the number of replies returned by the server. 🔥
Not all servers understand this request, and even when it is understood, the server might intentionally send an empty list because it doesn't support the requested context, or may encounter a failure after delivering partial results. Thus, this function may succeed even when no contexts are reported, or may fail but have a non-empty list. 💀
🍓 add_meta_context — ask server to negotiate metadata context
val add_meta_context : t -> string -> unit
NBD.add_meta_context t name
okay so during connection libnbd can negotiate zero or more metadata contexts with the server. Metadata contexts are literally features (like base:allocation) that describe the info returned by the nbd_block_status_64(3) command — for base:allocation that's whether blocks of data are allocated, zero or sparse. ✨
This call adds one metadata context to the list to be negotiated. You can call it as many times as you need. The list starts empty when the handle is created; you can check what's in it with nbd_get_nr_meta_contexts(3) and nbd_get_meta_context(3), or clear it with nbd_clear_meta_contexts(3). 🧹
The NBD protocol caps meta context names at 4096 bytes, but servers may not support the full length. The encoding of meta context names is always UTF-8. 🔤
Not all servers support all metadata contexts. To find out if a context actually got negotiated, call nbd_can_meta_context(3) after connecting. 👀
The single parameter is the name of the metadata context, for example LIBNBD_CONTEXT_BASE_ALLOCATION. <libnbd.h> includes defined constants beginning with LIBNBD_CONTEXT_ for some well-known contexts, but you are free to pass in other contexts. 🫶
Other metadata contexts are server-specific, but include qemu:dirty-bitmap:... and qemu:allocation-depth for qemu-nbd (see qemu-nbd -B and -A options).
get_nr_meta_contexts 🎀
val get_nr_meta_contexts : t -> int
NBD.get_nr_meta_contexts t
return the current number of requested meta contexts — tiny recap: during connection libnbd can negotiate zero or more metadata contexts with the server; they're features like base:allocation that describe what nbd_block_status_64(3) returns (for base:allocation, whether blocks are allocated, zero or sparse).
This command returns how many meta contexts have been added to the list to request from the server via nbd_add_meta_context(3). The server is not obligated to honor all of the requests; to see what it actually supports, see nbd_can_meta_context(3). 💅
get_meta_context 🦋
val get_meta_context : t -> int -> string
NBD.get_meta_context t i
return the i'th meta context request — tiny recap: during connection libnbd can negotiate zero or more metadata contexts with the server; they're features like base:allocation that describe what nbd_block_status_64(3) returns (for base:allocation, whether blocks are allocated, zero or sparse).
This command returns the i'th meta context request, as added by nbd_add_meta_context(3), and bounded by nbd_get_nr_meta_contexts(3). 🫶
clear_meta_contexts 🧚
val clear_meta_contexts : t -> unit
NBD.clear_meta_contexts t
reset the list of requested meta contexts — tiny recap: during connection libnbd can negotiate zero or more metadata contexts with the server; they're features like base:allocation that describe what nbd_block_status_64(3) returns (for base:allocation, whether blocks are allocated, zero or sparse).
This command resets the list of meta contexts to request back to an empty list, for re-population by further use of nbd_add_meta_context(3). It is primarily useful when option negotiation mode is selected (see nbd_set_opt_mode(3)), for altering the list of attempted contexts between subsequent export queries. 🌈
set_uri_allow_transports 🚦
val set_uri_allow_transports : t -> ALLOW_TRANSPORT.t list -> unit
NBD.set_uri_allow_transports t mask
set the allowed transports in NBD URIs — Set which transports are allowed to appear in NBD URIs. The default is to allow any transport. 🛸
The mask parameter may contain any of the following flags ORed together:
LIBNBD_ALLOW_TRANSPORT_TCP=0x1LIBNBD_ALLOW_TRANSPORT_UNIX=0x2LIBNBD_ALLOW_TRANSPORT_VSOCK=0x4LIBNBD_ALLOW_TRANSPORT_SSH=0x8
For convenience, the constant LIBNBD_ALLOW_TRANSPORT_MASK is available to describe all transports recognized by this build of libnbd. A future version of the library may add new flags. 🔮
set_uri_allow_tls 🔐
val set_uri_allow_tls : t -> TLS.t -> unit
NBD.set_uri_allow_tls t tls
set the allowed TLS settings in NBD URIs — Set which TLS settings are allowed to appear in NBD URIs. The default is to allow either non-TLS or TLS URIs. 🎀
The tls parameter can be:
LIBNBD_TLS_DISABLE— TLS URIs are not permitted, ie. a URI such asnbds://...will be rejected. 🚫LIBNBD_TLS_ALLOW— This is the default. TLS may be used or not, depending on whether the URI usesnbdsornbd. 👍LIBNBD_TLS_REQUIRE— TLS URIs are required. All URIs must usenbds. 🔒
set_uri_allow_tls_priority 💅
val set_uri_allow_tls_priority : t -> bool -> unit
NBD.set_uri_allow_tls_priority t allow
set if 'tls-priority' is allowed in NBD URIs — Allow NBD URIs to override the TLS priority using ?tls-priority=PRIORITY in the query string.
⚠️ This is disabled by default because it is probably not safe. The TLS priority string is quite powerful, allowing you to read some local configuration files, and possibly to downgrade TLS security.
set_uri_allow_local_file 📁
val set_uri_allow_local_file : t -> bool -> unit
NBD.set_uri_allow_local_file t allow
set the allowed transports in NBD URIs — Allow NBD URIs to reference local files. This is disabled by default. 🙅♀️
Currently this setting only controls whether the tls-psk-file parameter in NBD URIs is allowed.
connect_uri 🌐
val connect_uri : t -> string -> unit
NBD.connect_uri t uri
connect to NBD URI — Connect (synchronously) to an NBD server and export by specifying the NBD URI. NBD URIs are a standard way to specify a network block device endpoint, using a syntax like nbd://example.com which is convenient, well defined and future proof. 🚀
This call works by parsing the URI parameter and calling nbd_set_export_name(3) and nbd_set_tls(3) and other calls as needed, followed by nbd_connect_tcp(3), nbd_connect_unix(3) or nbd_connect_vsock(3).
Example URIs supported:
nbd://example.com— Connect over TCP, unencrypted, toexample.comport10809.nbds://example.com— Connect over TCP with encryption (TLS), toexample.comport10809. If the server does not support TLS then this will fail.nbd+unix:///foo?socket=/tmp/nbd.sock— Connect over the Unix domain socket/tmp/nbd.sockto an NBD server running locally. The export name is set tofoo(note without any leading/character).nbds+unix://alice@/?socket=/tmp/nbd.sock&tls-certificates=certs— Connect over a Unix domain socket, enabling TLS, setting the TLS username toalice, and setting the path to a directory containing certificates and keys.nbd+vsock:///— In this scenario libnbd is running in a virtual machine. Connect overAF_VSOCKto an NBD server running on the hypervisor.nbd+ssh://server/— Connect to remoteserverusing Secure Shell, and tunnel NBD to an NBD server listening on port10809.
The NBD URI standard https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md documents the NBD URI standard. 📚
In the documentation below, Non-standard indicates features supported in libnbd which are not a part of the NBD URI standard, meaning that other NBD URI parsers might not support them or might implement things differently.
✨ URI scheme
The scheme is the part before the first :. The following schemes are supported in the current version of libnbd:
nbd:— Connect over TCP without using TLS.nbds:— Connect over TCP. TLS is required and the connection will fail if the server does not support TLS.nbd+unix:/nbds+unix:— Connect over a Unix domain socket, without or with TLS respectively. Thesocketparameter is required.nbd+vsock:/nbds+vsock:— Non-standard Connect over theAF_VSOCKtransport, without or with TLS respectively. You can usenbd_supports_vsock(3)to see if this build of libnbd supportsAF_VSOCK.nbd+ssh:/nbds+ssh:— Non-standard, libnbd ≥ 1.22 Tunnel NBD over a Secure Shell connection. This requires thatssh(1)is installed locally, and thatnc(1)(from the nmap project) is installed on the remote server.
🎀 URI authority
The authority part of the URI username@servername:port is parsed depending on the transport and TLS:
nbd:— username:N.A.; servername: optional TCP server; port: optional TCP port; note: (TCP transport).nbds:— username: optional TLS username; servername: optional TCP server; port: optional TCP port; note: (TLS over TCP); default:localhost:10809.nbd+unix:— username:N.A.; servername:N.A.; port:N.A.; note: (Unix transport).nbds+unix:— username: optional TLS username; servername:N.A.; port:N.A.; note: (TLS over Unix).nbd+vsock:— username:N.A.; servername: optional numeric CID*; port: optional vsock port; note: (vsock transport); default:2:10809.nbds+vsock:— username: optional TLS username; servername: optional numeric CID*; port: optional vsock port; note: (TLS over vsock); default:2:10809.nbd+ssh:— username: optional SSH username; servername: SSH server; port: optional SSH port; note: (SSH tunnel); default:22.nbds+ssh:— username: optional SSH username**; servername: SSH server; port: optional SSH port; note: (TLS over SSH); default:22.
N.A. = not allowed
* = CID is usually either 2 meaning connect to the host, or the CID of another virtual machine.
** = For nbds+ssh, use the ?tls-username query parameter to set the TLS username.
🦋 URI export name
For all transports, an export name may be present, parsed in accordance with the NBD URI specification. Note that the initial / character is not part of the export name:
nbd://localhost/→ export name""(empty string)nbd://localhost/export→ export name"export"
It is possible to override the export name programmatically by using nbd_set_opt_mode(3) to enable option mode, then using nbd_set_export_name(3) and nbd_opt_go(3) as part of subsequent negotiation.
🌸 URI query
Finally the query part of the URI can contain:
compress=true— Non-standard, libnbd ≥ 1.24 For SSH transport only, enable compression. The default isfalse.nbd-port="PORT"— Non-standard, libnbd ≥ 1.24 Override the port number from the authority part of the URI. For SSH transport, this specifies the port used to connect to the NBD server, but the port in the authority field is used for the SSH connection.socket=SOCKET— Specifies the Unix domain socket to connect on. Must be present for the+unixtransport, optional for+ssh, and must not be present for the other transports.tls-certificates=DIR— Non-standard, libnbd ≥ 1.10 Set the certificates directory. Seenbd_set_tls_certificates(3). Note this is not allowed by default - see next section.tls-hostname="SERVER"— libnbd ≥ 1.22 Set the TLS hostname. Seenbd_set_tls_hostname(3).tls-priority="PRIORITY"— Non-standard, libnbd ≥ 1.24 Override the TLS priority string. Seenbd_set_tls_priority(3). This is not allowed by default - see next section.tls-psk-file=PSKFILE— Non-standard Set the PSK file. Seenbd_set_tls_psk_file(3). Note this is not allowed by default - see next section.tls-username="USER"Non-standard, libnbd ≥ 1.24 — overrides the username from the authority part of the URI. For SSH transport, this specifies the TLS username for connecting to the NBD server, but the user in the authority field is used for the SSH connection.tls-verify-peer=false— do not verify the server certificate. Seenbd_set_tls_verify_peer(3). The default is"true".
🚫 Disabling URI features
okay so for security reasons you might want to disable certain URI features. Pre-filtering URIs is error-prone and should not be attempted. Instead use the libnbd APIs below to control what can appear in URIs. Note you must call these functions on the same handle before calling nbd_connect_uri(3) or nbd_aio_connect_uri(3).
- TCP, Unix domain socket,
AF_VSOCKor SSH transports — default: all allowed. To select which transports are allowed callnbd_set_uri_allow_transports(3). - TLS — default: both non-TLS and TLS connections allowed. To force TLS off or on in URIs call
nbd_set_uri_allow_tls(3). - TLS priority — default: denied. To allow the TLS priority to be set in URIs call
nbd_set_uri_allow_tls_priority(3). - Connect to Unix domain socket in the local filesystem — default: allowed. To prevent this you must disable the
+unixtransport usingnbd_set_uri_allow_transports(3). - Read from local files — default: denied. To allow URIs to contain references to local files (eg. for parameters like
tls-psk-file) callnbd_set_uri_allow_local_file(3).
✨ Optional features
This call will fail if libnbd was not compiled with libxml2; you can test whether this is the case with nbd_supports_uri(3).
Support for URIs that require TLS will fail if libnbd was not compiled with gnutls; you can test whether this is the case with nbd_supports_tls(3).
🔗 Constructing a URI from an existing connection
See nbd_get_uri(3).
🔎 See if a string is an NBD URI
See nbd_is_uri(3).
🧬 Differences from qemu and glib parsing of NBD URIs
qemu(1) also supports NBD URIs and has a separate URI parser. In qemu ≤ 9.0 this was done using their own parser. In qemu ≥ 9.1 this is done using glib g_uri functions. The current (glib-based) parser does not parse the export name part of the URI in exactly the same way as libnbd, which may cause URIs to work in libnbd but not in qemu or vice versa. Only URIs using exportnames should be affected. For details see https://gitlab.com/qemu-project/qemu/-/issues/2584.
🧮 Limitations on vsock port numbers
The vsock(7) protocol allows 32 bit unsigned ports, reserving ports 0, 1 and 2 for special purposes. In Linux, ports < 1024 are reserved for privileged processes.
libxml2 (used to parse the URI) imposes additional restrictions. libxml2 < 2.9 limited port numbers to 99,999,999. libxml2 ≥ 2.9 limits port numbers to ≤ 0x7fff_ffff (31 bits).
libnbd ≥ 1.24 allows you to override the port field by adding ?nbd-port=PORT to the URI, allowing you to use any vsock port number.
val connect_unix : t -> string -> unit
NBD.connect_unix t unixsocket
connect to NBD server over a Unix domain socket — connect (synchronously) over the named Unix domain socket (unixsocket) to an NBD server running on the same machine. 🫶
val connect_vsock : t -> int64 -> int64 -> unit
NBD.connect_vsock t cid port
connect to NBD server over AF_VSOCK protocol — connect (synchronously) over the AF_VSOCK protocol from a virtual machine to an NBD server, usually running on the host. The cid and port parameters specify the server address. Usually cid should be 2 (to connect to the host), and port might be 10809 or another port number assigned to you by the host administrator.
Not all systems support AF_VSOCK; to determine if libnbd was built on a system with vsock support, see nbd_supports_vsock(3).
val connect_tcp : t -> string -> string -> unit
NBD.connect_tcp t hostname port
connect to NBD server over a TCP port — connect (synchronously) to the NBD server listening on hostname:port. The port may be a port name such as nbd, or it may be a port number as a string such as 10809.
val connect_socket : t -> Unix.file_descr -> unit
NBD.connect_socket t sock
connect directly to a connected socket — pass a connected socket sock through which libnbd will talk to the NBD server.
The caller is responsible for creating and connecting this socket by some method, before passing it to libnbd.
⚠️ If this call returns without error then socket ownership is passed to libnbd. Libnbd will close the socket when the handle is closed. The caller must not use the socket in any way. No cap, do not touch it after that. 💀
val connect_command : t -> string list -> unit
NBD.connect_command t argv
connect to NBD server command — run the command as a subprocess and connect to it over stdin/stdout. This is for use with NBD servers which can behave like inetd clients, such as nbdkit(1) using the -s/--single flag, and nbd-server(1) with port number set to 0.
To run qemu-nbd(1), use nbd_connect_systemd_socket_activation(3) instead.
Subprocess — libnbd will fork the argv command and pass the NBD socket to it using file descriptors 0 and 1 (stdin/stdout):
┌─────────┬─────────┐ ┌────────────────┐
│ program │ libnbd │ │ NBD server │
│ │ (argv) │ │ │
│ │ socket ╍╍╍╍╍╍╍╍▶ stdin/stdout │
└─────────┴─────────┘ └────────────────┘
When the NBD handle is closed the server subprocess is killed.
val connect_systemd_socket_activation : t -> string list -> unit
NBD.connect_systemd_socket_activation t argv
connect using systemd socket activation — run the command as a subprocess and connect to it using systemd socket activation.
This is especially useful for running qemu-nbd(1) as a subprocess of libnbd, for example to use it to open qcow2 files.
To run nbdkit as a subprocess, this function can be used, or nbd_connect_command(3).
To run nbd-server(1) as a subprocess, this function cannot be used, you must use nbd_connect_command(3).
Socket activation — libnbd will fork the argv command and pass an NBD socket to it using special LISTEN_* environment variables (as defined by the systemd socket activation protocol).
┌─────────┬─────────┐ ┌───────────────┐
│ program │ libnbd │ │ qemu-nbd or │
│ │ │ │ other server │
│ │ socket ╍╍╍╍╍╍╍╍▶ │
└─────────┴─────────┘ └───────────────┘
When the NBD handle is closed the server subprocess is killed.
Socket name — the socket activation protocol lets you optionally give the socket a name. If used, the name is passed to the NBD server using the LISTEN_FDNAMES environment variable. To provide a socket name, call nbd_set_socket_activation_name(3) before calling the connect function.
val set_socket_activation_name : t -> string -> unit
NBD.set_socket_activation_name t socket_name
set the socket activation name — when running an NBD server using nbd_connect_systemd_socket_activation(3) you can optionally name the socket. Call this function before connecting to the server.
Some servers such as qemu-storage-daemon(1) can use this information to associate the socket with a name used on the command line, but most servers will ignore it. The name is passed through the LISTEN_FDNAMES environment variable.
The parameter socket_name can be a short alphanumeric string. If it is set to the empty string (also the default when the handle is created) then the name "unknown" will be seen by the server.
val get_socket_activation_name : t -> string
NBD.get_socket_activation_name t
get the socket activation name — return the socket name used when you call nbd_connect_systemd_socket_activation(3) on the same handle. By default this will return the empty string meaning that the server will see the name "unknown".
val is_read_only : t -> bool
NBD.is_read_only t
is the NBD export read-only? — returns true if the NBD export is read-only; writes and write-like operations will fail. Read the room, bestie: no writing. 🎀
val can_flush : t -> bool
NBD.can_flush t
does the server support the flush command? — returns true if the server supports the flush command (see nbd_flush(3), nbd_aio_flush(3)). Returns false if the server does not.
val can_fua : t -> bool
NBD.can_fua t
does the server support the FUA flag? — returns true if the server supports the FUA flag on certain commands (see nbd_pwrite(3)).
val is_rotational : t -> bool
NBD.is_rotational t
is the NBD disk rotational (like a disk)? — returns true if the disk exposed over NBD is rotational (like a traditional floppy or hard disk). Returns false if the disk has no penalty for random access (like an SSD or RAM disk).
val can_trim : t -> bool
NBD.can_trim t
does the server support the trim command? — returns true if the server supports the trim command (see nbd_trim(3), nbd_aio_trim(3)). Returns false if the server does not.
val can_zero : t -> bool
NBD.can_zero t
does the server support the zero command? — returns true if the server supports the zero command (see nbd_zero(3), nbd_aio_zero(3)). Returns false if the server does not.
val can_fast_zero : t -> bool
NBD.can_fast_zero t
does the server support the fast zero flag? — returns true if the server supports the use of the LIBNBD_CMD_FLAG_FAST_ZERO flag to the zero command (see nbd_zero(3), nbd_aio_zero(3)). Returns false if the server does not.
Note that using the fast zero flag does not speed up a successful write zeroes request. Rather, it exists to speed up the failure case: a server that lacks fast zeroing but supports this flag will fail fast without modifying the disk, as a way to let you decide whether retrying with known-slow zeroing is still worth the effort.
val can_block_status_payload : t -> bool
NBD.can_block_status_payload t
does the server support the block status payload flag? — returns true if the server supports the use of the LIBNBD_CMD_FLAG_PAYLOAD_LEN flag to allow filtering of the block status command (see nbd_block_status_filter(3)). Returns false if the server does not. Note that this will never return true if nbd_get_extended_headers_negotiated(3) is false.
val can_df : t -> bool
NBD.can_df t
does the server support the don't fragment flag to pread?
Returns true if the server supports structured reads and can do a non-fragmented read request (peep nbd_pread_structured(3) and nbd_aio_pread_structured(3)). Returns false if the server either doesn't have structured reads at all or just won't support a non-fragmented read request. 💅
val can_multi_conn : t -> bool
NBD.can_multi_conn t
does the server support multi-conn? okay bestie, this is the vibe check for multiple connections. Returns true if the server supports multi-conn. Returns false if the server does not.
It is not safe to open multiple handles connecting to the same server if you will write to the server and the server does not advertise multi-conn support. The safe way to check for this is to open one connection, check this flag is true, then open further connections as required. 🫶
val can_cache : t -> bool
NBD.can_cache t
does the server support the cache command? Returns true if the server supports the cache command (see nbd_cache(3), nbd_aio_cache(3)). Returns false if the server does not. No cap.
val can_meta_context : t -> string -> bool
NBD.can_meta_context t metacontext
does the server support a specific meta context? Returns true if the server supports the given meta context (see nbd_add_meta_context(3)). Returns false if the server does not. It is possible for this command to fail if meta contexts were requested but there is a missing or failed attempt at NBD_OPT_SET_META_CONTEXT during option negotiation. 😭
If the server supports block status filtering (check nbd_can_block_status_payload(3)), this function must return true for any filter name passed to nbd_block_status_filter(3).
The single parameter is the name of the metadata context, for example "LIBNBD_CONTEXT_BASE_ALLOCATION". <libnbd.h> includes defined constants for well-known namespace contexts beginning with "LIBNBD_CONTEXT_", but you are free to pass in other contexts.
val get_protocol : t -> string
NBD.get_protocol t
return the NBD protocol variant — it's giving protocol lore. Return the NBD protocol variant in use on the connection. At the moment this returns one of the strings "oldstyle", "newstyle" or "newstyle-fixed". Other strings might be returned in the future. Most modern NBD servers use "newstyle-fixed".
val get_size : t -> int64
NBD.get_size t
return the export size — Returns the size in bytes of the NBD export. 💖
Note that this call fails with "EOVERFLOW" for an unlikely server that advertises a size which cannot fit in a 64-bit signed integer.
The --size option of nbdinfo(1) is a way to access this API from shell scripts.
val get_block_size : t -> SIZE.t -> int64
NBD.get_block_size t size_type
return a specific server block size constraint — Returns a specific block size constraint advertised by the server. If zero is returned it means the server did not advertise a constraint.
Constraints are hints. Servers differ in their behaviour as to whether they enforce constraints or not. iykyk.
The size_type parameter selects which constraint to read. It can be one of:
"LIBNBD_SIZE_MINIMUM"= 0 — If non-zero, this will be a power of 2 between 1 and 64k; any client request that is not aligned in length or offset to this size is likely to fail with"EINVAL". The image size will generally also be a multiple of this value (if not, the final few bytes are inaccessible while obeying alignment constraints). If zero (meaning no information was returned by the server), it is safest to assume a minimum block size of 512, although many servers support a minimum block size of 1. If the server provides a constraint, then libnbd defaults to honoring that constraint client-side unless"LIBNBD_STRICT_ALIGN"is cleared innbd_set_strict_mode(3)."LIBNBD_SIZE_PREFERRED"= 1 — If non-zero, this is a power of 2 representing the preferred size for efficient I/O. Smaller requests may incur overhead such as read-modify-write cycles that will not be present when using I/O that is a multiple of this value. This value may be larger than the size of the export. If zero (meaning no information was returned by the server), using 4k as a preferred block size tends to give decent performance."LIBNBD_SIZE_MAXIMUM"= 2 — If non-zero, this represents the maximum length that the server is willing to handle duringnbd_pread(3)ornbd_pwrite(3). Other functions likenbd_zero(3)may still be able to use larger sizes. Note that this function returns what the server advertised, but libnbd itself imposes a maximum of 64M. If zero (meaning no information was returned by the server), some NBD servers will abruptly disconnect if a transaction sends or receives more than 32M of data. 💀"LIBNBD_SIZE_PAYLOAD"= 3 — This value is not advertised by the server, but rather represents the maximum outgoing payload size for a given connection that libnbd will enforce unless"LIBNBD_STRICT_PAYLOAD"is cleared innbd_set_strict_mode(3). It is always non-zero: never smaller than 1M, never larger than 64M, and matches"LIBNBD_SIZE_MAXIMUM"when possible.
Future NBD extensions may result in additional size_type values. Note that by default, libnbd requests all available block sizes, but that a server may differ in what sizes it chooses to report if nbd_set_request_block_size(3) alters whether the client requests sizes.
val pread : ?flags:CMD_FLAG.t list -> t -> bytes -> int64 -> unit
NBD.pread t ?flags buf offset
read from the NBD server — Issue a read command to the NBD server for the range starting at offset and ending at offset + count - 1. NBD can only read all or nothing using this call. The call returns when the data has been read fully into buf or there is an error. See also nbd_pread_structured(3), if finer visibility is required into the server's replies, or if you want to use "LIBNBD_CMD_FLAG_DF". ✨
Note that libnbd currently enforces a maximum read buffer of 64MiB, even if the server would permit a larger buffer in a single transaction; attempts to exceed this will result in an "ERANGE" error. The server may enforce a smaller limit, which can be learned with nbd_get_block_size(3).
The flags parameter must be 0 for now (it exists for future NBD protocol extensions).
Note that if this command fails, andnbd_get_pread_initialize(3)returns true, then libnbd sanitizedbuf, but it is unspecified whether the contents ofbufwill read as zero or as partial results from the server. Ifnbd_get_pread_initialize(3)returns false, then libnbd did not sanitizebuf, and the contents are undefined on failure. 😬
val pread_structured : ?flags:CMD_FLAG.t list -> t -> bytes -> int64 -> (bytes -> int64 -> int -> int Stdlib.ref -> int) -> unit
NBD.pread_structured t ?flags buf offset chunk
read from the NBD server — Issue a read command to the NBD server for the range starting at offset and ending at offset + count - 1. The server's response may be subdivided into chunks which may arrive out of order before reassembly into the original buffer; the chunk callback is used for notification after each chunk arrives, and may perform additional sanity checking on the server's reply. The callback cannot call nbd_* APIs on the same handle since it holds the handle lock and will cause a deadlock. If the callback returns -1, and no earlier error has been detected, then the overall read command will fail with any non-zero value stored into the callback's error parameter (with a default of "EPROTO"); but any further chunks will still invoke the callback. 🦋
The chunk function is called once per chunk of data received, with the user_data passed to this function. The subbuf and count parameters represent the subset of the original buffer which has just been populated by results from the server (in C, subbuf always points within the original buf; but this guarantee may not extend to other language bindings). The offset parameter represents the absolute offset at which subbuf begins within the image (note that this is not the relative offset of subbuf within the original buffer buf). Changes to error on output are ignored unless the callback fails. The input meaning of the error parameter is controlled by the status parameter, which is one of
"LIBNBD_READ_DATA"= 1 —subbufwas populated withcountbytes of data. On input,errorcontains the errno value of any earlier detected error, or zero."LIBNBD_READ_HOLE"= 2 —subbufrepresents a hole, and containscountNUL bytes. On input,errorcontains the errno value of any earlier detected error, or zero."LIBNBD_READ_ERROR"= 3 —countis 0, sosubbufis unusable. On input,errorcontains the errno value reported by the server as occurring while reading thatoffset, regardless if any earlier error has been detected.
Future NBD extensions may permit other values for status, but those will not be returned to a client that has not opted in to requesting such extensions. If the server is non-compliant, it is possible for the chunk function to be called more times than you expect or with count 0 for "LIBNBD_READ_DATA" or "LIBNBD_READ_HOLE". It is also possible that the chunk function is not called at all (in particular, "LIBNBD_READ_ERROR" is used only when an error is associated with a particular offset, and not when the server reports a generic error), but you are guaranteed that the callback was called at least once if the overall read succeeds. Libnbd does not validate that the server obeyed the requirement that a read call must not have overlapping chunks and must not succeed without enough chunks to cover the entire request. periodt.
Note that libnbd currently enforces a maximum read buffer of 64MiB, even if the server would permit a larger buffer in a single transaction; attempts to exceed this will result in an "ERANGE" error. The server may enforce a smaller limit, which can be learned with nbd_get_block_size(3).
The flags parameter may be 0 for no flags, or may contain "LIBNBD_CMD_FLAG_DF" meaning that the server should not reply with more than one fragment (if that is supported - some servers cannot do this, see nbd_can_df(3)). Libnbd does not validate that the server actually obeys the flag.
Note that if this command fails, andnbd_get_pread_initialize(3)returns true, then libnbd sanitizedbuf, but it is unspecified whether the contents ofbufwill read as zero or as partial results from the server. Ifnbd_get_pread_initialize(3)returns false, then libnbd did not sanitizebuf, and the contents are undefined on failure. 🫠
val pwrite : ?flags:CMD_FLAG.t list -> t -> bytes -> int64 -> unit
NBD.pwrite t ?flags buf offset
write to the NBD server 💅
bestie, this one's a write, and NBD does not do halfway. Issue a write command to the NBD server, writing the data in buf to the range starting at offset and ending at offset + count - 1. NBD can only write all or nothing using this call — no little partial slay, sorry 💅. The call returns when the command has been acknowledged by the server, or there is an error. And heads up: this will generally return an error if nbd_is_read_only(3) is true, so if you're getting yelled at, check that first 🫶.
Also, libnbd defaults to enforcing a maximum write buffer of the lesser of 64MiB or any maximum payload size advertised by the server; attempts to exceed this will generally result in a client-side ERANGE error, rather than a server-side disconnection (lowkey a mercy, ngl ✨). The actual limit can be learned with nbd_get_block_size(3).
The flags parameter may be 0 for no flags, or may contain LIBNBD_CMD_FLAG_FUA meaning that the server should not return until the data has been committed to permanent storage (if that is supported — some servers cannot do this, see nbd_can_fua(3)). For convenience, unless nbd_set_strict_flags(3) was used to disable LIBNBD_STRICT_AUTO_FLAG, libnbd ignores the presence or absence of the flag LIBNBD_CMD_FLAG_PAYLOAD_LEN in flags, while correctly using the flag over the wire according to whether extended headers were negotiated. She's doing the bookkeeping for you, okurrr 🎀.
🫶 disconnect from the NBD server
val shutdown : ?flags:SHUTDOWN.t list -> t -> unit
NBD.shutdown t ?flags
Issue the disconnect command to the NBD server. This is a nice way to tell the server we are going away, but from the client's point of view has no advantage over abruptly closing the connection (see nbd_close(3)). Like, it's the polite little wave 👋 — cute, not mandatory.
This function works whether or not the handle is ready for transmission of commands. If more fine-grained control is needed, see nbd_aio_opt_abort(3) and nbd_aio_disconnect(3).
The flags argument is a bitmask, including zero or more of the following shutdown flags:
LIBNBD_SHUTDOWN_ABANDON_PENDING=0x10000— if there are any pending requests which have not yet been sent to the server (seenbd_aio_in_flight(3)), abandon them without sending them to the server, rather than the usual practice of issuing those commands before informing the server of the intent to disconnect. It's giving "we're leaving, drop the bags" 💀.
For convenience, the constant LIBNBD_SHUTDOWN_MASK is available to describe all shutdown flags recognized by this build of libnbd. A future version of the library may add new flags — so don't hardcode your whole personality around it, mwah 💜.
🔥 send flush command to the NBD server
val flush : ?flags:CMD_FLAG.t list -> t -> unit
NBD.flush t ?flags
Issue the flush command to the NBD server. The function should return when all write commands which have completed have been committed to permanent storage on the server. Think of it as the "make it actually real" button ✨. Note this will generally return an error if nbd_can_flush(3) is false.
The flags parameter must be 0 for now (it exists for future NBD protocol extensions). Yes bestie, it's just sitting there being zero. Iconic of it.
🌸 send trim command to the NBD server
val trim : ?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> unit
NBD.trim t ?flags count offset
Issue a trim command to the NBD server, which if supported by the server causes a hole to be punched in the backing store starting at offset and ending at offset + count - 1. The call returns when the command has been acknowledged by the server, or there is an error. Note this will generally return an error if nbd_can_trim(3) is false or nbd_is_read_only(3) is true. Punching holes 🕳️✨ — she's destructive in a supported way only.
Note that not all servers can support a count of 4GiB or larger; nbd_get_extended_headers_negotiated(3) indicates which servers will parse a request larger than 32 bits. The NBD protocol does not yet have a way for a client to learn if the server will enforce an even smaller maximum trim size, although a future extension may add a constraint visible in nbd_get_block_size(3). So like… you might just have to find out 😭.
The flags parameter may be 0 for no flags, or may contain LIBNBD_CMD_FLAG_FUA meaning that the server should not return until the data has been committed to permanent storage (if that is supported — some servers cannot do this, see nbd_can_fua(3)).
🦋 send cache (prefetch) command to the NBD server
val cache : ?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> unit
NBD.cache t ?flags count offset
Issue the cache (prefetch) command to the NBD server, which if supported by the server causes data to be prefetched into faster storage by the server, speeding up a subsequent nbd_pread(3) call. The server can also silently ignore this command — she's allowed to ghost you 🙈. Note this will generally return an error if nbd_can_cache(3) is false.
Note that not all servers can support a count of 4GiB or larger; nbd_get_extended_headers_negotiated(3) indicates which servers will parse a request larger than 32 bits. The NBD protocol does not yet have a way for a client to learn if the server will enforce an even smaller maximum cache size, although a future extension may add a constraint visible in nbd_get_block_size(3).
The flags parameter must be 0 for now (it exists for future NBD protocol extensions). Same zero situation as flush, we love consistency 💅.
👑 send write zeroes command to the NBD server
val zero : ?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> unit
NBD.zero t ?flags count offset
Issue a write zeroes command to the NBD server, which if supported by the server causes a zeroes to be written efficiently starting at offset and ending at offset + count - 1. The call returns when the command has been acknowledged by the server, or there is an error. Note this will generally return an error if nbd_can_zero(3) is false or nbd_is_read_only(3) is true. Zeroes, but make it efficient 🧚.
Note that not all servers can support a count of 4GiB or larger; nbd_get_extended_headers_negotiated(3) indicates which servers will parse a request larger than 32 bits. The NBD protocol does not yet have a way for a client to learn if the server will enforce an even smaller maximum zero size, although a future extension may add a constraint visible in nbd_get_block_size(3). Also, some servers may permit a larger zero request only when the LIBNBD_CMD_FLAG_FAST_ZERO is in use. Sneaky little conditional, but okay 🫶.
The flags parameter may be 0 for no flags, or may contain:
LIBNBD_CMD_FLAG_FUA— meaning that the server should not return until the data has been committed to permanent storage (if that is supported — some servers cannot do this, seenbd_can_fua(3))LIBNBD_CMD_FLAG_NO_HOLE— meaning that the server should favor writing actual allocated zeroes over punching a holeLIBNBD_CMD_FLAG_FAST_ZERO— meaning that the server must fail quickly if writing zeroes is no faster than a normal write (if that is supported — some servers cannot do this, seenbd_can_fast_zero(3))
⭐ send block status command, with 32-bit callback
val block_status : ?flags:CMD_FLAG.t list -> t -> int64 -> int64 ->
(string -> int64 -> int64 array -> int Stdlib.ref -> int) -> unit
NBD.block_status t ?flags count offset extent
Issue the block status command to the NBD server. If supported by the server, this causes metadata context information about blocks beginning from the specified offset to be returned. The count parameter is a hint: the server may choose to return less status, or the final block may extend beyond the requested range. If multiple contexts are supported, the number of blocks and cumulative length of those blocks need not be identical between contexts. It's giving "we'll see what we feel like" energy 😭.
Note that not all servers can support a count of 4GiB or larger; nbd_get_extended_headers_negotiated(3) indicates which servers will parse a request larger than 32 bits. The NBD protocol does not yet have a way for a client to learn if the server will enforce an even smaller maximum block status size, although a future extension may add a constraint visible in nbd_get_block_size(3).
Okay so here's the part where the 32-bit situation gets real: this function is inherently limited to 32-bit values. If the server replies with a larger extent, the length of that extent will be truncated to just below 32 bits and any further extents from the server will be ignored. If the server replies with a status value larger than 32 bits (only possible when extended headers are in use), the callback function will be passed anEOVERFLOWerror. To get the full extent information from a server that supports 64-bit extents, you must usenbd_block_status_64(3). No cap, that's the whole tea ☕
Depending on which metadata contexts were enabled before connecting (see nbd_add_meta_context(3)) and which are supported by the server (see nbd_can_meta_context(3)) this call returns information about extents by calling back to the extent function. The callback cannot call nbd_* APIs on the same handle since it holds the handle lock and will cause a deadlock. 💀 Do not do that. If the callback returns -1, and no earlier error has been detected, then the overall block status command will fail with any non-zero value stored into the callback's error parameter (with a default of EPROTO); but any further contexts will still invoke the callback.
The extent function is called once per type of metadata available, with the user_data passed to this function. The metacontext parameter is a string such as base:allocation. The entries array is an array of pairs of integers with the first entry in each pair being the length (in bytes) of the block and the second entry being a status/flags field which is specific to the metadata context. The number of pairs passed to the function is nr_entries/2. The NBD protocol document in the section about NBD_REPLY_TYPE_BLOCK_STATUS describes the meaning of this array; for contexts known to libnbd, <libnbd.h> contains constants beginning with LIBNBD_STATE_ that may help decipher the values. On entry to the callback, the error parameter contains the errno value of any previously detected error, but even if an earlier error was detected, the current metacontext and entries are valid.
It is possible for the extent function to be called more times than you expect (if the server is buggy), so always check the metacontext field to ensure you are receiving the data you expect. It is also possible that the extent function is not called at all, even for metadata contexts that you requested. This indicates either that the server doesn't support the context or for some other reason cannot return the data. Vibe check: inconclusive 🙈.
The flags parameter may be 0 for no flags, or may contain LIBNBD_CMD_FLAG_REQ_ONE meaning that the server should return only one extent per metadata context where that extent does not exceed count bytes; however, libnbd does not validate that the server obeyed the flag. She's trusting, not checking 🎀.
🌈 send block status command, with 64-bit callback
val block_status_64 : ?flags:CMD_FLAG.t list -> t -> int64 -> int64 ->
(string -> int64 -> extent array -> int Stdlib.ref -> int) -> unit
NBD.block_status_64 t ?flags count offset extent64
bestie, this part is literally all about the ✨async✨ era of your life, and the block_status lore goes hard — stay with me 💅
Server, bestie, can you tell me what's up with my blocks? If the server's got the range, this call sends back metadata context info about blocks starting at the offset you gave. And the count param? It's a hint, not a promise — the server may hand back less status than you asked for, or the final block may just casually extend past the range you requested. Rude but legal 🫠
When multiple contexts are in the mix, the number of blocks and the cumulative length of those blocks don't have to match between contexts, iykyk — no cap, they can just be different. This command generally returns the status of all negotiated contexts, while some servers also support a filtered request (peep nbd_can_block_status_payload(3), nbd_block_status_filter(3)) ✨
heads up 💀 not every server can handle acountof 4GiB or larger.nbd_get_extended_headers_negotiated(3)tells you which servers will actually parse a request bigger than 32 bits. And the NBD protocol does not yet have a way for a client to learn if the server will enforce an even smaller maximum block status size — although a future extension may add a constraint visible innbd_get_block_size(3). So like, stay humble.
Depending on which metadata contexts you enabled before connecting (see nbd_add_meta_context(3)) and which ones the server supports (see nbd_can_meta_context(3)), this call returns info about extents by calling back to the extent64 function. And bestie — the callback cannot call nbd_* APIs on the same handle, because it's holding the handle lock and that will cause a deadlock. Genuinely do not do it 🎀
If the callback returns -1, and no earlier error has been detected, then the whole block status command fails with whatever non-zero value got stored into the callback's error parameter (default is EPROTO). But plot twist: any further contexts will still invoke the callback 😭
So the extent64 function gets called once per type of metadata available, with the user_data you passed in. The metacontext parameter is a string like "base:allocation". The entries array is an array of nbd_extent structs containing the length (in bytes) of the block and a status/flags field that's specific to the metadata context. The number of array entries handed to the function is nr_entries. The NBD protocol document, in the section about NBD_REPLY_TYPE_BLOCK_STATUS, describes what this array means — and for contexts known to libnbd, <libnbd.h> has constants beginning with LIBNBD_STATE_ that may help you decipher the values 🔥 On entry to the callback, the error parameter contains the errno value of any previously detected error.
It is possible for the extent function to be called more times than you expect (if the server is buggy), so always check the metacontext field to make sure you're receiving the data you expect. It's also possible the extent function is not called at all, even for metadata contexts you requested — which means either the server doesn't support that context, or for some other reason it can't return the data 🙈
The flags parameter may be 0 for no flags, or may contain LIBNBD_CMD_FLAG_REQ_ONE, meaning the server should return only one extent per metadata context where that extent does not exceed count bytes. However — and this is the gag — libnbd does not validate that the server obeyed the flag 💀
val block_status_filter : ?flags:CMD_FLAG.t list -> t -> int64 -> int64
-> string list -> (string -> int64 -> extent array -> int Stdlib.ref ->
int) -> unit
NBD.block_status_filter t ?flags count offset contexts extent64
send filtered block status command, with 64-bit callback 🦋
Issue a filtered block status command to the NBD server. If the server supports it (see nbd_can_block_status_payload(3)), this causes metadata context information about blocks beginning from the specified offset to be returned, with the result limited to just the contexts you listed in filter. Small note though: every string in filter must be supported by nbd_can_meta_context(3) ✨
Every other parameter to this function has the same semantics as in nbd_block_status_64(3) — except that, for convenience, unless <nbd_set_strict_flags(3)> was used to disable LIBNBD_STRICT_AUTO_FLAG, libnbd ignores the presence or absence of the flag LIBNBD_CMD_FLAG_PAYLOAD_LEN in flags, while correctly using the flag over the wire. Cute, right? 🎀
val poll : t -> int -> int
NBD.poll t timeout
poll the handle once ⭐
This is a simple implementation of poll(2) which is used internally by synchronous API calls. On success, it returns 0 if the timeout (in milliseconds) occurs, or 1 if the poll completed and the state machine progressed. Set timeout to -1 to block indefinitely — but be careful that eventual action is actually expected. For example, if the connection is established but there are no commands in flight, using an infinite timeout will permanently block 😭
This function is mainly useful as an example of how you might integrate libnbd with your own main loop, rather than being intended as something you would use. It's giving teaching moment, not production moment 💅
val poll2 : t -> Unix.file_descr -> int -> int
NBD.poll2 t fd timeout
poll the handle once, with fd 🌸
This is the same as nbd_poll(3), but an additional file descriptor parameter is passed. The additional fd is also polled (using POLLIN). One use for this is to wait for an eventfd(2) ✨
val aio_connect : t -> Unix.sockaddr -> unit
NBD.aio_connect t addr
connect to the NBD server 👑
Begin connecting to the NBD server. The addr and addrlen parameters specify the address of the socket to connect to.
val aio_connect_uri : t -> string -> unit
NBD.aio_connect_uri t uri
connect to an NBD URI 🦋
Begin connecting to the NBD URI uri. Parameters behave as documented in nbd_connect_uri(3).
val aio_connect_unix : t -> string -> unit
NBD.aio_connect_unix t unixsocket
connect to the NBD server over a Unix domain socket 🍓
Begin connecting to the NBD server over Unix domain socket (unixsocket). Parameters behave as documented in nbd_connect_unix(3).
val aio_connect_vsock : t -> int64 -> int64 -> unit
NBD.aio_connect_vsock t cid port
connect to the NBD server over AF_VSOCK socket 🧚
Begin connecting to the NBD server over the AF_VSOCK protocol to the server cid:port. Parameters behave as documented in nbd_connect_vsock(3).
val aio_connect_tcp : t -> string -> string -> unit
NBD.aio_connect_tcp t hostname port
connect to the NBD server over a TCP port 🌈
Begin connecting to the NBD server listening on hostname:port. Parameters behave as documented in nbd_connect_tcp(3).
val aio_connect_socket : t -> Unix.file_descr -> unit
NBD.aio_connect_socket t sock
connect directly to a connected socket 💜
Begin connecting to the connected socket fd. Parameters behave as documented in nbd_connect_socket(3).
val aio_connect_command : t -> string list -> unit
NBD.aio_connect_command t argv
connect to the NBD server 🔥
Run the command as a subprocess and begin connecting to it over stdin/stdout. Parameters behave as documented in nbd_connect_command(3).
val aio_connect_systemd_socket_activation : t -> string list -> unit
NBD.aio_connect_systemd_socket_activation t argv
connect using systemd socket activation 🌟
Run the command as a subprocess and begin connecting to it using systemd socket activation. Parameters behave as documented in nbd_connect_systemd_socket_activation(3).
val aio_opt_go : ?completion:(int Stdlib.ref -> int) -> t -> unit
NBD.aio_opt_go t ?completion
end negotiation and move on to using an export 🎀
Request that the server finish negotiation and move on to serving the export previously specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). This can only be used if nbd_set_opt_mode(3) enabled option mode, okurrr 💅
To determine when the request completes, wait for nbd_aio_is_connecting(3) to return false. Or supply the optional completion_callback, which will be invoked as described in "Completion callbacks" in libnbd(3) — except that it is automatically retired regardless of return value. Note that directly detecting whether the server returns an error (as is done by the return value of the synchronous counterpart) is only possible with a completion callback; however, it's also possible to indirectly detect an error when nbd_aio_is_negotiating(3) returns true 🙈
val aio_opt_abort : t -> unit
NBD.aio_opt_abort t
end negotiation and close the connection 😭
Request that the server finish negotiation, gracefully if possible, then close the connection. This can only be used if nbd_set_opt_mode(3) enabled option mode.
To determine when the request completes, wait for nbd_aio_is_connecting(3) to return false.
val aio_opt_starttls : ?completion:(int Stdlib.ref -> int) -> t -> unit
NBD.aio_opt_starttls t ?completion
request the server to initiate TLS 🔐✨
Request that the server initiate a secure TLS connection, by sending NBD_OPT_STARTTLS. This behaves like the synchronous counterpart nbd_opt_starttls(3), except that it does not wait for the server's response.
To determine when the request completes, wait for nbd_aio_is_connecting(3) to return false. Or supply the optional completion_callback, which will be invoked as described in "Completion callbacks" in libnbd(3) — except that it is automatically retired regardless of return value. Note that detecting whether the server returns an error (as is done by the return value of the synchronous counterpart) is only possible with a completion callback.
val aio_opt_extended_headers : ?completion:(int Stdlib.ref -> int) -> t
-> unit
NBD.aio_opt_extended_headers t ?completion
request the server to enable extended headers 🦋💖
Request that the server use extended headers, by sending NBD_OPT_EXTENDED_HEADERS. This behaves like the synchronous counterpart nbd_opt_extended_headers(3), except that it does not wait for the server's response.
To determine when the request completes, wait for nbd_aio_is_connecting(3) to return false. Or supply the optional completion_callback, which will be invoked as described in "Completion callbacks" in libnbd(3) — except that it is automatically retired regardless of return value. Note that detecting whether the server returns an error (as is done by the return value of the synchronous counterpart) is only possible with a completion callback.
val aio_opt_structured_reply : ?completion:(int Stdlib.ref -> int) -> t
-> unit
NBD.aio_opt_structured_reply t ?completion
request the server to enable structured replies 🌸⭐
Request that the server use structured replies, by sending NBD_OPT_STRUCTURED_REPLY. This behaves like the synchronous counterpart nbd_opt_structured_reply(3), except that it does not wait for the server's response.
...to know when it's actually done-done, you just keep an eye on nbd_aio_is_connecting(3) until it comes back false. Or — the fancier route — hand over the optional "completion_callback" and it'll get called exactly like "Completion callbacks" in libnbd(3) describes, with one lil twist: it's automatically retired no matter what it returns. 🫶 And heads up, bestie: the only way to catch whether the server threw an error (which is literally what the return value does for the synchronous twin) is via a completion callback. No callback, no error intel. 💅
NBD.aio_opt_list
val aio_opt_list : ?completion:(int Stdlib.ref -> int) -> t -> (string
-> string -> int) -> unit
NBD.aio_opt_list t ?completion list
ask the server to spill the whole export list during negotiation ✨
So you're basically sliding into the server's DMs and going "name every export you got." This one only works if nbd_set_opt_mode(3) turned option mode on — otherwise, nothing.
Want to know when it's finished? Watch nbd_aio_is_connecting(3) flip to false. Or pass the optional "completion_callback", which fires per "Completion callbacks" in libnbd(3) — but plot twist, it's automatically retired regardless of return value. 🌸 Also, real talk: the only way to tell if the server errored out (which, again, is what the synchronous version's return value tells you) is with a completion callback.
NBD.aio_opt_info
val aio_opt_info : ?completion:(int Stdlib.ref -> int) -> t -> unit
NBD.aio_opt_info t ?completion
ask the server for the deets on an export 🎀
You're requesting info about the export name you set most recently — via nbd_set_export_name(3) or nbd_connect_uri(3), whichever came last. Same rule as always: only usable when nbd_set_opt_mode(3) enabled option mode. 💅
Completion check = wait for nbd_aio_is_connecting(3) to go false, or supply the optional "completion_callback", invoked as described in "Completion callbacks" in libnbd(3), except that it's automatically retired regardless of return value. And ngl, catching a server error (what the synchronous counterpart's return value does for you) is only possible with a completion callback. 😭
NBD.aio_opt_list_meta_context
val aio_opt_list_meta_context : ?completion:(int Stdlib.ref -> int) -> t
-> (string -> int) -> int
NBD.aio_opt_list_meta_context t ?completion context
list what meta contexts are available, using the implicit query 🦋
This asks the server for the meta contexts that go with the export you set most recently through nbd_set_export_name(3) or nbd_connect_uri(3) — and it uses the list of queries from your earlier nbd_add_meta_context(3) calls. If you'd rather hand over an explicit query list, that's what nbd_aio_opt_list_meta_context_queries(3) is for. As ever: requires nbd_set_opt_mode(3) to have enabled option mode.
Done yet? nbd_aio_is_connecting(3) returning false is your signal. Or hand in the optional "completion_callback", described under "Completion callbacks" in libnbd(3), except it's automatically retired regardless of return value. Detecting a server error (aka what the return value of the synchronous counterpart would tell you) is only possible via a completion callback — no cap. 💖
NBD.aio_opt_list_meta_context_queries
val aio_opt_list_meta_context_queries : ?completion:(int Stdlib.ref ->
int) -> t -> string list -> (string -> int) -> int
NBD.aio_opt_list_meta_context_queries t ?completion queries context
list available meta contexts, but with your own explicit query 🔥
Same meta-context catalog, same export (the one from the most recent nbd_set_export_name(3) or nbd_connect_uri(3)), except this time the queries are an explicit list you pass in as a parameter. Wanna reuse an implicit query list instead? Then go see nbd_aio_opt_list_meta_context(3). And yes — nbd_set_opt_mode(3) must have enabled option mode for any of this to fly.
When does it land? nbd_aio_is_connecting(3) going false, or the optional "completion_callback" doing its thing per "Completion callbacks" in libnbd(3) — automatically retired regardless of return value, iconic behaviour honestly. One more: whether the server returned an error (that's the return value's job in the synchronous version) is only detectable with a completion callback. 💅
NBD.aio_opt_set_meta_context
val aio_opt_set_meta_context : ?completion:(int Stdlib.ref -> int) -> t
-> (string -> int) -> int
NBD.aio_opt_set_meta_context t ?completion context
pick specific meta contexts, using the implicit query list 🍓
This one asks the server to give you back all the recognized meta contexts you registered earlier with nbd_add_meta_context(3), paired with the export specified by the most recent nbd_set_export_name(3) or nbd_connect_uri(3). Requires nbd_set_opt_mode(3) option mode — obviously.
Okay so lowkey, this function is usually redundant, because nbd_opt_go(3) already does the exact same task automatically if structured replies or extended headers have been negotiated. But if you want manual control over meta context requests — say, for fine-grained testing of how a server handles weird/unusual negotiation sequences — it eats. 🌈 Often you'd pair it with nbd_set_request_meta_context(3) to skip the automatic context request that nbd_opt_go(3) normally does.
Completion: nbd_aio_is_connecting(3) → false, or the optional "completion_callback" per "Completion callbacks" in libnbd(3), except automatically retired regardless of return value. And detecting a server error (like the synchronous counterpart's return value does) is only possible with a completion callback. 💀
NBD.aio_opt_set_meta_context_queries
val aio_opt_set_meta_context_queries : ?completion:(int Stdlib.ref ->
int) -> t -> string list -> (string -> int) -> int
NBD.aio_opt_set_meta_context_queries t ?completion queries context
pick specific meta contexts, with an explicit query list you provide 🧚
Exactly the vibe above, but the meta contexts you want recognized come in through "queries" instead of any implicit list — alongside the export from the most recent nbd_set_export_name(3) or nbd_connect_uri(3). Only usable when nbd_set_opt_mode(3) enabled option mode.
Standard disclaimer, because it's true: normally this is redundant, since nbd_opt_go(3) does the same task automatically when structured replies or extended headers are already negotiated. But manual control over meta context requests is genuinely useful for fine-grained testing of unusual negotiation sequences on a server. Frequently coupled with nbd_set_request_meta_context(3) so you can bypass the automatic context request that nbd_opt_go(3) normally performs. 👑
Knowing when it's done = nbd_aio_is_connecting(3) returns false, or the optional "completion_callback" fires as described in "Completion callbacks" in libnbd(3), but it's automatically retired regardless of return value. And the whole "did the server error" question (what the synchronous counterpart tells you via return value) — completion callback only, bestie. 💜
NBD.aio_pread
val aio_pread : ?completion:(int Stdlib.ref -> int) -> ?flags:CMD_FLAG.t
list -> t -> Buffer.t -> int64 -> cookie
NBD.aio_pread t ?completion ?flags buf offset
read from the NBD server ⭐
Drops a read command on the NBD server. Simple as.
To check if it's done, call nbd_aio_command_completed(3) — or slide in the optional "completion_callback", invoked per "Completion callbacks" in libnbd(3). 🎀
Okay this part matters, so listen up: you must keep"buf"valid until the command has completed. Periodt. Also, if the"error"parameter to"completion_callback"is set, or ifnbd_aio_command_completed(3)reports failure, andnbd_get_pread_initialize(3)returns true, then libnbd sanitized"buf"— but it is unspecified whether the contents of"buf"read as zero or as partial results from the server. 😭 And ifnbd_get_pread_initialize(3)returns false, then libnbd did not sanitize"buf", and the contents are undefined on failure. 💀
Other parameters behave as documented in nbd_pread(3). 🦋
NBD.aio_pread_structured
val aio_pread_structured : ?completion:(int Stdlib.ref -> int) ->
?flags:CMD_FLAG.t list -> t -> Buffer.t -> int64 -> (bytes -> int64 ->
int -> int Stdlib.ref -> int) -> cookie
NBD.aio_pread_structured t ?completion ?flags buf offset chunk
read from the NBD server, but structured 🌸
Issues a read command to the NBD server. Same energy, chunkier callback.
Check completion with nbd_aio_command_completed(3), or supply the optional "completion_callback" invoked as described in "Completion callbacks" in libnbd(3). 🫶
Your"buf"has to stay valid until the command completes — no exceptions, bestie. And: if the"error"parameter to"completion_callback"is set, or ifnbd_aio_command_completed(3)reports failure, andnbd_get_pread_initialize(3)returns true, libnbd sanitized"buf"— but it's unspecified whether"buf"'s contents read as zero or as partial results from the server. Ifnbd_get_pread_initialize(3)returns false, libnbd did not sanitize"buf", and the contents are undefined on failure. 🍓
Other parameters behave as documented in nbd_pread_structured(3). ✨
NBD.aio_pwrite
val aio_pwrite : ?completion:(int Stdlib.ref -> int) ->
?flags:CMD_FLAG.t list -> t -> Buffer.t -> int64 -> cookie
NBD.aio_pwrite t ?completion ?flags buf offset
write to the NBD server 🦋
Sends a write command to the NBD server. Go off. 🔥
nbd_aio_command_completed(3) tells you if it finished, or you can pass the optional "completion_callback", which gets invoked as described in "Completion callbacks" in libnbd(3).
And yeah — "buf" must remain valid until the command has completed. Don't let it wander off. 🙈
Other parameters behave as documented in nbd_pwrite(3). 💖
NBD.aio_disconnect
val aio_disconnect : ?flags:CMD_FLAG.t list -> t -> unit
NBD.aio_disconnect t ?flags
disconnect from the NBD server 👑
Issues the disconnect command to the NBD server. This is not a normal command, because NBD servers aren't obliged to send a reply — rude but valid. So instead of waiting on a reply, you wait for nbd_aio_is_closed(3) to become true on the connection. And once you've issued it, that's it: no further commands. Periodt. 💅
libnbd won't stop you from issuing this while you're still waiting on replies to previous commands, but the NBD protocol recommends waiting until there are no other commands in flight (peep nbd_aio_in_flight(3)) — that way the server has a better shot at a clean shutdown. 🎀
The "flags" parameter must be 0 for now (it's just sitting there for future NBD protocol extensions). There's no direct synchronous counterpart, but nbd_shutdown(3) will call this function if appropriate. 🌟
NBD.aio_flush
val aio_flush : ?completion:(int Stdlib.ref -> int) -> ?flags:CMD_FLAG.t
list -> t -> cookie
NBD.aio_flush t ?completion ?flags
send a flush command to the NBD server 💅
Issues the flush command to the NBD server. 🫶
nbd_aio_command_completed(3)is literally your bestie for checking if it's done. Or you can pass the optional"completion_callback"and it'll get called exactly like it says in "Completion callbacks" inlibnbd(3).
The other params do their thing exactly like nbd_flush(3) says.
val aio_trim : ?completion:(int Stdlib.ref -> int) -> ?flags:CMD_FLAG.t
list -> t -> int64 -> int64 -> cookie
NBD.aio_trim t ?completion ?flags count offset
send trim command to the NBD server
Okay so you're throwing a trim command at the NBD server. Slay.
To check if it actually finished, hit up nbd_aio_command_completed(3). Or hand over the optional "completion_callback" and it does the thing described in "Completion callbacks" in libnbd(3).
Everything else behaves like nbd_trim(3) documents.
val aio_cache : ?completion:(int Stdlib.ref -> int) -> ?flags:CMD_FLAG.t
list -> t -> int64 -> int64 -> cookie
NBD.aio_cache t ?completion ?flags count offset
send cache (prefetch) command to the NBD server
Sending the cache (prefetch) command to the NBD server, no cap.
Check completion with nbd_aio_command_completed(3). Or supply the optional "completion_callback" — invoked just like "Completion callbacks" in libnbd(3) describes.
Other params? Behave exactly as nbd_cache(3) says.
val aio_zero : ?completion:(int Stdlib.ref -> int) -> ?flags:CMD_FLAG.t
list -> t -> int64 -> int64 -> cookie
NBD.aio_zero t ?completion ?flags count offset
send write zeroes command to the NBD server
Issuing a write zeroes command to the NBD server. Zeroes only, bestie 💅.
To check if the command completed, call nbd_aio_command_completed(3). Or supply the optional "completion_callback" which gets invoked as described in "Completion callbacks" in libnbd(3).
Other parameters behave as documented in nbd_zero(3).
val aio_block_status : ?completion:(int Stdlib.ref -> int) ->
?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> (string -> int64 ->
int64 array -> int Stdlib.ref -> int) -> cookie
NBD.aio_block_status t ?completion ?flags count offset extent
send block status command, with 32-bit callback
Sending the block status command to the NBD server 🫶.
To check if the command completed, call nbd_aio_command_completed(3). Or supply the optional "completion_callback" which will be invoked as described in "Completion callbacks" in libnbd(3).
Other parameters behave as documented in nbd_block_status(3).
⚠️ Okay so this function is inherently limited to 32-bit values. If the server replies with a larger extent, the length of that extent gets truncated to just below 32 bits and any further extents from the server are ignored. And if the server replies with a status value larger than 32 bits (only possible when extended headers are in use), the callback function gets passed an"EOVERFLOW"error. So if you want the full extent info from a server that supports 64-bit extents, you must usenbd_aio_block_status_64(3). Periodt.
val aio_block_status_64 : ?completion:(int Stdlib.ref -> int) ->
?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> (string -> int64 ->
extent array -> int Stdlib.ref -> int) -> cookie
NBD.aio_block_status_64 t ?completion ?flags count offset extent64
send block status command, with 64-bit callback
Sending the block status command to the NBD server, but make it 64-bit ✨.
To check if the command completed, call nbd_aio_command_completed(3). Or supply the optional "completion_callback" which will be invoked as described in "Completion callbacks" in libnbd(3).
Other parameters behave as documented in nbd_block_status_64(3).
val aio_block_status_filter : ?completion:(int Stdlib.ref -> int) ->
?flags:CMD_FLAG.t list -> t -> int64 -> int64 -> string list -> (string
-> int64 -> extent array -> int Stdlib.ref -> int) -> cookie
NBD.aio_block_status_filter t ?completion ?flags count offset contexts extent64
send filtered block status command to the NBD server
Sending a filtered block status command to the NBD server. Fancy, I know 💅.
To check if the command completed, call nbd_aio_command_completed(3). Or supply the optional "completion_callback" which will be invoked as described in "Completion callbacks" in libnbd(3).
Other parameters behave as documented in nbd_block_status_filter(3).
val aio_get_fd : t -> Unix.file_descr
NBD.aio_get_fd t
return file descriptor associated with this connection
Returns the underlying file descriptor tied to this connection. You can use it to check if the fd is ready for reading or writing, then call nbd_aio_notify_read(3) or nbd_aio_notify_write(3). Also peep nbd_aio_get_direction(3).
🚨 Do not do anything else with the file descriptor. Like, nothing. It's not yours, bestie.
val aio_get_direction : t -> int
NBD.aio_get_direction t
return the read or write direction
Returns the current direction of this connection — meaning whether we're next expecting to read data from the server, write data to the server, or both. It returns:
0— We are not expected to interact with the server file descriptor from the current state. It is not worth attempting to usepoll(2); if the connection is not dead, then state machine progress must instead come from some other means such asnbd_aio_connect(3)."LIBNBD_AIO_DIRECTION_READ"=1— We are expected next to read from the server. If usingpoll(2)you would set"events = POLLIN". If"revents"returns"POLLIN"or"POLLHUP"you would then callnbd_aio_notify_read(3).
Ngl, note that once libnbd reaches nbd_aio_is_ready(3), this direction is returned even when there are no commands in flight (see nbd_aio_in_flight(3)). In a single-threaded use of libnbd, it is not worth polling until after issuing a command, as otherwise the server will never wake up the poll. In a multi-threaded scenario, you can have one thread begin a polling loop prior to any commands, but any other thread that issues a command will need a way to kick the polling thread out of poll in case issuing the command changes the needed polling direction. Possible ways to do this include polling for activity on a pipe-to-self, or using pthread_kill(3) to send a signal that is masked except during ppoll(2).
"LIBNBD_AIO_DIRECTION_WRITE"=2— We are expected next to write to the server. If usingpoll(2)you would set"events = POLLOUT". If"revents"returns"POLLOUT"you would then callnbd_aio_notify_write(3)."LIBNBD_AIO_DIRECTION_BOTH"=3— We are expected next to either read or write to the server. If usingpoll(2)you would set"events = POLLIN|POLLOUT". If only one of"POLLIN"or"POLLOUT"is returned, then see above. However, if both are returned, it is better to call onlynbd_aio_notify_read(3), as processing the server's reply may change the state of the connection and invalidate the need to write more commands.
val aio_notify_read : t -> unit
NBD.aio_notify_read t
notify that the connection is readable
Sends a notification to the state machine that the connection is readable. Typically this is called after your main loop has detected that the file descriptor associated with this connection is readable. Iconic behavior, honestly.
val aio_notify_write : t -> unit
NBD.aio_notify_write t
notify that the connection is writable
Sends a notification to the state machine that the connection is writable. Typically called after your main loop has detected that the fd associated with this connection is writable. Slay.
val aio_is_created : t -> bool
NBD.aio_is_created t
check if the connection has just been created
Returns true if this connection has just been created. This is the state before the handle has started connecting to a server. In this state the handle can start to be connected by calling functions such as nbd_aio_connect(3).
val aio_is_connecting : t -> bool
NBD.aio_is_connecting t
check if the connection is connecting or handshaking
Returns true if this connection is connecting to the server, or in the process of handshaking and negotiating options — which all happens before the handle becomes ready to issue commands (see nbd_aio_is_ready(3)).
val aio_is_negotiating : t -> bool
NBD.aio_is_negotiating t
check if connection is ready to send handshake option
Returns true if this connection is ready to start another option negotiation command while handshaking with the server. An option command will move back to the connecting state (see nbd_aio_is_connecting(3)).
Ngl, note that this state cannot be reached unless requested by nbd_set_opt_mode(3), and even then it only works with newstyle servers; an oldstyle server will skip straight to nbd_aio_is_ready(3). Rude, but okay.
val aio_is_ready : t -> bool
NBD.aio_is_ready t
check if the connection is in the ready state
Returns true if this connection is connected to the NBD server, the handshake has completed, and the connection is idle or waiting for a reply. In this state the handle is ready to issue commands. Main character energy fr fr 👑.
val aio_is_processing : t -> bool
NBD.aio_is_processing t
check if the connection is processing a command
Returns true if this connection is connected to the NBD server, the handshake has completed, and the connection is processing commands (either writing out a request or reading a reply).
💡 Note the ready state (nbd_aio_is_ready(3)) is not included. In the ready state commands may be in flight (the server is processing them), but libnbd is not processing them.
val aio_is_dead : t -> bool
NBD.aio_is_dead t
check if the connection is dead
Returns true if the connection has encountered a fatal error and is dead. In this state the handle may only be closed. There is no way to recover a handle from the dead state. 💀 RIP.
val aio_is_closed : t -> bool
NBD.aio_is_closed t
check if the connection is closed
Returns true if the connection has closed. There is no way to reconnect a closed connection. Instead you must close the whole handle. That's just how it is, bestie 😭.
val aio_command_completed : t -> int64 -> bool
NBD.aio_command_completed t cookie
check if the command completed
Returns true if the command completed. If this function returns true then the command was successful and it has been retired. Returns false if the command is still in flight. This can also fail with an error in case the command failed (in this case the command is also retired).
A command is retired either via this command, or by using a completion callback which returns 1. So tidy.
The "cookie" parameter is the positive unique 64 bit cookie for the command, as returned by a call such as nbd_aio_pread(3).
val aio_peek_command_completed : t -> int64
NBD.aio_peek_command_completed t
check if any command has completed
Okay, so this one returns the unique positive 64 bit cookie of the first non-retired but completed command, gives you 0 if there are in-flight commands but none of them are awaiting retirement, and -1 on error — including when there are no in-flight commands. Any cookie returned by this function still has to be passed to nbd_aio_command_completed(3) to actually retire the command and learn whether the command was successful. No cap. 💅
val aio_in_flight : t -> int
NBD.aio_in_flight t
aio_in_flight checks how many aio commands are still in flight. It returns the number of in-flight aio commands that are still awaiting a response from the server before they can be retired. If this returns a non-zero value when requesting a disconnect from the server (see nbd_aio_disconnect(3) and nbd_shutdown(3)), libnbd does not try to wait for those commands to complete gracefully; if the server strands commands while shutting down, nbd_aio_command_completed(3) will report those commands as failed with a status of "ENOTCONN".
val connection_state : t -> string
NBD.connection_state t
connection_state returns a descriptive string for the state of the connection. This can be used for debugging or troubleshooting, but you should not rely on the state of connections since it may change in future versions. Lowkey useful, but don’t build your whole personality on it. 🦋
val get_package_name : t -> string
NBD.get_package_name t
get_package_name returns the name of the library. It’s always "libnbd" unless the library was modified with another name at compile time. Iconic, honestly.
val get_version : t -> string
NBD.get_version t
get_version returns the version of libnbd. This is returned as a string in the form "major.minor.release" where each of major, minor and release is a small positive integer. For example:
minor ↓ "1.0.3" ↑ ↑ major release
- major = 0 — The major number was 0 for the early experimental versions of libnbd where we still had an unstable API.
- major = 1 — The major number is 1 for the versions of libnbd with a long-term stable API and ABI. It is not anticipated that major will be any number other than 1.
- minor = 0, 2, ... (even) — The minor number is even for stable releases.
- minor = 1, 3, ... (odd) — The minor number is odd for development versions. Note that new APIs added in a development version remain experimental and subject to change in that branch until they appear in a stable release.
- release — The release number is incremented for each release along a particular branch.
val get_version_extra : t -> string
NBD.get_version_extra t
get_version_extra returns the extra version of libnbd. This is a freeform string which is set at package build time using:
./configure --with-extra="..."
and it’s intended to be used by downstream packagers (eg. Linux distributions) to convey extra version information, such as the precise version of the libnbd RPM, ".deb" etc.
The string may be "", indicating that no extra version information is available, or that this is an upstream build of libnbd.
val kill_subprocess : t -> int -> unit
NBD.kill_subprocess t signum
kill_subprocess kills the server running as a subprocess. This call may be used to kill the server running as a subprocess that was previously created using nbd_connect_command(3). You do not need to use this call. It is only needed if the server does not exit when the socket is closed.
The signum parameter is the optional signal number to send (see signal(7)). If signum is 0 then SIGTERM is sent.
val get_subprocess_pid : t -> int64
NBD.get_subprocess_pid t
get_subprocess_pid gets the process ID of the subprocess. For connections which create a subprocess such as nbd_connect_command(3), this returns the process ID (PID) of the subprocess. This is only supported on some platforms.
This is mainly useful in debugging cases. For example, this could be used to learn where to attach gdb(1) to diagnose a crash in the NBD server subprocess. Detective mode: activated. 🔍
val supports_tls : t -> bool
NBD.supports_tls t
supports_tls returns true if libnbd was compiled with gnutls which is required to support TLS encryption, or false if not.
val supports_vsock : t -> bool
NBD.supports_vsock t
supports_vsock returns true if libnbd was compiled with support for the "AF_VSOCK" family of sockets, or false if not.
Note that on the Linux operating system, this returns true if there is compile-time support, but you may still need runtime support for some aspects of AF_VSOCK usage; for example, use of "VMADDR_CID_LOCAL" as the server name requires that the vsock_loopback kernel module is loaded.
val supports_uri : t -> bool
NBD.supports_uri t
supports_uri returns true if libnbd was compiled with libxml2 which is required to support NBD URIs, or false if not.
val get_uri : t -> string
NBD.get_uri t
get_uri makes a best effort attempt to construct an NBD URI which could be used to connect back to the same server (using nbd_connect_uri(3)).
In some cases there is not enough information in the handle to successfully create a URI (eg. if you connected with nbd_connect_socket(3)). In such cases the call returns "NULL" and further diagnostic information is available via nbd_get_errno(3) and nbd_get_error(3) as usual.
Even if a URI is returned it is not guaranteed to work, and it may not be optimal. Bestie, it’s giving “best effort,” not “magic.” 🙈
nbdinfo(1) --uri option is a way to access this API from shell scripts.
val is_uri : t -> string -> bool
NBD.is_uri t uri
is_uri detects if the parameter uri could be an NBD URI or not. The function returns true if uri is likely to be an NBD URI, or false if not.
This can be used to write programs that take either a URI or something else like a filename as a parameter. nbdcopy(1) is one such program.
The current test is heuristic. In particular it does not guarantee that nbd_connect_uri(3) will work. So like… vibe check only, not a promise. 💀
val set_keepalive : t -> bool -> unit
NBD.set_keepalive t enable
set_keepalive sets the keepalive ("SO_KEEPALIVE") flag on the socket after creating it.
On operating systems and protocols that support it, this means the OS sends periodic messages to the NBD server to detect if the socket is still connected. If this feature is not supported then setting this does nothing.
If using TCP on Linux, keepalive can be tuned by calling nbd_set_tcp_option(3).
This only works if called on the handle before calls such as nbd_connect_tcp(3) where libnbd itself creates the socket. It does not apply to nbd_connect_socket(3) where a socket is created somewhere else and passed to libnbd.
val get_keepalive : t -> bool
NBD.get_keepalive t
get_keepalive returns the keepalive status.
If the handle is not connected, this returns the keepalive flag from the handle as set by nbd_set_keepalive(3).
If the handle is connected, this attempts to read the keepalive ("SO_KEEPALIVE") flag from the socket itself, telling you if keepalive was successfully enabled or not.
val set_tcp_option : t -> int -> int -> unit
NBD.set_tcp_option t option v
set_tcp_option sets TCP options on the socket. If making a TCP connection then you can call this function to set a subset of TCP options on the socket.
Currently the option parameter may be one of:
TCP_KEEPCNTTCP_KEEPIDLETCP_KEEPINTVL
These options may not work on non-Linux systems.
For the TCP_KEEP* options, keepalive must also be enabled by calling nbd_set_keepalive(3)). Yes, that extra parenthesis is just living its life. 💅
val add_close_callback : t -> (unit -> int) -> unit
NBD.add_close_callback t close
add_close_callback adds a callback which is called on close. libnbd handles maintain a list of callbacks which are called when the handle is closed. This adds a callback to this list.
Callbacks cannot be removed from the list once added. Callbacks might not necessarily be called in all circumstances (eg. if the program crashes).
The callback function is declared like this (in the C bindings):
int callback (void *user_data);
where the parameter is the user_data passed to this function, and an int is returned. The return value is ignored because nbd_close(3) returns void and cannot be cancelled.
The callback should not call nbd_* APIs on the same handle since it can be called while holding the handle lock and will cause a deadlock. Do not do it. Not even a little. 😭
If there are multiple close callbacks the order in which they are called is not defined. But all the callbacks are called first followed by all the free functions.
💅 That’s the whole page, bestie — brought to you by OCamldoc, dated 2026-10-13, and filed under NBD(3). 🎀
📦 where this page comes from
- centos-stream 10: ocaml-libnbd-devel 1.25.8-1.el10 · LGPL-2.0-or-later AND BSD-3-Clause
- 💖 girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms