/src/cryptsetup/lib/utils_devpath.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * devname - search for device name |
4 | | * |
5 | | * Copyright (C) 2004 Jana Saout <jana@saout.de> |
6 | | * Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org> |
7 | | * Copyright (C) 2009-2025 Red Hat, Inc. All rights reserved. |
8 | | * Copyright (C) 2009-2025 Milan Broz |
9 | | */ |
10 | | |
11 | | #include <string.h> |
12 | | #include <stdio.h> |
13 | | #include <stdlib.h> |
14 | | #include <unistd.h> |
15 | | #include <dirent.h> |
16 | | #include <errno.h> |
17 | | #include <limits.h> |
18 | | #include <sys/stat.h> |
19 | | #include <sys/types.h> |
20 | | #if HAVE_SYS_SYSMACROS_H |
21 | | # include <sys/sysmacros.h> /* for major, minor */ |
22 | | #endif |
23 | | #include "internal.h" |
24 | | |
25 | | static char *__lookup_dev(char *path, dev_t dev, int dir_level, const int max_level) |
26 | 0 | { |
27 | 0 | struct dirent *entry; |
28 | 0 | struct stat st; |
29 | 0 | char *ptr; |
30 | 0 | char *result = NULL; |
31 | 0 | DIR *dir; |
32 | 0 | int space; |
33 | | |
34 | | /* Ignore strange nested directories */ |
35 | 0 | if (dir_level > max_level) |
36 | 0 | return NULL; |
37 | | |
38 | 0 | path[PATH_MAX - 1] = '\0'; |
39 | 0 | ptr = path + strlen(path); |
40 | 0 | *ptr++ = '/'; |
41 | 0 | *ptr = '\0'; |
42 | 0 | space = PATH_MAX - (ptr - path); |
43 | |
|
44 | 0 | dir = opendir(path); |
45 | 0 | if (!dir) |
46 | 0 | return NULL; |
47 | | |
48 | 0 | while((entry = readdir(dir))) { |
49 | 0 | if (entry->d_name[0] == '.' || |
50 | 0 | !strncmp(entry->d_name, "..", 2)) |
51 | 0 | continue; |
52 | | |
53 | 0 | if (dir_level == 0 && |
54 | 0 | (!strcmp(entry->d_name, "shm") || |
55 | 0 | !strcmp(entry->d_name, "fd") || |
56 | 0 | !strcmp(entry->d_name, "char") || |
57 | 0 | !strcmp(entry->d_name, "pts"))) |
58 | 0 | continue; |
59 | | |
60 | 0 | strncpy(ptr, entry->d_name, space); |
61 | 0 | if (stat(path, &st) < 0) |
62 | 0 | continue; |
63 | | |
64 | 0 | if (S_ISDIR(st.st_mode)) { |
65 | 0 | result = __lookup_dev(path, dev, dir_level + 1, max_level); |
66 | 0 | if (result) |
67 | 0 | break; |
68 | 0 | } else if (S_ISBLK(st.st_mode)) { |
69 | | /* workaround: ignore dm-X devices, these are internal kernel names */ |
70 | 0 | if (dir_level == 0 && dm_is_dm_kernel_name(entry->d_name)) |
71 | 0 | continue; |
72 | 0 | if (st.st_rdev == dev) { |
73 | 0 | result = strdup(path); |
74 | 0 | break; |
75 | 0 | } |
76 | 0 | } |
77 | 0 | } |
78 | |
|
79 | 0 | closedir(dir); |
80 | 0 | return result; |
81 | 0 | } |
82 | | |
83 | | /* |
84 | | * Non-udev systemd need to scan for device here. |
85 | | */ |
86 | | static char *lookup_dev_old(int major, int minor) |
87 | 0 | { |
88 | 0 | dev_t dev; |
89 | 0 | char *result = NULL, buf[PATH_MAX + 1]; |
90 | |
|
91 | 0 | dev = makedev(major, minor); |
92 | 0 | strncpy(buf, "/dev", PATH_MAX); |
93 | 0 | buf[PATH_MAX] = '\0'; |
94 | | |
95 | | /* First try low level device */ |
96 | 0 | if ((result = __lookup_dev(buf, dev, 0, 0))) |
97 | 0 | return result; |
98 | | |
99 | | /* If it is dm, try DM dir */ |
100 | 0 | if (dm_is_dm_device(major)) { |
101 | 0 | strncpy(buf, dm_get_dir(), PATH_MAX); |
102 | 0 | if ((result = __lookup_dev(buf, dev, 0, 0))) |
103 | 0 | return result; |
104 | 0 | } |
105 | | |
106 | 0 | strncpy(buf, "/dev", PATH_MAX); |
107 | 0 | return __lookup_dev(buf, dev, 0, 4); |
108 | 0 | } |
109 | | |
110 | | /* |
111 | | * Returns string pointing to device in /dev according to "major:minor" dev_id |
112 | | */ |
113 | | char *crypt_lookup_dev(const char *dev_id) |
114 | 0 | { |
115 | 0 | int major, minor; |
116 | 0 | char link[PATH_MAX], path[PATH_MAX], *devname, *devpath = NULL; |
117 | 0 | struct stat st; |
118 | 0 | ssize_t len; |
119 | |
|
120 | 0 | if (sscanf(dev_id, "%d:%d", &major, &minor) != 2) |
121 | 0 | return NULL; |
122 | | |
123 | 0 | if (snprintf(path, sizeof(path), "/sys/dev/block/%s", dev_id) < 0) |
124 | 0 | return NULL; |
125 | | |
126 | 0 | len = readlink(path, link, sizeof(link) - 1); |
127 | 0 | if (len < 0) { |
128 | | /* Without /sys use old scan */ |
129 | 0 | if (stat("/sys/dev/block", &st) < 0) |
130 | 0 | return lookup_dev_old(major, minor); |
131 | 0 | return NULL; |
132 | 0 | } |
133 | | |
134 | 0 | link[len] = '\0'; |
135 | 0 | devname = strrchr(link, '/'); |
136 | 0 | if (!devname) |
137 | 0 | return NULL; |
138 | 0 | devname++; |
139 | |
|
140 | 0 | if (dm_is_dm_kernel_name(devname)) |
141 | 0 | devpath = dm_device_path("/dev/mapper/", major, minor); |
142 | 0 | else if (snprintf(path, sizeof(path), "/dev/%s", devname) > 0) |
143 | 0 | devpath = strdup(path); |
144 | | |
145 | | /* |
146 | | * Check that path is correct. |
147 | | */ |
148 | 0 | if (devpath && ((stat(devpath, &st) < 0) || |
149 | 0 | !S_ISBLK(st.st_mode) || |
150 | 0 | (st.st_rdev != makedev(major, minor)))) { |
151 | 0 | free(devpath); |
152 | | /* Should never happen unless user mangles with dev nodes. */ |
153 | 0 | return lookup_dev_old(major, minor); |
154 | 0 | } |
155 | | |
156 | 0 | return devpath; |
157 | 0 | } |
158 | | |
159 | | static int _read_uint64(const char *sysfs_path, uint64_t *value) |
160 | 0 | { |
161 | 0 | char tmp[64] = {0}; |
162 | 0 | int fd, r; |
163 | |
|
164 | 0 | if ((fd = open(sysfs_path, O_RDONLY)) < 0) |
165 | 0 | return 0; |
166 | 0 | r = read(fd, tmp, sizeof(tmp)); |
167 | 0 | close(fd); |
168 | |
|
169 | 0 | if (r <= 0) |
170 | 0 | return 0; |
171 | | |
172 | 0 | if (sscanf(tmp, "%" PRIu64, value) != 1) |
173 | 0 | return 0; |
174 | | |
175 | 0 | return 1; |
176 | 0 | } |
177 | | |
178 | | static int _sysfs_get_uint64(int major, int minor, uint64_t *value, const char *attr) |
179 | 0 | { |
180 | 0 | char path[PATH_MAX]; |
181 | |
|
182 | 0 | if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d/%s", |
183 | 0 | major, minor, attr) < 0) |
184 | 0 | return 0; |
185 | | |
186 | 0 | return _read_uint64(path, value); |
187 | 0 | } |
188 | | |
189 | | static int _path_get_uint64(const char *sysfs_path, uint64_t *value, const char *attr) |
190 | 0 | { |
191 | 0 | char path[PATH_MAX]; |
192 | |
|
193 | 0 | if (snprintf(path, sizeof(path), "%s/%s", |
194 | 0 | sysfs_path, attr) < 0) |
195 | 0 | return 0; |
196 | | |
197 | 0 | return _read_uint64(path, value); |
198 | 0 | } |
199 | | |
200 | | static int _sysfs_get_string(int major, int minor, char *buf, size_t buf_size, const char *attr) |
201 | 0 | { |
202 | 0 | char path[PATH_MAX]; |
203 | 0 | int fd, r; |
204 | |
|
205 | 0 | if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d/%s", |
206 | 0 | major, minor, attr) < 0) |
207 | 0 | return 0; |
208 | | |
209 | 0 | if ((fd = open(path, O_RDONLY)) < 0) |
210 | 0 | return 0; |
211 | 0 | r = read(fd, buf, buf_size); |
212 | 0 | close(fd); |
213 | |
|
214 | 0 | return r < 0 ? 0 : r; |
215 | 0 | } |
216 | | |
217 | | int crypt_dev_get_partition_number(const char *dev_path) |
218 | 0 | { |
219 | 0 | uint64_t partno; |
220 | 0 | struct stat st; |
221 | |
|
222 | 0 | if (stat(dev_path, &st) < 0) |
223 | 0 | return 0; |
224 | | |
225 | 0 | if (!S_ISBLK(st.st_mode)) |
226 | 0 | return 0; |
227 | | |
228 | 0 | if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev), |
229 | 0 | &partno, "partition")) |
230 | 0 | return -EINVAL; |
231 | | |
232 | 0 | return (int)partno; |
233 | 0 | } |
234 | | |
235 | | int crypt_dev_is_rotational(int major, int minor) |
236 | 0 | { |
237 | 0 | uint64_t val; |
238 | |
|
239 | 0 | if (!_sysfs_get_uint64(major, minor, &val, "queue/rotational")) |
240 | 0 | return 1; /* if failed, expect rotational disk */ |
241 | | |
242 | 0 | return val ? 1 : 0; |
243 | 0 | } |
244 | | |
245 | | int crypt_dev_is_dax(int major, int minor) |
246 | 0 | { |
247 | 0 | uint64_t val; |
248 | |
|
249 | 0 | if (!_sysfs_get_uint64(major, minor, &val, "queue/dax")) |
250 | 0 | return 0; /* if failed, expect non-DAX device */ |
251 | | |
252 | 0 | return val ? 1 : 0; |
253 | 0 | } |
254 | | |
255 | | int crypt_dev_is_zoned(int major, int minor) |
256 | 0 | { |
257 | 0 | char buf[32] = {}; |
258 | |
|
259 | 0 | if (!_sysfs_get_string(major, minor, buf, sizeof(buf), "queue/zoned")) |
260 | 0 | return 0; /* if failed, expect non-zoned device */ |
261 | | |
262 | 0 | return strncmp(buf, "none", 4) ? 1 : 0; |
263 | 0 | } |
264 | | |
265 | | int crypt_dev_is_nop_dif(int major, int minor, uint32_t *tag_size) |
266 | 0 | { |
267 | 0 | char buf[64] = {}; |
268 | 0 | uint64_t val = 0; |
269 | |
|
270 | 0 | if (!_sysfs_get_string(major, minor, buf, sizeof(buf), "integrity/format")) |
271 | 0 | return 0; |
272 | | |
273 | 0 | if (strncmp(buf, "nop", 3)) |
274 | 0 | return 0; |
275 | | |
276 | | /* this field is currently supported only for NVMe */ |
277 | 0 | _sysfs_get_uint64(major, minor, &val, "metadata_bytes"); |
278 | | |
279 | | /* tag_size should be 0, but it is set by dm-integrity, try it as a fallback */ |
280 | 0 | if (val == 0) |
281 | 0 | _sysfs_get_uint64(major, minor, &val, "integrity/tag_size"); |
282 | | |
283 | | /* we can still return 0 and support metadata, caller must handle it */ |
284 | 0 | *tag_size = (uint32_t)val; |
285 | 0 | return 1; |
286 | 0 | } |
287 | | |
288 | | int crypt_dev_is_partition(const char *dev_path) |
289 | 0 | { |
290 | 0 | uint64_t val; |
291 | 0 | struct stat st; |
292 | |
|
293 | 0 | if (stat(dev_path, &st) < 0) |
294 | 0 | return 0; |
295 | | |
296 | 0 | if (!S_ISBLK(st.st_mode)) |
297 | 0 | return 0; |
298 | | |
299 | 0 | if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev), |
300 | 0 | &val, "partition")) |
301 | 0 | return 0; |
302 | | |
303 | 0 | return val ? 1 : 0; |
304 | 0 | } |
305 | | |
306 | | uint64_t crypt_dev_partition_offset(const char *dev_path) |
307 | 0 | { |
308 | 0 | uint64_t val; |
309 | 0 | struct stat st; |
310 | |
|
311 | 0 | if (!crypt_dev_is_partition(dev_path)) |
312 | 0 | return 0; |
313 | | |
314 | 0 | if (stat(dev_path, &st) < 0) |
315 | 0 | return 0; |
316 | | |
317 | 0 | if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev), |
318 | 0 | &val, "start")) |
319 | 0 | return 0; |
320 | | |
321 | | /* coverity[tainted_data_return:FALSE] */ |
322 | 0 | return val; |
323 | 0 | } |
324 | | |
325 | | /* Try to find partition which match offset and size on top level device */ |
326 | | char *crypt_get_partition_device(const char *dev_path, uint64_t offset, uint64_t size) |
327 | 0 | { |
328 | 0 | char link[PATH_MAX], path[PATH_MAX], part_path[PATH_MAX], *devname; |
329 | 0 | char *result = NULL; |
330 | 0 | struct stat st; |
331 | 0 | size_t devname_len; |
332 | 0 | ssize_t len; |
333 | 0 | struct dirent *entry; |
334 | 0 | DIR *dir; |
335 | 0 | uint64_t part_offset, part_size; |
336 | |
|
337 | 0 | if (stat(dev_path, &st) < 0) |
338 | 0 | return NULL; |
339 | | |
340 | 0 | if (!S_ISBLK(st.st_mode)) |
341 | 0 | return NULL; |
342 | | |
343 | 0 | if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d", |
344 | 0 | major(st.st_rdev), minor(st.st_rdev)) < 0) |
345 | 0 | return NULL; |
346 | | |
347 | 0 | dir = opendir(path); |
348 | 0 | if (!dir) |
349 | 0 | return NULL; |
350 | | |
351 | 0 | len = readlink(path, link, sizeof(link) - 1); |
352 | 0 | if (len < 0) { |
353 | 0 | closedir(dir); |
354 | 0 | return NULL; |
355 | 0 | } |
356 | | |
357 | | /* Get top level disk name for sysfs search */ |
358 | 0 | link[len] = '\0'; |
359 | 0 | devname = strrchr(link, '/'); |
360 | 0 | if (!devname) { |
361 | 0 | closedir(dir); |
362 | 0 | return NULL; |
363 | 0 | } |
364 | 0 | devname++; |
365 | | |
366 | | /* DM devices do not use kernel partitions. */ |
367 | 0 | if (dm_is_dm_kernel_name(devname)) { |
368 | 0 | closedir(dir); |
369 | 0 | return NULL; |
370 | 0 | } |
371 | | |
372 | 0 | devname_len = strlen(devname); |
373 | 0 | while((entry = readdir(dir))) { |
374 | 0 | if (strncmp(entry->d_name, devname, devname_len)) |
375 | 0 | continue; |
376 | | |
377 | 0 | if (snprintf(part_path, sizeof(part_path), "%s/%s", |
378 | 0 | path, entry->d_name) < 0) |
379 | 0 | continue; |
380 | | |
381 | 0 | if (stat(part_path, &st) < 0) |
382 | 0 | continue; |
383 | | |
384 | 0 | if (S_ISDIR(st.st_mode)) { |
385 | 0 | if (!_path_get_uint64(part_path, &part_offset, "start") || |
386 | 0 | !_path_get_uint64(part_path, &part_size, "size")) |
387 | 0 | continue; |
388 | 0 | if (part_offset == offset && part_size == size && |
389 | 0 | snprintf(part_path, sizeof(part_path), "/dev/%s", |
390 | 0 | entry->d_name) > 0) { |
391 | 0 | result = strdup(part_path); |
392 | 0 | break; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | 0 | closedir(dir); |
397 | |
|
398 | 0 | return result; |
399 | 0 | } |
400 | | |
401 | | /* Try to find base device from partition */ |
402 | | char *crypt_get_base_device(const char *dev_path) |
403 | 0 | { |
404 | 0 | char link[PATH_MAX], path[PATH_MAX], part_path[PATH_MAX], *devname; |
405 | 0 | struct stat st; |
406 | 0 | ssize_t len; |
407 | |
|
408 | 0 | if (!crypt_dev_is_partition(dev_path)) |
409 | 0 | return NULL; |
410 | | |
411 | 0 | if (stat(dev_path, &st) < 0) |
412 | 0 | return NULL; |
413 | | |
414 | 0 | if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d", |
415 | 0 | major(st.st_rdev), minor(st.st_rdev)) < 0) |
416 | 0 | return NULL; |
417 | | |
418 | 0 | len = readlink(path, link, sizeof(link) - 1); |
419 | 0 | if (len < 0) |
420 | 0 | return NULL; |
421 | | |
422 | | /* Get top level disk name for sysfs search */ |
423 | 0 | link[len] = '\0'; |
424 | 0 | devname = strrchr(link, '/'); |
425 | 0 | if (!devname) |
426 | 0 | return NULL; |
427 | 0 | *devname = '\0'; |
428 | 0 | devname = strrchr(link, '/'); |
429 | 0 | if (!devname) |
430 | 0 | return NULL; |
431 | 0 | devname++; |
432 | |
|
433 | 0 | if (dm_is_dm_kernel_name(devname)) |
434 | 0 | return NULL; |
435 | | |
436 | 0 | if (snprintf(part_path, sizeof(part_path), "/dev/%s", devname) < 0) |
437 | 0 | return NULL; |
438 | | |
439 | 0 | return strdup(part_path); |
440 | 0 | } |
441 | | |
442 | | int lookup_by_disk_id(const char *dm_uuid) |
443 | 0 | { |
444 | 0 | struct dirent *entry; |
445 | 0 | struct stat st; |
446 | 0 | int dfd, r = 0; /* not found */ |
447 | 0 | DIR *dir = opendir("/dev/disk/by-id"); |
448 | |
|
449 | 0 | if (!dir) |
450 | | /* map ENOTDIR to ENOENT we'll handle both errors same */ |
451 | 0 | return errno == ENOTDIR ? -ENOENT : -errno; |
452 | | |
453 | 0 | while ((entry = readdir(dir))) { |
454 | 0 | if (entry->d_name[0] == '.' || |
455 | 0 | !strncmp(entry->d_name, "..", 2)) |
456 | 0 | continue; |
457 | | |
458 | 0 | dfd = dirfd(dir); |
459 | 0 | if (dfd < 0 || fstatat(dfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW)) { |
460 | 0 | r = -EINVAL; |
461 | 0 | break; |
462 | 0 | } |
463 | | |
464 | 0 | if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) |
465 | 0 | continue; |
466 | | |
467 | 0 | if (!strncmp(entry->d_name, dm_uuid, strlen(dm_uuid))) { |
468 | 0 | r = 1; |
469 | 0 | break; |
470 | 0 | } |
471 | 0 | } |
472 | |
|
473 | 0 | closedir(dir); |
474 | |
|
475 | 0 | return r; |
476 | 0 | } |
477 | | |
478 | | int lookup_by_sysfs_uuid_field(const char *dm_uuid) |
479 | 0 | { |
480 | 0 | struct dirent *entry; |
481 | 0 | char subpath[PATH_MAX], uuid[DM_UUID_LEN]; |
482 | 0 | ssize_t s; |
483 | 0 | struct stat st; |
484 | 0 | int fd, dfd, len, r = 0; /* not found */ |
485 | 0 | DIR *dir = opendir("/sys/block/"); |
486 | |
|
487 | 0 | if (!dir) |
488 | | /* map ENOTDIR to ENOENT we'll handle both errors same */ |
489 | 0 | return errno == ENOTDIR ? -ENOENT : -errno; |
490 | | |
491 | 0 | while (r != 1 && (entry = readdir(dir))) { |
492 | 0 | if (entry->d_name[0] == '.' || |
493 | 0 | !strncmp(entry->d_name, "..", 2)) |
494 | 0 | continue; |
495 | | |
496 | 0 | len = snprintf(subpath, PATH_MAX, "%s/%s", entry->d_name, "dm/uuid"); |
497 | 0 | if (len < 0 || len >= PATH_MAX) { |
498 | 0 | r = -EINVAL; |
499 | 0 | break; |
500 | 0 | } |
501 | | |
502 | | /* looking for dm-X/dm/uuid file, symlinks are fine */ |
503 | 0 | dfd = dirfd(dir); |
504 | 0 | if (dfd < 0) |
505 | 0 | continue; |
506 | 0 | fd = openat(dfd, subpath, O_RDONLY | O_CLOEXEC); |
507 | 0 | if (fd < 0) |
508 | 0 | continue; |
509 | | |
510 | 0 | if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { |
511 | 0 | close(fd); |
512 | 0 | continue; |
513 | 0 | } |
514 | | |
515 | | /* reads binary data */ |
516 | 0 | s = read_buffer(fd, uuid, sizeof(uuid) - 1); |
517 | 0 | if (s > 0) { |
518 | 0 | uuid[s] = '\0'; |
519 | 0 | if (!strncmp(uuid, dm_uuid, strlen(dm_uuid))) |
520 | 0 | r = 1; |
521 | 0 | } |
522 | |
|
523 | 0 | close(fd); |
524 | 0 | } |
525 | |
|
526 | 0 | closedir(dir); |
527 | |
|
528 | 0 | return r; |
529 | 0 | } |