1  /*
       2   * blockdev.c --- Do various simple block device ioctls from the command line
       3   * aeb, 991028
       4   */
       5  
       6  #include <stdio.h>
       7  #include <fcntl.h>
       8  #include <stdlib.h>
       9  #include <string.h>
      10  #include <unistd.h>
      11  #include <sys/ioctl.h>
      12  #include <errno.h>
      13  
      14  #include "c.h"
      15  #include "nls.h"
      16  #include "blkdev.h"
      17  #include "pathnames.h"
      18  #include "closestream.h"
      19  #include "strutils.h"
      20  #include "sysfs.h"
      21  
      22  struct bdc {
      23  	long		ioc;		/* ioctl code */
      24  	const char	*iocname;	/* ioctl name (e.g. BLKROSET) */
      25  	long		argval;		/* default argument */
      26  
      27  	const char	*name;		/* --setfoo */
      28  	const char	*argname;	/* argument name or NULL */
      29  
      30  	const char	*help;
      31  
      32  	int		argtype;
      33  	int		flags;
      34  };
      35  
      36  /* command flags */
      37  enum {
      38  	FL_NOPTR	= (1 << 1),	/* does not assume pointer (ARG_INT only)*/
      39  	FL_NORESULT	= (1 << 2)	/* does not return any data */
      40  };
      41  
      42  /* ioctl argument types */
      43  enum {
      44  	ARG_NONE,
      45  	ARG_USHRT,
      46  	ARG_INT,
      47  	ARG_UINT,
      48  	ARG_LONG,
      49  	ARG_ULONG,
      50  	ARG_LLONG,
      51  	ARG_ULLONG
      52  };
      53  
      54  #define IOCTL_ENTRY( io )	.ioc = io, .iocname = # io
      55  
      56  static const struct bdc bdcms[] =
      57  {
      58  	{
      59  		IOCTL_ENTRY(BLKROSET),
      60  		.name = "--setro",
      61  		.argtype = ARG_INT,
      62  		.argval = 1,
      63  		.flags = FL_NORESULT,
      64  		.help = N_("set read-only")
      65  	},{
      66  		IOCTL_ENTRY(BLKROSET),
      67  		.name = "--setrw",
      68  		.argtype = ARG_INT,
      69  		.argval = 0,
      70  		.flags = FL_NORESULT,
      71  		.help = N_("set read-write")
      72  	},{
      73  		IOCTL_ENTRY(BLKROGET),
      74  		.name = "--getro",
      75  		.argtype = ARG_INT,
      76  		.argval = -1,
      77  		.help = N_("get read-only")
      78  	},{
      79  		IOCTL_ENTRY(BLKDISCARDZEROES),
      80  		.name = "--getdiscardzeroes",
      81  		.argtype = ARG_UINT,
      82  		.argval = -1,
      83  		.help = N_("get discard zeroes support status")
      84  	},{
      85  		IOCTL_ENTRY(BLKSSZGET),
      86  		.name = "--getss",
      87  		.argtype = ARG_INT,
      88  		.argval = -1,
      89  		.help = N_("get logical block (sector) size")
      90  	},{
      91  		IOCTL_ENTRY(BLKPBSZGET),
      92  		.name = "--getpbsz",
      93  		.argtype = ARG_UINT,
      94  		.argval = -1,
      95  		.help = N_("get physical block (sector) size")
      96  	},{
      97  		IOCTL_ENTRY(BLKIOMIN),
      98  		.name = "--getiomin",
      99  		.argtype = ARG_UINT,
     100  		.argval = -1,
     101  		.help = N_("get minimum I/O size")
     102  	},{
     103  		IOCTL_ENTRY(BLKIOOPT),
     104  		.name = "--getioopt",
     105  		.argtype = ARG_UINT,
     106  		.argval = -1,
     107  		.help = N_("get optimal I/O size")
     108  	},{
     109  		IOCTL_ENTRY(BLKALIGNOFF),
     110  		.name = "--getalignoff",
     111  		.argtype = ARG_INT,
     112  		.argval = -1,
     113  		.help = N_("get alignment offset in bytes")
     114  	},{
     115  		IOCTL_ENTRY(BLKSECTGET),
     116  		.name = "--getmaxsect",
     117  		.argtype = ARG_USHRT,
     118  		.argval = -1,
     119  		.help = N_("get max sectors per request")
     120  	},{
     121  		IOCTL_ENTRY(BLKBSZGET),
     122  		.name = "--getbsz",
     123  		.argtype = ARG_INT,
     124  		.argval = -1,
     125  		.help = N_("get blocksize")
     126  	},{
     127  		IOCTL_ENTRY(BLKBSZSET),
     128  		.name = "--setbsz",
     129  		.argname = "<bytes>",
     130  		.argtype = ARG_INT,
     131  		.flags = FL_NORESULT,
     132  	        .help = N_("set blocksize on file descriptor opening the block device")
     133  	},{
     134  		IOCTL_ENTRY(BLKGETSIZE),
     135  		.name = "--getsize",
     136  		.argtype = ARG_ULONG,
     137  		.argval = -1,
     138  		.help = N_("get 32-bit sector count (deprecated, use --getsz)")
     139  	},{
     140  		IOCTL_ENTRY(BLKGETSIZE64),
     141  		.name = "--getsize64",
     142  		.argtype = ARG_ULLONG,
     143  		.argval = -1,
     144  		.help = N_("get size in bytes")
     145  	},{
     146  		IOCTL_ENTRY(BLKRASET),
     147  		.name = "--setra",
     148  		.argname = "<sectors>",
     149  		.argtype = ARG_INT,
     150  		.flags = FL_NOPTR | FL_NORESULT,
     151  		.help = N_("set readahead")
     152  	},{
     153  		IOCTL_ENTRY(BLKRAGET),
     154  		.name = "--getra",
     155  		.argtype = ARG_LONG,
     156  		.argval = -1,
     157  		.help = N_("get readahead")
     158  	},{
     159  		IOCTL_ENTRY(BLKFRASET),
     160  		.name = "--setfra",
     161  		.argname = "<sectors>",
     162  		.argtype = ARG_INT,
     163  		.flags = FL_NOPTR | FL_NORESULT,
     164  		.help = N_("set filesystem readahead")
     165  	},{
     166  		IOCTL_ENTRY(BLKFRAGET),
     167  		.name = "--getfra",
     168  		.argtype = ARG_LONG,
     169  		.argval = -1,
     170  		.help = N_("get filesystem readahead")
     171  	},{
     172  		IOCTL_ENTRY(BLKGETDISKSEQ),
     173  		.name = "--getdiskseq",
     174  		.argtype = ARG_ULLONG,
     175  		.argval = -1,
     176  		.help = N_("get disk sequence number")
     177  	},{
     178  		IOCTL_ENTRY(BLKFLSBUF),
     179  		.name = "--flushbufs",
     180  		.help = N_("flush buffers")
     181  	},{
     182  		IOCTL_ENTRY(BLKRRPART),
     183  		.name = "--rereadpt",
     184  		.help = N_("reread partition table")
     185  	}
     186  };
     187  
     188  static void __attribute__((__noreturn__)) usage(void)
     189  {
     190  	size_t i;
     191  
     192  	fputs(USAGE_HEADER, stdout);
     193  	printf(_(
     194  	         " %1$s [-v|-q] commands devices\n"
     195  	         " %1$s --report [devices]\n"
     196  	         " %1$s -h|-V\n"
     197  		), program_invocation_short_name);
     198  
     199  	fputs(USAGE_SEPARATOR, stdout);
     200  	puts(  _("Call block device ioctls from the command line."));
     201  
     202  	fputs(USAGE_OPTIONS, stdout);
     203  	puts(  _(" -q             quiet mode"));
     204  	puts(  _(" -v             verbose mode"));
     205  	puts(  _("     --report   print report for specified (or all) devices"));
     206  	fputs(USAGE_SEPARATOR, stdout);
     207  	printf(USAGE_HELP_OPTIONS(16));
     208  
     209  	fputs(USAGE_SEPARATOR, stdout);
     210  	puts(  _("Available commands:"));
     211  	printf(_(" %-25s get size in 512-byte sectors\n"), "--getsz");
     212  	for (i = 0; i < ARRAY_SIZE(bdcms); i++) {
     213  		if (bdcms[i].argname)
     214  			printf(" %s %-*s %s\n", bdcms[i].name,
     215  				(int)(24 - strlen(bdcms[i].name)),
     216  				bdcms[i].argname, _(bdcms[i].help));
     217  		else
     218  			printf(" %-25s %s\n", bdcms[i].name,
     219  				_(bdcms[i].help));
     220  	}
     221  
     222  	printf(USAGE_MAN_TAIL("blockdev(8)"));
     223  	exit(EXIT_SUCCESS);
     224  }
     225  
     226  static int find_cmd(char *s)
     227  {
     228  	size_t j;
     229  
     230  	for (j = 0; j < ARRAY_SIZE(bdcms); j++)
     231  		if (!strcmp(s, bdcms[j].name))
     232  			return j;
     233  	return -1;
     234  }
     235  
     236  static void do_commands(int fd, char **argv, int d);
     237  static void report_header(void);
     238  static void report_device(char *device, int quiet);
     239  static void report_all_devices(void);
     240  
     241  int main(int argc, char **argv)
     242  {
     243  	int fd, d, j, k;
     244  
     245  	setlocale(LC_ALL, "");
     246  	bindtextdomain(PACKAGE, LOCALEDIR);
     247  	textdomain(PACKAGE);
     248  	close_stdout_atexit();
     249  
     250  	if (argc < 2) {
     251  		warnx(_("not enough arguments"));
     252  		errtryhelp(EXIT_FAILURE);
     253  	}
     254  
     255  	/* -V not together with commands */
     256  	if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version"))
     257  		print_version(EXIT_SUCCESS);
     258  	if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
     259  		usage();
     260  
     261  	/* --report not together with other commands */
     262  	if (!strcmp(argv[1], "--report")) {
     263  		report_header();
     264  		if (argc > 2) {
     265  			for (d = 2; d < argc; d++)
     266  				report_device(argv[d], 0);
     267  		} else {
     268  			report_all_devices();
     269  		}
     270  		return EXIT_SUCCESS;
     271  	}
     272  
     273  	/* do each of the commands on each of the devices */
     274  	/* devices start after last command */
     275  	for (d = 1; d < argc; d++) {
     276  		j = find_cmd(argv[d]);
     277  		if (j >= 0) {
     278  			if (bdcms[j].argname)
     279  				d++;
     280  			continue;
     281  		}
     282  		if (!strcmp(argv[d], "--getsz"))
     283  			continue;
     284  		if (!strcmp(argv[d], "--")) {
     285  			d++;
     286  			break;
     287  		}
     288  		if (argv[d][0] != '-')
     289  			break;
     290  	}
     291  
     292  	if (d >= argc) {
     293  		warnx(_("no device specified"));
     294  		errtryhelp(EXIT_FAILURE);
     295  	}
     296  
     297  	for (k = d; k < argc; k++) {
     298  		fd = open(argv[k], O_RDONLY, 0);
     299  		if (fd < 0)
     300  			err(EXIT_FAILURE, _("cannot open %s"), argv[k]);
     301  		do_commands(fd, argv, d);
     302  		close(fd);
     303  	}
     304  	return EXIT_SUCCESS;
     305  }
     306  
     307  static void do_commands(int fd, char **argv, int d)
     308  {
     309  	int res, i, j;
     310  	int iarg = 0;
     311  	unsigned int uarg = 0;
     312  	unsigned short huarg = 0;
     313  	long larg = 0;
     314  	long long llarg = 0;
     315  	unsigned long lu = 0;
     316  	unsigned long long llu = 0;
     317  	int verbose = 0;
     318  
     319  	for (i = 1; i < d; i++) {
     320  		if (!strcmp(argv[i], "-v")) {
     321  			verbose = 1;
     322  			continue;
     323  		}
     324  		if (!strcmp(argv[i], "-q")) {
     325  			verbose = 0;
     326  			continue;
     327  		}
     328  
     329  		if (!strcmp(argv[i], "--getsz")) {
     330  			res = blkdev_get_sectors(fd, &llu);
     331  			if (res == 0)
     332  				printf("%lld\n", llu);
     333  			else
     334  				errx(EXIT_FAILURE,
     335  				     _("could not get device size"));
     336  			continue;
     337  		}
     338  
     339  		j = find_cmd(argv[i]);
     340  		if (j == -1) {
     341  			warnx(_("Unknown command: %s"), argv[i]);
     342  			errtryhelp(EXIT_FAILURE);
     343  		}
     344  
     345  		switch (bdcms[j].argtype) {
     346  		default:
     347  		case ARG_NONE:
     348  			res = ioctl(fd, bdcms[j].ioc, 0);
     349  			break;
     350  		case ARG_USHRT:
     351  			huarg = bdcms[j].argval;
     352  			res = ioctl(fd, bdcms[j].ioc, &huarg);
     353  			break;
     354  		case ARG_INT:
     355  			if (bdcms[j].argname) {
     356  				if (i == d - 1) {
     357  					warnx(_("%s requires an argument"),
     358  					      bdcms[j].name);
     359  					errtryhelp(EXIT_FAILURE);
     360  				}
     361  				iarg = strtos32_or_err(argv[++i], _("failed to parse command argument"));
     362  			} else
     363  				iarg = bdcms[j].argval;
     364  
     365  			res = bdcms[j].flags & FL_NOPTR ?
     366  			    ioctl(fd, bdcms[j].ioc, iarg) :
     367  			    ioctl(fd, bdcms[j].ioc, &iarg);
     368  			break;
     369  		case ARG_UINT:
     370  			uarg = bdcms[j].argval;
     371  			res = ioctl(fd, bdcms[j].ioc, &uarg);
     372  			break;
     373  		case ARG_LONG:
     374  			larg = bdcms[j].argval;
     375  			res = ioctl(fd, bdcms[j].ioc, &larg);
     376  			break;
     377  		case ARG_LLONG:
     378  			llarg = bdcms[j].argval;
     379  			res = ioctl(fd, bdcms[j].ioc, &llarg);
     380  			break;
     381  		case ARG_ULONG:
     382  			lu = bdcms[j].argval;
     383  			res = ioctl(fd, bdcms[j].ioc, &lu);
     384  			break;
     385  		case ARG_ULLONG:
     386  			llu = bdcms[j].argval;
     387  			res = ioctl(fd, bdcms[j].ioc, &llu);
     388  			break;
     389  		}
     390  
     391  		if (res == -1) {
     392  			warn(_("ioctl error on %s"), bdcms[j].iocname);
     393  			if (verbose)
     394  				printf(_("%s failed.\n"), _(bdcms[j].help));
     395  			exit(EXIT_FAILURE);
     396  		}
     397  
     398  		if (bdcms[j].argtype == ARG_NONE ||
     399  		    (bdcms[j].flags & FL_NORESULT)) {
     400  			if (verbose)
     401  				printf(_("%s succeeded.\n"), _(bdcms[j].help));
     402  			continue;
     403  		}
     404  
     405  		if (verbose)
     406  			printf("%s: ", _(bdcms[j].help));
     407  
     408  		switch (bdcms[j].argtype) {
     409  		case ARG_USHRT:
     410  			printf("%hu\n", huarg);
     411  			break;
     412  		case ARG_INT:
     413  			printf("%d\n", iarg);
     414  			break;
     415  		case ARG_UINT:
     416  			printf("%u\n", uarg);
     417  			break;
     418  		case ARG_LONG:
     419  			printf("%ld\n", larg);
     420  			break;
     421  		case ARG_LLONG:
     422  			printf("%lld\n", llarg);
     423  			break;
     424  		case ARG_ULONG:
     425  			printf("%lu\n", lu);
     426  			break;
     427  		case ARG_ULLONG:
     428  			printf("%llu\n", llu);
     429  			break;
     430  		}
     431  	}
     432  }
     433  
     434  static void report_all_devices(void)
     435  {
     436  	FILE *procpt;
     437  	char line[200];
     438  	char ptname[200 + 1];
     439  	char device[210];
     440  	int ma, mi, sz;
     441  
     442  	procpt = fopen(_PATH_PROC_PARTITIONS, "r");
     443  	if (!procpt)
     444  		err(EXIT_FAILURE, _("cannot open %s"), _PATH_PROC_PARTITIONS);
     445  
     446  	while (fgets(line, sizeof(line), procpt)) {
     447  		if (sscanf(line, " %d %d %d %200[^\n ]",
     448  			   &ma, &mi, &sz, ptname) != 4)
     449  			continue;
     450  
     451  		snprintf(device, sizeof(device), "/dev/%s", ptname);
     452  		report_device(device, 1);
     453  	}
     454  
     455  	fclose(procpt);
     456  }
     457  
     458  static void report_device(char *device, int quiet)
     459  {
     460  	int fd;
     461  	int ro, ssz, bsz;
     462  	long ra;
     463  	unsigned long long bytes;
     464  	uint64_t start = 0;
     465  	char start_str[16] = { "\0" };
     466  	struct stat st;
     467  
     468  	fd = open(device, O_RDONLY | O_NONBLOCK);
     469  	if (fd < 0) {
     470  		if (!quiet)
     471  			warn(_("cannot open %s"), device);
     472  		return;
     473  	}
     474  
     475  	ro = ssz = bsz = 0;
     476  	ra = 0;
     477  	if (fstat(fd, &st) == 0) {
     478  		dev_t disk;
     479  		struct path_cxt *pc;
     480  
     481  		pc = ul_new_sysfs_path(st.st_rdev, NULL, NULL);
     482  		if (pc &&
     483  		    sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk) == 0 &&
     484  		    disk != st.st_rdev) {
     485  
     486  			if (ul_path_read_u64(pc, &start, "start") != 0)
     487  				/* TRANSLATORS: Start sector not available. Max. 15 letters. */
     488  				snprintf(start_str, sizeof(start_str), "%15s", _("N/A"));
     489  		}
     490  		ul_unref_path(pc);
     491  	}
     492  	if (!*start_str)
     493  		snprintf(start_str, sizeof(start_str), "%15ju", start);
     494  
     495  	if (ioctl(fd, BLKROGET, &ro) == 0 &&
     496  	    ioctl(fd, BLKRAGET, &ra) == 0 &&
     497  	    ioctl(fd, BLKSSZGET, &ssz) == 0 &&
     498  	    ioctl(fd, BLKBSZGET, &bsz) == 0 &&
     499  	    blkdev_get_size(fd, &bytes) == 0) {
     500  		printf("%s %5ld %5d %5d %s %15lld   %s\n",
     501  			ro ? "ro" : "rw", ra, ssz, bsz, start_str, bytes, device);
     502  	} else {
     503  		if (!quiet)
     504  			warnx(_("ioctl error on %s"), device);
     505  	}
     506  
     507  	close(fd);
     508  }
     509  
     510  static void report_header(void)
     511  {
     512  	printf(_("RO    RA   SSZ   BSZ        StartSec            Size   Device\n"));
     513  }