]>
Commit | Line | Data |
---|---|---|
c521693b GKH |
1 | /* |
2 | * scsi_id.c | |
3 | * | |
4 | * Main section of the scsi_id program | |
5 | * | |
6 | * Copyright (C) IBM Corp. 2003 | |
3d94fb87 | 7 | * Copyright (C) SUSE Linux Products GmbH, 2006 |
c521693b | 8 | * |
3d94fb87 KS |
9 | * Author: |
10 | * Patrick Mansfield<patmans@us.ibm.com> | |
c521693b | 11 | * |
3d94fb87 KS |
12 | * This program is free software; you can redistribute it and/or modify it |
13 | * under the terms of the GNU General Public License as published by the | |
14 | * Free Software Foundation version 2 of the License. | |
c521693b GKH |
15 | */ |
16 | ||
17 | #include <stdio.h> | |
18 | #include <stdlib.h> | |
c521693b GKH |
19 | #include <unistd.h> |
20 | #include <signal.h> | |
21 | #include <fcntl.h> | |
22 | #include <errno.h> | |
23 | #include <string.h> | |
24 | #include <syslog.h> | |
25 | #include <stdarg.h> | |
26 | #include <ctype.h> | |
27 | #include <sys/stat.h> | |
c521693b | 28 | |
1aa1e248 KS |
29 | #include "../../udev.h" |
30 | #include "scsi_id.h" | |
31 | #include "scsi_id_version.h" | |
c521693b | 32 | |
1aa1e248 KS |
33 | /* temporary names for mknod */ |
34 | #define TMP_DIR "/dev" | |
35 | #define TMP_PREFIX "tmp-scsi" | |
c521693b | 36 | |
b4a2906b | 37 | static const char short_options[] = "abd:f:gip:s:uvVx"; |
b484e436 | 38 | static const char dev_short_options[] = "bgp:"; |
c521693b | 39 | |
c521693b | 40 | static int all_good; |
b4a2906b | 41 | static int always_info; |
c521693b GKH |
42 | static int dev_specified; |
43 | static int sys_specified; | |
6ecd4d1e | 44 | static char config_file[MAX_PATH_LEN] = SCSI_ID_CONFIG_FILE; |
c521693b | 45 | static int display_bus_id; |
50be1401 | 46 | static enum page_code default_page_code; |
c521693b GKH |
47 | static int use_stderr; |
48 | static int debug; | |
49 | static int hotplug_mode; | |
01f950e2 | 50 | static int reformat_serial; |
34129109 KS |
51 | static int export; |
52 | static char vendor_str[64]; | |
53 | static char model_str[64]; | |
aaff09a3 KS |
54 | static char revision_str[16]; |
55 | static char type_str[16]; | |
c521693b | 56 | |
1aa1e248 KS |
57 | #ifdef USE_LOG |
58 | void log_message(int priority, const char *format, ...) | |
c521693b | 59 | { |
1aa1e248 KS |
60 | va_list args; |
61 | static int udev_log = -1; | |
c521693b | 62 | |
1aa1e248 KS |
63 | if (udev_log == -1) { |
64 | const char *value; | |
c521693b | 65 | |
1aa1e248 KS |
66 | value = getenv("UDEV_LOG"); |
67 | if (value) | |
68 | udev_log = log_priority(value); | |
69 | else | |
70 | udev_log = LOG_ERR; | |
c521693b | 71 | } |
1aa1e248 KS |
72 | |
73 | if (priority > udev_log) | |
74 | return; | |
75 | ||
76 | va_start(args, format); | |
77 | vsyslog(priority, format, args); | |
78 | va_end(args); | |
c521693b | 79 | } |
1aa1e248 | 80 | #endif |
c521693b | 81 | |
70721db6 | 82 | static void set_str(char *to, const char *from, size_t count) |
34129109 | 83 | { |
70721db6 | 84 | size_t i, j, len; |
34129109 | 85 | |
aaff09a3 | 86 | /* strip trailing whitespace */ |
34129109 | 87 | len = strnlen(from, count); |
c907c823 | 88 | while (len && isspace(from[len-1])) |
34129109 KS |
89 | len--; |
90 | ||
aaff09a3 | 91 | /* strip leading whitespace */ |
34129109 KS |
92 | i = 0; |
93 | while (isspace(from[i]) && (i < len)) | |
94 | i++; | |
95 | ||
96 | j = 0; | |
97 | while (i < len) { | |
aaff09a3 KS |
98 | /* substitute multiple whitespace */ |
99 | if (isspace(from[i])) { | |
100 | while (isspace(from[i])) | |
101 | i++; | |
34129109 | 102 | to[j++] = '_'; |
aaff09a3 KS |
103 | } |
104 | /* skip chars */ | |
105 | if (from[i] == '/') { | |
106 | i++; | |
107 | continue; | |
108 | } | |
109 | to[j++] = from[i++]; | |
110 | } | |
111 | to[j] = '\0'; | |
112 | } | |
113 | ||
853ccc43 | 114 | static void set_type(char *to, const char *from, size_t len) |
aaff09a3 KS |
115 | { |
116 | int type_num; | |
117 | char *eptr; | |
853ccc43 | 118 | char *type = "generic"; |
aaff09a3 KS |
119 | |
120 | type_num = strtoul(from, &eptr, 0); | |
121 | if (eptr != from) { | |
122 | switch (type_num) { | |
123 | case 0: | |
853ccc43 | 124 | type = "disk"; |
aaff09a3 KS |
125 | break; |
126 | case 1: | |
853ccc43 | 127 | type = "tape"; |
aaff09a3 KS |
128 | break; |
129 | case 4: | |
853ccc43 | 130 | type = "optical"; |
aaff09a3 KS |
131 | break; |
132 | case 5: | |
853ccc43 | 133 | type = "cd"; |
aaff09a3 KS |
134 | break; |
135 | case 7: | |
853ccc43 | 136 | type = "optical"; |
aaff09a3 KS |
137 | break; |
138 | case 0xe: | |
853ccc43 | 139 | type = "disk"; |
aaff09a3 KS |
140 | break; |
141 | case 0xf: | |
853ccc43 | 142 | type = "optical"; |
34129109 KS |
143 | break; |
144 | default: | |
aaff09a3 | 145 | break; |
34129109 | 146 | } |
34129109 | 147 | } |
853ccc43 KS |
148 | strncpy(to, type, len); |
149 | to[len-1] = '\0'; | |
34129109 KS |
150 | } |
151 | ||
1aa1e248 | 152 | static int create_tmp_dev(const char *devpath, char *tmpdev, int dev_type) |
e996d978 | 153 | { |
1aa1e248 KS |
154 | unsigned int maj, min; |
155 | const char *attr; | |
e996d978 | 156 | |
1aa1e248 KS |
157 | dbg("%s", devpath); |
158 | attr = sysfs_attr_get_value(devpath, "dev"); | |
159 | if (attr == NULL) { | |
160 | dbg("%s: could not get dev attribute: %s", devpath, strerror(errno)); | |
c521693b GKH |
161 | return -1; |
162 | } | |
c521693b | 163 | |
1aa1e248 KS |
164 | dbg("dev value %s", attr); |
165 | if (sscanf(attr, "%u:%u", &maj, &min) != 2) { | |
166 | err("%s: invalid dev major/minor", devpath); | |
c521693b GKH |
167 | return -1; |
168 | } | |
169 | ||
6ecd4d1e | 170 | snprintf(tmpdev, MAX_PATH_LEN, "%s/%s-maj%d-min%d-%u", |
062db23d | 171 | TMP_DIR, TMP_PREFIX, maj, min, getpid()); |
c521693b | 172 | |
1aa1e248 | 173 | dbg("tmpdev '%s'", tmpdev); |
062db23d | 174 | if (mknod(tmpdev, 0600 | dev_type, makedev(maj, min))) { |
1aa1e248 | 175 | err("mknod failed: %s", strerror(errno)); |
c521693b GKH |
176 | return -1; |
177 | } | |
178 | return 0; | |
179 | } | |
180 | ||
c521693b GKH |
181 | /* |
182 | * get_value: | |
183 | * | |
184 | * buf points to an '=' followed by a quoted string ("foo") or a string ending | |
185 | * with a space or ','. | |
186 | * | |
187 | * Return a pointer to the NUL terminated string, returns NULL if no | |
188 | * matches. | |
189 | */ | |
190 | static char *get_value(char **buffer) | |
191 | { | |
192 | static char *quote_string = "\"\n"; | |
193 | static char *comma_string = ",\n"; | |
194 | char *val; | |
195 | char *end; | |
196 | ||
197 | if (**buffer == '"') { | |
198 | /* | |
199 | * skip leading quote, terminate when quote seen | |
200 | */ | |
201 | (*buffer)++; | |
202 | end = quote_string; | |
203 | } else { | |
204 | end = comma_string; | |
205 | } | |
206 | val = strsep(buffer, end); | |
207 | if (val && end == quote_string) | |
208 | /* | |
209 | * skip trailing quote | |
210 | */ | |
211 | (*buffer)++; | |
212 | ||
213 | while (isspace(**buffer)) | |
214 | (*buffer)++; | |
215 | ||
216 | return val; | |
217 | } | |
218 | ||
219 | static int argc_count(char *opts) | |
220 | { | |
221 | int i = 0; | |
222 | while (*opts != '\0') | |
223 | if (*opts++ == ' ') | |
224 | i++; | |
225 | return i; | |
226 | } | |
227 | ||
228 | /* | |
229 | * get_file_options: | |
230 | * | |
231 | * If vendor == NULL, find a line in the config file with only "OPTIONS="; | |
232 | * if vendor and model are set find the first OPTIONS line in the config | |
233 | * file that matches. Set argc and argv to match the OPTIONS string. | |
234 | * | |
235 | * vendor and model can end in '\n'. | |
236 | */ | |
1aa1e248 KS |
237 | static int get_file_options(const char *vendor, const char *model, |
238 | int *argc, char ***newargv) | |
c521693b | 239 | { |
1bed1db4 | 240 | char *buffer; |
c521693b GKH |
241 | FILE *fd; |
242 | char *buf; | |
243 | char *str1; | |
244 | char *vendor_in, *model_in, *options_in; /* read in from file */ | |
245 | int lineno; | |
246 | int c; | |
247 | int retval = 0; | |
c521693b | 248 | |
1aa1e248 | 249 | dbg("vendor='%s'; model='%s'\n", vendor, model); |
c521693b GKH |
250 | fd = fopen(config_file, "r"); |
251 | if (fd == NULL) { | |
1aa1e248 | 252 | dbg("can't open %s\n", config_file); |
c521693b GKH |
253 | if (errno == ENOENT) { |
254 | return 1; | |
255 | } else { | |
1aa1e248 | 256 | err("can't open %s: %s", config_file, strerror(errno)); |
c521693b GKH |
257 | return -1; |
258 | } | |
259 | } | |
260 | ||
1bed1db4 PM |
261 | /* |
262 | * Allocate a buffer rather than put it on the stack so we can | |
263 | * keep it around to parse any options (any allocated newargv | |
264 | * points into this buffer for its strings). | |
265 | */ | |
266 | buffer = malloc(MAX_BUFFER_LEN); | |
267 | if (!buffer) { | |
1aa1e248 | 268 | err("Can't allocate memory."); |
1bed1db4 PM |
269 | return -1; |
270 | } | |
271 | ||
c521693b GKH |
272 | *newargv = NULL; |
273 | lineno = 0; | |
c521693b GKH |
274 | while (1) { |
275 | vendor_in = model_in = options_in = NULL; | |
276 | ||
1bed1db4 | 277 | buf = fgets(buffer, MAX_BUFFER_LEN, fd); |
c521693b GKH |
278 | if (buf == NULL) |
279 | break; | |
280 | lineno++; | |
1bed1db4 | 281 | if (buf[strlen(buffer) - 1] != '\n') { |
1aa1e248 | 282 | info("Config file line %d too long.\n", lineno); |
1bed1db4 PM |
283 | break; |
284 | } | |
c521693b GKH |
285 | |
286 | while (isspace(*buf)) | |
287 | buf++; | |
288 | ||
1aa1e248 | 289 | /* blank or all whitespace line */ |
c521693b | 290 | if (*buf == '\0') |
c521693b GKH |
291 | continue; |
292 | ||
1aa1e248 | 293 | /* comment line */ |
c521693b | 294 | if (*buf == '#') |
c521693b GKH |
295 | continue; |
296 | ||
1aa1e248 | 297 | dbg("lineno %d: '%s'\n", lineno, buf); |
c521693b GKH |
298 | str1 = strsep(&buf, "="); |
299 | if (str1 && strcasecmp(str1, "VENDOR") == 0) { | |
300 | str1 = get_value(&buf); | |
301 | if (!str1) { | |
302 | retval = -1; | |
303 | break; | |
304 | } | |
305 | vendor_in = str1; | |
306 | ||
307 | str1 = strsep(&buf, "="); | |
308 | if (str1 && strcasecmp(str1, "MODEL") == 0) { | |
309 | str1 = get_value(&buf); | |
310 | if (!str1) { | |
311 | retval = -1; | |
312 | break; | |
313 | } | |
314 | model_in = str1; | |
315 | str1 = strsep(&buf, "="); | |
316 | } | |
317 | } | |
318 | ||
319 | if (str1 && strcasecmp(str1, "OPTIONS") == 0) { | |
320 | str1 = get_value(&buf); | |
321 | if (!str1) { | |
322 | retval = -1; | |
323 | break; | |
324 | } | |
325 | options_in = str1; | |
326 | } | |
1aa1e248 | 327 | dbg("config file line %d:" |
c521693b GKH |
328 | " vendor '%s'; model '%s'; options '%s'\n", |
329 | lineno, vendor_in, model_in, options_in); | |
330 | /* | |
331 | * Only allow: [vendor=foo[,model=bar]]options=stuff | |
332 | */ | |
333 | if (!options_in || (!vendor_in && model_in)) { | |
1aa1e248 | 334 | info("Error parsing config file line %d '%s'", lineno, buffer); |
c521693b GKH |
335 | retval = -1; |
336 | break; | |
337 | } | |
338 | if (vendor == NULL) { | |
339 | if (vendor_in == NULL) { | |
1aa1e248 | 340 | dbg("matched global option\n"); |
c521693b GKH |
341 | break; |
342 | } | |
343 | } else if ((vendor_in && strncmp(vendor, vendor_in, | |
344 | strlen(vendor_in)) == 0) && | |
345 | (!model_in || (strncmp(model, model_in, | |
346 | strlen(model_in)) == 0))) { | |
347 | /* | |
348 | * Matched vendor and optionally model. | |
349 | * | |
350 | * Note: a short vendor_in or model_in can | |
351 | * give a partial match (that is FOO | |
352 | * matches FOOBAR). | |
353 | */ | |
1aa1e248 | 354 | dbg("matched vendor/model\n"); |
c521693b GKH |
355 | break; |
356 | } else { | |
1aa1e248 | 357 | dbg("no match\n"); |
c521693b GKH |
358 | } |
359 | } | |
360 | ||
361 | if (retval == 0) { | |
362 | if (vendor_in != NULL || model_in != NULL || | |
363 | options_in != NULL) { | |
364 | /* | |
365 | * Something matched. Allocate newargv, and store | |
366 | * values found in options_in. | |
367 | */ | |
1bed1db4 PM |
368 | strcpy(buffer, options_in); |
369 | c = argc_count(buffer) + 2; | |
c521693b GKH |
370 | *newargv = calloc(c, sizeof(**newargv)); |
371 | if (!*newargv) { | |
1aa1e248 | 372 | err("Can't allocate memory."); |
c521693b GKH |
373 | retval = -1; |
374 | } else { | |
375 | *argc = c; | |
376 | c = 0; | |
1bed1db4 PM |
377 | /* |
378 | * argv[0] at 0 is skipped by getopt, but | |
379 | * store the buffer address there for | |
380 | * alter freeing. | |
381 | */ | |
382 | (*newargv)[c] = buffer; | |
c521693b | 383 | for (c = 1; c < *argc; c++) |
1bed1db4 | 384 | (*newargv)[c] = strsep(&buffer, " "); |
c521693b GKH |
385 | } |
386 | } else { | |
1aa1e248 | 387 | /* No matches */ |
c521693b GKH |
388 | retval = 1; |
389 | } | |
390 | } | |
1bed1db4 PM |
391 | if (retval != 0) |
392 | free(buffer); | |
c521693b GKH |
393 | fclose(fd); |
394 | return retval; | |
395 | } | |
396 | ||
397 | static int set_options(int argc, char **argv, const char *short_opts, | |
1bed1db4 | 398 | char *target, char *maj_min_dev) |
c521693b GKH |
399 | { |
400 | int option; | |
c521693b GKH |
401 | |
402 | /* | |
1bed1db4 PM |
403 | * optind is a global extern used by getopt. Since we can call |
404 | * set_options twice (once for command line, and once for config | |
405 | * file) we have to reset this back to 1. [Note glibc handles | |
406 | * setting this to 0, but klibc does not.] | |
c521693b | 407 | */ |
1bed1db4 | 408 | optind = 1; |
c521693b | 409 | while (1) { |
853ccc43 | 410 | option = getopt(argc, argv, short_opts); |
c521693b GKH |
411 | if (option == -1) |
412 | break; | |
413 | ||
414 | if (optarg) | |
1aa1e248 | 415 | dbg("option '%c' arg '%s'\n", option, optarg); |
c521693b | 416 | else |
1aa1e248 | 417 | dbg("option '%c'\n", option); |
c521693b GKH |
418 | |
419 | switch (option) { | |
b4a2906b HR |
420 | case 'a': |
421 | always_info = 1; | |
422 | break; | |
c521693b GKH |
423 | case 'b': |
424 | all_good = 0; | |
425 | break; | |
426 | ||
c521693b GKH |
427 | case 'd': |
428 | dev_specified = 1; | |
6ecd4d1e KS |
429 | strncpy(maj_min_dev, optarg, MAX_PATH_LEN); |
430 | maj_min_dev[MAX_PATH_LEN-1] = '\0'; | |
c521693b GKH |
431 | break; |
432 | ||
433 | case 'e': | |
434 | use_stderr = 1; | |
435 | break; | |
436 | ||
437 | case 'f': | |
6ecd4d1e KS |
438 | strncpy(config_file, optarg, MAX_PATH_LEN); |
439 | config_file[MAX_PATH_LEN-1] = '\0'; | |
c521693b GKH |
440 | break; |
441 | ||
442 | case 'g': | |
443 | all_good = 1; | |
444 | break; | |
445 | ||
446 | case 'i': | |
447 | display_bus_id = 1; | |
448 | break; | |
449 | ||
450 | case 'p': | |
451 | if (strcmp(optarg, "0x80") == 0) { | |
50be1401 | 452 | default_page_code = PAGE_80; |
c521693b | 453 | } else if (strcmp(optarg, "0x83") == 0) { |
50be1401 EG |
454 | default_page_code = PAGE_83; |
455 | } else if (strcmp(optarg, "pre-spc3-83") == 0) { | |
456 | default_page_code = PAGE_83_PRE_SPC3; | |
c521693b | 457 | } else { |
1aa1e248 | 458 | info("Unknown page code '%s'", optarg); |
c521693b GKH |
459 | return -1; |
460 | } | |
461 | break; | |
462 | ||
463 | case 's': | |
464 | sys_specified = 1; | |
6ecd4d1e KS |
465 | strncpy(target, optarg, MAX_PATH_LEN); |
466 | target[MAX_PATH_LEN-1] = '\0'; | |
c521693b GKH |
467 | break; |
468 | ||
01f950e2 PM |
469 | case 'u': |
470 | reformat_serial = 1; | |
471 | break; | |
472 | ||
34129109 KS |
473 | case 'x': |
474 | export = 1; | |
475 | break; | |
476 | ||
c521693b GKH |
477 | case 'v': |
478 | debug++; | |
479 | break; | |
480 | ||
481 | case 'V': | |
1aa1e248 | 482 | info("scsi_id version: %s\n", SCSI_ID_VERSION); |
c521693b GKH |
483 | exit(0); |
484 | break; | |
485 | ||
486 | default: | |
1aa1e248 | 487 | info("Unknown or bad option '%c' (0x%x)", option, option); |
c521693b GKH |
488 | return -1; |
489 | } | |
490 | } | |
491 | return 0; | |
492 | } | |
493 | ||
1aa1e248 | 494 | static int per_dev_options(struct sysfs_device *dev_scsi, int *good_bad, int *page_code) |
c521693b GKH |
495 | { |
496 | int retval; | |
497 | int newargc; | |
498 | char **newargv = NULL; | |
1aa1e248 | 499 | const char *vendor, *model, *type; |
c521693b | 500 | int option; |
c521693b GKH |
501 | |
502 | *good_bad = all_good; | |
503 | *page_code = default_page_code; | |
c521693b | 504 | |
1aa1e248 | 505 | vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor"); |
4e05e423 | 506 | if (!vendor) { |
1aa1e248 | 507 | info("%s: cannot get vendor attribute", dev_scsi->devpath); |
c521693b GKH |
508 | return -1; |
509 | } | |
1aa1e248 | 510 | set_str(vendor_str, vendor, sizeof(vendor_str)-1); |
c521693b | 511 | |
1aa1e248 | 512 | model = sysfs_attr_get_value(dev_scsi->devpath, "model"); |
4e05e423 | 513 | if (!model) { |
1aa1e248 | 514 | info("%s: cannot get model attribute\n", dev_scsi->devpath); |
c521693b GKH |
515 | return -1; |
516 | } | |
1aa1e248 | 517 | set_str(model_str, model, sizeof(model_str)-1); |
c521693b | 518 | |
1aa1e248 | 519 | type = sysfs_attr_get_value(dev_scsi->devpath, "type"); |
aaff09a3 | 520 | if (!type) { |
1aa1e248 | 521 | info("%s: cannot get type attribute", dev_scsi->devpath); |
aaff09a3 KS |
522 | return -1; |
523 | } | |
1aa1e248 | 524 | set_type(type_str, type, sizeof(type_str)); |
aaff09a3 | 525 | |
1aa1e248 | 526 | type = sysfs_attr_get_value(dev_scsi->devpath, "rev"); |
aaff09a3 | 527 | if (!type) { |
1aa1e248 | 528 | info("%s: cannot get type attribute\n", dev_scsi->devpath); |
aaff09a3 KS |
529 | return -1; |
530 | } | |
1aa1e248 | 531 | set_str(revision_str, type, sizeof(revision_str)-1); |
aaff09a3 | 532 | |
1aa1e248 | 533 | retval = get_file_options(vendor, model, &newargc, &newargv); |
c521693b | 534 | |
1bed1db4 | 535 | optind = 1; /* reset this global extern */ |
c521693b | 536 | while (retval == 0) { |
1bed1db4 | 537 | option = getopt(newargc, newargv, dev_short_options); |
c521693b GKH |
538 | if (option == -1) |
539 | break; | |
540 | ||
541 | if (optarg) | |
1aa1e248 | 542 | dbg("option '%c' arg '%s'\n", option, optarg); |
c521693b | 543 | else |
1aa1e248 | 544 | dbg("option '%c'\n", option); |
c521693b GKH |
545 | |
546 | switch (option) { | |
547 | case 'b': | |
548 | *good_bad = 0; | |
549 | break; | |
550 | ||
c521693b GKH |
551 | case 'g': |
552 | *good_bad = 1; | |
553 | break; | |
554 | ||
555 | case 'p': | |
556 | if (strcmp(optarg, "0x80") == 0) { | |
50be1401 | 557 | *page_code = PAGE_80; |
c521693b | 558 | } else if (strcmp(optarg, "0x83") == 0) { |
50be1401 EG |
559 | *page_code = PAGE_83; |
560 | } else if (strcmp(optarg, "pre-spc3-83") == 0) { | |
561 | *page_code = PAGE_83_PRE_SPC3; | |
c521693b | 562 | } else { |
1aa1e248 | 563 | info("Unknown page code '%s'", optarg); |
c521693b GKH |
564 | retval = -1; |
565 | } | |
566 | break; | |
567 | ||
568 | default: | |
1aa1e248 | 569 | info("Unknown or bad option '%c' (0x%x)", option, option); |
c521693b GKH |
570 | retval = -1; |
571 | break; | |
572 | } | |
573 | } | |
574 | ||
1bed1db4 PM |
575 | if (newargv) { |
576 | free(newargv[0]); | |
c521693b | 577 | free(newargv); |
1bed1db4 | 578 | } |
c521693b GKH |
579 | return retval; |
580 | } | |
581 | ||
01f950e2 PM |
582 | /* |
583 | * format_serial: replace to whitespaces by underscores for calling | |
584 | * programs that use the serial for device naming (multipath, Suse | |
585 | * naming, etc...) | |
586 | */ | |
587 | static void format_serial(char *serial) | |
588 | { | |
d313632b | 589 | char *p = serial, *q; |
01f950e2 | 590 | |
d313632b | 591 | q = p; |
01f950e2 | 592 | while (*p != '\0') { |
d313632b HR |
593 | if (isspace(*p)) { |
594 | if (q > serial && q[-1] != '_') { | |
595 | *q = '_'; | |
596 | q++; | |
597 | } | |
598 | } else { | |
599 | *q = *p; | |
600 | q++; | |
601 | } | |
01f950e2 PM |
602 | p++; |
603 | } | |
d313632b | 604 | *q = '\0'; |
01f950e2 PM |
605 | } |
606 | ||
c521693b GKH |
607 | /* |
608 | * scsi_id: try to get an id, if one is found, printf it to stdout. | |
609 | * returns a value passed to exit() - 0 if printed an id, else 1. This | |
610 | * could be expanded, for example, if we want to report a failure like no | |
611 | * memory etc. return 2, and return 1 for expected cases (like broken | |
612 | * device found) that do not print an id. | |
613 | */ | |
1aa1e248 | 614 | static int scsi_id(const char *devpath, char *maj_min_dev) |
c521693b GKH |
615 | { |
616 | int retval; | |
617 | int dev_type = 0; | |
01f950e2 | 618 | char *serial, *unaligned_buf; |
1aa1e248 KS |
619 | struct sysfs_device *dev; |
620 | struct sysfs_device *dev_scsi; | |
c521693b GKH |
621 | int good_dev; |
622 | int page_code; | |
c521693b | 623 | |
1aa1e248 | 624 | dbg("devpath %s\n", devpath); |
c521693b | 625 | |
1aa1e248 KS |
626 | dev = sysfs_device_get(devpath); |
627 | if (dev == NULL) { | |
628 | err("unable to access '%s'", devpath); | |
b484e436 PM |
629 | return 1; |
630 | } | |
b484e436 | 631 | |
1aa1e248 KS |
632 | if (strcmp(dev->subsystem, "block") == 0) |
633 | dev_type = S_IFBLK; | |
634 | else | |
635 | dev_type = S_IFCHR; | |
b484e436 | 636 | |
1aa1e248 | 637 | /* get scsi parent device */ |
6ecd4d1e | 638 | dev_scsi = sysfs_device_get_parent_with_subsystem(dev, "scsi"); |
1aa1e248 KS |
639 | if (dev_scsi == NULL) { |
640 | err("unable to access parent device of '%s'", devpath); | |
c521693b | 641 | return 1; |
b484e436 | 642 | } |
c521693b | 643 | |
1aa1e248 KS |
644 | /* mknod a temp dev to communicate with the device */ |
645 | if (!dev_specified && create_tmp_dev(dev->devpath, maj_min_dev, dev_type)) { | |
646 | dbg("create_tmp_dev failed\n"); | |
c521693b GKH |
647 | return 1; |
648 | } | |
649 | ||
1aa1e248 KS |
650 | /* get per device (vendor + model) options from the config file */ |
651 | retval = per_dev_options(dev_scsi, &good_dev, &page_code); | |
652 | dbg("per dev options: good %d; page code 0x%x", good_dev, page_code); | |
c521693b | 653 | |
01f950e2 PM |
654 | #define ALIGN 512 |
655 | unaligned_buf = malloc(MAX_SERIAL_LEN + ALIGN); | |
b337e607 PM |
656 | serial = (char*) (((unsigned long) unaligned_buf + (ALIGN - 1)) |
657 | & ~(ALIGN - 1)); | |
1aa1e248 | 658 | dbg("buffer unaligned 0x%p; aligned 0x%p\n", unaligned_buf, serial); |
01f950e2 PM |
659 | #undef ALIGN |
660 | ||
c521693b GKH |
661 | if (!good_dev) { |
662 | retval = 1; | |
1aa1e248 | 663 | } else if (scsi_get_serial(dev_scsi, maj_min_dev, page_code, |
c521693b | 664 | serial, MAX_SERIAL_LEN)) { |
b4a2906b | 665 | retval = always_info?0:1; |
c521693b GKH |
666 | } else { |
667 | retval = 0; | |
668 | } | |
669 | if (!retval) { | |
34129109 KS |
670 | if (export) { |
671 | static char serial_str[64]; | |
672 | printf("ID_VENDOR=%s\n", vendor_str); | |
673 | printf("ID_MODEL=%s\n", model_str); | |
aaff09a3 | 674 | printf("ID_REVISION=%s\n", revision_str); |
34129109 KS |
675 | set_str(serial_str, serial, sizeof(serial_str)); |
676 | printf("ID_SERIAL=%s\n", serial_str); | |
aaff09a3 | 677 | printf("ID_TYPE=%s\n", type_str); |
46f01c6d | 678 | printf("ID_BUS=scsi\n"); |
34129109 KS |
679 | } else { |
680 | if (reformat_serial) | |
681 | format_serial(serial); | |
682 | if (display_bus_id) | |
1aa1e248 | 683 | printf("%s: ", dev_scsi->kernel_name); |
34129109 KS |
684 | printf("%s\n", serial); |
685 | } | |
1aa1e248 | 686 | dbg("%s\n", serial); |
c521693b GKH |
687 | retval = 0; |
688 | } | |
c521693b GKH |
689 | |
690 | if (!dev_specified) | |
691 | unlink(maj_min_dev); | |
692 | ||
693 | return retval; | |
694 | } | |
695 | ||
696 | int main(int argc, char **argv) | |
697 | { | |
1aa1e248 | 698 | int retval = 0; |
6ecd4d1e KS |
699 | char devpath[MAX_PATH_LEN]; |
700 | char maj_min_dev[MAX_PATH_LEN]; | |
c521693b | 701 | int newargc; |
1aa1e248 | 702 | const char *env; |
c521693b GKH |
703 | char **newargv; |
704 | ||
1aa1e248 KS |
705 | logging_init("scsi_id"); |
706 | sysfs_init(); | |
707 | dbg("argc is %d\n", argc); | |
c521693b | 708 | |
1aa1e248 KS |
709 | /* sysfs path can be overridden for testing */ |
710 | env = getenv("SYSFS_PATH"); | |
711 | if (env) { | |
712 | strncpy(sysfs_path, env, sizeof(sysfs_path)); | |
713 | sysfs_path[sizeof(sysfs_path)-1] = '\0'; | |
714 | } else | |
715 | strcpy(sysfs_path, "/sys"); | |
c521693b | 716 | |
1aa1e248 KS |
717 | env = getenv("DEVPATH"); |
718 | if (env) { | |
07544a93 | 719 | hotplug_mode = 1; |
c521693b | 720 | sys_specified = 1; |
6ecd4d1e | 721 | strncpy(devpath, env, MAX_PATH_LEN); |
1aa1e248 | 722 | devpath[sizeof(devpath)-1] = '\0'; |
c521693b GKH |
723 | } |
724 | ||
725 | /* | |
07544a93 | 726 | * Get config file options. |
c521693b GKH |
727 | */ |
728 | newargv = NULL; | |
729 | retval = get_file_options(NULL, NULL, &newargc, &newargv); | |
730 | if (retval < 0) { | |
1aa1e248 KS |
731 | retval = 1; |
732 | goto exit; | |
733 | } | |
734 | if (newargv && (retval == 0)) { | |
735 | if (set_options(newargc, newargv, short_options, devpath, | |
736 | maj_min_dev) < 0) { | |
737 | retval = 2; | |
738 | goto exit; | |
739 | } | |
c521693b GKH |
740 | free(newargv); |
741 | } | |
1aa1e248 | 742 | |
07544a93 PM |
743 | /* |
744 | * Get command line options (overriding any config file or DEVPATH | |
745 | * settings). | |
746 | */ | |
1aa1e248 | 747 | if (set_options(argc, argv, short_options, devpath, maj_min_dev) < 0) |
062db23d | 748 | exit(1); |
c521693b GKH |
749 | |
750 | if (!sys_specified) { | |
1aa1e248 KS |
751 | info("-s must be specified\n"); |
752 | retval = 1; | |
753 | goto exit; | |
c521693b GKH |
754 | } |
755 | ||
1aa1e248 KS |
756 | retval = scsi_id(devpath, maj_min_dev); |
757 | ||
758 | exit: | |
759 | sysfs_cleanup(); | |
760 | logging_close(); | |
761 | return retval; | |
c521693b | 762 | } |