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