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