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