chiark / gitweb /
8b9fbe06814c0f8cc531b9720018322336aee38c
[elogind.git] / extras / scsi_id / scsi_serial.c
1 /*
2  * scsi_serial.c
3  *
4  * Code related to requesting and getting an id from a scsi device
5  *
6  * Copyright (C) IBM Corp. 2003
7  *
8  *  This library is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License as
10  *  published by the Free Software Foundation; either version 2.1 of the
11  *  License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful, but
14  *  WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with this library; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21  *  USA
22  */
23
24 #include <sys/types.h>
25 #include <sys/ioctl.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <fcntl.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <syslog.h>
33 #include <linux/compiler.h> /* need __user when built via klibc */
34 #include <scsi/sg.h>
35 #include <sysfs/libsysfs.h>
36 #include "scsi_id.h"
37 #include "scsi.h"
38
39 /*
40  * A priority based list of id, naa, and binary/ascii for the identifier
41  * descriptor in VPD page 0x83.
42  *
43  * Brute force search for a match starting with the first value in the
44  * following id_search_list. This is not a performance issue, since there
45  * is normally one or some small number of descriptors.
46  */
47 static const struct scsi_id_search_values id_search_list[] = {
48         { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG_EXTENDED,  SCSI_ID_BINARY },
49         { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG_EXTENDED,  SCSI_ID_ASCII },
50         { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG,   SCSI_ID_BINARY },
51         { SCSI_ID_NAA,  SCSI_ID_NAA_IEEE_REG,   SCSI_ID_ASCII },
52         /*
53          * Devices already exist using NAA values that are now marked
54          * reserved. These should not conflict with other values, or it is
55          * a bug in the device. As long as we find the IEEE extended one
56          * first, we really don't care what other ones are used. Using
57          * don't care here means that a device that returns multiple
58          * non-IEEE descriptors in a random order will get different
59          * names.
60          */
61         { SCSI_ID_NAA,  SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
62         { SCSI_ID_NAA,  SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
63         { SCSI_ID_EUI_64,       SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
64         { SCSI_ID_EUI_64,       SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
65         { SCSI_ID_T10_VENDOR,   SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
66         { SCSI_ID_T10_VENDOR,   SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
67         { SCSI_ID_VENDOR_SPECIFIC,      SCSI_ID_NAA_DONT_CARE,  SCSI_ID_BINARY },
68         { SCSI_ID_VENDOR_SPECIFIC,      SCSI_ID_NAA_DONT_CARE,  SCSI_ID_ASCII },
69 };
70
71 static const char hex_str[]="0123456789abcdef";
72
73 /*
74  * Values returned in the result/status, only the ones used by the code
75  * are used here.
76  */
77
78 #define DID_NO_CONNECT 0x01     /* Unable to connect before timeout */
79
80 #define DID_BUS_BUSY 0x02       /* Bus remain busy until timeout */
81 #define DID_TIME_OUT 0x03       /* Timed out for some other reason */
82
83 #define DRIVER_TIMEOUT 0x06
84 #define DRIVER_SENSE 0x08       /* Sense_buffer has been set */
85
86 /* The following "category" function returns one of the following */
87 #define SG_ERR_CAT_CLEAN        0      /* No errors or other information */
88 #define SG_ERR_CAT_MEDIA_CHANGED        1 /* interpreted from sense buffer */
89 #define SG_ERR_CAT_RESET        2      /* interpreted from sense buffer */
90 #define SG_ERR_CAT_TIMEOUT      3
91 #define SG_ERR_CAT_RECOVERED    4  /* Successful command after recovered err */
92 #define SG_ERR_CAT_SENSE        98     /* Something else in the sense buffer */
93 #define SG_ERR_CAT_OTHER        99     /* Some other error/warning */
94
95 static int sg_err_category_new(int scsi_status, int msg_status, int
96                                host_status, int driver_status, const
97                                unsigned char *sense_buffer, int sb_len)
98 {
99         scsi_status &= 0x7e;
100
101         /*
102          * XXX change to return only two values - failed or OK.
103          */
104
105         /*
106          * checks msg_status
107          */
108         if (!scsi_status && !msg_status && !host_status && !driver_status)
109                 return SG_ERR_CAT_CLEAN;
110
111         if ((scsi_status == SCSI_CHECK_CONDITION) ||
112             (scsi_status == SCSI_COMMAND_TERMINATED) ||
113             ((driver_status & 0xf) == DRIVER_SENSE)) {
114                 if (sense_buffer && (sb_len > 2)) {
115                         int sense_key;
116                         unsigned char asc;
117
118                         if (sense_buffer[0] & 0x2) {
119                                 sense_key = sense_buffer[1] & 0xf;
120                                 asc = sense_buffer[2];
121                         } else {
122                                 sense_key = sense_buffer[2] & 0xf;
123                                 asc = (sb_len > 12) ? sense_buffer[12] : 0;
124                         }
125
126                         if (sense_key == RECOVERED_ERROR)
127                                 return SG_ERR_CAT_RECOVERED;
128                         else if (sense_key == UNIT_ATTENTION) {
129                                 if (0x28 == asc)
130                                         return SG_ERR_CAT_MEDIA_CHANGED;
131                                 if (0x29 == asc)
132                                         return SG_ERR_CAT_RESET;
133                         }
134                 }
135                 return SG_ERR_CAT_SENSE;
136         }
137         if (!host_status) {
138                 if ((host_status == DID_NO_CONNECT) ||
139                     (host_status == DID_BUS_BUSY) ||
140                     (host_status == DID_TIME_OUT))
141                         return SG_ERR_CAT_TIMEOUT;
142         }
143         if (!driver_status) {
144                 if (driver_status == DRIVER_TIMEOUT)
145                         return SG_ERR_CAT_TIMEOUT;
146         }
147         return SG_ERR_CAT_OTHER;
148 }
149
150 static int sg_err_category3(struct sg_io_hdr *hp)
151 {
152         return sg_err_category_new(hp->status, hp->msg_status,
153                                    hp->host_status, hp->driver_status,
154                                    hp->sbp, hp->sb_len_wr);
155 }
156
157 static int scsi_dump_sense(struct sysfs_device *scsi_dev, struct sg_io_hdr *io)
158 {
159         unsigned char *sense_buffer;
160         int s;
161         int sb_len;
162         int code;
163         int sense_class;
164         int sense_key;
165         int descriptor_format;
166         int asc, ascq;
167 #ifdef DUMP_SENSE
168         char out_buffer[256];
169         int i, j;
170 #endif
171
172         /*
173          * Figure out and print the sense key, asc and ascq.
174          *
175          * If you want to suppress these for a particular drive model, add
176          * a black list entry in the scsi_id config file.
177          *
178          * XXX We probably need to: lookup the sense/asc/ascq in a retry
179          * table, and if found return 1 (after dumping the sense, asc, and
180          * ascq). So, if/when we get something like a power on/reset,
181          * we'll retry the command.
182          */
183
184         dprintf("got check condition\n");
185
186         sb_len = io->sb_len_wr;
187         if (sb_len < 1) {
188                 log_message(LOG_WARNING, "%s: sense buffer empty\n",
189                             scsi_dev->name);
190                 return -1;
191         }
192
193         sense_buffer = io->sbp;
194         sense_class = (sense_buffer[0] >> 4) & 0x07;
195         code = sense_buffer[0] & 0xf;
196
197         if (sense_class == 7) {
198                 /*
199                  * extended sense data.
200                  */
201                 s = sense_buffer[7] + 8;
202                 if (sb_len < s) {
203                         log_message(LOG_WARNING,
204                                     "%s: sense buffer too small %d bytes,"
205                                     " %d bytes too short\n", scsi_dev->name,
206                                     sb_len, s - sb_len);
207                         return -1;
208                 }
209                 if ((code == 0x0) || (code == 0x1)) {
210                         descriptor_format = 0;
211                         sense_key = sense_buffer[2] & 0xf;
212                         if (s < 14) {
213                                 /*
214                                  * Possible?
215                                  */
216                                 log_message(LOG_WARNING, "%s: sense result too"
217                                             " small %d bytes\n",
218                                             scsi_dev->name, s);
219                                 return -1;
220                         }
221                         asc = sense_buffer[12];
222                         ascq = sense_buffer[13];
223                 } else if ((code == 0x2) || (code == 0x3)) {
224                         descriptor_format = 1;
225                         sense_key = sense_buffer[1] & 0xf;
226                         asc = sense_buffer[2];
227                         ascq = sense_buffer[3];
228                 } else {
229                         log_message(LOG_WARNING,
230                                     "%s: invalid sense code 0x%x\n",
231                                     scsi_dev->name, code);
232                         return -1;
233                 }
234                 log_message(LOG_WARNING,
235                             "%s: sense key 0x%x ASC 0x%x ASCQ 0x%x\n",
236                             scsi_dev->name, sense_key, asc, ascq);
237         } else {
238                 if (sb_len < 4) {
239                         log_message(LOG_WARNING,
240                                     "%s: sense buffer too small %d bytes, %d bytes too short\n",
241                                     scsi_dev->name, sb_len, 4 - sb_len);
242                         return -1;
243                 }
244
245                 if (sense_buffer[0] < 15)
246                         log_message(LOG_WARNING, "%s: old sense key: 0x%x\n",
247                                     scsi_dev->name, sense_buffer[0] & 0x0f);
248                 else
249                         log_message(LOG_WARNING, "%s: sense = %2x %2x\n",
250                                     scsi_dev->name,  sense_buffer[0],
251                                     sense_buffer[2]);
252                 log_message(LOG_WARNING,
253                             "%s: non-extended sense class %d code 0x%0x\n",
254                             scsi_dev->name, sense_class, code);
255
256         }
257
258 #ifdef DUMP_SENSE
259         for (i = 0, j = 0; (i < s) && (j < 254); i++) {
260                 dprintf("i %d, j %d\n", i, j);
261                 out_buffer[j++] = hex_str[(sense_buffer[i] & 0xf0) >> 4];
262                 out_buffer[j++] = hex_str[sense_buffer[i] & 0x0f];
263                 out_buffer[j++] = ' ';
264         }
265         out_buffer[j] = '\0';
266         log_message(LOG_WARNING, "%s: sense dump:\n", scsi_dev->name);
267         log_message(LOG_WARNING, "%s: %s\n", scsi_dev->name, out_buffer);
268
269 #endif
270         return -1;
271 }
272
273 static int scsi_dump(struct sysfs_device *scsi_dev, struct sg_io_hdr *io)
274 {
275         if (!io->status && !io->host_status && !io->msg_status &&
276             !io->driver_status) {
277                 /*
278                  * Impossible, should not be called.
279                  */
280                 log_message(LOG_WARNING, "%s: called with no error\n",
281                             __FUNCTION__);
282                 return -1;
283         }
284
285         log_message(LOG_WARNING, "%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x\n",
286                     scsi_dev->name, io->driver_status, io->host_status,
287                     io->msg_status, io->status);
288         if (io->status == SCSI_CHECK_CONDITION)
289                 return scsi_dump_sense(scsi_dev, io);
290         else
291                 return -1;
292 }
293
294 static int scsi_inquiry(struct sysfs_device *scsi_dev, int fd, unsigned
295                         char evpd, unsigned char page, unsigned char *buf,
296                         unsigned int buflen)
297 {
298         unsigned char inq_cmd[INQUIRY_CMDLEN] =
299                 { INQUIRY_CMD, evpd, page, 0, buflen, 0 };
300         unsigned char sense[SENSE_BUFF_LEN];
301         struct sg_io_hdr io_hdr;
302         int retval;
303         int retry = 3; /* rather random */
304
305         if (buflen > SCSI_INQ_BUFF_LEN) {
306                 log_message(LOG_WARNING, "buflen %d too long\n", buflen);
307                 return -1;
308         }
309
310 resend:
311         dprintf("%s evpd %d, page 0x%x\n", scsi_dev->name, evpd, page);
312
313         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
314         io_hdr.interface_id = 'S';
315         io_hdr.cmd_len = sizeof(inq_cmd);
316         io_hdr.mx_sb_len = sizeof(sense);
317         io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
318         io_hdr.dxfer_len = buflen;
319         io_hdr.dxferp = buf;
320         io_hdr.cmdp = inq_cmd;
321         io_hdr.sbp = sense;
322         io_hdr.timeout = DEF_TIMEOUT;
323
324         if (ioctl(fd, SG_IO, &io_hdr) < 0) {
325                 log_message(LOG_WARNING, "%s: ioctl failed: %s\n",
326                             scsi_dev->name, strerror(errno));
327                 retval = -1;
328                 goto error;
329         }
330
331         retval = sg_err_category3(&io_hdr);
332
333         switch (retval) {
334                 case SG_ERR_CAT_CLEAN:
335                 case SG_ERR_CAT_RECOVERED:
336                         retval = 0;
337                         break;
338
339                 default:
340                         retval = scsi_dump(scsi_dev, &io_hdr);
341         }
342
343         if (!retval) {
344                 retval = buflen;
345         } else if (retval > 0) {
346                 if (--retry > 0) {
347                         dprintf("%s: Retrying ...\n", scsi_dev->name);
348                         goto resend;
349                 }
350                 retval = -1;
351         }
352
353 error:
354         if (retval < 0)
355                 log_message(LOG_WARNING,
356                             "%s: Unable to get INQUIRY vpd %d page 0x%x.\n",
357                             scsi_dev->name, evpd, page);
358
359         return retval;
360 }
361
362 /* Get list of supported EVPD pages */
363 static int do_scsi_page0_inquiry(struct sysfs_device *scsi_dev, int fd,
364                                  char *buffer, int len)
365 {
366         int retval;
367         char vendor[MAX_ATTR_LEN];
368
369         memset(buffer, 0, len);
370         retval = scsi_inquiry(scsi_dev, fd, 1, 0x0, buffer, len);
371         if (retval < 0)
372                 return 1;
373
374         if (buffer[1] != 0) {
375                 log_message(LOG_WARNING, "%s: page 0 not available.\n",
376                             scsi_dev->name);
377                 return 1;
378         }
379         if (buffer[3] > len) {
380                 log_message(LOG_WARNING, "%s: page 0 buffer too long %d\n",
381                            scsi_dev->name,  buffer[3]);
382                 return 1;
383         }
384
385         /*
386          * Following check is based on code once included in the 2.5.x
387          * kernel.
388          *
389          * Some ill behaved devices return the standard inquiry here
390          * rather than the evpd data, snoop the data to verify.
391          */
392         if (buffer[3] > MODEL_LENGTH) {
393                 /*
394                  * If the vendor id appears in the page assume the page is
395                  * invalid.
396                  */
397                 if (sysfs_get_attr(scsi_dev->path, "vendor", vendor,
398                                    MAX_ATTR_LEN)) {
399                         log_message(LOG_WARNING,
400                                     "%s: cannot get model attribute\n",
401                                     scsi_dev->name);
402                         return 1;
403                 }
404                 if (!strncmp(&buffer[VENDOR_LENGTH], vendor, VENDOR_LENGTH)) {
405                         log_message(LOG_WARNING, "%s: invalid page0 data\n",
406                                     scsi_dev->name);
407                         return 1;
408                 }
409         }
410         return 0;
411 }
412
413 /*
414  * The caller checks that serial is long enough to include the vendor +
415  * model.
416  */
417 static int prepend_vendor_model(struct sysfs_device *scsi_dev, char *serial)
418 {
419         char attr[MAX_ATTR_LEN];
420         int ind;
421
422         if (sysfs_get_attr(scsi_dev->path, "vendor", attr, MAX_ATTR_LEN)) {
423                 log_message(LOG_WARNING, "%s: cannot get vendor attribute\n",
424                             scsi_dev->name);
425                 return 1;
426         }
427         strncpy(serial, attr, VENDOR_LENGTH);
428         ind = strlen(serial) - 1;
429         /*
430          * Remove sysfs added newlines.
431          */
432         if (serial[ind] == '\n')
433                 serial[ind] = '\0';
434
435         if (sysfs_get_attr(scsi_dev->path, "model", attr, MAX_ATTR_LEN)) {
436                 log_message(LOG_WARNING, "%s: cannot get model attribute\n",
437                             scsi_dev->name);
438                 return 1;
439         }
440         strncat(serial, attr, MODEL_LENGTH);
441         ind = strlen(serial) - 1;
442         if (serial[ind] == '\n')
443                 serial[ind] = '\0';
444         else
445                 ind++;
446
447         /*
448          * This is not a complete check, since we are using strncat/cpy
449          * above, ind will never be too large.
450          */
451         if (ind != (VENDOR_LENGTH + MODEL_LENGTH)) {
452                 log_message(LOG_WARNING, "%s: expected length %d, got length %d\n",
453                             scsi_dev->name, (VENDOR_LENGTH + MODEL_LENGTH),
454                             ind);
455                 return 1;
456         }
457         return ind;
458 }
459
460 /**
461  * check_fill_0x83_id - check the page 0x83 id, if OK allocate and fill
462  * serial number.
463  **/
464 static int check_fill_0x83_id(struct sysfs_device *scsi_dev, char
465                               *page_83, const struct scsi_id_search_values
466                               *id_search, char *serial, int max_len)
467 {
468         int i, j, len;
469
470         /*
471          * ASSOCIATION must be with the device (value 0)
472          */
473         if ((page_83[1] & 0x30) != 0)
474                 return 1;
475
476         if ((page_83[1] & 0x0f) != id_search->id_type)
477                 return 1;
478
479         /*
480          * Possibly check NAA sub-type.
481          */
482         if ((id_search->naa_type != SCSI_ID_NAA_DONT_CARE) &&
483             (id_search->naa_type != (page_83[4] & 0xf0) >> 4))
484                 return 1;
485
486         /*
487          * Check for matching code set - ASCII or BINARY.
488          */
489         if ((page_83[0] & 0x0f) != id_search->code_set)
490                 return 1;
491
492         /*
493          * page_83[3]: identifier length
494          */
495         len = page_83[3];
496         if ((page_83[0] & 0x0f) != SCSI_ID_ASCII)
497                 /*
498                  * If not ASCII, use two bytes for each binary value.
499                  */
500                 len *= 2;
501
502         /*
503          * Add one byte for the NUL termination, and one for the id_type.
504          */
505         len += 2;
506         if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
507                 len += VENDOR_LENGTH + MODEL_LENGTH;
508
509         if (max_len < len) {
510                 log_message(LOG_WARNING, "%s: length %d too short - need %d\n",
511                             scsi_dev->name, max_len, len);
512                 return 1;
513         }
514
515         serial[0] = hex_str[id_search->id_type];
516
517         /*
518          * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before
519          * the id since it is not unique across all vendors and models,
520          * this differs from SCSI_ID_T10_VENDOR, where the vendor is
521          * included in the identifier.
522          */
523         if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
524                 if (prepend_vendor_model(scsi_dev, &serial[1]) < 0) {
525                         dprintf("prepend failed\n");
526                         return 1;
527                 }
528
529         i = 4; /* offset to the start of the identifier */
530         j = strlen(serial);
531         if ((page_83[0] & 0x0f) == SCSI_ID_ASCII) {
532                 /*
533                  * ASCII descriptor.
534                  */
535                 while (i < (4 + page_83[3]))
536                         serial[j++] = page_83[i++];
537         } else {
538                 /*
539                  * Binary descriptor, convert to ASCII, using two bytes of
540                  * ASCII for each byte in the page_83.
541                  */
542                 while (i < (4 + page_83[3])) {
543                         serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
544                         serial[j++] = hex_str[page_83[i] & 0x0f];
545                         i++;
546                 }
547         }
548         return 0;
549 }
550
551 /* Get device identification VPD page */
552 static int do_scsi_page83_inquiry(struct sysfs_device *scsi_dev, int fd,
553                                   char *serial, int len)
554 {
555         int retval;
556         unsigned int id_ind, j;
557         unsigned char page_83[SCSI_INQ_BUFF_LEN];
558
559         memset(page_83, 0, SCSI_INQ_BUFF_LEN);
560         retval = scsi_inquiry(scsi_dev, fd, 1, 0x83, page_83,
561                               SCSI_INQ_BUFF_LEN);
562         if (retval < 0)
563                 return 1;
564
565         if (page_83[1] != 0x83) {
566                 log_message(LOG_WARNING, "%s: Invalid page 0x83\n",
567                             scsi_dev->name);
568                 return 1;
569         }
570         
571         /*
572          * XXX Some devices (IBM 3542) return all spaces for an identifier if
573          * the LUN is not actually configured. This leads to identifers of
574          * the form: "1            ".
575          */
576
577         /*
578          * Search for a match in the prioritized id_search_list.
579          */
580         for (id_ind = 0;
581              id_ind < sizeof(id_search_list)/sizeof(id_search_list[0]);
582              id_ind++) {
583                 /*
584                  * Examine each descriptor returned. There is normally only
585                  * one or a small number of descriptors.
586                  */
587                 for (j = 4; j <= (unsigned int)page_83[3] + 3; j += page_83[j + 3] + 4) {
588                         retval = check_fill_0x83_id(scsi_dev, &page_83[j],
589                                                     &id_search_list[id_ind],
590                                                     serial, len);
591                         dprintf("%s id desc %d/%d/%d\n", scsi_dev->name,
592                                 id_search_list[id_ind].id_type,
593                                 id_search_list[id_ind].naa_type,
594                                 id_search_list[id_ind].code_set);
595                         if (!retval) {
596                                 dprintf("       used\n");
597                                 return retval;
598                         } else if (retval < 0) {
599                                 dprintf("       failed\n");
600                                 return retval;
601                         } else {
602                                 dprintf("       not used\n");
603                         }
604                 }
605         }
606         return 1;
607 }
608
609 /* Get unit serial number VPD page */
610 static int do_scsi_page80_inquiry(struct sysfs_device *scsi_dev, int fd,
611                                   char *serial, int max_len)
612 {
613         int retval;
614         int ser_ind;
615         int i;
616         int len;
617         unsigned char buf[SCSI_INQ_BUFF_LEN];
618
619         memset(buf, 0, SCSI_INQ_BUFF_LEN);
620         retval = scsi_inquiry(scsi_dev, fd, 1, 0x80, buf, SCSI_INQ_BUFF_LEN);
621         if (retval < 0)
622                 return retval;
623
624         if (buf[1] != 0x80) {
625                 log_message(LOG_WARNING, "%s: Invalid page 0x80\n",
626                             scsi_dev->name);
627                 return 1;
628         }
629
630         len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3];
631         if (max_len < len) {
632                 log_message(LOG_WARNING, "%s: length %d too short - need %d\n",
633                             scsi_dev->name, max_len, len);
634                 return 1;
635         }
636         /*
637          * Prepend 'S' to avoid unlikely collision with page 0x83 vendor
638          * specific type where we prepend '0' + vendor + model.
639          */
640         serial[0] = 'S';
641         ser_ind = prepend_vendor_model(scsi_dev, &serial[1]);
642         if (ser_ind < 0)
643                 return 1;
644         len = buf[3];
645         for (i = 4; i < len + 4; i++, ser_ind++)
646                 serial[ser_ind] = buf[i];
647         return 0;
648 }
649
650 int scsi_get_serial (struct sysfs_device *scsi_dev, const char *devname,
651                      int page_code, char *serial, int len)
652 {
653         unsigned char page0[SCSI_INQ_BUFF_LEN];
654         int fd;
655         int ind;
656         int retval;
657
658         memset(serial, 0, len);
659         dprintf("opening %s\n", devname);
660         fd = open(devname, O_RDONLY | O_NONBLOCK);
661         if (fd < 0) {
662                 log_message(LOG_WARNING, "%s: cannot open %s: %s\n",
663                             scsi_dev->name, devname, strerror(errno));
664                 return 1;
665         }
666
667         if (page_code == 0x80) {
668                 if (do_scsi_page80_inquiry(scsi_dev, fd, serial, len)) {
669                         retval = 1;
670                         goto completed;
671                 } else  {
672                         retval = 0;
673                         goto completed;
674                 }
675         } else if (page_code == 0x83) {
676                 if (do_scsi_page83_inquiry(scsi_dev, fd, serial, len)) {
677                         retval = 1;
678                         goto completed;
679                 } else  {
680                         retval = 0;
681                         goto completed;
682                 }
683         } else if (page_code != 0x00) {
684                 log_message(LOG_WARNING, "%s: unsupported page code 0x%d\n",
685                             scsi_dev->name, page_code);
686                 return 1;
687         }
688
689         /*
690          * Get page 0, the page of the pages. By default, try from best to
691          * worst of supported pages: 0x83 then 0x80.
692          */
693         if (do_scsi_page0_inquiry(scsi_dev, fd, page0, SCSI_INQ_BUFF_LEN)) {
694                 /*
695                  * Don't try anything else. Black list if a specific page
696                  * should be used for this vendor+model, or maybe have an
697                  * optional fall-back to page 0x80 or page 0x83.
698                  */
699                 retval = 1;
700                 goto completed;
701         }
702
703         dprintf("%s: Checking page0\n", scsi_dev->name);
704
705         for (ind = 4; ind <= page0[3] + 3; ind++)
706                 if (page0[ind] == 0x83)
707                         if (!do_scsi_page83_inquiry(scsi_dev, fd, serial,
708                                                     len)) {
709                                 /*
710                                  * Success
711                                  */
712                                 retval = 0;
713                                 goto completed;
714                         }
715
716         for (ind = 4; ind <= page0[3] + 3; ind++)
717                 if (page0[ind] == 0x80)
718                         if (!do_scsi_page80_inquiry(scsi_dev, fd, serial,
719                                                     len)) {
720                                 /*
721                                  * Success
722                                  */
723                                 retval = 0;
724                                 goto completed;
725                         }
726         retval = 1;
727 completed:
728         if (close(fd) < 0)
729                 log_message(LOG_WARNING, "%s: close failed: %s\n", 
730                             scsi_dev->name, strerror(errno));
731         return retval;
732 }