(root)/
util-linux-2.39/
sys-utils/
blkzone.c
       1  /*
       2   * blkzone.c -- the block device zone commands
       3   *
       4   * Copyright (C) 2015,2016 Seagate Technology PLC
       5   * Written by Shaun Tancheff <shaun.tancheff@seagate.com>
       6   *
       7   * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
       8   *
       9   * This program is free software: you can redistribute it and/or modify
      10   * it under the terms of the GNU General Public License as published by
      11   * the Free Software Foundation, either version 2 of the License, or
      12   * (at your option) any later version.
      13   *
      14   * This program is distributed in the hope that it will be useful,
      15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17   * GNU General Public License for more details.
      18   *
      19   * You should have received a copy of the GNU General Public License
      20   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      21   */
      22  #include <string.h>
      23  #include <unistd.h>
      24  #include <stdlib.h>
      25  #include <stdio.h>
      26  #include <stdint.h>
      27  #include <stdbool.h>
      28  #include <fcntl.h>
      29  #include <limits.h>
      30  #include <getopt.h>
      31  #include <time.h>
      32  
      33  #include <sys/ioctl.h>
      34  #include <sys/stat.h>
      35  #include <sys/time.h>
      36  #include <linux/fs.h>
      37  #include <linux/blkzoned.h>
      38  
      39  #include "nls.h"
      40  #include "strutils.h"
      41  #include "xalloc.h"
      42  #include "c.h"
      43  #include "closestream.h"
      44  #include "blkdev.h"
      45  #include "sysfs.h"
      46  #include "optutils.h"
      47  
      48  /*
      49   * These ioctls are defined in linux/blkzoned.h starting with kernel 5.5.
      50   */
      51  #ifndef BLKOPENZONE
      52  #define BLKOPENZONE	_IOW(0x12, 134, struct blk_zone_range)
      53  #endif
      54  #ifndef BLKCLOSEZONE
      55  #define BLKCLOSEZONE	_IOW(0x12, 135, struct blk_zone_range)
      56  #endif
      57  #ifndef BLKFINISHZONE
      58  #define BLKFINISHZONE	_IOW(0x12, 136, struct blk_zone_range)
      59  #endif
      60  
      61  struct blkzone_control;
      62  
      63  static int blkzone_report(struct blkzone_control *ctl);
      64  static int blkzone_action(struct blkzone_control *ctl);
      65  
      66  struct blkzone_command {
      67  	const char *name;
      68  	int (*handler)(struct blkzone_control *);
      69  	unsigned long ioctl_cmd;
      70  	const char *ioctl_name;
      71  	const char *help;
      72  };
      73  
      74  struct blkzone_control {
      75  	const char *devname;
      76  	const struct blkzone_command *command;
      77  
      78  	uint64_t total_sectors;
      79  	int secsize;
      80  
      81  	uint64_t offset;
      82  	uint64_t length;
      83  	uint32_t count;
      84  
      85  	unsigned int force : 1;
      86  	unsigned int verbose : 1;
      87  };
      88  
      89  static const struct blkzone_command commands[] = {
      90  	{
      91  		.name = "report",
      92  		.handler = blkzone_report,
      93  		.help = N_("Report zone information about the given device")
      94  	},{
      95  		.name = "capacity",
      96  		.handler = blkzone_report,
      97  		.help = N_("Report sum of zone capacities for the given device")
      98  	},{
      99  		.name = "reset",
     100  		.handler = blkzone_action,
     101  		.ioctl_cmd = BLKRESETZONE,
     102  		.ioctl_name = "BLKRESETZONE",
     103  		.help = N_("Reset a range of zones.")
     104  	},{
     105  		.name = "open",
     106  		.handler = blkzone_action,
     107  		.ioctl_cmd = BLKOPENZONE,
     108  		.ioctl_name = "BLKOPENZONE",
     109  		.help = N_("Open a range of zones.")
     110  	},{
     111  		.name = "close",
     112  		.handler = blkzone_action,
     113  		.ioctl_cmd = BLKCLOSEZONE,
     114  		.ioctl_name = "BLKCLOSEZONE",
     115  		.help = N_("Close a range of zones.")
     116  	},{
     117  		.name = "finish",
     118  		.handler = blkzone_action,
     119  		.ioctl_cmd = BLKFINISHZONE,
     120  		.ioctl_name = "BLKFINISHZONE",
     121  		.help = N_("Set a range of zones to Full.")
     122  	}
     123  };
     124  
     125  static const struct blkzone_command *name_to_command(const char *name)
     126  {
     127  	size_t i;
     128  
     129  	for (i = 0; i < ARRAY_SIZE(commands); i++) {
     130  		if (strcmp(commands[i].name, name) == 0)
     131  			return &commands[i];
     132  	}
     133  
     134  	return NULL;
     135  }
     136  
     137  static int init_device(struct blkzone_control *ctl, int mode)
     138  {
     139  	struct stat sb;
     140  	int fd;
     141  
     142  	fd = open(ctl->devname, mode);
     143  	if (fd < 0)
     144  		err(EXIT_FAILURE, _("cannot open %s"), ctl->devname);
     145  
     146  	if (fstat(fd, &sb) == -1)
     147  		err(EXIT_FAILURE, _("stat of %s failed"), ctl->devname);
     148  	if (!S_ISBLK(sb.st_mode))
     149  		errx(EXIT_FAILURE, _("%s: not a block device"), ctl->devname);
     150  
     151  	if (blkdev_get_sectors(fd, (unsigned long long *) &ctl->total_sectors))
     152  		err(EXIT_FAILURE, _("%s: blkdev_get_sectors ioctl failed"), ctl->devname);
     153  
     154  	if (blkdev_get_sector_size(fd, &ctl->secsize))
     155  		err(EXIT_FAILURE, _("%s: BLKSSZGET ioctl failed"), ctl->devname);
     156  
     157  	return fd;
     158  }
     159  
     160  /*
     161   * Get the device zone size indicated by chunk sectors).
     162   */
     163  static unsigned long blkdev_chunk_sectors(const char *dname)
     164  {
     165  	struct path_cxt *pc = NULL;
     166  	dev_t devno = sysfs_devname_to_devno(dname);
     167  	dev_t disk;
     168  	uint64_t sz = 0;
     169  	int rc;
     170  
     171  	/*
     172  	 * Mapping /dev/sdXn -> /sys/block/sdX to read the chunk_size entry.
     173  	 * This method masks off the partition specified by the minor device
     174  	 * component.
     175  	 */
     176  	pc = ul_new_sysfs_path(devno, NULL, NULL);
     177  	if (!pc)
     178  		return 0;
     179  
     180  	rc = sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk);
     181  	if (rc != 0)
     182  		goto done;
     183  
     184  	/* if @pc is not while-disk device, switch to disk */
     185  	if (devno != disk) {
     186  		rc = sysfs_blkdev_init_path(pc, disk, NULL);
     187  		if (rc != 0)
     188  			goto done;
     189  	}
     190  
     191  	rc = ul_path_read_u64(pc, &sz, "queue/chunk_sectors");
     192  done:
     193  	ul_unref_path(pc);
     194  	return rc == 0 ? sz : 0;
     195  }
     196  
     197  #if HAVE_DECL_BLK_ZONE_REP_CAPACITY
     198  #define has_zone_capacity(zi)	((zi)->flags & BLK_ZONE_REP_CAPACITY)
     199  #define zone_capacity(z)	(z)->capacity
     200  #else
     201  #define has_zone_capacity(zi)	(false)
     202  #define zone_capacity(z)	(z)->len
     203  #endif
     204  
     205  /*
     206   * blkzone report
     207   */
     208  #define DEF_REPORT_LEN		(1U << 12) /* 4k zones per report (256k kzalloc) */
     209  
     210  static const char *type_text[] = {
     211  	"RESERVED",
     212  	"CONVENTIONAL",
     213  	"SEQ_WRITE_REQUIRED",
     214  	"SEQ_WRITE_PREFERRED",
     215  };
     216  
     217  static const char *condition_str[] = {
     218  	"nw", /* Not write pointer */
     219  	"em", /* Empty */
     220  	"oi", /* Implicitly opened */
     221  	"oe", /* Explicitly opened */
     222  	"cl", /* Closed */
     223  	"x5", "x6", "x7", "x8", "x9", "xA", "xB", "xC", /* xN: reserved */
     224  	"ro", /* Read only */
     225  	"fu", /* Full */
     226  	"of"  /* Offline */
     227  };
     228  
     229  static int blkzone_report(struct blkzone_control *ctl)
     230  {
     231  	bool only_capacity_sum = !strcmp(ctl->command->name, "capacity");
     232  	uint64_t capacity_sum = 0;
     233  	struct blk_zone_report *zi;
     234  	unsigned long zonesize;
     235  	uint32_t i, nr_zones;
     236  	int fd;
     237  
     238  	fd = init_device(ctl, O_RDONLY);
     239  
     240  	if (ctl->offset >= ctl->total_sectors)
     241  		errx(EXIT_FAILURE,
     242  		     _("%s: offset is greater than or equal to device size"), ctl->devname);
     243  
     244  	zonesize = blkdev_chunk_sectors(ctl->devname);
     245  	if (!zonesize)
     246  		errx(EXIT_FAILURE, _("%s: unable to determine zone size"), ctl->devname);
     247  
     248  	if (ctl->count)
     249  		nr_zones = ctl->count;
     250  	else if (ctl->length)
     251  		nr_zones = (ctl->length + zonesize - 1) / zonesize;
     252  	else
     253  		nr_zones = 1 + (ctl->total_sectors - ctl->offset) / zonesize;
     254  
     255  	zi = xmalloc(sizeof(struct blk_zone_report) +
     256  		     (DEF_REPORT_LEN * sizeof(struct blk_zone)));
     257  
     258  	while (nr_zones && ctl->offset < ctl->total_sectors) {
     259  
     260  		zi->nr_zones = min(nr_zones, DEF_REPORT_LEN);
     261  		zi->sector = ctl->offset;
     262  
     263  		if (ioctl(fd, BLKREPORTZONE, zi) == -1)
     264  			err(EXIT_FAILURE, _("%s: BLKREPORTZONE ioctl failed"), ctl->devname);
     265  
     266  		if (ctl->verbose)
     267  			printf(_("Found %d zones from 0x%"PRIx64"\n"),
     268  				zi->nr_zones, ctl->offset);
     269  
     270  		if (!zi->nr_zones)
     271  			break;
     272  
     273  		for (i = 0; i < zi->nr_zones; i++) {
     274  /*
     275   * blk_zone_report hasn't been packed since https://github.com/torvalds/linux/commit/b3e7e7d2d668de0102264302a4d10dd9d4438a42
     276   * was merged. See https://github.com/util-linux/util-linux/issues/1083
     277   */
     278  #pragma GCC diagnostic push
     279  #pragma GCC diagnostic ignored "-Waddress-of-packed-member"
     280  			const struct blk_zone *entry = &zi->zones[i];
     281  #pragma GCC diagnostic pop
     282  			unsigned int type = entry->type;
     283  			uint64_t start = entry->start;
     284  			uint64_t wp = entry->wp;
     285  			uint8_t cond = entry->cond;
     286  			uint64_t len = entry->len;
     287  			uint64_t cap;
     288  
     289  			if (!len) {
     290  				nr_zones = 0;
     291  				break;
     292  			}
     293  
     294  			if (has_zone_capacity(zi))
     295  				cap = zone_capacity(entry);
     296  			else
     297  				cap = entry->len;
     298  
     299  			if (only_capacity_sum) {
     300  				capacity_sum += cap;
     301  			} else if (has_zone_capacity(zi)) {
     302  				printf(_("  start: 0x%09"PRIx64", len 0x%06"PRIx64
     303  					", cap 0x%06"PRIx64", wptr 0x%06"PRIx64
     304  					" reset:%u non-seq:%u, zcond:%2u(%s) [type: %u(%s)]\n"),
     305  					start, len, cap, (type == 0x1) ? 0 : wp - start,
     306  					entry->reset, entry->non_seq,
     307  					cond, condition_str[cond & (ARRAY_SIZE(condition_str) - 1)],
     308  					type, type_text[type]);
     309  			} else {
     310  				printf(_("  start: 0x%09"PRIx64", len 0x%06"PRIx64
     311  					", wptr 0x%06"PRIx64
     312  					" reset:%u non-seq:%u, zcond:%2u(%s) [type: %u(%s)]\n"),
     313  					start, len, (type == 0x1) ? 0 : wp - start,
     314  					entry->reset, entry->non_seq,
     315  					cond, condition_str[cond & (ARRAY_SIZE(condition_str) - 1)],
     316  					type, type_text[type]);
     317  			}
     318  
     319  			nr_zones--;
     320  			ctl->offset = start + len;
     321  		}
     322  
     323  	}
     324  
     325  	if (only_capacity_sum)
     326  		printf(_("0x%09"PRIx64"\n"), capacity_sum);
     327  
     328  	free(zi);
     329  	close(fd);
     330  
     331  	return 0;
     332  }
     333  
     334  /*
     335   * blkzone reset, open, close, and finish.
     336   */
     337  static int blkzone_action(struct blkzone_control *ctl)
     338  {
     339  	struct blk_zone_range za = { .sector = 0 };
     340  	unsigned long zonesize;
     341  	uint64_t zlen;
     342  	int fd;
     343  
     344  	zonesize = blkdev_chunk_sectors(ctl->devname);
     345  	if (!zonesize)
     346  		errx(EXIT_FAILURE, _("%s: unable to determine zone size"), ctl->devname);
     347  
     348  	fd = init_device(ctl, O_WRONLY | (ctl->force ? 0 : O_EXCL));
     349  
     350  	if (ctl->offset % zonesize )
     351  		errx(EXIT_FAILURE, _("%s: offset %" PRIu64 " is not aligned "
     352  			"to zone size %lu"),
     353  			ctl->devname, ctl->offset, zonesize);
     354  
     355  	if (ctl->offset > ctl->total_sectors)
     356  		errx(EXIT_FAILURE, _("%s: offset is greater than device size"), ctl->devname);
     357  
     358  	if (ctl->count)
     359  		zlen = ctl->count * zonesize;
     360  	else if (ctl->length)
     361  		zlen = ctl->length;
     362  	else
     363  		zlen = ctl->total_sectors;
     364  	if (ctl->offset + zlen > ctl->total_sectors)
     365  		zlen = ctl->total_sectors - ctl->offset;
     366  
     367  	if (ctl->length &&
     368  	   (zlen % zonesize) &&
     369  	    ctl->offset + zlen != ctl->total_sectors)
     370  		errx(EXIT_FAILURE, _("%s: number of sectors %" PRIu64 " is not aligned "
     371  			"to zone size %lu"),
     372  			ctl->devname, ctl->length, zonesize);
     373  
     374  	za.sector = ctl->offset;
     375  	za.nr_sectors = zlen;
     376  
     377  	if (ioctl(fd, ctl->command->ioctl_cmd, &za) == -1)
     378  		err(EXIT_FAILURE, _("%s: %s ioctl failed"),
     379  		    ctl->devname, ctl->command->ioctl_name);
     380  	else if (ctl->verbose)
     381  		printf(_("%s: successful %s of zones in range from %" PRIu64 ", to %" PRIu64),
     382  			ctl->devname,
     383  			ctl->command->name,
     384  			ctl->offset,
     385  			ctl->offset + zlen);
     386  	close(fd);
     387  	return 0;
     388  }
     389  
     390  static void __attribute__((__noreturn__)) usage(void)
     391  {
     392  	FILE *out = stdout;
     393  	size_t i;
     394  
     395  	fputs(USAGE_HEADER, out);
     396  	fprintf(out, _(" %s <command> [options] <device>\n"), program_invocation_short_name);
     397  
     398  	fputs(USAGE_SEPARATOR, out);
     399  	fputs(_("Run zone command on the given block device.\n"), out);
     400  
     401  	fputs(USAGE_COMMANDS, out);
     402  	for (i = 0; i < ARRAY_SIZE(commands); i++)
     403  		fprintf(out, " %-11s  %s\n", commands[i].name, _(commands[i].help));
     404  
     405  	fputs(USAGE_OPTIONS, out);
     406  	fputs(_(" -o, --offset <sector>  start sector of zone to act (in 512-byte sectors)\n"), out);
     407  	fputs(_(" -l, --length <sectors> maximum sectors to act (in 512-byte sectors)\n"), out);
     408  	fputs(_(" -c, --count <number>   maximum number of zones\n"), out);
     409  	fputs(_(" -f, --force            enforce on block devices used by the system\n"), out);
     410  	fputs(_(" -v, --verbose          display more details\n"), out);
     411  	fputs(USAGE_SEPARATOR, out);
     412  	printf(USAGE_HELP_OPTIONS(24));
     413  
     414  	fputs(USAGE_ARGUMENTS, out);
     415  	printf(USAGE_ARG_SIZE(_("<sector> and <sectors>")));
     416  
     417  	printf(USAGE_MAN_TAIL("blkzone(8)"));
     418  	exit(EXIT_SUCCESS);
     419  }
     420  
     421  int main(int argc, char **argv)
     422  {
     423  	int c;
     424  	struct blkzone_control ctl = {
     425  		.devname = NULL
     426  	};
     427  
     428  	static const struct option longopts[] = {
     429  	    { "help",    no_argument,       NULL, 'h' },
     430  	    { "count",   required_argument, NULL, 'c' }, /* max #of zones to operate on */
     431  	    { "length",  required_argument, NULL, 'l' }, /* max of sectors to operate on */
     432  	    { "offset",  required_argument, NULL, 'o' }, /* starting LBA */
     433  	    { "force",   no_argument,       NULL, 'f' },
     434  	    { "verbose", no_argument,       NULL, 'v' },
     435  	    { "version", no_argument,       NULL, 'V' },
     436  	    { NULL, 0, NULL, 0 }
     437  	};
     438  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     439  		{ 'c', 'l' },
     440  		{ 0 }
     441  	};
     442  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     443  
     444  
     445  	setlocale(LC_ALL, "");
     446  	bindtextdomain(PACKAGE, LOCALEDIR);
     447  	textdomain(PACKAGE);
     448  	close_stdout_atexit();
     449  
     450  	if (argc >= 2 && *argv[1] != '-') {
     451  		ctl.command = name_to_command(argv[1]);
     452  		if (!ctl.command)
     453  			errx(EXIT_FAILURE, _("%s is not valid command name"), argv[1]);
     454  		argv++;
     455  		argc--;
     456  	}
     457  
     458  	while ((c = getopt_long(argc, argv, "hc:l:o:fvV", longopts, NULL)) != -1) {
     459  
     460  		err_exclusive_options(c, longopts, excl, excl_st);
     461  
     462  		switch (c) {
     463  		case 'c':
     464  			ctl.count = strtou32_or_err(optarg,
     465  					_("failed to parse number of zones"));
     466  			break;
     467  		case 'l':
     468  			ctl.length = strtosize_or_err(optarg,
     469  					_("failed to parse number of sectors"));
     470  			break;
     471  		case 'o':
     472  			ctl.offset = strtosize_or_err(optarg,
     473  					_("failed to parse zone offset"));
     474  			break;
     475  		case 'f':
     476  			ctl.force = 1;
     477  			break;
     478  		case 'v':
     479  			ctl.verbose = 1;
     480  			break;
     481  
     482  		case 'h':
     483  			usage();
     484  		case 'V':
     485  			print_version(EXIT_SUCCESS);
     486  		default:
     487  			errtryhelp(EXIT_FAILURE);
     488  		}
     489  	}
     490  
     491  	if (!ctl.command)
     492  		errx(EXIT_FAILURE, _("no command specified"));
     493  
     494  	if (optind == argc)
     495  		errx(EXIT_FAILURE, _("no device specified"));
     496  	ctl.devname = argv[optind++];
     497  
     498  	if (optind != argc)
     499  		errx(EXIT_FAILURE,_("unexpected number of arguments"));
     500  
     501  	if (ctl.command->handler(&ctl) < 0)
     502  		return EXIT_FAILURE;
     503  
     504  	return EXIT_SUCCESS;
     505  
     506  }