xref: /dragonfly/sbin/camcontrol/modeedit.c (revision d4ef6694)
1 /*-
2  * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
3  * Derived from work done by Julian Elischer <julian@tfs.com,
4  * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer,
12  *    without modification, immediately at the beginning of the file.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD: src/sbin/camcontrol/modeedit.c,v 1.5.2.2 2003/01/08 17:55:02 njl Exp $
29  */
30 
31 #include <sys/queue.h>
32 #include <sys/types.h>
33 
34 #include <assert.h>
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <stdio.h>
41 #include <sysexits.h>
42 #include <unistd.h>
43 
44 #include <cam/scsi/scsi_all.h>
45 #include <cam/cam.h>
46 #include <cam/cam_ccb.h>
47 #include <camlib.h>
48 #include "camcontrol.h"
49 
50 int verbose = 0;
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_COMMAND_SIZE	255	/* Mode/Log sense data buffer size. */
63 #define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
64 
65 
66 /* Macros for working with mode pages. */
67 #define	MODE_PAGE_HEADER(mh)						\
68 	(struct scsi_mode_page_header *)find_mode_page_6(mh)
69 
70 #define	MODE_PAGE_DATA(mph)						\
71 	(u_int8_t *)(mph) + sizeof(struct scsi_mode_page_header)
72 
73 
74 struct editentry {
75 	STAILQ_ENTRY(editentry) link;
76 	char	*name;
77 	char	type;
78 	int	editable;
79 	int	size;
80 	union {
81 		int	ivalue;
82 		char	*svalue;
83 	} value;
84 };
85 STAILQ_HEAD(, editentry) editlist;	/* List of page entries. */
86 int editlist_changed = 0;		/* Whether any entries were changed. */
87 
88 struct pagename {
89 	SLIST_ENTRY(pagename) link;
90 	int pagenum;
91 	char *name;
92 };
93 SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
94 
95 static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
96 
97 static FILE *edit_file = NULL;		/* File handle for edit file. */
98 static char edit_path[] = "/tmp/camXXXXXX";
99 
100 
101 /* Function prototypes. */
102 static void		 editentry_create(void *, int, void *, int, char *);
103 static void		 editentry_update(void *, int, void *, int, char *);
104 static int		 editentry_save(void *, char *);
105 static struct editentry	*editentry_lookup(char *);
106 static int		 editentry_set(char *, char *, int);
107 static void		 editlist_populate(struct cam_device *, int, int, int,
108 					   int, int);
109 static void		 editlist_save(struct cam_device *, int, int, int, int,
110 				       int);
111 static void		 nameentry_create(int, char *);
112 static struct pagename	*nameentry_lookup(int);
113 static int		 load_format(const char *, int);
114 static int		 modepage_write(FILE *, int);
115 static int		 modepage_read(FILE *);
116 static void		 modepage_edit(void);
117 static void		 modepage_dump(struct cam_device *, int, int, int, int,
118 				       int);
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 	assert(src != NULL);
192 
193 	switch (src->type) {
194 	case 'i':			/* Byte-sized integral type. */
195 	case 'b':			/* Bit-sized integral types. */
196 	case 't':
197 		return (src->value.ivalue);
198 		/* NOTREACHED */
199 
200 	case 'c':			/* Character array. */
201 	case 'z':			/* Null-padded string. */
202 		return ((intptr_t)src->value.svalue);
203 		/* NOTREACHED */
204 
205 	default:
206 		; /* NOTREACHED */
207 	}
208 
209 	return (0);			/* This should never happen. */
210 }
211 
212 static struct editentry *
213 editentry_lookup(char *name)
214 {
215 	struct editentry *scan;
216 
217 	assert(name != NULL);
218 
219 	STAILQ_FOREACH(scan, &editlist, link) {
220 		if (strcasecmp(scan->name, name) == 0)
221 			return (scan);
222 	}
223 
224 	/* Not found during list traversal. */
225 	return (NULL);
226 }
227 
228 static int
229 editentry_set(char *name, char *newvalue, int editonly)
230 {
231 	struct editentry *dest;	/* Modepage entry to update. */
232 	char *cval;		/* Pointer to new string value. */
233 	char *convertend;	/* End-of-conversion pointer. */
234 	int ival;		/* New integral value. */
235 	int resolution;		/* Resolution in bits for integer conversion. */
236 	int resolution_max;	/* Maximum resolution for modepage's size. */
237 
238 	assert(newvalue != NULL);
239 	if (*newvalue == '\0')
240 		return (0);	/* Nothing to do. */
241 
242 	if ((dest = editentry_lookup(name)) == NULL)
243 		returnerr(ENOENT);
244 	if (!dest->editable && editonly)
245 		returnerr(EPERM);
246 
247 	switch (dest->type) {
248 	case 'i':		/* Byte-sized integral type. */
249 	case 'b':		/* Bit-sized integral types. */
250 	case 't':
251 		/* Convert the value string to an integer. */
252 		resolution = (dest->type == 'i')? 8: 1;
253 		ival = (int)strtol(newvalue, &convertend, 0);
254 		if (*convertend != '\0')
255 			returnerr(EINVAL);
256 
257 		/*
258 		 * Determine the maximum value of the given size for the
259 		 * current resolution.
260 		 * XXX Lovely x86's optimize out the case of shifting by 32,
261 		 * and gcc doesn't currently workaround it (even for int64's),
262 		 * so we have to kludge it.
263 		 */
264 		if (resolution * dest->size == 32)
265 			resolution_max = 0xffffffff;
266 		else
267 			resolution_max = (1 << (resolution * dest->size)) - 1;
268 
269 		if (ival > resolution_max || ival < 0) {
270 			int newival = (ival < 0) ? 0 : resolution_max;
271 			warnx("value %d is out of range for entry %s; clipping "
272 			    "to %d", ival, name, newival);
273 			ival = newival;
274 		}
275 		if (dest->value.ivalue != ival)
276 			editlist_changed = 1;
277 		dest->value.ivalue = ival;
278 		break;
279 
280 	case 'c':		/* Character array. */
281 	case 'z':		/* Null-padded string. */
282 		if ((cval = malloc(dest->size + 1)) == NULL)
283 			err(EX_OSERR, NULL);
284 		bzero(cval, dest->size + 1);
285 		strncpy(cval, newvalue, dest->size);
286 		if (dest->type == 'z') {
287 			/* Convert trailing spaces to nulls. */
288 			char *conv_end;
289 
290 			for (conv_end = cval + dest->size;
291 			    conv_end >= cval; conv_end--) {
292 				if (*conv_end == ' ')
293 					*conv_end = '\0';
294 				else if (*conv_end != '\0')
295 					break;
296 			}
297 		}
298 		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
299 			/* Nothing changed, free the newly allocated string. */
300 			free(cval);
301 			break;
302 		}
303 		if (dest->value.svalue != NULL) {
304 			/* Free the current string buffer. */
305 			free(dest->value.svalue);
306 			dest->value.svalue = NULL;
307 		}
308 		dest->value.svalue = cval;
309 		editlist_changed = 1;
310 		break;
311 
312 	default:
313 		; /* NOTREACHED */
314 	}
315 
316 	return (0);
317 }
318 
319 static void
320 nameentry_create(int pagenum, char *name) {
321 	struct pagename *newentry;
322 
323 	if (pagenum < 0 || name == NULL || name[0] == '\0')
324 		return;
325 
326 	/* Allocate memory for the new entry and a copy of the entry name. */
327 	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
328 	    (newentry->name = strdup(name)) == NULL)
329 		err(EX_OSERR, NULL);
330 
331 	/* Trim any trailing whitespace for the page name. */
332 	RTRIM(newentry->name);
333 
334 	newentry->pagenum = pagenum;
335 	SLIST_INSERT_HEAD(&namelist, newentry, link);
336 }
337 
338 static struct pagename *
339 nameentry_lookup(int pagenum) {
340 	struct pagename *scan;
341 
342 	SLIST_FOREACH(scan, &namelist, link) {
343 		if (pagenum == scan->pagenum)
344 			return (scan);
345 	}
346 
347 	/* Not found during list traversal. */
348 	return (NULL);
349 }
350 
351 static int
352 load_format(const char *pagedb_path, int page)
353 {
354 	FILE *pagedb;
355 	char str_pagenum[MAX_PAGENUM_LEN];
356 	char str_pagename[MAX_PAGENAME_LEN];
357 	int pagenum;
358 	int depth;			/* Quoting depth. */
359 	int found;
360 	int lineno;
361 	enum { LOCATE, PAGENAME, PAGEDEF } state;
362 	char c;
363 
364 #define	SETSTATE_LOCATE do {						\
365 	str_pagenum[0] = '\0';						\
366 	str_pagename[0] = '\0';						\
367 	pagenum = -1;							\
368 	state = LOCATE;							\
369 } while (0)
370 
371 #define	SETSTATE_PAGENAME do {						\
372 	str_pagename[0] = '\0';						\
373 	state = PAGENAME;						\
374 } while (0)
375 
376 #define	SETSTATE_PAGEDEF do {						\
377 	format[0] = '\0';						\
378 	state = PAGEDEF;						\
379 } while (0)
380 
381 #define	UPDATE_LINENO do {						\
382 	if (c == '\n')							\
383 		lineno++;						\
384 } while (0)
385 
386 #define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
387 
388 	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
389 		returnerr(ENOENT);
390 
391 	SLIST_INIT(&namelist);
392 
393 	depth = 0;
394 	lineno = 0;
395 	found = 0;
396 	SETSTATE_LOCATE;
397 	while ((c = fgetc(pagedb)) != EOF) {
398 
399 		/* Keep a line count to make error messages more useful. */
400 		UPDATE_LINENO;
401 
402 		/* Skip over comments anywhere in the mode database. */
403 		if (c == '#') {
404 			do {
405 				c = fgetc(pagedb);
406 			} while (c != '\n' && c != EOF);
407 			UPDATE_LINENO;
408 			continue;
409 		}
410 
411 		/* Strip out newline characters. */
412 		if (c == '\n')
413 			continue;
414 
415 		/* Keep track of the nesting depth for braces. */
416 		if (c == PAGEDEF_START)
417 			depth++;
418 		else if (c == PAGEDEF_END) {
419 			depth--;
420 			if (depth < 0) {
421 				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
422 				    lineno, "mismatched bracket");
423 			}
424 		}
425 
426 		switch (state) {
427 		case LOCATE:
428 			/*
429 			 * Locate the page the user is interested in, skipping
430 			 * all others.
431 			 */
432 			if (isspace(c)) {
433 				/* Ignore all whitespace between pages. */
434 				break;
435 			} else if (depth == 0 && c == PAGEENTRY_END) {
436 				/*
437 				 * A page entry terminator will reset page
438 				 * scanning (useful for assigning names to
439 				 * modes without providing a mode definition).
440 				 */
441 				/* Record the name of this page. */
442 				pagenum = strtol(str_pagenum, NULL, 0);
443 				nameentry_create(pagenum, str_pagename);
444 				SETSTATE_LOCATE;
445 			} else if (depth == 0 && c == PAGENAME_START) {
446 				SETSTATE_PAGENAME;
447 			} else if (c == PAGEDEF_START) {
448 				pagenum = strtol(str_pagenum, NULL, 0);
449 				if (depth == 1) {
450 					/* Record the name of this page. */
451 					nameentry_create(pagenum, str_pagename);
452 					/*
453 					 * Only record the format if this is
454 					 * the page we are interested in.
455 					 */
456 					if (page == pagenum && !found)
457 						SETSTATE_PAGEDEF;
458 				}
459 			} else if (c == PAGEDEF_END) {
460 				/* Reset the processor state. */
461 				SETSTATE_LOCATE;
462 			} else if (depth == 0 && ! BUFFERFULL(str_pagenum)) {
463 				strncat(str_pagenum, &c, 1);
464 			} else if (depth == 0) {
465 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
466 				    lineno, "page identifier exceeds",
467 				    sizeof(str_pagenum) - 1, "characters");
468 			}
469 			break;
470 
471 		case PAGENAME:
472 			if (c == PAGENAME_END) {
473 				/*
474 				 * Return to LOCATE state without resetting the
475 				 * page number buffer.
476 				 */
477 				state = LOCATE;
478 			} else if (! BUFFERFULL(str_pagename)) {
479 				strncat(str_pagename, &c, 1);
480 			} else {
481 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
482 				    lineno, "page name exceeds",
483 				    sizeof(str_pagenum) - 1, "characters");
484 			}
485 			break;
486 
487 		case PAGEDEF:
488 			/*
489 			 * Transfer the page definition into a format buffer
490 			 * suitable for use with CDB encoding/decoding routines.
491 			 */
492 			if (depth == 0) {
493 				found = 1;
494 				SETSTATE_LOCATE;
495 			} else if (! BUFFERFULL(format)) {
496 				strncat(format, &c, 1);
497 			} else {
498 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
499 				    lineno, "page definition exceeds",
500 				    sizeof(format) - 1, "characters");
501 			}
502 			break;
503 
504 		default:
505 			; /* NOTREACHED */
506 		}
507 
508 		/* Repeat processing loop with next character. */
509 	}
510 
511 	if (ferror(pagedb))
512 		err(EX_OSFILE, "%s", pagedb_path);
513 
514 	/* Close the SCSI page database. */
515 	fclose(pagedb);
516 
517 	if (!found)			/* Never found a matching page. */
518 		returnerr(ESRCH);
519 
520 	return (0);
521 }
522 
523 static void
524 editlist_populate(struct cam_device *device, int modepage, int page_control,
525 		  int dbd, int retries, int timeout)
526 {
527 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
528 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
529 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
530 	struct scsi_mode_page_header *mph;
531 
532 	STAILQ_INIT(&editlist);
533 
534 	/* Fetch changeable values; use to build initial editlist. */
535 	mode_sense(device, modepage, 1, dbd, retries, timeout, data,
536 		   sizeof(data));
537 
538 	mh = (struct scsi_mode_header_6 *)data;
539 	mph = MODE_PAGE_HEADER(mh);
540 	mode_pars = MODE_PAGE_DATA(mph);
541 
542 	/* Decode the value data, creating edit_entries for each value. */
543 	buff_decode_visit(mode_pars, mh->data_length, format,
544 	    editentry_create, 0);
545 
546 	/* Fetch the current/saved values; use to set editentry values. */
547 	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
548 		   sizeof(data));
549 	buff_decode_visit(mode_pars, mh->data_length, format,
550 	    editentry_update, 0);
551 }
552 
553 static void
554 editlist_save(struct cam_device *device, int modepage, int page_control,
555 	      int dbd, int retries, int timeout)
556 {
557 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
558 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
559 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
560 	struct scsi_mode_page_header *mph;
561 
562 	/* Make sure that something changed before continuing. */
563 	if (! editlist_changed)
564 		return;
565 
566 	/*
567 	 * Preload the CDB buffer with the current mode page data.
568 	 * XXX If buff_encode_visit would return the number of bytes encoded
569 	 *     we *should* use that to build a header from scratch. As it is
570 	 *     now, we need mode_sense to find out the page length.
571 	 */
572 	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
573 		   sizeof(data));
574 
575 	/* Initial headers & offsets. */
576 	mh = (struct scsi_mode_header_6 *)data;
577 	mph = MODE_PAGE_HEADER(mh);
578 	mode_pars = MODE_PAGE_DATA(mph);
579 
580 	/* Encode the value data to be passed back to the device. */
581 	buff_encode_visit(mode_pars, mh->data_length, format,
582 	    editentry_save, 0);
583 
584 	/* Eliminate block descriptors. */
585 	bcopy(mph, ((u_int8_t *)mh) + sizeof(*mh),
586 	    sizeof(*mph) + mph->page_length);
587 
588 	/* Recalculate headers & offsets. */
589 	mh->blk_desc_len = 0;		/* No block descriptors. */
590 	mh->dev_spec = 0;		/* Clear device-specific parameters. */
591 	mph = MODE_PAGE_HEADER(mh);
592 	mode_pars = MODE_PAGE_DATA(mph);
593 
594 	mph->page_code &= SMS_PAGE_CODE;/* Isolate just the page code. */
595 	mh->data_length = 0;		/* Reserved for MODE SELECT command. */
596 
597 	/*
598 	 * Write the changes back to the device. If the user editted control
599 	 * page 3 (saved values) then request the changes be permanently
600 	 * recorded.
601 	 */
602 	mode_select(device,
603 	    (page_control << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
604 	    retries, timeout, (u_int8_t *)mh,
605 	    sizeof(*mh) + mh->blk_desc_len + sizeof(*mph) + mph->page_length);
606 }
607 
608 static int
609 modepage_write(FILE *file, int editonly)
610 {
611 	struct editentry *scan;
612 	int written = 0;
613 
614 	STAILQ_FOREACH(scan, &editlist, link) {
615 		if (scan->editable || !editonly) {
616 			written++;
617 			if (scan->type == 'c' || scan->type == 'z') {
618 				fprintf(file, "%s:  %s\n", scan->name,
619 				    scan->value.svalue);
620 			} else {
621 				fprintf(file, "%s:  %d\n", scan->name,
622 				    scan->value.ivalue);
623 			}
624 		}
625 	}
626 	return (written);
627 }
628 
629 static int
630 modepage_read(FILE *file)
631 {
632 	char *buffer;			/* Pointer to dynamic line buffer.  */
633 	char *line;			/* Pointer to static fgetln buffer. */
634 	char *name;			/* Name portion of the line buffer. */
635 	char *value;			/* Value portion of line buffer.    */
636 	size_t length;			/* Length of static fgetln buffer.  */
637 
638 #define	ABORT_READ(message, param) do {					\
639 	warnx(message, param);						\
640 	free(buffer);							\
641 	returnerr(EAGAIN);						\
642 } while (0)
643 
644 	while ((line = fgetln(file, &length)) != NULL) {
645 		/* Trim trailing whitespace (including optional newline). */
646 		while (length > 0 && isspace(line[length - 1]))
647 			length--;
648 
649 	    	/* Allocate a buffer to hold the line + terminating null. */
650 	    	if ((buffer = malloc(length + 1)) == NULL)
651 			err(EX_OSERR, NULL);
652 		memcpy(buffer, line, length);
653 		buffer[length] = '\0';
654 
655 		/* Strip out comments. */
656 		if ((value = strchr(buffer, '#')) != NULL)
657 			*value = '\0';
658 
659 		/* The name is first in the buffer. Trim whitespace.*/
660 		name = buffer;
661 		RTRIM(name);
662 		while (isspace(*name))
663 			name++;
664 
665 		/* Skip empty lines. */
666 		if (strlen(name) == 0)
667 			continue;
668 
669 		/* The name ends at the colon; the value starts there. */
670 		if ((value = strrchr(buffer, ':')) == NULL)
671 			ABORT_READ("no value associated with %s", name);
672 		*value = '\0';			/* Null-terminate name. */
673 		value++;			/* Value starts afterwards. */
674 
675 		/* Trim leading and trailing whitespace. */
676 		RTRIM(value);
677 		while (isspace(*value))
678 			value++;
679 
680 		/* Make sure there is a value left. */
681 		if (strlen(value) == 0)
682 			ABORT_READ("no value associated with %s", name);
683 
684 		/* Update our in-memory copy of the modepage entry value. */
685 		if (editentry_set(name, value, 1) != 0) {
686 			if (errno == ENOENT) {
687 				/* No entry by the name. */
688 				ABORT_READ("no such modepage entry \"%s\"",
689 				    name);
690 			} else if (errno == EINVAL) {
691 				/* Invalid value. */
692 				ABORT_READ("Invalid value for entry \"%s\"",
693 				    name);
694 			} else if (errno == ERANGE) {
695 				/* Value out of range for entry type. */
696 				ABORT_READ("value out of range for %s", name);
697 			} else if (errno == EPERM) {
698 				/* Entry is not editable; not fatal. */
699 				warnx("modepage entry \"%s\" is read-only; "
700 				    "skipping.", name);
701 			}
702 		}
703 
704 		free(buffer);
705 	}
706 	return (ferror(file)? -1: 0);
707 
708 #undef ABORT_READ
709 }
710 
711 static void
712 modepage_edit(void)
713 {
714 	const char *editor;
715 	char *commandline;
716 	int fd;
717 	int written;
718 
719 	if (!isatty(fileno(stdin))) {
720 		/* Not a tty, read changes from stdin. */
721 		modepage_read(stdin);
722 		return;
723 	}
724 
725 	/* Lookup editor to invoke. */
726 	if ((editor = getenv("EDITOR")) == NULL)
727 		editor = DEFAULT_EDITOR;
728 
729 	/* Create temp file for editor to modify. */
730 	if ((fd = mkstemp(edit_path)) == -1)
731 		errx(EX_CANTCREAT, "mkstemp failed");
732 
733 	atexit(cleanup_editfile);
734 
735 	if ((edit_file = fdopen(fd, "w")) == NULL)
736 		err(EX_NOINPUT, "%s", edit_path);
737 
738 	written = modepage_write(edit_file, 1);
739 
740 	fclose(edit_file);
741 	edit_file = NULL;
742 
743 	if (written == 0) {
744 		warnx("no editable entries");
745 		cleanup_editfile();
746 		return;
747 	}
748 
749 	/*
750 	 * Allocate memory to hold the command line (the 2 extra characters
751 	 * are to hold the argument separator (a space), and the terminating
752 	 * null character.
753 	 */
754 	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
755 	if (commandline == NULL)
756 		err(EX_OSERR, NULL);
757 	sprintf(commandline, "%s %s", editor, edit_path);
758 
759 	/* Invoke the editor on the temp file. */
760 	if (system(commandline) == -1)
761 		err(EX_UNAVAILABLE, "could not invoke %s", editor);
762 	free(commandline);
763 
764 	if ((edit_file = fopen(edit_path, "r")) == NULL)
765 		err(EX_NOINPUT, "%s", edit_path);
766 
767 	/* Read any changes made to the temp file. */
768 	modepage_read(edit_file);
769 
770 	cleanup_editfile();
771 }
772 
773 static void
774 modepage_dump(struct cam_device *device, int page, int page_control, int dbd,
775 	      int retries, int timeout)
776 {
777 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
778 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
779 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
780 	struct scsi_mode_page_header *mph;
781 	int mode_idx;			/* Index for scanning mode params. */
782 
783 	mode_sense(device, page, page_control, dbd, retries, timeout, data,
784 		   sizeof(data));
785 
786 	mh = (struct scsi_mode_header_6 *)data;
787 	mph = MODE_PAGE_HEADER(mh);
788 	mode_pars = MODE_PAGE_DATA(mph);
789 
790 	/* Print the raw mode page data with newlines each 8 bytes. */
791 	for (mode_idx = 0; mode_idx < mph->page_length; mode_idx++) {
792 		printf("%02x%c", mode_pars[mode_idx],
793 		    (((mode_idx + 1) % 8) == 0) ? '\n' : ' ');
794 	}
795 	putchar('\n');
796 }
797 
798 static void
799 cleanup_editfile(void)
800 {
801 	if (edit_file == NULL)
802 		return;
803 	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
804 		warn("%s", edit_path);
805 	edit_file = NULL;
806 }
807 
808 void
809 mode_edit(struct cam_device *device, int page, int page_control, int dbd,
810 	  int edit, int binary, int retry_count, int timeout)
811 {
812 	const char *pagedb_path;	/* Path to modepage database. */
813 
814 	if (edit && binary)
815 		errx(EX_USAGE, "cannot edit in binary mode.");
816 
817 	if (! binary) {
818 		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
819 			pagedb_path = DEFAULT_SCSI_MODE_DB;
820 
821 		if (load_format(pagedb_path, page) != 0 && (edit || verbose)) {
822 			if (errno == ENOENT) {
823 				/* Modepage database file not found. */
824 				warn("cannot open modepage database \"%s\"",
825 				    pagedb_path);
826 			} else if (errno == ESRCH) {
827 				/* Modepage entry not found in database. */
828 				warnx("modepage %d not found in database"
829 				    "\"%s\"", page, pagedb_path);
830 			}
831 			/* We can recover in display mode, otherwise we exit. */
832 			if (!edit) {
833 				warnx("reverting to binary display only");
834 				binary = 1;
835 			} else
836 				exit(EX_OSFILE);
837 		}
838 
839 		editlist_populate(device, page, page_control, dbd, retry_count,
840 			timeout);
841 	}
842 
843 	if (edit) {
844 		if (page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
845 		    page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
846 			errx(EX_USAGE, "it only makes sense to edit page 0 "
847 			    "(current) or page 3 (saved values)");
848 		modepage_edit();
849 		editlist_save(device, page, page_control, dbd, retry_count,
850 			timeout);
851 	} else if (binary || STAILQ_EMPTY(&editlist)) {
852 		/* Display without formatting information. */
853 		modepage_dump(device, page, page_control, dbd, retry_count,
854 		    timeout);
855 	} else {
856 		/* Display with format. */
857 		modepage_write(stdout, 0);
858 	}
859 }
860 
861 void
862 mode_list(struct cam_device *device, int page_control, int dbd,
863 	  int retry_count, int timeout)
864 {
865 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
866 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
867 	struct scsi_mode_page_header *mph;
868 	struct pagename *nameentry;
869 	const char *pagedb_path;
870 	int len;
871 
872 	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
873 		pagedb_path = DEFAULT_SCSI_MODE_DB;
874 
875 	if (load_format(pagedb_path, 0) != 0 && verbose && errno == ENOENT) {
876 		/* Modepage database file not found. */
877 		warn("cannot open modepage database \"%s\"", pagedb_path);
878 	}
879 
880 	/* Build the list of all mode pages by querying the "all pages" page. */
881 	mode_sense(device, SMS_ALL_PAGES_PAGE, page_control, dbd, retry_count,
882 	    timeout, data, sizeof(data));
883 
884 	mh = (struct scsi_mode_header_6 *)data;
885 	len = mh->blk_desc_len;		/* Skip block descriptors. */
886 	/* Iterate through the pages in the reply. */
887 	while (len < mh->data_length) {
888 		/* Locate the next mode page header. */
889 		mph = (struct scsi_mode_page_header *)
890 		    ((intptr_t)mh + sizeof(*mh) + len);
891 
892 		mph->page_code &= SMS_PAGE_CODE;
893 		nameentry = nameentry_lookup(mph->page_code);
894 
895 		if (nameentry == NULL || nameentry->name == NULL)
896 			printf("0x%02x\n", mph->page_code);
897 		else
898 			printf("0x%02x\t%s\n", mph->page_code,
899 			    nameentry->name);
900 		len += mph->page_length + sizeof(*mph);
901 	}
902 }
903