Documentation
¶
Overview ¶
Package kernel implements the Linux kernel adapter: sysfs reads/writes, netlink uevent listener, and the fd-passing handshake for usbip-host and vhci_hcd.
Three role-specific structs share a common substrate:
- ImporterAdapter satisfies app.ImporterKernel (vhci_hcd + usbip_core).
- ExporterAdapter satisfies app.ExporterKernel (usbip_host + usbip_core).
- EventsAdapter satisfies app.KernelEvents (netlink uevent fan-out).
Every file in this package carries //go:build linux. Non-Linux builds skip the package entirely; the app layer depends only on the interfaces in internal/app, not on the concrete types here.
Index ¶
- Constants
- Variables
- func ClassifyErrno(path string, errno unix.Errno) error
- func ListDirEntries(fsys fs.FS, path string) ([]string, error)
- func ReadHex16(fsys fs.FS, path string) (uint16, error)
- func ReadLine(fsys fs.FS, path string) (string, error)
- func ReadSpeedAttr(fsys fs.FS, path string) (domain.Speed, error)
- func ReadUint(fsys fs.FS, path string) (uint32, error)
- type EventsAdapter
- type ExporterAdapter
- func (a *ExporterAdapter) Bind(ctx context.Context, busID domain.BusID) error
- func (a *ExporterAdapter) Disconnect(ctx context.Context, busID domain.BusID) error
- func (a *ExporterAdapter) ExportOnConn(ctx context.Context, conn net.Conn, busID domain.BusID) error
- func (a *ExporterAdapter) ExportSessionActive(ctx context.Context, busID domain.BusID) (bool, error)
- func (a *ExporterAdapter) ListExportedDevices(ctx context.Context) ([]domain.Device, error)
- func (a *ExporterAdapter) ListLocalDevices(ctx context.Context) ([]domain.Device, error)
- func (a *ExporterAdapter) ModulesAvailable(_ context.Context) error
- func (a *ExporterAdapter) Unbind(ctx context.Context, busID domain.BusID) error
- type HubType
- type ImporterAdapter
- func (a *ImporterAdapter) AttachRemote(ctx context.Context, conn net.Conn, spec app.RemoteDeviceSpec) (domain.PortID, error)
- func (a *ImporterAdapter) DetachPort(ctx context.Context, id domain.PortID) error
- func (a *ImporterAdapter) ListPorts(ctx context.Context) ([]domain.Port, error)
- func (a *ImporterAdapter) ModulesAvailable(_ context.Context) error
- type NetlinkDialer
- type NetlinkSocket
- type Option
- type StatusTopology
- type Topology
- type VHCILocation
- type WriteFunc
Constants ¶
const ( // SysfsUSBDevices is the USB subsystem device directory. SysfsUSBDevices = "/sys/bus/usb/devices" // SysfsUSBIPHostDriver is the usbip-host driver-level attribute root. SysfsUSBIPHostDriver = "/sys/bus/usb/drivers/usbip-host" // SysfsDriversProbe is the global USB-bus probe trigger. Writing a // busid forces the kernel to re-evaluate the driver match-table for // that device, attaching whichever native driver claims it. Used in // Bind's rollback path to restore the original driver after a bind // failure that left the bare device unbound. SysfsDriversProbe = "/sys/bus/usb/drivers_probe" // SysfsDriverBind is the generic driver "bind" attribute filename. SysfsDriverBind = "bind" // SysfsDriverUnbind is the generic driver "unbind" attribute filename. SysfsDriverUnbind = "unbind" // SysfsMatchBusID is the usbip-host match_busid attribute filename. // Accepts "add <busid>" or "del <busid>". SysfsMatchBusID = "match_busid" // SysfsRebind is the usbip-host rebind attribute filename. Accepts // a bare "<busid>" to rebind to the original driver. SysfsRebind = "rebind" // SysfsUsbipSockfd is the per-device sockfd attribute. "<fd>" to // connect; "-1" to disconnect. SysfsUsbipSockfd = "usbip_sockfd" // SysfsUsbipStatus is the per-device status attribute. SysfsUsbipStatus = "usbip_status" // SysfsVHCIHCD is the vhci_hcd driver's sysfs group root. Even on // multi-controller kernels the group lives at vhci_hcd.0; additional // status files appear there as status.1, status.2, and so on. SysfsVHCIHCD = "/sys/devices/platform/vhci_hcd.0" // SysfsVHCIAttach is the vhci attach attribute filename. Write // "%u %d %u %u" = port sockfd devid speed. SysfsVHCIAttach = "attach" // SysfsVHCIDetach is the vhci detach attribute filename. Write a // single decimal integer (kstrtoint). SysfsVHCIDetach = "detach" // SysfsVHCIStatus is the vhci status file for controller 0. SysfsVHCIStatus = "status" // SysfsVHCIStatusFmt is the printf format for non-primary controller // status files (controllers 1..nports/VHCI_HC_PORTS-1). SysfsVHCIStatusFmt = "status.%d" // SysfsVHCINPorts is the read-only nports attribute; total ports // across all controllers. SysfsVHCINPorts = "nports" // SysfsModuleDir is the base path under which loaded kernel modules // appear as subdirectories. SysfsModuleDir = "/sys/module" )
Sysfs path constants verified against drivers/usb/usbip/{stub_dev, stub_main,vhci_sysfs}.c in the upstream kernel. Every byte offset, path, and write payload that appears in other files of this package must route through a constant declared here so the kernel-interface contract lives in exactly one file.
Absolute paths (rooted at "/sys/...") are the canonical spec values; at runtime the adapter resolves them relative to the injected fs.FS via pathFromAbs() below.
const ( // ModuleUsbipCore is the shared usbip core module. ModuleUsbipCore = "usbip_core" // ModuleUsbipHost is the exporter-side usbip-host stub driver. ModuleUsbipHost = "usbip_host" // ModuleVHCIHCD is the importer-side vhci_hcd virtual host controller. ModuleVHCIHCD = "vhci_hcd" )
Kernel module names probed by ModulesAvailable. Verbatim from the sysfs entry names; dashes are replaced by underscores per kernel convention.
const ( // MatchBusIDAddPrefix is the prefix for "add <busid>" writes to // match_busid. Includes trailing space. MatchBusIDAddPrefix = "add " // MatchBusIDDelPrefix is the prefix for "del <busid>" writes to // match_busid. Includes trailing space. MatchBusIDDelPrefix = "del " // UsbipSockfdDisconnect is the payload written to usbip_sockfd to // trigger SDEV_EVENT_DOWN (graceful disconnect). UsbipSockfdDisconnect = "-1" )
Write payloads whose exact byte sequence is dictated by the kernel parsers (see kernel-adapter OpenSpec "Key facts verified from kernel source").
const ( // HubTypeHighSpeed marks a high-speed slot in the status file. HubTypeHighSpeed = "hs" // HubTypeSuperSpeed marks a SuperSpeed slot in the status file. HubTypeSuperSpeed = "ss" )
Hub-type tokens used as the first whitespace-delimited token of each vhci status row. "hs" identifies a USB 2.x slot; "ss" identifies a USB 3.x slot.
const VHCIAttachFmt = "%u %d %u %u"
VHCIAttachFmt is the printf format used by vhci_sysfs::attach_store. Kernel source uses sscanf("%u %u %u %u") but upstream client writes a signed fd ("%u %d %u %u") for byte-for-byte interop.
const VHCIStatusHeaderPrefix = "hub"
VHCIStatusHeaderPrefix is the literal prefix of the status file's optional header line. The parser skips any line starting with this prefix regardless of trailing whitespace variation.
const VHCIStatusRowFmt = "%s %04u %03u %03u %08x %06u %s"
VHCIStatusRowFmt is the scanf format used by the vhci_hcd.c status file. Verbatim from drivers/usb/usbip/vhci_sysfs.c.
Variables ¶
var ErrSysfsSpeedUnrecognized = errors.New("unrecognized sysfs speed")
ErrSysfsSpeedUnrecognized is returned by ReadSpeedAttr when the kernel emitted a string outside speed_show()'s known output set. Surfacing as a typed sentinel lets callers distinguish "kernel reported a value we do not yet map" from a generic I/O error.
Functions ¶
func ClassifyErrno ¶
ClassifyErrno maps a raw errno against path-kind into a domain sentinel. Exported for tests; production call sites go through classifyFSErr / classifySyscallErr which wrap the result with context.
func ListDirEntries ¶
ListDirEntries returns the names of every entry in the directory at path, sorted lexicographically (fs.ReadDir already guarantees sorted order; we return the slice of names directly). Errors during open go through the errno classifier.
func ReadHex16 ¶
ReadHex16 reads a 16-bit hex-formatted sysfs attribute (e.g. idVendor, idProduct). Accepts both "0x0951" and "0951" forms; whitespace tolerated on either side.
func ReadLine ¶
ReadLine opens path through fsys, reads the entire file, and returns the content with leading and trailing whitespace trimmed. Intended for one-line sysfs attributes; for multi-line readers the caller should tokenise higher up the stack.
func ReadSpeedAttr ¶
ReadSpeedAttr reads the sysfs "speed" attribute at path and converts the Mbps string to a domain.Speed enum value. Returns ErrSysfsSpeedUnrecognized for any string not in the kernel's speed_show() output set.
Types ¶
type EventsAdapter ¶
type EventsAdapter struct {
// contains filtered or unexported fields
}
EventsAdapter satisfies app.KernelEvents. It opens and shares a single NETLINK_KOBJECT_UEVENT socket across subscribers via an internal fan-out. dispMu guards the first Subscribe that lazily opens the socket; disp is the live dispatcher or nil when no subscribers are active.
func NewEventsAdapter ¶
func NewEventsAdapter(opts ...Option) (*EventsAdapter, error)
NewEventsAdapter constructs an EventsAdapter with the same defaults as NewImporterAdapter. dispMu is allocated eagerly here — lazy initialisation in Subscribe would race under concurrent first- Subscribers, letting two callers lock different mutexes and nlDial twice (leaking one dispatcher + netlink socket).
func (*EventsAdapter) Subscribe ¶
Subscribe returns a buffered event channel fed by the internal netlink listener. The first Subscribe opens the socket and starts the dispatcher run-loop; subsequent Subscribes join the existing fan-out. Each caller receives its own cancel func; the dispatcher lives until the LAST subscriber unsubscribes (architecture-layering OpenSpec), not the first — the dispatcher carries its own context independent of any subscriber's.
Registration ordering: the subscriber is added to the fan-out map BEFORE the run-loop goroutine is kicked off on the first call, so there is no window in which events could be broadcast to an empty subscriber set.
type ExporterAdapter ¶
type ExporterAdapter struct {
// contains filtered or unexported fields
}
ExporterAdapter satisfies app.ExporterKernel. It operates against the usbip_host + usbip_core modules.
busidLocks serialises Bind/Unbind per busid so concurrent callers against the same device cannot race on match_busid: without this a loser's rollback (match_busid del) can erase the winner's just- added entry, leaving the kernel in a half-bound state.
func NewExporterAdapter ¶
func NewExporterAdapter(opts ...Option) (*ExporterAdapter, error)
NewExporterAdapter constructs an ExporterAdapter with the same defaults as NewImporterAdapter.
func (*ExporterAdapter) Bind ¶
Bind performs the three-write sequence required by usbip-host, ordered to match upstream usbip-utils' bind_device() exactly:
- add the busID to usbip-host/match_busid (BEFORE any unbind)
- unbind the bare-device usb_driver (typically the generic "usb" driver). USB core's disconnect cascade then unbinds every interface (drivers/usb/core/generic.c:265-272), and the kernel's auto-probe re-evaluates drivers — usbip-host's stub_probe wins because match_busid now contains the busid.
- write busid to usbip-host/bind as a belt-and-braces fallback in case auto-probe was disabled or another driver beat usbip-host to the device.
Why match_busid MUST precede the unbind: if unbind happens first, the kernel auto-probes drivers between unbind and our match_busid write. usbip-host's stub_probe runs during that window with the table empty, returns -ENODEV ("3-1 is not in match_busid table... skip!"), and the original driver (cdc_ncm, etc.) reclaims the device. The subsequent /bind write then races against the rebound driver and surfaces ENODEV to the operator. Putting match_busid first closes that window.
Step-3 failure rolls back step 1 (match_busid del) so the busid table is not poisoned. Step-2's unbind is preserved so the operator can rebind manually.
preflightBind() runs first: kernel-module check, vhci-loop refusal, hub guard (refuses bDeviceClass=0x09 to prevent cascade-disconnect), and already-exported short-circuit (returns ErrDeviceAlreadyBound before any sysfs mutation). The pipeline is serialized per-busid via lockBusID so concurrent Bind/Unbind calls cannot race on match_busid.
func (*ExporterAdapter) Disconnect ¶
Disconnect writes "-1" to /sys/bus/usb/devices/<busid>/usbip_sockfd. This triggers SDEV_EVENT_DOWN kernel-side; the export session drops cleanly. Do NOT close the caller's conn as a substitute — kernel owns the socket and a local close alone accomplishes nothing useful for the remote end.
func (*ExporterAdapter) ExportOnConn ¶
func (a *ExporterAdapter) ExportOnConn(ctx context.Context, conn net.Conn, busID domain.BusID) error
ExportOnConn extracts the OS fd from conn and writes its decimal ASCII form to /sys/bus/usb/devices/<busid>/usbip_sockfd. Mirror of ImporterAdapter.AttachRemote: after the kernel-side sockfd_lookup succeeds the kernel owns a ref on the socket; callers still own their own fd and MUST close it themselves when the session ends — this adapter never closes the caller's conn, as required by the kernel-adapter fd-handoff contract.
func (*ExporterAdapter) ExportSessionActive ¶
func (a *ExporterAdapter) ExportSessionActive(ctx context.Context, busID domain.BusID) (bool, error)
ExportSessionActive reads usbip_host's authoritative per-device connection state. Linux transitions usbip_status from SDEV_ST_USED back to SDEV_ST_AVAILABLE when a remote peer disconnects, but does not emit an exporter-side VHCI detach uevent for that transition.
func (*ExporterAdapter) ListExportedDevices ¶
ListExportedDevices returns only devices currently exportable on the wire: bound to usbip-host AND not actively claimed by an importer. Mirrors upstream usbipd.c::send_reply_devlist (lines 172-206) which filters via usbip_host_driver.c::is_my_device() and excludes SDEV_ST_USED.
Operators and the CLI's `list` continue to use ListLocalDevices to see every USB device on the host regardless of bind state. The daemon's OP_REP_DEVLIST handler must use THIS method so peers do not receive a bus dump including unbound or in-use devices.
func (*ExporterAdapter) ListLocalDevices ¶
ListLocalDevices walks /sys/bus/usb/devices, filters for bus-id-like entries, and returns one domain.Device per entry. The module preflight runs first so a module-loss mid-flight surfaces as ErrKernelModuleMissing rather than ErrDeviceNotFound.
func (*ExporterAdapter) ModulesAvailable ¶
func (a *ExporterAdapter) ModulesAvailable(_ context.Context) error
ModulesAvailable probes usbip_core + usbip_host — the exporter-side module set.
func (*ExporterAdapter) Unbind ¶
Unbind reverses Bind: refuses if the device is not bound to usbip-host (precheck mirrors upstream usbip_unbind.c lines 54-58), gracefully disconnects any active importer session, writes the usbip-host/unbind sequence, removes the busID from match_busid, and triggers a default-driver rebind.
The pre-disconnect step is crucial: writing -1 to the per-device usbip_sockfd attribute triggers SDEV_EVENT_DOWN in the kernel and drops any in-flight URBs cleanly. Without it, the subsequent usbip-host/unbind write blocks indefinitely while the kernel waits for the importer socket to drain — operators saw `usbip-go unbind` hang. The pre-disconnect failure is non-fatal: a freshly-bound device with no active session has no sockfd attribute (or the attribute returns ENODEV); the unbind sequence continues either way. Serialized per-busid via lockBusID.
type HubType ¶
type HubType uint8
HubType distinguishes the two per-controller VHCI hubs: high-speed (USB 2.x, root hub registered first by vhci_hcd.c) and super-speed (USB 3.x, registered second). The kernel's flat port numbering places all HS ports of controller N before all SS ports of controller N.
type ImporterAdapter ¶
type ImporterAdapter struct {
// contains filtered or unexported fields
}
ImporterAdapter satisfies app.ImporterKernel. It operates against the vhci_hcd + usbip_core modules.
portMutationMu serializes every VHCI attach/detach topology check and sysfs mutation. Attach-vs-attach needs it to make free-port discovery atomic with the write; attach-vs-detach uses the same adapter-local boundary so their topology reads and kernel mutations cannot overlap.
func NewImporterAdapter ¶
func NewImporterAdapter(opts ...Option) (*ImporterAdapter, error)
NewImporterAdapter constructs an ImporterAdapter with defaults (os.DirFS("/"), live sysfs WriteFunc, live netlink dialer, no-op logger, RealClock). Options override in declaration order.
func (*ImporterAdapter) AttachRemote ¶
func (a *ImporterAdapter) AttachRemote( ctx context.Context, conn net.Conn, spec app.RemoteDeviceSpec, ) (domain.PortID, error)
AttachRemote performs the fd-passing dance required to hand a live TCP socket to vhci_hcd. The kernel-adapter and importer-lifecycle OpenSpec documents pin the ordering contract; this method is the single source of truth for that ordering and any modification must re-verify the guarantees below.
NOTE — fd lifecycle (kernel-adapter and importer-lifecycle OpenSpec documents):
Write first, close second. We write the sysfs `attach` file, which triggers sockfd_lookup(fd) kernel-side. The kernel fgets the struct file and stores it in vdev->ud.tcp_socket. This must return successfully before we touch our fd. Closing earlier invalidates the lookup and the attach fails.
Close after success. Once the sysfs write succeeds, conn.Close() drops our file refcount from 2 (ours + kernel's) to 1 (kernel's). The socket stays alive; no FIN is sent.
Shutdown is a consequence. DetachPort / Disconnect signal the kernel to release its ref; the underlying TCP socket then emits FIN as part of normal socket teardown.
Failure-before-handoff cleanup. On *any* error path before the sysfs write returns success, the caller still owns the conn and MUST close it. This adapter NEVER closes the caller's conn on error paths — the calling app method observes errors and performs its own close.
Violating this ordering is the most common source of regressions; maintainers editing this function must re-read kernel-adapter and importer-lifecycle OpenSpec documents in full.
func (*ImporterAdapter) DetachPort ¶
DetachPort writes the decimal port ID to vhci_hcd.0/detach. Format per kernel-adapter OpenSpec: kstrtoint, single decimal integer, no trailing newline.
Defence-in-depth: the flat port is validated against the cached topology before any sysfs write, symmetric with attachAtPort. vhci_sysfs.c::detach_store returns -EINVAL when the flat port fails valid_port(), but surfacing that bare errno gives operators no context; the pre-write check wraps the adapter-local errPortOutOfRange with port + nports so a stale handle surviving a vhci_hcd module reload (or any other source of drift between the importer's cached portID and the current kernel port space) produces a diagnosable failure instead of a silent EINVAL. The check is cheap (one map lookup + two uint32 comparisons) and runs ahead of the expensive sysfs write regardless of caller.
The bounds check consumes only NControllers + VHCIPorts, so it routes through loadStatusTopology — the BusMap-free projection that survives live-host mid-probe races (carrying the precedent forward, mirrored by attachAtPort). Using loadTopology would tie every detach to BusMap completeness and spuriously fail on transient shortfalls irrelevant to the bounds arithmetic.
func (*ImporterAdapter) ListPorts ¶
ListPorts parses every row of every status file into domain.Port, including unused slots. importer-lifecycle and exporter-daemon OpenSpec documents: when modules are missing, both the nil slice AND ErrKernelModuleMissing surface — callers must check the error.
func (*ImporterAdapter) ModulesAvailable ¶
func (a *ImporterAdapter) ModulesAvailable(_ context.Context) error
ModulesAvailable probes usbip_core + vhci_hcd — the importer-side module set. Runs at startup and before each importer operation so runtime module disappearance described by the security-release-quality and operations-observability OpenSpec documents is surfaced with a clear error.
type NetlinkDialer ¶
type NetlinkDialer func() (NetlinkSocket, error)
NetlinkDialer opens a NETLINK_KOBJECT_UEVENT socket. The zero-arg signature keeps the contract trivial for test injection; production uses openNetlinkSocket() from uevent.go.
type NetlinkSocket ¶
type NetlinkSocket interface {
// Receive returns a single uevent payload (NUL-separated KEY=VALUE
// buffer). On close it returns a wrapped net.ErrClosed-like error;
// callers exit their read loop on error.
Receive() ([]byte, error)
// Close terminates the socket. Safe to call from any goroutine.
Close() error
}
NetlinkSocket is the minimal surface the uevent listener needs from a NETLINK_KOBJECT_UEVENT socket. Tests inject a fake producing a stream of pre-canned uevent payloads.
type Option ¶
type Option func(*commonAdapter)
Option configures a role adapter. All three role constructors accept the same option type because the underlying state substrate is identical.
func WithFS ¶
WithFS injects an fs.FS rooted at "/" (the whole filesystem, not just /sys). Tests pass a testing/fstest.MapFS populated with the minimal set of files each case needs.
func WithLogger ¶
WithLogger injects the structured logger used for slog.Warn signals and debug instrumentation. The zero-value commonAdapter carries a no-op logger so callers may omit this option.
func WithNetlinkDialer ¶
func WithNetlinkDialer(d NetlinkDialer) Option
WithNetlinkDialer injects a netlink-socket factory used by EventsAdapter.Subscribe. The default dialer opens a real NETLINK_KOBJECT_UEVENT socket.
func WithWriteFunc ¶
WithWriteFunc injects a sysfs write primitive. Tests use a recorder; production defaults to a wrapper around os.OpenFile.
type StatusTopology ¶
type StatusTopology struct {
// NControllers is the count of vhci_hcd.<N> platform devices.
NControllers uint32
// HCPorts is VHCI_HC_PORTS from the kernel's perspective — the
// per-hub port count. Derived as nports/(nControllers*2).
HCPorts uint32
// VHCIPorts is the per-controller total (HCPorts*2, one HS hub +
// one SS hub). Guaranteed nonzero on any StatusTopology returned
// successfully by discoverStatusTopology.
VHCIPorts uint32
}
StatusTopology is the minimal VHCI topology the status-reading path (readStatusRows, ListPorts, findFreePort) needs: controller count and per-controller VHCI_PORTS stride only. It deliberately omits the BusMap — status reading never consumes usb*/busnum, so requiring complete usb* children here would hard-fail ListPorts during live- host mid-probe races that the parser is otherwise equipped to handle. BusMap-dependent paths (uevent mapping) consume the full Topology instead.
type Topology ¶
type Topology struct {
// NControllers is the count of vhci_hcd.<N> platform devices.
NControllers uint32
// HCPorts is VHCI_HC_PORTS from the kernel's perspective — the
// per-hub port count. Derived as nports/(nControllers*2).
HCPorts uint32
// VHCIPorts is the per-controller total (HCPorts*2, one HS hub +
// one SS hub).
VHCIPorts uint32
// BusMap maps a Linux USB bus number (as reported by
// usbN/busnum) to the VHCI location (controller + hub) backing
// that bus.
BusMap map[uint32]VHCILocation
}
Topology is one operation- or event-local snapshot of the VHCI sysfs topology. BusMap-consuming code paths rediscover it at their boundary so a long-lived adapter observes vhci_hcd reloads. The status-reading path uses the lighter StatusTopology which omits BusMap.
func (Topology) FlatPort ¶
func (t Topology) FlatPort(loc VHCILocation, rhport0 uint32) domain.PortID
FlatPort converts a (location, rhport0) pair into the flat port identifier the kernel emits in the status file and expects on attach writes. rhport0 is 0-indexed and ranges over [0, HCPorts).
Formula matches status_show_vhci in vhci_sysfs.c:
flat = pdev_nr * VHCI_PORTS + hubOffset + rhport hubOffset = 0 for HS, VHCI_HC_PORTS for SS
func (Topology) Status ¶
func (t Topology) Status() StatusTopology
Status returns the BusMap-free projection of t. Used by callers that have a full Topology in hand but want to pass a StatusTopology to the status-reading helpers.
type VHCILocation ¶
type VHCILocation struct {
// ControllerIdx is the vhci_hcd.<N> suffix.
ControllerIdx uint32
// Hub selects the HS or SS root hub on that controller.
Hub HubType
}
VHCILocation identifies a (controller, hub) pair — the coordinates that together with an rhport (0-indexed, relative to the hub) uniquely locate a VHCI port in the kernel's flat port space.
type WriteFunc ¶
WriteFunc is the injected sysfs write primitive. path is the absolute path (e.g. "/sys/bus/usb/drivers/usbip-host/bind"); data is the byte payload written verbatim with no trailing newline unless a caller includes one.
Tests inject a recorder that captures the call for assertions. Production uses writeFile() from sysfs.go which opens with O_WRONLY and writes synchronously.