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