164382Skbyanc/*-
264382Skbyanc * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
364382Skbyanc * Derived from work done by Julian Elischer <julian@tfs.com,
464382Skbyanc * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
539214Sgibbs * All rights reserved.
639214Sgibbs *
739214Sgibbs * Redistribution and use in source and binary forms, with or without
839214Sgibbs * modification, are permitted provided that the following conditions
939214Sgibbs * are met:
1039214Sgibbs * 1. Redistributions of source code must retain the above copyright
1164382Skbyanc *    notice, this list of conditions and the following disclaimer,
1264382Skbyanc *    without modification, immediately at the beginning of the file.
1339214Sgibbs * 2. Redistributions in binary form must reproduce the above copyright
1439214Sgibbs *    notice, this list of conditions and the following disclaimer in the
1564382Skbyanc *    documentation and/or other materials provided with the distribution.
1664382Skbyanc *
1764382Skbyanc * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1864382Skbyanc * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1964382Skbyanc * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2064382Skbyanc * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2164382Skbyanc * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2264382Skbyanc * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2364382Skbyanc * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2464382Skbyanc * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2564382Skbyanc * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2664382Skbyanc * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2739214Sgibbs */
2864382Skbyanc
29114513Sobrien#include <sys/cdefs.h>
30114513Sobrien__FBSDID("$FreeBSD$");
3139214Sgibbs
3264382Skbyanc#include <sys/queue.h>
3364382Skbyanc#include <sys/types.h>
3464382Skbyanc
3564382Skbyanc#include <assert.h>
3639214Sgibbs#include <ctype.h>
3739214Sgibbs#include <err.h>
3839214Sgibbs#include <errno.h>
3964382Skbyanc#include <stdlib.h>
4039214Sgibbs#include <string.h>
4139214Sgibbs#include <stdio.h>
4264382Skbyanc#include <sysexits.h>
4339214Sgibbs#include <unistd.h>
4439214Sgibbs
4564382Skbyanc#include <cam/scsi/scsi_all.h>
4639214Sgibbs#include <cam/cam.h>
4739214Sgibbs#include <cam/cam_ccb.h>
4839214Sgibbs#include <camlib.h>
4939214Sgibbs#include "camcontrol.h"
5039214Sgibbs
5164382Skbyanc#define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
5264382Skbyanc#define	DEFAULT_EDITOR		"vi"
5364382Skbyanc#define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
5464382Skbyanc#define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
5564382Skbyanc#define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
5664382Skbyanc#define	PAGEDEF_START		'{'	/* Page definition delimiter. */
5764382Skbyanc#define	PAGEDEF_END		'}'	/* Page definition delimiter. */
5864382Skbyanc#define	PAGENAME_START		'"'	/* Page name delimiter. */
5964382Skbyanc#define	PAGENAME_END		'"'	/* Page name delimiter. */
6064382Skbyanc#define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
6164382Skbyanc#define	MAX_COMMAND_SIZE	255	/* Mode/Log sense data buffer size. */
6264474Skbyanc#define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
6364382Skbyanc
6464382Skbyanc
6564382Skbyanc/* Macros for working with mode pages. */
6664382Skbyanc#define	MODE_PAGE_HEADER(mh)						\
6764382Skbyanc	(struct scsi_mode_page_header *)find_mode_page_6(mh)
6864382Skbyanc
6964382Skbyanc#define	MODE_PAGE_DATA(mph)						\
7064382Skbyanc	(u_int8_t *)(mph) + sizeof(struct scsi_mode_page_header)
7164382Skbyanc
7264382Skbyanc
7364382Skbyancstruct editentry {
7464382Skbyanc	STAILQ_ENTRY(editentry) link;
7564382Skbyanc	char	*name;
7664382Skbyanc	char	type;
7764382Skbyanc	int	editable;
7864382Skbyanc	int	size;
7964382Skbyanc	union {
8064382Skbyanc		int	ivalue;
8164382Skbyanc		char	*svalue;
8264382Skbyanc	} value;
8364382Skbyanc};
84237740Sscottlstatic STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
85237740Sscottlstatic int editlist_changed = 0;	/* Whether any entries were changed. */
8664382Skbyanc
8764382Skbyancstruct pagename {
8864382Skbyanc	SLIST_ENTRY(pagename) link;
8964382Skbyanc	int pagenum;
9064382Skbyanc	char *name;
9164382Skbyanc};
92237740Sscottlstatic SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
9364382Skbyanc
9464382Skbyancstatic char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
9564382Skbyanc
9664382Skbyancstatic FILE *edit_file = NULL;		/* File handle for edit file. */
9764382Skbyancstatic char edit_path[] = "/tmp/camXXXXXX";
9864382Skbyanc
9964382Skbyanc
10064382Skbyanc/* Function prototypes. */
10164382Skbyancstatic void		 editentry_create(void *hook, int letter, void *arg,
10264382Skbyanc					  int count, char *name);
10364382Skbyancstatic void		 editentry_update(void *hook, int letter, void *arg,
10464382Skbyanc					  int count, char *name);
10564382Skbyancstatic int		 editentry_save(void *hook, char *name);
10664382Skbyancstatic struct editentry	*editentry_lookup(char *name);
10764382Skbyancstatic int		 editentry_set(char *name, char *newvalue,
10864382Skbyanc				       int editonly);
10964382Skbyancstatic void		 editlist_populate(struct cam_device *device,
11064382Skbyanc					   int modepage, int page_control,
11164382Skbyanc					   int dbd, int retries, int timeout);
11264382Skbyancstatic void		 editlist_save(struct cam_device *device, int modepage,
11364382Skbyanc				       int page_control, int dbd, int retries,
11464382Skbyanc				       int timeout);
11564382Skbyancstatic void		 nameentry_create(int pagenum, char *name);
11664382Skbyancstatic struct pagename	*nameentry_lookup(int pagenum);
117118478Sjohanstatic int		 load_format(const char *pagedb_path, int page);
11864382Skbyancstatic int		 modepage_write(FILE *file, int editonly);
11964382Skbyancstatic int		 modepage_read(FILE *file);
12064382Skbyancstatic void		 modepage_edit(void);
12164382Skbyancstatic void		 modepage_dump(struct cam_device *device, int page,
12264382Skbyanc				       int page_control, int dbd, int retries,
12364382Skbyanc				       int timeout);
12464382Skbyancstatic void		 cleanup_editfile(void);
12564382Skbyanc
12664382Skbyanc
12764382Skbyanc#define	returnerr(code) do {						\
12864382Skbyanc	errno = code;							\
12964382Skbyanc	return (-1);							\
13064382Skbyanc} while (0)
13164382Skbyanc
13264382Skbyanc
13364382Skbyanc#define	RTRIM(string) do {						\
134119252Simp	int _length;							\
13564382Skbyanc	while (isspace(string[_length = strlen(string) - 1]))		\
13664382Skbyanc		string[_length] = '\0';					\
13764382Skbyanc} while (0)
13864382Skbyanc
13964382Skbyanc
14064382Skbyancstatic void
141118478Sjohaneditentry_create(void *hook __unused, int letter, void *arg, int count,
142118478Sjohan		 char *name)
14339214Sgibbs{
14464382Skbyanc	struct editentry *newentry;	/* Buffer to hold new entry. */
14539214Sgibbs
14664382Skbyanc	/* Allocate memory for the new entry and a copy of the entry name. */
14764382Skbyanc	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
14864382Skbyanc	    (newentry->name = strdup(name)) == NULL)
14964382Skbyanc		err(EX_OSERR, NULL);
15039214Sgibbs
15164382Skbyanc	/* Trim any trailing whitespace for the entry name. */
15264382Skbyanc	RTRIM(newentry->name);
15339214Sgibbs
15464382Skbyanc	newentry->editable = (arg != NULL);
15564382Skbyanc	newentry->type = letter;
15664382Skbyanc	newentry->size = count;		/* Placeholder; not accurate. */
15764382Skbyanc	newentry->value.svalue = NULL;
15864382Skbyanc
15964382Skbyanc	STAILQ_INSERT_TAIL(&editlist, newentry, link);
16039214Sgibbs}
16139214Sgibbs
16264382Skbyancstatic void
163118478Sjohaneditentry_update(void *hook __unused, int letter, void *arg, int count,
164118478Sjohan		 char *name)
16539214Sgibbs{
16664382Skbyanc	struct editentry *dest;		/* Buffer to hold entry to update. */
16739214Sgibbs
16864382Skbyanc	dest = editentry_lookup(name);
16964382Skbyanc	assert(dest != NULL);
17064382Skbyanc
17164382Skbyanc	dest->type = letter;
17264382Skbyanc	dest->size = count;		/* We get the real size now. */
17364382Skbyanc
17464382Skbyanc	switch (dest->type) {
17564382Skbyanc	case 'i':			/* Byte-sized integral type. */
17664382Skbyanc	case 'b':			/* Bit-sized integral types. */
17764382Skbyanc	case 't':
17864382Skbyanc		dest->value.ivalue = (intptr_t)arg;
17964382Skbyanc		break;
18064382Skbyanc
18164382Skbyanc	case 'c':			/* Character array. */
18264382Skbyanc	case 'z':			/* Null-padded string. */
18364382Skbyanc		editentry_set(name, (char *)arg, 0);
18464382Skbyanc		break;
18564382Skbyanc	default:
18697636Swollman		; /* NOTREACHED */
18739214Sgibbs	}
18864382Skbyanc}
18939214Sgibbs
19064382Skbyancstatic int
191118478Sjohaneditentry_save(void *hook __unused, char *name)
19264382Skbyanc{
19364382Skbyanc	struct editentry *src;		/* Entry value to save. */
19439214Sgibbs
19564382Skbyanc	src = editentry_lookup(name);
19664382Skbyanc	assert(src != NULL);
19764382Skbyanc
19864382Skbyanc	switch (src->type) {
19964382Skbyanc	case 'i':			/* Byte-sized integral type. */
20064382Skbyanc	case 'b':			/* Bit-sized integral types. */
20164382Skbyanc	case 't':
20264382Skbyanc		return (src->value.ivalue);
20364382Skbyanc		/* NOTREACHED */
20464382Skbyanc
20564382Skbyanc	case 'c':			/* Character array. */
20664382Skbyanc	case 'z':			/* Null-padded string. */
20764382Skbyanc		return ((intptr_t)src->value.svalue);
20864382Skbyanc		/* NOTREACHED */
20964382Skbyanc
21064382Skbyanc	default:
21197636Swollman		; /* NOTREACHED */
21264382Skbyanc	}
21364382Skbyanc
21464382Skbyanc	return (0);			/* This should never happen. */
21539214Sgibbs}
21639214Sgibbs
21764382Skbyancstatic struct editentry *
21864382Skbyanceditentry_lookup(char *name)
21939214Sgibbs{
22064382Skbyanc	struct editentry *scan;
22139214Sgibbs
22264382Skbyanc	assert(name != NULL);
22339214Sgibbs
22464382Skbyanc	STAILQ_FOREACH(scan, &editlist, link) {
22564382Skbyanc		if (strcasecmp(scan->name, name) == 0)
22664382Skbyanc			return (scan);
22764382Skbyanc	}
22839214Sgibbs
22964382Skbyanc	/* Not found during list traversal. */
23064382Skbyanc	return (NULL);
23164382Skbyanc}
23239214Sgibbs
23364382Skbyancstatic int
23464382Skbyanceditentry_set(char *name, char *newvalue, int editonly)
23564382Skbyanc{
23664382Skbyanc	struct editentry *dest;	/* Modepage entry to update. */
23764382Skbyanc	char *cval;		/* Pointer to new string value. */
23864382Skbyanc	char *convertend;	/* End-of-conversion pointer. */
23964382Skbyanc	int ival;		/* New integral value. */
24064382Skbyanc	int resolution;		/* Resolution in bits for integer conversion. */
24139214Sgibbs
24264382Skbyanc/*
24364382Skbyanc * Macro to determine the maximum value of the given size for the current
24464382Skbyanc * resolution.
24564382Skbyanc * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
24664382Skbyanc *     currently workaround it (even for int64's), so we have to kludge it.
24764382Skbyanc */
24864382Skbyanc#define	RESOLUTION_MAX(size) ((resolution * (size) == 32)? 		\
249118478Sjohan	(int)0xffffffff: (1 << (resolution * (size))) - 1)
25064382Skbyanc
25164382Skbyanc	assert(newvalue != NULL);
25264382Skbyanc	if (*newvalue == '\0')
25364382Skbyanc		return (0);	/* Nothing to do. */
25464382Skbyanc
25564382Skbyanc	if ((dest = editentry_lookup(name)) == NULL)
25664382Skbyanc		returnerr(ENOENT);
25764382Skbyanc	if (!dest->editable && editonly)
25864382Skbyanc		returnerr(EPERM);
25964382Skbyanc
26064382Skbyanc	switch (dest->type) {
26164382Skbyanc	case 'i':		/* Byte-sized integral type. */
26264382Skbyanc	case 'b':		/* Bit-sized integral types. */
26364382Skbyanc	case 't':
26464382Skbyanc		/* Convert the value string to an integer. */
26564382Skbyanc		resolution = (dest->type == 'i')? 8: 1;
26664382Skbyanc		ival = (int)strtol(newvalue, &convertend, 0);
26764382Skbyanc		if (*convertend != '\0')
26864382Skbyanc			returnerr(EINVAL);
26964382Skbyanc		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
27064382Skbyanc			int newival = (ival < 0)? 0: RESOLUTION_MAX(dest->size);
27164382Skbyanc			warnx("value %d is out of range for entry %s; clipping "
27264382Skbyanc			    "to %d", ival, name, newival);
27364382Skbyanc			ival = newival;
27439214Sgibbs		}
27564382Skbyanc		if (dest->value.ivalue != ival)
27664382Skbyanc			editlist_changed = 1;
27764382Skbyanc		dest->value.ivalue = ival;
27864382Skbyanc		break;
27939214Sgibbs
28064382Skbyanc	case 'c':		/* Character array. */
28164382Skbyanc	case 'z':		/* Null-padded string. */
28264382Skbyanc		if ((cval = malloc(dest->size + 1)) == NULL)
28364382Skbyanc			err(EX_OSERR, NULL);
28464382Skbyanc		bzero(cval, dest->size + 1);
28564382Skbyanc		strncpy(cval, newvalue, dest->size);
28664382Skbyanc		if (dest->type == 'z') {
28764382Skbyanc			/* Convert trailing spaces to nulls. */
288118478Sjohan			char *convertend2;
28964382Skbyanc
290118478Sjohan			for (convertend2 = cval + dest->size;
291118478Sjohan			    convertend2 >= cval; convertend2--) {
292118478Sjohan				if (*convertend2 == ' ')
293118478Sjohan					*convertend2 = '\0';
294118478Sjohan				else if (*convertend2 != '\0')
29564382Skbyanc					break;
29664382Skbyanc			}
29764382Skbyanc		}
29864382Skbyanc		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
29964382Skbyanc			/* Nothing changed, free the newly allocated string. */
30064382Skbyanc			free(cval);
30164382Skbyanc			break;
30264382Skbyanc		}
30364382Skbyanc		if (dest->value.svalue != NULL) {
30464382Skbyanc			/* Free the current string buffer. */
30564382Skbyanc			free(dest->value.svalue);
30664382Skbyanc			dest->value.svalue = NULL;
30764382Skbyanc		}
30864382Skbyanc		dest->value.svalue = cval;
30964382Skbyanc		editlist_changed = 1;
31039214Sgibbs		break;
31139214Sgibbs
31264382Skbyanc	default:
31397636Swollman		; /* NOTREACHED */
31439214Sgibbs	}
31564382Skbyanc
31664382Skbyanc	return (0);
31764382Skbyanc#undef RESOLUTION_MAX
31839214Sgibbs}
31939214Sgibbs
32039214Sgibbsstatic void
32164382Skbyancnameentry_create(int pagenum, char *name) {
32264382Skbyanc	struct pagename *newentry;
32339214Sgibbs
32464382Skbyanc	if (pagenum < 0 || name == NULL || name[0] == '\0')
32564382Skbyanc		return;
32639214Sgibbs
32764382Skbyanc	/* Allocate memory for the new entry and a copy of the entry name. */
32864382Skbyanc	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
32964382Skbyanc	    (newentry->name = strdup(name)) == NULL)
33064382Skbyanc		err(EX_OSERR, NULL);
33139214Sgibbs
33264382Skbyanc	/* Trim any trailing whitespace for the page name. */
33364382Skbyanc	RTRIM(newentry->name);
33464382Skbyanc
33564382Skbyanc	newentry->pagenum = pagenum;
33664382Skbyanc	SLIST_INSERT_HEAD(&namelist, newentry, link);
33764382Skbyanc}
33864382Skbyanc
33964382Skbyancstatic struct pagename *
34064382Skbyancnameentry_lookup(int pagenum) {
34164382Skbyanc	struct pagename *scan;
34264382Skbyanc
34364382Skbyanc	SLIST_FOREACH(scan, &namelist, link) {
34464382Skbyanc		if (pagenum == scan->pagenum)
34564382Skbyanc			return (scan);
34639214Sgibbs	}
34739214Sgibbs
34864382Skbyanc	/* Not found during list traversal. */
34964382Skbyanc	return (NULL);
35039214Sgibbs}
35139214Sgibbs
35264382Skbyancstatic int
353118478Sjohanload_format(const char *pagedb_path, int page)
35439214Sgibbs{
35564382Skbyanc	FILE *pagedb;
35664382Skbyanc	char str_pagenum[MAX_PAGENUM_LEN];
35764382Skbyanc	char str_pagename[MAX_PAGENAME_LEN];
35864382Skbyanc	int pagenum;
35964382Skbyanc	int depth;			/* Quoting depth. */
36064382Skbyanc	int found;
36164382Skbyanc	int lineno;
36264382Skbyanc	enum { LOCATE, PAGENAME, PAGEDEF } state;
363124830Sgrehan	int ch;
36464382Skbyanc	char c;
36539214Sgibbs
36664382Skbyanc#define	SETSTATE_LOCATE do {						\
36764382Skbyanc	str_pagenum[0] = '\0';						\
36864382Skbyanc	str_pagename[0] = '\0';						\
36964382Skbyanc	pagenum = -1;							\
37064382Skbyanc	state = LOCATE;							\
37164382Skbyanc} while (0)
37239214Sgibbs
37364382Skbyanc#define	SETSTATE_PAGENAME do {						\
37464382Skbyanc	str_pagename[0] = '\0';						\
37564382Skbyanc	state = PAGENAME;						\
37664382Skbyanc} while (0)
37739214Sgibbs
37864382Skbyanc#define	SETSTATE_PAGEDEF do {						\
37964382Skbyanc	format[0] = '\0';						\
38064382Skbyanc	state = PAGEDEF;						\
38164382Skbyanc} while (0)
38239214Sgibbs
38364382Skbyanc#define	UPDATE_LINENO do {						\
38464382Skbyanc	if (c == '\n')							\
38564382Skbyanc		lineno++;						\
38664382Skbyanc} while (0)
38739214Sgibbs
38864382Skbyanc#define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
38939214Sgibbs
39064382Skbyanc	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
39164382Skbyanc		returnerr(ENOENT);
39239214Sgibbs
39364382Skbyanc	SLIST_INIT(&namelist);
39439214Sgibbs
395209051Suqs	c = '\0';
39664382Skbyanc	depth = 0;
39764382Skbyanc	lineno = 0;
39864382Skbyanc	found = 0;
39964382Skbyanc	SETSTATE_LOCATE;
400124830Sgrehan	while ((ch = fgetc(pagedb)) != EOF) {
40139214Sgibbs
40264382Skbyanc		/* Keep a line count to make error messages more useful. */
40364382Skbyanc		UPDATE_LINENO;
40464382Skbyanc
40564382Skbyanc		/* Skip over comments anywhere in the mode database. */
406124830Sgrehan		if (ch == '#') {
40764382Skbyanc			do {
408124830Sgrehan				ch = fgetc(pagedb);
409124830Sgrehan			} while (ch != '\n' && ch != EOF);
41064382Skbyanc			UPDATE_LINENO;
41164382Skbyanc			continue;
41264382Skbyanc		}
413124830Sgrehan		c = ch;
41464382Skbyanc
41564382Skbyanc		/* Strip out newline characters. */
41664382Skbyanc		if (c == '\n')
41764382Skbyanc			continue;
41864382Skbyanc
41964382Skbyanc		/* Keep track of the nesting depth for braces. */
42064382Skbyanc		if (c == PAGEDEF_START)
42164382Skbyanc			depth++;
42264382Skbyanc		else if (c == PAGEDEF_END) {
42364382Skbyanc			depth--;
42464382Skbyanc			if (depth < 0) {
42564382Skbyanc				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
42664382Skbyanc				    lineno, "mismatched bracket");
42739214Sgibbs			}
42864382Skbyanc		}
42939214Sgibbs
43064382Skbyanc		switch (state) {
43164382Skbyanc		case LOCATE:
43264382Skbyanc			/*
43364382Skbyanc			 * Locate the page the user is interested in, skipping
43464382Skbyanc			 * all others.
43564382Skbyanc			 */
43664382Skbyanc			if (isspace(c)) {
43764382Skbyanc				/* Ignore all whitespace between pages. */
43864382Skbyanc				break;
43964382Skbyanc			} else if (depth == 0 && c == PAGEENTRY_END) {
44064382Skbyanc				/*
44164382Skbyanc				 * A page entry terminator will reset page
44264382Skbyanc				 * scanning (useful for assigning names to
44364382Skbyanc				 * modes without providing a mode definition).
44464382Skbyanc				 */
44564382Skbyanc				/* Record the name of this page. */
44664382Skbyanc				pagenum = strtol(str_pagenum, NULL, 0);
44764382Skbyanc				nameentry_create(pagenum, str_pagename);
44864382Skbyanc				SETSTATE_LOCATE;
44964382Skbyanc			} else if (depth == 0 && c == PAGENAME_START) {
45064382Skbyanc				SETSTATE_PAGENAME;
45164382Skbyanc			} else if (c == PAGEDEF_START) {
45264382Skbyanc				pagenum = strtol(str_pagenum, NULL, 0);
45364382Skbyanc				if (depth == 1) {
45464382Skbyanc					/* Record the name of this page. */
45564382Skbyanc					nameentry_create(pagenum, str_pagename);
45664382Skbyanc					/*
45764382Skbyanc					 * Only record the format if this is
45864382Skbyanc					 * the page we are interested in.
45964382Skbyanc					 */
46064382Skbyanc					if (page == pagenum && !found)
46164382Skbyanc						SETSTATE_PAGEDEF;
46264382Skbyanc				}
46364382Skbyanc			} else if (c == PAGEDEF_END) {
46464382Skbyanc				/* Reset the processor state. */
46564382Skbyanc				SETSTATE_LOCATE;
46664382Skbyanc			} else if (depth == 0 && ! BUFFERFULL(str_pagenum)) {
46764382Skbyanc				strncat(str_pagenum, &c, 1);
46864382Skbyanc			} else if (depth == 0) {
469112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
47064382Skbyanc				    lineno, "page identifier exceeds",
47164382Skbyanc				    sizeof(str_pagenum) - 1, "characters");
47239214Sgibbs			}
47364382Skbyanc			break;
47464382Skbyanc
47564382Skbyanc		case PAGENAME:
47664382Skbyanc			if (c == PAGENAME_END) {
47764382Skbyanc				/*
47864382Skbyanc				 * Return to LOCATE state without resetting the
47964382Skbyanc				 * page number buffer.
48064382Skbyanc				 */
48164382Skbyanc				state = LOCATE;
48264382Skbyanc			} else if (! BUFFERFULL(str_pagename)) {
48364382Skbyanc				strncat(str_pagename, &c, 1);
48464382Skbyanc			} else {
485112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
48664382Skbyanc				    lineno, "page name exceeds",
48764382Skbyanc				    sizeof(str_pagenum) - 1, "characters");
48839214Sgibbs			}
48964382Skbyanc			break;
49039214Sgibbs
49164382Skbyanc		case PAGEDEF:
49264382Skbyanc			/*
49364382Skbyanc			 * Transfer the page definition into a format buffer
49464382Skbyanc			 * suitable for use with CDB encoding/decoding routines.
49564382Skbyanc			 */
49664382Skbyanc			if (depth == 0) {
49764382Skbyanc				found = 1;
49864382Skbyanc				SETSTATE_LOCATE;
49964382Skbyanc			} else if (! BUFFERFULL(format)) {
50064382Skbyanc				strncat(format, &c, 1);
50164382Skbyanc			} else {
502112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
50364382Skbyanc				    lineno, "page definition exceeds",
50464382Skbyanc				    sizeof(format) - 1, "characters");
50539214Sgibbs			}
50664382Skbyanc			break;
50764382Skbyanc
50864382Skbyanc		default:
50997636Swollman			; /* NOTREACHED */
51039214Sgibbs		}
51164382Skbyanc
51264382Skbyanc		/* Repeat processing loop with next character. */
51339214Sgibbs	}
51439214Sgibbs
51564382Skbyanc	if (ferror(pagedb))
51664382Skbyanc		err(EX_OSFILE, "%s", pagedb_path);
51739214Sgibbs
51864382Skbyanc	/* Close the SCSI page database. */
51964382Skbyanc	fclose(pagedb);
52039214Sgibbs
52164382Skbyanc	if (!found)			/* Never found a matching page. */
52264382Skbyanc		returnerr(ESRCH);
52339214Sgibbs
52464382Skbyanc	return (0);
52539214Sgibbs}
52639214Sgibbs
52739214Sgibbsstatic void
52864382Skbyanceditlist_populate(struct cam_device *device, int modepage, int page_control,
52964382Skbyanc		  int dbd, int retries, int timeout)
53039214Sgibbs{
53164382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
53264382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
53364382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
53464382Skbyanc	struct scsi_mode_page_header *mph;
53539214Sgibbs
53664382Skbyanc	STAILQ_INIT(&editlist);
53739214Sgibbs
53864382Skbyanc	/* Fetch changeable values; use to build initial editlist. */
53964382Skbyanc	mode_sense(device, modepage, 1, dbd, retries, timeout, data,
54064382Skbyanc		   sizeof(data));
54139214Sgibbs
54264382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
54364382Skbyanc	mph = MODE_PAGE_HEADER(mh);
54464382Skbyanc	mode_pars = MODE_PAGE_DATA(mph);
54539214Sgibbs
54664382Skbyanc	/* Decode the value data, creating edit_entries for each value. */
54764382Skbyanc	buff_decode_visit(mode_pars, mh->data_length, format,
54864382Skbyanc	    editentry_create, 0);
54939214Sgibbs
55064382Skbyanc	/* Fetch the current/saved values; use to set editentry values. */
55164382Skbyanc	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
55264382Skbyanc		   sizeof(data));
55364382Skbyanc	buff_decode_visit(mode_pars, mh->data_length, format,
55464382Skbyanc	    editentry_update, 0);
55539214Sgibbs}
55639214Sgibbs
55739214Sgibbsstatic void
55864382Skbyanceditlist_save(struct cam_device *device, int modepage, int page_control,
55964382Skbyanc	      int dbd, int retries, int timeout)
56039214Sgibbs{
56164382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
56264382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
56364382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
56464382Skbyanc	struct scsi_mode_page_header *mph;
56551737Sken
56664382Skbyanc	/* Make sure that something changed before continuing. */
56764382Skbyanc	if (! editlist_changed)
56864382Skbyanc		return;
56939214Sgibbs
57064382Skbyanc	/*
57164393Skbyanc	 * Preload the CDB buffer with the current mode page data.
57264382Skbyanc	 * XXX If buff_encode_visit would return the number of bytes encoded
57364382Skbyanc	 *     we *should* use that to build a header from scratch. As it is
57464382Skbyanc	 *     now, we need mode_sense to find out the page length.
57564382Skbyanc	 */
57664393Skbyanc	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
57764393Skbyanc		   sizeof(data));
57839214Sgibbs
57964382Skbyanc	/* Initial headers & offsets. */
58064382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
58164393Skbyanc	mph = MODE_PAGE_HEADER(mh);
58264393Skbyanc	mode_pars = MODE_PAGE_DATA(mph);
58339214Sgibbs
58464393Skbyanc	/* Encode the value data to be passed back to the device. */
58564435Sjhb	buff_encode_visit(mode_pars, mh->data_length, format,
58664382Skbyanc	    editentry_save, 0);
58739214Sgibbs
58864393Skbyanc	/* Eliminate block descriptors. */
58964393Skbyanc	bcopy(mph, ((u_int8_t *)mh) + sizeof(*mh),
59064393Skbyanc	    sizeof(*mph) + mph->page_length);
59139214Sgibbs
59264382Skbyanc	/* Recalculate headers & offsets. */
59364382Skbyanc	mh->blk_desc_len = 0;		/* No block descriptors. */
59464382Skbyanc	mh->dev_spec = 0;		/* Clear device-specific parameters. */
59564393Skbyanc	mph = MODE_PAGE_HEADER(mh);
59664393Skbyanc	mode_pars = MODE_PAGE_DATA(mph);
59739214Sgibbs
59864382Skbyanc	mph->page_code &= SMS_PAGE_CODE;/* Isolate just the page code. */
59964382Skbyanc	mh->data_length = 0;		/* Reserved for MODE SELECT command. */
60064382Skbyanc
60164382Skbyanc	/*
60264382Skbyanc	 * Write the changes back to the device. If the user editted control
60364382Skbyanc	 * page 3 (saved values) then request the changes be permanently
60464382Skbyanc	 * recorded.
60564382Skbyanc	 */
60664474Skbyanc	mode_select(device,
60764474Skbyanc	    (page_control << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
60864474Skbyanc	    retries, timeout, (u_int8_t *)mh,
60964474Skbyanc	    sizeof(*mh) + mh->blk_desc_len + sizeof(*mph) + mph->page_length);
61039214Sgibbs}
61139214Sgibbs
61264382Skbyancstatic int
61364382Skbyancmodepage_write(FILE *file, int editonly)
61439214Sgibbs{
61564382Skbyanc	struct editentry *scan;
61664382Skbyanc	int written = 0;
61739214Sgibbs
61864382Skbyanc	STAILQ_FOREACH(scan, &editlist, link) {
61964382Skbyanc		if (scan->editable || !editonly) {
62064382Skbyanc			written++;
62164382Skbyanc			if (scan->type == 'c' || scan->type == 'z') {
62264382Skbyanc				fprintf(file, "%s:  %s\n", scan->name,
62364382Skbyanc				    scan->value.svalue);
62464382Skbyanc			} else {
62564382Skbyanc				fprintf(file, "%s:  %d\n", scan->name,
62664382Skbyanc				    scan->value.ivalue);
62764382Skbyanc			}
62864382Skbyanc		}
62939214Sgibbs	}
63064382Skbyanc	return (written);
63139214Sgibbs}
63239214Sgibbs
63339214Sgibbsstatic int
63464382Skbyancmodepage_read(FILE *file)
63539214Sgibbs{
63664382Skbyanc	char *buffer;			/* Pointer to dynamic line buffer.  */
63764382Skbyanc	char *line;			/* Pointer to static fgetln buffer. */
63864382Skbyanc	char *name;			/* Name portion of the line buffer. */
63964382Skbyanc	char *value;			/* Value portion of line buffer.    */
640111195Sjohan	size_t length;			/* Length of static fgetln buffer.  */
64139214Sgibbs
64264382Skbyanc#define	ABORT_READ(message, param) do {					\
64364382Skbyanc	warnx(message, param);						\
64464382Skbyanc	free(buffer);							\
64564382Skbyanc	returnerr(EAGAIN);						\
64664382Skbyanc} while (0)
64739214Sgibbs
64864382Skbyanc	while ((line = fgetln(file, &length)) != NULL) {
64964382Skbyanc		/* Trim trailing whitespace (including optional newline). */
65064382Skbyanc		while (length > 0 && isspace(line[length - 1]))
65164382Skbyanc			length--;
65239214Sgibbs
65364382Skbyanc	    	/* Allocate a buffer to hold the line + terminating null. */
65464382Skbyanc	    	if ((buffer = malloc(length + 1)) == NULL)
65564382Skbyanc			err(EX_OSERR, NULL);
65664382Skbyanc		memcpy(buffer, line, length);
65764382Skbyanc		buffer[length] = '\0';
65839214Sgibbs
65964382Skbyanc		/* Strip out comments. */
66064382Skbyanc		if ((value = strchr(buffer, '#')) != NULL)
66164382Skbyanc			*value = '\0';
66264382Skbyanc
66364382Skbyanc		/* The name is first in the buffer. Trim whitespace.*/
66464382Skbyanc		name = buffer;
66564382Skbyanc		RTRIM(name);
66664382Skbyanc		while (isspace(*name))
66764382Skbyanc			name++;
66864382Skbyanc
66964382Skbyanc		/* Skip empty lines. */
67064382Skbyanc		if (strlen(name) == 0)
67164382Skbyanc			continue;
67264382Skbyanc
67364382Skbyanc		/* The name ends at the colon; the value starts there. */
67464382Skbyanc		if ((value = strrchr(buffer, ':')) == NULL)
67564382Skbyanc			ABORT_READ("no value associated with %s", name);
67664382Skbyanc		*value = '\0';			/* Null-terminate name. */
67764382Skbyanc		value++;			/* Value starts afterwards. */
67864382Skbyanc
67964382Skbyanc		/* Trim leading and trailing whitespace. */
68064382Skbyanc		RTRIM(value);
68164382Skbyanc		while (isspace(*value))
68264382Skbyanc			value++;
68364382Skbyanc
68464382Skbyanc		/* Make sure there is a value left. */
68564382Skbyanc		if (strlen(value) == 0)
68664382Skbyanc			ABORT_READ("no value associated with %s", name);
68764382Skbyanc
68864382Skbyanc		/* Update our in-memory copy of the modepage entry value. */
68964382Skbyanc		if (editentry_set(name, value, 1) != 0) {
69064382Skbyanc			if (errno == ENOENT) {
69164382Skbyanc				/* No entry by the name. */
69264382Skbyanc				ABORT_READ("no such modepage entry \"%s\"",
69364382Skbyanc				    name);
69464382Skbyanc			} else if (errno == EINVAL) {
69564382Skbyanc				/* Invalid value. */
69664382Skbyanc				ABORT_READ("Invalid value for entry \"%s\"",
69764382Skbyanc				    name);
69864382Skbyanc			} else if (errno == ERANGE) {
69964382Skbyanc				/* Value out of range for entry type. */
70064382Skbyanc				ABORT_READ("value out of range for %s", name);
70164382Skbyanc			} else if (errno == EPERM) {
70264382Skbyanc				/* Entry is not editable; not fatal. */
70364382Skbyanc				warnx("modepage entry \"%s\" is read-only; "
70464382Skbyanc				    "skipping.", name);
70564382Skbyanc			}
70664382Skbyanc		}
70764382Skbyanc
70864382Skbyanc		free(buffer);
70939214Sgibbs	}
71064382Skbyanc	return (ferror(file)? -1: 0);
71139214Sgibbs
71264382Skbyanc#undef ABORT_READ
71339214Sgibbs}
71439214Sgibbs
71539214Sgibbsstatic void
71664382Skbyancmodepage_edit(void)
71739214Sgibbs{
718118478Sjohan	const char *editor;
71964382Skbyanc	char *commandline;
72064382Skbyanc	int fd;
72164382Skbyanc	int written;
72239214Sgibbs
72364382Skbyanc	if (!isatty(fileno(stdin))) {
72464382Skbyanc		/* Not a tty, read changes from stdin. */
72564382Skbyanc		modepage_read(stdin);
72664382Skbyanc		return;
72764382Skbyanc	}
72839214Sgibbs
72964382Skbyanc	/* Lookup editor to invoke. */
73064382Skbyanc	if ((editor = getenv("EDITOR")) == NULL)
73164382Skbyanc		editor = DEFAULT_EDITOR;
73239214Sgibbs
73364382Skbyanc	/* Create temp file for editor to modify. */
73464382Skbyanc	if ((fd = mkstemp(edit_path)) == -1)
73564382Skbyanc		errx(EX_CANTCREAT, "mkstemp failed");
73639214Sgibbs
73764382Skbyanc	atexit(cleanup_editfile);
73839214Sgibbs
73964382Skbyanc	if ((edit_file = fdopen(fd, "w")) == NULL)
74064382Skbyanc		err(EX_NOINPUT, "%s", edit_path);
74139214Sgibbs
74264382Skbyanc	written = modepage_write(edit_file, 1);
74339214Sgibbs
74464382Skbyanc	fclose(edit_file);
74564382Skbyanc	edit_file = NULL;
74664382Skbyanc
74764382Skbyanc	if (written == 0) {
74864382Skbyanc		warnx("no editable entries");
74964382Skbyanc		cleanup_editfile();
75064382Skbyanc		return;
75139214Sgibbs	}
75239214Sgibbs
75364382Skbyanc	/*
75464382Skbyanc	 * Allocate memory to hold the command line (the 2 extra characters
75564382Skbyanc	 * are to hold the argument separator (a space), and the terminating
75664382Skbyanc	 * null character.
75764382Skbyanc	 */
75864382Skbyanc	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
75964382Skbyanc	if (commandline == NULL)
76064382Skbyanc		err(EX_OSERR, NULL);
76164382Skbyanc	sprintf(commandline, "%s %s", editor, edit_path);
76239214Sgibbs
76364382Skbyanc	/* Invoke the editor on the temp file. */
76464382Skbyanc	if (system(commandline) == -1)
76564382Skbyanc		err(EX_UNAVAILABLE, "could not invoke %s", editor);
76664382Skbyanc	free(commandline);
76739214Sgibbs
76864382Skbyanc	if ((edit_file = fopen(edit_path, "r")) == NULL)
76964382Skbyanc		err(EX_NOINPUT, "%s", edit_path);
77039214Sgibbs
77164382Skbyanc	/* Read any changes made to the temp file. */
77264382Skbyanc	modepage_read(edit_file);
77339214Sgibbs
77464382Skbyanc	cleanup_editfile();
77564382Skbyanc}
77639214Sgibbs
77764382Skbyancstatic void
77864382Skbyancmodepage_dump(struct cam_device *device, int page, int page_control, int dbd,
77964382Skbyanc	      int retries, int timeout)
78064382Skbyanc{
78164382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
78264382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
78364382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
78464382Skbyanc	struct scsi_mode_page_header *mph;
785118478Sjohan	int indx;			/* Index for scanning mode params. */
78639214Sgibbs
78764382Skbyanc	mode_sense(device, page, page_control, dbd, retries, timeout, data,
78864382Skbyanc		   sizeof(data));
78939214Sgibbs
79064382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
79164382Skbyanc	mph = MODE_PAGE_HEADER(mh);
79264382Skbyanc	mode_pars = MODE_PAGE_DATA(mph);
79339214Sgibbs
79464382Skbyanc	/* Print the raw mode page data with newlines each 8 bytes. */
795118478Sjohan	for (indx = 0; indx < mph->page_length; indx++) {
796118478Sjohan		printf("%02x%c",mode_pars[indx],
797118478Sjohan		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
79864382Skbyanc	}
79964382Skbyanc	putchar('\n');
80064382Skbyanc}
80139214Sgibbs
80264382Skbyancstatic void
80364382Skbyanccleanup_editfile(void)
80464382Skbyanc{
80564382Skbyanc	if (edit_file == NULL)
80664382Skbyanc		return;
80764382Skbyanc	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
80864382Skbyanc		warn("%s", edit_path);
80964382Skbyanc	edit_file = NULL;
81064382Skbyanc}
81139214Sgibbs
81264382Skbyancvoid
81364382Skbyancmode_edit(struct cam_device *device, int page, int page_control, int dbd,
81464382Skbyanc	  int edit, int binary, int retry_count, int timeout)
81564382Skbyanc{
816118478Sjohan	const char *pagedb_path;	/* Path to modepage database. */
81739214Sgibbs
81864382Skbyanc	if (edit && binary)
81964382Skbyanc		errx(EX_USAGE, "cannot edit in binary mode.");
82039214Sgibbs
82164382Skbyanc	if (! binary) {
82264382Skbyanc		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
82364382Skbyanc			pagedb_path = DEFAULT_SCSI_MODE_DB;
82439214Sgibbs
82564382Skbyanc		if (load_format(pagedb_path, page) != 0 && (edit || verbose)) {
82664382Skbyanc			if (errno == ENOENT) {
82764382Skbyanc				/* Modepage database file not found. */
82864382Skbyanc				warn("cannot open modepage database \"%s\"",
82964382Skbyanc				    pagedb_path);
83064382Skbyanc			} else if (errno == ESRCH) {
83164382Skbyanc				/* Modepage entry not found in database. */
83264382Skbyanc				warnx("modepage %d not found in database"
83364382Skbyanc				    "\"%s\"", page, pagedb_path);
83464382Skbyanc			}
83564382Skbyanc			/* We can recover in display mode, otherwise we exit. */
83664382Skbyanc			if (!edit) {
83764382Skbyanc				warnx("reverting to binary display only");
83864382Skbyanc				binary = 1;
83964382Skbyanc			} else
84064382Skbyanc				exit(EX_OSFILE);
84164382Skbyanc		}
84239214Sgibbs
84364382Skbyanc		editlist_populate(device, page, page_control, dbd, retry_count,
84464382Skbyanc			timeout);
84564382Skbyanc	}
84639214Sgibbs
84764382Skbyanc	if (edit) {
84864474Skbyanc		if (page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
84964474Skbyanc		    page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
85064382Skbyanc			errx(EX_USAGE, "it only makes sense to edit page 0 "
85164382Skbyanc			    "(current) or page 3 (saved values)");
85264382Skbyanc		modepage_edit();
85364382Skbyanc		editlist_save(device, page, page_control, dbd, retry_count,
85464382Skbyanc			timeout);
85564382Skbyanc	} else if (binary || STAILQ_EMPTY(&editlist)) {
85664382Skbyanc		/* Display without formatting information. */
85764382Skbyanc		modepage_dump(device, page, page_control, dbd, retry_count,
85864382Skbyanc		    timeout);
85964382Skbyanc	} else {
86064382Skbyanc		/* Display with format. */
86164382Skbyanc		modepage_write(stdout, 0);
86264382Skbyanc	}
86364382Skbyanc}
86439214Sgibbs
86564382Skbyancvoid
86664382Skbyancmode_list(struct cam_device *device, int page_control, int dbd,
86764382Skbyanc	  int retry_count, int timeout)
86864382Skbyanc{
86964382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
87064382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
87164382Skbyanc	struct scsi_mode_page_header *mph;
87264382Skbyanc	struct pagename *nameentry;
873118478Sjohan	const char *pagedb_path;
87464382Skbyanc	int len;
87539214Sgibbs
87664382Skbyanc	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
87764382Skbyanc		pagedb_path = DEFAULT_SCSI_MODE_DB;
87839214Sgibbs
87964382Skbyanc	if (load_format(pagedb_path, 0) != 0 && verbose && errno == ENOENT) {
88064382Skbyanc		/* Modepage database file not found. */
88164382Skbyanc		warn("cannot open modepage database \"%s\"", pagedb_path);
88239214Sgibbs	}
88339214Sgibbs
88464382Skbyanc	/* Build the list of all mode pages by querying the "all pages" page. */
88564382Skbyanc	mode_sense(device, SMS_ALL_PAGES_PAGE, page_control, dbd, retry_count,
88664382Skbyanc	    timeout, data, sizeof(data));
88739214Sgibbs
88864382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
889257101Smav	len = sizeof(*mh) + mh->blk_desc_len;	/* Skip block descriptors. */
89064382Skbyanc	/* Iterate through the pages in the reply. */
89164382Skbyanc	while (len < mh->data_length) {
89264382Skbyanc		/* Locate the next mode page header. */
89364382Skbyanc		mph = (struct scsi_mode_page_header *)
894257101Smav		    ((intptr_t)mh + len);
89539214Sgibbs
89664382Skbyanc		mph->page_code &= SMS_PAGE_CODE;
89764382Skbyanc		nameentry = nameentry_lookup(mph->page_code);
89864382Skbyanc
89964382Skbyanc		if (nameentry == NULL || nameentry->name == NULL)
90064382Skbyanc			printf("0x%02x\n", mph->page_code);
90164382Skbyanc		else
90264382Skbyanc			printf("0x%02x\t%s\n", mph->page_code,
90364382Skbyanc			    nameentry->name);
90464382Skbyanc		len += mph->page_length + sizeof(*mph);
90539214Sgibbs	}
90639214Sgibbs}
907