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