/* * 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 */ /* * This file implemented device table support. Device table entries take the * form of: * * /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) * l Link * * Don't bother with 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 #include #include #include #include #include "devtable.h" #include "ubifs.h" #include "defs.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 errmsg("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 errmsg("cannot allocate %d bytes of memory", name_len + 1); } memcpy(*name, n, name_len + 1); return 0; } static int interpret_table_entry(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_errmsg("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 == 0) return errmsg("empty path"); if (len == 1024) return errmsg("too long path"); if (buf[0] != '/') return errmsg("device table entries require absolute paths"); if (strstr(buf, "//")) return errmsg("'//' cannot be used in the path"); if (len > 1 && buf[len - 1] == '/') return errmsg("do not put '/' at the end"); if (strstr(buf, "/./") || strstr(buf, "/../") || !strcmp(buf + len - 2, "/.") || !strcmp(buf + len - 3, "/..")) return errmsg("'.' 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; case 'l': mode |= S_IFLNK; if ((mode & 0777) != 0777) return errmsg("link permission must be 0777"); break; default: return errmsg("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) { errmsg("cannot allocate %zd bytes of memory", sizeof(struct path_htbl_element)); goto out_free; } if (!hashtable_insert(path_htbl, path, ph_elt)) { errmsg("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) { errmsg("cannot create name hash table"); goto out_free; } } if (increment != 0 && count == 0) { errmsg("count cannot be zero if increment is non-zero"); goto out_free; } /* * 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) { errmsg("cannot allocate %zd 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)) { errmsg("'%s' is referred twice", buf); goto out_free; } nh_elt->name = name; if (!hashtable_insert(ph_elt->name_htbl, name, nh_elt)) { errmsg("cannot insert into name hash table"); goto out_free; } } else { int i, num = start + count, len = strlen(name) + 20; char *nm; for (i = start; i < num; i++) { nh_elt = malloc(sizeof(struct name_htbl_element)); if (!nh_elt) { errmsg("cannot allocate %zd 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) * increment); nm = malloc(len); if (!nm) { errmsg("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)) { errmsg("'%s' is referred twice", buf); free (nm); goto out_free; } if (!hashtable_insert(ph_elt->name_htbl, nm, nh_elt)) { errmsg("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 *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 errmsg("cannot create path hash table"); f = fopen(tbl_file, "r"); if (!f) return sys_errmsg("cannot open '%s'", tbl_file); if (fstat(fileno(f), &st) < 0) { sys_errmsg("cannot stat '%s'", tbl_file); goto out_close; } if (st.st_size < 10) { sys_errmsg("'%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(line)) { errmsg("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 errmsg("%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 errmsg("%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)); free(ph_itr); } hashtable_destroy(path_htbl, 1); }