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