1 /*- 2 * Copyright (c) 2014 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 * SCSI Read and Write Attribute support for camcontrol(8). 34 */ 35 36 #include <sys/cdefs.h> 37 #include <sys/ioctl.h> 38 #include <sys/stdint.h> 39 #include <sys/types.h> 40 #include <sys/endian.h> 41 #include <sys/sbuf.h> 42 #include <sys/queue.h> 43 #include <sys/chio.h> 44 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <inttypes.h> 48 #include <unistd.h> 49 #include <string.h> 50 #include <strings.h> 51 #include <fcntl.h> 52 #include <ctype.h> 53 #include <limits.h> 54 #include <err.h> 55 #include <locale.h> 56 57 #include <cam/cam.h> 58 #include <cam/cam_debug.h> 59 #include <cam/cam_ccb.h> 60 #include <cam/scsi/scsi_all.h> 61 #include <cam/scsi/scsi_pass.h> 62 #include <cam/scsi/scsi_ch.h> 63 #include <cam/scsi/scsi_message.h> 64 #include <camlib.h> 65 #include "camcontrol.h" 66 67 #if 0 68 struct scsi_attr_desc { 69 int attr_id; 70 71 STAILQ_ENTRY(scsi_attr_desc) links; 72 }; 73 #endif 74 75 static struct scsi_nv elem_type_map[] = { 76 { "all", ELEMENT_TYPE_ALL }, 77 { "picker", ELEMENT_TYPE_MT }, 78 { "slot", ELEMENT_TYPE_ST }, 79 { "portal", ELEMENT_TYPE_IE }, 80 { "drive", ELEMENT_TYPE_DT }, 81 }; 82 83 static struct scsi_nv sa_map[] = { 84 { "attr_values", SRA_SA_ATTR_VALUES }, 85 { "attr_list", SRA_SA_ATTR_LIST }, 86 { "lv_list", SRA_SA_LOG_VOL_LIST }, 87 { "part_list", SRA_SA_PART_LIST }, 88 { "supp_attr", SRA_SA_SUPPORTED_ATTRS } 89 }; 90 91 static struct scsi_nv output_format_map[] = { 92 { "text_esc", SCSI_ATTR_OUTPUT_TEXT_ESC }, 93 { "text_raw", SCSI_ATTR_OUTPUT_TEXT_RAW }, 94 { "nonascii_esc", SCSI_ATTR_OUTPUT_NONASCII_ESC }, 95 { "nonascii_trim", SCSI_ATTR_OUTPUT_NONASCII_TRIM }, 96 { "nonascii_raw", SCSI_ATTR_OUTPUT_NONASCII_RAW }, 97 { "field_all", SCSI_ATTR_OUTPUT_FIELD_ALL }, 98 { "field_none", SCSI_ATTR_OUTPUT_FIELD_NONE }, 99 { "field_desc", SCSI_ATTR_OUTPUT_FIELD_DESC }, 100 { "field_num", SCSI_ATTR_OUTPUT_FIELD_NUM }, 101 { "field_size", SCSI_ATTR_OUTPUT_FIELD_SIZE }, 102 { "field_rw", SCSI_ATTR_OUTPUT_FIELD_RW }, 103 }; 104 105 int 106 scsiattrib(struct cam_device *device, int argc, char **argv, char *combinedopt, 107 int task_attr, int retry_count, int timeout, int verbosemode, 108 int err_recover) 109 { 110 union ccb *ccb = NULL; 111 int attr_num = -1; 112 #if 0 113 int num_attrs = 0; 114 #endif 115 int start_attr = 0; 116 int cached_attr = 0; 117 int read_service_action = -1; 118 int read_attr = 0, write_attr = 0; 119 int element_address = 0; 120 int element_type = ELEMENT_TYPE_ALL; 121 int partition = 0; 122 int logical_volume = 0; 123 char *endptr; 124 uint8_t *data_buf = NULL; 125 uint32_t dxfer_len = UINT16_MAX - 1; 126 uint32_t valid_len; 127 uint32_t output_format; 128 STAILQ_HEAD(, scsi_attr_desc) write_attr_list; 129 int error = 0; 130 int c; 131 132 ccb = cam_getccb(device); 133 if (ccb == NULL) { 134 warnx("%s: error allocating CCB", __func__); 135 error = 1; 136 goto bailout; 137 } 138 139 STAILQ_INIT(&write_attr_list); 140 141 /* 142 * By default, when displaying attribute values, we trim out 143 * non-ASCII characters in ASCII fields. We display all fields 144 * (description, attribute number, attribute size, and readonly 145 * status). We default to displaying raw text. 146 * 147 * XXX KDM need to port this to stable/10 and newer FreeBSD 148 * versions that have iconv built in and can convert codesets. 149 */ 150 output_format = SCSI_ATTR_OUTPUT_NONASCII_TRIM | 151 SCSI_ATTR_OUTPUT_FIELD_ALL | 152 SCSI_ATTR_OUTPUT_TEXT_RAW; 153 154 data_buf = malloc(dxfer_len); 155 if (data_buf == NULL) { 156 warn("%s: error allocating %u bytes", __func__, dxfer_len); 157 error = 1; 158 goto bailout; 159 } 160 161 while ((c = getopt(argc, argv, combinedopt)) != -1) { 162 switch (c) { 163 case 'a': 164 attr_num = strtol(optarg, &endptr, 0); 165 if (*endptr != '\0') { 166 warnx("%s: invalid attribute number %s", 167 __func__, optarg); 168 error = 1; 169 goto bailout; 170 } 171 start_attr = attr_num; 172 break; 173 case 'c': 174 cached_attr = 1; 175 break; 176 case 'e': 177 element_address = strtol(optarg, &endptr, 0); 178 if (*endptr != '\0') { 179 warnx("%s: invalid element address %s", 180 __func__, optarg); 181 error = 1; 182 goto bailout; 183 } 184 break; 185 case 'F': { 186 scsi_nv_status status; 187 scsi_attrib_output_flags new_outflags; 188 int entry_num = 0; 189 char *tmpstr; 190 191 if (isdigit(optarg[0])) { 192 output_format = strtoul(optarg, &endptr, 0); 193 if (*endptr != '\0') { 194 warnx("%s: invalid numeric output " 195 "format argument %s", __func__, 196 optarg); 197 error = 1; 198 goto bailout; 199 } 200 break; 201 } 202 new_outflags = SCSI_ATTR_OUTPUT_NONE; 203 204 while ((tmpstr = strsep(&optarg, ",")) != NULL) { 205 status = scsi_get_nv(output_format_map, 206 sizeof(output_format_map) / 207 sizeof(output_format_map[0]), tmpstr, 208 &entry_num, SCSI_NV_FLAG_IG_CASE); 209 210 if (status == SCSI_NV_FOUND) 211 new_outflags |= 212 output_format_map[entry_num].value; 213 else { 214 warnx("%s: %s format option %s", 215 __func__, 216 (status == SCSI_NV_AMBIGUOUS) ? 217 "ambiguous" : "invalid", tmpstr); 218 error = 1; 219 goto bailout; 220 } 221 } 222 output_format = new_outflags; 223 break; 224 } 225 case 'p': 226 partition = strtol(optarg, &endptr, 0); 227 if (*endptr != '\0') { 228 warnx("%s: invalid partition number %s", 229 __func__, optarg); 230 error = 1; 231 goto bailout; 232 } 233 break; 234 case 'r': { 235 scsi_nv_status status; 236 int entry_num = 0; 237 238 status = scsi_get_nv(sa_map, sizeof(sa_map) / 239 sizeof(sa_map[0]), optarg, &entry_num, 240 SCSI_NV_FLAG_IG_CASE); 241 if (status == SCSI_NV_FOUND) 242 read_service_action = sa_map[entry_num].value; 243 else { 244 warnx("%s: %s %s option %s", __func__, 245 (status == SCSI_NV_AMBIGUOUS) ? 246 "ambiguous" : "invalid", "service action", 247 optarg); 248 error = 1; 249 goto bailout; 250 } 251 read_attr = 1; 252 break; 253 } 254 case 's': 255 start_attr = strtol(optarg, &endptr, 0); 256 if (*endptr != '\0') { 257 warnx("%s: invalid starting attr argument %s", 258 __func__, optarg); 259 error = 1; 260 goto bailout; 261 } 262 break; 263 case 'T': { 264 scsi_nv_status status; 265 int entry_num = 0; 266 267 status = scsi_get_nv(elem_type_map, 268 sizeof(elem_type_map) / sizeof(elem_type_map[0]), 269 optarg, &entry_num, SCSI_NV_FLAG_IG_CASE); 270 if (status == SCSI_NV_FOUND) 271 element_type = elem_type_map[entry_num].value; 272 else { 273 warnx("%s: %s %s option %s", __func__, 274 (status == SCSI_NV_AMBIGUOUS) ? 275 "ambiguous" : "invalid", "element type", 276 optarg); 277 error = 1; 278 goto bailout; 279 } 280 break; 281 } 282 case 'w': 283 warnx("%s: writing attributes is not implemented yet", 284 __func__); 285 error = 1; 286 goto bailout; 287 break; 288 case 'V': 289 logical_volume = strtol(optarg, &endptr, 0); 290 291 if (*endptr != '\0') { 292 warnx("%s: invalid logical volume argument %s", 293 __func__, optarg); 294 error = 1; 295 goto bailout; 296 } 297 break; 298 default: 299 break; 300 } 301 } 302 303 /* 304 * Default to reading attributes 305 */ 306 if (((read_attr == 0) && (write_attr == 0)) 307 || ((read_attr != 0) && (write_attr != 0))) { 308 warnx("%s: Must specify either -r or -w", __func__); 309 error = 1; 310 goto bailout; 311 } 312 313 if (read_attr != 0) { 314 scsi_read_attribute(&ccb->csio, 315 /*retries*/ retry_count, 316 /*cbfcnp*/ NULL, 317 /*tag_action*/ task_attr, 318 /*service_action*/ read_service_action, 319 /*element*/ element_address, 320 /*elem_type*/ element_type, 321 /*logical_volume*/ logical_volume, 322 /*partition*/ partition, 323 /*first_attribute*/ start_attr, 324 /*cache*/ cached_attr, 325 /*data_ptr*/ data_buf, 326 /*length*/ dxfer_len, 327 /*sense_len*/ SSD_FULL_SIZE, 328 /*timeout*/ timeout ? timeout : 60000); 329 #if 0 330 } else { 331 #endif 332 333 } 334 335 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; 336 337 if (err_recover != 0) 338 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; 339 340 if (cam_send_ccb(device, ccb) < 0) { 341 warn("error sending %s ATTRIBUTE", (read_attr != 0) ? 342 "READ" : "WRITE"); 343 error = 1; 344 goto bailout; 345 } 346 347 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 348 if (verbosemode != 0) { 349 cam_error_print(device, ccb, CAM_ESF_ALL, 350 CAM_EPF_ALL, stderr); 351 } 352 error = 1; 353 goto bailout; 354 } 355 356 if (read_attr == 0) 357 goto bailout; 358 359 valid_len = dxfer_len - ccb->csio.resid; 360 361 switch (read_service_action) { 362 case SRA_SA_ATTR_VALUES: { 363 uint32_t len_left, hdr_len, cur_len; 364 struct scsi_read_attribute_values *hdr; 365 struct scsi_mam_attribute_header *cur_id; 366 char error_str[512]; 367 uint8_t *cur_pos; 368 struct sbuf *sb; 369 370 hdr = (struct scsi_read_attribute_values *)data_buf; 371 372 if (valid_len < sizeof(*hdr)) { 373 fprintf(stdout, "No attributes returned.\n"); 374 error = 0; 375 goto bailout; 376 } 377 378 sb = sbuf_new_auto(); 379 if (sb == NULL) { 380 warn("%s: Unable to allocate sbuf", __func__); 381 error = 1; 382 goto bailout; 383 } 384 /* 385 * XXX KDM grab more data if it is available. 386 */ 387 hdr_len = scsi_4btoul(hdr->length); 388 389 for (len_left = MIN(valid_len, hdr_len), 390 cur_pos = &hdr->attribute_0[0]; len_left > sizeof(*cur_id); 391 len_left -= cur_len, cur_pos += cur_len) { 392 int cur_attr_num; 393 cur_id = (struct scsi_mam_attribute_header *)cur_pos; 394 cur_len = scsi_2btoul(cur_id->length) + sizeof(*cur_id); 395 cur_attr_num = scsi_2btoul(cur_id->id); 396 397 if ((attr_num != -1) 398 && (cur_attr_num != attr_num)) 399 continue; 400 401 error = scsi_attrib_sbuf(sb, cur_id, len_left, 402 /*user_table*/ NULL, /*num_user_entries*/ 0, 403 /*prefer_user_table*/ 0, output_format, error_str, 404 sizeof(error_str)); 405 if (error != 0) { 406 warnx("%s: %s", __func__, error_str); 407 sbuf_delete(sb); 408 error = 1; 409 goto bailout; 410 } 411 if (attr_num != -1) 412 break; 413 } 414 415 sbuf_finish(sb); 416 fprintf(stdout, "%s", sbuf_data(sb)); 417 sbuf_delete(sb); 418 break; 419 } 420 case SRA_SA_SUPPORTED_ATTRS: 421 case SRA_SA_ATTR_LIST: { 422 uint32_t len_left, hdr_len; 423 struct scsi_attrib_list_header *hdr; 424 struct scsi_attrib_table_entry *entry = NULL; 425 const char *sa_name = "Supported Attributes"; 426 const char *at_name = "Available Attributes"; 427 int attr_id; 428 uint8_t *cur_id; 429 430 hdr = (struct scsi_attrib_list_header *)data_buf; 431 if (valid_len < sizeof(*hdr)) { 432 fprintf(stdout, "No %s\n", 433 (read_service_action == SRA_SA_SUPPORTED_ATTRS)? 434 sa_name : at_name); 435 error = 0; 436 goto bailout; 437 } 438 fprintf(stdout, "%s:\n", 439 (read_service_action == SRA_SA_SUPPORTED_ATTRS) ? 440 sa_name : at_name); 441 hdr_len = scsi_4btoul(hdr->length); 442 for (len_left = MIN(valid_len, hdr_len), 443 cur_id = &hdr->first_attr_0[0]; len_left > 1; 444 len_left -= sizeof(uint16_t), cur_id += sizeof(uint16_t)) { 445 attr_id = scsi_2btoul(cur_id); 446 447 if ((attr_num != -1) 448 && (attr_id != attr_num)) 449 continue; 450 451 entry = scsi_get_attrib_entry(attr_id); 452 fprintf(stdout, "0x%.4x", attr_id); 453 if (entry == NULL) 454 fprintf(stdout, "\n"); 455 else 456 fprintf(stdout, ": %s\n", entry->desc); 457 458 if (attr_num != -1) 459 break; 460 } 461 break; 462 } 463 case SRA_SA_PART_LIST: 464 case SRA_SA_LOG_VOL_LIST: { 465 struct scsi_attrib_lv_list *lv_list; 466 const char *partition_name = "Partition"; 467 const char *lv_name = "Logical Volume"; 468 469 if (valid_len < sizeof(*lv_list)) { 470 fprintf(stdout, "No %s list returned\n", 471 (read_service_action == SRA_SA_PART_LIST) ? 472 partition_name : lv_name); 473 error = 0; 474 goto bailout; 475 } 476 477 lv_list = (struct scsi_attrib_lv_list *)data_buf; 478 479 fprintf(stdout, "First %s: %d\n", 480 (read_service_action == SRA_SA_PART_LIST) ? 481 partition_name : lv_name, 482 lv_list->first_lv_number); 483 fprintf(stdout, "Number of %ss: %d\n", 484 (read_service_action == SRA_SA_PART_LIST) ? 485 partition_name : lv_name, 486 lv_list->num_logical_volumes); 487 break; 488 } 489 default: 490 break; 491 } 492 bailout: 493 if (ccb != NULL) 494 cam_freeccb(ccb); 495 496 free(data_buf); 497 498 return (error); 499 } 500