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