1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
5 * Derived from work done by Julian Elischer <julian@tfs.com,
6 * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer,
14 *    without modification, immediately at the beginning of the file.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32#include <sys/queue.h>
33#include <sys/types.h>
34#include <sys/sbuf.h>
35
36#include <assert.h>
37#include <ctype.h>
38#include <err.h>
39#include <errno.h>
40#include <stdlib.h>
41#include <string.h>
42#include <stdio.h>
43#include <sysexits.h>
44#include <unistd.h>
45
46#include <cam/scsi/scsi_all.h>
47#include <cam/cam.h>
48#include <cam/cam_ccb.h>
49#include <camlib.h>
50#include "camcontrol.h"
51
52#define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
53#define	DEFAULT_EDITOR		"vi"
54#define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
55#define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
56#define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
57#define	PAGEDEF_START		'{'	/* Page definition delimiter. */
58#define	PAGEDEF_END		'}'	/* Page definition delimiter. */
59#define	PAGENAME_START		'"'	/* Page name delimiter. */
60#define	PAGENAME_END		'"'	/* Page name delimiter. */
61#define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
62#define	MAX_DATA_SIZE		4096	/* Mode/Log sense data buffer size. */
63#define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
64
65struct editentry {
66	STAILQ_ENTRY(editentry) link;
67	char	*name;
68	char	type;
69	int	editable;
70	int	size;
71	union {
72		int	ivalue;
73		char	*svalue;
74	} value;
75};
76static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
77static int editlist_changed = 0;	/* Whether any entries were changed. */
78
79struct pagename {
80	SLIST_ENTRY(pagename) link;
81	int page;
82	int subpage;
83	char *name;
84};
85static SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
86
87static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
88
89static FILE *edit_file = NULL;		/* File handle for edit file. */
90static char edit_path[] = "/tmp/camXXXXXX";
91
92
93/* Function prototypes. */
94static void		 editentry_create(void *hook, int letter, void *arg,
95					  int count, char *name);
96static void		 editentry_update(void *hook, int letter, void *arg,
97					  int count, char *name);
98static void		 editentry_create_desc(void *hook, int letter, void *arg,
99					  int count, char *name);
100static int		 editentry_save(void *hook, char *name);
101static struct editentry	*editentry_lookup(char *name);
102static int		 editentry_set(char *name, char *newvalue,
103				       int editonly);
104static void		 editlist_populate(struct cam_device *device,
105			    int cdb_len, int dbd, int pc, int page, int subpage,
106			    int task_attr, int retries, int timeout);
107static void		 editlist_populate_desc(struct cam_device *device,
108			    int cdb_len, int llbaa, int pc, int page,
109			    int subpage, int task_attr, int retries,
110			    int timeout);
111static void		 editlist_save(struct cam_device *device, int cdb_len,
112			    int dbd, int pc, int page, int subpage,
113			    int task_attr, int retries, int timeout);
114static void		 editlist_save_desc(struct cam_device *device, int cdb_len,
115			    int llbaa, int pc, int page, int subpage,
116			    int task_attr, int retries, int timeout);
117static void		 nameentry_create(int page, int subpage, char *name);
118static struct pagename	*nameentry_lookup(int page, int subpage);
119static int		 load_format(const char *pagedb_path, int lpage,
120			    int lsubpage);
121static int		 modepage_write(FILE *file, int editonly);
122static int		 modepage_read(FILE *file);
123static void		 modepage_edit(void);
124static void		 modepage_dump(struct cam_device *device, int cdb_len,
125			    int dbd, int pc, int page, int subpage,
126			    int task_attr, int retries, int timeout);
127static void		 modepage_dump_desc(struct cam_device *device,
128			    int cdb_len, int llbaa, int pc, int page,
129			    int subpage, int task_attr, int retries,
130			    int timeout);
131static void		 cleanup_editfile(void);
132
133
134#define	returnerr(code) do {						\
135	errno = code;							\
136	return (-1);							\
137} while (0)
138
139
140#define	RTRIM(string) do {						\
141	int _length;							\
142	while (isspace(string[_length = strlen(string) - 1]))		\
143		string[_length] = '\0';					\
144} while (0)
145
146
147static void
148editentry_create(void *hook __unused, int letter, void *arg, int count,
149		 char *name)
150{
151	struct editentry *newentry;	/* Buffer to hold new entry. */
152
153	/* Allocate memory for the new entry and a copy of the entry name. */
154	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
155	    (newentry->name = strdup(name)) == NULL)
156		err(EX_OSERR, NULL);
157
158	/* Trim any trailing whitespace for the entry name. */
159	RTRIM(newentry->name);
160
161	newentry->editable = (arg != NULL);
162	newentry->type = letter;
163	newentry->size = count;		/* Placeholder; not accurate. */
164	newentry->value.svalue = NULL;
165
166	STAILQ_INSERT_TAIL(&editlist, newentry, link);
167}
168
169static void
170editentry_update(void *hook __unused, int letter, void *arg, int count,
171		 char *name)
172{
173	struct editentry *dest;		/* Buffer to hold entry to update. */
174
175	dest = editentry_lookup(name);
176	assert(dest != NULL);
177
178	dest->type = letter;
179	dest->size = count;		/* We get the real size now. */
180
181	switch (dest->type) {
182	case 'i':			/* Byte-sized integral type. */
183	case 'b':			/* Bit-sized integral types. */
184	case 't':
185		dest->value.ivalue = (intptr_t)arg;
186		break;
187
188	case 'c':			/* Character array. */
189	case 'z':			/* Null-padded string. */
190		editentry_set(name, (char *)arg, 0);
191		break;
192	default:
193		; /* NOTREACHED */
194	}
195}
196
197static void
198editentry_create_desc(void *hook __unused, int letter, void *arg, int count,
199		 char *name)
200{
201	struct editentry *newentry;	/* Buffer to hold new entry. */
202
203	/* Allocate memory for the new entry and a copy of the entry name. */
204	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
205	    (newentry->name = strdup(name)) == NULL)
206		err(EX_OSERR, NULL);
207
208	/* Trim any trailing whitespace for the entry name. */
209	RTRIM(newentry->name);
210
211	newentry->editable = 1;
212	newentry->type = letter;
213	newentry->size = count;
214	newentry->value.svalue = NULL;
215
216	STAILQ_INSERT_TAIL(&editlist, newentry, link);
217
218	switch (letter) {
219	case 'i':			/* Byte-sized integral type. */
220	case 'b':			/* Bit-sized integral types. */
221	case 't':
222		newentry->value.ivalue = (intptr_t)arg;
223		break;
224
225	case 'c':			/* Character array. */
226	case 'z':			/* Null-padded string. */
227		editentry_set(name, (char *)arg, 0);
228		break;
229	default:
230		; /* NOTREACHED */
231	}
232}
233
234static int
235editentry_save(void *hook __unused, char *name)
236{
237	struct editentry *src;		/* Entry value to save. */
238
239	src = editentry_lookup(name);
240	if (src == 0) {
241		/*
242		 * This happens if field does not fit into read page size.
243		 * It also means that this field won't be written, so the
244		 * returned value does not really matter.
245		 */
246		return (0);
247	}
248
249	switch (src->type) {
250	case 'i':			/* Byte-sized integral type. */
251	case 'b':			/* Bit-sized integral types. */
252	case 't':
253		return (src->value.ivalue);
254		/* NOTREACHED */
255
256	case 'c':			/* Character array. */
257	case 'z':			/* Null-padded string. */
258		return ((intptr_t)src->value.svalue);
259		/* NOTREACHED */
260
261	default:
262		; /* NOTREACHED */
263	}
264
265	return (0);			/* This should never happen. */
266}
267
268static struct editentry *
269editentry_lookup(char *name)
270{
271	struct editentry *scan;
272
273	assert(name != NULL);
274
275	STAILQ_FOREACH(scan, &editlist, link) {
276		if (strcasecmp(scan->name, name) == 0)
277			return (scan);
278	}
279
280	/* Not found during list traversal. */
281	return (NULL);
282}
283
284static int
285editentry_set(char *name, char *newvalue, int editonly)
286{
287	struct editentry *dest;	/* Modepage entry to update. */
288	char *cval;		/* Pointer to new string value. */
289	char *convertend;	/* End-of-conversion pointer. */
290	long long ival, newival; /* New integral value. */
291	int resolution;		/* Resolution in bits for integer conversion. */
292
293/*
294 * Macro to determine the maximum value of the given size for the current
295 * resolution.
296 */
297#define	RESOLUTION_MAX(size)	((1LL << (resolution * (size))) - 1)
298
299	assert(newvalue != NULL);
300	if (*newvalue == '\0')
301		return (0);	/* Nothing to do. */
302
303	if ((dest = editentry_lookup(name)) == NULL)
304		returnerr(ENOENT);
305	if (!dest->editable && editonly)
306		returnerr(EPERM);
307
308	switch (dest->type) {
309	case 'i':		/* Byte-sized integral type. */
310	case 'b':		/* Bit-sized integral types. */
311	case 't':
312		/* Convert the value string to an integer. */
313		resolution = (dest->type == 'i')? 8: 1;
314		ival = strtoll(newvalue, &convertend, 0);
315		if (*convertend != '\0')
316			returnerr(EINVAL);
317		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
318			newival = (ival < 0) ? 0 : RESOLUTION_MAX(dest->size);
319			warnx("value %lld is out of range for entry %s; "
320			    "clipping to %lld", ival, name, newival);
321			ival = newival;
322		}
323		if (dest->value.ivalue != ival)
324			editlist_changed = 1;
325		dest->value.ivalue = ival;
326		break;
327
328	case 'c':		/* Character array. */
329	case 'z':		/* Null-padded string. */
330		if ((cval = calloc(1, dest->size + 1)) == NULL)
331			err(EX_OSERR, NULL);
332		strlcpy(cval, newvalue, dest->size + 1);
333		if (dest->type == 'z') {
334			/* Convert trailing spaces to nulls. */
335			char *convertend2;
336
337			for (convertend2 = cval + dest->size;
338			    convertend2 >= cval; convertend2--) {
339				if (*convertend2 == ' ')
340					*convertend2 = '\0';
341				else if (*convertend2 != '\0')
342					break;
343			}
344		}
345		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
346			/* Nothing changed, free the newly allocated string. */
347			free(cval);
348			break;
349		}
350		if (dest->value.svalue != NULL) {
351			/* Free the current string buffer. */
352			free(dest->value.svalue);
353			dest->value.svalue = NULL;
354		}
355		dest->value.svalue = cval;
356		editlist_changed = 1;
357		break;
358
359	default:
360		; /* NOTREACHED */
361	}
362
363	return (0);
364#undef RESOLUTION_MAX
365}
366
367static void
368nameentry_create(int page, int subpage, char *name) {
369	struct pagename *newentry;
370
371	if (page < 0 || subpage < 0 || name == NULL || name[0] == '\0')
372		return;
373
374	/* Allocate memory for the new entry and a copy of the entry name. */
375	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
376	    (newentry->name = strdup(name)) == NULL)
377		err(EX_OSERR, NULL);
378
379	/* Trim any trailing whitespace for the page name. */
380	RTRIM(newentry->name);
381
382	newentry->page = page;
383	newentry->subpage = subpage;
384	SLIST_INSERT_HEAD(&namelist, newentry, link);
385}
386
387static struct pagename *
388nameentry_lookup(int page, int subpage) {
389	struct pagename *scan;
390
391	SLIST_FOREACH(scan, &namelist, link) {
392		if (page == scan->page && subpage == scan->subpage)
393			return (scan);
394	}
395
396	/* Not found during list traversal. */
397	return (NULL);
398}
399
400static int
401load_format(const char *pagedb_path, int lpage, int lsubpage)
402{
403	FILE *pagedb;
404	char str_page[MAX_PAGENUM_LEN];
405	char *str_subpage;
406	char str_pagename[MAX_PAGENAME_LEN];
407	int page;
408	int subpage;
409	int depth;			/* Quoting depth. */
410	int found;
411	int lineno;
412	enum { LOCATE, PAGENAME, PAGEDEF } state;
413	int ch;
414	char c;
415
416#define	SETSTATE_LOCATE do {						\
417	str_page[0] = '\0';						\
418	str_pagename[0] = '\0';						\
419	page = -1;							\
420	subpage = -1;							\
421	state = LOCATE;							\
422} while (0)
423
424#define	SETSTATE_PAGENAME do {						\
425	str_pagename[0] = '\0';						\
426	state = PAGENAME;						\
427} while (0)
428
429#define	SETSTATE_PAGEDEF do {						\
430	format[0] = '\0';						\
431	state = PAGEDEF;						\
432} while (0)
433
434#define	UPDATE_LINENO do {						\
435	if (c == '\n')							\
436		lineno++;						\
437} while (0)
438
439#define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
440
441	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
442		returnerr(ENOENT);
443
444	SLIST_INIT(&namelist);
445
446	c = '\0';
447	depth = 0;
448	lineno = 0;
449	found = 0;
450	SETSTATE_LOCATE;
451	while ((ch = fgetc(pagedb)) != EOF) {
452
453		/* Keep a line count to make error messages more useful. */
454		UPDATE_LINENO;
455
456		/* Skip over comments anywhere in the mode database. */
457		if (ch == '#') {
458			do {
459				ch = fgetc(pagedb);
460			} while (ch != '\n' && ch != EOF);
461			UPDATE_LINENO;
462			continue;
463		}
464		c = ch;
465
466		/* Strip out newline characters. */
467		if (c == '\n')
468			continue;
469
470		/* Keep track of the nesting depth for braces. */
471		if (c == PAGEDEF_START)
472			depth++;
473		else if (c == PAGEDEF_END) {
474			depth--;
475			if (depth < 0) {
476				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
477				    lineno, "mismatched bracket");
478			}
479		}
480
481		switch (state) {
482		case LOCATE:
483			/*
484			 * Locate the page the user is interested in, skipping
485			 * all others.
486			 */
487			if (isspace(c)) {
488				/* Ignore all whitespace between pages. */
489				break;
490			} else if (depth == 0 && c == PAGEENTRY_END) {
491				/*
492				 * A page entry terminator will reset page
493				 * scanning (useful for assigning names to
494				 * modes without providing a mode definition).
495				 */
496				/* Record the name of this page. */
497				str_subpage = str_page;
498				strsep(&str_subpage, ",");
499				page = strtol(str_page, NULL, 0);
500				if (str_subpage)
501				    subpage = strtol(str_subpage, NULL, 0);
502				else
503				    subpage = 0;
504				nameentry_create(page, subpage, str_pagename);
505				SETSTATE_LOCATE;
506			} else if (depth == 0 && c == PAGENAME_START) {
507				SETSTATE_PAGENAME;
508			} else if (c == PAGEDEF_START) {
509				str_subpage = str_page;
510				strsep(&str_subpage, ",");
511				page = strtol(str_page, NULL, 0);
512				if (str_subpage)
513				    subpage = strtol(str_subpage, NULL, 0);
514				else
515				    subpage = 0;
516				if (depth == 1) {
517					/* Record the name of this page. */
518					nameentry_create(page, subpage,
519					    str_pagename);
520					/*
521					 * Only record the format if this is
522					 * the page we are interested in.
523					 */
524					if (lpage == page &&
525					    lsubpage == subpage && !found)
526						SETSTATE_PAGEDEF;
527				}
528			} else if (c == PAGEDEF_END) {
529				/* Reset the processor state. */
530				SETSTATE_LOCATE;
531			} else if (depth == 0 && ! BUFFERFULL(str_page)) {
532				strncat(str_page, &c, 1);
533			} else if (depth == 0) {
534				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
535				    lineno, "page identifier exceeds",
536				    sizeof(str_page) - 1, "characters");
537			}
538			break;
539
540		case PAGENAME:
541			if (c == PAGENAME_END) {
542				/*
543				 * Return to LOCATE state without resetting the
544				 * page number buffer.
545				 */
546				state = LOCATE;
547			} else if (! BUFFERFULL(str_pagename)) {
548				strncat(str_pagename, &c, 1);
549			} else {
550				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
551				    lineno, "page name exceeds",
552				    sizeof(str_page) - 1, "characters");
553			}
554			break;
555
556		case PAGEDEF:
557			/*
558			 * Transfer the page definition into a format buffer
559			 * suitable for use with CDB encoding/decoding routines.
560			 */
561			if (depth == 0) {
562				found = 1;
563				SETSTATE_LOCATE;
564			} else if (! BUFFERFULL(format)) {
565				strncat(format, &c, 1);
566			} else {
567				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
568				    lineno, "page definition exceeds",
569				    sizeof(format) - 1, "characters");
570			}
571			break;
572
573		default:
574			; /* NOTREACHED */
575		}
576
577		/* Repeat processing loop with next character. */
578	}
579
580	if (ferror(pagedb))
581		err(EX_OSFILE, "%s", pagedb_path);
582
583	/* Close the SCSI page database. */
584	fclose(pagedb);
585
586	if (!found)			/* Never found a matching page. */
587		returnerr(ESRCH);
588
589	return (0);
590}
591
592static void
593editlist_populate(struct cam_device *device, int cdb_len, int dbd, int pc,
594    int page, int subpage, int task_attr, int retries, int timeout)
595{
596	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
597	uint8_t *mode_pars;		/* Pointer to modepage params. */
598	struct scsi_mode_page_header *mph;
599	struct scsi_mode_page_header_sp *mphsp;
600	size_t len;
601
602	STAILQ_INIT(&editlist);
603
604	/* Fetch changeable values; use to build initial editlist. */
605	mode_sense(device, &cdb_len, dbd, 0, 1, page, subpage, task_attr,
606		   retries, timeout, data, sizeof(data));
607
608	if (cdb_len == 6) {
609		struct scsi_mode_header_6 *mh =
610		    (struct scsi_mode_header_6 *)data;
611		mph = find_mode_page_6(mh);
612	} else {
613		struct scsi_mode_header_10 *mh =
614		    (struct scsi_mode_header_10 *)data;
615		mph = find_mode_page_10(mh);
616	}
617	if ((mph->page_code & SMPH_SPF) == 0) {
618		mode_pars = (uint8_t *)(mph + 1);
619		len = mph->page_length;
620	} else {
621		mphsp = (struct scsi_mode_page_header_sp *)mph;
622		mode_pars = (uint8_t *)(mphsp + 1);
623		len = scsi_2btoul(mphsp->page_length);
624	}
625	len = MIN(len, sizeof(data) - (mode_pars - data));
626
627	/* Decode the value data, creating edit_entries for each value. */
628	buff_decode_visit(mode_pars, len, format, editentry_create, 0);
629
630	/* Fetch the current/saved values; use to set editentry values. */
631	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
632	    retries, timeout, data, sizeof(data));
633	buff_decode_visit(mode_pars, len, format, editentry_update, 0);
634}
635
636static void
637editlist_populate_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
638    int page, int subpage, int task_attr, int retries, int timeout)
639{
640	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
641	uint8_t *desc;			/* Pointer to block descriptor. */
642	char num[8];
643	struct sbuf sb;
644	size_t len;
645	u_int longlba, dlen, i;
646
647	STAILQ_INIT(&editlist);
648
649	/* Fetch the current/saved values. */
650	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
651	    retries, timeout, data, sizeof(data));
652
653	if (cdb_len == 6) {
654		struct scsi_mode_header_6 *mh =
655		    (struct scsi_mode_header_6 *)data;
656		desc = (uint8_t *)(mh + 1);
657		len = mh->blk_desc_len;
658		longlba = 0;
659	} else {
660		struct scsi_mode_header_10 *mh =
661		    (struct scsi_mode_header_10 *)data;
662		desc = (uint8_t *)(mh + 1);
663		len = scsi_2btoul(mh->blk_desc_len);
664		longlba = (mh->flags & SMH_LONGLBA) != 0;
665	}
666	dlen = longlba ? 16 : 8;
667	len = MIN(len, sizeof(data) - (desc - data));
668
669	sbuf_new(&sb, format, sizeof(format), SBUF_FIXEDLEN);
670	num[0] = 0;
671	for (i = 0; i * dlen < len; i++) {
672		if (i > 0)
673			snprintf(num, sizeof(num), " %d", i + 1);
674		if (longlba) {
675			sbuf_printf(&sb, "{Number of Logical Blocks%s High} i4\n", num);
676			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
677			sbuf_cat(&sb, "{Reserved} *i4\n");
678			sbuf_printf(&sb, "{Logical Block Length%s} i4\n", num);
679		} else if (device->pd_type == T_DIRECT) {
680			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
681			sbuf_cat(&sb, "{Reserved} *i1\n");
682			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
683		} else {
684			sbuf_printf(&sb, "{Density Code%s} i1\n", num);
685			sbuf_printf(&sb, "{Number of Logical Blocks%s} i3\n", num);
686			sbuf_cat(&sb, "{Reserved} *i1\n");
687			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
688		}
689	}
690	sbuf_finish(&sb);
691	sbuf_delete(&sb);
692
693	/* Decode the value data, creating edit_entries for each value. */
694	buff_decode_visit(desc, len, format, editentry_create_desc, 0);
695}
696
697static void
698editlist_save(struct cam_device *device, int cdb_len, int dbd, int pc,
699    int page, int subpage, int task_attr, int retries, int timeout)
700{
701	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
702	uint8_t *mode_pars;		/* Pointer to modepage params. */
703	struct scsi_mode_page_header *mph;
704	struct scsi_mode_page_header_sp *mphsp;
705	size_t len, hlen, mphlen;
706
707	/* Make sure that something changed before continuing. */
708	if (! editlist_changed)
709		return;
710
711	/* Preload the CDB buffer with the current mode page data. */
712	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
713	    retries, timeout, data, sizeof(data));
714
715	/* Initial headers & offsets. */
716	/*
717	 * Tape drives include write protect (WP), Buffered Mode and Speed
718	 * settings in the device-specific parameter.  Clearing this
719	 * parameter on a mode select can have the effect of turning off
720	 * write protect or buffered mode, or changing the speed setting of
721	 * the tape drive.
722	 *
723	 * Disks report DPO/FUA support via the device specific parameter
724	 * for MODE SENSE, but the bit is reserved for MODE SELECT.  So we
725	 * clear this for disks (and other non-tape devices) to avoid
726	 * potential errors from the target device.
727	 */
728	if (cdb_len == 6) {
729		struct scsi_mode_header_6 *mh =
730		    (struct scsi_mode_header_6 *)data;
731		hlen = sizeof(*mh);
732		/* Eliminate block descriptors. */
733		if (mh->blk_desc_len > 0) {
734			bcopy(find_mode_page_6(mh), mh + 1,
735			    mh->data_length + 1 - hlen -
736			    mh->blk_desc_len);
737			mh->blk_desc_len = 0;
738		}
739		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
740		if (device->pd_type != T_SEQUENTIAL)
741			mh->dev_spec = 0;	/* See comment above */
742		mph = find_mode_page_6(mh);
743	} else {
744		struct scsi_mode_header_10 *mh =
745		    (struct scsi_mode_header_10 *)data;
746		hlen = sizeof(*mh);
747		/* Eliminate block descriptors. */
748		if (scsi_2btoul(mh->blk_desc_len) > 0) {
749			bcopy(find_mode_page_10(mh), mh + 1,
750			    scsi_2btoul(mh->data_length) + 1 - hlen -
751			    scsi_2btoul(mh->blk_desc_len));
752			scsi_ulto2b(0, mh->blk_desc_len);
753		}
754		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
755		if (device->pd_type != T_SEQUENTIAL)
756			mh->dev_spec = 0;	/* See comment above */
757		mph = find_mode_page_10(mh);
758	}
759	if ((mph->page_code & SMPH_SPF) == 0) {
760		mphlen = sizeof(*mph);
761		mode_pars = (uint8_t *)(mph + 1);
762		len = mph->page_length;
763	} else {
764		mphsp = (struct scsi_mode_page_header_sp *)mph;
765		mphlen = sizeof(*mphsp);
766		mode_pars = (uint8_t *)(mphsp + 1);
767		len = scsi_2btoul(mphsp->page_length);
768	}
769	len = MIN(len, sizeof(data) - (mode_pars - data));
770
771	/* Encode the value data to be passed back to the device. */
772	buff_encode_visit(mode_pars, len, format, editentry_save, 0);
773
774	mph->page_code &= ~SMPH_PS;	/* Reserved for MODE SELECT command. */
775
776	/*
777	 * Write the changes back to the device. If the user editted control
778	 * page 3 (saved values) then request the changes be permanently
779	 * recorded.
780	 */
781	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
782	    task_attr, retries, timeout, data, hlen + mphlen + len);
783}
784
785static void
786editlist_save_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
787    int page, int subpage, int task_attr, int retries, int timeout)
788{
789	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
790	uint8_t *desc;			/* Pointer to block descriptor. */
791	size_t len, hlen;
792
793	/* Make sure that something changed before continuing. */
794	if (! editlist_changed)
795		return;
796
797	/* Preload the CDB buffer with the current mode page data. */
798	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
799	    retries, timeout, data, sizeof(data));
800
801	/* Initial headers & offsets. */
802	if (cdb_len == 6) {
803		struct scsi_mode_header_6 *mh =
804		    (struct scsi_mode_header_6 *)data;
805		hlen = sizeof(*mh);
806		desc = (uint8_t *)(mh + 1);
807		len = mh->blk_desc_len;
808		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
809		if (device->pd_type != T_SEQUENTIAL)
810			mh->dev_spec = 0;	/* See comment above */
811	} else {
812		struct scsi_mode_header_10 *mh =
813		    (struct scsi_mode_header_10 *)data;
814		hlen = sizeof(*mh);
815		desc = (uint8_t *)(mh + 1);
816		len = scsi_2btoul(mh->blk_desc_len);
817		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
818		if (device->pd_type != T_SEQUENTIAL)
819			mh->dev_spec = 0;	/* See comment above */
820	}
821	len = MIN(len, sizeof(data) - (desc - data));
822
823	/* Encode the value data to be passed back to the device. */
824	buff_encode_visit(desc, len, format, editentry_save, 0);
825
826	/*
827	 * Write the changes back to the device. If the user editted control
828	 * page 3 (saved values) then request the changes be permanently
829	 * recorded.
830	 */
831	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
832	    task_attr, retries, timeout, data, hlen + len);
833}
834
835static int
836modepage_write(FILE *file, int editonly)
837{
838	struct editentry *scan;
839	int written = 0;
840
841	STAILQ_FOREACH(scan, &editlist, link) {
842		if (scan->editable || !editonly) {
843			written++;
844			if (scan->type == 'c' || scan->type == 'z') {
845				fprintf(file, "%s:  %s\n", scan->name,
846				    scan->value.svalue);
847			} else {
848				fprintf(file, "%s:  %u\n", scan->name,
849				    scan->value.ivalue);
850			}
851		}
852	}
853	return (written);
854}
855
856static int
857modepage_read(FILE *file)
858{
859	char *buffer;			/* Pointer to dynamic line buffer.  */
860	char *line;			/* Pointer to static fgetln buffer. */
861	char *name;			/* Name portion of the line buffer. */
862	char *value;			/* Value portion of line buffer.    */
863	size_t length;			/* Length of static fgetln buffer.  */
864
865#define	ABORT_READ(message, param) do {					\
866	warnx(message, param);						\
867	free(buffer);							\
868	returnerr(EAGAIN);						\
869} while (0)
870
871	while ((line = fgetln(file, &length)) != NULL) {
872		/* Trim trailing whitespace (including optional newline). */
873		while (length > 0 && isspace(line[length - 1]))
874			length--;
875
876	    	/* Allocate a buffer to hold the line + terminating null. */
877	    	if ((buffer = malloc(length + 1)) == NULL)
878			err(EX_OSERR, NULL);
879		memcpy(buffer, line, length);
880		buffer[length] = '\0';
881
882		/* Strip out comments. */
883		if ((value = strchr(buffer, '#')) != NULL)
884			*value = '\0';
885
886		/* The name is first in the buffer. Trim whitespace.*/
887		name = buffer;
888		RTRIM(name);
889		while (isspace(*name))
890			name++;
891
892		/* Skip empty lines. */
893		if (strlen(name) == 0)
894			continue;
895
896		/* The name ends at the colon; the value starts there. */
897		if ((value = strrchr(buffer, ':')) == NULL)
898			ABORT_READ("no value associated with %s", name);
899		*value = '\0';			/* Null-terminate name. */
900		value++;			/* Value starts afterwards. */
901
902		/* Trim leading and trailing whitespace. */
903		RTRIM(value);
904		while (isspace(*value))
905			value++;
906
907		/* Make sure there is a value left. */
908		if (strlen(value) == 0)
909			ABORT_READ("no value associated with %s", name);
910
911		/* Update our in-memory copy of the modepage entry value. */
912		if (editentry_set(name, value, 1) != 0) {
913			if (errno == ENOENT) {
914				/* No entry by the name. */
915				ABORT_READ("no such modepage entry \"%s\"",
916				    name);
917			} else if (errno == EINVAL) {
918				/* Invalid value. */
919				ABORT_READ("Invalid value for entry \"%s\"",
920				    name);
921			} else if (errno == ERANGE) {
922				/* Value out of range for entry type. */
923				ABORT_READ("value out of range for %s", name);
924			} else if (errno == EPERM) {
925				/* Entry is not editable; not fatal. */
926				warnx("modepage entry \"%s\" is read-only; "
927				    "skipping.", name);
928			}
929		}
930
931		free(buffer);
932	}
933	return (ferror(file)? -1: 0);
934
935#undef ABORT_READ
936}
937
938static void
939modepage_edit(void)
940{
941	const char *editor;
942	char *commandline;
943	int fd;
944	int written;
945
946	if (!isatty(fileno(stdin))) {
947		/* Not a tty, read changes from stdin. */
948		modepage_read(stdin);
949		return;
950	}
951
952	/* Lookup editor to invoke. */
953	if ((editor = getenv("EDITOR")) == NULL)
954		editor = DEFAULT_EDITOR;
955
956	/* Create temp file for editor to modify. */
957	if ((fd = mkstemp(edit_path)) == -1)
958		errx(EX_CANTCREAT, "mkstemp failed");
959
960	atexit(cleanup_editfile);
961
962	if ((edit_file = fdopen(fd, "w")) == NULL)
963		err(EX_NOINPUT, "%s", edit_path);
964
965	written = modepage_write(edit_file, 1);
966
967	fclose(edit_file);
968	edit_file = NULL;
969
970	if (written == 0) {
971		warnx("no editable entries");
972		cleanup_editfile();
973		return;
974	}
975
976	/*
977	 * Allocate memory to hold the command line (the 2 extra characters
978	 * are to hold the argument separator (a space), and the terminating
979	 * null character.
980	 */
981	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
982	if (commandline == NULL)
983		err(EX_OSERR, NULL);
984	sprintf(commandline, "%s %s", editor, edit_path);
985
986	/* Invoke the editor on the temp file. */
987	if (system(commandline) == -1)
988		err(EX_UNAVAILABLE, "could not invoke %s", editor);
989	free(commandline);
990
991	if ((edit_file = fopen(edit_path, "r")) == NULL)
992		err(EX_NOINPUT, "%s", edit_path);
993
994	/* Read any changes made to the temp file. */
995	modepage_read(edit_file);
996
997	cleanup_editfile();
998}
999
1000static void
1001modepage_dump(struct cam_device *device, int cdb_len, int dbd, int pc,
1002	      int page, int subpage, int task_attr, int retries, int timeout)
1003{
1004	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1005	uint8_t *mode_pars;		/* Pointer to modepage params. */
1006	struct scsi_mode_page_header *mph;
1007	struct scsi_mode_page_header_sp *mphsp;
1008	size_t indx, len;
1009
1010	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
1011	    retries, timeout, data, sizeof(data));
1012
1013	if (cdb_len == 6) {
1014		struct scsi_mode_header_6 *mh =
1015		    (struct scsi_mode_header_6 *)data;
1016		mph = find_mode_page_6(mh);
1017	} else {
1018		struct scsi_mode_header_10 *mh =
1019		    (struct scsi_mode_header_10 *)data;
1020		mph = find_mode_page_10(mh);
1021	}
1022	if ((mph->page_code & SMPH_SPF) == 0) {
1023		mode_pars = (uint8_t *)(mph + 1);
1024		len = mph->page_length;
1025	} else {
1026		mphsp = (struct scsi_mode_page_header_sp *)mph;
1027		mode_pars = (uint8_t *)(mphsp + 1);
1028		len = scsi_2btoul(mphsp->page_length);
1029	}
1030	len = MIN(len, sizeof(data) - (mode_pars - data));
1031
1032	/* Print the raw mode page data with newlines each 8 bytes. */
1033	for (indx = 0; indx < len; indx++) {
1034		printf("%02x%c",mode_pars[indx],
1035		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1036	}
1037	putchar('\n');
1038}
1039static void
1040modepage_dump_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
1041	      int page, int subpage, int task_attr, int retries, int timeout)
1042{
1043	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1044	uint8_t *desc;			/* Pointer to block descriptor. */
1045	size_t indx, len;
1046
1047	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
1048	    retries, timeout, data, sizeof(data));
1049
1050	if (cdb_len == 6) {
1051		struct scsi_mode_header_6 *mh =
1052		    (struct scsi_mode_header_6 *)data;
1053		desc = (uint8_t *)(mh + 1);
1054		len = mh->blk_desc_len;
1055	} else {
1056		struct scsi_mode_header_10 *mh =
1057		    (struct scsi_mode_header_10 *)data;
1058		desc = (uint8_t *)(mh + 1);
1059		len = scsi_2btoul(mh->blk_desc_len);
1060	}
1061	len = MIN(len, sizeof(data) - (desc - data));
1062
1063	/* Print the raw mode page data with newlines each 8 bytes. */
1064	for (indx = 0; indx < len; indx++) {
1065		printf("%02x%c", desc[indx],
1066		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1067	}
1068	putchar('\n');
1069}
1070
1071static void
1072cleanup_editfile(void)
1073{
1074	if (edit_file == NULL)
1075		return;
1076	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
1077		warn("%s", edit_path);
1078	edit_file = NULL;
1079}
1080
1081void
1082mode_edit(struct cam_device *device, int cdb_len, int desc, int dbd, int llbaa,
1083    int pc, int page, int subpage, int edit, int binary, int task_attr,
1084    int retry_count, int timeout)
1085{
1086	const char *pagedb_path;	/* Path to modepage database. */
1087
1088	if (binary) {
1089		if (edit)
1090			errx(EX_USAGE, "cannot edit in binary mode.");
1091	} else if (desc) {
1092		editlist_populate_desc(device, cdb_len, llbaa, pc, page,
1093		    subpage, task_attr, retry_count, timeout);
1094	} else {
1095		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1096			pagedb_path = DEFAULT_SCSI_MODE_DB;
1097
1098		if (load_format(pagedb_path, page, subpage) != 0 &&
1099		    (edit || verbose)) {
1100			if (errno == ENOENT) {
1101				/* Modepage database file not found. */
1102				warn("cannot open modepage database \"%s\"",
1103				    pagedb_path);
1104			} else if (errno == ESRCH) {
1105				/* Modepage entry not found in database. */
1106				warnx("modepage 0x%02x,0x%02x not found in "
1107				    "database \"%s\"", page, subpage,
1108				    pagedb_path);
1109			}
1110			/* We can recover in display mode, otherwise we exit. */
1111			if (!edit) {
1112				warnx("reverting to binary display only");
1113				binary = 1;
1114			} else
1115				exit(EX_OSFILE);
1116		}
1117
1118		editlist_populate(device, cdb_len, dbd, pc, page, subpage,
1119		    task_attr, retry_count, timeout);
1120	}
1121
1122	if (edit) {
1123		if (pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
1124		    pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
1125			errx(EX_USAGE, "it only makes sense to edit page 0 "
1126			    "(current) or page 3 (saved values)");
1127		modepage_edit();
1128		if (desc) {
1129			editlist_save_desc(device, cdb_len, llbaa, pc, page,
1130			    subpage, task_attr, retry_count, timeout);
1131		} else {
1132			editlist_save(device, cdb_len, dbd, pc, page, subpage,
1133			    task_attr, retry_count, timeout);
1134		}
1135	} else if (binary || STAILQ_EMPTY(&editlist)) {
1136		/* Display without formatting information. */
1137		if (desc) {
1138			modepage_dump_desc(device, cdb_len, llbaa, pc, page,
1139			    subpage, task_attr, retry_count, timeout);
1140		} else {
1141			modepage_dump(device, cdb_len, dbd, pc, page, subpage,
1142			    task_attr, retry_count, timeout);
1143		}
1144	} else {
1145		/* Display with format. */
1146		modepage_write(stdout, 0);
1147	}
1148}
1149
1150void
1151mode_list(struct cam_device *device, int cdb_len, int dbd, int pc, int subpages,
1152	  int task_attr, int retry_count, int timeout)
1153{
1154	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1155	struct scsi_mode_page_header *mph;
1156	struct scsi_mode_page_header_sp *mphsp;
1157	struct pagename *nameentry;
1158	const char *pagedb_path;
1159	int len, off, page, subpage;
1160
1161	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1162		pagedb_path = DEFAULT_SCSI_MODE_DB;
1163
1164	if (load_format(pagedb_path, 0, 0) != 0 && verbose && errno == ENOENT) {
1165		/* Modepage database file not found. */
1166		warn("cannot open modepage database \"%s\"", pagedb_path);
1167	}
1168
1169	/* Build the list of all mode pages by querying the "all pages" page. */
1170	mode_sense(device, &cdb_len, dbd, 0, pc, SMS_ALL_PAGES_PAGE,
1171	    subpages ? SMS_SUBPAGE_ALL : 0,
1172	    task_attr, retry_count, timeout, data, sizeof(data));
1173
1174	/* Skip block descriptors. */
1175	if (cdb_len == 6) {
1176		struct scsi_mode_header_6 *mh =
1177		    (struct scsi_mode_header_6 *)data;
1178		len = mh->data_length;
1179		off = sizeof(*mh) + mh->blk_desc_len;
1180	} else {
1181		struct scsi_mode_header_10 *mh =
1182		    (struct scsi_mode_header_10 *)data;
1183		len = scsi_2btoul(mh->data_length);
1184		off = sizeof(*mh) + scsi_2btoul(mh->blk_desc_len);
1185	}
1186	/* Iterate through the pages in the reply. */
1187	while (off < len) {
1188		/* Locate the next mode page header. */
1189		mph = (struct scsi_mode_page_header *)(data + off);
1190
1191		if ((mph->page_code & SMPH_SPF) == 0) {
1192			page = mph->page_code & SMS_PAGE_CODE;
1193			subpage = 0;
1194			off += sizeof(*mph) + mph->page_length;
1195		} else {
1196			mphsp = (struct scsi_mode_page_header_sp *)mph;
1197			page = mphsp->page_code & SMS_PAGE_CODE;
1198			subpage = mphsp->subpage;
1199			off += sizeof(*mphsp) + scsi_2btoul(mphsp->page_length);
1200		}
1201
1202		nameentry = nameentry_lookup(page, subpage);
1203		if (subpage == 0) {
1204			printf("0x%02x\t%s\n", page,
1205			    nameentry ? nameentry->name : "");
1206		} else {
1207			printf("0x%02x,0x%02x\t%s\n", page, subpage,
1208			    nameentry ? nameentry->name : "");
1209		}
1210	}
1211}
1212