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 uint32_t i; 230 231 field_widths[ZONE_FW_START] = 11; 232 field_widths[ZONE_FW_LEN] = 6; 233 field_widths[ZONE_FW_WP] = 11; 234 field_widths[ZONE_FW_TYPE] = 13; 235 field_widths[ZONE_FW_COND] = 13; 236 field_widths[ZONE_FW_SEQ] = 14; 237 field_widths[ZONE_FW_RESET] = 16; 238 239 if ((report->entries_available - report->entries_filled) > 0) 240 status = ZONE_PRINT_MORE_DATA; 241 242 if (out_flags == ZONE_OF_SCRIPT) 243 word_sep = '_'; 244 else 245 word_sep = ' '; 246 247 if ((out_flags != ZONE_OF_SCRIPT) 248 && (first_pass != 0)) { 249 printf("%u zones, Maximum LBA %#jx (%ju)\n", 250 report->entries_available, 251 (uintmax_t)header->maximum_lba, 252 (uintmax_t)header->maximum_lba); 253 254 switch (header->same) { 255 case DISK_ZONE_SAME_ALL_DIFFERENT: 256 printf("Zone lengths and types may vary\n"); 257 break; 258 case DISK_ZONE_SAME_ALL_SAME: 259 printf("Zone lengths and types are all the same\n"); 260 break; 261 case DISK_ZONE_SAME_LAST_DIFFERENT: 262 printf("Zone types are the same, last zone length " 263 "differs\n"); 264 break; 265 case DISK_ZONE_SAME_TYPES_DIFFERENT: 266 printf("Zone lengths are the same, types vary\n"); 267 break; 268 default: 269 printf("Unknown SAME field value %#x\n",header->same); 270 break; 271 } 272 } 273 if (out_flags == ZONE_OF_SUMMARY) { 274 status = ZONE_PRINT_OK; 275 goto bailout; 276 } 277 278 if ((out_flags == ZONE_OF_NORMAL) 279 && (first_pass != 0)) { 280 printf("%*s %*s %*s %*s %*s %*s %*s\n", 281 field_widths[ZONE_FW_START], "Start LBA", 282 field_widths[ZONE_FW_LEN], "Length", 283 field_widths[ZONE_FW_WP], "WP LBA", 284 field_widths[ZONE_FW_TYPE], "Zone Type", 285 field_widths[ZONE_FW_COND], "Condition", 286 field_widths[ZONE_FW_SEQ], "Sequential", 287 field_widths[ZONE_FW_RESET], "Reset"); 288 } 289 290 for (i = 0; i < report->entries_filled; i++) { 291 entry = &report->entries[i]; 292 293 printf("%#*jx, %*ju, %#*jx, ", field_widths[ZONE_FW_START], 294 (uintmax_t)entry->zone_start_lba, 295 field_widths[ZONE_FW_LEN], 296 (uintmax_t)entry->zone_length, field_widths[ZONE_FW_WP], 297 (uintmax_t)entry->write_pointer_lba); 298 299 switch (entry->zone_type) { 300 case DISK_ZONE_TYPE_CONVENTIONAL: 301 snprintf(tmpstr, sizeof(tmpstr), "Conventional"); 302 break; 303 case DISK_ZONE_TYPE_SEQ_PREFERRED: 304 case DISK_ZONE_TYPE_SEQ_REQUIRED: 305 snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s", 306 word_sep, (entry->zone_type == 307 DISK_ZONE_TYPE_SEQ_PREFERRED) ? "Preferred" : 308 "Required"); 309 break; 310 default: 311 snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x", 312 word_sep, word_sep, entry->zone_type); 313 break; 314 } 315 printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr); 316 317 switch (entry->zone_condition) { 318 case DISK_ZONE_COND_NOT_WP: 319 snprintf(tmpstr, sizeof(tmpstr), "NWP"); 320 break; 321 case DISK_ZONE_COND_EMPTY: 322 snprintf(tmpstr, sizeof(tmpstr), "Empty"); 323 break; 324 case DISK_ZONE_COND_IMPLICIT_OPEN: 325 snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen", 326 word_sep); 327 break; 328 case DISK_ZONE_COND_EXPLICIT_OPEN: 329 snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen", 330 word_sep); 331 break; 332 case DISK_ZONE_COND_CLOSED: 333 snprintf(tmpstr, sizeof(tmpstr), "Closed"); 334 break; 335 case DISK_ZONE_COND_READONLY: 336 snprintf(tmpstr, sizeof(tmpstr), "Readonly"); 337 break; 338 case DISK_ZONE_COND_FULL: 339 snprintf(tmpstr, sizeof(tmpstr), "Full"); 340 break; 341 case DISK_ZONE_COND_OFFLINE: 342 snprintf(tmpstr, sizeof(tmpstr), "Offline"); 343 break; 344 default: 345 snprintf(tmpstr, sizeof(tmpstr), "%#x", 346 entry->zone_condition); 347 break; 348 } 349 350 printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr); 351 352 if (entry->zone_flags & DISK_ZONE_FLAG_NON_SEQ) 353 snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential", 354 word_sep); 355 else 356 snprintf(tmpstr, sizeof(tmpstr), "Sequential"); 357 358 printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr); 359 360 if (entry->zone_flags & DISK_ZONE_FLAG_RESET) 361 snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded", 362 word_sep); 363 else 364 snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded", 365 word_sep, word_sep); 366 367 printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr); 368 369 next_lba = entry->zone_start_lba + entry->zone_length; 370 } 371 bailout: 372 report->starting_id = next_lba; 373 374 return (status); 375 } 376 377 int 378 main(int argc, char **argv) 379 { 380 int c; 381 int all_zones = 0; 382 int error = 0; 383 int action = -1, rep_option = -1; 384 int fd = -1; 385 uint64_t lba = 0; 386 zone_output_flags out_flags = ZONE_OF_NORMAL; 387 char *filename = NULL; 388 struct disk_zone_args zone_args; 389 struct disk_zone_rep_entry *entries = NULL; 390 uint32_t num_entries = 16384; 391 zone_print_status zp_status; 392 int first_pass = 1; 393 size_t entry_alloc_size; 394 int open_flags = O_RDONLY; 395 396 while ((c = getopt(argc, argv, "ac:d:hl:o:P:?")) != -1) { 397 switch (c) { 398 case 'a': 399 all_zones = 1; 400 break; 401 case 'c': { 402 scsi_nv_status status; 403 int entry_num; 404 405 status = scsi_get_nv(zone_cmd_map, 406 (sizeof(zone_cmd_map) / sizeof(zone_cmd_map[0])), 407 optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); 408 if (status == SCSI_NV_FOUND) 409 action = zone_cmd_map[entry_num].value; 410 else { 411 warnx("%s: %s: %s option %s", __func__, 412 (status == SCSI_NV_AMBIGUOUS) ? 413 "ambiguous" : "invalid", "zone command", 414 optarg); 415 error = 1; 416 goto bailout; 417 } 418 break; 419 } 420 case 'd': 421 filename = strdup(optarg); 422 if (filename == NULL) 423 err(1, "Unable to allocate memory for " 424 "filename"); 425 break; 426 case 'l': { 427 char *endptr; 428 429 lba = strtoull(optarg, &endptr, 0); 430 if (*endptr != '\0') { 431 warnx("%s: invalid lba argument %s", __func__, 432 optarg); 433 error = 1; 434 goto bailout; 435 } 436 break; 437 } 438 case 'o': { 439 scsi_nv_status status; 440 int entry_num; 441 442 status = scsi_get_nv(zone_rep_opts, 443 (sizeof(zone_rep_opts) / 444 sizeof(zone_rep_opts[0])), 445 optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); 446 if (status == SCSI_NV_FOUND) 447 rep_option = zone_rep_opts[entry_num].value; 448 else { 449 warnx("%s: %s: %s option %s", __func__, 450 (status == SCSI_NV_AMBIGUOUS) ? 451 "ambiguous" : "invalid", "report zones", 452 optarg); 453 error = 1; 454 goto bailout; 455 } 456 break; 457 } 458 case 'P': { 459 scsi_nv_status status; 460 int entry_num; 461 462 status = scsi_get_nv(zone_print_opts, 463 (sizeof(zone_print_opts) / 464 sizeof(zone_print_opts[0])), optarg, &entry_num, 465 SCSI_NV_FLAG_IG_CASE); 466 if (status == SCSI_NV_FOUND) 467 out_flags = zone_print_opts[entry_num].value; 468 else { 469 warnx("%s: %s: %s option %s", __func__, 470 (status == SCSI_NV_AMBIGUOUS) ? 471 "ambiguous" : "invalid", "print", 472 optarg); 473 error = 1; 474 goto bailout; 475 } 476 break; 477 } 478 default: 479 error = 1; 480 case 'h': /*FALLTHROUGH*/ 481 usage(error); 482 goto bailout; 483 break; /*NOTREACHED*/ 484 } 485 } 486 487 if (filename == NULL) { 488 warnx("You must specify a device with -d"); 489 error = 1; 490 } 491 if (action == -1) { 492 warnx("You must specify an action with -c"); 493 error = 1; 494 } 495 496 if (error != 0) { 497 usage(error); 498 goto bailout; 499 } 500 501 bzero(&zone_args, sizeof(zone_args)); 502 503 zone_args.zone_cmd = action; 504 505 switch (action) { 506 case DISK_ZONE_OPEN: 507 case DISK_ZONE_CLOSE: 508 case DISK_ZONE_FINISH: 509 case DISK_ZONE_RWP: 510 open_flags = O_RDWR; 511 zone_args.zone_params.rwp.id = lba; 512 if (all_zones != 0) 513 zone_args.zone_params.rwp.flags |= 514 DISK_ZONE_RWP_FLAG_ALL; 515 break; 516 case DISK_ZONE_REPORT_ZONES: { 517 entry_alloc_size = num_entries * 518 sizeof(struct disk_zone_rep_entry); 519 entries = malloc(entry_alloc_size); 520 if (entries == NULL) { 521 warn("Could not allocate %zu bytes", 522 entry_alloc_size); 523 error = 1; 524 goto bailout; 525 } 526 zone_args.zone_params.report.entries_allocated = num_entries; 527 zone_args.zone_params.report.entries = entries; 528 zone_args.zone_params.report.starting_id = lba; 529 if (rep_option != -1) 530 zone_args.zone_params.report.rep_options = rep_option; 531 break; 532 } 533 case DISK_ZONE_GET_PARAMS: 534 break; 535 default: 536 warnx("Unknown action %d", action); 537 error = 1; 538 goto bailout; 539 break; /*NOTREACHED*/ 540 } 541 542 fd = open(filename, open_flags); 543 if (fd == -1) { 544 warn("Unable to open device %s", filename); 545 error = 1; 546 goto bailout; 547 } 548 next_chunk: 549 error = ioctl(fd, DIOCZONECMD, &zone_args); 550 if (error == -1) { 551 warn("DIOCZONECMD ioctl failed"); 552 error = 1; 553 goto bailout; 554 } 555 556 switch (action) { 557 case DISK_ZONE_OPEN: 558 case DISK_ZONE_CLOSE: 559 case DISK_ZONE_FINISH: 560 case DISK_ZONE_RWP: 561 break; 562 case DISK_ZONE_REPORT_ZONES: 563 zp_status = zonectl_print_rz(&zone_args.zone_params.report, 564 out_flags, first_pass); 565 if (zp_status == ZONE_PRINT_MORE_DATA) { 566 first_pass = 0; 567 bzero(entries, entry_alloc_size); 568 zone_args.zone_params.report.entries_filled = 0; 569 goto next_chunk; 570 } else if (zp_status == ZONE_PRINT_ERROR) 571 error = 1; 572 break; 573 case DISK_ZONE_GET_PARAMS: 574 zonectl_print_params(&zone_args.zone_params.disk_params); 575 break; 576 default: 577 warnx("Unknown action %d", action); 578 error = 1; 579 goto bailout; 580 break; /*NOTREACHED*/ 581 } 582 bailout: 583 free(entries); 584 585 if (fd != -1) 586 close(fd); 587 exit (error); 588 } 589