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