Coverage Report

Created: 2026-07-16 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/sysfs.c
Line
Count
Source
1
/*
2
 * No copyright is claimed.  This code is in the public domain; do with
3
 * it what you wish.
4
 *
5
 * Written by Karel Zak <kzak@redhat.com> [2011]
6
 */
7
#include <ctype.h>
8
#include <inttypes.h>
9
#include <libgen.h>
10
#include <fcntl.h>
11
#include <sys/stat.h>
12
#include <unistd.h>
13
14
#include "c.h"
15
#include "cctype.h"
16
#include "pathnames.h"
17
#include "sysfs.h"
18
#include "fileutils.h"
19
#include "all-io.h"
20
#include "debug.h"
21
#include "strutils.h"
22
#include "buffer.h"
23
24
static void sysfs_blkdev_deinit_path(struct path_cxt *pc);
25
static int  sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd);
26
27
/*
28
 * Debug stuff (based on include/debug.h)
29
 */
30
static UL_DEBUG_DEFINE_MASK(ulsysfs);
31
UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES;
32
33
0
#define ULSYSFS_DEBUG_INIT  (1 << 1)
34
0
#define ULSYSFS_DEBUG_CXT (1 << 2)
35
36
#define DBG(m, x)   __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x)
37
0
#define DBG_OBJ(m, h, x)  __UL_DBG_OBJ(ulsysfs, ULSYSFS_DEBUG_, m, h, x)
38
#define ON_DBG(m, x)    __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x)
39
40
void ul_sysfs_init_debug(void)
41
0
{
42
0
  if (ulsysfs_debug_mask)
43
0
    return;
44
0
  __UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG);
45
0
}
46
47
struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix)
48
0
{
49
0
  struct path_cxt *pc = ul_new_path(NULL);
50
51
0
  if (!pc)
52
0
    return NULL;
53
0
  if (prefix)
54
0
    ul_path_set_prefix(pc, prefix);
55
56
0
  if (sysfs_blkdev_init_path(pc, devno, parent) != 0) {
57
0
    ul_unref_path(pc);
58
0
    return NULL;
59
0
  }
60
61
0
  DBG_OBJ(CXT, pc, ul_debug("alloc"));
62
0
  return pc;
63
0
}
64
65
/*
66
 * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices.
67
 *
68
 * The function is possible to call in loop and without sysfs_blkdev_deinit_path().
69
 * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path().
70
 *
71
 */
72
int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent)
73
0
{
74
0
  struct sysfs_blkdev *blk;
75
0
  int rc;
76
0
  char buf[sizeof(_PATH_SYS_DEVBLOCK)
77
0
     + sizeof(stringify_value(UINT32_MAX)) * 2
78
0
     + 3];
79
80
  /* define path to devno stuff */
81
0
  snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%u:%u", major(devno), minor(devno));
82
0
  rc = ul_path_set_dir(pc, buf);
83
0
  if (rc)
84
0
    return rc;
85
86
  /* make sure path exists */
87
0
  rc = ul_path_get_dirfd(pc);
88
0
  if (rc < 0)
89
0
    return rc;
90
91
  /* initialize sysfs blkdev specific stuff */
92
0
  blk = ul_path_get_dialect(pc);
93
0
  if (!blk) {
94
0
    DBG_OBJ(CXT, pc, ul_debug("alloc new sysfs handler"));
95
0
    blk = calloc(1, sizeof(struct sysfs_blkdev));
96
0
    if (!blk)
97
0
      return -ENOMEM;
98
99
0
    ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path);
100
0
    ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect);
101
0
  }
102
103
0
  DBG_OBJ(CXT, pc, ul_debug("init sysfs stuff"));
104
105
0
  blk->devno = devno;
106
0
  sysfs_blkdev_set_parent(pc, parent);
107
108
0
  return 0;
109
0
}
110
111
static void sysfs_blkdev_deinit_path(struct path_cxt *pc)
112
0
{
113
0
  struct sysfs_blkdev *blk;
114
115
0
  if (!pc)
116
0
    return;
117
118
0
  DBG_OBJ(CXT, pc, ul_debug("deinit"));
119
120
0
  blk = ul_path_get_dialect(pc);
121
0
  if (!blk)
122
0
    return;
123
124
0
  ul_unref_path(blk->parent);
125
0
  free(blk);
126
127
0
  ul_path_set_dialect(pc, NULL, NULL);
128
0
}
129
130
int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent)
131
0
{
132
0
  struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
133
134
0
  if (!pc || !blk)
135
0
    return -EINVAL;
136
137
0
  if (blk->parent) {
138
0
    ul_unref_path(blk->parent);
139
0
    blk->parent = NULL;
140
0
  }
141
142
0
  if (parent) {
143
0
    ul_ref_path(parent);
144
0
    blk->parent = parent;
145
0
  } else
146
0
    blk->parent = NULL;
147
148
0
  DBG_OBJ(CXT, pc, ul_debug("new parent"));
149
0
  return 0;
150
0
}
151
152
struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc)
153
0
{
154
0
  struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
155
0
  return blk ? blk->parent : NULL;
156
0
}
157
158
/*
159
 * Redirects ENOENT errors to the parent.
160
 * For example
161
 *
162
 *  /sys/dev/block/8:1/queue/logical_block_size redirects to
163
 *  /sys/dev/block/8:0/queue/logical_block_size
164
 */
165
static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd)
166
0
{
167
0
  struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
168
169
0
  if (blk && blk->parent && path) {
170
0
    *dirfd = ul_path_get_dirfd(blk->parent);
171
0
    if (*dirfd >= 0) {
172
0
      DBG_OBJ(CXT, pc, ul_debug("%s redirected to parent", path));
173
0
      return 0;
174
0
    }
175
0
  }
176
0
  return 1; /* no redirect */
177
0
}
178
179
char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz)
180
0
{
181
0
  char link[PATH_MAX];
182
0
  char *name;
183
0
  ssize_t sz;
184
185
  /* read /sys/dev/block/<maj:min> link */
186
0
  sz = ul_path_readlink(pc, link, sizeof(link), NULL);
187
0
  if (sz < 0)
188
0
    return NULL;
189
190
0
  name = strrchr(link, '/');
191
0
  if (!name)
192
0
    return NULL;
193
194
0
  name++;
195
0
  sz = strlen(name);
196
0
  if ((size_t) sz + 1 > bufsiz)
197
0
    return NULL;
198
199
0
  memcpy(buf, name, sz + 1);
200
0
  sysfs_devname_sys_to_dev(buf);
201
0
  return buf;
202
0
}
203
204
int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
205
0
{
206
0
  char path[NAME_MAX + 6 + 1];
207
208
0
#ifdef _DIRENT_HAVE_D_TYPE
209
0
  if (d->d_type != DT_DIR &&
210
0
      d->d_type != DT_LNK &&
211
0
      d->d_type != DT_UNKNOWN)
212
0
    return 0;
213
0
#endif
214
0
  size_t len = 0;
215
216
0
  if (parent_name) {
217
0
    const char *p = parent_name;
218
219
    /* /dev/sda --> "sda" */
220
0
    if (*parent_name == '/') {
221
0
      p = strrchr(parent_name, '/');
222
0
      if (!p)
223
0
        return 0;
224
0
      p++;
225
0
    }
226
227
0
    len = strlen(p);
228
0
    if ((strlen(d->d_name) <= len) || (strncmp(p, d->d_name, len) != 0))
229
0
      len = 0;
230
0
  }
231
232
0
  if (len > 0) {
233
    /* partitions subdir name is
234
     *  "<parent>[:digit:]" or "<parent>p[:digit:]"
235
     */
236
0
    return ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
237
0
      || isdigit(*(d->d_name + len)));
238
0
  }
239
240
  /* Cannot use /partition file, not supported on old sysfs */
241
0
  snprintf(path, sizeof(path), "%s/start", d->d_name);
242
243
0
  return faccessat(dirfd(dir), path, R_OK, 0) == 0;
244
0
}
245
246
int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname)
247
0
{
248
0
  DIR *dir;
249
0
  struct dirent *d;
250
0
  int r = 0;
251
252
0
  dir = ul_path_opendir(pc, NULL);
253
0
  if (!dir)
254
0
    return 0;
255
256
0
  while ((d = xreaddir(dir))) {
257
0
    if (sysfs_blkdev_is_partition_dirent(dir, d, devname))
258
0
      r++;
259
0
  }
260
261
0
  closedir(dir);
262
0
  return r;
263
0
}
264
265
/*
266
 * Converts @partno (partition number) to devno of the partition.
267
 * The @pc handles wholedisk device.
268
 *
269
 * Note that this code does not expect any special format of the
270
 * partitions devnames.
271
 */
272
dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno)
273
0
{
274
0
  DIR *dir;
275
0
  struct dirent *d;
276
0
  dev_t devno = 0;
277
278
0
  dir = ul_path_opendir(pc, NULL);
279
0
  if (!dir)
280
0
    return 0;
281
282
0
  while ((d = xreaddir(dir))) {
283
0
    int n;
284
285
0
    if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL))
286
0
      continue;
287
288
0
    if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name))
289
0
      continue;
290
291
0
    if (n == partno) {
292
0
      if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0)
293
0
        break;
294
0
    }
295
0
  }
296
297
0
  closedir(dir);
298
0
  DBG_OBJ(CXT, pc, ul_debug("partno (%d) -> devno (%d)", (int) partno, (int) devno));
299
0
  return devno;
300
0
}
301
302
303
/*
304
 * Returns slave name if there is only one slave, otherwise returns NULL.
305
 * The result should be deallocated by free().
306
 */
307
char *sysfs_blkdev_get_slave(struct path_cxt *pc)
308
0
{
309
0
  DIR *dir;
310
0
  struct dirent *d;
311
0
  char *name = NULL;
312
313
0
  dir = ul_path_opendir(pc, "slaves");
314
0
  if (!dir)
315
0
    return NULL;
316
317
0
  while ((d = xreaddir(dir))) {
318
0
    if (name)
319
0
      goto err; /* more slaves */
320
0
    name = strdup(d->d_name);
321
0
  }
322
323
0
  closedir(dir);
324
0
  return name;
325
0
err:
326
0
  free(name);
327
0
  closedir(dir);
328
0
  return NULL;
329
0
}
330
331
332
0
#define SUBSYSTEM_LINKNAME  "/subsystem"
333
334
/*
335
 * For example:
336
 *
337
 * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
338
 *                           1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
339
 *
340
 * The function check if <chain>/subsystem symlink exists, if yes then returns
341
 * basename of the readlink result, and remove the last subdirectory from the
342
 * <chain> path.
343
 */
344
static char *get_subsystem(char *chain, char *buf, size_t bufsz)
345
0
{
346
0
  size_t len;
347
0
  char *p;
348
349
0
  if (!chain || !*chain)
350
0
    return NULL;
351
352
0
  len = strlen(chain);
353
0
  if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX)
354
0
    return NULL;
355
356
0
  do {
357
0
    ssize_t sz;
358
359
    /* append "/subsystem" to the path */
360
0
    memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME));
361
362
    /* try if subsystem symlink exists */
363
0
    sz = readlink(chain, buf, bufsz - 1);
364
365
    /* remove last subsystem from chain */
366
0
    chain[len] = '\0';
367
0
    p = strrchr(chain, '/');
368
0
    if (p) {
369
0
      *p = '\0';
370
0
      len = p - chain;
371
0
    }
372
373
0
    if (sz > 0) {
374
      /* we found symlink to subsystem, return basename */
375
0
      buf[sz] = '\0';
376
0
      return basename(buf);
377
0
    }
378
379
0
  } while (p);
380
381
0
  return NULL;
382
0
}
383
384
/*
385
 * Returns complete path to the device, the path contains all subsystems
386
 * used for the device.
387
 */
388
char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz)
389
0
{
390
  /* read /sys/dev/block/<maj>:<min> symlink */
391
0
  ssize_t ssz;
392
0
  size_t sz = 0;
393
0
  struct ul_buffer tmp = UL_INIT_BUFFER;
394
0
  const char *p;
395
0
  char *res = NULL;
396
397
0
  ssz = ul_path_readlink(pc, buf, bufsz, NULL);
398
0
  if (ssz <= 0)
399
0
    return NULL;
400
401
0
  if ((p = ul_path_get_prefix(pc)))
402
0
    ul_buffer_append_string(&tmp, p);
403
404
0
  ul_buffer_append_string(&tmp, _PATH_SYS_DEVBLOCK "/");
405
0
  ul_buffer_append_data(&tmp, buf, ssz);
406
407
0
  p = ul_buffer_get_string(&tmp, &sz, NULL);
408
0
  if (p && sz <= bufsz) {
409
0
    memcpy(buf, p, sz);
410
0
    res = buf;
411
0
  }
412
0
  ul_buffer_free_data(&tmp);
413
0
  return res;
414
0
}
415
416
/*
417
 * The @subsys returns the next subsystem in the chain. Function modifies
418
 * @devchain string.
419
 *
420
 * Returns: 0 in success, <0 on error, 1 on end of chain
421
 */
422
int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)),
423
       char *devchain, char **subsys)
424
0
{
425
0
  char subbuf[PATH_MAX];
426
0
  char *sub;
427
428
0
  if (!subsys || !devchain)
429
0
    return -EINVAL;
430
431
0
  *subsys = NULL;
432
433
0
  while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) {
434
0
    *subsys = strdup(sub);
435
0
    if (!*subsys)
436
0
      return -ENOMEM;
437
0
    return 0;
438
0
  }
439
440
0
  return 1;
441
0
}
442
443
0
#define REMOVABLE_FILENAME  "/removable"
444
445
/*
446
 * For example:
447
 *
448
 * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
449
 *                           1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
450
 */
451
static int sysfs_devchain_is_removable(char *chain)
452
0
{
453
0
  size_t len;
454
0
  char buf[20];
455
0
  char *p;
456
457
0
  if (!chain || !*chain)
458
0
    return 0;
459
460
0
  len = strlen(chain);
461
0
  if (len + sizeof(REMOVABLE_FILENAME) > PATH_MAX)
462
0
    return 0;
463
464
0
  do {
465
0
    int fd, rc;
466
467
    /* append "/removable" to the path */
468
0
    memcpy(chain + len, REMOVABLE_FILENAME, sizeof(REMOVABLE_FILENAME));
469
470
    /* root of device hierarchy */
471
0
    if (strcmp(chain, "/sys/dev/block" REMOVABLE_FILENAME) == 0)
472
0
      break;
473
474
    /* try to read it */
475
0
    fd = open(chain, O_RDONLY);
476
0
    if (fd != -1) {
477
0
      rc = ul_read_all(fd, buf, sizeof(buf));
478
0
      close(fd);
479
480
0
      if (rc > 0) {
481
0
        if (strncmp(buf, "fixed", min(rc, 5)) == 0) {
482
0
          return 0;
483
0
        } else if (strncmp(buf, "removable", min(rc, 9)) == 0) {
484
0
          return 1;
485
0
        }
486
0
      }
487
0
    }
488
489
    /* remove last subsystem from chain */
490
0
    chain[len] = '\0';
491
0
    p = strrchr(chain, '/');
492
0
    if (p) {
493
0
      *p = '\0';
494
0
      len = p - chain;
495
0
    }
496
497
0
  } while (p);
498
499
0
  return 0;
500
0
}
501
502
int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc)
503
0
{
504
0
  char buf[PATH_MAX], *chain;
505
506
0
  chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf));
507
0
  return sysfs_devchain_is_removable(chain);
508
0
}
509
510
int sysfs_blkdev_is_removable(struct path_cxt *pc)
511
0
{
512
0
  int rc = 0;
513
514
  // FIXME usb is not actually removable
515
516
  /* check /sys/dev/block/<maj>:<min>/removable attribute */
517
0
  if (ul_path_read_s32(pc, &rc, "removable") == 0)
518
0
    return rc;
519
520
0
  return 0;
521
0
}
522
523
static int get_dm_wholedisk(struct path_cxt *pc, char *diskname,
524
                            size_t len, dev_t *diskdevno)
525
0
{
526
0
  int rc = 0;
527
0
  char *name;
528
529
  /* Note: sysfs_blkdev_get_slave() returns the first slave only;
530
   * if there are more slaves, it returns NULL.
531
   */
532
0
  name = sysfs_blkdev_get_slave(pc);
533
0
  if (!name)
534
0
    return -1;
535
536
0
  if (diskname && len)
537
0
    xstrncpy(diskname, name, len);
538
539
0
  if (diskdevno) {
540
0
    *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
541
0
    if (!*diskdevno)
542
0
      rc = -1;
543
0
  }
544
545
0
  free(name);
546
0
  return rc;
547
0
}
548
549
/*
550
 * Returns by @diskdevno whole disk device devno and (optionally) by
551
 * @diskname the whole disk device name.
552
 */
553
int sysfs_blkdev_get_wholedisk( struct path_cxt *pc,
554
        char *diskname,
555
        size_t len,
556
        dev_t *diskdevno)
557
0
{
558
0
  int is_part = 0;
559
560
0
  if (!pc)
561
0
    return -1;
562
563
0
  is_part = ul_path_access(pc, F_OK, "partition") == 0;
564
0
  if (!is_part) {
565
    /*
566
     * Extra case for partitions mapped by device-mapper.
567
     *
568
     * All regular partitions (added by BLKPG ioctl or kernel PT
569
     * parser) have the /sys/.../partition file. The partitions
570
     * mapped by DM don't have such file, but they have "part"
571
     * prefix in DM UUID.
572
     */
573
0
    char *uuid = NULL, *tmp, *prefix;
574
575
0
    ul_path_read_string(pc, &uuid, "dm/uuid");
576
0
    tmp = uuid;
577
0
    prefix = uuid ? strsep(&tmp, "-") : NULL;
578
579
0
    if (prefix && c_strncasecmp(prefix, "part", 4) == 0)
580
0
      is_part = 1;
581
0
    free(uuid);
582
583
0
    if (is_part &&
584
0
      get_dm_wholedisk(pc, diskname, len, diskdevno) == 0)
585
      /*
586
       * partitioned device, mapped by DM
587
       */
588
0
      goto done;
589
590
0
    is_part = 0;
591
0
  }
592
593
0
  if (!is_part) {
594
    /*
595
     * unpartitioned device
596
     */
597
0
    if (diskname && !sysfs_blkdev_get_name(pc, diskname, len))
598
0
      goto err;
599
0
    if (diskdevno)
600
0
      *diskdevno = sysfs_blkdev_get_devno(pc);
601
0
  } else {
602
    /*
603
     * partitioned device
604
     *  - readlink /sys/dev/block/8:1   = ../../block/sda/sda1
605
     *  - dirname  ../../block/sda/sda1 = ../../block/sda
606
     *  - basename ../../block/sda    = sda
607
     */
608
0
    char linkpath[PATH_MAX];
609
0
    char *name;
610
0
    ssize_t linklen;
611
612
0
    linklen = ul_path_readlink(pc, linkpath, sizeof(linkpath), NULL);
613
0
    if (linklen < 0)
614
0
      goto err;
615
616
0
    stripoff_last_component(linkpath);    /* dirname */
617
0
    name = stripoff_last_component(linkpath);   /* basename */
618
0
    if (!name)
619
0
      goto err;
620
621
0
    sysfs_devname_sys_to_dev(name);
622
0
    if (diskname && len)
623
0
      xstrncpy(diskname, name, len);
624
625
0
    if (diskdevno) {
626
0
      *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
627
0
      if (!*diskdevno)
628
0
        goto err;
629
0
    }
630
0
  }
631
632
0
done:
633
0
  return 0;
634
0
err:
635
0
  return -1;
636
0
}
637
638
int sysfs_devno_to_wholedisk(dev_t devno, char *diskname,
639
                 size_t len, dev_t *diskdevno)
640
0
{
641
0
  struct path_cxt *pc;
642
0
  int rc = 0;
643
644
0
  if (!devno)
645
0
    return -EINVAL;
646
0
  pc = ul_new_sysfs_path(devno, NULL, NULL);
647
0
  if (!pc)
648
0
    return -ENOMEM;
649
650
0
  rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno);
651
0
  ul_unref_path(pc);
652
0
  return rc;
653
0
}
654
655
/*
656
 * Returns 1 if the device is a hidden device-mapper device -- i.e. a device
657
 * that exists only as a building block for another DM target and should not
658
 * be opened by user-space tools (blkid, mkfs, etc.) during udev processing.
659
 *
660
 * Unlike sysfs_devno_is_dm_private(), this does NOT flag Stratis devices.
661
 * Stratis places its own UUID ("stratis-1-private*") in the DM uuid field,
662
 * but its devices are legitimately opened by tools such as mkfs.xfs to
663
 * obtain device geometry.
664
 *
665
 * The @uuid (if not NULL) returns DM device UUID, use free() to deallocate.
666
 */
667
int sysfs_devno_is_dm_hidden(dev_t devno, char **uuid,
668
           const struct ul_vfs_ops *vfs)
669
0
{
670
0
  struct path_cxt *pc = NULL;
671
0
  char *id = NULL;
672
0
  int rc = 0;
673
674
0
  pc = ul_new_sysfs_path(devno, NULL, NULL);
675
0
  if (!pc)
676
0
    goto done;
677
0
  ul_path_refer_vfs(pc, vfs);
678
0
  if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
679
0
    goto done;
680
681
  /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
682
   * is the "LVM" prefix and "-<name>" postfix).
683
   */
684
0
  if (strncmp(id, "LVM-", 4) == 0) {
685
0
    char *p = strrchr(id + 4, '-');
686
687
0
    if (p && *(p + 1))
688
0
      rc = 1;
689
0
  }
690
0
done:
691
0
  ul_unref_path(pc);
692
0
  if (uuid)
693
0
    *uuid = id;
694
0
  else
695
0
    free(id);
696
0
  return rc;
697
0
}
698
699
/*
700
 * Returns 1 if the device is private device mapper device. The @uuid
701
 * (if not NULL) returns DM device UUID, use free() to deallocate.
702
 */
703
int sysfs_devno_is_dm_private(dev_t devno, char **uuid,
704
            const struct ul_vfs_ops *vfs)
705
0
{
706
0
  struct path_cxt *pc = NULL;
707
0
  char *id = NULL;
708
0
  int rc = 0;
709
710
0
  pc = ul_new_sysfs_path(devno, NULL, NULL);
711
0
  if (!pc)
712
0
    goto done;
713
0
  ul_path_refer_vfs(pc, vfs);
714
0
  if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
715
0
    goto done;
716
717
  /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
718
   * is the "LVM" prefix and "-<name>" postfix).
719
   */
720
0
  if (strncmp(id, "LVM-", 4) == 0) {
721
0
    char *p = strrchr(id + 4, '-');
722
723
0
    if (p && *(p + 1))
724
0
      rc = 1;
725
726
  /* Private Stratis devices prefix the UUID with "stratis-1-private"
727
   */
728
0
  } else if (strncmp(id, "stratis-1-private", 17) == 0) {
729
0
    rc = 1;
730
0
  }
731
0
done:
732
0
  ul_unref_path(pc);
733
0
  if (uuid)
734
0
    *uuid = id;
735
0
  else
736
0
    free(id);
737
0
  return rc;
738
0
}
739
740
/*
741
 * Return 0 or 1, or < 0 in case of error
742
 */
743
int sysfs_devno_is_wholedisk(dev_t devno)
744
0
{
745
0
  dev_t disk;
746
747
0
  if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
748
0
    return -1;
749
750
0
  return devno == disk;
751
0
}
752
753
754
int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l)
755
0
{
756
0
  char buf[PATH_MAX], *hctl;
757
0
  struct sysfs_blkdev *blk;
758
0
  ssize_t len;
759
760
0
  blk = ul_path_get_dialect(pc);
761
762
0
  if (!blk || blk->hctl_error)
763
0
    return -EINVAL;
764
0
  if (blk->has_hctl)
765
0
    goto done;
766
767
0
  blk->hctl_error = 1;
768
0
  len = ul_path_readlink(pc, buf, sizeof(buf), "device");
769
0
  if (len < 0)
770
0
    return len;
771
772
0
  hctl = strrchr(buf, '/');
773
0
  if (!hctl)
774
0
    return -1;
775
0
  hctl++;
776
777
0
  if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel,
778
0
        &blk->scsi_target, &blk->scsi_lun) != 4)
779
0
    return -1;
780
781
0
  blk->has_hctl = 1;
782
0
done:
783
0
  if (h)
784
0
    *h = blk->scsi_host;
785
0
  if (c)
786
0
    *c = blk->scsi_channel;
787
0
  if (t)
788
0
    *t = blk->scsi_target;
789
0
  if (l)
790
0
    *l = blk->scsi_lun;
791
792
0
  blk->hctl_error = 0;
793
0
  return 0;
794
0
}
795
796
797
static char *scsi_host_attribute_path(
798
      struct path_cxt *pc,
799
      const char *type,
800
      char *buf,
801
      size_t bufsz,
802
      const char *attr)
803
0
{
804
0
  int len;
805
0
  int host;
806
0
  const char *prefix;
807
808
0
  if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL))
809
0
    return NULL;
810
811
0
  prefix = ul_path_get_prefix(pc);
812
0
  if (!prefix)
813
0
    prefix = "";
814
815
0
  if (attr)
816
0
    len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s",
817
0
        prefix, _PATH_SYS_CLASS, type, host, attr);
818
0
  else
819
0
    len = snprintf(buf, bufsz, "%s%s/%s_host/host%d",
820
0
        prefix, _PATH_SYS_CLASS, type, host);
821
822
0
  return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
823
0
}
824
825
char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc,
826
      const char *type, const char *attr)
827
0
{
828
0
  char buf[1024];
829
0
  int rc;
830
0
  FILE *f;
831
832
0
  if (!attr || !type ||
833
0
      !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr))
834
0
    return NULL;
835
836
0
  if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
837
0
    return NULL;
838
839
0
  rc = fscanf(f, "%1023[^\n]", buf);
840
0
  fclose(f);
841
842
0
  return rc == 1 ? strdup(buf) : NULL;
843
0
}
844
845
int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type)
846
0
{
847
0
  char buf[PATH_MAX];
848
0
  struct stat st;
849
850
0
  if (!type || !scsi_host_attribute_path(pc, type,
851
0
        buf, sizeof(buf), NULL))
852
0
    return 0;
853
854
0
  return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
855
0
}
856
857
static char *scsi_attribute_path(struct path_cxt *pc,
858
    char *buf, size_t bufsz, const char *attr)
859
0
{
860
0
  int len, h, c, t, l;
861
0
  const char *prefix;
862
863
0
  if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0)
864
0
    return NULL;
865
866
0
  prefix = ul_path_get_prefix(pc);
867
0
  if (!prefix)
868
0
    prefix = "";
869
870
0
  if (attr)
871
0
    len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s",
872
0
        prefix, _PATH_SYS_SCSI,
873
0
        h,c,t,l, attr);
874
0
  else
875
0
    len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d",
876
0
        prefix, _PATH_SYS_SCSI,
877
0
        h,c,t,l);
878
0
  return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
879
0
}
880
881
int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr)
882
0
{
883
0
  char path[PATH_MAX];
884
0
  struct stat st;
885
886
0
  if (!scsi_attribute_path(pc, path, sizeof(path), attr))
887
0
    return 0;
888
889
0
  return stat(path, &st) == 0;
890
0
}
891
892
int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern)
893
0
{
894
0
  char path[PATH_MAX], linkc[PATH_MAX];
895
0
  struct stat st;
896
0
  ssize_t len;
897
898
0
  if (!scsi_attribute_path(pc, path, sizeof(path), NULL))
899
0
    return 0;
900
901
0
  if (stat(path, &st) != 0)
902
0
    return 0;
903
904
0
  len = readlink(path, linkc, sizeof(linkc) - 1);
905
0
  if (len < 0)
906
0
    return 0;
907
908
0
  linkc[len] = '\0';
909
0
  return strstr(linkc, pattern) != NULL;
910
0
}
911
912
static dev_t read_devno(const char *path)
913
0
{
914
0
  FILE *f;
915
0
  int maj = 0, min = 0;
916
0
  dev_t dev = 0;
917
918
0
  f = fopen(path, "r" UL_CLOEXECSTR);
919
0
  if (!f)
920
0
    return 0;
921
922
0
  if (fscanf(f, "%d:%d", &maj, &min) == 2)
923
0
    dev = makedev(maj, min);
924
0
  fclose(f);
925
0
  return dev;
926
0
}
927
928
int sysfs_devname_is_hidden(const char *prefix, const char *name)
929
0
{
930
0
  char buf[PATH_MAX];
931
0
  int rc = 0, hidden = 0, len;
932
0
  FILE *f;
933
934
0
  if (strncmp("/dev/", name, 5) == 0)
935
0
    return 0;
936
937
0
  if (!prefix)
938
0
    prefix = "";
939
  /*
940
   * Create path to /sys/block/<name>/hidden
941
   */
942
0
  len = snprintf(buf, sizeof(buf),
943
0
      "%s" _PATH_SYS_BLOCK "/%s/hidden",
944
0
      prefix, name);
945
946
0
  if (len < 0 || (size_t) len + 1 > sizeof(buf))
947
0
    return 0;
948
949
0
  f = fopen(buf, "r" UL_CLOEXECSTR);
950
0
  if (!f)
951
0
    return 0;
952
953
0
  rc = fscanf(f, "%d", &hidden);
954
0
  fclose(f);
955
956
0
  return rc == 1 ? hidden : 0;
957
0
}
958
959
960
dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent)
961
0
{
962
0
  char buf[PATH_MAX];
963
0
  char *_name = NULL, *_parent = NULL; /* name as encoded in sysfs */
964
0
  dev_t dev = 0;
965
0
  int len;
966
967
0
  if (!prefix)
968
0
    prefix = "";
969
970
0
  assert(name);
971
972
0
  if (strncmp("/dev/", name, 5) == 0) {
973
    /*
974
     * Read from /dev
975
     */
976
0
    struct stat st;
977
978
0
    if (stat(name, &st) == 0) {
979
0
      dev = st.st_rdev;
980
0
      goto done;
981
0
    }
982
0
    name += 5;  /* unaccessible, or not node in /dev */
983
0
  }
984
985
0
  _name = strdup(name);
986
0
  if (!_name)
987
0
    goto done;
988
0
  sysfs_devname_dev_to_sys(_name);
989
990
0
  if (parent) {
991
0
    _parent = strdup(parent);
992
0
    if (!_parent)
993
0
      goto done;
994
0
  }
995
996
0
  if (parent && strncmp("dm-", name, 3) != 0) {
997
    /*
998
     * Create path to /sys/block/<parent>/<name>/dev
999
     */
1000
0
    sysfs_devname_dev_to_sys(_parent);
1001
0
    len = snprintf(buf, sizeof(buf),
1002
0
        "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
1003
0
        prefix, _parent, _name);
1004
0
    if (len < 0 || (size_t) len >= sizeof(buf))
1005
0
      goto done;
1006
1007
    /* don't try anything else for dm-* */
1008
0
    dev = read_devno(buf);
1009
0
    goto done;
1010
0
  }
1011
1012
  /*
1013
   * Read from /sys/block/<sysname>/dev
1014
   */
1015
0
  len = snprintf(buf, sizeof(buf),
1016
0
      "%s" _PATH_SYS_BLOCK "/%s/dev",
1017
0
      prefix, _name);
1018
0
  if (len < 0 || (size_t) len >= sizeof(buf))
1019
0
    goto done;
1020
0
  dev = read_devno(buf);
1021
1022
  /*
1023
   * Read from /sys/block/<parent>/<partition>/dev
1024
   */
1025
0
  if (!dev && parent && ul_startswith(name, parent)) {
1026
0
    len = snprintf(buf, sizeof(buf),
1027
0
        "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
1028
0
        prefix, _parent, _name);
1029
0
    if (len < 0 || (size_t) len >= sizeof(buf))
1030
0
      goto done;
1031
0
    dev = read_devno(buf);
1032
0
  }
1033
1034
  /*
1035
   * Read from /sys/block/<sysname>/device/dev
1036
   */
1037
0
  if (!dev) {
1038
0
    len = snprintf(buf, sizeof(buf),
1039
0
        "%s" _PATH_SYS_BLOCK "/%s/device/dev",
1040
0
        prefix, _name);
1041
0
    if (len < 0 || (size_t) len >= sizeof(buf))
1042
0
      goto done;
1043
0
    dev = read_devno(buf);
1044
0
  }
1045
0
done:
1046
0
  free(_name);
1047
0
  free(_parent);
1048
0
  return dev;
1049
0
}
1050
1051
dev_t sysfs_devname_to_devno(const char *name)
1052
0
{
1053
0
  return __sysfs_devname_to_devno(NULL, name, NULL);
1054
0
}
1055
1056
char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz)
1057
0
{
1058
0
  const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz);
1059
0
  char *res = NULL;
1060
0
  size_t sz;
1061
0
  struct stat st;
1062
1063
0
  if (!name)
1064
0
    goto done;
1065
1066
0
  sz = strlen(name);
1067
0
  if (sz + sizeof("/dev/") > bufsiz)
1068
0
    goto done;
1069
1070
  /* create the final "/dev/<name>" string */
1071
0
  memmove(buf + 5, name, sz + 1);
1072
0
  memcpy(buf, "/dev/", 5);
1073
1074
0
  if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc))
1075
0
    res = buf;
1076
0
done:
1077
0
  return res;
1078
0
}
1079
1080
dev_t sysfs_blkdev_get_devno(struct path_cxt *pc)
1081
0
{
1082
0
  return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno;
1083
0
}
1084
1085
/*
1086
 * Returns devname (e.g. "/dev/sda1") for the given devno.
1087
 *
1088
 * Please, use more robust blkid_devno_to_devname() in your applications.
1089
 */
1090
char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
1091
0
{
1092
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1093
0
  char *res = NULL;
1094
1095
0
  if (pc) {
1096
0
    res = sysfs_blkdev_get_path(pc, buf, bufsiz);
1097
0
    ul_unref_path(pc);
1098
0
  }
1099
0
  return res;
1100
0
}
1101
1102
char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
1103
0
{
1104
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1105
0
  char *res = NULL;
1106
1107
0
  if (pc) {
1108
0
    res = sysfs_blkdev_get_name(pc, buf, bufsiz);
1109
0
    ul_unref_path(pc);
1110
0
  }
1111
0
  return res;
1112
0
}
1113
1114
int sysfs_devno_count_partitions(dev_t devno)
1115
0
{
1116
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1117
0
  int n = 0;
1118
1119
0
  if (pc) {
1120
0
    char buf[PATH_MAX + 1];
1121
0
    char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf));
1122
1123
0
    n = sysfs_blkdev_count_partitions(pc, name);
1124
0
    ul_unref_path(pc);
1125
0
  }
1126
0
  return n;
1127
0
}
1128
1129
char *sysfs_chrdev_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
1130
0
{
1131
0
  char link[PATH_MAX];
1132
0
  struct path_cxt *pc;
1133
0
  char *name;
1134
0
  ssize_t sz;
1135
1136
0
  pc = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u", major(devno), minor(devno));
1137
0
  if (!pc)
1138
0
    return NULL;
1139
1140
  /* read /sys/dev/char/<maj:min> link */
1141
0
  sz = ul_path_readlink(pc, link, sizeof(link), NULL);
1142
0
  ul_unref_path(pc);
1143
1144
0
  if (sz < 0)
1145
0
    return NULL;
1146
1147
0
  name = strrchr(link, '/');
1148
0
  if (!name)
1149
0
    return NULL;
1150
1151
0
  name++;
1152
0
  sz = strlen(name);
1153
0
  if ((size_t) sz + 1 > bufsiz)
1154
0
    return NULL;
1155
1156
0
  memcpy(buf, name, sz + 1);
1157
0
  sysfs_devname_sys_to_dev(buf);
1158
0
  return buf;
1159
1160
0
}
1161
1162
enum sysfs_byteorder sysfs_get_byteorder(struct path_cxt *pc)
1163
0
{
1164
0
  int rc;
1165
0
  char buf[BUFSIZ];
1166
0
  enum sysfs_byteorder ret;
1167
1168
0
  rc = ul_path_read_buffer(pc, buf, sizeof(buf), _PATH_SYS_CPU_BYTEORDER);
1169
0
  if (rc < 0)
1170
0
    goto unknown;
1171
1172
0
  if (strncmp(buf, "little", sizeof(buf)) == 0) {
1173
0
    ret = SYSFS_BYTEORDER_LITTLE;
1174
0
    goto out;
1175
0
  } else if (strncmp(buf, "big", sizeof(buf)) == 0) {
1176
0
    ret = SYSFS_BYTEORDER_BIG;
1177
0
    goto out;
1178
0
  }
1179
1180
0
unknown:
1181
0
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1182
0
  ret = SYSFS_BYTEORDER_LITTLE;
1183
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1184
  ret = SYSFS_BYTEORDER_BIG;
1185
#else
1186
#error Unknown byte order
1187
#endif
1188
1189
0
out:
1190
0
  return ret;
1191
0
}
1192
1193
int sysfs_get_address_bits(struct path_cxt *pc)
1194
0
{
1195
0
  int rc;
1196
0
  int address_bits;
1197
1198
0
  rc = ul_path_scanf(pc, _PATH_SYS_ADDRESS_BITS, "%d", &address_bits);
1199
0
  if (rc < 0)
1200
0
    return rc;
1201
0
  if (address_bits < 0)
1202
0
    return -EINVAL;
1203
0
  return address_bits;
1204
0
}
1205
1206
1207
#ifdef TEST_PROGRAM_SYSFS
1208
#include <errno.h>
1209
#include <err.h>
1210
#include <stdlib.h>
1211
1212
int main(int argc, char *argv[])
1213
{
1214
  struct path_cxt *pc;
1215
  char *devname;
1216
  dev_t devno, disk_devno;
1217
  char path[PATH_MAX], *sub, *chain;
1218
  char diskname[32];
1219
  int i, is_part, rc = EXIT_SUCCESS;
1220
  uint64_t u64;
1221
1222
  if (argc != 2)
1223
    errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
1224
1225
  ul_sysfs_init_debug();
1226
1227
  devname = argv[1];
1228
  devno = sysfs_devname_to_devno(devname);
1229
1230
  if (!devno)
1231
    err(EXIT_FAILURE, "failed to read devno");
1232
1233
  printf("non-context:\n");
1234
  printf(" DEVNO:   %u (%u:%u)\n", (unsigned int) devno, major(devno), minor(devno));
1235
  printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path)));
1236
  printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
1237
1238
  sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1239
  printf(" WHOLEDISK-DEVNO:   %u (%u:%u)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1240
  printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1241
1242
  pc = ul_new_sysfs_path(devno, NULL, NULL);
1243
  if (!pc)
1244
    goto done;
1245
1246
  printf("context based:\n");
1247
  devno = sysfs_blkdev_get_devno(pc);
1248
  printf(" DEVNO:   %u (%u:%u)\n", (unsigned int) devno, major(devno), minor(devno));
1249
  printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path)));
1250
  printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path)));
1251
1252
  sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1253
  printf(" WHOLEDISK-DEVNO: %u (%u:%u)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1254
  printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1255
1256
  is_part = ul_path_access(pc, F_OK, "partition") == 0;
1257
  printf(" PARTITION: %s\n", is_part ? "YES" : "NOT");
1258
1259
  if (is_part && disk_devno) {
1260
    struct path_cxt *disk_pc =  ul_new_sysfs_path(disk_devno, NULL, NULL);
1261
    sysfs_blkdev_set_parent(pc, disk_pc);
1262
1263
    ul_unref_path(disk_pc);
1264
  }
1265
1266
  printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no");
1267
  printf(" REMOVABLE: %s\n", sysfs_blkdev_is_removable(pc) ? "yes" : "no");
1268
  printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves"));
1269
1270
  if (!is_part) {
1271
    printf("First 5 partitions:\n");
1272
    for (i = 1; i <= 5; i++) {
1273
      dev_t dev = sysfs_blkdev_partno_to_devno(pc, i);
1274
      if (dev)
1275
        printf("\t#%d %u:%u\n", i, major(dev), minor(dev));
1276
    }
1277
  }
1278
1279
  if (ul_path_read_u64(pc, &u64, "size") != 0)
1280
    printf(" (!) read SIZE failed\n");
1281
  else
1282
    printf(" SIZE: %"PRIu64"\n", u64);
1283
1284
  if (ul_path_read_s32(pc, &i, "queue/hw_sector_size"))
1285
    printf(" (!) read SECTOR failed\n");
1286
  else
1287
    printf(" SECTOR: %d\n", i);
1288
1289
1290
  chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path));
1291
  printf(" SUBSYSTEMS:\n");
1292
1293
  while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
1294
    printf("\t%s\n", sub);
1295
    free(sub);
1296
  }
1297
1298
  rc = EXIT_SUCCESS;
1299
done:
1300
  ul_unref_path(pc);
1301
  return rc;
1302
}
1303
#endif /* TEST_PROGRAM_SYSFS */