chiark / gitweb /
udev-builtin-blkid: modernizations and minor fixes
[elogind.git] / src / udev / udevadm-hwdb.c
index fd288db1cb73d68cf162743b46c0ca807225e0b1..65cbf618656a970ec447b2eed276625a77753c0c 100644 (file)
@@ -21,6 +21,7 @@
 #include <unistd.h>
 #include <getopt.h>
 #include <string.h>
+#include <ctype.h>
 
 #include "util.h"
 #include "strbuf.h"
@@ -84,14 +85,12 @@ static int trie_children_cmp(const void *v1, const void *v2) {
 
 static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
         struct trie_child_entry *child;
-        int err = 0;
 
         /* extend array, add new entry, sort for bisection */
         child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
-        if (!child) {
-                err = -ENOMEM;
-                goto out;
-        }
+        if (!child)
+                return -ENOMEM;
+
         node->children = child;
         trie->children_count++;
         node->children[node->children_count].c = c;
@@ -99,8 +98,8 @@ static int node_add_child(struct trie *trie, struct trie_node *node, struct trie
         node->children_count++;
         qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
         trie->nodes_count++;
-out:
-        return err;
+
+        return 0;
 }
 
 static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
@@ -183,46 +182,44 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se
                 struct trie_node *child;
 
                 for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
-                        char *s;
+                        _cleanup_free_ char *s = NULL;
                         ssize_t off;
+                        _cleanup_free_ struct trie_node *new_child = NULL;
 
                         if (c == search[i + p])
                                 continue;
 
                         /* split node */
-                        child = calloc(sizeof(struct trie_node), 1);
-                        if (!child) {
-                                err = -ENOMEM;
-                                goto out;
-                        }
+                        new_child = new0(struct trie_node, 1);
+                        if (!new_child)
+                                return -ENOMEM;
 
                         /* move values from parent to child */
-                        child->prefix_off = node->prefix_off + p+1;
-                        child->children = node->children;
-                        child->children_count = node->children_count;
-                        child->values = node->values;
-                        child->values_count = node->values_count;
+                        new_child->prefix_off = node->prefix_off + p+1;
+                        new_child->children = node->children;
+                        new_child->children_count = node->children_count;
+                        new_child->values = node->values;
+                        new_child->values_count = node->values_count;
 
                         /* update parent; use strdup() because the source gets realloc()d */
                         s = strndup(trie->strings->buf + node->prefix_off, p);
-                        if (!s) {
-                                err = -ENOMEM;
-                                goto out;
-                        }
+                        if (!s)
+                                return -ENOMEM;
+
                         off = strbuf_add_string(trie->strings, s, p);
-                        free(s);
-                        if (off < 0) {
-                                err = off;
-                                goto out;
-                        }
+                        if (off < 0)
+                                return off;
+
                         node->prefix_off = off;
                         node->children = NULL;
                         node->children_count = 0;
                         node->values = NULL;
                         node->values_count = 0;
-                        err = node_add_child(trie, node, child, c);
+                        err = node_add_child(trie, node, new_child, c);
                         if (err)
-                                goto out;
+                                return err;
+
+                        new_child = NULL; /* avoid cleanup */
                         break;
                 }
                 i += p;
@@ -236,28 +233,29 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se
                         ssize_t off;
 
                         /* new child */
-                        child = calloc(sizeof(struct trie_node), 1);
-                        if (!child) {
-                                err = -ENOMEM;
-                                goto out;
-                        }
+                        child = new0(struct trie_node, 1);
+                        if (!child)
+                                return -ENOMEM;
+
                         off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
                         if (off < 0) {
-                                err = off;
-                                goto out;
+                                free(child);
+                                return off;
                         }
+
                         child->prefix_off = off;
                         err = node_add_child(trie, node, child, c);
-                        if (err)
-                                goto out;
+                        if (err) {
+                                free(child);
+                                return err;
+                        }
+
                         return trie_node_add_value(trie, child, key, value);
                 }
 
                 node = child;
                 i++;
         }
-out:
-        return err;
 }
 
 struct trie_f {
@@ -305,8 +303,10 @@ static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
                 int64_t child_off;
 
                 child_off = trie_store_nodes(trie, node->children[i].child);
-                if (child_off < 0)
+                if (child_off < 0) {
+                        free(children);
                         return child_off;
+                }
                 children[i].c = node->children[i].c;
                 children[i].child_off = htole64(child_off);
         }
@@ -341,7 +341,7 @@ static int trie_store(struct trie *trie, const char *filename) {
         struct trie_f t = {
                 .trie = trie,
         };
-        char *filename_tmp;
+        _cleanup_free_ char *filename_tmp = NULL;
         int64_t pos;
         int64_t root_off;
         int64_t size;
@@ -385,138 +385,197 @@ static int trie_store(struct trie *trie, const char *filename) {
                 err = -errno;
         fclose(t.f);
         if (err < 0 || rename(filename_tmp, filename) < 0) {
-                unlink(filename_tmp);
-                goto out;
+                unlink_noerrno(filename_tmp);
+                return err < 0 ? err : -errno;
         }
 
-        log_debug("=== trie on-disk ===\n");
-        log_debug("size:             %8llu bytes\n", (unsigned long long)size);
-        log_debug("header:           %8zu bytes\n", sizeof(struct trie_header_f));
-        log_debug("nodes:            %8llu bytes (%8llu)\n",
-                  (unsigned long long)t.nodes_count * sizeof(struct trie_node_f), (unsigned long long)t.nodes_count);
-        log_debug("child pointers:   %8llu bytes (%8llu)\n",
-                  (unsigned long long)t.children_count * sizeof(struct trie_child_entry_f), (unsigned long long)t.children_count);
-        log_debug("value pointers:   %8llu bytes (%8llu)\n",
-                  (unsigned long long)t.values_count * sizeof(struct trie_value_entry_f), (unsigned long long)t.values_count);
-        log_debug("string store:     %8llu bytes\n", (unsigned long long)trie->strings->len);
-        log_debug("strings start:    %8llu\n", (unsigned long long) t.strings_off);
-out:
-        free(filename_tmp);
-        return err;
+        log_debug("=== trie on-disk ===");
+        log_debug("size:             %8"PRIu64" bytes", size);
+        log_debug("header:           %8zu bytes", sizeof(struct trie_header_f));
+        log_debug("nodes:            %8"PRIu64" bytes (%8"PRIu64")",
+                  t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
+        log_debug("child pointers:   %8"PRIu64" bytes (%8"PRIu64")",
+                  t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
+        log_debug("value pointers:   %8"PRIu64" bytes (%8"PRIu64")",
+                  t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
+        log_debug("string store:     %8zu bytes", trie->strings->len);
+        log_debug("strings start:    %8"PRIu64, t.strings_off);
+
+        return 0;
 }
 
-static int import_file(struct trie *trie, const char *filename) {
+static int insert_data(struct trie *trie, struct udev_list *match_list,
+                       char *line, const char *filename) {
+        char *value;
+        struct udev_list_entry *entry;
+
+        value = strchr(line, '=');
+        if (!value) {
+                log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename);
+                return -EINVAL;
+        }
+
+        value[0] = '\0';
+        value++;
+
+        if (line[0] == '\0' || value[0] == '\0') {
+                log_error("Error, empty key or value '%s' in '%s':", line, filename);
+                return -EINVAL;
+        }
+
+        udev_list_entry_foreach(entry, udev_list_get_entry(match_list))
+                trie_insert(trie, trie->root, udev_list_entry_get_name(entry), line, value);
+
+        return 0;
+}
+
+static int import_file(struct udev *udev, struct trie *trie, const char *filename) {
+        enum {
+                HW_MATCH,
+                HW_DATA,
+                HW_NONE,
+        } state = HW_NONE;
         FILE *f;
         char line[LINE_MAX];
-        char match[LINE_MAX];
-        char cond[LINE_MAX];
+        struct udev_list match_list;
+
+        udev_list_init(udev, &match_list, false);
 
         f = fopen(filename, "re");
         if (f == NULL)
                 return -errno;
 
-        match[0] = '\0';
-        cond[0] = '\0';
         while (fgets(line, sizeof(line), f)) {
                 size_t len;
+                char *pos;
 
+                /* comment line */
                 if (line[0] == '#')
                         continue;
 
-                /* new line, new record */
-                if (line[0] == '\n') {
-                        match[0] = '\0';
-                        cond[0] = '\0';
-                        continue;
-                }
+                /* strip trailing comment */
+                pos = strchr(line, '#');
+                if (pos)
+                        pos[0] = '\0';
 
-                /* remove newline */
+                /* strip trailing whitespace */
                 len = strlen(line);
-                if (len < 2)
-                        continue;
-                line[len-1] = '\0';
+                while (len > 0 && isspace(line[len-1]))
+                        len--;
+                line[len] = '\0';
+
+                switch (state) {
+                case HW_NONE:
+                        if (len == 0)
+                                break;
+
+                        if (line[0] == ' ') {
+                                log_error("Error, MATCH expected but got '%s' in '%s':", line, filename);
+                                break;
+                        }
 
-                /* start of new record */
-                if (match[0] == '\0') {
-                        strcpy(match, line);
-                        cond[0] = '\0';
-                        continue;
-                }
+                        /* start of record, first match */
+                        state = HW_MATCH;
+                        udev_list_entry_add(&match_list, line, NULL);
+                        break;
 
-                if (line[0] == '+') {
-                        strcpy(cond, line);
-                        continue;
-                }
+                case HW_MATCH:
+                        if (len == 0) {
+                                log_error("Error, DATA expected but got empty line in '%s':", filename);
+                                state = HW_NONE;
+                                udev_list_cleanup(&match_list);
+                                break;
+                        }
 
-                /* TODO: support +; skip the entire record until we support it */
-                if (cond[0] != '\0')
-                        continue;
+                        /* another match */
+                        if (line[0] != ' ') {
+                                udev_list_entry_add(&match_list, line, NULL);
+                                break;
+                        }
 
-                /* value lines */
-                if (line[0] == ' ') {
-                        char *value;
+                        /* first data */
+                        state = HW_DATA;
+                        insert_data(trie, &match_list, line, filename);
+                        break;
 
-                        value = strchr(line, '=');
-                        if (!value)
-                                continue;
-                        value[0] = '\0';
-                        value++;
-                        trie_insert(trie, trie->root, match, line, value);
-                }
+                case HW_DATA:
+                        /* end of record */
+                        if (len == 0) {
+                                state = HW_NONE;
+                                udev_list_cleanup(&match_list);
+                                break;
+                        }
+
+                        if (line[0] != ' ') {
+                                log_error("Error, DATA expected but got '%s' in '%s':", line, filename);
+                                state = HW_NONE;
+                                udev_list_cleanup(&match_list);
+                                break;
+                        }
+
+                        insert_data(trie, &match_list, line, filename);
+                        break;
+                };
         }
+
         fclose(f);
+        udev_list_cleanup(&match_list);
         return 0;
 }
 
 static void help(void) {
-        printf("Usage: udevadm hwdb [--create] [--help]\n"
-               "  --update            update the hardware database\n"
-               "  --test <modalias>   query database and print result\n"
-               "  --help\n\n");
+        printf("Usage: udevadm hwdb OPTIONS\n"
+               "  -u,--update          update the hardware database\n"
+               "  -t,--test=MODALIAS   query database and print result\n"
+               "  -r,--root=PATH       alternative root path in the filesystem\n"
+               "  -h,--help\n\n");
 }
 
 static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
         static const struct option options[] = {
-                { "update", no_argument, NULL, 'u' },
-                { "test", required_argument, NULL, 't' },
-                { "help", no_argument, NULL, 'h' },
+                { "update", no_argument,       NULL, 'u' },
+                { "test",   required_argument, NULL, 't' },
+                { "root",   required_argument, NULL, 'r' },
+                { "help",   no_argument,       NULL, 'h' },
                 {}
         };
         const char *test = NULL;
+        const char *root = "";
         bool update = false;
         struct trie *trie = NULL;
-        int err;
+        int err, c;
         int rc = EXIT_SUCCESS;
 
-        for (;;) {
-                int option;
-
-                option = getopt_long(argc, argv, "ut:h", options, NULL);
-                if (option == -1)
-                        break;
-
-                switch (option) {
+        while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0)
+                switch(c) {
                 case 'u':
                         update = true;
                         break;
                 case 't':
                         test = optarg;
                         break;
+                case 'r':
+                        root = optarg;
+                        break;
                 case 'h':
                         help();
                         return EXIT_SUCCESS;
+                case '?':
+                        return EXIT_FAILURE;
+                default:
+                        assert_not_reached("Unknown option");
                 }
-        }
 
         if (!update && !test) {
-                help();
-                return EXIT_SUCCESS;
+                log_error("Either --update or --test must be used");
+                return EXIT_FAILURE;
         }
 
         if (update) {
                 char **files, **f;
+                _cleanup_free_ char *hwdb_bin = NULL;
 
-                trie = calloc(sizeof(struct trie), 1);
+                trie = new0(struct trie, 1);
                 if (!trie) {
                         rc = EXIT_FAILURE;
                         goto out;
@@ -530,46 +589,49 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
                 }
 
                 /* index */
-                trie->root = calloc(sizeof(struct trie_node), 1);
+                trie->root = new0(struct trie_node, 1);
                 if (!trie->root) {
                         rc = EXIT_FAILURE;
                         goto out;
                 }
                 trie->nodes_count++;
 
-                err = conf_files_list_strv(&files, ".hwdb", (const char **)conf_file_dirs);
+                err = conf_files_list_strv(&files, ".hwdb", root, conf_file_dirs);
                 if (err < 0) {
-                        log_error("failed to enumerate hwdb files: %s\n", strerror(-err));
+                        log_error("failed to enumerate hwdb files: %s", strerror(-err));
                         rc = EXIT_FAILURE;
                         goto out;
                 }
                 STRV_FOREACH(f, files) {
                         log_debug("reading file '%s'", *f);
-                        import_file(trie, *f);
+                        import_file(udev, trie, *f);
                 }
                 strv_free(files);
 
                 strbuf_complete(trie->strings);
 
-                log_debug("=== trie in-memory ===\n");
-                log_debug("nodes:            %8zu bytes (%8zu)\n",
+                log_debug("=== trie in-memory ===");
+                log_debug("nodes:            %8zu bytes (%8zu)",
                           trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
-                log_debug("children arrays:  %8zu bytes (%8zu)\n",
+                log_debug("children arrays:  %8zu bytes (%8zu)",
                           trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
-                log_debug("values arrays:    %8zu bytes (%8zu)\n",
+                log_debug("values arrays:    %8zu bytes (%8zu)",
                           trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
-                log_debug("strings:          %8zu bytes\n",
+                log_debug("strings:          %8zu bytes",
                           trie->strings->len);
-                log_debug("strings incoming: %8zu bytes (%8zu)\n",
+                log_debug("strings incoming: %8zu bytes (%8zu)",
                           trie->strings->in_len, trie->strings->in_count);
-                log_debug("strings dedup'ed: %8zu bytes (%8zu)\n",
+                log_debug("strings dedup'ed: %8zu bytes (%8zu)",
                           trie->strings->dedup_len, trie->strings->dedup_count);
 
-                mkdir_parents(HWDB_BIN, 0755);
-                err = trie_store(trie, HWDB_BIN);
+                if (asprintf(&hwdb_bin, "%s/etc/udev/hwdb.bin", root) < 0) {
+                        rc = EXIT_FAILURE;
+                        goto out;
+                }
+                mkdir_parents(hwdb_bin, 0755);
+                err = trie_store(trie, hwdb_bin);
                 if (err < 0) {
-                        log_error("Failure writing hardware database '%s': %s",
-                                  HWDB_BIN, strerror(-err));
+                        log_error("Failure writing database %s: %s", hwdb_bin, strerror(-err));
                         rc = EXIT_FAILURE;
                 }
         }
@@ -582,7 +644,7 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
 
                         udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, test, 0))
                                 printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry));
-                        hwdb = udev_hwdb_unref(hwdb);
+                        udev_hwdb_unref(hwdb);
                 }
         }
 out: