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, %#*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 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