Coverage Report

Created: 2026-01-07 06:10

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