diff options
Diffstat (limited to 'mkfs.ubifs/devtable.c')
-rw-r--r-- | mkfs.ubifs/devtable.c | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/mkfs.ubifs/devtable.c b/mkfs.ubifs/devtable.c new file mode 100644 index 0000000..3f38339 --- /dev/null +++ b/mkfs.ubifs/devtable.c @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2008 Nokia Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Artem Bityutskiy + * + * Part of the device table parsing code was taken from the mkfs.jffs2 utility. + * The original author of that code is Erik Andersen, hence: + * Copyright (C) 2001, 2002 Erik Andersen <andersen@codepoet.org> + */ + +/* + * This file implemented device table support. Device table entries take the + * form of: + * <path> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count> + * /dev/mem c 640 0 0 1 1 0 0 - + * + * Type can be one of: + * f A regular file + * d Directory + * c Character special device file + * b Block special device file + * p Fifo (named pipe) + * + * Don't bother with symlinks (permissions are irrelevant), hard links (special + * cases of regular files), or sockets (why bother). + * + * Regular files must exist in the target root directory. If a char, block, + * fifo, or directory does not exist, it will be created. + * + * Please, refer the device_table.txt file which can be found at MTD utilities + * for more information about what the device table is. + */ + +#include "mkfs.ubifs.h" +#include "hashtable/hashtable.h" +#include "hashtable/hashtable_itr.h" + +/* + * The hash table which contains paths to files/directories/device nodes + * referred to in the device table. For example, if the device table refers + * "/dev/loop0", the @path_htbl will contain "/dev" element. + */ +static struct hashtable *path_htbl; + +/* Hash function used for hash tables */ +static unsigned int r5_hash(void *s) +{ + unsigned int a = 0; + const signed char *str = s; + + while (*str) { + a += *str << 4; + a += *str >> 4; + a *= 11; + str++; + } + + return a; +} + +/* + * Check whether 2 keys of a hash table are equivalent. The keys are path/file + * names, so we simply use 'strcmp()'. + */ +static int is_equivalent(void *k1, void *k2) +{ + return !strcmp(k1, k2); +} + +/** + * separate_last - separate out the last path component + * @buf: the path to split + * @len: length of the @buf string + * @path: the beginning of path is returned here + * @name: the last path component is returned here + * + * This helper function separates out the the last component of the full path + * string. For example, "/dev/loop" would be split on "/dev" and "loop". This + * function allocates memory for @path and @name and return the result there. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +static int separate_last(const char *buf, int len, char **path, char **name) +{ + int path_len = len, name_len; + const char *p = buf + len, *n; + + while (*--p != '/') + path_len -= 1; + + /* Drop the final '/' unless this is the root directory */ + name_len = len - path_len; + n = buf + path_len; + if (path_len > 1) + path_len -= 1; + + *path = malloc(path_len + 1); + if (!*path) + return err_msg("cannot allocate %d bytes of memory", + path_len + 1); + memcpy(*path, buf, path_len); + (*path)[path_len] = '\0'; + + *name = malloc(name_len + 1); + if (!*name) { + free(*path); + return err_msg("cannot allocate %d bytes of memory", + name_len + 1); + } + memcpy(*name, n, name_len + 1); + + return 0; +} + +static int interpret_table_entry(const char *root, const char *line) +{ + char buf[1024], type, *path = NULL, *name = NULL; + int len; + struct path_htbl_element *ph_elt = NULL; + struct name_htbl_element *nh_elt = NULL; + unsigned int mode = 0755, uid = 0, gid = 0, major = 0, minor = 0; + unsigned int start = 0, increment = 0, count = 0; + + if (sscanf(line, "%1023s %c %o %u %u %u %u %u %u %u", + buf, &type, &mode, &uid, &gid, &major, &minor, + &start, &increment, &count) < 0) + return sys_err_msg("sscanf failed"); + + dbg_msg(3, "name %s, type %c, mode %o, uid %u, gid %u, major %u, " + "minor %u, start %u, inc %u, cnt %u", + buf, type, mode, uid, gid, major, minor, start, + increment, count); + + len = strnlen(buf, 1024); + if (len == 1024) + return err_msg("too long path"); + + if (!strcmp(buf, "/")) + return err_msg("device table entries require absolute paths"); + if (buf[1] == '\0') + return err_msg("root directory cannot be created"); + if (strstr(buf, "//")) + return err_msg("'//' cannot be used in the path"); + if (buf[len - 1] == '/') + return err_msg("do not put '/' at the end"); + + if (strstr(buf, "/./") || strstr(buf, "/../") || + !strcmp(buf + len - 2, "/.") || !strcmp(buf + len - 3, "/..")) + return err_msg("'.' and '..' cannot be used in the path"); + + switch (type) { + case 'd': + mode |= S_IFDIR; + break; + case 'f': + mode |= S_IFREG; + break; + case 'p': + mode |= S_IFIFO; + break; + case 'c': + mode |= S_IFCHR; + break; + case 'b': + mode |= S_IFBLK; + break; + default: + return err_msg("unsupported file type '%c'", type); + } + + if (separate_last(buf, len, &path, &name)) + return -1; + + /* + * Check if this path already exist in the path hash table and add it + * if it is not. + */ + ph_elt = hashtable_search(path_htbl, path); + if (!ph_elt) { + dbg_msg(3, "inserting '%s' into path hash table", path); + ph_elt = malloc(sizeof(struct path_htbl_element)); + if (!ph_elt) { + err_msg("cannot allocate %d bytes of memory", + sizeof(struct path_htbl_element)); + goto out_free; + } + + if (!hashtable_insert(path_htbl, path, ph_elt)) { + err_msg("cannot insert into path hash table"); + goto out_free; + } + + ph_elt->path = path; + path = NULL; + ph_elt->name_htbl = create_hashtable(128, &r5_hash, + &is_equivalent); + if (!ph_elt->name_htbl) { + err_msg("cannot create name hash table"); + goto out_free; + } + } + + if (increment != 0 && count == 0) + return err_msg("count cannot be zero if increment is non-zero"); + + /* + * Add the file/directory/device node (last component of the path) to + * the name hashtable. The name hashtable resides in the corresponding + * path hashtable element. + */ + + if (count == 0) { + /* This entry does not require any iterating */ + nh_elt = malloc(sizeof(struct name_htbl_element)); + if (!nh_elt) { + err_msg("cannot allocate %d bytes of memory", + sizeof(struct name_htbl_element)); + goto out_free; + } + + nh_elt->mode = mode; + nh_elt->uid = uid; + nh_elt->gid = gid; + nh_elt->dev = makedev(major, minor); + + dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)", + name, major(nh_elt->dev), minor(nh_elt->dev)); + + if (hashtable_search(ph_elt->name_htbl, name)) + return err_msg("'%s' is referred twice", buf); + + nh_elt->name = name; + if (!hashtable_insert(ph_elt->name_htbl, name, nh_elt)) { + err_msg("cannot insert into name hash table"); + goto out_free; + } + } else { + int i, num = start + increment * count, len = strlen(name) + 20; + char *nm; + + for (i = start; i < num; i++) { + nh_elt = malloc(sizeof(struct name_htbl_element)); + if (!nh_elt) { + err_msg("cannot allocate %d bytes of memory", + sizeof(struct name_htbl_element)); + goto out_free; + } + + nh_elt->mode = mode; + nh_elt->uid = uid; + nh_elt->gid = gid; + nh_elt->dev = makedev(major, minor + i - start); + + nm = malloc(len); + if (!nm) { + err_msg("cannot allocate %d bytes of memory", len); + goto out_free; + } + + sprintf(nm, "%s%d", name, i); + nh_elt->name = nm; + + dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)", + nm, major(nh_elt->dev), minor(nh_elt->dev)); + + if (hashtable_search(ph_elt->name_htbl, nm)) { + err_msg("'%s' is referred twice", buf); + free (nm); + goto out_free; + } + + if (!hashtable_insert(ph_elt->name_htbl, nm, nh_elt)) { + err_msg("cannot insert into name hash table"); + free (nm); + goto out_free; + } + } + free(name); + name = NULL; + } + + return 0; + +out_free: + free(ph_elt); + free(nh_elt); + free(path); + free(name); + return -1; +} + +/** + * parse_devtable - parse the device table. + * @tbl_file: device table file name + * + * This function parses the device table and prepare the hash table which will + * later be used by mkfs.ubifs to create the specified files/device nodes. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +int parse_devtable(const char *root, const char *tbl_file) +{ + FILE *f; + char *line = NULL; + struct stat st; + size_t len; + + dbg_msg(1, "parsing device table file '%s'", tbl_file); + + path_htbl = create_hashtable(128, &r5_hash, &is_equivalent); + if (!path_htbl) + return err_msg("cannot create path hash table"); + + f = fopen(tbl_file, "r"); + if (!f) + return sys_err_msg("cannot open '%s'", tbl_file); + + if (fstat(fileno(f), &st) < 0) { + sys_err_msg("cannot stat '%s'", tbl_file); + goto out_close; + } + + if (st.st_size < 10) { + sys_err_msg("'%s' is too short", tbl_file); + goto out_close; + } + + /* + * The general plan now is to read in one line at a time, check for + * leading comment delimiters ('#'), then try and parse the line as a + * device table + */ + while (getline(&line, &len, f) != -1) { + /* First trim off any white-space */ + len = strlen(line); + + /* Trim trailing white-space */ + while (len > 0 && isspace(line[len - 1])) + line[--len] = '\0'; + /* Trim leading white-space */ + memmove(line, &line[strspn(line, " \n\r\t\v")], len); + + /* How long are we after trimming? */ + len = strlen(line); + + /* If this is not a comment line, try to interpret it */ + if (len && *line != '#') { + if (interpret_table_entry(root, line)) { + err_msg("cannot parse '%s'", line); + goto out_close; + } + } + + free(line); + line = NULL; + } + + dbg_msg(1, "finished parsing"); + fclose(f); + return 0; + +out_close: + fclose(f); + free_devtable_info(); + return -1; +} + +/** + * devtbl_find_path - find a path in the path hash table. + * @path: UBIFS path to find. + * + * This looks up the path hash table. Returns the path hash table element + * reference if @path was found and %NULL if not. + */ +struct path_htbl_element *devtbl_find_path(const char *path) +{ + if (!path_htbl) + return NULL; + + return hashtable_search(path_htbl, (void *)path); +} + +/** + * devtbl_find_name - find a name in the name hash table. + * @ph_etl: path hash table element to find at + * @name: name to find + * + * This looks up the name hash table. Returns the name hash table element + * reference if @name found and %NULL if not. + */ +struct name_htbl_element *devtbl_find_name(struct path_htbl_element *ph_elt, + const char *name) +{ + if (!path_htbl) + return NULL; + + return hashtable_search(ph_elt->name_htbl, (void *)name); +} + +/** + * override_attributes - override inode attributes. + * @st: struct stat object to containing the attributes to override + * @ph_elt: path hash table element object + * @nh_elt: name hash table element object containing the new values + * + * The device table file may override attributes like UID of files. For + * example, the device table may contain a "/dev" entry, and the UBIFS FS on + * the host may contain "/dev" directory. In this case the attributes of the + * "/dev" directory inode has to be as the device table specifies. + * + * Note, the hash element is removed by this function as well. + */ +int override_attributes(struct stat *st, struct path_htbl_element *ph_elt, + struct name_htbl_element *nh_elt) +{ + if (!path_htbl) + return 0; + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode) || + S_ISFIFO(st->st_mode)) + return err_msg("%s/%s both exists at UBIFS root at host, " + "and is referred from the device table", + strcmp(ph_elt->path, "/") ? ph_elt->path : "", + nh_elt->name); + + if ((st->st_mode & S_IFMT) != (nh_elt->mode & S_IFMT)) + return err_msg("%s/%s is referred from the device table also exists in " + "the UBIFS root directory at host, but the file type is " + "different", strcmp(ph_elt->path, "/") ? ph_elt->path : "", + nh_elt->name); + + dbg_msg(3, "set UID %d, GID %d, mode %o for %s/%s as device table says", + nh_elt->uid, nh_elt->gid, nh_elt->mode, ph_elt->path, nh_elt->name); + + st->st_uid = nh_elt->uid; + st->st_gid = nh_elt->gid; + st->st_mode = nh_elt->mode; + + hashtable_remove(ph_elt->name_htbl, (void *)nh_elt->name); + return 0; +} + +/** + * first_name_htbl_element - return first element of the name hash table. + * @ph_elt: the path hash table the name hash table belongs to + * @itr: double pointer to a 'struct hashtable_itr' object where the + * information about further iterations is stored + * + * This function implements name hash table iteration together with + * 'next_name_htbl_element()'. Returns the first name hash table element or + * %NULL if the hash table is empty. + */ +struct name_htbl_element * +first_name_htbl_element(struct path_htbl_element *ph_elt, + struct hashtable_itr **itr) +{ + if (!path_htbl || !ph_elt || hashtable_count(ph_elt->name_htbl) == 0) + return NULL; + + *itr = hashtable_iterator(ph_elt->name_htbl); + return hashtable_iterator_value(*itr); +} + +/** + * first_name_htbl_element - return next element of the name hash table. + * @ph_elt: the path hash table the name hash table belongs to + * @itr: double pointer to a 'struct hashtable_itr' object where the + * information about further iterations is stored + * + * This function implements name hash table iteration together with + * 'first_name_htbl_element()'. Returns the next name hash table element or + * %NULL if there are no more elements. + */ +struct name_htbl_element * +next_name_htbl_element(struct path_htbl_element *ph_elt, + struct hashtable_itr **itr) +{ + if (!path_htbl || !ph_elt || !hashtable_iterator_advance(*itr)) + return NULL; + + return hashtable_iterator_value(*itr); +} + +/** + * free_devtable_info - free device table information. + * + * This function frees the path hash table and the name hash tables. + */ +void free_devtable_info(void) +{ + struct hashtable_itr *ph_itr; + struct path_htbl_element *ph_elt; + + if (!path_htbl) + return; + + if (hashtable_count(path_htbl) > 0) { + ph_itr = hashtable_iterator(path_htbl); + do { + ph_elt = hashtable_iterator_value(ph_itr); + /* + * Note, since we use the same string for the key and + * @name in the name hash table elements, we do not + * have to iterate name hash table because @name memory + * will be freed when freeing the key. + */ + hashtable_destroy(ph_elt->name_htbl, 1); + } while (hashtable_iterator_advance(ph_itr)); + } + hashtable_destroy(path_htbl, 1); +} |