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