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