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/param.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
68struct scsi_attr_desc {
69	int attr_id;
70
71	STAILQ_ENTRY(scsi_attr_desc) links;
72};
73#endif
74
75static 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
83static 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
91static 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
105int
106scsiattrib(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			    nitems(elem_type_map),
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	}
492bailout:
493	if (ccb != NULL)
494		cam_freeccb(ccb);
495
496	free(data_buf);
497
498	return (error);
499}
500