Coverage Report

Created: 2025-06-22 06:56

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