# vdevadm

`vdevadm` is the vdev administration CLI — a **udevadm-like** front-end that talks
directly to vdev’s udev-compat database, sysfs, and settle/control interfaces. It is
linked against **libvdev** (and helper `common.c` for sysfs resolution). The
**`monitor`** subcommand uses **libudev** / **libudev-compat** for the event transport.

Install: `usr/bin/vdevadm` (see top-level `make install`).

## Replacing `udevadm` for legacy programs

Many scripts and packages hard-code the command name **`udevadm`** (`udevadm settle`,
`udevadm info`, initramfs hooks, etc.). vdev does not ship a separate `udevd` control
plane; instead **`vdevadm`** implements the subset those programs need.

To satisfy that dependency without patching every caller, install a symbolic link:

```sh
ln -sf vdevadm /usr/bin/udevadm
```

The Debian **`postinst`** script creates this link when `vdevadm` is installed.
Initramfs images built with the vdev hook copy `vdevadm` and add the same symlink in
the initrd.

This is intentional: legacy software keeps invoking `udevadm`, while the binary
executed is **`vdevadm`**.

### `info` and the initramfs

`vdevadm info` prefers the udev-compat database under `$VDEV_UDEV_ROOT/data/`.
When that record is absent (common in a minimal initrd that does not run
`zzz-udev-compat.act`), it falls back to reading the device’s sysfs `uevent`
and `subsystem` — the same baseline properties classic `udevadm info` can
obtain without a populated `/run/udev/data` store. That is enough for
**live-boot** (`DEVTYPE=disk`, `-q name`) without mounting hwdb or running the
full udev-compat helper during coldplug. For strict ABI compatibility with the full eudev tool
set, **libudev-compat-helpers** may still provide its own `udevadm`; on vdev systems
the symlink above is the supported integration path.

## Credits — udev / eudev

`vdevadm` is **not** a fork of the entire eudev `udevadm` tree. Most subcommands are
vdev-native (reading vdev’s filesystem metadata, settle FIFOs, and `vdevd` pid files).

The **`monitor`** subcommand is derived from **`udevadm-monitor.c`** in
**[eudev](https://github.com/eudev-project/eudev)** (originally by Kay Sievers, GPL-2+).
It was adapted for `vdevadm` so one binary can cover the common `udevadm` surface on
vdev systems. Event delivery uses **libudev-compat** (eventfs-backed “udev” netlink on
vdev), the same stack applications already use via `libudev`.

Thank you to the **udev** and **eudev** projects for the monitor design and for
establishing the `udevadm` CLI conventions this tool follows.

## Environment

| Variable | Default | Meaning |
|----------|---------|---------|
| `VDEV_MOUNTPOINT` | `/dev` | Device node root |
| `VDEV_UDEV_ROOT` | `/run/udev` or `$VDEV_MOUNTPOINT/metadata/udev` | udev-compat DB root |
| `VDEV_STATE_DIR` | `/run/vdev` | Runtime state (settle FIFOs, pid files, stamps) |
| `VDEV_UDEV_CONTROL` | `/run/udev/control` | vdevd control socket (eudev wire protocol) |
| `VDEV_SYSFS` | `/sys` | sysfs mount point |

Use `vdevadm info --ctx` to print the resolved paths.

## Commands vs eudev `udevadm`

| vdevadm | udevadm analogue | vdev backend |
|---------|------------------|--------------|
| **`info`** | `info` | Reads `$VDEV_UDEV_ROOT/data/*` (`E:`, `G:`, `S:` records from `udev-compat.sh`) |
| **`trigger`** | `trigger` | Writes `add`/`remove`/`change` to `.../uevent` (kernel → vdevd netlink) |
| **`settle`** | `settle` (partial) | Default: PING on `/run/udev/control` until vdevd workqueue is idle. `--input` uses `vdev_settle` FIFO; `--stamp` waits for coldplug `.ready` files |
| **`control`** | `control` | Unix socket `$VDEV_UDEV_CONTROL` or `/run/udev/control` (eudev wire protocol) |
| **`monitor`** | `monitor` | libudev netlink monitor (`--udev`, `--kernel`, filters); derived from eudev |

There is **no** separate `udevd` process. **`vdevd`** binds the eudev-compatible
**`/run/udev/control`** socket. Override the socket with **`VDEV_UDEV_CONTROL`**.
Listeners (**early**, **initrd**, **long-running**) bind control; **`coldplug_only`**
does not (so **`vdevadm control --print-pid`** / **`--exit`** / **`--promote`** only
reach listeners). For a foreground **`vdevd -1`** test, use **Ctrl+C** (**`SIGINT`**)
to stop; production oneshots still **`SIGTERM`** **`$PPID`** from
**`os-queue-tracking.sh`**. **`vdev`** starts **`vdevd.early`** without waiting for
**`vdev-coldplug`**. The oneshot runs KMS fallback, then a background worker:
**control → early trigger → settle → `vdevadm control --promote` → `vdevadm trigger --sysfs`**.

`vdevadm settle` waits for a **PING** reply (workqueue idle). It fails with a non-zero
exit if the control socket is missing (**ENOENT** / **ECONNREFUSED**), not silently.
It does **not**
wait for `/run/udev/queue` to vanish: that path is often a directory because
`/run/udev` is a symlink to `/dev/metadata/udev`.

## Examples

```sh
# Device database (like udevadm info)
vdevadm info /dev/sda5
vdevadm info b8:5
vdevadm info --export-db

# Sysfs uevent (like udevadm trigger)
vdevadm trigger --subsystem-match=input --action=add
vdevadm trigger -s net -a add
vdevadm trigger --sysfs -a add          # full /sys/devices coldplug sweep
vdevadm trigger /sys/class/input/event3
vdevadm trigger /sys/class/net/eth0

# --path-match: prefix under $VDEV_SYSFS (no /sys); full /sys/... also accepted
vdevadm trigger --path-match=/class/net/eth0 -a add

# Wait for input settle or coldplug stamp
vdevadm settle --status
vdevadm settle --list
vdevadm settle --stamp=/run/vdev/vdev.ready
vdevadm settle -t 30

# Administration (eudev-compatible control socket)
vdevadm control --reload
vdevadm control --promote                 # early → long-running (same PID)
vdevadm control --promote=/etc/vdev/vdevd.conf
vdevadm control --print-pid               # listener PID (SO_PEERCRED; no wire msg)
kill -TERM "$(vdevadm control --print-pid)"   # init stop / init-bottom
vdevadm control --stop-exec-queue
vdevadm control --property=VDEV_TEST=1
vdevadm settle                    # PING until workqueue idle
vdevadm settle --input            # wait for vdev_settle input sync (optional)
vdevadm settle --input-worker     # full input settle worker (optional / debug)
vdevadm trigger -s input --name-prefix=event -a add

# Event stream (like udevadm monitor; requires libudev-compat)
vdevadm monitor --udev
vdevadm monitor --kernel --subsystem-match=block
vdevadm monitor --property
```

## `trigger` semantics and gotchas

- `--subsystem-match=<name>` scans multiple sysfs layouts (eudev-style):
  - `$VDEV_SYSFS/class/<name>` (entries are usually symlinks into `/sys/devices`)
  - `$VDEV_SYSFS/bus/<name>/devices`
  - `$VDEV_SYSFS/subsystem/<name>/devices`
  Symlinks are followed; only nodes with a `uevent` file are triggered.
  Recursive `--sysfs` still walks only real directories under `/sys/devices`.
- Scan entries that are not device directories are skipped.
- If no matching device is found, `vdevadm` returns an error (`ENOENT`).

Practical implications:

- `vdevadm trigger -s net -a add` requires at least one present interface.
- `--action` accepts **`add`**, **`remove`**, **`change`**, **`bind`**, and **`unbind`**
  (default **`add`**). **`bind`** / **`unbind`** are handled like **`add`** / **`remove`**
  inside **`vdevd`** for action matching.
- Per-device trigger is often more deterministic:

```sh
vdevadm trigger --action=add /sys/class/net/eth0
```

- Writing directly to sysfs is equivalent and used by settle-sync:

```sh
printf 'add\n' > /sys/class/input/event3/uevent
```

- `--path-match` accepts:
  - a path relative to `$VDEV_SYSFS` (recommended), e.g. `/class/net/eth0`
  - a full `/sys/...` path (also accepted)

## FIFO settle interface

Same protocol as `vdev_settle` (optional input settle helper):

```sh
exec 3< /run/vdev/vdev-settle.rsp
printf 'list\n' > /run/vdev/vdev-settle.ctl
cat <&3
```

`vdevadm settle` wraps this; see **`example/README.md`** for the full settle worker
model.

## Source layout

| File | Role |
|------|------|
| `vdevadm.c` | Dispatcher |
| `vdevadm-ctx.c` | Path resolution (`libvdev` util) |
| `vdevadm-device.c` | Device ID + FIFO helper (`common.c` sysfs) |
| `vdevadm-info.c` | Database query |
| `vdevadm-trigger.c` | sysfs uevent trigger |
| `vdevadm-settle.c` | settle / stamp wait |
| `vdevadm-control.c` | Control socket, **`--print-pid`** (**`SO_PEERCRED`**) |
| `vdevadm-monitor.c` | libudev monitor (from eudev `udevadm-monitor.c`) |

## Relation to eudev tree

The eudev sources under `~/VDEV/eudev-3.2.14/` implement the full `udevd` control
plane (`udev-ctrl.c`, queue files). vdev replaces `udevd` with `vdevd` + filesystem
metadata; `vdevadm` is the matching query/trigger/monitor tool rather than linking
the whole eudev `udevadm` binary against libudev.

Future work (not yet implemented):

- `vdevadm test` (single-action replay)

Reload/restart during phased boot relies on **`eventfs_fstype_ok()`** in
`dev-setup.sh` (accepts `fuse`, `fuseblk`, and `fuse.*` from `stat -f '%T'`).
On Devuan Excalibur and similar systems, kernels report **`fuse`** instead of
**`fuseblk`**; without that check, preseed would remount eventfs on top of a live
queue and corrupt it.
