528 lines
14 KiB
C
528 lines
14 KiB
C
/*
|
|
* 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 *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;
|
|
case 's':
|
|
mode |= S_IFLNK;
|
|
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 %zd 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 %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))
|
|
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 + 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 %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) {
|
|
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 *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(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);
|
|
}
|