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
usage(int error)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
zonectl_print_params(struct disk_zone_disk_params * params)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
zonectl_print_rz(struct disk_zone_report * report,zone_output_flags out_flags,int first_pass)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, %#*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 }
369 bailout:
370 report->starting_id = next_lba;
371
372 return (status);
373 }
374
375 int
main(int argc,char ** argv)376 main(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 }
546 next_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 }
580 bailout:
581 free(entries);
582
583 if (fd != -1)
584 close(fd);
585 exit (error);
586 }
587