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
61static 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
71static 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
88typedef enum {
89	ZONE_OF_NORMAL	= 0x00,
90	ZONE_OF_SUMMARY	= 0x01,
91	ZONE_OF_SCRIPT	= 0x02
92} zone_output_flags;
93
94static struct scsi_nv zone_print_opts[] = {
95	{ "normal", ZONE_OF_NORMAL },
96	{ "summary", ZONE_OF_SUMMARY },
97	{ "script", ZONE_OF_SCRIPT }
98};
99
100static 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
108typedef enum {
109	ZONE_PRINT_OK,
110	ZONE_PRINT_MORE_DATA,
111	ZONE_PRINT_ERROR
112} zone_print_status;
113
114typedef 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
126static void usage(int error);
127static void zonectl_print_params(struct disk_zone_disk_params *params);
128zone_print_status zonectl_print_rz(struct disk_zone_report *report,
129				   zone_output_flags out_flags, int first_pass);
130
131static void
132usage(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
139static void
140zonectl_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
218zone_print_status
219zonectl_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	}
374bailout:
375	report->starting_id = next_lba;
376
377	return (status);
378}
379
380int
381main(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	}
551next_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	}
585bailout:
586	free(entries);
587
588	if (fd != -1)
589		close(fd);
590	exit (error);
591}
592