chiark / gitweb /
make ATTR{[$SUBSYSTEM/$KERNEL]<attr>}="<value>" working
[elogind.git] / udev_rules.c
index 526993e21ca64043ac483f26488a2c8698b3484c..6ef320dfc72385983a1d2d6791dcfac87eaecde4 100644 (file)
@@ -87,54 +87,29 @@ static int get_key(char **line, char **key, char **value)
                linepos++;
 
        /* get the key */
+       temp = strchr(linepos, '=');
+       if (temp == NULL || temp == linepos)
+               return -1;
+       temp[0] = '\0';
        *key = linepos;
-       while (1) {
-               linepos++;
-               if (linepos[0] == '\0')
-                       return -1;
-               if (isspace(linepos[0]))
-                       break;
-               if (linepos[0] == '=')
-                       break;
+       linepos = &temp[1];
+
+       /* get a quoted value */
+       if (linepos[0] == '"' || linepos[0] == '\'') {
+               temp = strchr(&linepos[1], linepos[0]);
+               if (temp != NULL) {
+                       temp[0] = '\0';
+                       *value = &linepos[1];
+                       goto out;
+               }
        }
 
-       /* terminate key */
-       linepos[0] = '\0';
-       linepos++;
-
-       /* skip whitespace */
-       while (isspace(linepos[0]))
-               linepos++;
-
        /* get the value*/
-       if (linepos[0] == '"') {
-               linepos++;
-               temp = strchr(linepos, '"');
-               if (!temp) {
-                       dbg("missing closing quote");
-                       return -1;
-               }
-               dbg("value is quoted");
+       temp = strchr(linepos, '\n');
+       if (temp != NULL)
                temp[0] = '\0';
-       } else if (linepos[0] == '\'') {
-               linepos++;
-               temp = strchr(linepos, '\'');
-               if (!temp) {
-                       dbg("missing closing quote");
-                       return -1;
-               }
-               dbg("value is quoted");
-               temp[0] = '\0';
-       } else if (linepos[0] == '\0') {
-               dbg("value is empty");
-       } else {
-               temp = linepos;
-               while (temp[0] && !isspace(temp[0]))
-                       temp++;
-               temp[0] = '\0';
-       }
        *value = linepos;
-
+out:
        return 0;
 }
 
@@ -292,10 +267,48 @@ static int wait_for_sysfs(struct udevice *udev, const char *file, int timeout)
                info("wait for '%s' for %i mseconds", filepath, 1000 / WAIT_LOOP_PER_SECOND);
                usleep(1000 * 1000 / WAIT_LOOP_PER_SECOND);
        }
-       err("waiting for '%s' failed", filepath);
+       info("waiting for '%s' failed", filepath);
        return -1;
 }
 
+/* handle "[$SUBSYSTEM/$KERNEL]<attribute>" lookup */
+static int attr_get_by_subsys_id(const char *attrstr, char *devpath, size_t len, char **attr)
+{
+       char subsys[NAME_SIZE];
+       char *attrib;
+       char *id;
+       int found = 0;
+
+       if (attrstr[0] != '[')
+               goto out;
+
+       strlcpy(subsys, &attrstr[1], sizeof(subsys));
+
+       attrib = strchr(subsys, ']');
+       if (attrib == NULL)
+               goto out;
+       attrib[0] = '\0';
+       attrib = &attrib[1];
+
+       id = strchr(subsys, '/');
+       if (id == NULL)
+               goto out;
+       id[0] = '\0';
+       id = &id[1];
+
+       if (sysfs_lookup_devpath_by_subsys_id(devpath, len, subsys, id)) {
+               if (attr != NULL) {
+                       if (attrib[0] != '\0')
+                               *attr = attrib;
+                       else
+                               *attr = NULL;
+               }
+               found = 1;
+       }
+out:
+       return found;
+}
+
 void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize)
 {
        char temp[PATH_SIZE];
@@ -317,6 +330,7 @@ void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize)
                SUBST_PARENT,
                SUBST_TEMP_NODE,
                SUBST_ROOT,
+               SUBST_SYS,
                SUBST_ENV,
        };
        static const struct subst_map {
@@ -336,6 +350,7 @@ void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize)
                { .name = "parent",     .fmt = 'P',     .type = SUBST_PARENT },
                { .name = "tempnode",   .fmt = 'N',     .type = SUBST_TEMP_NODE },
                { .name = "root",       .fmt = 'r',     .type = SUBST_ROOT },
+               { .name = "sys",        .fmt = 'S',     .type = SUBST_SYS },
                { .name = "env",        .fmt = 'E',     .type = SUBST_ENV },
                { NULL, '\0', 0 }
        };
@@ -466,11 +481,20 @@ found:
                        if (attr == NULL)
                                err("missing file parameter for attr");
                        else {
+                               char devpath[PATH_SIZE];
+                               char *attrib;
                                const char *value = NULL;
                                size_t size;
 
-                               /* first try the current device, other matches may have selected */
-                               if (udev->dev_parent != NULL && udev->dev_parent != udev->dev)
+                               if (attr_get_by_subsys_id(attr, devpath, sizeof(devpath), &attrib)) {
+                                       if (attrib != NULL)
+                                               value = sysfs_attr_get_value(devpath, attrib);
+                                       else
+                                               break;
+                               }
+
+                               /* try the current device, other matches may have selected */
+                               if (value == NULL && udev->dev_parent != NULL && udev->dev_parent != udev->dev)
                                        value = sysfs_attr_get_value(udev->dev_parent->devpath, attr);
 
                                /* look at all devices along the chain of parents */
@@ -541,6 +565,10 @@ found:
                        strlcat(string, udev_root, maxsize);
                        dbg("substitute udev_root '%s'", udev_root);
                        break;
+               case SUBST_SYS:
+                       strlcat(string, sysfs_path, maxsize);
+                       dbg("substitute sysfs_path '%s'", sysfs_path);
+                       break;
                case SUBST_ENV:
                        if (attr == NULL) {
                                dbg("missing attribute");
@@ -660,6 +688,40 @@ static int match_rule(struct udevice *udev, struct udev_rule *rule)
                }
        }
 
+       if (rule->test.operation != KEY_OP_UNSET) {
+               char filename[PATH_SIZE];
+               char devpath[PATH_SIZE];
+               char *attr;
+               struct stat statbuf;
+               int match;
+
+               strlcpy(filename, key_val(rule, &rule->test), sizeof(filename));
+               udev_rules_apply_format(udev, filename, sizeof(filename));
+
+               if (attr_get_by_subsys_id(filename, devpath, sizeof(devpath), &attr)) {
+                       strlcpy(filename, sysfs_path, sizeof(filename));
+                       strlcat(filename, devpath, sizeof(filename));
+                       if (attr != NULL) {
+                               strlcat(filename, "/", sizeof(filename));
+                               strlcat(filename, attr, sizeof(filename));
+                       }
+               }
+
+               match = (stat(filename, &statbuf) == 0);
+               info("'%s' %s", filename, match ? "exists" : "does not exist");
+               if (match && rule->test_mode_mask > 0) {
+                       match = ((statbuf.st_mode & rule->test_mode_mask) > 0);
+                       info("'%s' has mode=%#o and %s %#o", filename, statbuf.st_mode,
+                            match ? "matches" : "does not match",
+                            rule->test_mode_mask);
+               }
+               if (match && rule->test.operation == KEY_OP_NOMATCH)
+                       goto nomatch;
+               if (!match && rule->test.operation == KEY_OP_MATCH)
+                       goto nomatch;
+               dbg("TEST key is true");
+       }
+
        if (rule->wait_for_sysfs.operation != KEY_OP_UNSET) {
                int found;
 
@@ -676,11 +738,20 @@ static int match_rule(struct udevice *udev, struct udev_rule *rule)
                    pair->key.operation == KEY_OP_NOMATCH) {
                        const char *key_name = key_pair_name(rule, pair);
                        const char *key_value = key_val(rule, &pair->key);
-                       const char *value;
+                       char devpath[PATH_SIZE];
+                       char *attrib;
+                       const char *value = NULL;
                        char val[VALUE_SIZE];
                        size_t len;
 
-                       value = sysfs_attr_get_value(udev->dev->devpath, key_name);
+                       if (attr_get_by_subsys_id(key_name, devpath, sizeof(devpath), &attrib)) {
+                               if (attrib != NULL)
+                                       value = sysfs_attr_get_value(devpath, attrib);
+                               else
+                                       goto nomatch;
+                       }
+                       if (value == NULL)
+                               value = sysfs_attr_get_value(udev->dev->devpath, key_name);
                        if (value == NULL)
                                goto nomatch;
                        strlcpy(val, value, sizeof(val));
@@ -852,14 +923,28 @@ try_parent:
 
                if (pair->key.operation == KEY_OP_ASSIGN) {
                        const char *key_name = key_pair_name(rule, pair);
-                       char attr[PATH_SIZE];
+                       char devpath[PATH_SIZE];
+                       char *attrib;
+                       char attr[PATH_SIZE] = "";
                        char value[NAME_SIZE];
                        FILE *f;
 
-                       strlcpy(attr, sysfs_path, sizeof(attr));
-                       strlcat(attr, udev->dev->devpath, sizeof(attr));
-                       strlcat(attr, "/", sizeof(attr));
-                       strlcat(attr, key_name, sizeof(attr));
+                       if (attr_get_by_subsys_id(key_name, devpath, sizeof(devpath), &attrib)) {
+                               if (attrib != NULL) {
+                                       strlcpy(attr, sysfs_path, sizeof(attr));
+                                       strlcat(attr, devpath, sizeof(attr));
+                                       strlcat(attr, "/", sizeof(attr));
+                                       strlcat(attr, attrib, sizeof(attr));
+                               }
+                       }
+
+                       if (attr[0] == '\0') {
+                               strlcpy(attr, sysfs_path, sizeof(attr));
+                               strlcat(attr, udev->dev->devpath, sizeof(attr));
+                               strlcat(attr, "/", sizeof(attr));
+                               strlcat(attr, key_name, sizeof(attr));
+                       }
+
                        strlcpy(value, key_val(rule, &pair->key), sizeof(value));
                        udev_rules_apply_format(udev, value, sizeof(value));
                        info("writing '%s' to sysfs file '%s'", value, attr);
@@ -1056,7 +1141,8 @@ int udev_rules_get_run(struct udev_rules *rules, struct udevice *udev)
 
                dbg("process rule");
                if (rule->name.operation != KEY_OP_UNSET || rule->symlink.operation != KEY_OP_UNSET ||
-                   rule->mode_operation != KEY_OP_UNSET || rule->owner.operation != KEY_OP_UNSET || rule->group.operation != KEY_OP_UNSET) {
+                   rule->mode_operation != KEY_OP_UNSET || rule->owner.operation != KEY_OP_UNSET ||
+                   rule->group.operation != KEY_OP_UNSET) {
                        dbg("skip rule that names a device");
                        continue;
                }
@@ -1069,7 +1155,8 @@ int udev_rules_get_run(struct udev_rules *rules, struct udevice *udev)
                        }
 
                        if (!udev->run_final && rule->run.operation != KEY_OP_UNSET) {
-                               if (rule->run.operation == KEY_OP_ASSIGN || rule->run.operation == KEY_OP_ASSIGN_FINAL) {
+                               if (rule->run.operation == KEY_OP_ASSIGN ||
+                                   rule->run.operation == KEY_OP_ASSIGN_FINAL) {
                                        info("reset run list");
                                        name_list_cleanup(&udev->run_list);
                                }