Coverage Report

Created: 2026-04-11 06:29

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 = 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 private device mapper device. The @uuid
657
 * (if not NULL) returns DM device UUID, use free() to deallocate.
658
 */
659
int sysfs_devno_is_dm_private(dev_t devno, char **uuid)
660
0
{
661
0
  struct path_cxt *pc = NULL;
662
0
  char *id = NULL;
663
0
  int rc = 0;
664
665
0
  pc = ul_new_sysfs_path(devno, NULL, NULL);
666
0
  if (!pc)
667
0
    goto done;
668
0
  if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
669
0
    goto done;
670
671
  /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
672
   * is the "LVM" prefix and "-<name>" postfix).
673
   */
674
0
  if (strncmp(id, "LVM-", 4) == 0) {
675
0
    char *p = strrchr(id + 4, '-');
676
677
0
    if (p && *(p + 1))
678
0
      rc = 1;
679
680
  /* Private Stratis devices prefix the UUID with "stratis-1-private"
681
   */
682
0
  } else if (strncmp(id, "stratis-1-private", 17) == 0) {
683
0
    rc = 1;
684
0
  }
685
0
done:
686
0
  ul_unref_path(pc);
687
0
  if (uuid)
688
0
    *uuid = id;
689
0
  else
690
0
    free(id);
691
0
  return rc;
692
0
}
693
694
/*
695
 * Return 0 or 1, or < 0 in case of error
696
 */
697
int sysfs_devno_is_wholedisk(dev_t devno)
698
0
{
699
0
  dev_t disk;
700
701
0
  if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
702
0
    return -1;
703
704
0
  return devno == disk;
705
0
}
706
707
708
int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l)
709
0
{
710
0
  char buf[PATH_MAX], *hctl;
711
0
  struct sysfs_blkdev *blk;
712
0
  ssize_t len;
713
714
0
  blk = ul_path_get_dialect(pc);
715
716
0
  if (!blk || blk->hctl_error)
717
0
    return -EINVAL;
718
0
  if (blk->has_hctl)
719
0
    goto done;
720
721
0
  blk->hctl_error = 1;
722
0
  len = ul_path_readlink(pc, buf, sizeof(buf), "device");
723
0
  if (len < 0)
724
0
    return len;
725
726
0
  hctl = strrchr(buf, '/');
727
0
  if (!hctl)
728
0
    return -1;
729
0
  hctl++;
730
731
0
  if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel,
732
0
        &blk->scsi_target, &blk->scsi_lun) != 4)
733
0
    return -1;
734
735
0
  blk->has_hctl = 1;
736
0
done:
737
0
  if (h)
738
0
    *h = blk->scsi_host;
739
0
  if (c)
740
0
    *c = blk->scsi_channel;
741
0
  if (t)
742
0
    *t = blk->scsi_target;
743
0
  if (l)
744
0
    *l = blk->scsi_lun;
745
746
0
  blk->hctl_error = 0;
747
0
  return 0;
748
0
}
749
750
751
static char *scsi_host_attribute_path(
752
      struct path_cxt *pc,
753
      const char *type,
754
      char *buf,
755
      size_t bufsz,
756
      const char *attr)
757
0
{
758
0
  int len;
759
0
  int host;
760
0
  const char *prefix;
761
762
0
  if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL))
763
0
    return NULL;
764
765
0
  prefix = ul_path_get_prefix(pc);
766
0
  if (!prefix)
767
0
    prefix = "";
768
769
0
  if (attr)
770
0
    len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s",
771
0
        prefix, _PATH_SYS_CLASS, type, host, attr);
772
0
  else
773
0
    len = snprintf(buf, bufsz, "%s%s/%s_host/host%d",
774
0
        prefix, _PATH_SYS_CLASS, type, host);
775
776
0
  return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
777
0
}
778
779
char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc,
780
      const char *type, const char *attr)
781
0
{
782
0
  char buf[1024];
783
0
  int rc;
784
0
  FILE *f;
785
786
0
  if (!attr || !type ||
787
0
      !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr))
788
0
    return NULL;
789
790
0
  if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
791
0
    return NULL;
792
793
0
  rc = fscanf(f, "%1023[^\n]", buf);
794
0
  fclose(f);
795
796
0
  return rc == 1 ? strdup(buf) : NULL;
797
0
}
798
799
int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type)
800
0
{
801
0
  char buf[PATH_MAX];
802
0
  struct stat st;
803
804
0
  if (!type || !scsi_host_attribute_path(pc, type,
805
0
        buf, sizeof(buf), NULL))
806
0
    return 0;
807
808
0
  return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
809
0
}
810
811
static char *scsi_attribute_path(struct path_cxt *pc,
812
    char *buf, size_t bufsz, const char *attr)
813
0
{
814
0
  int len, h, c, t, l;
815
0
  const char *prefix;
816
817
0
  if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0)
818
0
    return NULL;
819
820
0
  prefix = ul_path_get_prefix(pc);
821
0
  if (!prefix)
822
0
    prefix = "";
823
824
0
  if (attr)
825
0
    len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s",
826
0
        prefix, _PATH_SYS_SCSI,
827
0
        h,c,t,l, attr);
828
0
  else
829
0
    len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d",
830
0
        prefix, _PATH_SYS_SCSI,
831
0
        h,c,t,l);
832
0
  return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
833
0
}
834
835
int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr)
836
0
{
837
0
  char path[PATH_MAX];
838
0
  struct stat st;
839
840
0
  if (!scsi_attribute_path(pc, path, sizeof(path), attr))
841
0
    return 0;
842
843
0
  return stat(path, &st) == 0;
844
0
}
845
846
int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern)
847
0
{
848
0
  char path[PATH_MAX], linkc[PATH_MAX];
849
0
  struct stat st;
850
0
  ssize_t len;
851
852
0
  if (!scsi_attribute_path(pc, path, sizeof(path), NULL))
853
0
    return 0;
854
855
0
  if (stat(path, &st) != 0)
856
0
    return 0;
857
858
0
  len = readlink(path, linkc, sizeof(linkc) - 1);
859
0
  if (len < 0)
860
0
    return 0;
861
862
0
  linkc[len] = '\0';
863
0
  return strstr(linkc, pattern) != NULL;
864
0
}
865
866
static dev_t read_devno(const char *path)
867
0
{
868
0
  FILE *f;
869
0
  int maj = 0, min = 0;
870
0
  dev_t dev = 0;
871
872
0
  f = fopen(path, "r" UL_CLOEXECSTR);
873
0
  if (!f)
874
0
    return 0;
875
876
0
  if (fscanf(f, "%d:%d", &maj, &min) == 2)
877
0
    dev = makedev(maj, min);
878
0
  fclose(f);
879
0
  return dev;
880
0
}
881
882
int sysfs_devname_is_hidden(const char *prefix, const char *name)
883
0
{
884
0
  char buf[PATH_MAX];
885
0
  int rc = 0, hidden = 0, len;
886
0
  FILE *f;
887
888
0
  if (strncmp("/dev/", name, 5) == 0)
889
0
    return 0;
890
891
0
  if (!prefix)
892
0
    prefix = "";
893
  /*
894
   * Create path to /sys/block/<name>/hidden
895
   */
896
0
  len = snprintf(buf, sizeof(buf),
897
0
      "%s" _PATH_SYS_BLOCK "/%s/hidden",
898
0
      prefix, name);
899
900
0
  if (len < 0 || (size_t) len + 1 > sizeof(buf))
901
0
    return 0;
902
903
0
  f = fopen(buf, "r" UL_CLOEXECSTR);
904
0
  if (!f)
905
0
    return 0;
906
907
0
  rc = fscanf(f, "%d", &hidden);
908
0
  fclose(f);
909
910
0
  return rc == 1 ? hidden : 0;
911
0
}
912
913
914
dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent)
915
0
{
916
0
  char buf[PATH_MAX];
917
0
  char *_name = NULL, *_parent = NULL; /* name as encoded in sysfs */
918
0
  dev_t dev = 0;
919
0
  int len;
920
921
0
  if (!prefix)
922
0
    prefix = "";
923
924
0
  assert(name);
925
926
0
  if (strncmp("/dev/", name, 5) == 0) {
927
    /*
928
     * Read from /dev
929
     */
930
0
    struct stat st;
931
932
0
    if (stat(name, &st) == 0) {
933
0
      dev = st.st_rdev;
934
0
      goto done;
935
0
    }
936
0
    name += 5;  /* unaccessible, or not node in /dev */
937
0
  }
938
939
0
  _name = strdup(name);
940
0
  if (!_name)
941
0
    goto done;
942
0
  sysfs_devname_dev_to_sys(_name);
943
944
0
  if (parent) {
945
0
    _parent = strdup(parent);
946
0
    if (!_parent)
947
0
      goto done;
948
0
  }
949
950
0
  if (parent && strncmp("dm-", name, 3) != 0) {
951
    /*
952
     * Create path to /sys/block/<parent>/<name>/dev
953
     */
954
0
    sysfs_devname_dev_to_sys(_parent);
955
0
    len = snprintf(buf, sizeof(buf),
956
0
        "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
957
0
        prefix, _parent, _name);
958
0
    if (len < 0 || (size_t) len >= sizeof(buf))
959
0
      goto done;
960
961
    /* don't try anything else for dm-* */
962
0
    dev = read_devno(buf);
963
0
    goto done;
964
0
  }
965
966
  /*
967
   * Read from /sys/block/<sysname>/dev
968
   */
969
0
  len = snprintf(buf, sizeof(buf),
970
0
      "%s" _PATH_SYS_BLOCK "/%s/dev",
971
0
      prefix, _name);
972
0
  if (len < 0 || (size_t) len >= sizeof(buf))
973
0
    goto done;
974
0
  dev = read_devno(buf);
975
976
  /*
977
   * Read from /sys/block/<parent>/<partition>/dev
978
   */
979
0
  if (!dev && parent && ul_startswith(name, parent)) {
980
0
    len = snprintf(buf, sizeof(buf),
981
0
        "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
982
0
        prefix, _parent, _name);
983
0
    if (len < 0 || (size_t) len >= sizeof(buf))
984
0
      goto done;
985
0
    dev = read_devno(buf);
986
0
  }
987
988
  /*
989
   * Read from /sys/block/<sysname>/device/dev
990
   */
991
0
  if (!dev) {
992
0
    len = snprintf(buf, sizeof(buf),
993
0
        "%s" _PATH_SYS_BLOCK "/%s/device/dev",
994
0
        prefix, _name);
995
0
    if (len < 0 || (size_t) len >= sizeof(buf))
996
0
      goto done;
997
0
    dev = read_devno(buf);
998
0
  }
999
0
done:
1000
0
  free(_name);
1001
0
  free(_parent);
1002
0
  return dev;
1003
0
}
1004
1005
dev_t sysfs_devname_to_devno(const char *name)
1006
0
{
1007
0
  return __sysfs_devname_to_devno(NULL, name, NULL);
1008
0
}
1009
1010
char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz)
1011
0
{
1012
0
  const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz);
1013
0
  char *res = NULL;
1014
0
  size_t sz;
1015
0
  struct stat st;
1016
1017
0
  if (!name)
1018
0
    goto done;
1019
1020
0
  sz = strlen(name);
1021
0
  if (sz + sizeof("/dev/") > bufsiz)
1022
0
    goto done;
1023
1024
  /* create the final "/dev/<name>" string */
1025
0
  memmove(buf + 5, name, sz + 1);
1026
0
  memcpy(buf, "/dev/", 5);
1027
1028
0
  if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc))
1029
0
    res = buf;
1030
0
done:
1031
0
  return res;
1032
0
}
1033
1034
dev_t sysfs_blkdev_get_devno(struct path_cxt *pc)
1035
0
{
1036
0
  return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno;
1037
0
}
1038
1039
/*
1040
 * Returns devname (e.g. "/dev/sda1") for the given devno.
1041
 *
1042
 * Please, use more robust blkid_devno_to_devname() in your applications.
1043
 */
1044
char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
1045
0
{
1046
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1047
0
  char *res = NULL;
1048
1049
0
  if (pc) {
1050
0
    res = sysfs_blkdev_get_path(pc, buf, bufsiz);
1051
0
    ul_unref_path(pc);
1052
0
  }
1053
0
  return res;
1054
0
}
1055
1056
char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
1057
0
{
1058
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1059
0
  char *res = NULL;
1060
1061
0
  if (pc) {
1062
0
    res = sysfs_blkdev_get_name(pc, buf, bufsiz);
1063
0
    ul_unref_path(pc);
1064
0
  }
1065
0
  return res;
1066
0
}
1067
1068
int sysfs_devno_count_partitions(dev_t devno)
1069
0
{
1070
0
  struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1071
0
  int n = 0;
1072
1073
0
  if (pc) {
1074
0
    char buf[PATH_MAX + 1];
1075
0
    char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf));
1076
1077
0
    n = sysfs_blkdev_count_partitions(pc, name);
1078
0
    ul_unref_path(pc);
1079
0
  }
1080
0
  return n;
1081
0
}
1082
1083
char *sysfs_chrdev_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
1084
0
{
1085
0
  char link[PATH_MAX];
1086
0
  struct path_cxt *pc;
1087
0
  char *name;
1088
0
  ssize_t sz;
1089
1090
0
  pc = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u", major(devno), minor(devno));
1091
0
  if (!pc)
1092
0
    return NULL;
1093
1094
  /* read /sys/dev/char/<maj:min> link */
1095
0
  sz = ul_path_readlink(pc, link, sizeof(link), NULL);
1096
0
  ul_unref_path(pc);
1097
1098
0
  if (sz < 0)
1099
0
    return NULL;
1100
1101
0
  name = strrchr(link, '/');
1102
0
  if (!name)
1103
0
    return NULL;
1104
1105
0
  name++;
1106
0
  sz = strlen(name);
1107
0
  if ((size_t) sz + 1 > bufsiz)
1108
0
    return NULL;
1109
1110
0
  memcpy(buf, name, sz + 1);
1111
0
  sysfs_devname_sys_to_dev(buf);
1112
0
  return buf;
1113
1114
0
}
1115
1116
enum sysfs_byteorder sysfs_get_byteorder(struct path_cxt *pc)
1117
0
{
1118
0
  int rc;
1119
0
  char buf[BUFSIZ];
1120
0
  enum sysfs_byteorder ret;
1121
1122
0
  rc = ul_path_read_buffer(pc, buf, sizeof(buf), _PATH_SYS_CPU_BYTEORDER);
1123
0
  if (rc < 0)
1124
0
    goto unknown;
1125
1126
0
  if (strncmp(buf, "little", sizeof(buf)) == 0) {
1127
0
    ret = SYSFS_BYTEORDER_LITTLE;
1128
0
    goto out;
1129
0
  } else if (strncmp(buf, "big", sizeof(buf)) == 0) {
1130
0
    ret = SYSFS_BYTEORDER_BIG;
1131
0
    goto out;
1132
0
  }
1133
1134
0
unknown:
1135
0
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1136
0
  ret = SYSFS_BYTEORDER_LITTLE;
1137
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1138
  ret = SYSFS_BYTEORDER_BIG;
1139
#else
1140
#error Unknown byte order
1141
#endif
1142
1143
0
out:
1144
0
  return ret;
1145
0
}
1146
1147
int sysfs_get_address_bits(struct path_cxt *pc)
1148
0
{
1149
0
  int rc;
1150
0
  int address_bits;
1151
1152
0
  rc = ul_path_scanf(pc, _PATH_SYS_ADDRESS_BITS, "%d", &address_bits);
1153
0
  if (rc < 0)
1154
0
    return rc;
1155
0
  if (address_bits < 0)
1156
0
    return -EINVAL;
1157
0
  return address_bits;
1158
0
}
1159
1160
1161
#ifdef TEST_PROGRAM_SYSFS
1162
#include <errno.h>
1163
#include <err.h>
1164
#include <stdlib.h>
1165
1166
int main(int argc, char *argv[])
1167
{
1168
  struct path_cxt *pc;
1169
  char *devname;
1170
  dev_t devno, disk_devno;
1171
  char path[PATH_MAX], *sub, *chain;
1172
  char diskname[32];
1173
  int i, is_part, rc = EXIT_SUCCESS;
1174
  uint64_t u64;
1175
1176
  if (argc != 2)
1177
    errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
1178
1179
  ul_sysfs_init_debug();
1180
1181
  devname = argv[1];
1182
  devno = sysfs_devname_to_devno(devname);
1183
1184
  if (!devno)
1185
    err(EXIT_FAILURE, "failed to read devno");
1186
1187
  printf("non-context:\n");
1188
  printf(" DEVNO:   %u (%u:%u)\n", (unsigned int) devno, major(devno), minor(devno));
1189
  printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path)));
1190
  printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
1191
1192
  sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1193
  printf(" WHOLEDISK-DEVNO:   %u (%u:%u)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1194
  printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1195
1196
  pc = ul_new_sysfs_path(devno, NULL, NULL);
1197
  if (!pc)
1198
    goto done;
1199
1200
  printf("context based:\n");
1201
  devno = sysfs_blkdev_get_devno(pc);
1202
  printf(" DEVNO:   %u (%u:%u)\n", (unsigned int) devno, major(devno), minor(devno));
1203
  printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path)));
1204
  printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path)));
1205
1206
  sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1207
  printf(" WHOLEDISK-DEVNO: %u (%u:%u)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1208
  printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1209
1210
  is_part = ul_path_access(pc, F_OK, "partition") == 0;
1211
  printf(" PARTITION: %s\n", is_part ? "YES" : "NOT");
1212
1213
  if (is_part && disk_devno) {
1214
    struct path_cxt *disk_pc =  ul_new_sysfs_path(disk_devno, NULL, NULL);
1215
    sysfs_blkdev_set_parent(pc, disk_pc);
1216
1217
    ul_unref_path(disk_pc);
1218
  }
1219
1220
  printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no");
1221
  printf(" REMOVABLE: %s\n", sysfs_blkdev_is_removable(pc) ? "yes" : "no");
1222
  printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves"));
1223
1224
  if (!is_part) {
1225
    printf("First 5 partitions:\n");
1226
    for (i = 1; i <= 5; i++) {
1227
      dev_t dev = sysfs_blkdev_partno_to_devno(pc, i);
1228
      if (dev)
1229
        printf("\t#%d %u:%u\n", i, major(dev), minor(dev));
1230
    }
1231
  }
1232
1233
  if (ul_path_read_u64(pc, &u64, "size") != 0)
1234
    printf(" (!) read SIZE failed\n");
1235
  else
1236
    printf(" SIZE: %"PRIu64"\n", u64);
1237
1238
  if (ul_path_read_s32(pc, &i, "queue/hw_sector_size"))
1239
    printf(" (!) read SECTOR failed\n");
1240
  else
1241
    printf(" SECTOR: %d\n", i);
1242
1243
1244
  chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path));
1245
  printf(" SUBSYSTEMS:\n");
1246
1247
  while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
1248
    printf("\t%s\n", sub);
1249
    free(sub);
1250
  }
1251
1252
  rc = EXIT_SUCCESS;
1253
done:
1254
  ul_unref_path(pc);
1255
  return rc;
1256
}
1257
#endif /* TEST_PROGRAM_SYSFS */