chiark / gitweb /
[PATCH] scsi_id changes for use with udev %N and %p
[elogind.git] / extras / scsi_id / scsi_id.c
index d34d9284e50d2d06b22622d4043f358a92f28988..507fa27766fe2d5fd02055a8d05dd3d2fff96a0a 100644 (file)
@@ -23,7 +23,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <getopt.h>
 #include <unistd.h>
 #include <signal.h>
 #include <fcntl.h>
 #include <stdarg.h>
 #include <ctype.h>
 #include <sys/stat.h>
-#include <sys/libsysfs.h>
+#include <sysfs/libsysfs.h>
+#include "scsi_id_version.h"
 #include "scsi_id.h"
 
-#ifndef VERSION
+#ifndef SCSI_ID_VERSION
 #warning No version
-#define VERSION        "unknown"
+#define SCSI_ID_VERSION        "unknown"
 #endif
 
 /*
 #define TMP_DIR        "/tmp"
 #define TMP_PREFIX "scsi"
 
-#define CONFIG_FILE "/etc/scsi_id.config"
-
-#define        MAX_NAME_LEN    72
-
-#define        MAX_SERIAL_LEN  128
-
-static const char short_options[] = "bc:d:ef:gip:s:vV";
-static const struct option long_options[] = {
-       {"broken",      no_argument,            NULL,   'b'}, /* also per dev */
-       {"callout",     required_argument,      NULL,   'c'}, /* also per dev */
-       {"device",      required_argument,      NULL,   'd'},
-       {"stderr",      no_argument,            NULL,   'e'},
-       {"file",        required_argument,      NULL,   'f'},
-       {"good",        no_argument,            NULL,   'g'}, /* also per dev */
-       {"busid",       no_argument,            NULL,   'i'}, /* also per dev */
-       {"page",        required_argument,      NULL,   'p'}, /* also per dev */
-       {"devpath",     required_argument,      NULL,   's'},
-       {"verbose",     no_argument,            NULL,   'v'},
-       {"version",     no_argument,            NULL,   'V'},
-       {0, 0, 0, 0}
-};
+/*
+ * XXX Note the 'e' (send output to stderr in all cases), and 'c' (callout)
+ * options are not supported, but other code is still left in place for
+ * now.
+ */
+static const char short_options[] = "bd:f:gip:s:uvV";
 /*
  * Just duplicate per dev options.
  */
-static const char dev_short_options[] = "bc:gp:";
-static const struct option dev_long_options[] = {
-       {"broken",      no_argument,            NULL,   'b'}, /* also per dev */
-       {"callout",     required_argument,      NULL,   'c'}, /* also per dev */
-       {"good",        no_argument,            NULL,   'g'}, /* also per dev */
-       {"page",        required_argument,      NULL,   'p'}, /* also per dev */
-       {0, 0, 0, 0}
-};
+static const char dev_short_options[] = "bgp:";
 
 char sysfs_mnt_path[SYSFS_PATH_MAX];
 
@@ -86,12 +64,13 @@ static int all_good;
 static char *default_callout;
 static int dev_specified;
 static int sys_specified;
-static char config_file[MAX_NAME_LEN] = CONFIG_FILE;
+static char config_file[MAX_NAME_LEN] = SCSI_ID_CONFIG_FILE;
 static int display_bus_id;
 static int default_page_code;
 static int use_stderr;
 static int debug;
 static int hotplug_mode;
+static int reformat_serial;
 
 void log_message (int level, const char *format, ...)
 {
@@ -105,8 +84,13 @@ void log_message (int level, const char *format, ...)
                vfprintf(stderr, format, args);
        } else {
                static int logging_init = 0;
+               static unsigned char logname[32];
                if (!logging_init) {
-                       openlog ("scsi_id", LOG_PID, LOG_DAEMON);
+                       /*
+                        * klibc does not have LOG_PID.
+                        */
+                       snprintf(logname, 32, "scsi_id[%d]", getpid());
+                       openlog (logname, 0, LOG_DAEMON);
                        logging_init = 1;
                }
 
@@ -116,86 +100,37 @@ void log_message (int level, const char *format, ...)
        return;
 }
 
-static int sysfs_get_actual_dev(const char *sysfs_path, char *dev, int len)
-{
-       dprintf("%s\n", sysfs_path);
-       strncpy(dev, sysfs_path, len);
-       strncat(dev, "/device", len);
-       if (sysfs_get_link(dev, dev, len)) {
-               if (!hotplug_mode)
-                       log_message(LOG_WARNING, "%s: %s\n", dev,
-                                   strerror(errno));
-               return -1;
-       }
-       return 0;
-}
-
-/*
- * sysfs_is_bus: Given the sysfs_path to a device, return 1 if sysfs_path
- * is on bus, 0 if not on bus, and < 0 on error
- */
-static int sysfs_is_bus(const char *sysfs_path, const char *bus)
+int sysfs_get_attr(const char *devpath, const char *attr, char *value,
+                  size_t bufsize)
 {
-       char bus_dev_name[SYSFS_PATH_MAX];
-       char bus_id[SYSFS_NAME_LEN];
-       struct stat stat_buf;
-       ino_t dev_inode;
+       char attr_path[SYSFS_PATH_MAX];
 
-       dprintf("%s\n", sysfs_path);
-
-       if (sysfs_get_name_from_path(sysfs_path, bus_id, SYSFS_NAME_LEN))
-               return -1;
-
-       snprintf(bus_dev_name, MAX_NAME_LEN, "%s/%s/%s/%s/%s", sysfs_mnt_path,
-                SYSFS_BUS_DIR, bus, SYSFS_DEVICES_NAME, bus_id);
-
-       if (stat(sysfs_path, &stat_buf))
-               return -1;
-       dev_inode = stat_buf.st_ino;
-
-       if (stat(bus_dev_name, &stat_buf)) {
-               if (errno == ENOENT)
-                       return 0;
-               else
-                       return -1;
-       }
-       if (dev_inode == stat_buf.st_ino)
-               return 1;
-       else
-               return 0;
+       strncpy(attr_path, devpath, SYSFS_PATH_MAX);
+       strncat(attr_path, "/", SYSFS_PATH_MAX);
+       strncat(attr_path, attr,  SYSFS_PATH_MAX);
+       dprintf("%s\n", attr_path);
+       return sysfs_read_attribute_value(attr_path, value, SYSFS_NAME_LEN);
 }
 
-static int get_major_minor(const char *devpath, int *major, int *minor)
+static int get_major_minor(const char *devpath, int *maj, int *min)
 {
-       struct sysfs_class_device *class_dev;
-       char dev_value[SYSFS_NAME_LEN];
-       char *dev;
-
-       dprintf("%s\n", devpath);
-       class_dev = sysfs_open_class_device(devpath);
-       if (!class_dev) {
-               log_message(LOG_WARNING, "open class %s failed: %s\n", devpath,
-                           strerror(errno));
-               return -1;
-       }
+       char dev_value[MAX_ATTR_LEN];
 
-       dev = sysfs_get_attr(class_dev, "dev");
-       if (dev)
-               strncpy(dev_value, dev, SYSFS_NAME_LEN);
-       sysfs_close_class_device(class_dev);
-       if (!dev) {
+       if (sysfs_get_attr(devpath, "dev", dev_value, MAX_ATTR_LEN)) {
                /*
                 * XXX This happens a lot, since sg has no dev attr.
-                * Someday change this back to a LOG_WARNING.
+                * And now sysfsutils does not set a meaningful errno
+                * value. Someday change this back to a LOG_WARNING.
+                * And if sysfsutils changes, check for ENOENT and handle
+                * it separately.
                 */
-               log_message(LOG_DEBUG, "%s could not get dev attribute: %s\n",
+               log_message(LOG_DEBUG, "%s: could not get dev attribute: %s\n",
                        devpath, strerror(errno));
                return -1;
        }
-       dev = NULL;
 
-       dprintf("dev %s", dev_value); /* dev_value has a trailing \n */
-       if (sscanf(dev_value, "%u:%u", major, minor) != 2) {
+       dprintf("dev value %s", dev_value); /* dev_value has a trailing \n */
+       if (sscanf(dev_value, "%u:%u", maj, min) != 2) {
                log_message(LOG_WARNING, "%s: invalid dev major/minor\n",
                            devpath);
                return -1;
@@ -206,18 +141,18 @@ static int get_major_minor(const char *devpath, int *major, int *minor)
 
 static int create_tmp_dev(const char *devpath, char *tmpdev, int dev_type)
 {
-       int major, minor;
+       int maj, min;
 
        dprintf("(%s)\n", devpath);
 
-       if (get_major_minor(devpath, &major, &minor))
+       if (get_major_minor(devpath, &maj, &min))
                return -1;
        snprintf(tmpdev, MAX_NAME_LEN, "%s/%s-maj%d-min%d-%u",
-                TMP_DIR, TMP_PREFIX, major, minor, getpid());
+                TMP_DIR, TMP_PREFIX, maj, min, getpid());
 
        dprintf("tmpdev '%s'\n", tmpdev);
 
-       if (mknod(tmpdev, 0600 | dev_type, makedev(major, minor))) {
+       if (mknod(tmpdev, 0600 | dev_type, makedev(maj, min))) {
                log_message(LOG_WARNING, "mknod failed: %s\n", strerror(errno));
                return -1;
        }
@@ -295,7 +230,7 @@ static int argc_count(char *opts)
 static int get_file_options(char *vendor, char *model, int *argc,
                            char ***newargv)
 {
-       char buffer[256];
+       char *buffer;
        FILE *fd;
        char *buf;
        char *str1;
@@ -303,7 +238,6 @@ static int get_file_options(char *vendor, char *model, int *argc,
        int lineno;
        int c;
        int retval = 0;
-       static char *prog_string = "arg0";
 
        dprintf("vendor='%s'; model='%s'\n", vendor, model);
        fd = fopen(config_file, "r");
@@ -318,16 +252,31 @@ static int get_file_options(char *vendor, char *model, int *argc,
                }
        }
 
+       /*
+        * Allocate a buffer rather than put it on the stack so we can
+        * keep it around to parse any options (any allocated newargv
+        * points into this buffer for its strings).
+        */
+       buffer = malloc(MAX_BUFFER_LEN);
+       if (!buffer) {
+               log_message(LOG_WARNING, "Can't allocate memory.\n");
+               return -1;
+       }
+
        *newargv = NULL;
        lineno = 0;
-
        while (1) {
                vendor_in = model_in = options_in = NULL;
 
-               buf = fgets(buffer, sizeof(buffer), fd);
+               buf = fgets(buffer, MAX_BUFFER_LEN, fd);
                if (buf == NULL)
                        break;
                lineno++;
+               if (buf[strlen(buffer) - 1] != '\n') {
+                       log_message(LOG_WARNING,
+                                   "Config file line %d too long.\n", lineno);
+                       break;
+               }
 
                while (isspace(*buf))
                        buf++;
@@ -419,18 +368,24 @@ static int get_file_options(char *vendor, char *model, int *argc,
                         * Something matched. Allocate newargv, and store
                         * values found in options_in.
                         */
-                       c = argc_count(options_in) + 2;
+                       strcpy(buffer, options_in);
+                       c = argc_count(buffer) + 2;
                        *newargv = calloc(c, sizeof(**newargv));
                        if (!*newargv) {
                                log_message(LOG_WARNING,
-                                           "Can't allocate memory\n");
+                                           "Can't allocate memory.\n");
                                retval = -1;
                        } else {
                                *argc = c;
                                c = 0;
-                               (*newargv)[c] = prog_string; /* nothing */
+                               /*
+                                * argv[0] at 0 is skipped by getopt, but
+                                * store the buffer address there for
+                                * alter freeing.
+                                */
+                               (*newargv)[c] = buffer;
                                for (c = 1; c < *argc; c++)
-                                       (*newargv)[c] = strsep(&options_in, " ");
+                                       (*newargv)[c] = strsep(&buffer, " ");
                        }
                } else {
                        /*
@@ -439,26 +394,26 @@ static int get_file_options(char *vendor, char *model, int *argc,
                        retval = 1;
                }
        }
+       if (retval != 0)
+               free(buffer);
        fclose(fd);
        return retval;
 }
 
 static int set_options(int argc, char **argv, const char *short_opts,
-                      const struct option *long_opts, char *target,
-                      char *maj_min_dev)
+                      char *target, char *maj_min_dev)
 {
        int option;
-       int option_ind;
 
        /*
-        * optind is a global extern used by getopt_long. Since we can
-        * call set_options twice (once for command line, and once for
-        * config file) we have to reset this back to 0.
+        * optind is a global extern used by getopt. Since we can call
+        * set_options twice (once for command line, and once for config
+        * file) we have to reset this back to 1. [Note glibc handles
+        * setting this to 0, but klibc does not.]
         */
-       optind = 0;
+       optind = 1;
        while (1) {
-               option = getopt_long(argc, argv, short_options, long_options,
-                                    &option_ind);
+               option = getopt(argc, argv, short_options);
                if (option == -1)
                        break;
 
@@ -515,13 +470,17 @@ static int set_options(int argc, char **argv, const char *short_opts,
                        strncat(target, optarg, MAX_NAME_LEN);
                        break;
 
+               case 'u':
+                       reformat_serial = 1;
+                       break;
+
                case 'v':
                        debug++;
                        break;
 
                case 'V':
                        log_message(LOG_WARNING, "scsi_id version: %s\n",
-                                   VERSION);
+                                   SCSI_ID_VERSION);
                        exit(0);
                        break;
 
@@ -535,17 +494,15 @@ static int set_options(int argc, char **argv, const char *short_opts,
        return 0;
 }
 
-static int per_dev_options(struct sysfs_class_device *scsi_dev, int *good_bad,
+static int per_dev_options(struct sysfs_device *scsi_dev, int *good_bad,
                           int *page_code, char *callout)
 {
        int retval;
        int newargc;
        char **newargv = NULL;
-       char *vendor;
-       char *model;
+       char vendor[MAX_ATTR_LEN];
+       char model[MAX_ATTR_LEN];
        int option;
-       int option_ind;
-
 
        *good_bad = all_good;
        *page_code = default_page_code;
@@ -554,26 +511,23 @@ static int per_dev_options(struct sysfs_class_device *scsi_dev, int *good_bad,
        else
                callout[0] = '\0';
 
-       vendor = sysfs_get_attr(scsi_dev, "vendor");
-       if (!vendor) {
-               log_message(LOG_WARNING, "%s: no vendor attribute\n",
+       if (sysfs_get_attr(scsi_dev->path, "vendor", vendor, MAX_ATTR_LEN)) {
+               log_message(LOG_WARNING, "%s: cannot get vendor attribute\n",
                            scsi_dev->name);
                return -1;
        }
 
-       model = sysfs_get_attr(scsi_dev, "model");
-       if (!vendor) {
-               log_message(LOG_WARNING, "%s: no model attribute\n",
+       if (sysfs_get_attr(scsi_dev->path, "model", model, MAX_ATTR_LEN)) {
+               log_message(LOG_WARNING, "%s: cannot get model attribute\n",
                            scsi_dev->name);
                return -1;
        }
 
        retval = get_file_options(vendor, model, &newargc, &newargv);
 
-       optind = 0; /* global extern, reset to 0 */
+       optind = 1; /* reset this global extern */
        while (retval == 0) {
-               option = getopt_long(newargc, newargv, dev_short_options,
-                                    dev_long_options, &option_ind);
+               option = getopt(newargc, newargv, dev_short_options);
                if (option == -1)
                        break;
 
@@ -616,11 +570,30 @@ static int per_dev_options(struct sysfs_class_device *scsi_dev, int *good_bad,
                }
        }
 
-       if (newargv)
+       if (newargv) {
+               free(newargv[0]);
                free(newargv);
+       }
        return retval;
 }
 
+/*
+ * format_serial: replace to whitespaces by underscores for calling
+ * programs that use the serial for device naming (multipath, Suse
+ * naming, etc...)
+ */
+static void format_serial(char *serial)
+{
+       char *p = serial;
+
+       while (*p != '\0') {
+               if (isspace(*p))
+                       *p = '_';
+               p++;
+       }
+       return;
+}
+
 /*
  * scsi_id: try to get an id, if one is found, printf it to stdout.
  * returns a value passed to exit() - 0 if printed an id, else 1. This
@@ -632,9 +605,10 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
 {
        int retval;
        int dev_type = 0;
-       char full_dev_path[MAX_NAME_LEN];
-       char serial[MAX_SERIAL_LEN];
-       struct sysfs_class_device *scsi_dev; /* of scsi_device full_dev_path */
+       char *serial, *unaligned_buf;
+       struct sysfs_class_device *class_dev; /* of target_path */
+       struct sysfs_class_device *class_dev_parent; /* for partitions */
+       struct sysfs_device *scsi_dev; /* the scsi_device */
        int good_dev;
        int page_code;
        char callout[MAX_NAME_LEN];
@@ -665,20 +639,48 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
                }
        }
 
-       if (sysfs_get_actual_dev(target_path, full_dev_path, MAX_NAME_LEN))
+       class_dev = sysfs_open_class_device_path(target_path);
+       if (!class_dev) {
+               log_message(LOG_WARNING, "open class %s failed: %s\n",
+                           target_path, strerror(errno));
                return 1;
+       }
+       class_dev_parent = sysfs_get_classdev_parent(class_dev);
+       dprintf("class_dev 0x%p; class_dev_parent 0x%p\n", class_dev,
+               class_dev_parent);
+       if (class_dev_parent) {
+               scsi_dev = sysfs_get_classdev_device(class_dev_parent);
+       } else {
+               scsi_dev = sysfs_get_classdev_device(class_dev);
+       }
 
-       dprintf("full_dev_path %s\n", full_dev_path);
+       /*
+        * The close of scsi_dev will close class_dev or class_dev_parent.
+        */
 
        /*
-        * Allow only scsi devices (those that have a matching device
-        * under /bus/scsi/devices).
+        * We assume we are called after the device is completely ready,
+        * so we don't have to loop here like udev. (And we are usually
+        * called via udev.)
+        */
+       if (!scsi_dev) {
+               /*
+                * errno is not set if we can't find the device link, so
+                * don't print it out here.
+                */
+               log_message(LOG_WARNING, "Cannot find sysfs device associated with %s\n",
+                           target_path);
+               return 1;
+       }
+
+
+       /*
+        * Allow only scsi devices.
         *
         * Other block devices can support SG IO, but only ide-cd does, so
         * for now, don't bother with anything else.
         */
-       retval = sysfs_is_bus(full_dev_path, "scsi");
-       if (retval == 0) {
+       if (strcmp(scsi_dev->bus, "scsi") != 0) {
                if (hotplug_mode)
                        /*
                         * Expected in some cases.
@@ -688,14 +690,12 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
                        log_message(LOG_WARNING, "%s is not a scsi device\n",
                                    target_path);
                return 1;
-       } else if (retval < 0) {
-               log_message(LOG_WARNING, "sysfs_is_bus failed: %s\n",
-                       strerror(errno));
-               return 1;
        }
 
        /*
         * mknod a temp dev to communicate with the device.
+        *
+        * XXX pass down class_dev or class_dev_parent.
         */
        if (!dev_specified && create_tmp_dev(target_path, maj_min_dev,
                                             dev_type)) {
@@ -703,13 +703,6 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
                return 1;
        }
 
-       scsi_dev = sysfs_open_class_device(full_dev_path);
-       if (!scsi_dev) {
-               log_message(LOG_WARNING, "open class %s failed: %s\n",
-                           full_dev_path, strerror(errno));
-               return 1;
-       }
-
        /*
         * Get any per device (vendor + model) options from the config
         * file.
@@ -718,18 +711,18 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
        dprintf("per dev options: good %d; page code 0x%x; callout '%s'\n",
                good_dev, page_code, callout);
 
+#define ALIGN   512
+       unaligned_buf = malloc(MAX_SERIAL_LEN + ALIGN);
+       serial = (char*) (((unsigned long) unaligned_buf + (ALIGN - 1))
+                         & ~(ALIGN - 1));
+       dprintf("buffer unaligned 0x%p; aligned 0x%p\n", unaligned_buf, serial);
+#undef ALIGN
+
        if (!good_dev) {
                retval = 1;
        } else if (callout[0] != '\0') {
                /*
-                * exec vendor callout, pass it only the "name" to be used
-                * for error messages, and the dev to open.
-                *
-                * This won't work if we need to pass on the original
-                * command line (when not hotplug mode) since the option
-                * parsing and per dev parsing modify the argv's.
-                *
-                * XXX Not implemented yet. And not fully tested ;-)
+                * XXX Disabled for now ('c' is not in any options[]).
                 */
                retval = 1;
        } else if (scsi_get_serial(scsi_dev, maj_min_dev, page_code,
@@ -739,16 +732,15 @@ static int scsi_id(const char *target_path, char *maj_min_dev)
                retval = 0;
        }
        if (!retval) {
+               if (reformat_serial)
+                       format_serial(serial);
                if (display_bus_id)
-                       printf("%s ", scsi_dev->name);
-               printf("%s", serial);
-               if (!hotplug_mode)
-                       printf("\n");
+                       printf("%s: ", scsi_dev->name);
+               printf("%s\n", serial);
                dprintf("%s\n", serial);
                retval = 0;
        }
-       fflush(stdout);
-       sysfs_close_class_device(scsi_dev);
+       sysfs_close_device(scsi_dev);
 
        if (!dev_specified)
                unlink(maj_min_dev);
@@ -768,11 +760,6 @@ int main(int argc, char **argv)
        if (getenv("DEBUG"))
                debug++;
 
-       if ((argc == 2) && (argv[1][0] != '-')) {
-               hotplug_mode = 1;
-               dprintf("hotplug assumed\n");
-       }
-
        dprintf("argc is %d\n", argc);
        if (sysfs_get_mnt_path(sysfs_mnt_path, MAX_NAME_LEN)) {
                log_message(LOG_WARNING, "sysfs_get_mnt_path failed: %s\n",
@@ -780,42 +767,37 @@ int main(int argc, char **argv)
                exit(1);
        }
 
-       if (hotplug_mode) {
+       devpath = getenv("DEVPATH");
+       if (devpath) {
                /*
-                * There is a kernel race creating attributes, if called
-                * directly, uncomment the sleep.
+                * This implies that we were invoked via udev or hotplug.
                 */
-               /* sleep(1); */
-
-               devpath = getenv("DEVPATH");
-               if (!devpath) {
-                       log_message(LOG_WARNING, "DEVPATH is not set\n");
-                       exit(1);
-               }
+               hotplug_mode = 1;
                sys_specified = 1;
-
                strncpy(target_path, sysfs_mnt_path, MAX_NAME_LEN);
                strncat(target_path, devpath, MAX_NAME_LEN);
-       } else {
-               if (set_options(argc, argv, short_options, long_options,
-                               target_path, maj_min_dev) < 0)
-                       exit(1);
        }
 
        /*
-        * Override any command line options set via the config file. This
-        * is the only way to set options when in hotplug mode.
+        * Get config file options.
         */
        newargv = NULL;
        retval = get_file_options(NULL, NULL, &newargc, &newargv);
        if (retval < 0) {
                exit(1);
        } else if (newargv && (retval == 0)) {
-               if (set_options(newargc, newargv, short_options, long_options,
-                               target_path, maj_min_dev) < 0)
+               if (set_options(newargc, newargv, short_options, target_path,
+                               maj_min_dev) < 0)
                        exit(1);
                free(newargv);
        }
+       /*
+        * Get command line options (overriding any config file or DEVPATH
+        * settings).
+        */
+       if (set_options(argc, argv, short_options, target_path,
+                       maj_min_dev) < 0)
+               exit(1);
 
        if (!sys_specified) {
                log_message(LOG_WARNING, "-s must be specified\n");