Coverage Report

Created: 2024-05-21 06:33

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