/*- * Copyright (c) 2015, 2016 Spectra Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * Authors: Ken Merry (Spectra Logic Corporation) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct scsi_nv zone_cmd_map[] = { { "rz", DISK_ZONE_REPORT_ZONES }, { "reportzones", DISK_ZONE_REPORT_ZONES }, { "close", DISK_ZONE_CLOSE }, { "finish", DISK_ZONE_FINISH }, { "open", DISK_ZONE_OPEN }, { "rwp", DISK_ZONE_RWP }, { "params", DISK_ZONE_GET_PARAMS } }; static struct scsi_nv zone_rep_opts[] = { { "all", DISK_ZONE_REP_ALL }, { "empty", DISK_ZONE_REP_EMPTY }, { "imp_open", DISK_ZONE_REP_IMP_OPEN }, { "exp_open", DISK_ZONE_REP_EXP_OPEN }, { "closed", DISK_ZONE_REP_CLOSED }, { "full", DISK_ZONE_REP_FULL }, { "readonly", DISK_ZONE_REP_READONLY }, { "ro", DISK_ZONE_REP_READONLY }, { "offline", DISK_ZONE_REP_OFFLINE }, { "reset", DISK_ZONE_REP_RWP }, { "rwp", DISK_ZONE_REP_RWP }, { "nonseq", DISK_ZONE_REP_NON_SEQ }, { "nonwp", DISK_ZONE_REP_NON_WP } }; typedef enum { ZONE_OF_NORMAL = 0x00, ZONE_OF_SUMMARY = 0x01, ZONE_OF_SCRIPT = 0x02 } zone_output_flags; static struct scsi_nv zone_print_opts[] = { { "normal", ZONE_OF_NORMAL }, { "summary", ZONE_OF_SUMMARY }, { "script", ZONE_OF_SCRIPT } }; static struct scsi_nv zone_cmd_desc_table[] = { {"Report Zones", DISK_ZONE_RZ_SUP }, {"Open", DISK_ZONE_OPEN_SUP }, {"Close", DISK_ZONE_CLOSE_SUP }, {"Finish", DISK_ZONE_FINISH_SUP }, {"Reset Write Pointer", DISK_ZONE_RWP_SUP } }; typedef enum { ZONE_PRINT_OK, ZONE_PRINT_MORE_DATA, ZONE_PRINT_ERROR } zone_print_status; typedef enum { ZONE_FW_START, ZONE_FW_LEN, ZONE_FW_WP, ZONE_FW_TYPE, ZONE_FW_COND, ZONE_FW_SEQ, ZONE_FW_RESET, ZONE_NUM_FIELDS } zone_field_widths; static void usage(int error); static void zonectl_print_params(struct disk_zone_disk_params *params); zone_print_status zonectl_print_rz(struct disk_zone_report *report, zone_output_flags out_flags, int first_pass); static void usage(int error) { fprintf(error ? stderr : stdout, "usage: zonectl <-d dev> <-c cmd> [-a][-o rep_opts] [-l lba][-P print_opts]\n" ); } static void zonectl_print_params(struct disk_zone_disk_params *params) { unsigned int i; int first; printf("Zone Mode: "); switch (params->zone_mode) { case DISK_ZONE_MODE_NONE: printf("None"); break; case DISK_ZONE_MODE_HOST_AWARE: printf("Host Aware"); break; case DISK_ZONE_MODE_DRIVE_MANAGED: printf("Drive Managed"); break; case DISK_ZONE_MODE_HOST_MANAGED: printf("Host Managed"); break; default: printf("Unknown mode %#x", params->zone_mode); break; } printf("\n"); first = 1; printf("Command support: "); for (i = 0; i < sizeof(zone_cmd_desc_table) / sizeof(zone_cmd_desc_table[0]); i++) { if (params->flags & zone_cmd_desc_table[i].value) { if (first == 0) printf(", "); else first = 0; printf("%s", zone_cmd_desc_table[i].name); } } if (first == 1) printf("None"); printf("\n"); printf("Unrestricted Read in Sequential Write Required Zone " "(URSWRZ): %s\n", (params->flags & DISK_ZONE_DISK_URSWRZ) ? "Yes" : "No"); printf("Optimal Number of Open Sequential Write Preferred Zones: "); if (params->flags & DISK_ZONE_OPT_SEQ_SET) if (params->optimal_seq_zones == SVPD_ZBDC_OPT_SEQ_NR) printf("Not Reported"); else printf("%ju", (uintmax_t)params->optimal_seq_zones); else printf("Not Set"); printf("\n"); printf("Optimal Number of Non-Sequentially Written Sequential Write " "Preferred Zones: "); if (params->flags & DISK_ZONE_OPT_NONSEQ_SET) if (params->optimal_nonseq_zones == SVPD_ZBDC_OPT_NONSEQ_NR) printf("Not Reported"); else printf("%ju",(uintmax_t)params->optimal_nonseq_zones); else printf("Not Set"); printf("\n"); printf("Maximum Number of Open Sequential Write Required Zones: "); if (params->flags & DISK_ZONE_MAX_SEQ_SET) if (params->max_seq_zones == SVPD_ZBDC_MAX_SEQ_UNLIMITED) printf("Unlimited"); else printf("%ju", (uintmax_t)params->max_seq_zones); else printf("Not Set"); printf("\n"); } zone_print_status zonectl_print_rz(struct disk_zone_report *report, zone_output_flags out_flags, int first_pass) { zone_print_status status = ZONE_PRINT_OK; struct disk_zone_rep_header *header = &report->header; int field_widths[ZONE_NUM_FIELDS]; struct disk_zone_rep_entry *entry; uint64_t next_lba = 0; char tmpstr[80]; char word_sep; uint32_t i; field_widths[ZONE_FW_START] = 11; field_widths[ZONE_FW_LEN] = 6; field_widths[ZONE_FW_WP] = 11; field_widths[ZONE_FW_TYPE] = 13; field_widths[ZONE_FW_COND] = 13; field_widths[ZONE_FW_SEQ] = 14; field_widths[ZONE_FW_RESET] = 16; if ((report->entries_available - report->entries_filled) > 0) status = ZONE_PRINT_MORE_DATA; if (out_flags == ZONE_OF_SCRIPT) word_sep = '_'; else word_sep = ' '; if ((out_flags != ZONE_OF_SCRIPT) && (first_pass != 0)) { printf("%u zones, Maximum LBA %#jx (%ju)\n", report->entries_available, (uintmax_t)header->maximum_lba, (uintmax_t)header->maximum_lba); switch (header->same) { case DISK_ZONE_SAME_ALL_DIFFERENT: printf("Zone lengths and types may vary\n"); break; case DISK_ZONE_SAME_ALL_SAME: printf("Zone lengths and types are all the same\n"); break; case DISK_ZONE_SAME_LAST_DIFFERENT: printf("Zone types are the same, last zone length " "differs\n"); break; case DISK_ZONE_SAME_TYPES_DIFFERENT: printf("Zone lengths are the same, types vary\n"); break; default: printf("Unknown SAME field value %#x\n",header->same); break; } } if (out_flags == ZONE_OF_SUMMARY) { status = ZONE_PRINT_OK; goto bailout; } if ((out_flags == ZONE_OF_NORMAL) && (first_pass != 0)) { printf("%*s %*s %*s %*s %*s %*s %*s\n", field_widths[ZONE_FW_START], "Start LBA", field_widths[ZONE_FW_LEN], "Length", field_widths[ZONE_FW_WP], "WP LBA", field_widths[ZONE_FW_TYPE], "Zone Type", field_widths[ZONE_FW_COND], "Condition", field_widths[ZONE_FW_SEQ], "Sequential", field_widths[ZONE_FW_RESET], "Reset"); } for (i = 0; i < report->entries_filled; i++) { entry = &report->entries[i]; printf("%#*jx, %*ju, %#*jx, ", field_widths[ZONE_FW_START], (uintmax_t)entry->zone_start_lba, field_widths[ZONE_FW_LEN], (uintmax_t)entry->zone_length, field_widths[ZONE_FW_WP], (uintmax_t)entry->write_pointer_lba); switch (entry->zone_type) { case DISK_ZONE_TYPE_CONVENTIONAL: snprintf(tmpstr, sizeof(tmpstr), "Conventional"); break; case DISK_ZONE_TYPE_SEQ_PREFERRED: case DISK_ZONE_TYPE_SEQ_REQUIRED: snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s", word_sep, (entry->zone_type == DISK_ZONE_TYPE_SEQ_PREFERRED) ? "Preferred" : "Required"); break; default: snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x", word_sep, word_sep, entry->zone_type); break; } printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr); switch (entry->zone_condition) { case DISK_ZONE_COND_NOT_WP: snprintf(tmpstr, sizeof(tmpstr), "NWP"); break; case DISK_ZONE_COND_EMPTY: snprintf(tmpstr, sizeof(tmpstr), "Empty"); break; case DISK_ZONE_COND_IMPLICIT_OPEN: snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen", word_sep); break; case DISK_ZONE_COND_EXPLICIT_OPEN: snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen", word_sep); break; case DISK_ZONE_COND_CLOSED: snprintf(tmpstr, sizeof(tmpstr), "Closed"); break; case DISK_ZONE_COND_READONLY: snprintf(tmpstr, sizeof(tmpstr), "Readonly"); break; case DISK_ZONE_COND_FULL: snprintf(tmpstr, sizeof(tmpstr), "Full"); break; case DISK_ZONE_COND_OFFLINE: snprintf(tmpstr, sizeof(tmpstr), "Offline"); break; default: snprintf(tmpstr, sizeof(tmpstr), "%#x", entry->zone_condition); break; } printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr); if (entry->zone_flags & DISK_ZONE_FLAG_NON_SEQ) snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential", word_sep); else snprintf(tmpstr, sizeof(tmpstr), "Sequential"); printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr); if (entry->zone_flags & DISK_ZONE_FLAG_RESET) snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded", word_sep); else snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded", word_sep, word_sep); printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr); next_lba = entry->zone_start_lba + entry->zone_length; } bailout: report->starting_id = next_lba; return (status); } int main(int argc, char **argv) { int c; int all_zones = 0; int error = 0; int action = -1, rep_option = -1; int fd = -1; uint64_t lba = 0; zone_output_flags out_flags = ZONE_OF_NORMAL; char *filename = NULL; struct disk_zone_args zone_args; struct disk_zone_rep_entry *entries = NULL; uint32_t num_entries = 16384; zone_print_status zp_status; int first_pass = 1; size_t entry_alloc_size; int open_flags = O_RDONLY; while ((c = getopt(argc, argv, "ac:d:hl:o:P:?")) != -1) { switch (c) { case 'a': all_zones = 1; break; case 'c': { scsi_nv_status status; int entry_num; status = scsi_get_nv(zone_cmd_map, (sizeof(zone_cmd_map) / sizeof(zone_cmd_map[0])), optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); if (status == SCSI_NV_FOUND) action = zone_cmd_map[entry_num].value; else { warnx("%s: %s: %s option %s", __func__, (status == SCSI_NV_AMBIGUOUS) ? "ambiguous" : "invalid", "zone command", optarg); error = 1; goto bailout; } break; } case 'd': filename = strdup(optarg); if (filename == NULL) err(1, "Unable to allocate memory for " "filename"); break; case 'l': { char *endptr; lba = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { warnx("%s: invalid lba argument %s", __func__, optarg); error = 1; goto bailout; } break; } case 'o': { scsi_nv_status status; int entry_num; status = scsi_get_nv(zone_rep_opts, (sizeof(zone_rep_opts) / sizeof(zone_rep_opts[0])), optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); if (status == SCSI_NV_FOUND) rep_option = zone_rep_opts[entry_num].value; else { warnx("%s: %s: %s option %s", __func__, (status == SCSI_NV_AMBIGUOUS) ? "ambiguous" : "invalid", "report zones", optarg); error = 1; goto bailout; } break; } case 'P': { scsi_nv_status status; int entry_num; status = scsi_get_nv(zone_print_opts, (sizeof(zone_print_opts) / sizeof(zone_print_opts[0])), optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); if (status == SCSI_NV_FOUND) out_flags = zone_print_opts[entry_num].value; else { warnx("%s: %s: %s option %s", __func__, (status == SCSI_NV_AMBIGUOUS) ? "ambiguous" : "invalid", "print", optarg); error = 1; goto bailout; } break; } default: error = 1; case 'h': /*FALLTHROUGH*/ usage(error); goto bailout; break; /*NOTREACHED*/ } } if (filename == NULL) { warnx("You must specify a device with -d"); error = 1; } if (action == -1) { warnx("You must specify an action with -c"); error = 1; } if (error != 0) { usage(error); goto bailout; } bzero(&zone_args, sizeof(zone_args)); zone_args.zone_cmd = action; switch (action) { case DISK_ZONE_OPEN: case DISK_ZONE_CLOSE: case DISK_ZONE_FINISH: case DISK_ZONE_RWP: open_flags = O_RDWR; zone_args.zone_params.rwp.id = lba; if (all_zones != 0) zone_args.zone_params.rwp.flags |= DISK_ZONE_RWP_FLAG_ALL; break; case DISK_ZONE_REPORT_ZONES: { entry_alloc_size = num_entries * sizeof(struct disk_zone_rep_entry); entries = malloc(entry_alloc_size); if (entries == NULL) { warn("Could not allocate %zu bytes", entry_alloc_size); error = 1; goto bailout; } zone_args.zone_params.report.entries_allocated = num_entries; zone_args.zone_params.report.entries = entries; zone_args.zone_params.report.starting_id = lba; if (rep_option != -1) zone_args.zone_params.report.rep_options = rep_option; break; } case DISK_ZONE_GET_PARAMS: break; default: warnx("Unknown action %d", action); error = 1; goto bailout; break; /*NOTREACHED*/ } fd = open(filename, open_flags); if (fd == -1) { warn("Unable to open device %s", filename); error = 1; goto bailout; } next_chunk: error = ioctl(fd, DIOCZONECMD, &zone_args); if (error == -1) { warn("DIOCZONECMD ioctl failed"); error = 1; goto bailout; } switch (action) { case DISK_ZONE_OPEN: case DISK_ZONE_CLOSE: case DISK_ZONE_FINISH: case DISK_ZONE_RWP: break; case DISK_ZONE_REPORT_ZONES: zp_status = zonectl_print_rz(&zone_args.zone_params.report, out_flags, first_pass); if (zp_status == ZONE_PRINT_MORE_DATA) { first_pass = 0; bzero(entries, entry_alloc_size); zone_args.zone_params.report.entries_filled = 0; goto next_chunk; } else if (zp_status == ZONE_PRINT_ERROR) error = 1; break; case DISK_ZONE_GET_PARAMS: zonectl_print_params(&zone_args.zone_params.disk_params); break; default: warnx("Unknown action %d", action); error = 1; goto bailout; break; /*NOTREACHED*/ } bailout: free(entries); if (fd != -1) close(fd); exit (error); }