Coverage Report

Created: 2025-06-13 06:36

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