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
59static 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
69static 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
86typedef enum {
87	ZONE_OF_NORMAL	= 0x00,
88	ZONE_OF_SUMMARY	= 0x01,
89	ZONE_OF_SCRIPT	= 0x02
90} zone_output_flags;
91
92static struct scsi_nv zone_print_opts[] = {
93	{ "normal", ZONE_OF_NORMAL },
94	{ "summary", ZONE_OF_SUMMARY },
95	{ "script", ZONE_OF_SCRIPT }
96};
97
98static 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
106typedef enum {
107	ZONE_PRINT_OK,
108	ZONE_PRINT_MORE_DATA,
109	ZONE_PRINT_ERROR
110} zone_print_status;
111
112typedef 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
124static void usage(int error);
125static void zonectl_print_params(struct disk_zone_disk_params *params);
126zone_print_status zonectl_print_rz(struct disk_zone_report *report,
127				   zone_output_flags out_flags, int first_pass);
128
129static void
130usage(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
137static void
138zonectl_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
216zone_print_status
217zonectl_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, %#*jx, ", field_widths[ZONE_FW_START],
292		    (uintmax_t)entry->zone_start_lba,
293		    field_widths[ZONE_FW_LEN],
294		    (uintmax_t)entry->zone_length, field_widths[ZONE_FW_WP],
295		    (uintmax_t)entry->write_pointer_lba);
296
297		switch (entry->zone_type) {
298		case DISK_ZONE_TYPE_CONVENTIONAL:
299			snprintf(tmpstr, sizeof(tmpstr), "Conventional");
300			break;
301		case DISK_ZONE_TYPE_SEQ_PREFERRED:
302		case DISK_ZONE_TYPE_SEQ_REQUIRED:
303			snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s",
304			    word_sep, (entry->zone_type ==
305			    DISK_ZONE_TYPE_SEQ_PREFERRED) ? "Preferred" :
306			    "Required");
307			break;
308		default:
309			snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x",
310			    word_sep, word_sep, entry->zone_type);
311			break;
312		}
313		printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr);
314
315		switch (entry->zone_condition) {
316		case DISK_ZONE_COND_NOT_WP:
317			snprintf(tmpstr, sizeof(tmpstr), "NWP");
318			break;
319		case DISK_ZONE_COND_EMPTY:
320			snprintf(tmpstr, sizeof(tmpstr), "Empty");
321			break;
322		case DISK_ZONE_COND_IMPLICIT_OPEN:
323			snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen",
324			    word_sep);
325			break;
326		case DISK_ZONE_COND_EXPLICIT_OPEN:
327			snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen",
328			    word_sep);
329			break;
330		case DISK_ZONE_COND_CLOSED:
331			snprintf(tmpstr, sizeof(tmpstr), "Closed");
332			break;
333		case DISK_ZONE_COND_READONLY:
334			snprintf(tmpstr, sizeof(tmpstr), "Readonly");
335			break;
336		case DISK_ZONE_COND_FULL:
337			snprintf(tmpstr, sizeof(tmpstr), "Full");
338			break;
339		case DISK_ZONE_COND_OFFLINE:
340			snprintf(tmpstr, sizeof(tmpstr), "Offline");
341			break;
342		default:
343			snprintf(tmpstr, sizeof(tmpstr), "%#x",
344			    entry->zone_condition);
345			break;
346		}
347
348		printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr);
349
350		if (entry->zone_flags & DISK_ZONE_FLAG_NON_SEQ)
351			snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential",
352			    word_sep);
353		else
354			snprintf(tmpstr, sizeof(tmpstr), "Sequential");
355
356		printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr);
357
358		if (entry->zone_flags & DISK_ZONE_FLAG_RESET)
359			snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded",
360			    word_sep);
361		else
362			snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded",
363			    word_sep, word_sep);
364
365		printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr);
366
367		next_lba = entry->zone_start_lba + entry->zone_length;
368	}
369bailout:
370	report->starting_id = next_lba;
371
372	return (status);
373}
374
375int
376main(int argc, char **argv)
377{
378	int c;
379	int all_zones = 0;
380	int error = 0;
381	int action = -1, rep_option = -1;
382	int fd = -1;
383	uint64_t lba = 0;
384	zone_output_flags out_flags = ZONE_OF_NORMAL;
385	char *filename = NULL;
386	struct disk_zone_args zone_args;
387	struct disk_zone_rep_entry *entries = NULL;
388	uint32_t num_entries = 16384;
389	zone_print_status zp_status;
390	int first_pass = 1;
391	size_t entry_alloc_size;
392	int open_flags = O_RDONLY;
393
394	while ((c = getopt(argc, argv, "ac:d:hl:o:P:?")) != -1) {
395		switch (c) {
396		case 'a':
397			all_zones = 1;
398			break;
399		case 'c': {
400			scsi_nv_status status;
401			int entry_num;
402
403			status = scsi_get_nv(zone_cmd_map,
404			    nitems(zone_cmd_map),
405			    optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
406			if (status == SCSI_NV_FOUND)
407				action = zone_cmd_map[entry_num].value;
408			else {
409				warnx("%s: %s: %s option %s", __func__,
410				    (status == SCSI_NV_AMBIGUOUS) ?
411				    "ambiguous" : "invalid", "zone command",
412				    optarg);
413				error = 1;
414				goto bailout;
415			}
416			break;
417		}
418		case 'd':
419			filename = strdup(optarg);
420			if (filename == NULL)
421				err(1, "Unable to allocate memory for "
422				    "filename");
423			break;
424		case 'l': {
425			char *endptr;
426
427			lba = strtoull(optarg, &endptr, 0);
428			if (*endptr != '\0') {
429				warnx("%s: invalid lba argument %s", __func__,
430				    optarg);
431				error = 1;
432				goto bailout;
433			}
434			break;
435		}
436		case 'o': {
437			scsi_nv_status status;
438			int entry_num;
439
440			status = scsi_get_nv(zone_rep_opts,
441			    (sizeof(zone_rep_opts) /
442			    sizeof(zone_rep_opts[0])),
443			    optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
444			if (status == SCSI_NV_FOUND)
445				rep_option = zone_rep_opts[entry_num].value;
446			else {
447				warnx("%s: %s: %s option %s", __func__,
448				    (status == SCSI_NV_AMBIGUOUS) ?
449				    "ambiguous" : "invalid", "report zones",
450				    optarg);
451				error = 1;
452				goto bailout;
453			}
454			break;
455		}
456		case 'P': {
457			scsi_nv_status status;
458			int entry_num;
459
460			status = scsi_get_nv(zone_print_opts,
461			    (sizeof(zone_print_opts) /
462			    sizeof(zone_print_opts[0])), optarg, &entry_num,
463			    SCSI_NV_FLAG_IG_CASE);
464			if (status == SCSI_NV_FOUND)
465				out_flags = zone_print_opts[entry_num].value;
466			else {
467				warnx("%s: %s: %s option %s", __func__,
468				    (status == SCSI_NV_AMBIGUOUS) ?
469				    "ambiguous" : "invalid", "print",
470				    optarg);
471				error = 1;
472				goto bailout;
473			}
474			break;
475		}
476		default:
477			error = 1;
478		case 'h': /*FALLTHROUGH*/
479			usage(error);
480			goto bailout;
481			break; /*NOTREACHED*/
482		}
483	}
484
485	if (filename == NULL) {
486		warnx("You must specify a device with -d");
487		error = 1;
488	}
489	if (action == -1) {
490		warnx("You must specify an action with -c");
491		error = 1;
492	}
493
494	if (error != 0) {
495		usage(error);
496		goto bailout;
497	}
498
499	bzero(&zone_args, sizeof(zone_args));
500
501	zone_args.zone_cmd = action;
502
503	switch (action) {
504	case DISK_ZONE_OPEN:
505	case DISK_ZONE_CLOSE:
506	case DISK_ZONE_FINISH:
507	case DISK_ZONE_RWP:
508		open_flags = O_RDWR;
509		zone_args.zone_params.rwp.id = lba;
510		if (all_zones != 0)
511			zone_args.zone_params.rwp.flags |=
512			    DISK_ZONE_RWP_FLAG_ALL;
513		break;
514	case DISK_ZONE_REPORT_ZONES: {
515		entry_alloc_size = num_entries *
516		    sizeof(struct disk_zone_rep_entry);
517		entries = malloc(entry_alloc_size);
518		if (entries == NULL) {
519			warn("Could not allocate %zu bytes",
520			    entry_alloc_size);
521			error = 1;
522			goto bailout;
523		}
524		zone_args.zone_params.report.entries_allocated = num_entries;
525		zone_args.zone_params.report.entries = entries;
526		zone_args.zone_params.report.starting_id = lba;
527		if (rep_option != -1)
528			zone_args.zone_params.report.rep_options = rep_option;
529		break;
530	}
531	case DISK_ZONE_GET_PARAMS:
532		break;
533	default:
534		warnx("Unknown action %d", action);
535		error = 1;
536		goto bailout;
537		break; /*NOTREACHED*/
538	}
539
540	fd = open(filename, open_flags);
541	if (fd == -1) {
542		warn("Unable to open device %s", filename);
543		error = 1;
544		goto bailout;
545	}
546next_chunk:
547	error = ioctl(fd, DIOCZONECMD, &zone_args);
548	if (error == -1) {
549		warn("DIOCZONECMD ioctl failed");
550		error = 1;
551		goto bailout;
552	}
553
554	switch (action) {
555	case DISK_ZONE_OPEN:
556	case DISK_ZONE_CLOSE:
557	case DISK_ZONE_FINISH:
558	case DISK_ZONE_RWP:
559		break;
560	case DISK_ZONE_REPORT_ZONES:
561		zp_status = zonectl_print_rz(&zone_args.zone_params.report,
562		    out_flags, first_pass);
563		if (zp_status == ZONE_PRINT_MORE_DATA) {
564			first_pass = 0;
565			bzero(entries, entry_alloc_size);
566			zone_args.zone_params.report.entries_filled = 0;
567			goto next_chunk;
568		} else if (zp_status == ZONE_PRINT_ERROR)
569			error = 1;
570		break;
571	case DISK_ZONE_GET_PARAMS:
572		zonectl_print_params(&zone_args.zone_params.disk_params);
573		break;
574	default:
575		warnx("Unknown action %d", action);
576		error = 1;
577		goto bailout;
578		break; /*NOTREACHED*/
579	}
580bailout:
581	free(entries);
582
583	if (fd != -1)
584		close(fd);
585	exit (error);
586}
587