/
githubmirror
/
nvme-cli
Обзор
Документация
Войти
/
githubmirror
/
nvme-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
discoverd/src/units.c
750 строк
19 KB
Martin Belanger
discoverd: add the nvme-discoverd daemon
28 июл 2026, 17:26
28 июл 2026, 17:26
b3e5bc1
Код
Авторство
О чём код?
// SPDX-License-Identifier: GPL-2.0-or-later /* * This file is part of nvme-cli. * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries. * * Authors: Martin Belanger <martin.belanger@dell.com> */ #include <errno.h> #include <inttypes.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <systemd/sd-bus.h> #include <ccan/array_size/array_size.h> #include <ccan/list/list.h> #include "log.h" #include "state.h" #include "units.h" /* * NVME_PATH is the absolute path to the nvme binary the transient units exec. * The build system supplies it from <sbindir>/nvme (see discoverd/meson.build) * rather than hardcoding it here. It is the default; the --nvme-path command * line option overrides it (e.g. to run the daemon from a build tree), plumbed * through unit_mgr->nvme_path. */ #ifndef NVME_PATH #error "NVME_PATH must be defined by the build system (<sbindir>/nvme)" #endif #define SH_PATH "/bin/sh" /* * systemd does NOT expand specifiers (%t, %N, %n) in a transient unit's Exec * arguments passed over the D-Bus StartTransientUnit API — that expansion only * happens when parsing a unit *file*. So discoverd substitutes the real values * itself when building each unit: the runtime directory (/run, via the STATE_* * paths in state.h) and the unit name. The per-unit devid file is * STATE_UNITS_DIR/<unit-name-without-.service>.devid; the connect writes the * kernel device name into it and the ExecStartPost/ExecStop/ExecStopPost shell * commands (built in build_start_transient) read it back. */ static int unit_devid_path(char *buf, size_t len, const char *unit_name) { static const char suffix[] = ".service"; size_t slen = sizeof(suffix) - 1; size_t n = strlen(unit_name); if (n > slen && !strcmp(unit_name + n - slen, suffix)) n -= slen; return snprintf(buf, len, STATE_UNITS_DIR "/%.*s.devid", (int)n, unit_name); } /* Tracked pending systemd job. */ struct pending_job { struct list_node entry; char *job_path; char *unit_name; }; struct unit_mgr { sd_bus *bus; sd_event *event; unit_job_callback callback; void *user_data; struct list_head jobs; sd_bus_slot *job_removed_slot; /* nvme binary the transient units exec; borrowed (lives for argv's life). */ const char *nvme_path; }; /* * These functions append a single (name, sv) struct to an already-open * container of type a(sv). */ static int append_string_prop(sd_bus_message *m, const char *name, const char *val) { int r; r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', name); if (r < 0) return r; r = sd_bus_message_open_container(m, 'v', "s"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', val); if (r < 0) return r; r = sd_bus_message_close_container(m); if (r < 0) return r; return sd_bus_message_close_container(m); } static int append_bool_prop(sd_bus_message *m, const char *name, bool val) { int bv = val ? 1 : 0; int r; r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', name); if (r < 0) return r; r = sd_bus_message_open_container(m, 'v', "b"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 'b', &bv); if (r < 0) return r; r = sd_bus_message_close_container(m); if (r < 0) return r; return sd_bus_message_close_container(m); } static int append_uint64_prop(sd_bus_message *m, const char *name, uint64_t val) { int r; r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', name); if (r < 0) return r; r = sd_bus_message_open_container(m, 'v', "t"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 't', &val); if (r < 0) return r; r = sd_bus_message_close_container(m); if (r < 0) return r; return sd_bus_message_close_container(m); } /* Append a string-array property: (name, a(s)). */ static int append_strv_prop(sd_bus_message *m, const char *name, const char *const *strv) { int r; r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', name); if (r < 0) return r; r = sd_bus_message_open_container(m, 'v', "as"); if (r < 0) return r; r = sd_bus_message_open_container(m, 'a', "s"); if (r < 0) return r; for (; *strv; strv++) { r = sd_bus_message_append_basic(m, 's', *strv); if (r < 0) return r; } r = sd_bus_message_close_container(m); if (r < 0) return r; r = sd_bus_message_close_container(m); if (r < 0) return r; return sd_bus_message_close_container(m); } /* * Append an exec property (ExecStart, ExecStop, etc.) with a single entry. * argv[0] must be the executable path; argv is NULL-terminated. * ignore=true means non-zero exit is acceptable (the `-` prefix). */ static int append_exec_prop(sd_bus_message *m, const char *name, const char **argv, bool ignore) { int bv = ignore ? 1 : 0; int r; r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', name); if (r < 0) return r; r = sd_bus_message_open_container(m, 'v', "a(sasb)"); if (r < 0) return r; r = sd_bus_message_open_container(m, 'a', "(sasb)"); if (r < 0) return r; r = sd_bus_message_open_container(m, 'r', "sasb"); if (r < 0) return r; r = sd_bus_message_append_basic(m, 's', argv[0]); if (r < 0) return r; r = sd_bus_message_open_container(m, 'a', "s"); if (r < 0) return r; for (const char **a = argv; *a; a++) { r = sd_bus_message_append_basic(m, 's', *a); if (r < 0) return r; } r = sd_bus_message_close_container(m); // as if (r < 0) return r; r = sd_bus_message_append_basic(m, 'b', &bv); if (r < 0) return r; r = sd_bus_message_close_container(m); // r sasb if (r < 0) return r; r = sd_bus_message_close_container(m); // a(sasb) if (r < 0) return r; r = sd_bus_message_close_container(m); // v if (r < 0) return r; return sd_bus_message_close_container(m); // r sv } /* Append a shell-command exec property with ignore=true. */ static int append_shell_exec_prop(sd_bus_message *m, const char *name, const char *cmd) { const char *argv[] = { SH_PATH, "-c", cmd, NULL }; return append_exec_prop(m, name, argv, true); } static int track_job(struct unit_mgr *mgr, const char *job_path, const char *unit_name) { struct pending_job *j; j = calloc(1, sizeof(*j)); if (!j) return -ENOMEM; j->job_path = strdup(job_path); j->unit_name = strdup(unit_name); if (!j->job_path || !j->unit_name) { free(j->job_path); free(j->unit_name); free(j); return -ENOMEM; } list_add(&mgr->jobs, &j->entry); return 0; } static int job_removed_handler(sd_bus_message *m, void *user_data, sd_bus_error *ret_err __attribute__((unused))) { struct unit_mgr *mgr = user_data; struct pending_job *e; uint32_t id; const char *job_path, *unit_name, *result; int r; r = sd_bus_message_read(m, "uoss", &id, &job_path, &unit_name, &result); if (r < 0) return 0; list_for_each(&mgr->jobs, e, entry) { if (!streq(e->job_path, job_path)) continue; list_del_init(&e->entry); if (mgr->callback) mgr->callback(e->unit_name, streq(result, "done"), mgr->user_data); free(e->job_path); free(e->unit_name); free(e); return 0; } return 0; } struct unit_mgr *unit_mgr_new(sd_bus *bus, sd_event *event, unit_job_callback callback, void *user_data, const char *nvme_path) { struct unit_mgr *mgr; int r; mgr = calloc(1, sizeof(*mgr)); if (!mgr) return NULL; mgr->bus = bus; mgr->event = event; mgr->callback = callback; mgr->user_data = user_data; mgr->nvme_path = (nvme_path && *nvme_path) ? nvme_path : NVME_PATH; list_head_init(&mgr->jobs); /* * Subscribe to JobRemoved before any units are created. The bus must * already be attached to the event loop (sd_bus_attach_event) by the * caller so that the signal is dispatched through the event loop. */ r = sd_bus_match_signal(bus, &mgr->job_removed_slot, SYSTEMD_BUS_NAME, SYSTEMD_OBJ_PATH, SYSTEMD_MGR_IFACE, "JobRemoved", job_removed_handler, mgr); if (r < 0) { disc_err("sd_bus_match_signal(JobRemoved): %s", strerror(-r)); free(mgr); return NULL; } return mgr; } void unit_mgr_free(struct unit_mgr *mgr) { struct pending_job *e, *next; if (!mgr) return; sd_bus_slot_unref(mgr->job_removed_slot); list_for_each_safe(&mgr->jobs, e, next, entry) { free(e->job_path); free(e->unit_name); free(e); } free(mgr); } /* * Internal: call StartTransientUnit and track the returned job. * * Caller has built the message up through the properties array and must * close the properties array container and the aux array before calling. * We call sd_bus_call() synchronously — StartTransientUnit only enqueues a * systemd job; the actual nvme-connect subprocess is managed by systemd and * reports back via JobRemoved. */ static int do_start_transient(struct unit_mgr *mgr, sd_bus_message *msg, const char *unit_name) { sd_bus_error err = SD_BUS_ERROR_NULL; sd_bus_message *reply = NULL; const char *job_path; int r; r = sd_bus_call(mgr->bus, msg, 0, &err, &reply); if (r < 0) { disc_err("StartTransientUnit(%s): %s", unit_name, err.message ?: strerror(-r)); sd_bus_error_free(&err); return r; } r = sd_bus_message_read(reply, "o", &job_path); if (r < 0) goto out; r = track_job(mgr, job_path, unit_name); out: sd_bus_message_unref(reply); return r; } /* * Build a StartTransientUnit message for an nvme connect unit. * * connect_argv is a NULL-terminated argv starting at the nvme binary path. * desc is the Description= value. * is_fc determines whether After=network.target is added. */ static int build_start_transient(struct unit_mgr *mgr, const char *unit_name, const char *devid_path, const char *desc, bool is_fc, const char **connect_argv, sd_bus_message **out) { sd_bus_message *m = NULL; int r; r = sd_bus_message_new_method_call(mgr->bus, &m, SYSTEMD_BUS_NAME, SYSTEMD_OBJ_PATH, SYSTEMD_MGR_IFACE, "StartTransientUnit"); if (r < 0) return r; /* Name and mode. */ r = sd_bus_message_append(m, "ss", unit_name, "replace"); if (r < 0) goto err; /* Open the properties array a(sv). */ r = sd_bus_message_open_container(m, 'a', "(sv)"); if (r < 0) goto err; r = append_string_prop(m, "Type", "oneshot"); if (r < 0) goto err; r = append_bool_prop(m, "RemainAfterExit", true); if (r < 0) goto err; r = append_string_prop(m, "Description", desc); if (r < 0) goto err; r = append_string_prop(m, "CollectMode", "inactive-or-failed"); if (r < 0) goto err; r = append_uint64_prop(m, "TimeoutStopUSec", DISCONNECT_TIMEOUT_SEC * UINT64_C(1000000)); if (r < 0) goto err; /* Ordering: Before=nvme-discoverd.service always. */ { static const char *const before[] = { "nvme-discoverd.service", NULL }; r = append_strv_prop(m, "Before", before); if (r < 0) goto err; } /* After=network.target for TCP/RDMA (omit for FC). */ if (!is_fc) { static const char *const after[] = { "network.target", NULL }; r = append_strv_prop(m, "After", after); if (r < 0) goto err; } r = append_exec_prop(m, "ExecStart", connect_argv, false); if (r < 0) goto err; /* * Build the post/stop shell commands with the real devid path, state * directory and unit name substituted (systemd does not expand %t/%N/%n * here — see unit_devid_path()). The nvme path in ExecStop is * double-quoted to tolerate spaces. */ { char exec_start_post[512]; char exec_stop[PATH_MAX + 512]; char exec_stop_post[512]; int n1, n2, n3; n1 = snprintf(exec_start_post, sizeof(exec_start_post), "DEV=$(cat %s 2>/dev/null) && " "mkdir -p %s/$DEV && " "echo %s > %s/$DEV/unit", devid_path, STATE_CTRLS_DIR, unit_name, STATE_CTRLS_DIR); n2 = snprintf(exec_stop, sizeof(exec_stop), "DEV=$(cat %s 2>/dev/null); " "[ -n \"$DEV\" ] && " "[ \"$(cat %s/$DEV/unit 2>/dev/null)\" = \"%s\" ] && " "\"%s\" disconnect -d $DEV", devid_path, STATE_CTRLS_DIR, unit_name, mgr->nvme_path); n3 = snprintf(exec_stop_post, sizeof(exec_stop_post), "DEV=$(cat %s 2>/dev/null); " "rm -f %s; " "[ -n \"$DEV\" ] && " "[ \"$(cat %s/$DEV/unit 2>/dev/null)\" = \"%s\" ] && " "rm -rf %s/$DEV", devid_path, devid_path, STATE_CTRLS_DIR, unit_name, STATE_CTRLS_DIR); if (n1 < 0 || n1 >= (int)sizeof(exec_start_post) || n2 < 0 || n2 >= (int)sizeof(exec_stop) || n3 < 0 || n3 >= (int)sizeof(exec_stop_post)) { r = -ENAMETOOLONG; goto err; } r = append_shell_exec_prop(m, "ExecStartPost", exec_start_post); if (r < 0) goto err; r = append_shell_exec_prop(m, "ExecStop", exec_stop); if (r < 0) goto err; r = append_shell_exec_prop(m, "ExecStopPost", exec_stop_post); if (r < 0) goto err; } r = sd_bus_message_close_container(m); // a(sv) if (r < 0) goto err; /* Empty auxiliary units array. */ r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); if (r < 0) goto err; r = sd_bus_message_close_container(m); if (r < 0) goto err; *out = m; return 0; err: sd_bus_message_unref(m); return r; } /* * Build the nvme-connect argv into s->argv[] and return it. * @params: resolved connect parameters to emit (kato, ctrl-loss-tmo, tls, …). * is_nbft: use --owner=nbft; otherwise --owner=discoverd. * Strings are formatted into the provided scratch buffers (in the struct); * the TID fields and params share the extra[] pool. */ #define MAX_EXTRA_ARGS 40 struct connect_scratch { char owner[32]; char devid_arg[160]; char extra[MAX_EXTRA_ARGS][320]; // TID fields + resolved params int n_extra; const char *argv[8 + MAX_EXTRA_ARGS]; }; /* * libnvmf_connect_args_emit() callback: copy one formatted option into the * extra[] pool. */ struct emit_ctx { struct connect_scratch *s; int *argc; }; static void emit_arg(const char *arg, void *user_data) { struct emit_ctx *e = user_data; struct connect_scratch *s = e->s; if (s->n_extra >= MAX_EXTRA_ARGS || *e->argc >= (int)ARRAY_SIZE(s->argv) - 4) return; // leave room for --idempotent, owner, devid-file, NULL snprintf(s->extra[s->n_extra], sizeof(s->extra[0]), "%s", arg); s->argv[(*e->argc)++] = s->extra[s->n_extra++]; } static int build_connect_argv(const char *nvme_bin, const char *devid_path, const struct libnvmf_tid *t, const struct libnvmf_params *params, bool is_nbft, struct connect_scratch *s) { struct emit_ctx ec; int i = 0; int r; s->argv[i++] = nvme_bin; s->argv[i++] = "connect"; /* * libnvmf_connect_args_emit() renders the TID's addressing/identity * fields and the resolved parameters as "nvme connect" options, in * that order; discoverd only appends the connect-manager-private * options below (--idempotent, --owner, --devid-file). */ ec.s = s; ec.argc = &i; r = libnvmf_connect_args_emit(t, params, emit_arg, &ec); if (r < 0) return r; s->argv[i++] = "--idempotent"; snprintf(s->owner, sizeof(s->owner), "--owner=%s", is_nbft ? "nbft" : "discoverd"); s->argv[i++] = s->owner; snprintf(s->devid_arg, sizeof(s->devid_arg), "--devid-file=%s", devid_path); s->argv[i++] = s->devid_arg; s->argv[i] = NULL; return 0; } int unit_start_dc(struct unit_mgr *mgr, const struct libnvmf_tid *t, const struct libnvmf_params *params, bool is_nbft) { struct connect_scratch scratch = { }; char desc[320]; char devid_path[128]; char *unit_name; sd_bus_message *m = NULL; const char *subsysnqn = libnvmf_tid_get_subsysnqn(t); const char *transport = libnvmf_tid_get_transport(t); const char *traddr = libnvmf_tid_get_traddr(t); const char *trsvcid = libnvmf_tid_get_trsvcid(t); bool is_fc; int r; unit_name = tid_unit_name(t); if (!unit_name) return -ENOMEM; unit_devid_path(devid_path, sizeof(devid_path), unit_name); r = build_connect_argv(mgr->nvme_path, devid_path, t, params, is_nbft, &scratch); if (r < 0) goto out; snprintf(desc, sizeof(desc), "NVMe Discovery Controller %s @ %s:%s", subsysnqn, traddr, trsvcid ? trsvcid : "none"); is_fc = shr_streq0(transport, "fc"); r = build_start_transient(mgr, unit_name, devid_path, desc, is_fc, scratch.argv, &m); if (r < 0) goto out; r = do_start_transient(mgr, m, unit_name); sd_bus_message_unref(m); out: free(unit_name); return r; } int unit_start_ioc(struct unit_mgr *mgr, const struct libnvmf_tid *t, const struct libnvmf_params *params, bool is_nbft) { struct connect_scratch scratch = { }; char desc[320]; char devid_path[128]; char *unit_name; sd_bus_message *m = NULL; const char *subsysnqn = libnvmf_tid_get_subsysnqn(t); const char *transport = libnvmf_tid_get_transport(t); const char *traddr = libnvmf_tid_get_traddr(t); const char *trsvcid = libnvmf_tid_get_trsvcid(t); bool is_fc; int r; unit_name = tid_unit_name(t); if (!unit_name) return -ENOMEM; unit_devid_path(devid_path, sizeof(devid_path), unit_name); r = build_connect_argv(mgr->nvme_path, devid_path, t, params, is_nbft, &scratch); if (r < 0) goto out; snprintf(desc, sizeof(desc), "NVMe I/O Controller %s @ %s:%s", subsysnqn, traddr, trsvcid ? trsvcid : "none"); is_fc = shr_streq0(transport, "fc"); r = build_start_transient(mgr, unit_name, devid_path, desc, is_fc, scratch.argv, &m); if (r < 0) goto out; r = do_start_transient(mgr, m, unit_name); sd_bus_message_unref(m); out: free(unit_name); return r; } int unit_restart(struct unit_mgr *mgr, const char *unit_name) { sd_bus_error err = SD_BUS_ERROR_NULL; sd_bus_message *reply = NULL; const char *job_path; int r; r = sd_bus_call_method(mgr->bus, SYSTEMD_BUS_NAME, SYSTEMD_OBJ_PATH, SYSTEMD_MGR_IFACE, "RestartUnit", &err, &reply, "ss", unit_name, "replace"); if (r < 0) { disc_err("RestartUnit(%s): %s", unit_name, err.message ?: strerror(-r)); sd_bus_error_free(&err); return r; } r = sd_bus_message_read(reply, "o", &job_path); if (r >= 0) track_job(mgr, job_path, unit_name); sd_bus_message_unref(reply); return r < 0 ? r : 0; } int unit_stop(struct unit_mgr *mgr, const char *unit_name) { sd_bus_error err = SD_BUS_ERROR_NULL; int r; r = sd_bus_call_method(mgr->bus, SYSTEMD_BUS_NAME, SYSTEMD_OBJ_PATH, SYSTEMD_MGR_IFACE, "StopUnit", &err, NULL, "ss", unit_name, "replace"); if (r < 0) { disc_err("StopUnit(%s): %s", unit_name, err.message ?: strerror(-r)); sd_bus_error_free(&err); } return r; } int unit_reset_failed(struct unit_mgr *mgr, const char *unit_name) { sd_bus_error err = SD_BUS_ERROR_NULL; int r; r = sd_bus_call_method(mgr->bus, SYSTEMD_BUS_NAME, SYSTEMD_OBJ_PATH, SYSTEMD_MGR_IFACE, "ResetFailedUnit", &err, NULL, "s", unit_name); if (r < 0) { disc_err("ResetFailedUnit(%s): %s", unit_name, err.message ?: strerror(-r)); sd_bus_error_free(&err); } return r; }