(root)/
util-linux-2.39/
lib/
blkdev.c
       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  blkdev_valid_offset (int fd, off_t offset) {
      44  	char ch;
      45  
      46  	if (lseek (fd, offset, 0) < 0)
      47  		return 0;
      48  	if (read_all (fd, &ch, 1) < 1)
      49  		return 0;
      50  	return 1;
      51  }
      52  
      53  int is_blkdev(int fd)
      54  {
      55  	struct stat st;
      56  	return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode));
      57  }
      58  
      59  off_t
      60  blkdev_find_size (int fd) {
      61  	off_t high, low = 0;
      62  
      63  	for (high = 1024; blkdev_valid_offset (fd, high); ) {
      64  		if (high == SINT_MAX(off_t)) {
      65  			errno = EFBIG;
      66  			return -1;
      67  		}
      68  
      69  		low = high;
      70  
      71  		if (high >= SINT_MAX(off_t)/2)
      72  			high = SINT_MAX(off_t);
      73  		else
      74  			high *= 2;
      75  	}
      76  
      77  	while (low < high - 1)
      78  	{
      79  		off_t mid = (low + high) / 2;
      80  
      81  		if (blkdev_valid_offset (fd, mid))
      82  			low = mid;
      83  		else
      84  			high = mid;
      85  	}
      86  	blkdev_valid_offset (fd, 0);
      87  	return (low + 1);
      88  }
      89  
      90  /* get size in bytes */
      91  int
      92  blkdev_get_size(int fd, unsigned long long *bytes)
      93  {
      94  #ifdef DKIOCGETBLOCKCOUNT
      95  	/* Apple Darwin */
      96  	if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
      97  		*bytes <<= 9;
      98  		return 0;
      99  	}
     100  #endif
     101  
     102  #ifdef BLKGETSIZE64
     103  	if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
     104  		return 0;
     105  #endif
     106  
     107  #ifdef BLKGETSIZE
     108  	{
     109  		unsigned long size;
     110  
     111  		if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
     112  			*bytes = ((unsigned long long)size << 9);
     113  			return 0;
     114  		}
     115  	}
     116  
     117  #endif /* BLKGETSIZE */
     118  
     119  #ifdef DIOCGMEDIASIZE
     120  	/* FreeBSD */
     121  	if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
     122  		return 0;
     123  #endif
     124  
     125  #ifdef FDGETPRM
     126  	{
     127  		struct floppy_struct this_floppy;
     128  
     129  		if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
     130  			*bytes = ((unsigned long long) this_floppy.size) << 9;
     131  			return 0;
     132  		}
     133  	}
     134  #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  	{
     167  		struct stat st;
     168  
     169  		if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
     170  			*bytes = st.st_size;
     171  			return 0;
     172  		}
     173  		if (!S_ISBLK(st.st_mode)) {
     174  			errno = ENOTBLK;
     175  			return -1;
     176  		}
     177  	}
     178  
     179  	*bytes = blkdev_find_size(fd);
     180  	return 0;
     181  }
     182  
     183  /* get 512-byte sector count */
     184  int
     185  blkdev_get_sectors(int fd, unsigned long long *sectors)
     186  {
     187  	unsigned long long bytes;
     188  
     189  	if (blkdev_get_size(fd, &bytes) == 0) {
     190  		*sectors = (bytes >> 9);
     191  		return 0;
     192  	}
     193  
     194  	return -1;
     195  }
     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  {
     206  	if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
     207  		return 0;
     208  	return -1;
     209  }
     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  {
     235  	if (ioctl(fd, BLKPBSZGET, sector_size) >= 0)
     236      {
     237  		return 0;
     238      }
     239  	return -1;
     240  }
     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  {
     255  	int aligned;
     256  
     257  	if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
     258  		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  	return aligned != 0 ? 1 : 0;
     264  }
     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  {
     274  	int fd;
     275  
     276  	if (S_ISBLK(st->st_mode)) {
     277  		fd = open(name, oflag | O_EXCL);
     278  	} else
     279  		fd = open(name, oflag);
     280  	if (-1 < fd && !is_same_inode(fd, st)) {
     281  		close(fd);
     282  		errno = EBADFD;
     283  		return -1;
     284  	}
     285  	if (-1 < fd && S_ISBLK(st->st_mode) && blkdev_is_misaligned(fd))
     286  		warnx(_("warning: %s is misaligned"), name);
     287  	return fd;
     288  }
     289  
     290  #ifdef CDROM_GET_CAPABILITY
     291  int blkdev_is_cdrom(int fd)
     292  {
     293  	int ret;
     294  
     295  	if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0)
     296  		return 0;
     297  
     298  	return ret;
     299  }
     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  {
     318  	struct hd_geometry geometry;
     319  
     320  	if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
     321  		*h = geometry.heads;
     322  		*s = geometry.sectors;
     323  		return 0;
     324  	}
     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  	return -1;
     333  }
     334  
     335  /*
     336   * Convert scsi type to human readable string.
     337   */
     338  const char *blkdev_scsi_type_to_name(int type)
     339  {
     340  	switch (type) {
     341  	case SCSI_TYPE_DISK:
     342  		return "disk";
     343  	case SCSI_TYPE_TAPE:
     344  		return "tape";
     345  	case SCSI_TYPE_PRINTER:
     346  		return "printer";
     347  	case SCSI_TYPE_PROCESSOR:
     348  		return "processor";
     349  	case SCSI_TYPE_WORM:
     350  		return "worm";
     351  	case SCSI_TYPE_ROM:
     352  		return "rom";
     353  	case SCSI_TYPE_SCANNER:
     354  		return "scanner";
     355  	case SCSI_TYPE_MOD:
     356  		return "mo-disk";
     357  	case SCSI_TYPE_MEDIUM_CHANGER:
     358  		return "changer";
     359  	case SCSI_TYPE_COMM:
     360  		return "comm";
     361  	case SCSI_TYPE_RAID:
     362  		return "raid";
     363  	case SCSI_TYPE_ENCLOSURE:
     364  		return "enclosure";
     365  	case SCSI_TYPE_RBC:
     366  		return "rbc";
     367  	case SCSI_TYPE_OSD:
     368  		return "osd";
     369  	case SCSI_TYPE_NO_LUN:
     370  		return "no-lun";
     371  	default:
     372  		break;
     373  	}
     374  	return NULL;
     375  }
     376  
     377  /* return 0 on success */
     378  int blkdev_lock(int fd, const char *devname, const char *lockmode)
     379  {
     380  	int oper, rc, msg = 0;
     381  
     382  	if (!lockmode)
     383  		lockmode = getenv("LOCK_BLOCK_DEVICE");
     384  	if (!lockmode)
     385  		return 0;
     386  
     387  	if (strcasecmp(lockmode, "yes") == 0 ||
     388  	    strcmp(lockmode, "1") == 0)
     389  		oper = LOCK_EX;
     390  
     391  	else if (strcasecmp(lockmode, "nonblock") == 0)
     392  		oper = LOCK_EX | LOCK_NB;
     393  
     394  	else if (strcasecmp(lockmode, "no") == 0 ||
     395  		 strcmp(lockmode, "0") == 0)
     396  		return 0;
     397  	else {
     398  		warnx(_("unsupported lock mode: %s"), lockmode);
     399  		return -EINVAL;
     400  	}
     401  
     402  	if (!(oper & LOCK_NB)) {
     403  		/* Try non-block first to provide message */
     404  		rc = flock(fd, oper | LOCK_NB);
     405  		if (rc == 0)
     406  			return 0;
     407  		if (rc != 0 && errno == EWOULDBLOCK) {
     408  			fprintf(stderr, _("%s: %s: device already locked, waiting to get lock ... "),
     409  					program_invocation_short_name, devname);
     410  			msg = 1;
     411  		}
     412  	}
     413  	rc = flock(fd, oper);
     414  	if (rc != 0) {
     415  		switch (errno) {
     416  		case EWOULDBLOCK: /* LOCK_NB */
     417  			warnx(_("%s: device already locked"), devname);
     418  			break;
     419  		default:
     420  			warn(_("%s: failed to get lock"), devname);
     421  		}
     422  	} else if (msg)
     423  		fprintf(stderr, _("OK\n"));
     424  	return rc;
     425  }
     426  
     427  #ifdef HAVE_LINUX_BLKZONED_H
     428  struct blk_zone_report *blkdev_get_zonereport(int fd, uint64_t sector, uint32_t nzones)
     429  {
     430  	struct blk_zone_report *rep;
     431  	size_t rep_size;
     432  	int ret;
     433  
     434  	rep_size = sizeof(struct blk_zone_report) + sizeof(struct blk_zone) * 2;
     435  	rep = calloc(1, rep_size);
     436  	if (!rep)
     437  		return NULL;
     438  
     439  	rep->sector = sector;
     440  	rep->nr_zones = nzones;
     441  
     442  	ret = ioctl(fd, BLKREPORTZONE, rep);
     443  	if (ret || rep->nr_zones != nzones) {
     444  		free(rep);
     445  		return NULL;
     446  	}
     447  
     448  	return rep;
     449  }
     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 */