xref: /freebsd/usr.sbin/zonectl/zonectl.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*-
2  * Copyright (c) 2015, 2016 Spectra Logic Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions, and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    substantially similar to the "NO WARRANTY" disclaimer below
13  *    ("Disclaimer") and any redistribution must be conditioned upon
14  *    including a substantially similar Disclaimer requirement for further
15  *    binary redistribution.
16  *
17  * NO WARRANTY
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGES.
29  *
30  * Authors: Ken Merry           (Spectra Logic Corporation)
31  */
32 
33 #include <sys/cdefs.h>
34 #include <sys/ioctl.h>
35 #include <sys/param.h>
36 #include <sys/stdint.h>
37 #include <sys/endian.h>
38 #include <sys/sbuf.h>
39 #include <sys/queue.h>
40 #include <sys/disk.h>
41 #include <sys/disk_zone.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <inttypes.h>
45 #include <unistd.h>
46 #include <string.h>
47 #include <strings.h>
48 #include <fcntl.h>
49 #include <ctype.h>
50 #include <limits.h>
51 #include <err.h>
52 #include <locale.h>
53 
54 #include <cam/cam.h>
55 #include <cam/cam_debug.h>
56 #include <cam/cam_ccb.h>
57 #include <cam/scsi/scsi_all.h>
58 
59 static struct scsi_nv zone_cmd_map[] = {
60 	{ "rz", DISK_ZONE_REPORT_ZONES },
61 	{ "reportzones", DISK_ZONE_REPORT_ZONES },
62 	{ "close", DISK_ZONE_CLOSE },
63 	{ "finish", DISK_ZONE_FINISH },
64 	{ "open", DISK_ZONE_OPEN },
65 	{ "rwp", DISK_ZONE_RWP },
66 	{ "params", DISK_ZONE_GET_PARAMS }
67 };
68 
69 static struct scsi_nv zone_rep_opts[] = {
70 	{ "all", DISK_ZONE_REP_ALL },
71 	{ "empty", DISK_ZONE_REP_EMPTY },
72 	{ "imp_open", DISK_ZONE_REP_IMP_OPEN },
73 	{ "exp_open", DISK_ZONE_REP_EXP_OPEN },
74 	{ "closed", DISK_ZONE_REP_CLOSED },
75 	{ "full", DISK_ZONE_REP_FULL },
76 	{ "readonly", DISK_ZONE_REP_READONLY },
77 	{ "ro", DISK_ZONE_REP_READONLY },
78 	{ "offline", DISK_ZONE_REP_OFFLINE },
79 	{ "reset", DISK_ZONE_REP_RWP },
80 	{ "rwp", DISK_ZONE_REP_RWP },
81 	{ "nonseq", DISK_ZONE_REP_NON_SEQ },
82 	{ "nonwp", DISK_ZONE_REP_NON_WP }
83 };
84 
85 
86 typedef enum {
87 	ZONE_OF_NORMAL	= 0x00,
88 	ZONE_OF_SUMMARY	= 0x01,
89 	ZONE_OF_SCRIPT	= 0x02
90 } zone_output_flags;
91 
92 static struct scsi_nv zone_print_opts[] = {
93 	{ "normal", ZONE_OF_NORMAL },
94 	{ "summary", ZONE_OF_SUMMARY },
95 	{ "script", ZONE_OF_SCRIPT }
96 };
97 
98 static struct scsi_nv zone_cmd_desc_table[] = {
99 	{"Report Zones", DISK_ZONE_RZ_SUP },
100 	{"Open", DISK_ZONE_OPEN_SUP },
101 	{"Close", DISK_ZONE_CLOSE_SUP },
102 	{"Finish", DISK_ZONE_FINISH_SUP },
103 	{"Reset Write Pointer", DISK_ZONE_RWP_SUP }
104 };
105 
106 typedef enum {
107 	ZONE_PRINT_OK,
108 	ZONE_PRINT_MORE_DATA,
109 	ZONE_PRINT_ERROR
110 } zone_print_status;
111 
112 typedef enum {
113 	ZONE_FW_START,
114 	ZONE_FW_LEN,
115 	ZONE_FW_WP,
116 	ZONE_FW_TYPE,
117 	ZONE_FW_COND,
118 	ZONE_FW_SEQ,
119 	ZONE_FW_RESET,
120 	ZONE_NUM_FIELDS
121 } zone_field_widths;
122 
123 
124 static void usage(int error);
125 static void zonectl_print_params(struct disk_zone_disk_params *params);
126 zone_print_status zonectl_print_rz(struct disk_zone_report *report,
127 				   zone_output_flags out_flags, int first_pass);
128 
129 static void
130 usage(int error)
131 {
132 	fprintf(error ? stderr : stdout,
133 "usage: zonectl <-d dev> <-c cmd> [-a][-o rep_opts] [-l lba][-P print_opts]\n"
134 	);
135 }
136 
137 static void
138 zonectl_print_params(struct disk_zone_disk_params *params)
139 {
140 	unsigned int i;
141 	int first;
142 
143 	printf("Zone Mode: ");
144 	switch (params->zone_mode) {
145 	case DISK_ZONE_MODE_NONE:
146 		printf("None");
147 		break;
148 	case DISK_ZONE_MODE_HOST_AWARE:
149 		printf("Host Aware");
150 		break;
151 	case DISK_ZONE_MODE_DRIVE_MANAGED:
152 		printf("Drive Managed");
153 		break;
154 	case DISK_ZONE_MODE_HOST_MANAGED:
155 		printf("Host Managed");
156 		break;
157 	default:
158 		printf("Unknown mode %#x", params->zone_mode);
159 		break;
160 	}
161 	printf("\n");
162 
163 	first = 1;
164 	printf("Command support: ");
165 	for (i = 0; i < sizeof(zone_cmd_desc_table) /
166 	     sizeof(zone_cmd_desc_table[0]); i++) {
167 		if (params->flags & zone_cmd_desc_table[i].value) {
168 			if (first == 0)
169 				printf(", ");
170 			else
171 				first = 0;
172 			printf("%s", zone_cmd_desc_table[i].name);
173 		}
174 	}
175 	if (first == 1)
176 		printf("None");
177 	printf("\n");
178 
179 	printf("Unrestricted Read in Sequential Write Required Zone "
180 	    "(URSWRZ): %s\n", (params->flags & DISK_ZONE_DISK_URSWRZ) ?
181 	    "Yes" : "No");
182 
183 	printf("Optimal Number of Open Sequential Write Preferred Zones: ");
184 	if (params->flags & DISK_ZONE_OPT_SEQ_SET)
185 		if (params->optimal_seq_zones == SVPD_ZBDC_OPT_SEQ_NR)
186 			printf("Not Reported");
187 		else
188 			printf("%ju", (uintmax_t)params->optimal_seq_zones);
189 	else
190 		printf("Not Set");
191 	printf("\n");
192 
193 
194 	printf("Optimal Number of Non-Sequentially Written Sequential Write "
195 	   "Preferred Zones: ");
196 	if (params->flags & DISK_ZONE_OPT_NONSEQ_SET)
197 		if (params->optimal_nonseq_zones == SVPD_ZBDC_OPT_NONSEQ_NR)
198 			printf("Not Reported");
199 		else
200 			printf("%ju",(uintmax_t)params->optimal_nonseq_zones);
201 	else
202 		printf("Not Set");
203 	printf("\n");
204 
205 	printf("Maximum Number of Open Sequential Write Required Zones: ");
206 	if (params->flags & DISK_ZONE_MAX_SEQ_SET)
207 		if (params->max_seq_zones == SVPD_ZBDC_MAX_SEQ_UNLIMITED)
208 			printf("Unlimited");
209 		else
210 			printf("%ju", (uintmax_t)params->max_seq_zones);
211 	else
212 		printf("Not Set");
213 	printf("\n");
214 }
215 
216 zone_print_status
217 zonectl_print_rz(struct disk_zone_report *report, zone_output_flags out_flags,
218 		 int first_pass)
219 {
220 	zone_print_status status = ZONE_PRINT_OK;
221 	struct disk_zone_rep_header *header = &report->header;
222 	int field_widths[ZONE_NUM_FIELDS];
223 	struct disk_zone_rep_entry *entry;
224 	uint64_t next_lba = 0;
225 	char tmpstr[80];
226 	char word_sep;
227 	uint32_t i;
228 
229 	field_widths[ZONE_FW_START] = 11;
230 	field_widths[ZONE_FW_LEN] = 6;
231 	field_widths[ZONE_FW_WP] = 11;
232 	field_widths[ZONE_FW_TYPE] = 13;
233 	field_widths[ZONE_FW_COND] = 13;
234 	field_widths[ZONE_FW_SEQ] = 14;
235 	field_widths[ZONE_FW_RESET] = 16;
236 
237 	if ((report->entries_available - report->entries_filled) > 0)
238 		status = ZONE_PRINT_MORE_DATA;
239 
240 	if (out_flags == ZONE_OF_SCRIPT)
241 		word_sep = '_';
242 	else
243 		word_sep = ' ';
244 
245 	if ((out_flags != ZONE_OF_SCRIPT)
246 	 && (first_pass != 0)) {
247 		printf("%u zones, Maximum LBA %#jx (%ju)\n",
248 		    report->entries_available,
249 		    (uintmax_t)header->maximum_lba,
250 		    (uintmax_t)header->maximum_lba);
251 
252 		switch (header->same) {
253 		case DISK_ZONE_SAME_ALL_DIFFERENT:
254 			printf("Zone lengths and types may vary\n");
255 			break;
256 		case DISK_ZONE_SAME_ALL_SAME:
257 			printf("Zone lengths and types are all the same\n");
258 			break;
259 		case DISK_ZONE_SAME_LAST_DIFFERENT:
260 			printf("Zone types are the same, last zone length "
261 			    "differs\n");
262 			break;
263 		case DISK_ZONE_SAME_TYPES_DIFFERENT:
264 			printf("Zone lengths are the same, types vary\n");
265 			break;
266 		default:
267 			printf("Unknown SAME field value %#x\n",header->same);
268 			break;
269 		}
270 	}
271 	if (out_flags == ZONE_OF_SUMMARY) {
272 		status = ZONE_PRINT_OK;
273 		goto bailout;
274 	}
275 
276 	if ((out_flags == ZONE_OF_NORMAL)
277 	 && (first_pass != 0)) {
278 		printf("%*s  %*s  %*s  %*s  %*s  %*s  %*s\n",
279 		    field_widths[ZONE_FW_START], "Start LBA",
280 		    field_widths[ZONE_FW_LEN], "Length",
281 		    field_widths[ZONE_FW_WP], "WP LBA",
282 		    field_widths[ZONE_FW_TYPE], "Zone Type",
283 		    field_widths[ZONE_FW_COND], "Condition",
284 		    field_widths[ZONE_FW_SEQ], "Sequential",
285 		    field_widths[ZONE_FW_RESET], "Reset");
286 	}
287 
288 	for (i = 0; i < report->entries_filled; i++) {
289 		entry = &report->entries[i];
290 
291 		printf("%#*jx, %*ju, ", field_widths[ZONE_FW_START],
292 		    (uintmax_t)entry->zone_start_lba,
293 		    field_widths[ZONE_FW_LEN],
294 		    (uintmax_t)entry->zone_length);
295 		if (DISK_ZONE_WP_INVALID(entry->zone_condition)) {
296 			/*
297 			 * The zone has no write pointer, which really means
298 			 * "N/A".  Report it as -1, even though the field is
299 			 * technically unsigned, to save space.
300 			 */
301 			printf("%*d, ", field_widths[ZONE_FW_WP], -1);
302 		} else {
303 			printf("%#*jx, ", field_widths[ZONE_FW_WP],
304 			    (uintmax_t)entry->write_pointer_lba);
305 		}
306 
307 		switch (entry->zone_type) {
308 		case DISK_ZONE_TYPE_CONVENTIONAL:
309 			snprintf(tmpstr, sizeof(tmpstr), "Conventional");
310 			break;
311 		case DISK_ZONE_TYPE_SEQ_PREFERRED:
312 		case DISK_ZONE_TYPE_SEQ_REQUIRED:
313 			snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s",
314 			    word_sep, (entry->zone_type ==
315 			    DISK_ZONE_TYPE_SEQ_PREFERRED) ? "Preferred" :
316 			    "Required");
317 			break;
318 		default:
319 			snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x",
320 			    word_sep, word_sep, entry->zone_type);
321 			break;
322 		}
323 		printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr);
324 
325 		switch (entry->zone_condition) {
326 		case DISK_ZONE_COND_NOT_WP:
327 			snprintf(tmpstr, sizeof(tmpstr), "NWP");
328 			break;
329 		case DISK_ZONE_COND_EMPTY:
330 			snprintf(tmpstr, sizeof(tmpstr), "Empty");
331 			break;
332 		case DISK_ZONE_COND_IMPLICIT_OPEN:
333 			snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen",
334 			    word_sep);
335 			break;
336 		case DISK_ZONE_COND_EXPLICIT_OPEN:
337 			snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen",
338 			    word_sep);
339 			break;
340 		case DISK_ZONE_COND_CLOSED:
341 			snprintf(tmpstr, sizeof(tmpstr), "Closed");
342 			break;
343 		case DISK_ZONE_COND_READONLY:
344 			snprintf(tmpstr, sizeof(tmpstr), "Readonly");
345 			break;
346 		case DISK_ZONE_COND_FULL:
347 			snprintf(tmpstr, sizeof(tmpstr), "Full");
348 			break;
349 		case DISK_ZONE_COND_OFFLINE:
350 			snprintf(tmpstr, sizeof(tmpstr), "Offline");
351 			break;
352 		default:
353 			snprintf(tmpstr, sizeof(tmpstr), "%#x",
354 			    entry->zone_condition);
355 			break;
356 		}
357 
358 		printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr);
359 
360 		if (entry->zone_flags & DISK_ZONE_FLAG_NON_SEQ)
361 			snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential",
362 			    word_sep);
363 		else
364 			snprintf(tmpstr, sizeof(tmpstr), "Sequential");
365 
366 		printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr);
367 
368 		if (entry->zone_flags & DISK_ZONE_FLAG_RESET)
369 			snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded",
370 			    word_sep);
371 		else
372 			snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded",
373 			    word_sep, word_sep);
374 
375 		printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr);
376 
377 		next_lba = entry->zone_start_lba + entry->zone_length;
378 	}
379 bailout:
380 	report->starting_id = next_lba;
381 
382 	return (status);
383 }
384 
385 int
386 main(int argc, char **argv)
387 {
388 	int c;
389 	int all_zones = 0;
390 	int error = 0;
391 	int action = -1, rep_option = -1;
392 	int fd = -1;
393 	uint64_t lba = 0;
394 	zone_output_flags out_flags = ZONE_OF_NORMAL;
395 	char *filename = NULL;
396 	struct disk_zone_args zone_args;
397 	struct disk_zone_rep_entry *entries = NULL;
398 	uint32_t num_entries = 16384;
399 	zone_print_status zp_status;
400 	int first_pass = 1;
401 	size_t entry_alloc_size;
402 	int open_flags = O_RDONLY;
403 
404 	while ((c = getopt(argc, argv, "ac:d:hl:o:P:?")) != -1) {
405 		switch (c) {
406 		case 'a':
407 			all_zones = 1;
408 			break;
409 		case 'c': {
410 			scsi_nv_status status;
411 			int entry_num;
412 
413 			status = scsi_get_nv(zone_cmd_map,
414 			    nitems(zone_cmd_map),
415 			    optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
416 			if (status == SCSI_NV_FOUND)
417 				action = zone_cmd_map[entry_num].value;
418 			else {
419 				warnx("%s: %s: %s option %s", __func__,
420 				    (status == SCSI_NV_AMBIGUOUS) ?
421 				    "ambiguous" : "invalid", "zone command",
422 				    optarg);
423 				error = 1;
424 				goto bailout;
425 			}
426 			break;
427 		}
428 		case 'd':
429 			filename = strdup(optarg);
430 			if (filename == NULL)
431 				err(1, "Unable to allocate memory for "
432 				    "filename");
433 			break;
434 		case 'l': {
435 			char *endptr;
436 
437 			lba = strtoull(optarg, &endptr, 0);
438 			if (*endptr != '\0') {
439 				warnx("%s: invalid lba argument %s", __func__,
440 				    optarg);
441 				error = 1;
442 				goto bailout;
443 			}
444 			break;
445 		}
446 		case 'o': {
447 			scsi_nv_status status;
448 			int entry_num;
449 
450 			status = scsi_get_nv(zone_rep_opts,
451 			    (sizeof(zone_rep_opts) /
452 			    sizeof(zone_rep_opts[0])),
453 			    optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
454 			if (status == SCSI_NV_FOUND)
455 				rep_option = zone_rep_opts[entry_num].value;
456 			else {
457 				warnx("%s: %s: %s option %s", __func__,
458 				    (status == SCSI_NV_AMBIGUOUS) ?
459 				    "ambiguous" : "invalid", "report zones",
460 				    optarg);
461 				error = 1;
462 				goto bailout;
463 			}
464 			break;
465 		}
466 		case 'P': {
467 			scsi_nv_status status;
468 			int entry_num;
469 
470 			status = scsi_get_nv(zone_print_opts,
471 			    (sizeof(zone_print_opts) /
472 			    sizeof(zone_print_opts[0])), optarg, &entry_num,
473 			    SCSI_NV_FLAG_IG_CASE);
474 			if (status == SCSI_NV_FOUND)
475 				out_flags = zone_print_opts[entry_num].value;
476 			else {
477 				warnx("%s: %s: %s option %s", __func__,
478 				    (status == SCSI_NV_AMBIGUOUS) ?
479 				    "ambiguous" : "invalid", "print",
480 				    optarg);
481 				error = 1;
482 				goto bailout;
483 			}
484 			break;
485 		}
486 		default:
487 			error = 1;
488 		case 'h': /*FALLTHROUGH*/
489 			usage(error);
490 			goto bailout;
491 			break; /*NOTREACHED*/
492 		}
493 	}
494 
495 	if (filename == NULL) {
496 		warnx("You must specify a device with -d");
497 		error = 1;
498 	}
499 	if (action == -1) {
500 		warnx("You must specify an action with -c");
501 		error = 1;
502 	}
503 
504 	if (error != 0) {
505 		usage(error);
506 		goto bailout;
507 	}
508 
509 	bzero(&zone_args, sizeof(zone_args));
510 
511 	zone_args.zone_cmd = action;
512 
513 	switch (action) {
514 	case DISK_ZONE_OPEN:
515 	case DISK_ZONE_CLOSE:
516 	case DISK_ZONE_FINISH:
517 	case DISK_ZONE_RWP:
518 		open_flags = O_RDWR;
519 		zone_args.zone_params.rwp.id = lba;
520 		if (all_zones != 0)
521 			zone_args.zone_params.rwp.flags |=
522 			    DISK_ZONE_RWP_FLAG_ALL;
523 		break;
524 	case DISK_ZONE_REPORT_ZONES: {
525 		entry_alloc_size = num_entries *
526 		    sizeof(struct disk_zone_rep_entry);
527 		entries = malloc(entry_alloc_size);
528 		if (entries == NULL) {
529 			warn("Could not allocate %zu bytes",
530 			    entry_alloc_size);
531 			error = 1;
532 			goto bailout;
533 		}
534 		zone_args.zone_params.report.entries_allocated = num_entries;
535 		zone_args.zone_params.report.entries = entries;
536 		zone_args.zone_params.report.starting_id = lba;
537 		if (rep_option != -1)
538 			zone_args.zone_params.report.rep_options = rep_option;
539 		break;
540 	}
541 	case DISK_ZONE_GET_PARAMS:
542 		break;
543 	default:
544 		warnx("Unknown action %d", action);
545 		error = 1;
546 		goto bailout;
547 		break; /*NOTREACHED*/
548 	}
549 
550 	fd = open(filename, open_flags);
551 	if (fd == -1) {
552 		warn("Unable to open device %s", filename);
553 		error = 1;
554 		goto bailout;
555 	}
556 next_chunk:
557 	error = ioctl(fd, DIOCZONECMD, &zone_args);
558 	if (error == -1) {
559 		warn("DIOCZONECMD ioctl failed");
560 		error = 1;
561 		goto bailout;
562 	}
563 
564 	switch (action) {
565 	case DISK_ZONE_OPEN:
566 	case DISK_ZONE_CLOSE:
567 	case DISK_ZONE_FINISH:
568 	case DISK_ZONE_RWP:
569 		break;
570 	case DISK_ZONE_REPORT_ZONES:
571 		zp_status = zonectl_print_rz(&zone_args.zone_params.report,
572 		    out_flags, first_pass);
573 		if (zp_status == ZONE_PRINT_MORE_DATA) {
574 			first_pass = 0;
575 			bzero(entries, entry_alloc_size);
576 			zone_args.zone_params.report.entries_filled = 0;
577 			goto next_chunk;
578 		} else if (zp_status == ZONE_PRINT_ERROR)
579 			error = 1;
580 		break;
581 	case DISK_ZONE_GET_PARAMS:
582 		zonectl_print_params(&zone_args.zone_params.disk_params);
583 		break;
584 	default:
585 		warnx("Unknown action %d", action);
586 		error = 1;
587 		goto bailout;
588 		break; /*NOTREACHED*/
589 	}
590 bailout:
591 	free(entries);
592 
593 	if (fd != -1)
594 		close(fd);
595 	exit (error);
596 }
597