xref: /freebsd/sbin/camcontrol/modeedit.c (revision e48c002fa40a496ab097717902856fd2667e28c5)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 __FBSDID("$FreeBSD$");
33 
34 #include <sys/queue.h>
35 #include <sys/types.h>
36 
37 #include <assert.h>
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <stdio.h>
44 #include <sysexits.h>
45 #include <unistd.h>
46 
47 #include <cam/scsi/scsi_all.h>
48 #include <cam/cam.h>
49 #include <cam/cam_ccb.h>
50 #include <camlib.h>
51 #include "camcontrol.h"
52 
53 #define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
54 #define	DEFAULT_EDITOR		"vi"
55 #define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
56 #define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
57 #define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
58 #define	PAGEDEF_START		'{'	/* Page definition delimiter. */
59 #define	PAGEDEF_END		'}'	/* Page definition delimiter. */
60 #define	PAGENAME_START		'"'	/* Page name delimiter. */
61 #define	PAGENAME_END		'"'	/* Page name delimiter. */
62 #define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
63 #define	MAX_DATA_SIZE		4096	/* Mode/Log sense data buffer size. */
64 #define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
65 
66 struct editentry {
67 	STAILQ_ENTRY(editentry) link;
68 	char	*name;
69 	char	type;
70 	int	editable;
71 	int	size;
72 	union {
73 		int	ivalue;
74 		char	*svalue;
75 	} value;
76 };
77 static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
78 static int editlist_changed = 0;	/* Whether any entries were changed. */
79 
80 struct pagename {
81 	SLIST_ENTRY(pagename) link;
82 	int page;
83 	int subpage;
84 	char *name;
85 };
86 static SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
87 
88 static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
89 
90 static FILE *edit_file = NULL;		/* File handle for edit file. */
91 static char edit_path[] = "/tmp/camXXXXXX";
92 
93 
94 /* Function prototypes. */
95 static void		 editentry_create(void *hook, int letter, void *arg,
96 					  int count, char *name);
97 static void		 editentry_update(void *hook, int letter, void *arg,
98 					  int count, char *name);
99 static int		 editentry_save(void *hook, char *name);
100 static struct editentry	*editentry_lookup(char *name);
101 static int		 editentry_set(char *name, char *newvalue,
102 				       int editonly);
103 static void		 editlist_populate(struct cam_device *device,
104 			    int cdb_len, int dbd, int pc, int page, int subpage,
105 			    int task_attr, int retries, int timeout);
106 static void		 editlist_save(struct cam_device *device, int cdb_len,
107 			    int dbd, int pc, int page, int subpage,
108 			    int task_attr, int retries, int timeout);
109 static void		 nameentry_create(int page, int subpage, char *name);
110 static struct pagename	*nameentry_lookup(int page, int subpage);
111 static int		 load_format(const char *pagedb_path, int lpage,
112 			    int lsubpage);
113 static int		 modepage_write(FILE *file, int editonly);
114 static int		 modepage_read(FILE *file);
115 static void		 modepage_edit(void);
116 static void		 modepage_dump(struct cam_device *device, int cdb_len,
117 			    int dbd, int pc, int page, int subpage,
118 			    int task_attr, int retries, int timeout);
119 static void		 cleanup_editfile(void);
120 
121 
122 #define	returnerr(code) do {						\
123 	errno = code;							\
124 	return (-1);							\
125 } while (0)
126 
127 
128 #define	RTRIM(string) do {						\
129 	int _length;							\
130 	while (isspace(string[_length = strlen(string) - 1]))		\
131 		string[_length] = '\0';					\
132 } while (0)
133 
134 
135 static void
136 editentry_create(void *hook __unused, int letter, void *arg, int count,
137 		 char *name)
138 {
139 	struct editentry *newentry;	/* Buffer to hold new entry. */
140 
141 	/* Allocate memory for the new entry and a copy of the entry name. */
142 	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
143 	    (newentry->name = strdup(name)) == NULL)
144 		err(EX_OSERR, NULL);
145 
146 	/* Trim any trailing whitespace for the entry name. */
147 	RTRIM(newentry->name);
148 
149 	newentry->editable = (arg != NULL);
150 	newentry->type = letter;
151 	newentry->size = count;		/* Placeholder; not accurate. */
152 	newentry->value.svalue = NULL;
153 
154 	STAILQ_INSERT_TAIL(&editlist, newentry, link);
155 }
156 
157 static void
158 editentry_update(void *hook __unused, int letter, void *arg, int count,
159 		 char *name)
160 {
161 	struct editentry *dest;		/* Buffer to hold entry to update. */
162 
163 	dest = editentry_lookup(name);
164 	assert(dest != NULL);
165 
166 	dest->type = letter;
167 	dest->size = count;		/* We get the real size now. */
168 
169 	switch (dest->type) {
170 	case 'i':			/* Byte-sized integral type. */
171 	case 'b':			/* Bit-sized integral types. */
172 	case 't':
173 		dest->value.ivalue = (intptr_t)arg;
174 		break;
175 
176 	case 'c':			/* Character array. */
177 	case 'z':			/* Null-padded string. */
178 		editentry_set(name, (char *)arg, 0);
179 		break;
180 	default:
181 		; /* NOTREACHED */
182 	}
183 }
184 
185 static int
186 editentry_save(void *hook __unused, char *name)
187 {
188 	struct editentry *src;		/* Entry value to save. */
189 
190 	src = editentry_lookup(name);
191 	if (src == 0) {
192 		/*
193 		 * This happens if field does not fit into read page size.
194 		 * It also means that this field won't be written, so the
195 		 * returned value does not really matter.
196 		 */
197 		return (0);
198 	}
199 
200 	switch (src->type) {
201 	case 'i':			/* Byte-sized integral type. */
202 	case 'b':			/* Bit-sized integral types. */
203 	case 't':
204 		return (src->value.ivalue);
205 		/* NOTREACHED */
206 
207 	case 'c':			/* Character array. */
208 	case 'z':			/* Null-padded string. */
209 		return ((intptr_t)src->value.svalue);
210 		/* NOTREACHED */
211 
212 	default:
213 		; /* NOTREACHED */
214 	}
215 
216 	return (0);			/* This should never happen. */
217 }
218 
219 static struct editentry *
220 editentry_lookup(char *name)
221 {
222 	struct editentry *scan;
223 
224 	assert(name != NULL);
225 
226 	STAILQ_FOREACH(scan, &editlist, link) {
227 		if (strcasecmp(scan->name, name) == 0)
228 			return (scan);
229 	}
230 
231 	/* Not found during list traversal. */
232 	return (NULL);
233 }
234 
235 static int
236 editentry_set(char *name, char *newvalue, int editonly)
237 {
238 	struct editentry *dest;	/* Modepage entry to update. */
239 	char *cval;		/* Pointer to new string value. */
240 	char *convertend;	/* End-of-conversion pointer. */
241 	int ival;		/* New integral value. */
242 	int resolution;		/* Resolution in bits for integer conversion. */
243 
244 /*
245  * Macro to determine the maximum value of the given size for the current
246  * resolution.
247  * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
248  *     currently workaround it (even for int64's), so we have to kludge it.
249  */
250 #define	RESOLUTION_MAX(size) ((resolution * (size) == 32)? 		\
251 	INT_MAX: (1 << (resolution * (size))) - 1)
252 
253 	assert(newvalue != NULL);
254 	if (*newvalue == '\0')
255 		return (0);	/* Nothing to do. */
256 
257 	if ((dest = editentry_lookup(name)) == NULL)
258 		returnerr(ENOENT);
259 	if (!dest->editable && editonly)
260 		returnerr(EPERM);
261 
262 	switch (dest->type) {
263 	case 'i':		/* Byte-sized integral type. */
264 	case 'b':		/* Bit-sized integral types. */
265 	case 't':
266 		/* Convert the value string to an integer. */
267 		resolution = (dest->type == 'i')? 8: 1;
268 		ival = (int)strtol(newvalue, &convertend, 0);
269 		if (*convertend != '\0')
270 			returnerr(EINVAL);
271 		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
272 			int newival = (ival < 0)? 0: RESOLUTION_MAX(dest->size);
273 			warnx("value %d is out of range for entry %s; clipping "
274 			    "to %d", ival, name, newival);
275 			ival = newival;
276 		}
277 		if (dest->value.ivalue != ival)
278 			editlist_changed = 1;
279 		dest->value.ivalue = ival;
280 		break;
281 
282 	case 'c':		/* Character array. */
283 	case 'z':		/* Null-padded string. */
284 		if ((cval = malloc(dest->size + 1)) == NULL)
285 			err(EX_OSERR, NULL);
286 		bzero(cval, dest->size + 1);
287 		strncpy(cval, newvalue, dest->size);
288 		if (dest->type == 'z') {
289 			/* Convert trailing spaces to nulls. */
290 			char *convertend2;
291 
292 			for (convertend2 = cval + dest->size;
293 			    convertend2 >= cval; convertend2--) {
294 				if (*convertend2 == ' ')
295 					*convertend2 = '\0';
296 				else if (*convertend2 != '\0')
297 					break;
298 			}
299 		}
300 		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
301 			/* Nothing changed, free the newly allocated string. */
302 			free(cval);
303 			break;
304 		}
305 		if (dest->value.svalue != NULL) {
306 			/* Free the current string buffer. */
307 			free(dest->value.svalue);
308 			dest->value.svalue = NULL;
309 		}
310 		dest->value.svalue = cval;
311 		editlist_changed = 1;
312 		break;
313 
314 	default:
315 		; /* NOTREACHED */
316 	}
317 
318 	return (0);
319 #undef RESOLUTION_MAX
320 }
321 
322 static void
323 nameentry_create(int page, int subpage, char *name) {
324 	struct pagename *newentry;
325 
326 	if (page < 0 || subpage < 0 || name == NULL || name[0] == '\0')
327 		return;
328 
329 	/* Allocate memory for the new entry and a copy of the entry name. */
330 	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
331 	    (newentry->name = strdup(name)) == NULL)
332 		err(EX_OSERR, NULL);
333 
334 	/* Trim any trailing whitespace for the page name. */
335 	RTRIM(newentry->name);
336 
337 	newentry->page = page;
338 	newentry->subpage = subpage;
339 	SLIST_INSERT_HEAD(&namelist, newentry, link);
340 }
341 
342 static struct pagename *
343 nameentry_lookup(int page, int subpage) {
344 	struct pagename *scan;
345 
346 	SLIST_FOREACH(scan, &namelist, link) {
347 		if (page == scan->page && subpage == scan->subpage)
348 			return (scan);
349 	}
350 
351 	/* Not found during list traversal. */
352 	return (NULL);
353 }
354 
355 static int
356 load_format(const char *pagedb_path, int lpage, int lsubpage)
357 {
358 	FILE *pagedb;
359 	char str_page[MAX_PAGENUM_LEN];
360 	char *str_subpage;
361 	char str_pagename[MAX_PAGENAME_LEN];
362 	int page;
363 	int subpage;
364 	int depth;			/* Quoting depth. */
365 	int found;
366 	int lineno;
367 	enum { LOCATE, PAGENAME, PAGEDEF } state;
368 	int ch;
369 	char c;
370 
371 #define	SETSTATE_LOCATE do {						\
372 	str_page[0] = '\0';						\
373 	str_pagename[0] = '\0';						\
374 	page = -1;							\
375 	subpage = -1;							\
376 	state = LOCATE;							\
377 } while (0)
378 
379 #define	SETSTATE_PAGENAME do {						\
380 	str_pagename[0] = '\0';						\
381 	state = PAGENAME;						\
382 } while (0)
383 
384 #define	SETSTATE_PAGEDEF do {						\
385 	format[0] = '\0';						\
386 	state = PAGEDEF;						\
387 } while (0)
388 
389 #define	UPDATE_LINENO do {						\
390 	if (c == '\n')							\
391 		lineno++;						\
392 } while (0)
393 
394 #define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
395 
396 	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
397 		returnerr(ENOENT);
398 
399 	SLIST_INIT(&namelist);
400 
401 	c = '\0';
402 	depth = 0;
403 	lineno = 0;
404 	found = 0;
405 	SETSTATE_LOCATE;
406 	while ((ch = fgetc(pagedb)) != EOF) {
407 
408 		/* Keep a line count to make error messages more useful. */
409 		UPDATE_LINENO;
410 
411 		/* Skip over comments anywhere in the mode database. */
412 		if (ch == '#') {
413 			do {
414 				ch = fgetc(pagedb);
415 			} while (ch != '\n' && ch != EOF);
416 			UPDATE_LINENO;
417 			continue;
418 		}
419 		c = ch;
420 
421 		/* Strip out newline characters. */
422 		if (c == '\n')
423 			continue;
424 
425 		/* Keep track of the nesting depth for braces. */
426 		if (c == PAGEDEF_START)
427 			depth++;
428 		else if (c == PAGEDEF_END) {
429 			depth--;
430 			if (depth < 0) {
431 				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
432 				    lineno, "mismatched bracket");
433 			}
434 		}
435 
436 		switch (state) {
437 		case LOCATE:
438 			/*
439 			 * Locate the page the user is interested in, skipping
440 			 * all others.
441 			 */
442 			if (isspace(c)) {
443 				/* Ignore all whitespace between pages. */
444 				break;
445 			} else if (depth == 0 && c == PAGEENTRY_END) {
446 				/*
447 				 * A page entry terminator will reset page
448 				 * scanning (useful for assigning names to
449 				 * modes without providing a mode definition).
450 				 */
451 				/* Record the name of this page. */
452 				str_subpage = str_page;
453 				strsep(&str_subpage, ",");
454 				page = strtol(str_page, NULL, 0);
455 				if (str_subpage)
456 				    subpage = strtol(str_subpage, NULL, 0);
457 				else
458 				    subpage = 0;
459 				nameentry_create(page, subpage, str_pagename);
460 				SETSTATE_LOCATE;
461 			} else if (depth == 0 && c == PAGENAME_START) {
462 				SETSTATE_PAGENAME;
463 			} else if (c == PAGEDEF_START) {
464 				str_subpage = str_page;
465 				strsep(&str_subpage, ",");
466 				page = strtol(str_page, NULL, 0);
467 				if (str_subpage)
468 				    subpage = strtol(str_subpage, NULL, 0);
469 				else
470 				    subpage = 0;
471 				if (depth == 1) {
472 					/* Record the name of this page. */
473 					nameentry_create(page, subpage,
474 					    str_pagename);
475 					/*
476 					 * Only record the format if this is
477 					 * the page we are interested in.
478 					 */
479 					if (lpage == page &&
480 					    lsubpage == subpage && !found)
481 						SETSTATE_PAGEDEF;
482 				}
483 			} else if (c == PAGEDEF_END) {
484 				/* Reset the processor state. */
485 				SETSTATE_LOCATE;
486 			} else if (depth == 0 && ! BUFFERFULL(str_page)) {
487 				strncat(str_page, &c, 1);
488 			} else if (depth == 0) {
489 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
490 				    lineno, "page identifier exceeds",
491 				    sizeof(str_page) - 1, "characters");
492 			}
493 			break;
494 
495 		case PAGENAME:
496 			if (c == PAGENAME_END) {
497 				/*
498 				 * Return to LOCATE state without resetting the
499 				 * page number buffer.
500 				 */
501 				state = LOCATE;
502 			} else if (! BUFFERFULL(str_pagename)) {
503 				strncat(str_pagename, &c, 1);
504 			} else {
505 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
506 				    lineno, "page name exceeds",
507 				    sizeof(str_page) - 1, "characters");
508 			}
509 			break;
510 
511 		case PAGEDEF:
512 			/*
513 			 * Transfer the page definition into a format buffer
514 			 * suitable for use with CDB encoding/decoding routines.
515 			 */
516 			if (depth == 0) {
517 				found = 1;
518 				SETSTATE_LOCATE;
519 			} else if (! BUFFERFULL(format)) {
520 				strncat(format, &c, 1);
521 			} else {
522 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
523 				    lineno, "page definition exceeds",
524 				    sizeof(format) - 1, "characters");
525 			}
526 			break;
527 
528 		default:
529 			; /* NOTREACHED */
530 		}
531 
532 		/* Repeat processing loop with next character. */
533 	}
534 
535 	if (ferror(pagedb))
536 		err(EX_OSFILE, "%s", pagedb_path);
537 
538 	/* Close the SCSI page database. */
539 	fclose(pagedb);
540 
541 	if (!found)			/* Never found a matching page. */
542 		returnerr(ESRCH);
543 
544 	return (0);
545 }
546 
547 static void
548 editlist_populate(struct cam_device *device, int cdb_len, int dbd, int pc,
549     int page, int subpage, int task_attr, int retries, int timeout)
550 {
551 	u_int8_t data[MAX_DATA_SIZE];/* Buffer to hold sense data. */
552 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
553 	struct scsi_mode_page_header *mph;
554 	struct scsi_mode_page_header_sp *mphsp;
555 	size_t len;
556 
557 	STAILQ_INIT(&editlist);
558 
559 	/* Fetch changeable values; use to build initial editlist. */
560 	mode_sense(device, &cdb_len, dbd, 1, page, subpage, task_attr, retries,
561 		   timeout, data, sizeof(data));
562 
563 	if (cdb_len == 6) {
564 		struct scsi_mode_header_6 *mh =
565 		    (struct scsi_mode_header_6 *)data;
566 		mph = find_mode_page_6(mh);
567 	} else {
568 		struct scsi_mode_header_10 *mh =
569 		    (struct scsi_mode_header_10 *)data;
570 		mph = find_mode_page_10(mh);
571 	}
572 	if ((mph->page_code & SMPH_SPF) == 0) {
573 		mode_pars = (uint8_t *)(mph + 1);
574 		len = mph->page_length;
575 	} else {
576 		mphsp = (struct scsi_mode_page_header_sp *)mph;
577 		mode_pars = (uint8_t *)(mphsp + 1);
578 		len = scsi_2btoul(mphsp->page_length);
579 	}
580 	len = MIN(len, sizeof(data) - (mode_pars - data));
581 
582 	/* Decode the value data, creating edit_entries for each value. */
583 	buff_decode_visit(mode_pars, len, format, editentry_create, 0);
584 
585 	/* Fetch the current/saved values; use to set editentry values. */
586 	mode_sense(device, &cdb_len, dbd, pc, page, subpage, task_attr,
587 	    retries, timeout, data, sizeof(data));
588 	buff_decode_visit(mode_pars, len, format, editentry_update, 0);
589 }
590 
591 static void
592 editlist_save(struct cam_device *device, int cdb_len, int dbd, int pc,
593     int page, int subpage, int task_attr, int retries, int timeout)
594 {
595 	u_int8_t data[MAX_DATA_SIZE];/* Buffer to hold sense data. */
596 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
597 	struct scsi_mode_page_header *mph;
598 	struct scsi_mode_page_header_sp *mphsp;
599 	size_t len, hlen, mphlen;
600 
601 	/* Make sure that something changed before continuing. */
602 	if (! editlist_changed)
603 		return;
604 
605 	/* Preload the CDB buffer with the current mode page data. */
606 	mode_sense(device, &cdb_len, dbd, pc, page, subpage, task_attr,
607 	    retries, timeout, data, sizeof(data));
608 
609 	/* Initial headers & offsets. */
610 	/*
611 	 * Tape drives include write protect (WP), Buffered Mode and Speed
612 	 * settings in the device-specific parameter.  Clearing this
613 	 * parameter on a mode select can have the effect of turning off
614 	 * write protect or buffered mode, or changing the speed setting of
615 	 * the tape drive.
616 	 *
617 	 * Disks report DPO/FUA support via the device specific parameter
618 	 * for MODE SENSE, but the bit is reserved for MODE SELECT.  So we
619 	 * clear this for disks (and other non-tape devices) to avoid
620 	 * potential errors from the target device.
621 	 */
622 	if (cdb_len == 6) {
623 		struct scsi_mode_header_6 *mh =
624 		    (struct scsi_mode_header_6 *)data;
625 		hlen = sizeof(*mh);
626 		/* Eliminate block descriptors. */
627 		if (mh->blk_desc_len > 0) {
628 			bcopy(find_mode_page_6(mh), mh + 1,
629 			    mh->data_length + 1 - hlen -
630 			    mh->blk_desc_len);
631 			mh->blk_desc_len = 0;
632 		}
633 		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
634 		if (device->pd_type != T_SEQUENTIAL)
635 			mh->dev_spec = 0;	/* See comment above */
636 		mph = find_mode_page_6(mh);
637 	} else {
638 		struct scsi_mode_header_10 *mh =
639 		    (struct scsi_mode_header_10 *)data;
640 		hlen = sizeof(*mh);
641 		/* Eliminate block descriptors. */
642 		if (scsi_2btoul(mh->blk_desc_len) > 0) {
643 			bcopy(find_mode_page_10(mh), mh + 1,
644 			    scsi_2btoul(mh->data_length) + 1 - hlen -
645 			    scsi_2btoul(mh->blk_desc_len));
646 			scsi_ulto2b(0, mh->blk_desc_len);
647 		}
648 		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
649 		if (device->pd_type != T_SEQUENTIAL)
650 			mh->dev_spec = 0;	/* See comment above */
651 		mph = find_mode_page_10(mh);
652 	}
653 	if ((mph->page_code & SMPH_SPF) == 0) {
654 		mphlen = sizeof(*mph);
655 		mode_pars = (uint8_t *)(mph + 1);
656 		len = mph->page_length;
657 	} else {
658 		mphsp = (struct scsi_mode_page_header_sp *)mph;
659 		mphlen = sizeof(*mphsp);
660 		mode_pars = (uint8_t *)(mphsp + 1);
661 		len = scsi_2btoul(mphsp->page_length);
662 	}
663 	len = MIN(len, sizeof(data) - (mode_pars - data));
664 
665 	/* Encode the value data to be passed back to the device. */
666 	buff_encode_visit(mode_pars, len, format, editentry_save, 0);
667 
668 	mph->page_code &= ~SMPH_PS;	/* Reserved for MODE SELECT command. */
669 
670 	/*
671 	 * Write the changes back to the device. If the user editted control
672 	 * page 3 (saved values) then request the changes be permanently
673 	 * recorded.
674 	 */
675 	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
676 	    task_attr, retries, timeout, data, hlen + mphlen + len);
677 }
678 
679 static int
680 modepage_write(FILE *file, int editonly)
681 {
682 	struct editentry *scan;
683 	int written = 0;
684 
685 	STAILQ_FOREACH(scan, &editlist, link) {
686 		if (scan->editable || !editonly) {
687 			written++;
688 			if (scan->type == 'c' || scan->type == 'z') {
689 				fprintf(file, "%s:  %s\n", scan->name,
690 				    scan->value.svalue);
691 			} else {
692 				fprintf(file, "%s:  %d\n", scan->name,
693 				    scan->value.ivalue);
694 			}
695 		}
696 	}
697 	return (written);
698 }
699 
700 static int
701 modepage_read(FILE *file)
702 {
703 	char *buffer;			/* Pointer to dynamic line buffer.  */
704 	char *line;			/* Pointer to static fgetln buffer. */
705 	char *name;			/* Name portion of the line buffer. */
706 	char *value;			/* Value portion of line buffer.    */
707 	size_t length;			/* Length of static fgetln buffer.  */
708 
709 #define	ABORT_READ(message, param) do {					\
710 	warnx(message, param);						\
711 	free(buffer);							\
712 	returnerr(EAGAIN);						\
713 } while (0)
714 
715 	while ((line = fgetln(file, &length)) != NULL) {
716 		/* Trim trailing whitespace (including optional newline). */
717 		while (length > 0 && isspace(line[length - 1]))
718 			length--;
719 
720 	    	/* Allocate a buffer to hold the line + terminating null. */
721 	    	if ((buffer = malloc(length + 1)) == NULL)
722 			err(EX_OSERR, NULL);
723 		memcpy(buffer, line, length);
724 		buffer[length] = '\0';
725 
726 		/* Strip out comments. */
727 		if ((value = strchr(buffer, '#')) != NULL)
728 			*value = '\0';
729 
730 		/* The name is first in the buffer. Trim whitespace.*/
731 		name = buffer;
732 		RTRIM(name);
733 		while (isspace(*name))
734 			name++;
735 
736 		/* Skip empty lines. */
737 		if (strlen(name) == 0)
738 			continue;
739 
740 		/* The name ends at the colon; the value starts there. */
741 		if ((value = strrchr(buffer, ':')) == NULL)
742 			ABORT_READ("no value associated with %s", name);
743 		*value = '\0';			/* Null-terminate name. */
744 		value++;			/* Value starts afterwards. */
745 
746 		/* Trim leading and trailing whitespace. */
747 		RTRIM(value);
748 		while (isspace(*value))
749 			value++;
750 
751 		/* Make sure there is a value left. */
752 		if (strlen(value) == 0)
753 			ABORT_READ("no value associated with %s", name);
754 
755 		/* Update our in-memory copy of the modepage entry value. */
756 		if (editentry_set(name, value, 1) != 0) {
757 			if (errno == ENOENT) {
758 				/* No entry by the name. */
759 				ABORT_READ("no such modepage entry \"%s\"",
760 				    name);
761 			} else if (errno == EINVAL) {
762 				/* Invalid value. */
763 				ABORT_READ("Invalid value for entry \"%s\"",
764 				    name);
765 			} else if (errno == ERANGE) {
766 				/* Value out of range for entry type. */
767 				ABORT_READ("value out of range for %s", name);
768 			} else if (errno == EPERM) {
769 				/* Entry is not editable; not fatal. */
770 				warnx("modepage entry \"%s\" is read-only; "
771 				    "skipping.", name);
772 			}
773 		}
774 
775 		free(buffer);
776 	}
777 	return (ferror(file)? -1: 0);
778 
779 #undef ABORT_READ
780 }
781 
782 static void
783 modepage_edit(void)
784 {
785 	const char *editor;
786 	char *commandline;
787 	int fd;
788 	int written;
789 
790 	if (!isatty(fileno(stdin))) {
791 		/* Not a tty, read changes from stdin. */
792 		modepage_read(stdin);
793 		return;
794 	}
795 
796 	/* Lookup editor to invoke. */
797 	if ((editor = getenv("EDITOR")) == NULL)
798 		editor = DEFAULT_EDITOR;
799 
800 	/* Create temp file for editor to modify. */
801 	if ((fd = mkstemp(edit_path)) == -1)
802 		errx(EX_CANTCREAT, "mkstemp failed");
803 
804 	atexit(cleanup_editfile);
805 
806 	if ((edit_file = fdopen(fd, "w")) == NULL)
807 		err(EX_NOINPUT, "%s", edit_path);
808 
809 	written = modepage_write(edit_file, 1);
810 
811 	fclose(edit_file);
812 	edit_file = NULL;
813 
814 	if (written == 0) {
815 		warnx("no editable entries");
816 		cleanup_editfile();
817 		return;
818 	}
819 
820 	/*
821 	 * Allocate memory to hold the command line (the 2 extra characters
822 	 * are to hold the argument separator (a space), and the terminating
823 	 * null character.
824 	 */
825 	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
826 	if (commandline == NULL)
827 		err(EX_OSERR, NULL);
828 	sprintf(commandline, "%s %s", editor, edit_path);
829 
830 	/* Invoke the editor on the temp file. */
831 	if (system(commandline) == -1)
832 		err(EX_UNAVAILABLE, "could not invoke %s", editor);
833 	free(commandline);
834 
835 	if ((edit_file = fopen(edit_path, "r")) == NULL)
836 		err(EX_NOINPUT, "%s", edit_path);
837 
838 	/* Read any changes made to the temp file. */
839 	modepage_read(edit_file);
840 
841 	cleanup_editfile();
842 }
843 
844 static void
845 modepage_dump(struct cam_device *device, int cdb_len, int dbd, int pc,
846 	      int page, int subpage, int task_attr, int retries, int timeout)
847 {
848 	u_int8_t data[MAX_DATA_SIZE];/* Buffer to hold sense data. */
849 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
850 	struct scsi_mode_page_header *mph;
851 	struct scsi_mode_page_header_sp *mphsp;
852 	size_t indx, len;
853 
854 	mode_sense(device, &cdb_len, dbd, pc, page, subpage, task_attr,
855 	    retries, timeout, data, sizeof(data));
856 
857 	if (cdb_len == 6) {
858 		struct scsi_mode_header_6 *mh =
859 		    (struct scsi_mode_header_6 *)data;
860 		mph = find_mode_page_6(mh);
861 	} else {
862 		struct scsi_mode_header_10 *mh =
863 		    (struct scsi_mode_header_10 *)data;
864 		mph = find_mode_page_10(mh);
865 	}
866 	if ((mph->page_code & SMPH_SPF) == 0) {
867 		mode_pars = (uint8_t *)(mph + 1);
868 		len = mph->page_length;
869 	} else {
870 		mphsp = (struct scsi_mode_page_header_sp *)mph;
871 		mode_pars = (uint8_t *)(mphsp + 1);
872 		len = scsi_2btoul(mphsp->page_length);
873 	}
874 	len = MIN(len, sizeof(data) - (mode_pars - data));
875 
876 	/* Print the raw mode page data with newlines each 8 bytes. */
877 	for (indx = 0; indx < len; indx++) {
878 		printf("%02x%c",mode_pars[indx],
879 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
880 	}
881 	putchar('\n');
882 }
883 
884 static void
885 cleanup_editfile(void)
886 {
887 	if (edit_file == NULL)
888 		return;
889 	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
890 		warn("%s", edit_path);
891 	edit_file = NULL;
892 }
893 
894 void
895 mode_edit(struct cam_device *device, int cdb_len, int dbd, int pc, int page,
896 	  int subpage, int edit, int binary, int task_attr, int retry_count,
897 	  int timeout)
898 {
899 	const char *pagedb_path;	/* Path to modepage database. */
900 
901 	if (edit && binary)
902 		errx(EX_USAGE, "cannot edit in binary mode.");
903 
904 	if (! binary) {
905 		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
906 			pagedb_path = DEFAULT_SCSI_MODE_DB;
907 
908 		if (load_format(pagedb_path, page, subpage) != 0 &&
909 		    (edit || verbose)) {
910 			if (errno == ENOENT) {
911 				/* Modepage database file not found. */
912 				warn("cannot open modepage database \"%s\"",
913 				    pagedb_path);
914 			} else if (errno == ESRCH) {
915 				/* Modepage entry not found in database. */
916 				warnx("modepage 0x%02x,0x%02x not found in "
917 				    "database \"%s\"", page, subpage,
918 				    pagedb_path);
919 			}
920 			/* We can recover in display mode, otherwise we exit. */
921 			if (!edit) {
922 				warnx("reverting to binary display only");
923 				binary = 1;
924 			} else
925 				exit(EX_OSFILE);
926 		}
927 
928 		editlist_populate(device, cdb_len, dbd, pc, page, subpage,
929 		    task_attr, retry_count, timeout);
930 	}
931 
932 	if (edit) {
933 		if (pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
934 		    pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
935 			errx(EX_USAGE, "it only makes sense to edit page 0 "
936 			    "(current) or page 3 (saved values)");
937 		modepage_edit();
938 		editlist_save(device, cdb_len, dbd, pc, page, subpage,
939 		    task_attr, retry_count, timeout);
940 	} else if (binary || STAILQ_EMPTY(&editlist)) {
941 		/* Display without formatting information. */
942 		modepage_dump(device, cdb_len, dbd, pc, page, subpage,
943 		    task_attr, retry_count, timeout);
944 	} else {
945 		/* Display with format. */
946 		modepage_write(stdout, 0);
947 	}
948 }
949 
950 void
951 mode_list(struct cam_device *device, int cdb_len, int dbd, int pc, int subpages,
952 	  int task_attr, int retry_count, int timeout)
953 {
954 	u_int8_t data[MAX_DATA_SIZE];/* Buffer to hold sense data. */
955 	struct scsi_mode_page_header *mph;
956 	struct scsi_mode_page_header_sp *mphsp;
957 	struct pagename *nameentry;
958 	const char *pagedb_path;
959 	int len, off, page, subpage;
960 
961 	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
962 		pagedb_path = DEFAULT_SCSI_MODE_DB;
963 
964 	if (load_format(pagedb_path, 0, 0) != 0 && verbose && errno == ENOENT) {
965 		/* Modepage database file not found. */
966 		warn("cannot open modepage database \"%s\"", pagedb_path);
967 	}
968 
969 	/* Build the list of all mode pages by querying the "all pages" page. */
970 	mode_sense(device, &cdb_len, dbd, pc, SMS_ALL_PAGES_PAGE,
971 	    subpages ? SMS_SUBPAGE_ALL : 0,
972 	    task_attr, retry_count, timeout, data, sizeof(data));
973 
974 	/* Skip block descriptors. */
975 	if (cdb_len == 6) {
976 		struct scsi_mode_header_6 *mh =
977 		    (struct scsi_mode_header_6 *)data;
978 		len = mh->data_length;
979 		off = sizeof(*mh) + mh->blk_desc_len;
980 	} else {
981 		struct scsi_mode_header_10 *mh =
982 		    (struct scsi_mode_header_10 *)data;
983 		len = scsi_2btoul(mh->data_length);
984 		off = sizeof(*mh) + scsi_2btoul(mh->blk_desc_len);
985 	}
986 	/* Iterate through the pages in the reply. */
987 	while (off < len) {
988 		/* Locate the next mode page header. */
989 		mph = (struct scsi_mode_page_header *)(data + off);
990 
991 		if ((mph->page_code & SMPH_SPF) == 0) {
992 			page = mph->page_code & SMS_PAGE_CODE;
993 			subpage = 0;
994 			off += sizeof(*mph) + mph->page_length;
995 		} else {
996 			mphsp = (struct scsi_mode_page_header_sp *)mph;
997 			page = mphsp->page_code & SMS_PAGE_CODE;
998 			subpage = mphsp->subpage;
999 			off += sizeof(*mphsp) + scsi_2btoul(mphsp->page_length);
1000 		}
1001 
1002 		nameentry = nameentry_lookup(page, subpage);
1003 		if (subpage == 0) {
1004 			printf("0x%02x\t%s\n", page,
1005 			    nameentry ? nameentry->name : "");
1006 		} else {
1007 			printf("0x%02x,0x%02x\t%s\n", page, subpage,
1008 			    nameentry ? nameentry->name : "");
1009 		}
1010 	}
1011 }
1012