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