modeedit.c revision 312569
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 312569 2017-01-21 08:19:42Z mav $");
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 retries, int timeout);
110static void		 editlist_save(struct cam_device *device, int dbd,
111				       int pc, int page, int subpage,
112				       int retries, int timeout);
113static void		 nameentry_create(int page, int subpage, char *name);
114static struct pagename	*nameentry_lookup(int page, int subpage);
115static int		 load_format(const char *pagedb_path, int lpage,
116			    int lsubpage);
117static int		 modepage_write(FILE *file, int editonly);
118static int		 modepage_read(FILE *file);
119static void		 modepage_edit(void);
120static void		 modepage_dump(struct cam_device *device, int dbd,
121			    int pc, int page, int subpage, int retries,
122			    int timeout);
123static 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
139static void
140editentry_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
161static void
162editentry_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
189static int
190editentry_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
223static struct editentry *
224editentry_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
239static int
240editentry_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
326static void
327nameentry_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
346static struct pagename *
347nameentry_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
359static int
360load_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
551static void
552editlist_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
589static void
590editlist_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
645static int
646modepage_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
666static int
667modepage_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
748static void
749modepage_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
810static void
811modepage_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
844static void
845cleanup_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
854void
855mode_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
907void
908mode_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