diff options
Diffstat (limited to 'sbin/camcontrol/persist.c')
-rw-r--r-- | sbin/camcontrol/persist.c | 966 |
1 files changed, 966 insertions, 0 deletions
diff --git a/sbin/camcontrol/persist.c b/sbin/camcontrol/persist.c new file mode 100644 index 0000000..bcc1073 --- /dev/null +++ b/sbin/camcontrol/persist.c @@ -0,0 +1,966 @@ +/*- + * Copyright (c) 2013 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) + */ +/* + * SCSI Persistent Reservation support for camcontrol(8). + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/ioctl.h> +#include <sys/stdint.h> +#include <sys/types.h> +#include <sys/endian.h> +#include <sys/sbuf.h> +#include <sys/queue.h> + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <fcntl.h> +#include <ctype.h> +#include <limits.h> +#include <err.h> + +#include <cam/cam.h> +#include <cam/cam_debug.h> +#include <cam/cam_ccb.h> +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_pass.h> +#include <cam/scsi/scsi_message.h> +#include <camlib.h> +#include "camcontrol.h" + +struct persist_transport_id { + struct scsi_transportid_header *hdr; + unsigned int alloc_len; + STAILQ_ENTRY(persist_transport_id) links; +}; + +/* + * Service Actions for PERSISTENT RESERVE IN. + */ +static struct scsi_nv persist_in_actions[] = { + { "read_keys", SPRI_RK }, + { "read_reservation", SPRI_RR }, + { "report_capabilities", SPRI_RC }, + { "read_full_status", SPRI_RS } +}; + +/* + * Service Actions for PERSISTENT RESERVE OUT. + */ +static struct scsi_nv persist_out_actions[] = { + { "register", SPRO_REGISTER }, + { "reserve", SPRO_RESERVE }, + { "release" , SPRO_RELEASE }, + { "clear", SPRO_CLEAR }, + { "preempt", SPRO_PREEMPT }, + { "preempt_abort", SPRO_PRE_ABO }, + { "register_ignore", SPRO_REG_IGNO }, + { "register_move", SPRO_REG_MOVE }, + { "replace_lost", SPRO_REPL_LOST_RES } +}; + +/* + * Known reservation scopes. As of SPC-4, only LU_SCOPE is used in the + * spec. The others are obsolete. + */ +static struct scsi_nv persist_scope_table[] = { + { "lun", SPR_LU_SCOPE }, + { "extent", SPR_EXTENT_SCOPE }, + { "element", SPR_ELEMENT_SCOPE } +}; + +/* + * Reservation types. The longer name for a given reservation type is + * listed first, so that it makes more sense when we print out the + * reservation type. We step through the table linearly when looking for + * the text name for a particular numeric reservation type value. + */ +static struct scsi_nv persist_type_table[] = { + { "read_shared", SPR_TYPE_RD_SHARED }, + { "write_exclusive", SPR_TYPE_WR_EX }, + { "wr_ex", SPR_TYPE_WR_EX }, + { "read_exclusive", SPR_TYPE_RD_EX }, + { "rd_ex", SPR_TYPE_RD_EX }, + { "exclusive_access", SPR_TYPE_EX_AC }, + { "ex_ac", SPR_TYPE_EX_AC }, + { "write_exclusive_reg_only", SPR_TYPE_WR_EX_RO }, + { "wr_ex_ro", SPR_TYPE_WR_EX_RO }, + { "exclusive_access_reg_only", SPR_TYPE_EX_AC_RO }, + { "ex_ac_ro", SPR_TYPE_EX_AC_RO }, + { "write_exclusive_all_regs", SPR_TYPE_WR_EX_AR }, + { "wr_ex_ar", SPR_TYPE_WR_EX_AR }, + { "exclusive_access_all_regs", SPR_TYPE_EX_AC_AR }, + { "ex_ac_ar", SPR_TYPE_EX_AC_AR } +}; + +/* + * Print out the standard scope/type field. + */ +static void +persist_print_scopetype(uint8_t scopetype) +{ + const char *tmpstr; + int num_entries; + + num_entries = sizeof(persist_scope_table) / + sizeof(persist_scope_table[0]); + tmpstr = scsi_nv_to_str(persist_scope_table, num_entries, + scopetype & SPR_SCOPE_MASK); + fprintf(stdout, "Scope: %s (%#x)\n", (tmpstr != NULL) ? tmpstr : + "Unknown", (scopetype & SPR_SCOPE_MASK) >> SPR_SCOPE_SHIFT); + + num_entries = sizeof(persist_type_table) / + sizeof(persist_type_table[0]); + tmpstr = scsi_nv_to_str(persist_type_table, num_entries, + scopetype & SPR_TYPE_MASK); + fprintf(stdout, "Type: %s (%#x)\n", (tmpstr != NULL) ? tmpstr : + "Unknown", scopetype & SPR_TYPE_MASK); +} + +static void +persist_print_transportid(uint8_t *buf, uint32_t len) +{ + struct sbuf *sb; + + sb = sbuf_new_auto(); + if (sb == NULL) + fprintf(stderr, "Unable to allocate sbuf\n"); + + scsi_transportid_sbuf(sb, (struct scsi_transportid_header *)buf, len); + + sbuf_finish(sb); + + fprintf(stdout, "%s\n", sbuf_data(sb)); + + sbuf_delete(sb); +} + +/* + * Print out a persistent reservation. This is used with the READ + * RESERVATION (0x01) service action of the PERSISTENT RESERVE IN command. + */ +static void +persist_print_res(struct scsi_per_res_in_header *hdr, uint32_t valid_len) +{ + uint32_t length; + struct scsi_per_res_in_rsrv *res; + + length = scsi_4btoul(hdr->length); + length = MIN(length, valid_len); + + res = (struct scsi_per_res_in_rsrv *)hdr; + + if (length < sizeof(res->data) - sizeof(res->data.extent_length)) { + if (length == 0) + fprintf(stdout, "No reservations.\n"); + else + warnx("unable to print reservation, only got %u " + "valid bytes", length); + return; + } + fprintf(stdout, "PRgeneration: %#x\n", + scsi_4btoul(res->header.generation)); + fprintf(stdout, "Reservation Key: %#jx\n", + (uintmax_t)scsi_8btou64(res->data.reservation)); + fprintf(stdout, "Scope address: %#x\n", + scsi_4btoul(res->data.scope_addr)); + + persist_print_scopetype(res->data.scopetype); + + fprintf(stdout, "Extent length: %u\n", + scsi_2btoul(res->data.extent_length)); +} + +/* + * Print out persistent reservation keys. This is used with the READ KEYS + * service action of the PERSISTENT RESERVE IN command. + */ +static void +persist_print_keys(struct scsi_per_res_in_header *hdr, uint32_t valid_len) +{ + uint32_t length, num_keys, i; + struct scsi_per_res_key *key; + + length = scsi_4btoul(hdr->length); + length = MIN(length, valid_len); + + num_keys = length / sizeof(*key); + + fprintf(stdout, "PRgeneration: %#x\n", scsi_4btoul(hdr->generation)); + fprintf(stdout, "%u key%s%s\n", num_keys, (num_keys == 1) ? "" : "s", + (num_keys == 0) ? "." : ":"); + + for (i = 0, key = (struct scsi_per_res_key *)&hdr[1]; i < num_keys; + i++, key++) { + fprintf(stdout, "%u: %#jx\n", i, + (uintmax_t)scsi_8btou64(key->key)); + } +} + +/* + * Print out persistent reservation capabilities. This is used with the + * REPORT CAPABILITIES service action of the PERSISTENT RESERVE IN command. + */ +static void +persist_print_cap(struct scsi_per_res_cap *cap, uint32_t valid_len) +{ + uint32_t length; + int check_type_mask = 0; + + length = scsi_2btoul(cap->length); + length = MIN(length, valid_len); + + if (length < __offsetof(struct scsi_per_res_cap, type_mask)) { + fprintf(stdout, "Insufficient data (%u bytes) to report " + "full capabilities\n", length); + return; + } + if (length >= __offsetof(struct scsi_per_res_cap, reserved)) + check_type_mask = 1; + + fprintf(stdout, "Replace Lost Reservation Capable (RLR_C): %d\n", + (cap->flags1 & SPRI_RLR_C) ? 1 : 0); + fprintf(stdout, "Compatible Reservation Handling (CRH): %d\n", + (cap->flags1 & SPRI_CRH) ? 1 : 0); + fprintf(stdout, "Specify Initiator Ports Capable (SIP_C): %d\n", + (cap->flags1 & SPRI_SIP_C) ? 1 : 0); + fprintf(stdout, "All Target Ports Capable (ATP_C): %d\n", + (cap->flags1 & SPRI_ATP_C) ? 1 : 0); + fprintf(stdout, "Persist Through Power Loss Capable (PTPL_C): %d\n", + (cap->flags1 & SPRI_PTPL_C) ? 1 : 0); + fprintf(stdout, "ALLOW COMMANDS field: (%#x)\n", + (cap->flags2 & SPRI_ALLOW_CMD_MASK) >> SPRI_ALLOW_CMD_SHIFT); + /* + * These cases are cut-and-pasted from SPC4r36l. There is no + * succinct way to describe these otherwise, and even with the + * verbose description, the user will probably have to refer to + * the spec to fully understand what is going on. + */ + switch (cap->flags2 & SPRI_ALLOW_CMD_MASK) { + case SPRI_ALLOW_1: + fprintf(stdout, +" The device server allows the TEST UNIT READY command through Write\n" +" Exclusive type reservations and Exclusive Access type reservations\n" +" and does not provide information about whether the following commands\n" +" are allowed through Write Exclusive type reservations:\n" +" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n" +" command, RECEIVE COPY RESULTS command, RECEIVE DIAGNOSTIC\n" +" RESULTS command, REPORT SUPPORTED OPERATION CODES command,\n" +" and REPORT SUPPORTED TASK MANAGEMENT FUNCTION command; and\n" +" b) the READ DEFECT DATA command (see SBC-3).\n"); + break; + case SPRI_ALLOW_2: + fprintf(stdout, +" The device server allows the TEST UNIT READY command through Write\n" +" Exclusive type reservations and Exclusive Access type reservations\n" +" and does not allow the following commands through Write Exclusive type\n" +" reservations:\n" +" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n" +" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n" +" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n" +" FUNCTION command; and\n" +" b) the READ DEFECT DATA command.\n" +" The device server does not allow the RECEIVE COPY RESULTS command\n" +" through Write Exclusive type reservations or Exclusive Access type\n" +" reservations.\n"); + break; + case SPRI_ALLOW_3: + fprintf(stdout, +" The device server allows the TEST UNIT READY command through Write\n" +" Exclusive type reservations and Exclusive Access type reservations\n" +" and allows the following commands through Write Exclusive type\n" +" reservations:\n" +" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n" +" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n" +" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n" +" FUNCTION command; and\n" +" b) the READ DEFECT DATA command.\n" +" The device server does not allow the RECEIVE COPY RESULTS command\n" +" through Write Exclusive type reservations or Exclusive Access type\n" +" reservations.\n"); + break; + case SPRI_ALLOW_4: + fprintf(stdout, +" The device server allows the TEST UNIT READY command and the RECEIVE\n" +" COPY RESULTS command through Write Exclusive type reservations and\n" +" Exclusive Access type reservations and allows the following commands\n" +" through Write Exclusive type reservations:\n" +" a) the MODE SENSE command, READ ATTRIBUTE command, READ BUFFER\n" +" command, RECEIVE DIAGNOSTIC RESULTS command, REPORT SUPPORTED\n" +" OPERATION CODES command, and REPORT SUPPORTED TASK MANAGEMENT\n" +" FUNCTION command; and\n" +" b) the READ DEFECT DATA command.\n"); + break; + case SPRI_ALLOW_NA: + fprintf(stdout, +" No information is provided about whether certain commands are allowed\n" +" through certain types of persistent reservations.\n"); + break; + default: + fprintf(stdout, +" Unknown ALLOW COMMANDS value %#x\n", + (cap->flags2 & SPRI_ALLOW_CMD_MASK) >> + SPRI_ALLOW_CMD_SHIFT); + break; + } + fprintf(stdout, "Persist Through Power Loss Activated (PTPL_A): %d\n", + (cap->flags2 & SPRI_PTPL_A) ? 1 : 0); + if ((check_type_mask != 0) + && (cap->flags2 & SPRI_TMV)) { + fprintf(stdout, "Supported Persistent Reservation Types:\n"); + fprintf(stdout, " Write Exclusive - All Registrants " + "(WR_EX_AR): %d\n", + (cap->type_mask[0] & SPRI_TM_WR_EX_AR)? 1 : 0); + fprintf(stdout, " Exclusive Access - Registrants Only " + "(EX_AC_RO): %d\n", + (cap->type_mask[0] & SPRI_TM_EX_AC_RO) ? 1 : 0); + fprintf(stdout, " Write Exclusive - Registrants Only " + "(WR_EX_RO): %d\n", + (cap->type_mask[0] & SPRI_TM_WR_EX_RO)? 1 : 0); + fprintf(stdout, " Exclusive Access (EX_AC): %d\n", + (cap->type_mask[0] & SPRI_TM_EX_AC) ? 1 : 0); + fprintf(stdout, " Write Exclusive (WR_EX): %d\n", + (cap->type_mask[0] & SPRI_TM_WR_EX) ? 1 : 0); + fprintf(stdout, " Exclusive Access - All Registrants " + "(EX_AC_AR): %d\n", + (cap->type_mask[1] & SPRI_TM_EX_AC_AR) ? 1 : 0); + } else { + fprintf(stdout, "Persistent Reservation Type Mask is NOT " + "valid\n"); + } + + +} + +static void +persist_print_full(struct scsi_per_res_in_header *hdr, uint32_t valid_len) +{ + uint32_t length, len_to_go = 0; + struct scsi_per_res_in_full_desc *desc; + uint8_t *cur_pos; + int i; + + length = scsi_4btoul(hdr->length); + length = MIN(length, valid_len); + + if (length < sizeof(*desc)) { + if (length == 0) + fprintf(stdout, "No reservations.\n"); + else + warnx("unable to print reservation, only got %u " + "valid bytes", length); + return; + } + + fprintf(stdout, "PRgeneration: %#x\n", scsi_4btoul(hdr->generation)); + cur_pos = (uint8_t *)&hdr[1]; + for (len_to_go = length, i = 0, + desc = (struct scsi_per_res_in_full_desc *)cur_pos; + len_to_go >= sizeof(*desc); + desc = (struct scsi_per_res_in_full_desc *)cur_pos, i++) { + uint32_t additional_length, cur_length; + + + fprintf(stdout, "Reservation Key: %#jx\n", + (uintmax_t)scsi_8btou64(desc->res_key.key)); + fprintf(stdout, "All Target Ports (ALL_TG_PT): %d\n", + (desc->flags & SPRI_FULL_ALL_TG_PT) ? 1 : 0); + fprintf(stdout, "Reservation Holder (R_HOLDER): %d\n", + (desc->flags & SPRI_FULL_R_HOLDER) ? 1 : 0); + + if (desc->flags & SPRI_FULL_R_HOLDER) + persist_print_scopetype(desc->scopetype); + + if ((desc->flags & SPRI_FULL_ALL_TG_PT) == 0) + fprintf(stdout, "Relative Target Port ID: %#x\n", + scsi_2btoul(desc->rel_trgt_port_id)); + + additional_length = scsi_4btoul(desc->additional_length); + + persist_print_transportid(desc->transport_id, + additional_length); + + cur_length = sizeof(*desc) + additional_length; + len_to_go -= cur_length; + cur_pos += cur_length; + } +} + +int +scsipersist(struct cam_device *device, int argc, char **argv, char *combinedopt, + int retry_count, int timeout, int verbosemode, int err_recover) +{ + union ccb *ccb = NULL; + int c, in = 0, out = 0; + int action = -1, num_ids = 0; + int error = 0; + uint32_t res_len = 0; + unsigned long rel_tgt_port = 0; + uint8_t *res_buf = NULL; + int scope = SPR_LU_SCOPE, res_type = 0, key_set = 0, sa_key_set = 0; + struct persist_transport_id *id, *id2; + STAILQ_HEAD(, persist_transport_id) transport_id_list; + uint64_t key = 0, sa_key = 0; + struct scsi_nv *table = NULL; + size_t table_size = 0, id_len = 0; + uint32_t valid_len = 0; + int all_tg_pt = 0, aptpl = 0, spec_i_pt = 0, unreg = 0,rel_port_set = 0; + + STAILQ_INIT(&transport_id_list); + + ccb = cam_getccb(device); + if (ccb == NULL) { + warnx("%s: error allocating CCB", __func__); + error = 1; + goto bailout; + } + + bzero(&(&ccb->ccb_h)[1], + sizeof(union ccb) - sizeof(struct ccb_hdr)); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'a': + all_tg_pt = 1; + break; + case 'I': { + int error_str_len = 128; + char error_str[error_str_len]; + char *id_str; + + id = malloc(sizeof(*id)); + if (id == NULL) { + warnx("%s: error allocating %zu bytes", + __func__, sizeof(*id)); + error = 1; + goto bailout; + } + bzero(id, sizeof(*id)); + + id_str = strdup(optarg); + if (id_str == NULL) { + warnx("%s: error duplicating string %s", + __func__, optarg); + free(id); + error = 1; + goto bailout; + } + error = scsi_parse_transportid(id_str, &id->hdr, + &id->alloc_len, error_str, error_str_len); + if (error != 0) { + warnx("%s", error_str); + error = 1; + free(id); + free(id_str); + goto bailout; + } + free(id_str); + + STAILQ_INSERT_TAIL(&transport_id_list, id, links); + num_ids++; + id_len += id->alloc_len; + break; + } + case 'k': + case 'K': { + char *endptr; + uint64_t tmpval; + + tmpval = strtoumax(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("%s: invalid key argument %s", __func__, + optarg); + error = 1; + goto bailout; + } + if (c == 'k') { + key = tmpval; + key_set = 1; + } else { + sa_key = tmpval; + sa_key_set = 1; + } + break; + } + case 'i': + case 'o': { + scsi_nv_status status; + int table_entry = 0; + + if (c == 'i') { + in = 1; + table = persist_in_actions; + table_size = sizeof(persist_in_actions) / + sizeof(persist_in_actions[0]); + } else { + out = 1; + table = persist_out_actions; + table_size = sizeof(persist_out_actions) / + sizeof(persist_out_actions[0]); + } + + if ((in + out) > 1) { + warnx("%s: only one in (-i) or out (-o) " + "action is allowed", __func__); + error = 1; + goto bailout; + } + + status = scsi_get_nv(table, table_size, optarg, + &table_entry,SCSI_NV_FLAG_IG_CASE); + if (status == SCSI_NV_FOUND) + action = table[table_entry].value; + else { + warnx("%s: %s %s option %s", __func__, + (status == SCSI_NV_AMBIGUOUS) ? + "ambiguous" : "invalid", in ? "in" : + "out", optarg); + error = 1; + goto bailout; + } + break; + } + case 'p': + aptpl = 1; + break; + case 'R': { + char *endptr; + + rel_tgt_port = strtoul(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("%s: invalid relative target port %s", + __func__, optarg); + error = 1; + goto bailout; + } + rel_port_set = 1; + break; + } + case 's': { + size_t scope_size; + struct scsi_nv *scope_table = NULL; + scsi_nv_status status; + int table_entry = 0; + char *endptr; + + /* + * First check to see if the user gave us a numeric + * argument. If so, we'll try using it. + */ + if (isdigit(optarg[0])) { + scope = strtol(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("%s: invalid scope %s", + __func__, optarg); + error = 1; + goto bailout; + } + scope = (scope << SPR_SCOPE_SHIFT) & + SPR_SCOPE_MASK; + break; + } + + scope_size = sizeof(persist_scope_table) / + sizeof(persist_scope_table[0]); + scope_table = persist_scope_table; + status = scsi_get_nv(scope_table, scope_size, optarg, + &table_entry,SCSI_NV_FLAG_IG_CASE); + if (status == SCSI_NV_FOUND) + scope = scope_table[table_entry].value; + else { + warnx("%s: %s scope %s", __func__, + (status == SCSI_NV_AMBIGUOUS) ? + "ambiguous" : "invalid", optarg); + error = 1; + goto bailout; + } + break; + } + case 'S': + spec_i_pt = 1; + break; + case 'T': { + size_t res_type_size; + struct scsi_nv *rtype_table = NULL; + scsi_nv_status status; + char *endptr; + int table_entry = 0; + + /* + * First check to see if the user gave us a numeric + * argument. If so, we'll try using it. + */ + if (isdigit(optarg[0])) { + res_type = strtol(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("%s: invalid reservation type %s", + __func__, optarg); + error = 1; + goto bailout; + } + break; + } + + res_type_size = sizeof(persist_type_table) / + sizeof(persist_type_table[0]); + rtype_table = persist_type_table; + status = scsi_get_nv(rtype_table, res_type_size, + optarg, &table_entry, + SCSI_NV_FLAG_IG_CASE); + if (status == SCSI_NV_FOUND) + res_type = rtype_table[table_entry].value; + else { + warnx("%s: %s reservation type %s", __func__, + (status == SCSI_NV_AMBIGUOUS) ? + "ambiguous" : "invalid", optarg); + error = 1; + goto bailout; + } + break; + } + case 'U': + unreg = 1; + break; + default: + break; + } + } + + if ((in + out) != 1) { + warnx("%s: you must specify one of -i or -o", __func__); + error = 1; + goto bailout; + } + + /* + * Note that we don't really try to figure out whether the user + * needs to specify one or both keys. There are a number of + * scenarios, and sometimes 0 is a valid and desired value. + */ + if (in != 0) { + switch (action) { + case SPRI_RK: + case SPRI_RR: + case SPRI_RS: + /* + * Allocate the maximum length possible for these + * service actions. According to the spec, the + * target is supposed to return the available + * length in the header, regardless of the + * allocation length. In practice, though, with + * the READ FULL STATUS (SPRI_RS) service action, + * some Seagate drives (in particular a + * Constellation ES, <SEAGATE ST32000444SS 0006>) + * don't return the available length if you only + * allocate the length of the header. So just + * allocate the maximum here so we don't miss + * anything. + */ + res_len = SPRI_MAX_LEN; + break; + case SPRI_RC: + res_len = sizeof(struct scsi_per_res_cap); + break; + default: + /* In theory we should catch this above */ + warnx("%s: invalid action %d", __func__, action); + error = 1; + goto bailout; + break; + } + } else { + + /* + * XXX KDM need to add length for transport IDs for the + * register and move service action and the register + * service action with the SPEC_I_PT bit set. + */ + if (action == SPRO_REG_MOVE) { + if (num_ids != 1) { + warnx("%s: register and move requires a " + "single transport ID (-I)", __func__); + error = 1; + goto bailout; + } + if (rel_port_set == 0) { + warnx("%s: register and move requires a " + "relative target port (-R)", __func__); + error = 1; + goto bailout; + } + res_len = sizeof(struct scsi_per_res_reg_move) + id_len; + } else { + res_len = sizeof(struct scsi_per_res_out_parms); + if ((action == SPRO_REGISTER) + && (num_ids != 0)) { + /* + * If the user specifies any IDs with the + * register service action, turn on the + * spec_i_pt bit. + */ + spec_i_pt = 1; + res_len += id_len; + res_len += + sizeof(struct scsi_per_res_out_trans_ids); + } + } + } +retry: + if (res_buf != NULL) { + free(res_buf); + res_buf = NULL; + } + res_buf = malloc(res_len); + if (res_buf == NULL) { + warn("%s: error allocating %d bytes", __func__, res_len); + error = 1; + goto bailout; + } + bzero(res_buf, res_len); + + if (in != 0) { + scsi_persistent_reserve_in(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*service_action*/ action, + /*data_ptr*/ res_buf, + /*dxfer_len*/ res_len, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ timeout ? timeout :5000); + + } else { + switch (action) { + case SPRO_REGISTER: + if (spec_i_pt != 0) { + struct scsi_per_res_out_trans_ids *id_hdr; + uint8_t *bufptr; + + bufptr = res_buf + + sizeof(struct scsi_per_res_out_parms) + + sizeof(struct scsi_per_res_out_trans_ids); + STAILQ_FOREACH(id, &transport_id_list, links) { + bcopy(id->hdr, bufptr, id->alloc_len); + bufptr += id->alloc_len; + } + id_hdr = (struct scsi_per_res_out_trans_ids *) + (res_buf + + sizeof(struct scsi_per_res_out_parms)); + scsi_ulto4b(id_len, id_hdr->additional_length); + } + case SPRO_REG_IGNO: + case SPRO_PREEMPT: + case SPRO_PRE_ABO: + case SPRO_RESERVE: + case SPRO_RELEASE: + case SPRO_CLEAR: + case SPRO_REPL_LOST_RES: { + struct scsi_per_res_out_parms *parms; + + parms = (struct scsi_per_res_out_parms *)res_buf; + + scsi_u64to8b(key, parms->res_key.key); + scsi_u64to8b(sa_key, parms->serv_act_res_key); + if (spec_i_pt != 0) + parms->flags |= SPR_SPEC_I_PT; + if (all_tg_pt != 0) + parms->flags |= SPR_ALL_TG_PT; + if (aptpl != 0) + parms->flags |= SPR_APTPL; + break; + } + case SPRO_REG_MOVE: { + struct scsi_per_res_reg_move *reg_move; + uint8_t *bufptr; + + reg_move = (struct scsi_per_res_reg_move *)res_buf; + + scsi_u64to8b(key, reg_move->res_key.key); + scsi_u64to8b(sa_key, reg_move->serv_act_res_key); + if (unreg != 0) + reg_move->flags |= SPR_REG_MOVE_UNREG; + if (aptpl != 0) + reg_move->flags |= SPR_REG_MOVE_APTPL; + scsi_ulto2b(rel_tgt_port, reg_move->rel_trgt_port_id); + id = STAILQ_FIRST(&transport_id_list); + /* + * This shouldn't happen, since we already checked + * the number of IDs above. + */ + if (id == NULL) { + warnx("%s: No transport IDs found!", __func__); + error = 1; + goto bailout; + } + bufptr = (uint8_t *)®_move[1]; + bcopy(id->hdr, bufptr, id->alloc_len); + scsi_ulto4b(id->alloc_len, + reg_move->transport_id_length); + break; + } + default: + break; + } + scsi_persistent_reserve_out(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*service_action*/ action, + /*scope*/ scope, + /*res_type*/ res_type, + /*data_ptr*/ res_buf, + /*dxfer_len*/ res_len, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ timeout ?timeout :5000); + } + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (err_recover != 0) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + warn("error sending PERSISTENT RESERVE %s", (in != 0) ? + "IN" : "OUT"); + + if (verbosemode != 0) { + cam_error_print(device, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + } + + error = 1; + goto bailout; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if (verbosemode != 0) { + cam_error_print(device, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + } + error = 1; + goto bailout; + } + + if (in == 0) + goto bailout; + + valid_len = res_len - ccb->csio.resid; + + switch (action) { + case SPRI_RK: + case SPRI_RR: + case SPRI_RS: { + struct scsi_per_res_in_header *hdr; + uint32_t hdr_len; + + if (valid_len < sizeof(*hdr)) { + warnx("%s: only got %d valid bytes, need %zd", + __func__, valid_len, sizeof(*hdr)); + error = 1; + goto bailout; + } + hdr = (struct scsi_per_res_in_header *)res_buf; + hdr_len = scsi_4btoul(hdr->length); + + if (hdr_len > (res_len - sizeof(*hdr))) { + res_len = hdr_len + sizeof(*hdr); + goto retry; + } + + if (action == SPRI_RK) { + persist_print_keys(hdr, valid_len); + } else if (action == SPRI_RR) { + persist_print_res(hdr, valid_len); + } else { + persist_print_full(hdr, valid_len); + } + break; + } + case SPRI_RC: { + struct scsi_per_res_cap *cap; + uint32_t cap_len; + + if (valid_len < sizeof(*cap)) { + warnx("%s: only got %u valid bytes, need %zd", + __func__, valid_len, sizeof(*cap)); + error = 1; + goto bailout; + } + cap = (struct scsi_per_res_cap *)res_buf; + cap_len = scsi_2btoul(cap->length); + if (cap_len != sizeof(*cap)) { + /* + * We should be able to deal with this, + * it's just more trouble. + */ + warnx("%s: reported size %u is different " + "than expected size %zd", __func__, + cap_len, sizeof(*cap)); + } + + /* + * If there is more data available, grab it all, + * even though we don't really know what to do with + * the extra data since it obviously wasn't in the + * spec when this code was written. + */ + if (cap_len > res_len) { + res_len = cap_len; + goto retry; + } + persist_print_cap(cap, valid_len); + break; + } + default: + break; + } + +bailout: + free(res_buf); + + if (ccb != NULL) + cam_freeccb(ccb); + + STAILQ_FOREACH_SAFE(id, &transport_id_list, links, id2) { + STAILQ_REMOVE(&transport_id_list, id, persist_transport_id, + links); + free(id); + } + return (error); +} |