Coverage Report

Created: 2026-10-16 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/blkdev.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>
6
 */
7
#include <sys/types.h>
8
#include <sys/stat.h>
9
#include <sys/file.h>
10
#include <sys/ioctl.h>
11
#include <errno.h>
12
#include <unistd.h>
13
#include <stdint.h>
14
15
#ifdef HAVE_LINUX_FD_H
16
#include <linux/fd.h>
17
#endif
18
19
#ifdef HAVE_LINUX_BLKZONED_H
20
#include <linux/blkzoned.h>
21
#endif
22
23
#ifdef HAVE_SYS_DISKLABEL_H
24
#include <sys/disklabel.h>
25
#endif
26
27
#ifdef HAVE_SYS_DISK_H
28
# include <sys/disk.h>
29
#endif
30
31
#ifndef EBADFD
32
# define EBADFD 77    /* File descriptor in bad state */
33
#endif
34
35
#include "all-io.h"
36
#include "blkdev.h"
37
#include "c.h"
38
#include "cctype.h"
39
#include "linux_version.h"
40
#include "fileutils.h"
41
#include "nls.h"
42
43
static long blkdev_valid_offset(int fd, off_t offset)
44
0
{
45
0
  char ch;
46
47
0
  if (lseek (fd, offset, 0) < 0)
48
0
    return 0;
49
0
  if (ul_read_all (fd, &ch, 1) < 1)
50
0
    return 0;
51
0
  return 1;
52
0
}
53
54
int is_blkdev(int fd)
55
0
{
56
0
  struct stat st;
57
0
  return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode));
58
0
}
59
60
off_t blkdev_find_size (int fd)
61
0
{
62
0
  off_t high, low = 0;
63
64
0
  for (high = 1024; blkdev_valid_offset(fd, high); ) {
65
0
    if (high == SINT_MAX(off_t)) {
66
0
      errno = EFBIG;
67
0
      return -1;
68
0
    }
69
70
0
    low = high;
71
72
0
    if (high >= SINT_MAX(off_t)/2)
73
0
      high = SINT_MAX(off_t);
74
0
    else
75
0
      high *= 2;
76
0
  }
77
78
0
  while (low < high - 1) {
79
0
    off_t mid = (low + high) / 2;
80
81
0
    if (blkdev_valid_offset (fd, mid))
82
0
      low = mid;
83
0
    else
84
0
      high = mid;
85
0
  }
86
0
  blkdev_valid_offset (fd, 0);
87
0
  return (low + 1);
88
0
}
89
90
/* get size in bytes */
91
int blkdev_get_size(int fd, uint64_t *bytes)
92
0
{
93
#ifdef DKIOCGETBLOCKCOUNT
94
  /* Apple Darwin */
95
  if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
96
    *bytes <<= 9;
97
    return 0;
98
  }
99
#endif
100
101
0
#ifdef BLKGETSIZE64
102
0
  if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
103
0
    return 0;
104
0
#endif
105
106
0
#ifdef BLKGETSIZE
107
0
  {
108
0
    unsigned long size;
109
110
0
    if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
111
0
      *bytes = ((uint64_t) size) << 9;
112
0
      return 0;
113
0
    }
114
0
  }
115
116
0
#endif /* BLKGETSIZE */
117
118
#ifdef DIOCGMEDIASIZE
119
  /* FreeBSD */
120
  if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
121
    return 0;
122
#endif
123
124
0
#ifdef FDGETPRM
125
0
  {
126
0
    struct floppy_struct this_floppy;
127
128
0
    if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
129
0
      *bytes = ((uint64_t) this_floppy.size) << 9;
130
0
      return 0;
131
0
    }
132
0
  }
133
0
#endif /* FDGETPRM */
134
135
#if defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO)
136
  {
137
    /*
138
     * This code works for FreeBSD 4.11 i386, except for the full device
139
     * (such as /dev/ad0). It doesn't work properly for newer FreeBSD
140
     * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE
141
     * above however.
142
     *
143
     * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw,
144
     * character) devices, so we need to check for S_ISCHR, too.
145
     */
146
    int part = -1;
147
    struct disklabel lab;
148
    struct partition *pp;
149
    struct stat st;
150
151
    if ((fstat(fd, &st) >= 0) &&
152
        (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
153
      part = st.st_rdev & 7;
154
155
    if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) {
156
      pp = &lab.d_partitions[part];
157
      if (pp->p_size) {
158
         *bytes = ((uint64_t) pp->p_size) << 9;
159
         return 0;
160
      }
161
    }
162
  }
163
#endif /* defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO) */
164
165
0
  {
166
0
    struct stat st;
167
168
0
    if (fstat(fd, &st) != 0)
169
0
      return -1;
170
0
    if (S_ISREG(st.st_mode)) {
171
0
      *bytes = (uint64_t) st.st_size;
172
0
      return 0;
173
0
    }
174
0
    if (!S_ISBLK(st.st_mode)) {
175
0
      errno = ENOTBLK;
176
0
      return -1;
177
0
    }
178
0
  }
179
180
0
  {
181
0
    off_t size = blkdev_find_size(fd);
182
183
0
    if (size < 0)
184
0
      return -1;
185
186
0
    *bytes = (uint64_t) size;
187
0
  }
188
189
0
  return 0;
190
0
}
191
192
/* get 512-byte sector count */
193
int blkdev_get_sectors(int fd, uint64_t *sectors)
194
0
{
195
0
  uint64_t bytes;
196
197
0
  if (blkdev_get_size(fd, &bytes) == 0) {
198
0
    *sectors = (bytes >> 9);
199
0
    return 0;
200
0
  }
201
202
0
  return -1;
203
0
}
204
205
/*
206
 * Get logical sector size.
207
 *
208
 * This is the smallest unit the storage device can
209
 * address. It is typically 512 bytes.
210
 */
211
#ifdef BLKSSZGET
212
int blkdev_get_sector_size(int fd, int *sector_size)
213
0
{
214
0
  if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
215
0
    return 0;
216
0
  return -1;
217
0
}
218
#else
219
int blkdev_get_sector_size(int fd __attribute__((__unused__)), int *sector_size)
220
{
221
  *sector_size = DEFAULT_SECTOR_SIZE;
222
  return 0;
223
}
224
#endif
225
226
/*
227
 * Get physical block device size. The BLKPBSZGET is supported since Linux
228
 * 2.6.32. For old kernels is probably the best to assume that physical sector
229
 * size is the same as logical sector size.
230
 *
231
 * Example:
232
 *
233
 * rc = blkdev_get_physector_size(fd, &physec);
234
 * if (rc || physec == 0) {
235
 *  rc = blkdev_get_sector_size(fd, &physec);
236
 *  if (rc)
237
 *    physec = DEFAULT_SECTOR_SIZE;
238
 * }
239
 */
240
#ifdef BLKPBSZGET
241
int blkdev_get_physector_size(int fd, int *sector_size)
242
0
{
243
0
  if (ioctl(fd, BLKPBSZGET, sector_size) >= 0)
244
0
    return 0;
245
0
  return -1;
246
0
}
247
#else
248
int blkdev_get_physector_size(int fd __attribute__((__unused__)), int *sector_size)
249
{
250
  *sector_size = DEFAULT_SECTOR_SIZE;
251
  return 0;
252
}
253
#endif
254
255
/*
256
 * Return the alignment status of a device
257
 */
258
#ifdef BLKALIGNOFF
259
int blkdev_is_misaligned(int fd)
260
0
{
261
0
  int aligned;
262
263
0
  if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
264
0
    return 0; /* probably kernel < 2.6.32 */
265
  /*
266
   * Note that kernel returns -1 as alignment offset if no compatible
267
   * sizes and alignments exist for stacked devices
268
   */
269
0
  return aligned != 0 ? 1 : 0;
270
0
}
271
#else
272
int blkdev_is_misaligned(int fd __attribute__((__unused__)))
273
{
274
  return 0;
275
}
276
#endif
277
278
int open_blkdev_or_file(const struct stat *st, const char *name, const int oflag)
279
0
{
280
0
  int fd;
281
282
0
  if (S_ISBLK(st->st_mode)) {
283
0
    fd = open(name, oflag | O_EXCL | O_CLOEXEC);
284
0
  } else
285
0
    fd = open(name, oflag);
286
0
  if (-1 < fd && !is_same_inode(fd, st)) {
287
0
    close(fd);
288
0
    errno = EBADFD;
289
0
    return -1;
290
0
  }
291
0
  if (-1 < fd && S_ISBLK(st->st_mode) && blkdev_is_misaligned(fd))
292
0
    warnx(_("warning: %s is misaligned"), name);
293
0
  return fd;
294
0
}
295
296
#ifdef CDROM_GET_CAPABILITY
297
int blkdev_is_cdrom(int fd)
298
0
{
299
0
  int ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL);
300
0
  return ret < 0 ? 0 : ret;
301
0
}
302
#else
303
int blkdev_is_cdrom(int fd __attribute__((__unused__)))
304
{
305
  return 0;
306
}
307
#endif
308
309
/*
310
 * Get kernel's interpretation of the device's geometry.
311
 *
312
 * Returns the heads and sectors - but not cylinders
313
 * as it's truncated for disks with more than 65535 tracks.
314
 *
315
 * Note that this is deprecated in favor of LBA addressing.
316
 */
317
#ifdef HDIO_GETGEO
318
int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s)
319
0
{
320
0
  struct hd_geometry geometry;
321
322
0
  if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
323
0
    *h = geometry.heads;
324
0
    *s = geometry.sectors;
325
0
    return 0;
326
0
  }
327
#else
328
int blkdev_get_geometry(int fd __attribute__((__unused__)),
329
    unsigned int *h, unsigned int *s)
330
{
331
  *h = 0;
332
  *s = 0;
333
#endif
334
0
  return -1;
335
0
}
336
337
/*
338
 * Convert scsi type to human readable string.
339
 */
340
const char *blkdev_scsi_type_to_name(int type)
341
0
{
342
0
  switch (type) {
343
0
  case SCSI_TYPE_DISK:
344
0
    return "disk";
345
0
  case SCSI_TYPE_TAPE:
346
0
    return "tape";
347
0
  case SCSI_TYPE_PRINTER:
348
0
    return "printer";
349
0
  case SCSI_TYPE_PROCESSOR:
350
0
    return "processor";
351
0
  case SCSI_TYPE_WORM:
352
0
    return "worm";
353
0
  case SCSI_TYPE_ROM:
354
0
    return "rom";
355
0
  case SCSI_TYPE_SCANNER:
356
0
    return "scanner";
357
0
  case SCSI_TYPE_MOD:
358
0
    return "mo-disk";
359
0
  case SCSI_TYPE_MEDIUM_CHANGER:
360
0
    return "changer";
361
0
  case SCSI_TYPE_COMM:
362
0
    return "comm";
363
0
  case SCSI_TYPE_RAID:
364
0
    return "raid";
365
0
  case SCSI_TYPE_ENCLOSURE:
366
0
    return "enclosure";
367
0
  case SCSI_TYPE_RBC:
368
0
    return "rbc";
369
0
  case SCSI_TYPE_OSD:
370
0
    return "osd";
371
0
  case SCSI_TYPE_NO_LUN:
372
0
    return "no-lun";
373
0
  default:
374
0
    break;
375
0
  }
376
0
  return NULL;
377
0
}
378
379
/* return 0 on success */
380
int blkdev_lock(int fd, const char *devname, const char *lockmode)
381
0
{
382
0
  int oper, rc, msg = 0;
383
384
0
  if (!lockmode)
385
0
    lockmode = getenv("LOCK_BLOCK_DEVICE");
386
0
  if (!lockmode)
387
0
    return 0;
388
389
0
  if (c_strcasecmp(lockmode, "yes") == 0 ||
390
0
      strcmp(lockmode, "1") == 0)
391
0
    oper = LOCK_EX;
392
393
0
  else if (c_strcasecmp(lockmode, "nonblock") == 0)
394
0
    oper = LOCK_EX | LOCK_NB;
395
396
0
  else if (c_strcasecmp(lockmode, "no") == 0 ||
397
0
     strcmp(lockmode, "0") == 0)
398
0
    return 0;
399
0
  else {
400
0
    warnx(_("unsupported lock mode: %s"), lockmode);
401
0
    return -EINVAL;
402
0
  }
403
404
0
  if (!(oper & LOCK_NB)) {
405
    /* Try non-block first to provide message */
406
0
    rc = flock(fd, oper | LOCK_NB);
407
0
    if (rc == 0)
408
0
      return 0;
409
0
    if (rc != 0 && errno == EWOULDBLOCK) {
410
0
      fprintf(stderr, _("%s: %s: device already locked, waiting to get lock ... "),
411
0
          program_invocation_short_name, devname);
412
0
      msg = 1;
413
0
    }
414
0
  }
415
0
  rc = flock(fd, oper);
416
0
  if (rc != 0) {
417
0
    switch (errno) {
418
0
    case EWOULDBLOCK: /* LOCK_NB */
419
0
      warnx(_("%s: device already locked"), devname);
420
0
      break;
421
0
    default:
422
0
      warn(_("%s: failed to get lock"), devname);
423
0
    }
424
0
  } else if (msg)
425
0
    fprintf(stderr, _("OK\n"));
426
0
  return rc;
427
0
}
428
429
#ifdef HAVE_LINUX_BLKZONED_H
430
struct blk_zone_report *blkdev_get_zonereport(int fd, uint64_t sector, uint32_t nzones)
431
0
{
432
0
  struct blk_zone_report *rep;
433
0
  size_t rep_size;
434
0
  int ret;
435
436
0
  rep_size = sizeof(struct blk_zone_report) + sizeof(struct blk_zone) * nzones;
437
0
  rep = calloc(1, rep_size);
438
0
  if (!rep)
439
0
    return NULL;
440
441
0
  rep->sector = sector;
442
0
  rep->nr_zones = nzones;
443
444
0
  ret = ioctl(fd, BLKREPORTZONE, rep);
445
0
  if (ret || rep->nr_zones != nzones) {
446
0
    free(rep);
447
0
    return NULL;
448
0
  }
449
450
0
  return rep;
451
0
}
452
#endif
453
454
455
#ifdef TEST_PROGRAM_BLKDEV
456
#include <stdio.h>
457
#include <stdlib.h>
458
#include <fcntl.h>
459
460
int main(int argc, char **argv)
461
{
462
  uint64_t bytes, sectors;
463
  int sector_size, phy_sector_size;
464
  int fd;
465
466
  if (argc != 2) {
467
    fprintf(stderr, "usage: %s device\n", argv[0]);
468
    exit(EXIT_FAILURE);
469
  }
470
471
  if ((fd = open(argv[1], O_RDONLY|O_CLOEXEC)) < 0)
472
    err(EXIT_FAILURE, "open %s failed", argv[1]);
473
474
  if (blkdev_get_size(fd, &bytes) < 0)
475
    err(EXIT_FAILURE, "blkdev_get_size() failed");
476
  if (blkdev_get_sectors(fd, &sectors) < 0)
477
    err(EXIT_FAILURE, "blkdev_get_sectors() failed");
478
  if (blkdev_get_sector_size(fd, &sector_size) < 0)
479
    err(EXIT_FAILURE, "blkdev_get_sector_size() failed");
480
  if (blkdev_get_physector_size(fd, &phy_sector_size) < 0)
481
    err(EXIT_FAILURE, "blkdev_get_physector_size() failed");
482
483
  printf("          bytes: %" PRIu64 "\n", bytes);
484
  printf("        sectors: %" PRIu64 "\n", sectors);
485
  printf("    sector size: %d\n", sector_size);
486
  printf("phy-sector size: %d\n", phy_sector_size);
487
488
  return EXIT_SUCCESS;
489
}
490
#endif /* TEST_PROGRAM_BLKDEV */