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