#include <unistd.h>
#include <getopt.h>
#include <string.h>
+#include <ctype.h>
#include "util.h"
#include "strbuf.h"
#include "conf-files.h"
#include "udev.h"
-#include "libudev-hwdb-def.h"
+#include "hwdb-internal.h"
/*
* Generic udev properties, key/value database based on modalias strings.
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;
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) {
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;
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 {
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);
}
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;
fchmod(fileno(t.f), 0444);
/* write nodes */
- fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
+ err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
root_off = trie_store_nodes(&t, trie->root);
h.nodes_root_off = htole64(root_off);
pos = ftello(t.f);
/* write header */
size = ftello(t.f);
h.file_size = htole64(size);
- fseeko(t.f, 0, SEEK_SET);
+ err = fseeko(t.f, 0, SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
err = ferror(t.f);
if (err)
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++;
+
+ /* libudev requires properties to start with a space */
+ while (isblank(line[0]) && isblank(line[1]))
+ line++;
+
+ 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"
+ " --usr generate in " UDEVLIBEXECDIR " instead of /etc/udev\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[]) {
+ enum {
+ ARG_USR = 0x100,
+ };
+
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' },
+ { "usr", no_argument, NULL, ARG_USR },
+ { "test", required_argument, NULL, 't' },
+ { "root", required_argument, NULL, 'r' },
+ { "help", no_argument, NULL, 'h' },
{}
};
const char *test = NULL;
+ const char *root = "";
+ const char *hwdb_bin_dir = "/etc/udev";
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 ARG_USR:
+ hwdb_bin_dir = UDEVLIBEXECDIR;
+ 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;
}
/* 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_errno(err, "failed to enumerate hwdb files: %m");
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);
+ hwdb_bin = strjoin(root, "/", hwdb_bin_dir, "/hwdb.bin", NULL);
+ if (!hwdb_bin) {
+ 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_errno(err, "Failure writing database %s: %m", hwdb_bin);
rc = EXIT_FAILURE;
}
}
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: