blob: dee035d5e948835ca728c2e824cd6609893cd625 [file] [log] [blame]
/*
* 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;
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);
}