]>
Commit | Line | Data |
---|---|---|
1761066b LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <getopt.h> | |
4 | #include <sys/file.h> | |
5 | ||
6 | #include "af-list.h" | |
7 | #include "alloc-util.h" | |
8 | #include "blockdev-util.h" | |
9 | #include "build.h" | |
10 | #include "daemon-util.h" | |
11 | #include "device-util.h" | |
12 | #include "fd-util.h" | |
13 | #include "fileio.h" | |
14 | #include "format-util.h" | |
15 | #include "fs-util.h" | |
abc19a6f | 16 | #include "id128-util.h" |
1761066b LP |
17 | #include "local-addresses.h" |
18 | #include "loop-util.h" | |
19 | #include "main-func.h" | |
abc19a6f | 20 | #include "os-util.h" |
1761066b LP |
21 | #include "parse-argument.h" |
22 | #include "path-util.h" | |
95d54802 | 23 | #include "plymouth-util.h" |
1761066b LP |
24 | #include "pretty-print.h" |
25 | #include "process-util.h" | |
26 | #include "random-util.h" | |
27 | #include "recurse-dir.h" | |
28 | #include "socket-util.h" | |
29 | #include "terminal-util.h" | |
30 | #include "udev-util.h" | |
31 | ||
32 | static char **arg_devices = NULL; | |
33 | static char *arg_nqn = NULL; | |
34 | static int arg_all = 0; | |
35 | ||
36 | STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep); | |
37 | STATIC_DESTRUCTOR_REGISTER(arg_nqn, freep); | |
38 | ||
39 | static int help(void) { | |
40 | _cleanup_free_ char *link = NULL; | |
41 | int r; | |
42 | ||
43 | r = terminal_urlify_man("systemd-storagetm", "8", &link); | |
44 | if (r < 0) | |
45 | return log_oom(); | |
46 | ||
47 | printf("%s [OPTIONS...] [DEVICE...]\n" | |
48 | "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n" | |
49 | " -h --help Show this help\n" | |
50 | " --version Show package version\n" | |
51 | " --nqn=STRING Select NQN (NVMe Qualified Name)\n" | |
52 | " -a --all Expose all devices\n" | |
53 | "\nSee the %s for details.\n", | |
54 | program_invocation_short_name, | |
55 | ansi_highlight(), | |
56 | ansi_normal(), | |
57 | link); | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | static int parse_argv(int argc, char *argv[]) { | |
63 | ||
64 | enum { | |
65 | ARG_NQN = 0x100, | |
66 | ARG_VERSION, | |
67 | }; | |
68 | ||
69 | static const struct option options[] = { | |
70 | { "help", no_argument, NULL, 'h' }, | |
71 | { "version", no_argument, NULL, ARG_VERSION }, | |
72 | { "nqn", required_argument, NULL, ARG_NQN }, | |
73 | { "all", no_argument, NULL, 'a' }, | |
74 | {} | |
75 | }; | |
76 | ||
77 | int r, c; | |
78 | ||
79 | assert(argc >= 0); | |
80 | assert(argv); | |
81 | ||
82 | while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) | |
83 | ||
84 | switch (c) { | |
85 | ||
86 | case 'h': | |
87 | return help(); | |
88 | ||
89 | case ARG_VERSION: | |
90 | return version(); | |
91 | ||
92 | case ARG_NQN: | |
93 | if (!filename_is_valid(optarg)) | |
94 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", optarg); | |
95 | ||
96 | if (free_and_strdup(&arg_nqn, optarg) < 0) | |
97 | return log_oom(); | |
98 | ||
99 | break; | |
100 | ||
101 | case 'a': | |
102 | arg_all++; | |
103 | break; | |
104 | ||
105 | case '?': | |
106 | return -EINVAL; | |
107 | ||
108 | default: | |
109 | assert_not_reached(); | |
110 | } | |
111 | ||
112 | if (arg_all > 0) { | |
113 | if (argc > optind) | |
114 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expects no further arguments if --all/-a is specified."); | |
115 | } else { | |
116 | if (optind >= argc) | |
117 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expecting device name or --all/-a."); | |
118 | ||
119 | for (int i = optind; i < argc; i++) | |
120 | if (!path_is_valid(argv[i])) | |
121 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", argv[i]); | |
122 | ||
123 | arg_devices = strv_copy(argv + optind); | |
124 | } | |
125 | ||
126 | if (!arg_nqn) { | |
127 | sd_id128_t id; | |
128 | ||
129 | r = sd_id128_get_machine_app_specific(SD_ID128_MAKE(b4,f9,4e,52,b8,e2,45,db,88,84,6e,2e,c3,f4,ef,18), &id); | |
130 | if (r < 0) | |
131 | return log_error_errno(r, "Failed to get machine ID: %m"); | |
132 | ||
133 | /* See NVM Express Base Specification 2.0c, 4.5 "NVMe Qualified Names" */ | |
134 | if (asprintf(&arg_nqn, "nqn.2023-10.io.systemd:storagetm." SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0) | |
135 | return log_oom(); | |
136 | } | |
137 | ||
138 | return 1; | |
139 | } | |
140 | ||
141 | typedef struct NvmeSubsystem { | |
142 | char *name; | |
143 | struct stat device_stat; | |
144 | int device_fd; | |
145 | int nvme_all_subsystems_fd; /* The /sys/kernel/config/nvmet/subsystems/ dir, that contains all subsystems */ | |
146 | int nvme_our_subsystem_fd; /* Our private subsystem dir below it. */ | |
147 | char *device; | |
148 | } NvmeSubsystem; | |
149 | ||
150 | static NvmeSubsystem* nvme_subsystem_free(NvmeSubsystem *s) { | |
151 | if (!s) | |
152 | return NULL; | |
153 | ||
154 | free(s->name); | |
155 | safe_close(s->nvme_all_subsystems_fd); | |
156 | safe_close(s->nvme_our_subsystem_fd); | |
157 | safe_close(s->device_fd); | |
158 | free(s->device); | |
159 | ||
160 | return mfree(s); | |
161 | } | |
162 | ||
163 | static int nvme_subsystem_unlink(NvmeSubsystem *s) { | |
164 | int r; | |
165 | ||
166 | assert(s); | |
167 | ||
168 | if (s->nvme_our_subsystem_fd >= 0) { | |
169 | _cleanup_close_ int namespaces_fd = -EBADF; | |
170 | ||
171 | namespaces_fd = openat(s->nvme_our_subsystem_fd, "namespaces", O_CLOEXEC|O_DIRECTORY|O_RDONLY); | |
172 | if (namespaces_fd < 0) | |
173 | log_warning_errno(errno, "Failed to open 'namespaces' directory of subsystem '%s': %m", s->name); | |
174 | else { | |
175 | _cleanup_free_ DirectoryEntries *de = NULL; | |
176 | ||
177 | r = readdir_all(namespaces_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); | |
178 | if (r < 0) | |
179 | log_warning_errno(r, "Failed to read 'namespaces' dir of subsystem '%s', ignoring: %m", s->name); | |
180 | else { | |
181 | FOREACH_ARRAY(ee, de->entries, de->n_entries) { | |
182 | _cleanup_free_ char *enable_fn = NULL; | |
183 | const struct dirent *e = *ee; | |
184 | ||
185 | enable_fn = path_join(e->d_name, "enable"); | |
186 | if (!enable_fn) | |
187 | return log_oom(); | |
188 | ||
189 | r = write_string_file_at(namespaces_fd, enable_fn, "0", WRITE_STRING_FILE_DISABLE_BUFFER); | |
190 | if (r < 0) | |
191 | log_warning_errno(r, "Failed to disable namespace '%s' of NVME subsystem '%s', ignoring: %m", e->d_name, s->name); | |
192 | ||
193 | if (unlinkat(namespaces_fd, e->d_name, AT_REMOVEDIR) < 0 && errno != ENOENT) | |
194 | log_warning_errno(errno, "Failed to remove namespace '%s' of NVME subsystem '%s', ignoring: %m", e->d_name, s->name); | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | s->nvme_our_subsystem_fd = safe_close(s->nvme_our_subsystem_fd); | |
200 | } | |
201 | ||
202 | if (s->nvme_all_subsystems_fd >= 0 && s->name) { | |
203 | if (unlinkat(s->nvme_all_subsystems_fd, s->name, AT_REMOVEDIR) < 0 && errno != ENOENT) | |
204 | log_warning_errno(errno, "Failed to remove NVME subsystem '%s', ignoring: %m", s->name); | |
205 | ||
206 | s->nvme_all_subsystems_fd = safe_close(s->nvme_all_subsystems_fd); /* Invalidate the subsystems/ dir fd, to remember we unlinked the thing already */ | |
207 | ||
208 | log_info("NVME subsystem '%s' removed.", s->name); | |
209 | } | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | static NvmeSubsystem *nvme_subsystem_destroy(NvmeSubsystem *s) { | |
215 | if (!s) | |
216 | return NULL; | |
217 | ||
218 | (void) nvme_subsystem_unlink(s); | |
219 | ||
220 | return nvme_subsystem_free(s); | |
221 | } | |
222 | ||
223 | DEFINE_TRIVIAL_CLEANUP_FUNC(NvmeSubsystem*, nvme_subsystem_destroy); | |
224 | ||
abc19a6f LP |
225 | static int nvme_subsystem_write_metadata(int subsystem_fd, sd_device *device) { |
226 | _cleanup_free_ char *image_id = NULL, *image_version = NULL, *os_id = NULL, *os_version = NULL, *combined_model = NULL, *synthetic_serial = NULL; | |
227 | const char *hwmodel = NULL, *hwserial = NULL, *w; | |
228 | int r; | |
229 | ||
230 | assert(subsystem_fd >= 0); | |
231 | ||
232 | (void) parse_os_release( | |
233 | /* root= */ NULL, | |
234 | "IMAGE_ID", &image_id, | |
235 | "IMAGE_VERSION", &image_version, | |
236 | "ID", &os_id, | |
237 | "VERSION_ID", &os_version); | |
238 | ||
239 | if (device) { | |
240 | (void) device_get_model_string(device, &hwmodel); | |
241 | (void) sd_device_get_property_value(device, "ID_SERIAL_SHORT", &hwserial); | |
242 | } | |
243 | ||
244 | w = secure_getenv("SYSTEMD_NVME_MODEL"); | |
245 | if (!w) { | |
246 | if (hwmodel && (image_id || os_id)) { | |
247 | if (asprintf(&combined_model, "%s (%s)", hwmodel, image_id ?: os_id) < 0) | |
248 | return log_oom(); | |
249 | w = combined_model; | |
250 | } else | |
251 | w = hwmodel ?: image_id ?: os_id; | |
252 | } | |
253 | if (w) { | |
254 | _cleanup_free_ char *truncated = strndup(w, 40); /* kernel refuses more than 40 chars (as per nvme spec) */ | |
255 | ||
256 | /* The default string stored in 'attr_model' is "Linux" btw. */ | |
257 | r = write_string_file_at(subsystem_fd, "attr_model", truncated, WRITE_STRING_FILE_DISABLE_BUFFER); | |
258 | if (r < 0) | |
259 | log_warning_errno(r, "Failed to set model of subsystem to '%s', ignoring: %m", w); | |
260 | } | |
261 | ||
262 | w = secure_getenv("SYSTEMD_NVME_FIRMWARE"); | |
263 | if (!w) | |
264 | w = image_version ?: os_version; | |
265 | if (w) { | |
266 | _cleanup_free_ char *truncated = strndup(w, 8); /* kernel refuses more than 8 chars (as per nvme spec) */ | |
267 | if (!truncated) | |
268 | return log_oom(); | |
269 | ||
270 | /* The default string stored in 'attr_firmware' is `uname -r` btw, but truncated to 8 chars. */ | |
271 | r = write_string_file_at(subsystem_fd, "attr_firmware", truncated, WRITE_STRING_FILE_DISABLE_BUFFER); | |
272 | if (r < 0) | |
273 | log_warning_errno(r, "Failed to set model of subsystem to '%s', ignoring: %m", truncated); | |
274 | } | |
275 | ||
276 | w = secure_getenv("SYSTEMD_NVME_SERIAL"); | |
277 | if (!w) { | |
278 | if (hwserial) | |
279 | w = hwserial; | |
280 | else { | |
281 | sd_id128_t mid; | |
282 | ||
283 | r = sd_id128_get_machine_app_specific(SD_ID128_MAKE(39,7f,4d,bf,1e,bf,46,6d,b3,cb,45,b8,0d,49,5b,c1), &mid); | |
284 | if (r < 0) | |
285 | log_warning_errno(r, "Failed to get machine ID, ignoring: %m"); | |
286 | else { | |
287 | if (asprintf(&synthetic_serial, SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(mid)) < 0) | |
288 | return log_oom(); | |
289 | w = synthetic_serial; | |
290 | } | |
291 | } | |
292 | } | |
293 | if (w) { | |
294 | _cleanup_free_ char *truncated = strndup(w, 20); /* kernel refuses more than 20 chars (as per nvme spec) */ | |
295 | if (!truncated) | |
296 | return log_oom(); | |
297 | ||
298 | r = write_string_file_at(subsystem_fd, "attr_serial", truncated, WRITE_STRING_FILE_DISABLE_BUFFER); | |
299 | if (r < 0) | |
300 | log_warning_errno(r, "Failed to set serial of subsystem to '%s', ignoring: %m", truncated); | |
301 | } | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
306 | static int nvme_namespace_write_metadata(int namespace_fd, sd_device *device, const char *node) { | |
307 | sd_id128_t id = SD_ID128_NULL; | |
308 | const char *e; | |
309 | int r; | |
310 | ||
311 | assert(namespace_fd >= 0); | |
312 | ||
313 | e = secure_getenv("SYSTEMD_NVME_UUID"); | |
314 | if (e) { | |
315 | r = sd_id128_from_string(e, &id); | |
316 | if (r < 0) | |
317 | log_warning_errno(r, "Failed to parse $SYSTEMD_NVME_UUID, ignoring: %s", e); | |
318 | } | |
319 | ||
320 | if (sd_id128_is_null(id)) { | |
321 | const char *serial = NULL; | |
322 | sd_id128_t mid = SD_ID128_NULL; | |
323 | ||
324 | /* We combine machine ID and ID_SERIAL and hash a UUID from it */ | |
325 | ||
326 | if (device) { | |
327 | (void) sd_device_get_property_value(device, "ID_SERIAL", &serial); | |
328 | if (!serial) | |
921961c3 YW |
329 | (void) sd_device_get_devname(device, &serial); |
330 | } | |
331 | if (!serial) | |
abc19a6f LP |
332 | serial = node; |
333 | ||
334 | r = sd_id128_get_machine(&mid); | |
335 | if (r < 0) | |
336 | log_warning_errno(r, "Failed to get machine ID, ignoring: %m"); | |
337 | ||
338 | size_t l = sizeof(mid) + strlen_ptr(serial); | |
339 | _cleanup_free_ void *j = malloc(l + 1); | |
340 | if (!j) | |
341 | return log_oom(); | |
342 | ||
343 | strcpy(mempcpy(j, &mid, sizeof(mid)), strempty(serial)); | |
344 | ||
345 | id = id128_digest(j, l); | |
346 | } | |
347 | ||
348 | r = write_string_file_at(namespace_fd, "device_uuid", SD_ID128_TO_UUID_STRING(id), WRITE_STRING_FILE_DISABLE_BUFFER); | |
349 | if (r < 0) | |
350 | log_warning_errno(r, "Failed to set uuid of namespace to '%s', ignoring: %m", SD_ID128_TO_UUID_STRING(id)); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
355 | static int nvme_subsystem_add(const char *node, int consumed_fd, sd_device *device, NvmeSubsystem **ret) { | |
356 | _cleanup_(sd_device_unrefp) sd_device *allocated_device = NULL; | |
1761066b LP |
357 | _cleanup_close_ int fd = consumed_fd; /* always take possession of the fd */ |
358 | int r; | |
359 | ||
360 | assert(node); | |
361 | assert(ret); | |
362 | ||
363 | _cleanup_free_ char *fname = NULL; | |
364 | r = path_extract_filename(node, &fname); | |
365 | if (r < 0) | |
366 | return log_error_errno(r, "Failed to extract file name from path: %s", node); | |
367 | ||
368 | _cleanup_free_ char *j = NULL; | |
369 | j = strjoin(arg_nqn, ".", fname); | |
370 | if (!j) | |
371 | return log_oom(); | |
372 | ||
373 | if (fd < 0) { | |
374 | fd = RET_NERRNO(open(node, O_RDONLY|O_CLOEXEC|O_NONBLOCK)); | |
375 | if (fd < 0) | |
376 | return log_error_errno(fd, "Failed to open '%s': %m", node); | |
377 | } | |
378 | ||
379 | struct stat st; | |
380 | if (fstat(fd, &st) < 0) | |
381 | return log_error_errno(errno, "Failed to fstat '%s': %m", node); | |
abc19a6f LP |
382 | if (S_ISBLK(st.st_mode)) { |
383 | if (!device) { | |
384 | r = sd_device_new_from_devnum(&allocated_device, 'b', st.st_dev); | |
385 | if (r < 0) | |
386 | return log_error_errno(r, "Failed to get device information for device '%s': %m", node); | |
387 | ||
388 | device = allocated_device; | |
389 | } | |
390 | } else { | |
1761066b LP |
391 | r = stat_verify_regular(&st); |
392 | if (r < 0) | |
393 | return log_error_errno(r, "Not a block device or regular file, refusing: %s", node); | |
394 | } | |
395 | ||
396 | /* Let's lock this device continuously while we are operating on it */ | |
397 | r = lock_generic_with_timeout(fd, LOCK_BSD, LOCK_EX, 10 * USEC_PER_SEC); | |
398 | if (r < 0) | |
399 | return log_error_errno(r, "Failed to lock block device: %m"); | |
400 | ||
401 | _cleanup_close_ int subsystems_fd = -EBADF; | |
402 | subsystems_fd = RET_NERRNO(open("/sys/kernel/config/nvmet/subsystems", O_DIRECTORY|O_CLOEXEC|O_RDONLY)); | |
403 | if (subsystems_fd < 0) | |
404 | return log_error_errno(subsystems_fd, "Failed to open /sys/kernel/config/nvmet/subsystems: %m"); | |
405 | ||
406 | _cleanup_close_ int subsystem_fd = -EBADF; | |
407 | subsystem_fd = open_mkdir_at(subsystems_fd, j, O_EXCL|O_RDONLY|O_CLOEXEC, 0777); | |
408 | if (subsystem_fd < 0) | |
409 | return log_error_errno(subsystem_fd, "Failed to create NVME subsystem '%s': %m", j); | |
410 | ||
411 | r = write_string_file_at(subsystem_fd, "attr_allow_any_host", "1", WRITE_STRING_FILE_DISABLE_BUFFER); | |
412 | if (r < 0) | |
413 | return log_error_errno(r, "Failed to set 'attr_allow_any_host' flag: %m"); | |
414 | ||
abc19a6f LP |
415 | (void) nvme_subsystem_write_metadata(subsystem_fd, device); |
416 | ||
1761066b LP |
417 | _cleanup_close_ int namespace_fd = -EBADF; |
418 | namespace_fd = open_mkdir_at(subsystem_fd, "namespaces/1", O_EXCL|O_RDONLY|O_CLOEXEC, 0777); | |
419 | if (namespace_fd < 0) | |
420 | return log_error_errno(namespace_fd, "Failed to create NVME namespace '1': %m"); | |
421 | ||
abc19a6f LP |
422 | (void) nvme_namespace_write_metadata(namespace_fd, device, node); |
423 | ||
1761066b LP |
424 | /* We use /proc/$PID/fd/$FD rather than /proc/self/fd/$FD, because this string is visible to others |
425 | * via configfs, and by including the PID it's clear to who the stuff belongs. */ | |
426 | r = write_string_file_at(namespace_fd, "device_path", FORMAT_PROC_PID_FD_PATH(0, fd), WRITE_STRING_FILE_DISABLE_BUFFER); | |
427 | if (r < 0) | |
428 | return log_error_errno(r, "Failed to write 'device_path' attribute: %m"); | |
429 | ||
430 | r = write_string_file_at(namespace_fd, "enable", "1", WRITE_STRING_FILE_DISABLE_BUFFER); | |
431 | if (r < 0) | |
432 | return log_error_errno(r, "Failed to write 'enable' attribute: %m"); | |
433 | ||
434 | _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *subsys = NULL; | |
435 | ||
436 | subsys = new(NvmeSubsystem, 1); | |
437 | if (!subsys) | |
438 | return log_oom(); | |
439 | ||
440 | *subsys = (NvmeSubsystem) { | |
441 | .name = TAKE_PTR(j), | |
442 | .device_fd = TAKE_FD(fd), | |
443 | .nvme_all_subsystems_fd = TAKE_FD(subsystems_fd), | |
444 | .nvme_our_subsystem_fd = TAKE_FD(subsystem_fd), | |
445 | .device_stat = st, | |
446 | }; | |
447 | ||
448 | subsys->device = strdup(node); | |
449 | if (!subsys->device) | |
450 | return log_oom(); | |
451 | ||
452 | *ret = TAKE_PTR(subsys); | |
453 | return 0; | |
454 | } | |
455 | ||
456 | typedef struct NvmePort { | |
457 | uint16_t portnr; /* used for both the IP and the NVME port numer */ | |
458 | ||
459 | int nvme_port_fd; | |
460 | int nvme_ports_fd; | |
461 | ||
462 | int ip_family; | |
463 | } NvmePort; | |
464 | ||
465 | static NvmePort *nvme_port_free(NvmePort *p) { | |
466 | if (!p) | |
467 | return NULL; | |
468 | ||
469 | safe_close(p->nvme_port_fd); | |
470 | safe_close(p->nvme_ports_fd); | |
471 | ||
472 | return mfree(p); | |
473 | } | |
474 | ||
475 | static int nvme_port_unlink(NvmePort *p) { | |
476 | int r, ret = 0; | |
477 | ||
478 | assert(p); | |
479 | ||
480 | if (p->nvme_port_fd >= 0) { | |
481 | _cleanup_close_ int subsystems_dir_fd = -EBADF; | |
482 | ||
483 | subsystems_dir_fd = openat(p->nvme_port_fd, "subsystems", O_DIRECTORY|O_RDONLY|O_CLOEXEC); | |
484 | if (subsystems_dir_fd < 0) | |
485 | log_warning_errno(errno, "Failed to open 'subsystems' dir of port %" PRIu16 ", ignoring: %m", p->portnr); | |
486 | else { | |
487 | _cleanup_free_ DirectoryEntries *de = NULL; | |
488 | ||
489 | r = readdir_all(subsystems_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); | |
490 | if (r < 0) | |
491 | log_warning_errno(r, "Failed to read 'subsystems' dir of port %" PRIu16 ", ignoring: %m", p->portnr); | |
492 | else | |
493 | FOREACH_ARRAY(ee, de->entries, de->n_entries) { | |
494 | const struct dirent *e = *ee; | |
495 | ||
496 | if (unlinkat(subsystems_dir_fd, e->d_name, 0) < 0 && errno != ENOENT) | |
497 | log_warning_errno(errno, "Failed to remove 'subsystems' symlink '%s' of port %" PRIu16 ", ignoring: %m", e->d_name, p->portnr); | |
498 | } | |
499 | } | |
500 | ||
501 | p->nvme_port_fd = safe_close(p->nvme_port_fd); | |
502 | } | |
503 | ||
504 | if (p->nvme_ports_fd >= 0) { | |
505 | _cleanup_free_ char *fn = NULL; | |
506 | if (asprintf(&fn, "%" PRIu16, p->portnr) < 0) | |
507 | return log_oom(); | |
508 | ||
509 | if (unlinkat(p->nvme_ports_fd, fn, AT_REMOVEDIR) < 0) { | |
510 | if (errno == ENOENT) | |
511 | ret = 0; | |
512 | else | |
513 | ret = log_warning_errno(errno, "Failed to remove port '%" PRIu16 ", ignoring: %m", p->portnr); | |
514 | } else | |
515 | ret = 1; | |
516 | ||
517 | p->nvme_ports_fd = safe_close(p->nvme_ports_fd); | |
518 | } | |
519 | ||
520 | return ret; | |
521 | } | |
522 | ||
523 | static NvmePort *nvme_port_destroy(NvmePort *p) { | |
524 | if (!p) | |
525 | return NULL; | |
526 | ||
527 | (void) nvme_port_unlink(p); | |
528 | ||
529 | return nvme_port_free(p); | |
530 | } | |
531 | ||
532 | DEFINE_TRIVIAL_CLEANUP_FUNC(NvmePort*, nvme_port_destroy); | |
533 | ||
534 | static int nvme_port_add_portnr( | |
535 | int ports_fd, | |
536 | uint16_t portnr, | |
537 | int ip_family, | |
538 | int *ret_fd) { | |
539 | ||
540 | int r; | |
541 | ||
542 | assert(ports_fd >= 0); | |
543 | assert(IN_SET(ip_family, AF_INET, AF_INET6)); | |
544 | assert(ret_fd); | |
545 | ||
546 | _cleanup_free_ char *fname = NULL; | |
547 | if (asprintf(&fname, "%" PRIu16, portnr) < 0) | |
548 | return log_oom(); | |
549 | ||
550 | _cleanup_close_ int port_fd = -EBADF; | |
551 | port_fd = open_mkdir_at(ports_fd, fname, O_EXCL|O_RDONLY|O_CLOEXEC, 0777); | |
552 | if (port_fd < 0) { | |
553 | if (port_fd != -EEXIST) | |
554 | return log_error_errno(port_fd, "Failed to create port %" PRIu16 ": %m", portnr); | |
555 | ||
556 | *ret_fd = -EBADF; | |
557 | return 0; | |
558 | } | |
559 | ||
560 | r = write_string_file_at(port_fd, "addr_adrfam", af_to_ipv4_ipv6(ip_family), WRITE_STRING_FILE_DISABLE_BUFFER); | |
561 | if (r < 0) | |
562 | return log_error_errno(r, "Failed to set address family on NVME port %" PRIu16 ": %m", portnr); | |
563 | ||
564 | r = write_string_file_at(port_fd, "addr_trtype", "tcp", WRITE_STRING_FILE_DISABLE_BUFFER); | |
565 | if (r < 0) | |
566 | return log_error_errno(r, "Failed to set transport type on NVME port %" PRIu16 ": %m", portnr); | |
567 | ||
568 | r = write_string_file_at(port_fd, "addr_trsvcid", fname, WRITE_STRING_FILE_DISABLE_BUFFER); | |
569 | if (r < 0) | |
570 | return log_error_errno(r, "Failed to set IP port on NVME port %" PRIu16 ": %m", portnr); | |
571 | ||
572 | r = write_string_file_at(port_fd, "addr_traddr", ip_family == AF_INET6 ? "::" : "0.0.0.0", WRITE_STRING_FILE_DISABLE_BUFFER); | |
573 | if (r < 0) | |
574 | return log_error_errno(r, "Failed to set IP address on NVME port %" PRIu16 ": %m", portnr); | |
575 | ||
576 | *ret_fd = TAKE_FD(port_fd); | |
577 | return 1; | |
578 | } | |
579 | ||
580 | static uint16_t calculate_start_port(const char *name, int ip_family) { | |
581 | struct siphash state; | |
582 | uint16_t nr; | |
583 | ||
584 | assert(name); | |
585 | assert(IN_SET(ip_family, AF_INET, AF_INET6)); | |
586 | ||
587 | /* Use some fixed key Lennart pulled from /dev/urandom, so that we are deterministic */ | |
588 | siphash24_init(&state, SD_ID128_MAKE(d1,0b,67,b5,e2,b7,4a,91,8d,6b,27,b6,35,c1,9f,d9).bytes); | |
589 | siphash24_compress_string(name, &state); | |
c01a5c05 | 590 | siphash24_compress_typesafe(ip_family, &state); |
1761066b LP |
591 | |
592 | nr = 1024U + siphash24_finalize(&state) % (0xFFFFU - 1024U); | |
593 | SET_FLAG(nr, 1, ip_family == AF_INET6); /* Lowest bit reflects family */ | |
594 | ||
595 | return nr; | |
596 | } | |
597 | ||
598 | static uint16_t calculate_next_port(int ip_family) { | |
599 | uint16_t nr; | |
600 | ||
601 | assert(IN_SET(ip_family, AF_INET, AF_INET6)); | |
602 | ||
603 | nr = 1024U + random_u64_range(0xFFFFU - 1024U); | |
604 | SET_FLAG(nr, 1, ip_family == AF_INET6); /* Lowest bit reflects family */ | |
605 | ||
606 | return nr; | |
607 | } | |
608 | ||
609 | static int nvme_port_add(const char *name, int ip_family, NvmePort **ret) { | |
610 | int r; | |
611 | ||
612 | assert(name); | |
613 | assert(IN_SET(ip_family, AF_INET, AF_INET6)); | |
614 | assert(ret); | |
615 | ||
616 | _cleanup_close_ int ports_fd = -EBADF; | |
617 | ports_fd = RET_NERRNO(open("/sys/kernel/config/nvmet/ports", O_DIRECTORY|O_RDONLY|O_CLOEXEC)); | |
618 | if (ports_fd < 0) | |
619 | return log_error_errno(ports_fd, "Failed to open /sys/kernel/config/nvmet/ports: %m"); | |
620 | ||
621 | _cleanup_close_ int port_fd = -EBADF; | |
622 | uint16_t portnr = calculate_start_port(name, ip_family); | |
623 | for (unsigned attempt = 0;; attempt++) { | |
624 | r = nvme_port_add_portnr(ports_fd, portnr, ip_family, &port_fd); | |
625 | if (r < 0) | |
626 | return r; | |
627 | if (r > 0) | |
628 | break; | |
629 | ||
630 | if (attempt > 16) | |
631 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find free NVME port after %u attempts.", attempt); | |
632 | ||
633 | log_debug_errno(port_fd, "NVME port %" PRIu16 " exists already, randomizing port.", portnr); | |
634 | ||
635 | portnr = calculate_next_port(ip_family); | |
636 | } | |
637 | ||
638 | _cleanup_(nvme_port_destroyp) NvmePort *p = new(NvmePort, 1); | |
639 | if (!p) | |
640 | return log_oom(); | |
641 | ||
642 | *p = (NvmePort) { | |
643 | .portnr = portnr, | |
644 | .nvme_ports_fd = TAKE_FD(ports_fd), | |
645 | .nvme_port_fd = TAKE_FD(port_fd), | |
646 | .ip_family = ip_family, | |
647 | }; | |
648 | ||
649 | *ret = TAKE_PTR(p); | |
650 | return 0; | |
651 | } | |
652 | ||
653 | static int nvme_port_link_subsystem(NvmePort *port, NvmeSubsystem *subsys) { | |
654 | assert(port); | |
655 | assert(subsys); | |
656 | ||
657 | _cleanup_free_ char *target = NULL, *linkname = NULL; | |
658 | target = path_join("/sys/kernel/config/nvmet/subsystems", subsys->name); | |
659 | if (!target) | |
660 | return log_oom(); | |
661 | ||
662 | linkname = path_join("subsystems", subsys->name); | |
663 | if (!linkname) | |
664 | return log_oom(); | |
665 | ||
666 | if (symlinkat(target, port->nvme_port_fd, linkname) < 0) | |
667 | return log_error_errno(errno, "Failed to link subsystem '%s' to port %" PRIu16 ": %m", subsys->name, port->portnr); | |
668 | ||
669 | return 0; | |
670 | } | |
671 | ||
672 | static int nvme_port_unlink_subsystem(NvmePort *port, NvmeSubsystem *subsys) { | |
673 | assert(port); | |
674 | assert(subsys); | |
675 | ||
676 | _cleanup_free_ char *linkname = NULL; | |
677 | linkname = path_join("subsystems", subsys->name); | |
678 | if (!linkname) | |
679 | return log_oom(); | |
680 | ||
681 | if (unlinkat(port->nvme_port_fd, linkname, 0) < 0 && errno != ENOENT) | |
682 | return log_error_errno(errno, "Failed to unlink subsystem '%s' to port %" PRIu16 ": %m", subsys->name, port->portnr); | |
683 | ||
684 | return 0; | |
685 | } | |
686 | ||
687 | static int nvme_subsystem_report(NvmeSubsystem *subsystem, NvmePort *ipv4, NvmePort *ipv6) { | |
688 | assert(subsystem); | |
689 | ||
690 | _cleanup_free_ struct local_address *addresses = NULL; | |
691 | int n_addresses; | |
692 | n_addresses = local_addresses(NULL, 0, AF_UNSPEC, &addresses); | |
693 | if (n_addresses < 0) | |
694 | return log_error_errno(n_addresses, "Failed to determine local IP addresses: %m"); | |
695 | ||
696 | log_notice("NVMe-TCP: %s %s%s%s (%s)", | |
697 | special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), | |
698 | emoji_enabled() ? special_glyph(SPECIAL_GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "", | |
699 | subsystem->name, subsystem->device); | |
700 | ||
701 | FOREACH_ARRAY(a, addresses, n_addresses) { | |
702 | NvmePort *port = a->family == AF_INET ? ipv4 : ipv6; | |
703 | ||
704 | if (!port) | |
705 | continue; | |
706 | ||
707 | log_info(" %s Try for specific device: nvme connect -t tcp -n '%s' -a %s -s %" PRIu16, | |
708 | special_glyph(a >= addresses + (n_addresses - 1) ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), | |
709 | subsystem->name, | |
710 | IN_ADDR_TO_STRING(a->family, &a->address), | |
711 | port->portnr); | |
712 | } | |
713 | ||
714 | return 0; | |
715 | } | |
716 | ||
95d54802 LP |
717 | static int plymouth_send_text(const char *text) { |
718 | _cleanup_free_ char *plymouth_message = NULL; | |
719 | int c, r; | |
720 | ||
721 | assert(text); | |
722 | ||
723 | c = asprintf(&plymouth_message, | |
724 | "M\x02%c%s%c" | |
725 | "A%c", /* pause spinner */ | |
726 | (int) strlen(text) + 1, text, '\x00', | |
727 | '\x00'); | |
728 | if (c < 0) | |
729 | return log_oom(); | |
730 | ||
731 | r = plymouth_send_raw(plymouth_message, c, SOCK_NONBLOCK); | |
732 | if (r < 0) | |
733 | return log_full_errno(ERRNO_IS_NO_PLYMOUTH(r) ? LOG_DEBUG : LOG_WARNING, r, | |
734 | "Failed to communicate with plymouth, ignoring: %m"); | |
735 | ||
736 | return 0; | |
737 | } | |
738 | ||
739 | static int plymouth_notify_port(NvmePort *port, struct local_address *a) { | |
740 | _cleanup_free_ char *m = NULL; | |
741 | ||
742 | if (!port || !a) | |
743 | return 0; | |
744 | ||
745 | if (asprintf(&m, "nvme connect-all -t tcp -a %s -s %" PRIu16, IN_ADDR_TO_STRING(a->family, &a->address), port->portnr) < 0) | |
746 | return log_oom(); | |
747 | ||
748 | return plymouth_send_text(m); | |
749 | } | |
750 | ||
751 | static int nvme_port_report(NvmePort *port, bool *plymouth_done) { | |
1761066b LP |
752 | if (!port) |
753 | return 0; | |
754 | ||
755 | _cleanup_free_ struct local_address *addresses = NULL; | |
756 | int n_addresses; | |
757 | n_addresses = local_addresses(NULL, 0, port->ip_family, &addresses); | |
758 | if (n_addresses < 0) | |
759 | return log_error_errno(n_addresses, "Failed to determine local IP addresses: %m"); | |
760 | ||
761 | log_notice("NVMe-TCP: %s %s%sListening on %s (port %" PRIu16 ")", | |
762 | special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), | |
763 | emoji_enabled() ? special_glyph(SPECIAL_GLYPH_WORLD) : "", emoji_enabled() ? " " : "", | |
764 | af_to_ipv4_ipv6(port->ip_family), | |
765 | port->portnr); | |
766 | ||
767 | FOREACH_ARRAY(a, addresses, n_addresses) | |
768 | log_info(" %s Try for all devices: nvme connect-all -t tcp -a %s -s %" PRIu16, | |
769 | special_glyph(a >= addresses + (n_addresses - 1) ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), | |
770 | IN_ADDR_TO_STRING(a->family, &a->address), | |
771 | port->portnr); | |
772 | ||
95d54802 LP |
773 | if (plymouth_done && !*plymouth_done) { |
774 | (void) plymouth_notify_port(port, n_addresses > 0 ? addresses : NULL); | |
775 | *plymouth_done = n_addresses > 0; | |
776 | } | |
777 | ||
1761066b LP |
778 | return 0; |
779 | } | |
780 | ||
781 | typedef struct Context { | |
782 | Hashmap *subsystems; | |
783 | NvmePort *ipv4_port, *ipv6_port; | |
784 | ||
785 | bool display_refresh_scheduled; | |
786 | } Context; | |
787 | ||
788 | static void device_hash_func(const struct stat *q, struct siphash *state) { | |
789 | assert(q); | |
790 | ||
791 | if (S_ISBLK(q->st_mode) || S_ISCHR(q->st_mode)) { | |
792 | mode_t m = q->st_mode & S_IFMT; | |
c01a5c05 YW |
793 | siphash24_compress_typesafe(m, state); |
794 | siphash24_compress_typesafe(q->st_rdev, state); | |
1761066b LP |
795 | return; |
796 | } | |
797 | ||
798 | return inode_hash_func(q, state); | |
799 | } | |
800 | ||
801 | static int device_compare_func(const struct stat *a, const struct stat *b) { | |
802 | int r; | |
803 | ||
804 | assert(a); | |
805 | assert(b); | |
806 | ||
807 | r = CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT); | |
808 | if (r != 0) | |
809 | return r; | |
810 | ||
811 | if (S_ISBLK(a->st_mode) || S_ISCHR(a->st_mode)) { | |
812 | r = CMP(major(a->st_rdev), major(b->st_rdev)); | |
813 | if (r != 0) | |
814 | return r; | |
815 | ||
816 | r = CMP(minor(a->st_rdev), minor(b->st_rdev)); | |
817 | if (r != 0) | |
818 | return r; | |
819 | ||
820 | return 0; | |
821 | } | |
822 | ||
823 | return inode_compare_func(a, b); | |
824 | } | |
825 | ||
826 | DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( | |
827 | nvme_subsystem_hash_ops, | |
828 | struct stat, | |
829 | device_hash_func, | |
830 | device_compare_func, | |
831 | NvmeSubsystem, | |
832 | nvme_subsystem_destroy); | |
833 | ||
834 | static void context_done(Context *c) { | |
835 | assert(c); | |
836 | ||
837 | c->ipv4_port = nvme_port_destroy(c->ipv4_port); | |
838 | c->ipv6_port = nvme_port_destroy(c->ipv6_port); | |
839 | ||
840 | c->subsystems = hashmap_free(c->subsystems); | |
841 | } | |
842 | ||
843 | static void device_track_back(sd_device *d, sd_device **ret) { | |
844 | int r; | |
845 | ||
846 | assert(d); | |
847 | assert(ret); | |
848 | ||
849 | const char *devname = NULL; | |
850 | (void) sd_device_get_devname(d, &devname); | |
851 | ||
852 | _cleanup_(sd_device_unrefp) sd_device *d_originating = NULL; | |
853 | r = block_device_get_originating(d, &d_originating); | |
854 | if (r < 0) | |
855 | log_device_debug_errno(d, r, "Failed to get originating device for '%s', ignoring: %m", strna(devname)); | |
856 | ||
857 | sd_device *d_whole = NULL; | |
858 | r = block_device_get_whole_disk(d_originating ?: d, &d_whole); /* does not ref returned device */ | |
859 | if (r < 0) | |
860 | log_device_debug_errno(d, r, "Failed to get whole device for '%s', ignoring: %m", strna(devname)); | |
861 | ||
862 | *ret = d_whole ? sd_device_ref(d_whole) : d_originating ? TAKE_PTR(d_originating) : sd_device_ref(d); | |
863 | } | |
864 | ||
865 | static int device_is_same(sd_device *a, sd_device *b) { | |
866 | dev_t devnum_a, devnum_b; | |
867 | int r; | |
868 | ||
869 | assert(a); | |
870 | assert(b); | |
871 | ||
872 | r = sd_device_get_devnum(a, &devnum_a); | |
873 | if (r < 0) | |
874 | return r; | |
875 | ||
876 | r = sd_device_get_devnum(b, &devnum_b); | |
877 | if (r < 0) | |
878 | return r; | |
879 | ||
880 | return devnum_a == devnum_b; | |
881 | } | |
882 | ||
883 | static bool device_is_allowed(sd_device *d) { | |
884 | int r; | |
885 | ||
886 | assert(d); | |
887 | ||
888 | if (arg_all >= 2) /* If --all is specified twice we allow even the root fs to shared */ | |
889 | return true; | |
890 | ||
891 | const char *devname; | |
892 | r = sd_device_get_devname(d, &devname); | |
893 | if (r < 0) | |
894 | return log_device_error_errno(d, r, "Failed to get device name: %m"); | |
895 | ||
896 | dev_t root_devnum; | |
897 | r = get_block_device("/", &root_devnum); | |
898 | if (r < 0) { | |
899 | log_warning_errno(r, "Failed to get backing device of the root file system: %m"); | |
900 | return false; /* Better safe */ | |
901 | } | |
902 | if (root_devnum == 0) /* Not backed by a block device? */ | |
903 | return true; | |
904 | ||
905 | _cleanup_(sd_device_unrefp) sd_device *root_device = NULL; | |
906 | r = sd_device_new_from_devnum(&root_device, 'b', root_devnum); | |
907 | if (r < 0) { | |
908 | log_warning_errno(r, "Failed to get root block device, assuming device '%s' is same as root device: %m", devname); | |
909 | return false; | |
910 | } | |
911 | ||
912 | _cleanup_(sd_device_unrefp) sd_device *whole_root_device = NULL; | |
913 | device_track_back(root_device, &whole_root_device); | |
914 | ||
915 | _cleanup_(sd_device_unrefp) sd_device *whole_d = NULL; | |
916 | device_track_back(d, &whole_d); | |
917 | ||
918 | r = device_is_same(whole_root_device, whole_d); | |
919 | if (r < 0) { | |
920 | log_warning_errno(r, "Failed to determine if root device and device '%s' are the same, assuming they are: %m", devname); | |
921 | return false; /* Better safe */ | |
922 | } | |
923 | ||
924 | return !r; | |
925 | } | |
926 | ||
927 | static int device_added(Context *c, sd_device *device) { | |
928 | _cleanup_close_ int fd = -EBADF; | |
929 | int r; | |
930 | ||
931 | assert(c); | |
932 | assert(device); | |
933 | ||
934 | const char *sysname; | |
935 | r = sd_device_get_sysname(device, &sysname); | |
936 | if (r < 0) | |
937 | return log_device_error_errno(device, r, "Failed to get device name: %m"); | |
938 | ||
939 | log_device_debug(device, "new block device '%s'", sysname); | |
940 | ||
941 | if (STARTSWITH_SET(sysname, "loop", "zram")) /* Ignore some devices */ | |
942 | return 0; | |
943 | ||
944 | const char *devname; | |
945 | r = sd_device_get_devname(device, &devname); | |
946 | if (r < 0) | |
947 | return log_device_error_errno(device, r, "Failed to get device node path: %m"); | |
948 | ||
949 | struct stat lookup_key = { | |
950 | .st_mode = S_IFBLK, | |
951 | }; | |
952 | ||
953 | r = sd_device_get_devnum(device, &lookup_key.st_rdev); | |
954 | if (r < 0) | |
955 | return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); | |
956 | ||
957 | if (hashmap_contains(c->subsystems, &lookup_key)) { | |
958 | log_debug("Device '%s' already seen.", devname); | |
959 | return 0; | |
960 | } | |
961 | ||
962 | if (!device_is_allowed(device)) { | |
963 | log_device_debug(device, "Not exposing device '%s', as it is backed by root disk.", devname); | |
964 | return 0; | |
965 | } | |
966 | ||
967 | fd = sd_device_open(device, O_RDONLY|O_CLOEXEC|O_NONBLOCK); | |
968 | if (fd < 0) { | |
969 | log_device_warning_errno(device, fd, "Failed to open newly acquired device '%s', ignoring device: %m", devname); | |
970 | return 0; | |
971 | } | |
972 | ||
973 | _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *s = NULL; | |
abc19a6f | 974 | r = nvme_subsystem_add(devname, TAKE_FD(fd), device, &s); |
1761066b LP |
975 | if (r < 0) |
976 | return r; | |
977 | ||
978 | if (c->ipv4_port) { | |
979 | r = nvme_port_link_subsystem(c->ipv4_port, s); | |
980 | if (r < 0) | |
981 | return r; | |
982 | } | |
983 | ||
984 | if (c->ipv6_port) { | |
985 | r = nvme_port_link_subsystem(c->ipv6_port, s); | |
986 | if (r < 0) | |
987 | return r; | |
988 | } | |
989 | ||
990 | r = hashmap_ensure_put(&c->subsystems, &nvme_subsystem_hash_ops, &s->device_stat, s); | |
991 | if (r < 0) | |
992 | return log_error_errno(r, "Failed to add subsystem to hash table: %m"); | |
993 | ||
994 | (void) nvme_subsystem_report(s, c->ipv4_port, c->ipv6_port); | |
995 | ||
996 | TAKE_PTR(s); | |
997 | return 1; | |
998 | } | |
999 | ||
1000 | static int device_removed(Context *c, sd_device *device) { | |
1001 | int r; | |
1002 | ||
1003 | assert(device); | |
1004 | ||
1005 | struct stat lookup_key = { | |
1006 | .st_mode = S_IFBLK, | |
1007 | }; | |
1008 | ||
1009 | r = sd_device_get_devnum(device, &lookup_key.st_rdev); | |
1010 | if (r < 0) | |
1011 | return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); | |
1012 | ||
1013 | NvmeSubsystem *s = hashmap_remove(c->subsystems, &lookup_key); | |
1014 | if (!s) | |
1015 | return 0; | |
1016 | ||
1017 | log_device_debug(device, "removed block device '%s'", s->name); | |
1018 | ||
1019 | if (c->ipv4_port) | |
1020 | (void) nvme_port_unlink_subsystem(c->ipv4_port, s); | |
1021 | if (c->ipv6_port) | |
1022 | (void) nvme_port_unlink_subsystem(c->ipv6_port, s); | |
1023 | ||
1024 | s = nvme_subsystem_destroy(s); | |
1025 | return 1; | |
1026 | } | |
1027 | ||
1028 | static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { | |
1029 | Context *c = ASSERT_PTR(userdata); | |
1030 | ||
1031 | if (device_for_action(device, SD_DEVICE_REMOVE)) | |
1032 | device_removed(c, device); | |
1033 | else | |
1034 | device_added(c, device); | |
1035 | ||
1036 | return 0; | |
1037 | } | |
1038 | ||
1039 | static int on_display_refresh(sd_event_source *s, uint64_t usec, void *userdata) { | |
1040 | Context *c = ASSERT_PTR(userdata); | |
1041 | ||
1042 | assert(s); | |
1043 | ||
1044 | c->display_refresh_scheduled = false; | |
1045 | ||
dd9c8da8 | 1046 | if (isatty(STDERR_FILENO)) |
1761066b LP |
1047 | fputs(ANSI_HOME_CLEAR, stderr); |
1048 | ||
95d54802 LP |
1049 | /* If we have both IPv4 and IPv6, we display IPv4 info via Plymouth, since it doesn't have much |
1050 | * space, and IPv4 is simply shorter (and easy to type off screen) */ | |
1051 | ||
1052 | bool plymouth_done = false; | |
1053 | (void) nvme_port_report(c->ipv4_port, &plymouth_done); | |
1054 | (void) nvme_port_report(c->ipv6_port, &plymouth_done); | |
1055 | ||
1056 | if (!plymouth_done) | |
1057 | (void) plymouth_send_text("Network disconnected."); | |
1761066b LP |
1058 | |
1059 | NvmeSubsystem *i; | |
1060 | HASHMAP_FOREACH(i, c->subsystems) | |
1061 | (void) nvme_subsystem_report(i, c->ipv4_port, c->ipv6_port); | |
1062 | ||
1063 | return 0; | |
1064 | } | |
1065 | ||
1066 | static int on_address_change(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { | |
1067 | Context *c = ASSERT_PTR(userdata); | |
1068 | int r, family; | |
1069 | ||
1070 | assert(rtnl); | |
1071 | assert(mm); | |
1072 | ||
1073 | r = sd_rtnl_message_addr_get_family(mm, &family); | |
1074 | if (r < 0) { | |
1075 | log_warning_errno(r, "Failed to get address family from netlink address message, ignoring: %m"); | |
1076 | return 0; | |
1077 | } | |
1078 | ||
1079 | if (!c->display_refresh_scheduled) { | |
1080 | r = sd_event_add_time_relative( | |
1081 | sd_netlink_get_event(rtnl), | |
1082 | /* ret_slot= */ NULL, | |
1083 | CLOCK_MONOTONIC, | |
1084 | 750 * USEC_PER_MSEC, | |
1085 | 0, | |
1086 | on_display_refresh, | |
1087 | c); | |
1088 | if (r < 0) | |
1089 | log_warning_errno(r, "Failed to schedule display refresh, ignoring: %m"); | |
1090 | else | |
1091 | c->display_refresh_scheduled = true; | |
1092 | } | |
1093 | ||
1094 | return 0; | |
1095 | } | |
1096 | ||
1097 | static int run(int argc, char* argv[]) { | |
1098 | _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; | |
1099 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
1100 | _cleanup_(context_done) Context context = {}; | |
1101 | int r; | |
1102 | ||
1103 | log_show_color(true); | |
1104 | log_parse_environment(); | |
1105 | log_open(); | |
1106 | ||
1107 | r = parse_argv(argc, argv); | |
1108 | if (r <= 0) | |
1109 | return r; | |
1110 | ||
1111 | r = sd_event_new(&event); | |
1112 | if (r < 0) | |
1113 | return log_error_errno(r, "Failed to allocate event loop: %m"); | |
1114 | ||
1115 | r = sd_event_set_signal_exit(event, true); | |
1116 | if (r < 0) | |
1117 | return log_error_errno(r, "Failed to install exit signal handlers: %m"); | |
1118 | ||
1119 | STRV_FOREACH(i, arg_devices) { | |
1120 | _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *subsys = NULL; | |
1121 | ||
abc19a6f | 1122 | r = nvme_subsystem_add(*i, -EBADF, /* device= */ NULL, &subsys); |
1761066b LP |
1123 | if (r < 0) |
1124 | return r; | |
1125 | ||
1126 | r = hashmap_ensure_put(&context.subsystems, &nvme_subsystem_hash_ops, &subsys->device_stat, subsys); | |
1127 | if (r == -EEXIST) { | |
1128 | log_warning_errno(r, "Duplicate device '%s' specified, skipping: %m", *i); | |
1129 | continue; | |
1130 | } | |
1131 | if (r < 0) | |
1132 | return log_error_errno(r, "Failed to add subsystem to hash table: %m"); | |
1133 | ||
1134 | TAKE_PTR(subsys); | |
1135 | } | |
1136 | ||
1137 | r = nvme_port_add(arg_nqn, AF_INET, &context.ipv4_port); | |
1138 | if (r < 0) | |
1139 | return r; | |
1140 | ||
95d54802 LP |
1141 | bool plymouth_done = false; |
1142 | nvme_port_report(context.ipv4_port, &plymouth_done); | |
1761066b LP |
1143 | |
1144 | if (socket_ipv6_is_enabled()) { | |
1145 | r = nvme_port_add(arg_nqn, AF_INET6, &context.ipv6_port); | |
1146 | if (r < 0) | |
1147 | return r; | |
1148 | ||
95d54802 | 1149 | nvme_port_report(context.ipv6_port, &plymouth_done); |
1761066b LP |
1150 | } |
1151 | ||
95d54802 LP |
1152 | if (!plymouth_done) |
1153 | (void) plymouth_send_text("Network disconnected."); | |
1154 | ||
1761066b LP |
1155 | NvmeSubsystem *i; |
1156 | HASHMAP_FOREACH(i, context.subsystems) { | |
1157 | if (context.ipv4_port) { | |
1158 | r = nvme_port_link_subsystem(context.ipv4_port, i); | |
1159 | if (r < 0) | |
1160 | return r; | |
1161 | } | |
1162 | ||
1163 | if (context.ipv6_port) { | |
1164 | r = nvme_port_link_subsystem(context.ipv6_port, i); | |
1165 | if (r < 0) | |
1166 | return r; | |
1167 | } | |
1168 | ||
1169 | (void) nvme_subsystem_report(i, context.ipv4_port, context.ipv6_port); | |
1170 | } | |
1171 | ||
1172 | if (arg_all > 0) { | |
1173 | r = sd_device_monitor_new(&monitor); | |
1174 | if (r < 0) | |
1175 | return log_error_errno(r, "Failed to allocate device monitor: %m"); | |
1176 | ||
1177 | r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, "block", "disk"); | |
1178 | if (r < 0) | |
1179 | return log_error_errno(r, "Failed to configure device monitor match: %m"); | |
1180 | ||
1181 | r = sd_device_monitor_attach_event(monitor, event); | |
1182 | if (r < 0) | |
1183 | return log_error_errno(r, "Failed to attach device monitor to event loop: %m"); | |
1184 | ||
1185 | r = sd_device_monitor_start(monitor, device_monitor_handler, &context); | |
1186 | if (r < 0) | |
1187 | return log_error_errno(r, "Failed to start device monitor: %m"); | |
1188 | ||
1189 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; | |
1190 | r = sd_device_enumerator_new(&enumerator); | |
1191 | if (r < 0) | |
1192 | return log_error_errno(r, "Failed to allocate enumerator: %m"); | |
1193 | ||
1194 | r = sd_device_enumerator_add_match_subsystem(enumerator, "block", /* match= */ true); | |
1195 | if (r < 0) | |
1196 | return log_error_errno(r, "Failed to match block devices: %m"); | |
1197 | ||
1198 | r = sd_device_enumerator_add_match_property(enumerator, "DEVTYPE", "disk"); | |
1199 | if (r < 0) | |
1200 | return log_error_errno(r, "Failed to match whole block devices: %m"); | |
1201 | ||
1202 | r = sd_device_enumerator_add_nomatch_sysname(enumerator, "loop*"); | |
1203 | if (r < 0) | |
1204 | return log_error_errno(r, "Failed to exclude loop devices: %m"); | |
1205 | ||
1206 | FOREACH_DEVICE(enumerator, device) | |
1207 | device_added(&context, device); | |
1208 | } | |
1209 | ||
1210 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; | |
1211 | r = sd_netlink_open(&rtnl); | |
1212 | if (r < 0) | |
1213 | return log_error_errno(r, "Failed to connect to netlink: %m"); | |
1214 | ||
1215 | r = sd_netlink_attach_event(rtnl, event, SD_EVENT_PRIORITY_NORMAL); | |
1216 | if (r < 0) | |
1217 | return log_error_errno(r, "Failed to attach netlink socket to event loop: %m"); | |
1218 | ||
1219 | r = sd_netlink_add_match(rtnl, /* ret_slot= */ NULL, RTM_NEWADDR, on_address_change, /* destroy_callback= */ NULL, &context, "storagetm-newaddr"); | |
1220 | if (r < 0) | |
1221 | return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR events: %m"); | |
1222 | ||
1223 | r = sd_netlink_add_match(rtnl, /* ret_slot= */ NULL, RTM_DELADDR, on_address_change, /* destroy_callback= */ NULL, &context, "storagetm-deladdr"); | |
1224 | if (r < 0) | |
1225 | return log_error_errno(r, "Failed to subscribe to RTM_DELADDR events: %m"); | |
1226 | ||
dd9c8da8 | 1227 | if (isatty(STDIN_FILENO)) |
1761066b LP |
1228 | log_info("Hit Ctrl-C to exit target mode."); |
1229 | ||
1230 | _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = | |
1231 | notify_start("READY=1\n" | |
1232 | "STATUS=Exposing disks in target mode...", | |
1233 | NOTIFY_STOPPING); | |
1234 | ||
1235 | r = sd_event_loop(event); | |
1236 | if (r < 0) | |
1237 | return log_error_errno(r, "Failed to run event loop: %m"); | |
1238 | ||
1239 | log_info("Exiting target mode."); | |
1240 | return r; | |
1241 | } | |
1242 | ||
1243 | DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); |