]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sbin/camcontrol/modeedit.c
MFC r311623: Make do_buff_decode() not read past the end of the buffer.
[FreeBSD/stable/10.git] / sbin / camcontrol / modeedit.c
1 /*-
2  * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
3  * Derived from work done by Julian Elischer <julian@tfs.com,
4  * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer,
12  *    without modification, immediately at the beginning of the file.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution. 
16  *    
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <sys/queue.h>
33 #include <sys/types.h>
34
35 #include <assert.h>
36 #include <ctype.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <stdio.h>
42 #include <sysexits.h>
43 #include <unistd.h>
44
45 #include <cam/scsi/scsi_all.h>
46 #include <cam/cam.h>
47 #include <cam/cam_ccb.h>
48 #include <camlib.h>
49 #include "camcontrol.h"
50
51 #define DEFAULT_SCSI_MODE_DB    "/usr/share/misc/scsi_modes"
52 #define DEFAULT_EDITOR          "vi"
53 #define MAX_FORMAT_SPEC         4096    /* Max CDB format specifier. */
54 #define MAX_PAGENUM_LEN         10      /* Max characters in page num. */
55 #define MAX_PAGENAME_LEN        64      /* Max characters in page name. */
56 #define PAGEDEF_START           '{'     /* Page definition delimiter. */
57 #define PAGEDEF_END             '}'     /* Page definition delimiter. */
58 #define PAGENAME_START          '"'     /* Page name delimiter. */
59 #define PAGENAME_END            '"'     /* Page name delimiter. */
60 #define PAGEENTRY_END           ';'     /* Page entry terminator (optional). */
61 #define MAX_COMMAND_SIZE        255     /* Mode/Log sense data buffer size. */
62 #define PAGE_CTRL_SHIFT         6       /* Bit offset to page control field. */
63
64
65 /* Macros for working with mode pages. */
66 #define MODE_PAGE_HEADER(mh)                                            \
67         (struct scsi_mode_page_header *)find_mode_page_6(mh)
68
69 #define MODE_PAGE_DATA(mph)                                             \
70         (u_int8_t *)(mph) + sizeof(struct scsi_mode_page_header)
71
72
73 struct editentry {
74         STAILQ_ENTRY(editentry) link;
75         char    *name;
76         char    type;
77         int     editable;
78         int     size;
79         union {
80                 int     ivalue;
81                 char    *svalue;
82         } value;
83 };
84 static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
85 static int editlist_changed = 0;        /* Whether any entries were changed. */
86
87 struct pagename {
88         SLIST_ENTRY(pagename) link;
89         int pagenum;
90         char *name;
91 };
92 static SLIST_HEAD(, pagename) namelist; /* Page number to name mappings. */
93
94 static char format[MAX_FORMAT_SPEC];    /* Buffer for scsi cdb format def. */
95
96 static FILE *edit_file = NULL;          /* File handle for edit file. */
97 static char edit_path[] = "/tmp/camXXXXXX";
98
99
100 /* Function prototypes. */
101 static void              editentry_create(void *hook, int letter, void *arg,
102                                           int count, char *name);
103 static void              editentry_update(void *hook, int letter, void *arg,
104                                           int count, char *name);
105 static int               editentry_save(void *hook, char *name);
106 static struct editentry *editentry_lookup(char *name);
107 static int               editentry_set(char *name, char *newvalue,
108                                        int editonly);
109 static void              editlist_populate(struct cam_device *device,
110                                            int modepage, int page_control,
111                                            int dbd, int retries, int timeout);
112 static void              editlist_save(struct cam_device *device, int modepage,
113                                        int page_control, int dbd, int retries,
114                                        int timeout);
115 static void              nameentry_create(int pagenum, char *name);
116 static struct pagename  *nameentry_lookup(int pagenum);
117 static int               load_format(const char *pagedb_path, int page);
118 static int               modepage_write(FILE *file, int editonly);
119 static int               modepage_read(FILE *file);
120 static void              modepage_edit(void);
121 static void              modepage_dump(struct cam_device *device, int page,
122                                        int page_control, int dbd, int retries,
123                                        int timeout);
124 static 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
140 static void
141 editentry_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
162 static void
163 editentry_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
190 static int
191 editentry_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
224 static struct editentry *
225 editentry_lookup(char *name)
226 {
227         struct editentry *scan;
228
229         assert(name != NULL);
230
231         STAILQ_FOREACH(scan, &editlist, link) {
232                 if (strcasecmp(scan->name, name) == 0)
233                         return (scan);
234         }
235
236         /* Not found during list traversal. */
237         return (NULL);
238 }
239
240 static int
241 editentry_set(char *name, char *newvalue, int editonly)
242 {
243         struct editentry *dest; /* Modepage entry to update. */
244         char *cval;             /* Pointer to new string value. */
245         char *convertend;       /* End-of-conversion pointer. */
246         int ival;               /* New integral value. */
247         int resolution;         /* Resolution in bits for integer conversion. */
248
249 /*
250  * Macro to determine the maximum value of the given size for the current
251  * resolution.
252  * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
253  *     currently workaround it (even for int64's), so we have to kludge it.
254  */
255 #define RESOLUTION_MAX(size) ((resolution * (size) == 32)?              \
256         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
327 static void
328 nameentry_create(int pagenum, char *name) {
329         struct pagename *newentry;
330
331         if (pagenum < 0 || name == NULL || name[0] == '\0')
332                 return;
333
334         /* Allocate memory for the new entry and a copy of the entry name. */
335         if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
336             (newentry->name = strdup(name)) == NULL)
337                 err(EX_OSERR, NULL);
338
339         /* Trim any trailing whitespace for the page name. */
340         RTRIM(newentry->name);
341
342         newentry->pagenum = pagenum;
343         SLIST_INSERT_HEAD(&namelist, newentry, link);
344 }
345
346 static struct pagename *
347 nameentry_lookup(int pagenum) {
348         struct pagename *scan;
349
350         SLIST_FOREACH(scan, &namelist, link) {
351                 if (pagenum == scan->pagenum)
352                         return (scan);
353         }
354
355         /* Not found during list traversal. */
356         return (NULL);
357 }
358
359 static int
360 load_format(const char *pagedb_path, int page)
361 {
362         FILE *pagedb;
363         char str_pagenum[MAX_PAGENUM_LEN];
364         char str_pagename[MAX_PAGENAME_LEN];
365         int pagenum;
366         int depth;                      /* Quoting depth. */
367         int found;
368         int lineno;
369         enum { LOCATE, PAGENAME, PAGEDEF } state;
370         int ch;
371         char c;
372
373 #define SETSTATE_LOCATE do {                                            \
374         str_pagenum[0] = '\0';                                          \
375         str_pagename[0] = '\0';                                         \
376         pagenum = -1;                                                   \
377         state = LOCATE;                                                 \
378 } while (0)
379
380 #define SETSTATE_PAGENAME do {                                          \
381         str_pagename[0] = '\0';                                         \
382         state = PAGENAME;                                               \
383 } while (0)
384
385 #define SETSTATE_PAGEDEF do {                                           \
386         format[0] = '\0';                                               \
387         state = PAGEDEF;                                                \
388 } while (0)
389
390 #define UPDATE_LINENO do {                                              \
391         if (c == '\n')                                                  \
392                 lineno++;                                               \
393 } while (0)
394
395 #define BUFFERFULL(buffer)      (strlen(buffer) + 1 >= sizeof(buffer))
396
397         if ((pagedb = fopen(pagedb_path, "r")) == NULL)
398                 returnerr(ENOENT);
399
400         SLIST_INIT(&namelist);
401
402         c = '\0';
403         depth = 0;
404         lineno = 0;
405         found = 0;
406         SETSTATE_LOCATE;
407         while ((ch = fgetc(pagedb)) != EOF) {
408
409                 /* Keep a line count to make error messages more useful. */
410                 UPDATE_LINENO;
411
412                 /* Skip over comments anywhere in the mode database. */
413                 if (ch == '#') {
414                         do {
415                                 ch = fgetc(pagedb);
416                         } while (ch != '\n' && ch != EOF);
417                         UPDATE_LINENO;
418                         continue;
419                 }
420                 c = ch;
421
422                 /* Strip out newline characters. */
423                 if (c == '\n')
424                         continue;
425
426                 /* Keep track of the nesting depth for braces. */
427                 if (c == PAGEDEF_START)
428                         depth++;
429                 else if (c == PAGEDEF_END) {
430                         depth--;
431                         if (depth < 0) {
432                                 errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
433                                     lineno, "mismatched bracket");
434                         }
435                 }
436
437                 switch (state) {
438                 case LOCATE:
439                         /*
440                          * Locate the page the user is interested in, skipping
441                          * all others.
442                          */
443                         if (isspace(c)) {
444                                 /* Ignore all whitespace between pages. */
445                                 break;
446                         } else if (depth == 0 && c == PAGEENTRY_END) {
447                                 /*
448                                  * A page entry terminator will reset page
449                                  * scanning (useful for assigning names to
450                                  * modes without providing a mode definition).
451                                  */
452                                 /* Record the name of this page. */
453                                 pagenum = strtol(str_pagenum, NULL, 0);
454                                 nameentry_create(pagenum, str_pagename);
455                                 SETSTATE_LOCATE;
456                         } else if (depth == 0 && c == PAGENAME_START) {
457                                 SETSTATE_PAGENAME;
458                         } else if (c == PAGEDEF_START) {
459                                 pagenum = strtol(str_pagenum, NULL, 0);
460                                 if (depth == 1) {
461                                         /* Record the name of this page. */
462                                         nameentry_create(pagenum, str_pagename);
463                                         /*
464                                          * Only record the format if this is
465                                          * the page we are interested in.
466                                          */
467                                         if (page == pagenum && !found)
468                                                 SETSTATE_PAGEDEF;
469                                 }
470                         } else if (c == PAGEDEF_END) {
471                                 /* Reset the processor state. */
472                                 SETSTATE_LOCATE;
473                         } else if (depth == 0 && ! BUFFERFULL(str_pagenum)) {
474                                 strncat(str_pagenum, &c, 1);
475                         } else if (depth == 0) {
476                                 errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
477                                     lineno, "page identifier exceeds",
478                                     sizeof(str_pagenum) - 1, "characters");
479                         }
480                         break;
481
482                 case PAGENAME:
483                         if (c == PAGENAME_END) {
484                                 /*
485                                  * Return to LOCATE state without resetting the
486                                  * page number buffer.
487                                  */
488                                 state = LOCATE;
489                         } else if (! BUFFERFULL(str_pagename)) {
490                                 strncat(str_pagename, &c, 1);
491                         } else {
492                                 errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
493                                     lineno, "page name exceeds",
494                                     sizeof(str_pagenum) - 1, "characters");
495                         }
496                         break;
497
498                 case PAGEDEF:
499                         /*
500                          * Transfer the page definition into a format buffer
501                          * suitable for use with CDB encoding/decoding routines.
502                          */
503                         if (depth == 0) {
504                                 found = 1;
505                                 SETSTATE_LOCATE;
506                         } else if (! BUFFERFULL(format)) {
507                                 strncat(format, &c, 1);
508                         } else {
509                                 errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
510                                     lineno, "page definition exceeds",
511                                     sizeof(format) - 1, "characters");
512                         }
513                         break;
514
515                 default:
516                         ; /* NOTREACHED */
517                 }
518
519                 /* Repeat processing loop with next character. */
520         }
521
522         if (ferror(pagedb))
523                 err(EX_OSFILE, "%s", pagedb_path);
524
525         /* Close the SCSI page database. */
526         fclose(pagedb);
527
528         if (!found)                     /* Never found a matching page. */
529                 returnerr(ESRCH);
530
531         return (0);
532 }
533
534 static void
535 editlist_populate(struct cam_device *device, int modepage, int page_control,
536                   int dbd, int retries, int timeout)
537 {
538         u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
539         u_int8_t *mode_pars;            /* Pointer to modepage params. */
540         struct scsi_mode_header_6 *mh;  /* Location of mode header. */
541         struct scsi_mode_page_header *mph;
542
543         STAILQ_INIT(&editlist);
544
545         /* Fetch changeable values; use to build initial editlist. */
546         mode_sense(device, modepage, 1, dbd, retries, timeout, data,
547                    sizeof(data));
548
549         mh = (struct scsi_mode_header_6 *)data;
550         mph = MODE_PAGE_HEADER(mh);
551         mode_pars = MODE_PAGE_DATA(mph);
552
553         /* Decode the value data, creating edit_entries for each value. */
554         buff_decode_visit(mode_pars, mh->data_length, format,
555             editentry_create, 0);
556
557         /* Fetch the current/saved values; use to set editentry values. */
558         mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
559                    sizeof(data));
560         buff_decode_visit(mode_pars, mh->data_length, format,
561             editentry_update, 0);
562 }
563
564 static void
565 editlist_save(struct cam_device *device, int modepage, int page_control,
566               int dbd, int retries, int timeout)
567 {
568         u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
569         u_int8_t *mode_pars;            /* Pointer to modepage params. */
570         struct scsi_mode_header_6 *mh;  /* Location of mode header. */
571         struct scsi_mode_page_header *mph;
572
573         /* Make sure that something changed before continuing. */
574         if (! editlist_changed)
575                 return;
576
577         /*
578          * Preload the CDB buffer with the current mode page data.
579          * XXX If buff_encode_visit would return the number of bytes encoded
580          *     we *should* use that to build a header from scratch. As it is
581          *     now, we need mode_sense to find out the page length.
582          */
583         mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
584                    sizeof(data));
585
586         /* Initial headers & offsets. */
587         mh = (struct scsi_mode_header_6 *)data;
588         mph = MODE_PAGE_HEADER(mh);
589         mode_pars = MODE_PAGE_DATA(mph);
590
591         /* Encode the value data to be passed back to the device. */
592         buff_encode_visit(mode_pars, mh->data_length, format,
593             editentry_save, 0);
594
595         /* Eliminate block descriptors. */
596         bcopy(mph, ((u_int8_t *)mh) + sizeof(*mh),
597             sizeof(*mph) + mph->page_length);
598
599         /* Recalculate headers & offsets. */
600         mh->blk_desc_len = 0;           /* No block descriptors. */
601         mh->dev_spec = 0;               /* Clear device-specific parameters. */
602         mph = MODE_PAGE_HEADER(mh);
603         mode_pars = MODE_PAGE_DATA(mph);
604
605         mph->page_code &= SMS_PAGE_CODE;/* Isolate just the page code. */
606         mh->data_length = 0;            /* Reserved for MODE SELECT command. */
607
608         /*
609          * Write the changes back to the device. If the user editted control
610          * page 3 (saved values) then request the changes be permanently
611          * recorded.
612          */
613         mode_select(device,
614             (page_control << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
615             retries, timeout, (u_int8_t *)mh,
616             sizeof(*mh) + mh->blk_desc_len + sizeof(*mph) + mph->page_length);
617 }
618
619 static int
620 modepage_write(FILE *file, int editonly)
621 {
622         struct editentry *scan;
623         int written = 0;
624
625         STAILQ_FOREACH(scan, &editlist, link) {
626                 if (scan->editable || !editonly) {
627                         written++;
628                         if (scan->type == 'c' || scan->type == 'z') {
629                                 fprintf(file, "%s:  %s\n", scan->name,
630                                     scan->value.svalue);
631                         } else {
632                                 fprintf(file, "%s:  %d\n", scan->name,
633                                     scan->value.ivalue);
634                         }
635                 }
636         }
637         return (written);
638 }
639
640 static int
641 modepage_read(FILE *file)
642 {
643         char *buffer;                   /* Pointer to dynamic line buffer.  */
644         char *line;                     /* Pointer to static fgetln buffer. */
645         char *name;                     /* Name portion of the line buffer. */
646         char *value;                    /* Value portion of line buffer.    */
647         size_t length;                  /* Length of static fgetln buffer.  */
648
649 #define ABORT_READ(message, param) do {                                 \
650         warnx(message, param);                                          \
651         free(buffer);                                                   \
652         returnerr(EAGAIN);                                              \
653 } while (0)
654
655         while ((line = fgetln(file, &length)) != NULL) {
656                 /* Trim trailing whitespace (including optional newline). */
657                 while (length > 0 && isspace(line[length - 1]))
658                         length--;
659
660                 /* Allocate a buffer to hold the line + terminating null. */
661                 if ((buffer = malloc(length + 1)) == NULL)
662                         err(EX_OSERR, NULL);
663                 memcpy(buffer, line, length);
664                 buffer[length] = '\0';
665
666                 /* Strip out comments. */
667                 if ((value = strchr(buffer, '#')) != NULL)
668                         *value = '\0';
669
670                 /* The name is first in the buffer. Trim whitespace.*/
671                 name = buffer;
672                 RTRIM(name);
673                 while (isspace(*name))
674                         name++;
675
676                 /* Skip empty lines. */
677                 if (strlen(name) == 0)
678                         continue;
679
680                 /* The name ends at the colon; the value starts there. */
681                 if ((value = strrchr(buffer, ':')) == NULL)
682                         ABORT_READ("no value associated with %s", name);
683                 *value = '\0';                  /* Null-terminate name. */
684                 value++;                        /* Value starts afterwards. */
685
686                 /* Trim leading and trailing whitespace. */
687                 RTRIM(value);
688                 while (isspace(*value))
689                         value++;
690
691                 /* Make sure there is a value left. */
692                 if (strlen(value) == 0)
693                         ABORT_READ("no value associated with %s", name);
694
695                 /* Update our in-memory copy of the modepage entry value. */
696                 if (editentry_set(name, value, 1) != 0) {
697                         if (errno == ENOENT) {
698                                 /* No entry by the name. */
699                                 ABORT_READ("no such modepage entry \"%s\"",
700                                     name);
701                         } else if (errno == EINVAL) {
702                                 /* Invalid value. */
703                                 ABORT_READ("Invalid value for entry \"%s\"",
704                                     name);
705                         } else if (errno == ERANGE) {
706                                 /* Value out of range for entry type. */
707                                 ABORT_READ("value out of range for %s", name);
708                         } else if (errno == EPERM) {
709                                 /* Entry is not editable; not fatal. */
710                                 warnx("modepage entry \"%s\" is read-only; "
711                                     "skipping.", name);
712                         }
713                 }
714
715                 free(buffer);
716         }
717         return (ferror(file)? -1: 0);
718
719 #undef ABORT_READ
720 }
721
722 static void
723 modepage_edit(void)
724 {
725         const char *editor;
726         char *commandline;
727         int fd;
728         int written;
729
730         if (!isatty(fileno(stdin))) {
731                 /* Not a tty, read changes from stdin. */
732                 modepage_read(stdin);
733                 return;
734         }
735
736         /* Lookup editor to invoke. */
737         if ((editor = getenv("EDITOR")) == NULL)
738                 editor = DEFAULT_EDITOR;
739
740         /* Create temp file for editor to modify. */
741         if ((fd = mkstemp(edit_path)) == -1)
742                 errx(EX_CANTCREAT, "mkstemp failed");
743
744         atexit(cleanup_editfile);
745
746         if ((edit_file = fdopen(fd, "w")) == NULL)
747                 err(EX_NOINPUT, "%s", edit_path);
748
749         written = modepage_write(edit_file, 1);
750
751         fclose(edit_file);
752         edit_file = NULL;
753
754         if (written == 0) {
755                 warnx("no editable entries");
756                 cleanup_editfile();
757                 return;
758         }
759
760         /*
761          * Allocate memory to hold the command line (the 2 extra characters
762          * are to hold the argument separator (a space), and the terminating
763          * null character.
764          */
765         commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
766         if (commandline == NULL)
767                 err(EX_OSERR, NULL);
768         sprintf(commandline, "%s %s", editor, edit_path);
769
770         /* Invoke the editor on the temp file. */
771         if (system(commandline) == -1)
772                 err(EX_UNAVAILABLE, "could not invoke %s", editor);
773         free(commandline);
774
775         if ((edit_file = fopen(edit_path, "r")) == NULL)
776                 err(EX_NOINPUT, "%s", edit_path);
777
778         /* Read any changes made to the temp file. */
779         modepage_read(edit_file);
780
781         cleanup_editfile();
782 }
783
784 static void
785 modepage_dump(struct cam_device *device, int page, int page_control, int dbd,
786               int retries, int timeout)
787 {
788         u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
789         u_int8_t *mode_pars;            /* Pointer to modepage params. */
790         struct scsi_mode_header_6 *mh;  /* Location of mode header. */
791         struct scsi_mode_page_header *mph;
792         int indx;                       /* Index for scanning mode params. */
793
794         mode_sense(device, page, page_control, dbd, retries, timeout, data,
795                    sizeof(data));
796
797         mh = (struct scsi_mode_header_6 *)data;
798         mph = MODE_PAGE_HEADER(mh);
799         mode_pars = MODE_PAGE_DATA(mph);
800
801         /* Print the raw mode page data with newlines each 8 bytes. */
802         for (indx = 0; indx < mph->page_length; indx++) {
803                 printf("%02x%c",mode_pars[indx],
804                     (((indx + 1) % 8) == 0) ? '\n' : ' ');
805         }
806         putchar('\n');
807 }
808
809 static void
810 cleanup_editfile(void)
811 {
812         if (edit_file == NULL)
813                 return;
814         if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
815                 warn("%s", edit_path);
816         edit_file = NULL;
817 }
818
819 void
820 mode_edit(struct cam_device *device, int page, int page_control, int dbd,
821           int edit, int binary, int retry_count, int timeout)
822 {
823         const char *pagedb_path;        /* Path to modepage database. */
824
825         if (edit && binary)
826                 errx(EX_USAGE, "cannot edit in binary mode.");
827
828         if (! binary) {
829                 if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
830                         pagedb_path = DEFAULT_SCSI_MODE_DB;
831
832                 if (load_format(pagedb_path, page) != 0 && (edit || verbose)) {
833                         if (errno == ENOENT) {
834                                 /* Modepage database file not found. */
835                                 warn("cannot open modepage database \"%s\"",
836                                     pagedb_path);
837                         } else if (errno == ESRCH) {
838                                 /* Modepage entry not found in database. */
839                                 warnx("modepage %d not found in database"
840                                     "\"%s\"", page, pagedb_path);
841                         }
842                         /* We can recover in display mode, otherwise we exit. */
843                         if (!edit) {
844                                 warnx("reverting to binary display only");
845                                 binary = 1;
846                         } else
847                                 exit(EX_OSFILE);
848                 }
849
850                 editlist_populate(device, page, page_control, dbd, retry_count,
851                         timeout);
852         }
853
854         if (edit) {
855                 if (page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
856                     page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
857                         errx(EX_USAGE, "it only makes sense to edit page 0 "
858                             "(current) or page 3 (saved values)");
859                 modepage_edit();
860                 editlist_save(device, page, page_control, dbd, retry_count,
861                         timeout);
862         } else if (binary || STAILQ_EMPTY(&editlist)) {
863                 /* Display without formatting information. */
864                 modepage_dump(device, page, page_control, dbd, retry_count,
865                     timeout);
866         } else {
867                 /* Display with format. */
868                 modepage_write(stdout, 0);
869         }
870 }
871
872 void
873 mode_list(struct cam_device *device, int page_control, int dbd,
874           int retry_count, int timeout)
875 {
876         u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
877         struct scsi_mode_header_6 *mh;  /* Location of mode header. */
878         struct scsi_mode_page_header *mph;
879         struct pagename *nameentry;
880         const char *pagedb_path;
881         int len;
882
883         if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
884                 pagedb_path = DEFAULT_SCSI_MODE_DB;
885
886         if (load_format(pagedb_path, 0) != 0 && verbose && errno == ENOENT) {
887                 /* Modepage database file not found. */
888                 warn("cannot open modepage database \"%s\"", pagedb_path);
889         }
890
891         /* Build the list of all mode pages by querying the "all pages" page. */
892         mode_sense(device, SMS_ALL_PAGES_PAGE, page_control, dbd, retry_count,
893             timeout, data, sizeof(data));
894
895         mh = (struct scsi_mode_header_6 *)data;
896         len = sizeof(*mh) + mh->blk_desc_len;   /* Skip block descriptors. */
897         /* Iterate through the pages in the reply. */
898         while (len < mh->data_length) {
899                 /* Locate the next mode page header. */
900                 mph = (struct scsi_mode_page_header *)
901                     ((intptr_t)mh + len);
902
903                 mph->page_code &= SMS_PAGE_CODE;
904                 nameentry = nameentry_lookup(mph->page_code);
905
906                 if (nameentry == NULL || nameentry->name == NULL)
907                         printf("0x%02x\n", mph->page_code);
908                 else
909                         printf("0x%02x\t%s\n", mph->page_code,
910                             nameentry->name); 
911                 len += mph->page_length + sizeof(*mph);
912         }
913 }