u-boot/fs/ubifs/ubifs.c
Simon Glass 6e295186c7 Move malloc_cache_aligned() to its own header
At present malloc.h is included everywhere since it recently was added to
common.h in this commit:

   4519668 mtd/nand/ubi: assortment of alignment fixes

This seems wasteful and unnecessary. We have been trying to trim down
common.h and put separate functions into separate header files and that
change goes in the opposite direction.

Move malloc_cache_aligned() to a new header so that this can be avoided.
The header would perhaps be better named as alignmem.h but it needs to be
included after common.h and people might be confused by this. With the name
memalign.h it fits nicely after malloc() in most cases.

Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Marcel Ziswiler <marcel.ziswiler@toradex.com>
2015-09-11 17:15:16 -04:00

862 lines
19 KiB
C

/*
* This file is part of UBIFS.
*
* Copyright (C) 2006-2008 Nokia Corporation.
*
* (C) Copyright 2008-2010
* Stefan Roese, DENX Software Engineering, sr@denx.de.
*
* 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
*
* Authors: Artem Bityutskiy (Битюцкий Артём)
* Adrian Hunter
*/
#include <common.h>
#include <memalign.h>
#include "ubifs.h"
#include <u-boot/zlib.h>
#include <linux/err.h>
#include <linux/lzo.h>
DECLARE_GLOBAL_DATA_PTR;
/* compress.c */
/*
* We need a wrapper for zunzip() because the parameters are
* incompatible with the lzo decompressor.
*/
static int gzip_decompress(const unsigned char *in, size_t in_len,
unsigned char *out, size_t *out_len)
{
return zunzip(out, *out_len, (unsigned char *)in,
(unsigned long *)out_len, 0, 0);
}
/* Fake description object for the "none" compressor */
static struct ubifs_compressor none_compr = {
.compr_type = UBIFS_COMPR_NONE,
.name = "none",
.capi_name = "",
.decompress = NULL,
};
static struct ubifs_compressor lzo_compr = {
.compr_type = UBIFS_COMPR_LZO,
#ifndef __UBOOT__
.comp_mutex = &lzo_mutex,
#endif
.name = "lzo",
.capi_name = "lzo",
.decompress = lzo1x_decompress_safe,
};
static struct ubifs_compressor zlib_compr = {
.compr_type = UBIFS_COMPR_ZLIB,
#ifndef __UBOOT__
.comp_mutex = &deflate_mutex,
.decomp_mutex = &inflate_mutex,
#endif
.name = "zlib",
.capi_name = "deflate",
.decompress = gzip_decompress,
};
/* All UBIFS compressors */
struct ubifs_compressor *ubifs_compressors[UBIFS_COMPR_TYPES_CNT];
#ifdef __UBOOT__
/* from mm/util.c */
/**
* kmemdup - duplicate region of memory
*
* @src: memory region to duplicate
* @len: memory region length
* @gfp: GFP mask to use
*/
void *kmemdup(const void *src, size_t len, gfp_t gfp)
{
void *p;
p = kmalloc(len, gfp);
if (p)
memcpy(p, src, len);
return p;
}
struct crypto_comp {
int compressor;
};
static inline struct crypto_comp *crypto_alloc_comp(const char *alg_name,
u32 type, u32 mask)
{
struct ubifs_compressor *comp;
struct crypto_comp *ptr;
int i = 0;
ptr = malloc_cache_aligned(sizeof(struct crypto_comp));
while (i < UBIFS_COMPR_TYPES_CNT) {
comp = ubifs_compressors[i];
if (!comp) {
i++;
continue;
}
if (strncmp(alg_name, comp->capi_name, strlen(alg_name)) == 0) {
ptr->compressor = i;
return ptr;
}
i++;
}
if (i >= UBIFS_COMPR_TYPES_CNT) {
ubifs_err("invalid compression type %s", alg_name);
free (ptr);
return NULL;
}
return ptr;
}
static inline int crypto_comp_decompress(struct crypto_comp *tfm,
const u8 *src, unsigned int slen,
u8 *dst, unsigned int *dlen)
{
struct ubifs_compressor *compr = ubifs_compressors[tfm->compressor];
int err;
if (compr->compr_type == UBIFS_COMPR_NONE) {
memcpy(dst, src, slen);
*dlen = slen;
return 0;
}
err = compr->decompress(src, slen, dst, (size_t *)dlen);
if (err)
ubifs_err("cannot decompress %d bytes, compressor %s, "
"error %d", slen, compr->name, err);
return err;
return 0;
}
/* from shrinker.c */
/* Global clean znode counter (for all mounted UBIFS instances) */
atomic_long_t ubifs_clean_zn_cnt;
#endif
/**
* ubifs_decompress - decompress data.
* @in_buf: data to decompress
* @in_len: length of the data to decompress
* @out_buf: output buffer where decompressed data should
* @out_len: output length is returned here
* @compr_type: type of compression
*
* This function decompresses data from buffer @in_buf into buffer @out_buf.
* The length of the uncompressed data is returned in @out_len. This functions
* returns %0 on success or a negative error code on failure.
*/
int ubifs_decompress(const void *in_buf, int in_len, void *out_buf,
int *out_len, int compr_type)
{
int err;
struct ubifs_compressor *compr;
if (unlikely(compr_type < 0 || compr_type >= UBIFS_COMPR_TYPES_CNT)) {
ubifs_err("invalid compression type %d", compr_type);
return -EINVAL;
}
compr = ubifs_compressors[compr_type];
if (unlikely(!compr->capi_name)) {
ubifs_err("%s compression is not compiled in", compr->name);
return -EINVAL;
}
if (compr_type == UBIFS_COMPR_NONE) {
memcpy(out_buf, in_buf, in_len);
*out_len = in_len;
return 0;
}
if (compr->decomp_mutex)
mutex_lock(compr->decomp_mutex);
err = crypto_comp_decompress(compr->cc, in_buf, in_len, out_buf,
(unsigned int *)out_len);
if (compr->decomp_mutex)
mutex_unlock(compr->decomp_mutex);
if (err)
ubifs_err("cannot decompress %d bytes, compressor %s, error %d",
in_len, compr->name, err);
return err;
}
/**
* compr_init - initialize a compressor.
* @compr: compressor description object
*
* This function initializes the requested compressor and returns zero in case
* of success or a negative error code in case of failure.
*/
static int __init compr_init(struct ubifs_compressor *compr)
{
ubifs_compressors[compr->compr_type] = compr;
#ifdef CONFIG_NEEDS_MANUAL_RELOC
ubifs_compressors[compr->compr_type]->name += gd->reloc_off;
ubifs_compressors[compr->compr_type]->capi_name += gd->reloc_off;
ubifs_compressors[compr->compr_type]->decompress += gd->reloc_off;
#endif
if (compr->capi_name) {
compr->cc = crypto_alloc_comp(compr->capi_name, 0, 0);
if (IS_ERR(compr->cc)) {
ubifs_err("cannot initialize compressor %s, error %ld",
compr->name, PTR_ERR(compr->cc));
return PTR_ERR(compr->cc);
}
}
return 0;
}
/**
* ubifs_compressors_init - initialize UBIFS compressors.
*
* This function initializes the compressor which were compiled in. Returns
* zero in case of success and a negative error code in case of failure.
*/
int __init ubifs_compressors_init(void)
{
int err;
err = compr_init(&lzo_compr);
if (err)
return err;
err = compr_init(&zlib_compr);
if (err)
return err;
err = compr_init(&none_compr);
if (err)
return err;
return 0;
}
/*
* ubifsls...
*/
static int filldir(struct ubifs_info *c, const char *name, int namlen,
u64 ino, unsigned int d_type)
{
struct inode *inode;
char filetime[32];
switch (d_type) {
case UBIFS_ITYPE_REG:
printf("\t");
break;
case UBIFS_ITYPE_DIR:
printf("<DIR>\t");
break;
case UBIFS_ITYPE_LNK:
printf("<LNK>\t");
break;
default:
printf("other\t");
break;
}
inode = ubifs_iget(c->vfs_sb, ino);
if (IS_ERR(inode)) {
printf("%s: Error in ubifs_iget(), ino=%lld ret=%p!\n",
__func__, ino, inode);
return -1;
}
ctime_r((time_t *)&inode->i_mtime, filetime);
printf("%9lld %24.24s ", inode->i_size, filetime);
#ifndef __UBOOT__
ubifs_iput(inode);
#endif
printf("%s\n", name);
return 0;
}
static int ubifs_printdir(struct file *file, void *dirent)
{
int err, over = 0;
struct qstr nm;
union ubifs_key key;
struct ubifs_dent_node *dent;
struct inode *dir = file->f_path.dentry->d_inode;
struct ubifs_info *c = dir->i_sb->s_fs_info;
dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos);
if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2)
/*
* The directory was seek'ed to a senseless position or there
* are no more entries.
*/
return 0;
if (file->f_pos == 1) {
/* Find the first entry in TNC and save it */
lowest_dent_key(c, &key, dir->i_ino);
nm.name = NULL;
dent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(dent)) {
err = PTR_ERR(dent);
goto out;
}
file->f_pos = key_hash_flash(c, &dent->key);
file->private_data = dent;
}
dent = file->private_data;
if (!dent) {
/*
* The directory was seek'ed to and is now readdir'ed.
* Find the entry corresponding to @file->f_pos or the
* closest one.
*/
dent_key_init_hash(c, &key, dir->i_ino, file->f_pos);
nm.name = NULL;
dent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(dent)) {
err = PTR_ERR(dent);
goto out;
}
file->f_pos = key_hash_flash(c, &dent->key);
file->private_data = dent;
}
while (1) {
dbg_gen("feed '%s', ino %llu, new f_pos %#x",
dent->name, (unsigned long long)le64_to_cpu(dent->inum),
key_hash_flash(c, &dent->key));
ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum);
nm.len = le16_to_cpu(dent->nlen);
over = filldir(c, (char *)dent->name, nm.len,
le64_to_cpu(dent->inum), dent->type);
if (over)
return 0;
/* Switch to the next entry */
key_read(c, &dent->key, &key);
nm.name = (char *)dent->name;
dent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(dent)) {
err = PTR_ERR(dent);
goto out;
}
kfree(file->private_data);
file->f_pos = key_hash_flash(c, &dent->key);
file->private_data = dent;
cond_resched();
}
out:
if (err != -ENOENT) {
ubifs_err("cannot find next direntry, error %d", err);
return err;
}
kfree(file->private_data);
file->private_data = NULL;
file->f_pos = 2;
return 0;
}
static int ubifs_finddir(struct super_block *sb, char *dirname,
unsigned long root_inum, unsigned long *inum)
{
int err;
struct qstr nm;
union ubifs_key key;
struct ubifs_dent_node *dent;
struct ubifs_info *c;
struct file *file;
struct dentry *dentry;
struct inode *dir;
int ret = 0;
file = kzalloc(sizeof(struct file), 0);
dentry = kzalloc(sizeof(struct dentry), 0);
dir = kzalloc(sizeof(struct inode), 0);
if (!file || !dentry || !dir) {
printf("%s: Error, no memory for malloc!\n", __func__);
err = -ENOMEM;
goto out;
}
dir->i_sb = sb;
file->f_path.dentry = dentry;
file->f_path.dentry->d_parent = dentry;
file->f_path.dentry->d_inode = dir;
file->f_path.dentry->d_inode->i_ino = root_inum;
c = sb->s_fs_info;
dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos);
/* Find the first entry in TNC and save it */
lowest_dent_key(c, &key, dir->i_ino);
nm.name = NULL;
dent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(dent)) {
err = PTR_ERR(dent);
goto out;
}
file->f_pos = key_hash_flash(c, &dent->key);
file->private_data = dent;
while (1) {
dbg_gen("feed '%s', ino %llu, new f_pos %#x",
dent->name, (unsigned long long)le64_to_cpu(dent->inum),
key_hash_flash(c, &dent->key));
ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum);
nm.len = le16_to_cpu(dent->nlen);
if ((strncmp(dirname, (char *)dent->name, nm.len) == 0) &&
(strlen(dirname) == nm.len)) {
*inum = le64_to_cpu(dent->inum);
ret = 1;
goto out_free;
}
/* Switch to the next entry */
key_read(c, &dent->key, &key);
nm.name = (char *)dent->name;
dent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(dent)) {
err = PTR_ERR(dent);
goto out;
}
kfree(file->private_data);
file->f_pos = key_hash_flash(c, &dent->key);
file->private_data = dent;
cond_resched();
}
out:
if (err != -ENOENT)
ubifs_err("cannot find next direntry, error %d", err);
out_free:
if (file->private_data)
kfree(file->private_data);
if (file)
free(file);
if (dentry)
free(dentry);
if (dir)
free(dir);
return ret;
}
static unsigned long ubifs_findfile(struct super_block *sb, char *filename)
{
int ret;
char *next;
char fpath[128];
char symlinkpath[128];
char *name = fpath;
unsigned long root_inum = 1;
unsigned long inum;
int symlink_count = 0; /* Don't allow symlink recursion */
char link_name[64];
strcpy(fpath, filename);
/* Remove all leading slashes */
while (*name == '/')
name++;
/*
* Handle root-direcoty ('/')
*/
inum = root_inum;
if (!name || *name == '\0')
return inum;
for (;;) {
struct inode *inode;
struct ubifs_inode *ui;
/* Extract the actual part from the pathname. */
next = strchr(name, '/');
if (next) {
/* Remove all leading slashes. */
while (*next == '/')
*(next++) = '\0';
}
ret = ubifs_finddir(sb, name, root_inum, &inum);
if (!ret)
return 0;
inode = ubifs_iget(sb, inum);
if (!inode)
return 0;
ui = ubifs_inode(inode);
if ((inode->i_mode & S_IFMT) == S_IFLNK) {
char buf[128];
/* We have some sort of symlink recursion, bail out */
if (symlink_count++ > 8) {
printf("Symlink recursion, aborting\n");
return 0;
}
memcpy(link_name, ui->data, ui->data_len);
link_name[ui->data_len] = '\0';
if (link_name[0] == '/') {
/* Absolute path, redo everything without
* the leading slash */
next = name = link_name + 1;
root_inum = 1;
continue;
}
/* Relative to cur dir */
sprintf(buf, "%s/%s",
link_name, next == NULL ? "" : next);
memcpy(symlinkpath, buf, sizeof(buf));
next = name = symlinkpath;
continue;
}
/*
* Check if directory with this name exists
*/
/* Found the node! */
if (!next || *next == '\0')
return inum;
root_inum = inum;
name = next;
}
return 0;
}
int ubifs_ls(char *filename)
{
struct ubifs_info *c = ubifs_sb->s_fs_info;
struct file *file;
struct dentry *dentry;
struct inode *dir;
void *dirent = NULL;
unsigned long inum;
int ret = 0;
c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY);
inum = ubifs_findfile(ubifs_sb, filename);
if (!inum) {
ret = -1;
goto out;
}
file = kzalloc(sizeof(struct file), 0);
dentry = kzalloc(sizeof(struct dentry), 0);
dir = kzalloc(sizeof(struct inode), 0);
if (!file || !dentry || !dir) {
printf("%s: Error, no memory for malloc!\n", __func__);
ret = -ENOMEM;
goto out_mem;
}
dir->i_sb = ubifs_sb;
file->f_path.dentry = dentry;
file->f_path.dentry->d_parent = dentry;
file->f_path.dentry->d_inode = dir;
file->f_path.dentry->d_inode->i_ino = inum;
file->f_pos = 1;
file->private_data = NULL;
ubifs_printdir(file, dirent);
out_mem:
if (file)
free(file);
if (dentry)
free(dentry);
if (dir)
free(dir);
out:
ubi_close_volume(c->ubi);
return ret;
}
/*
* ubifsload...
*/
/* file.c */
static inline void *kmap(struct page *page)
{
return page->addr;
}
static int read_block(struct inode *inode, void *addr, unsigned int block,
struct ubifs_data_node *dn)
{
struct ubifs_info *c = inode->i_sb->s_fs_info;
int err, len, out_len;
union ubifs_key key;
unsigned int dlen;
data_key_init(c, &key, inode->i_ino, block);
err = ubifs_tnc_lookup(c, &key, dn);
if (err) {
if (err == -ENOENT)
/* Not found, so it must be a hole */
memset(addr, 0, UBIFS_BLOCK_SIZE);
return err;
}
ubifs_assert(le64_to_cpu(dn->ch.sqnum) > ubifs_inode(inode)->creat_sqnum);
len = le32_to_cpu(dn->size);
if (len <= 0 || len > UBIFS_BLOCK_SIZE)
goto dump;
dlen = le32_to_cpu(dn->ch.len) - UBIFS_DATA_NODE_SZ;
out_len = UBIFS_BLOCK_SIZE;
err = ubifs_decompress(&dn->data, dlen, addr, &out_len,
le16_to_cpu(dn->compr_type));
if (err || len != out_len)
goto dump;
/*
* Data length can be less than a full block, even for blocks that are
* not the last in the file (e.g., as a result of making a hole and
* appending data). Ensure that the remainder is zeroed out.
*/
if (len < UBIFS_BLOCK_SIZE)
memset(addr + len, 0, UBIFS_BLOCK_SIZE - len);
return 0;
dump:
ubifs_err("bad data node (block %u, inode %lu)",
block, inode->i_ino);
ubifs_dump_node(c, dn);
return -EINVAL;
}
static int do_readpage(struct ubifs_info *c, struct inode *inode,
struct page *page, int last_block_size)
{
void *addr;
int err = 0, i;
unsigned int block, beyond;
struct ubifs_data_node *dn;
loff_t i_size = inode->i_size;
dbg_gen("ino %lu, pg %lu, i_size %lld",
inode->i_ino, page->index, i_size);
addr = kmap(page);
block = page->index << UBIFS_BLOCKS_PER_PAGE_SHIFT;
beyond = (i_size + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT;
if (block >= beyond) {
/* Reading beyond inode */
memset(addr, 0, PAGE_CACHE_SIZE);
goto out;
}
dn = kmalloc(UBIFS_MAX_DATA_NODE_SZ, GFP_NOFS);
if (!dn)
return -ENOMEM;
i = 0;
while (1) {
int ret;
if (block >= beyond) {
/* Reading beyond inode */
err = -ENOENT;
memset(addr, 0, UBIFS_BLOCK_SIZE);
} else {
/*
* Reading last block? Make sure to not write beyond
* the requested size in the destination buffer.
*/
if (((block + 1) == beyond) || last_block_size) {
void *buff;
int dlen;
/*
* We need to buffer the data locally for the
* last block. This is to not pad the
* destination area to a multiple of
* UBIFS_BLOCK_SIZE.
*/
buff = malloc_cache_aligned(UBIFS_BLOCK_SIZE);
if (!buff) {
printf("%s: Error, malloc fails!\n",
__func__);
err = -ENOMEM;
break;
}
/* Read block-size into temp buffer */
ret = read_block(inode, buff, block, dn);
if (ret) {
err = ret;
if (err != -ENOENT) {
free(buff);
break;
}
}
if (last_block_size)
dlen = last_block_size;
else
dlen = le32_to_cpu(dn->size);
/* Now copy required size back to dest */
memcpy(addr, buff, dlen);
free(buff);
} else {
ret = read_block(inode, addr, block, dn);
if (ret) {
err = ret;
if (err != -ENOENT)
break;
}
}
}
if (++i >= UBIFS_BLOCKS_PER_PAGE)
break;
block += 1;
addr += UBIFS_BLOCK_SIZE;
}
if (err) {
if (err == -ENOENT) {
/* Not found, so it must be a hole */
dbg_gen("hole");
goto out_free;
}
ubifs_err("cannot read page %lu of inode %lu, error %d",
page->index, inode->i_ino, err);
goto error;
}
out_free:
kfree(dn);
out:
return 0;
error:
kfree(dn);
return err;
}
int ubifs_load(char *filename, u32 addr, u32 size)
{
struct ubifs_info *c = ubifs_sb->s_fs_info;
unsigned long inum;
struct inode *inode;
struct page page;
int err = 0;
int i;
int count;
int last_block_size = 0;
c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY);
/* ubifs_findfile will resolve symlinks, so we know that we get
* the real file here */
inum = ubifs_findfile(ubifs_sb, filename);
if (!inum) {
err = -1;
goto out;
}
/*
* Read file inode
*/
inode = ubifs_iget(ubifs_sb, inum);
if (IS_ERR(inode)) {
printf("%s: Error reading inode %ld!\n", __func__, inum);
err = PTR_ERR(inode);
goto out;
}
/*
* If no size was specified or if size bigger than filesize
* set size to filesize
*/
if ((size == 0) || (size > inode->i_size))
size = inode->i_size;
count = (size + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT;
printf("Loading file '%s' to addr 0x%08x with size %d (0x%08x)...\n",
filename, addr, size, size);
page.addr = (void *)addr;
page.index = 0;
page.inode = inode;
for (i = 0; i < count; i++) {
/*
* Make sure to not read beyond the requested size
*/
if (((i + 1) == count) && (size < inode->i_size))
last_block_size = size - (i * PAGE_SIZE);
err = do_readpage(c, inode, &page, last_block_size);
if (err)
break;
page.addr += PAGE_SIZE;
page.index++;
}
if (err)
printf("Error reading file '%s'\n", filename);
else {
setenv_hex("filesize", size);
printf("Done\n");
}
ubifs_iput(inode);
out:
ubi_close_volume(c->ubi);
return err;
}