Coverage Report

Created: 2025-11-07 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}