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