blob: f8e0dad860b2694b0c738978c9838210a770ccfd [file] [edit]
/*
* Copyright 2016, NICTA
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(NICTA_GPL)
*/
#include <bilbyfs.h>
int ostore_init(struct bilbyfs_info *bi)
{
struct bilbyfs_wbuf *wbuf = &bi->wbuf;
int err;
wbuf->buf = vmalloc(bi->super.leb_size);
if (!wbuf->buf)
return -ENOMEM;
bi->sup_wbuf.buf = vmalloc(bi->super.leb_size);
if (!bi->sup_wbuf.buf) {
vfree(wbuf->buf);
return -ENOMEM;
}
bi->summary = vmalloc(bi->super.leb_size);
if (!bi->summary) {
vfree(wbuf->buf);
vfree(bi->sup_wbuf.buf);
return -ENOMEM;
}
bi->summary->nb_sum_entry = 0;
wbuf->size = bi->super.leb_size;
bi->sup_wbuf.size = bi->super.leb_size;
err = allocpool_init(&bi->node_pool);
if (!err) {
err = idx_init(bi);
if (!err) {
err = fsm_init(bi);
if (!err) {
err = wbuf_init(bi);
if (!err) {
err = gim_init(bi);
if (!err) {
err = gc_init(bi);
if (!err)
return 0;
gim_clean(bi);
}
wbuf_clean(bi);
}
fsm_clean(bi);
}
idx_clean(bi);
}
allocpool_destroy(&bi->node_pool);
}
vfree(wbuf->buf);
vfree(bi->sup_wbuf.buf);
vfree(bi->summary);
return err;
}
void ostore_clean(struct bilbyfs_info *bi)
{
struct bilbyfs_wbuf *wbuf = &bi->wbuf;
vfree(wbuf->buf);
memset(wbuf, 0, sizeof(*wbuf));
vfree(bi->sup_wbuf.buf);
memset(&bi->sup_wbuf, 0, sizeof(*wbuf));
vfree(bi->summary);
idx_clean(bi);
fsm_clean(bi);
wbuf_clean(bi);
gim_clean(bi);
gc_clean(bi);
allocpool_destroy(&bi->node_pool);
}
int ostore_get_obj_size(struct bilbyfs_info *bi, obj_id id)
{
struct obj_addr addr;
int err;
err = idx_get_obj_addr(bi, id, &addr);
if (err)
return err;
return addr.len;
}
int ostore_read_obj(struct bilbyfs_info *bi, obj_id id, void *buf, u32 len)
{
struct obj_addr addr;
int err;
err = idx_get_obj_addr(bi, id, &addr);
if (err)
return err;
if (len < addr.len) {
bilbyfs_err("ostore_read_obj: buf too small\n");
return -EINVAL; /* please try again with a larger buffer */
}
if (addr.lnum == fsm_get_lnum(bi)) { /* Read from write-buffer */
bilbyfs_debug("read_obj(oid=%llx) from buffer\n", id);
memcpy(buf, bi->wbuf.buf + addr.offs, addr.len);
return 0;
}
err = wbuf_read_obj(bi, buf, &addr);
{
struct obj_ch *obj = buf;
bilbyfs_debug("read_obj({%s,oid=%llx}, {lnum=%d,offs=%d,len=%d}) = %d\n",
(obj->type == BILBYFS_INODE_OBJ ? "inode" :
(obj->type == BILBYFS_DENTARR_OBJ ? "dentarr":
(obj->type == BILBYFS_DATA_OBJ ? "data" :
(obj->type == BILBYFS_DEL_OBJ ? "del" : "unknown")))),
id,
addr.lnum, addr.offs, addr.len, err);
bilbyfs_assert(get_obj_id(obj) == id);
(void)obj;
}
return err;
}
obj_id ostore_next_obj_id(struct bilbyfs_info *bi, obj_id id)
{
obj_id nxt = idx_next_obj_id(bi, id);
if (inum_from_id(id) == inum_from_id(nxt))
return nxt;
return NIL_ID;
}
/**
* proc_obj - process an object
*
* @bi: global fs info
* @type: type of the object
* @id: ID of the object
* @addr: address of the object on disk
*
* This function will update:
* - Index: if the object is newer, update the index with the object
* - FSM: older version (if exist) of an object is marked as dirty
*
* Returns negative error code if unsuccessful or zero otherwise
*/
static int proc_obj(struct bilbyfs_info *bi, u8 type, obj_id id, struct obj_addr *addr)
{
obj_id curr_id;
struct obj_addr old_addr;
struct idx_node *idx_node;
struct gim_node *gnode;
int err = 0;
switch (type) {
case BILBYFS_PAD_OBJ:
fsm_mark_dirty(bi, addr);
break;
case BILBYFS_INODE_OBJ:
case BILBYFS_DENTARR_OBJ:
case BILBYFS_DATA_OBJ:
/* Valid object */
idx_node = idx_get_or_create_obj_addr_node(bi, id);
if (!idx_node->addr.sqnum) { /* If we just created this node */
idx_node->addr = *addr;
} else {
if (idx_node->addr.sqnum < addr->sqnum) {
/* idx_set_obj_addr(bi, id, addr, NULL); */
idx_node->addr = *addr;
gim_mark_garbage(bi, id, &idx_node->addr, NULL);
fsm_mark_dirty(bi, &idx_node->addr);
} else {
gim_mark_garbage(bi, id, addr, NULL);
fsm_mark_dirty(bi, addr);
}
}
break;
case BILBYFS_DEL_OBJ:
/* Delete object */
switch (type_from_id(id)) {
case BILBYFS_DENTARR_OBJ:
/* Deletion object that cover single object */
err = idx_get_obj_addr(bi, id, &old_addr);
if (!err) { /* Obj found in index */
if (old_addr.sqnum < addr->sqnum) {
gnode = idx_del_obj_addr(bi, id);
gim_mark_garbage(bi, id, &old_addr, gnode);
fsm_mark_dirty(bi, &old_addr);
}
}
break;
case BILBYFS_INODE_OBJ:
case BILBYFS_DATA_OBJ:
/* Deletion object that cover a range of objects */
for (curr_id = id; curr_id != NIL_ID; curr_id = idx_next_obj_id(bi, curr_id)) {
err = idx_get_obj_addr(bi, curr_id, &old_addr);
if (!err) {
if (old_addr.sqnum < addr->sqnum) {
gnode = idx_del_obj_addr(bi, curr_id);
gim_mark_garbage(bi, curr_id, &old_addr, gnode);
fsm_mark_dirty(bi, &old_addr);
}
}
}
break;
default:
bilbyfs_assert(0);
}
/* Mark the deletion object itself as dirty */
gim_mark_garbage(bi, id, addr, NULL);
fsm_mark_dirty(bi, addr);
break;
default:
bilbyfs_assert(0);
}
return 0;
}
static u8 get_obj_trans(int count, int i)
{
if (count == 1)
return BILBYFS_TRANS_ATOM;
else if (i == 0)
return BILBYFS_TRANS_ST;
else if (i == count - 1)
return BILBYFS_TRANS_END;
return BILBYFS_TRANS_IN;
}
#define NOBJ BILBYFS_MAX_OBJ_PER_TRANS
int obj_has_id(struct obj_ch *obj)
{
return (obj->type == BILBYFS_INODE_OBJ ||
obj->type == BILBYFS_DENTARR_OBJ ||
obj->type == BILBYFS_DATA_OBJ ||
obj->type == BILBYFS_DEL_OBJ);
}
static inline int debug_print_objs(struct bilbyfs_info *bi, u32 lnum)
{
struct obj_ch **olist, **olist2;
struct obj_addr addr;
int err;
int i;
struct obj_ch *obj;
struct bilbyfs_rbuf rbuf;
int nbo, nbo2;
bilbyfs_debug("ostore_scan_obj() : alloc = %lu\n", (unsigned long)(sizeof(*olist) * NOBJ));
olist = kmalloc(sizeof(*olist) * NOBJ);
if (!olist)
return -ENOMEM;
rbuf.buf = bi->wbuf.buf;
rbuf.size = bi->wbuf.size;
rbuf.offs = 0;
err = ostore_scan_obj(bi, &rbuf, lnum, (void **)olist, NOBJ);
bilbyfs_debug("ostore_scan_obj() = %d\n", err);
if (err < 0) {
kfree(olist);
return err;
}
addr.lnum = lnum;
addr.offs = 0;
addr.len = 0;
nbo = err;
for (i = 0; i < nbo; i++) {
obj = olist[i];
addr.len = obj->len;
bilbyfs_debug("scan_obj({%s,oid=%llx}, {lnum=%d,offs=%d,len=%d})\n",
(obj->type == BILBYFS_INODE_OBJ ? "inode" :
(obj->type == BILBYFS_DENTARR_OBJ ? "dentarr":
(obj->type == BILBYFS_DATA_OBJ ? "data" :
(obj->type == BILBYFS_DEL_OBJ ? "del" :
(obj->type == BILBYFS_SUP_OBJ ? "super" :
(obj->type == BILBYFS_PAD_OBJ ? "pad" : "unknown")))))),
(!obj_has_id(obj) ? 0 : get_obj_id(obj)),
addr.lnum, addr.offs, addr.len);
addr.offs += addr.len;
}
bilbyfs_debug("ostore_scan_obj() = %d\n", err);
olist2 = kmalloc(sizeof(*olist) * NOBJ);
if (!olist2)
return -ENOMEM;
err = ostore_scan_leb_obj(bi, &bi->rbuf, lnum, (void **)olist2, NOBJ);
if (err < 0) {
kfree(olist);
kfree(olist2);
return err;
}
addr.lnum = lnum;
addr.offs = 0;
addr.len = 0;
nbo2 = err;
for (i = 0; i < nbo2; i++) {
obj = olist2[i];
addr.len = obj->len;
bilbyfs_debug("scan_obj({%s,oid=%llx}, {lnum=%d,offs=%d,len=%d})\n",
(obj->type == BILBYFS_INODE_OBJ ? "inode" :
(obj->type == BILBYFS_DENTARR_OBJ ? "dentarr":
(obj->type == BILBYFS_DATA_OBJ ? "data" :
(obj->type == BILBYFS_DEL_OBJ ? "del" :
(obj->type == BILBYFS_SUP_OBJ ? "super" :
(obj->type == BILBYFS_PAD_OBJ ? "pad" : "unknown")))))),
(!obj_has_id(obj) ? 0 : get_obj_id(obj)),
addr.lnum, addr.offs, addr.len);
err = memcmp(obj, olist[i], obj->len);
bilbyfs_assert(!err);
addr.offs += addr.len;
}
if (nbo != nbo2) {
print_hex_dump(KERN_ERR, "wbuf: ", DUMP_PREFIX_ADDRESS, 32, 8, rbuf.buf, rbuf.size, true);
print_hex_dump(KERN_ERR, "rbuf: ", DUMP_PREFIX_ADDRESS, 32, 8, bi->rbuf.buf, bi->rbuf.size, true);
}
kfree(olist2);
kfree(olist);
return 0;
}
int obj_sum_size_with_extra(struct obj_sum *sum, u32 nb_extra_entries)
{
return ALIGN(obj_sum_size(sum) + nb_extra_entries * BILBYFS_SUM_ENTRY_SZ, BILBYFS_OBJ_PADDING);
}
int ostore_write_summary(struct bilbyfs_info *bi, u32 *padding_sz)
{
struct bilbyfs_wbuf *wbuf = &bi->wbuf;
struct obj_ch *ch;
int err;
u32 len;
u32 pad_sz;
len = obj_sum_size(bi->summary);
bilbyfs_assert(wbuf->avail >= len);
/* wbuf summary aware padding */
pad_sz = wbuf->avail - len;
if (pad_sz < BILBYFS_CH_SZ) {
memset(wbuf->buf + wbuf->used, BILBYFS_PAD_BYTE, pad_sz);
} else {
ch = wbuf->buf + wbuf->used;
pack_obj_pad(ch, pad_sz);
pack_obj_header(ch, next_sqnum(bi), BILBYFS_TRANS_ATOM);
}
if (padding_sz)
*padding_sz = pad_sz;
wbuf->avail -= pad_sz;
wbuf->used += pad_sz;
pack_obj_sum(bi->summary, wbuf->used);
pack_obj_header(&bi->summary->ch, next_sqnum(bi), BILBYFS_TRANS_ATOM);
err = wbuf_write_obj(bi, bi->summary, len, wbuf);
bilbyfs_debug("ostore_write_summary(offset=%u) = %d\n", wbuf->used, err);
if (!err)
bi->summary->nb_sum_entry = 0;
bilbyfs_assert(wbuf->avail == 0);
return err;
}
void sum_obj(struct bilbyfs_info *bi, struct obj_ch *obj, struct obj_addr *addr)
{
struct obj_sum *sum = bi->summary;
struct obj_sum_entry *entry;
obj_id oid, eoid;
u8 oid_type;
u32 nb_sum_entry = le32_to_cpu(sum->nb_sum_entry);
bool is_del;
int i;
oid = get_obj_id(obj);
oid_type = type_from_id(oid);
is_del = obj->type == BILBYFS_DEL_OBJ;
/* If we have a deletion object, we can only replace younger deletion
* objects that have the exact same oid in case of dentarr deletion or
* objects that have a higher oid for inode and data deletion objects.
* Summary of non-deletion objects can only replace objects with
* the exact same oid.
*/
if (is_del) {
for (i = 0; i < nb_sum_entry; i++) {
entry = &sum->entries[i];
eoid = le64_to_cpu(entry->id);
if (oid_type == BILBYFS_DENTARR_OBJ) {
if (eoid == oid) {
if (le64_to_cpu(entry->sqnum) > addr->sqnum) {
return;
}
break;
}
} else if (inum_from_id(eoid) == inum_from_id(oid) && eoid > oid) {
if (le64_to_cpu(entry->sqnum) < addr->sqnum)
break;
}
}
} else {
for (i = 0; i < nb_sum_entry; i++) {
entry = &sum->entries[i];
/* We cannot replace a del object with an new object */
if (obj_sum_entry_is_del(entry) && oid_type != BILBYFS_DENTARR_OBJ)
continue;
eoid = le64_to_cpu(entry->id);
if (eoid == oid) {
if (le64_to_cpu(entry->sqnum) > addr->sqnum)
return;
break;
}
}
}
/* Not that the whole if/else above is an optimisation to
* reuse a sum_entry, we could safely replace it with
* i = nb_sum_entry;
*/
entry = &sum->entries[i];
entry->id = oid;
entry->len = cpu_to_le32(addr->len);
entry->sqnum = cpu_to_le64(addr->sqnum);
entry->del_flag_and_offs = cpu_to_le32(addr->offs | (is_del ? BILBYFS_SUM_ENTRY_DEL_FLAG_MASK : 0));
if (i == nb_sum_entry) {
sum->nb_sum_entry = cpu_to_le32(nb_sum_entry + 1);
entry->count = 0;
} else
entry->count = cpu_to_le32(le32_to_cpu(entry->count) + 1);
}
int ostore_sync(struct bilbyfs_info *bi, int force_summary)
{
struct bilbyfs_wbuf *wbuf = &bi->wbuf;
int err = 0;
u32 padding_size;
struct obj_addr curr_addr;
u32 sum_sz;
bilbyfs_debug("ostore_sync()\n");
if (bi->no_summary)
force_summary = 0;
if (wbuf->sync_offs == wbuf->used && !force_summary)
return 0;
if (!wbuf->avail)
return 0;
bilbyfs_assert(!!fsm_get_lnum(bi));
curr_addr.lnum = fsm_get_lnum(bi);
curr_addr.offs = wbuf->used;
/* Pad buffer to min_io_size */
/*
* We want to pad with a obj_pad only if that leaves enough space to
* write at least one more object (remember we also need to count space
* for the summary).
*/
sum_sz = obj_sum_size_with_extra(bi->summary, 1);
if (!bi->no_summary && (force_summary || ALIGN(wbuf->used, bi->super.min_io_size) + sum_sz + BILBYFS_MIN_OBJ_SZ > bi->super.leb_size)) {
err = ostore_write_summary(bi, &padding_size);
if (err)
return err;
} else
wbuf_prepare_commit(bi, &padding_size, wbuf);
if (padding_size > 0) {
curr_addr.len = padding_size;
fsm_mark_dirty(bi, &curr_addr);
}
err = wbuf_commit(bi, fsm_get_lnum(bi), wbuf);
/* bilbyfs_debug("debug_print_obj(%u) = %d\n", fsm_get_lnum(bi), debug_print_objs(bi, fsm_get_lnum(bi))); */
return err;
}
u32 obj_list_serial_size(struct obj_ch **obj_list, int count)
{
u32 sz = 0;
int i;
for (i = 0; i < count; i++) {
sz += obj_list[i]->len;
}
return sz;
}
int ostore_write_obj_list(struct bilbyfs_info *bi, void **obj_list, int count, int osw_flag)
{
int i;
int err = 0;
struct obj_ch *obj;
struct obj_addr curr_addr;
struct bilbyfs_wbuf *wbuf = &bi->wbuf;
u32 serial_sz;
u32 offs;
if (!fsm_get_lnum(bi) || !wbuf->avail) {
err = fsm_alloc_eb(bi, osw_flag);
if (err)
return err;
wbuf_start(bi, wbuf);
}
bilbyfs_debug("wbuf->avail = %u, obj_sum_size = %u\n", wbuf->avail , obj_sum_size(bi->summary));
if (!bi->no_summary)
bilbyfs_assert(wbuf->avail >= obj_sum_size(bi->summary));
if (osw_flag & OSW_SYNC) {
err = ostore_sync(bi, false);
if (err)
return err;
}
if (!bi->no_summary)
bilbyfs_assert(wbuf->avail >= obj_sum_size(bi->summary));
bilbyfs_assert(count > 0);
for (i = 0; i < count; i++) {
obj = obj_list[i];
pack_obj_header(obj, next_sqnum(bi), get_obj_trans(count, i));
}
serial_sz = obj_list_serial_size((struct obj_ch **)obj_list, count);
bilbyfs_assert(serial_sz <= bi->super.leb_size);
if (wbuf->avail < serial_sz + (bi->no_summary ? 0 : obj_sum_size_with_extra(bi->summary, count))) {
err = ostore_sync(bi, true);
if (err)
return err;
err = fsm_alloc_eb(bi, osw_flag);
if (err)
return err;
wbuf_start(bi, wbuf);
}
offs = wbuf->used;
/* Write objects into wbuf */
for (i = 0; i < count; i++) {
obj = obj_list[i];
err = wbuf_write_obj(bi, obj, le32_to_cpu(obj->len), wbuf);
if (err)
return err;
}
if (osw_flag & OSW_SYNC) {
err = ostore_sync(bi, false);
if (err)
return err;
}
/* Preallocate new index and gim entries */
err = allocpool_alloc(&bi->node_pool, count * 2, NODE_SIZE);
if (err)
return err;
/* Update index & fsm */
curr_addr.lnum = fsm_get_lnum(bi);
curr_addr.offs = offs;
curr_addr.len = 0;
bilbyfs_debug(".\n");
for (i = 0; i < count; i++) {
obj = obj_list[i];
curr_addr.offs += curr_addr.len;
curr_addr.len = le32_to_cpu(obj->len);
curr_addr.sqnum = le64_to_cpu(obj->sqnum);
err = proc_obj(bi, obj->type, get_obj_id(obj), &curr_addr);
bilbyfs_debug("write_obj({%s,oid=%llx}, {lnum=%d,offs=%d,len=%d,sqnum=%llu}) = %d\n",
(obj->type == BILBYFS_INODE_OBJ ? "inode" :
(obj->type == BILBYFS_DENTARR_OBJ ? "dentarr":
(obj->type == BILBYFS_DATA_OBJ ? "data" :
(obj->type == BILBYFS_DEL_OBJ ? "del" : "unknown")))),
get_obj_id(obj),
curr_addr.lnum, curr_addr.offs, curr_addr.len, curr_addr.sqnum, err);
if (err)
bilbyfs_assert(false);
if (!bi->no_summary)
sum_obj(bi, obj, &curr_addr);
}
allocpool_empty(&bi->node_pool);
return 0;
}
int ostore_erase(struct bilbyfs_info *bi, int lnum)
{
int err;
err = wbuf_erase(bi, lnum);
if (err)
return err;
fsm_mark_erased(bi, lnum);
return 0;
}
int ostore_scan_obj(struct bilbyfs_info *bi, struct bilbyfs_rbuf *rbuf, int lnum,
void **list, int max_count)
{
int err;
struct obj_addr addr;
struct obj_ch *obj;
int count = 0;
addr.lnum = lnum;
addr.offs = 0;
addr.len = 0;
obj = wbuf_next_obj_addr(bi, &addr, rbuf);
while (!IS_ERR(obj)) {
if (count >= max_count)
return -EOVERFLOW;
list[count] = obj;
count++;
obj = wbuf_next_obj_addr(bi, &addr, rbuf);
}
err = PTR_ERR(obj);
if (err == -ENOENT)
if (count > 0)
return count;
return err;
}
int ostore_scan_leb_obj(struct bilbyfs_info *bi, struct bilbyfs_rbuf *rbuf, int lnum,
void **list, int max_count)
{
int err;
rbuf->offs = 0;
err = wbuf_read_leb(bi, lnum, rbuf);
if (err)
return err;
return ostore_scan_obj(bi, rbuf, lnum, list, max_count);
}
unsigned long long ostore_get_free_space(struct bilbyfs_info *bi)
{
return fsm_get_free_space(bi);
}
unsigned long long ostore_get_available_space(struct bilbyfs_info *bi)
{
return fsm_get_available_space(bi);
}
static struct obj_super *next_super_addr(struct bilbyfs_info *bi,
struct obj_addr *addr,
struct bilbyfs_rbuf *rbuf)
{
struct obj_ch *obj;
do {
obj = wbuf_next_obj_addr(bi, addr, rbuf);
if (IS_ERR(obj))
return (struct obj_super *)obj;
} while (obj->type != BILBYFS_SUP_OBJ);
return (struct obj_super *)obj;
}
/* SuperBlock objects are sequentially stored in BILBYFS_SUP_LNUM
* When BILBYFS_SUP_LNUM is full we need to atomically erase the erase-block
* and store a new object in it. To this end we use ubi_leb_change.
*/
static int mount_read_super(struct bilbyfs_info *bi)
{
struct bilbyfs_rbuf *rbuf = &bi->rbuf;
struct obj_super *super, *perr;
struct obj_addr addr;
int err;
err = wbuf_read_leb_fast(bi, BILBYFS_SUP_LNUM, rbuf);
if (err)
return err;
addr.lnum = BILBYFS_SUP_LNUM;
addr.offs = 0;
addr.len = 0;
perr = next_super_addr(bi, &addr, rbuf);
super = perr;
while (!IS_ERR(perr)) {
super = perr;
perr = next_super_addr(bi, &addr, rbuf);
}
if (IS_ERR(super))
return PTR_ERR(super);
if (super->ch.type != BILBYFS_SUP_OBJ ||
le32_to_cpu(super->ch.len) != BILBYFS_SUPER_SZ) {
bilbyfs_err("Invalid Super object!\n");
return -EINVAL;
}
unpack_obj_super(&bi->super, super);
bi->super.min_io_size = bi->di.min_io_size;
/* FIXME: Ensure that max_io_size is only used for write accesses */
bi->super.max_io_size = bi->di.max_write_size;
/* Go to the very last object and get the offs */
while (!IS_ERR(wbuf_next_obj_addr(bi, &addr, rbuf)));
bi->super_offs = addr.offs + addr.len;
if (bi->next_sqnum < bi->super.sqnum) /* FIXME sanity check */
bi->next_sqnum = bi->super.sqnum + 1;
if (bi->next_inum < bi->super.last_inum) /* FIXME sanity check */
bi->next_inum = bi->super.last_inum + 1;
return 0;
}
int ostore_write_super(struct bilbyfs_info *bi)
{
struct bilbyfs_wbuf *wbuf = &bi->sup_wbuf;
struct obj_super sup;
int size_trans;
int err;
bilbyfs_debug("ostore_write_super(lnum = %d, offs = %d) = ?\n",
BILBYFS_SUP_LNUM, bi->super_offs);
bi->super.log_lnum = fsm_get_lnum(bi);
bi->super.log_offs = bi->wbuf.sync_offs;
bi->super.last_inum = bi->next_inum - 1;
wbuf_start(bi, wbuf);
wbuf->used = bi->super_offs;
wbuf->sync_offs = bi->super_offs;
memset(&sup, 0, sizeof(sup));
pack_obj_super(&sup, &bi->super);
pack_obj_header(&sup, next_sqnum(bi), BILBYFS_TRANS_ATOM);
err = wbuf_write_obj(bi, &sup, BILBYFS_SUPER_SZ, wbuf);
if (!err) {
size_trans = wbuf_prepare_commit(bi, NULL, wbuf);
err = wbuf_commit(bi, BILBYFS_SUP_LNUM, wbuf);
if (!err) /* Obtain the next super offset */
bi->super_offs = wbuf->used;
} else {
wbuf_start(bi, wbuf);
err = wbuf_write_obj(bi, &sup, BILBYFS_SUPER_SZ, wbuf);
if (!err) {
size_trans = wbuf_prepare_commit(bi, NULL, wbuf);
err = wbuf_atom_leb_commit(bi, BILBYFS_SUP_LNUM, wbuf);
if (!err) /* Obtain the next super offset */
bi->super_offs = wbuf->used;
}
}
return err;
}
static int mount_check_super(struct bilbyfs_info *bi)
{
struct bilbyfs_super *sup = &bi->super;
if (sup->leb_size != bi->vi.usable_leb_size) {
bilbyfs_err("super.leb_size (%d) is not compatible with the"
"vi.usable_leb_size (%d) of the flash\n",
sup->leb_size, bi->vi.usable_leb_size);
return -EINVAL;
}
if (sup->min_io_size > sup->leb_size ||
sup->max_io_size > sup->leb_size ||
sup->min_io_size > sup->max_io_size ||
(sup->max_io_size % sup->min_io_size) != 0 ||
(sup->leb_size % sup->min_io_size) != 0 ||
(sup->leb_size % sup->max_io_size) != 0) {
bilbyfs_err("Invalid leb_size/min_io_size/max_io_size "
"combinaison: "
"(leb_size = %d, max_io_sz = %d, min_io_sz = %d)",
sup->leb_size, sup->max_io_size, sup->min_io_size);
return -EINVAL;
}
if (sup->leb_cnt < sup->nb_reserved_gc + sup->nb_reserved_del + BILBYFS_MIN_USABLE_LOG_SZ) {
bilbyfs_err("Number of erasble-blocks too small for nb_(eb|gc|del) and min_usable_log_sz "
"(leb_cnt = %d, nb_gc = %d, nb_del = %d, min_usable_log_sz = %d)\n",
sup->leb_cnt, sup->nb_reserved_gc, sup->nb_reserved_del, BILBYFS_MIN_USABLE_LOG_SZ);
return -EINVAL;
}
return 0;
}
/**
* A list list to store deletion objects and their addresses at mount time
*/
struct list_obj_del {
u8 type;
obj_id id;
struct obj_addr addr;
struct list_obj_del *next;
};
static int add_to_del_list(struct list_obj_del **del_list, obj_id id, struct obj_addr *addr)
{
struct list_obj_del *node;
node = kmalloc(sizeof(*node));
if (!node)
return -ENOMEM;
node->id = id;
memcpy(&node->addr, addr, sizeof(*addr));
node->next = *del_list;
*del_list = node;
return 0;
}
static void clean_del_list(struct list_obj_del *del_list)
{
struct list_obj_del *curr, *next;
curr = del_list;
while (curr != NULL) {
next = curr->next;
kfree(curr);
curr = next;
}
}
/* check_trans_pos: Checks whether the transaction attribute @pos is
* valid.
* @trans_nb_obj: current number of elements in the transaction
* @pos: transaction attribute of last element read
*
* This function returns %-EINVAL if the attribute is invalid, it
* returns 1 if it's the last element of the transaction, 0 if more
* objects are expected.
*/
static int check_trans_pos(int trans_nb_obj, u8 pos)
{
switch (pos) {
case BILBYFS_TRANS_ATOM:
if (trans_nb_obj)
return -EINVAL;
return 1;
case BILBYFS_TRANS_ST:
if (trans_nb_obj)
return -EINVAL;
return 0;
case BILBYFS_TRANS_IN:
if (!trans_nb_obj)
return -EINVAL;
return 0;
case BILBYFS_TRANS_END:
if (!trans_nb_obj)
return -EINVAL;
return 1;
default:
bilbyfs_err("Unknown trans pos value %x", pos);
return -EINVAL;
}
}
/* mount_scan_rbuf: Scan every object stored in a read-buffer
* @bi: global fs info
* @addr: address to start with
* @rbuf: read-buffer to scan
* @del_list: a list to store the deletion objects
*
* This function returns a negative error-code if unsuccessful.
*/
static int mount_scan_rbuf(struct bilbyfs_info *bi, struct obj_addr *addr,
struct bilbyfs_rbuf *rbuf, struct list_obj_del **del_list)
{
struct obj_addr *addrs;
struct obj_ch *obj;
int trans_nb_obj = 0;
int last;
int err;
int i;
bilbyfs_assert(addr->len == 0);
addrs = bi->addrs;
if (!addrs)
return -ENOMEM;
for (obj = wbuf_next_obj_addr(bi, addr, rbuf);
!IS_ERR(obj);
obj = wbuf_next_obj_addr(bi, addr, rbuf)) {
bilbyfs_debug("mount_scan_rbuf() loop obj->type = %d nb_obj_in_trans=%d\n", obj->type, trans_nb_obj);
last = check_trans_pos(trans_nb_obj, obj->trans);
/* Invalid transaction value */
if (last < 0) {
bilbyfs_err("Invalid trans pos value 0x%x, this may just"
"be because of a power-failure.", obj->trans);
trans_nb_obj = 0;
continue;
}
/* We enqueue all the objects of a transaction */
memcpy(&addrs[trans_nb_obj], addr, sizeof(*addr));
trans_nb_obj++;
if (!last) {
if (trans_nb_obj == BILBYFS_MAX_OBJ_PER_TRANS) {
bilbyfs_err("Invalid transaction with too many objects (%d"
"objects, limit=%d)", trans_nb_obj,
BILBYFS_MAX_OBJ_PER_TRANS);
trans_nb_obj = 0;
}
continue;
}
/* This was the last object of the transaction,
* will process all the objects.
* Note: deletion objects are saved in a list and will be processed later.
*/
err = allocpool_alloc(&bi->node_pool, trans_nb_obj * 2, NODE_SIZE);
if (err)
return err;
for (i = 0; i < trans_nb_obj; i++) {
obj = rbuf->buf + addrs[i].offs;
if (obj->type == BILBYFS_DEL_OBJ)
err = add_to_del_list(del_list, get_obj_id(obj), &addrs[i]);
else if (obj->type == BILBYFS_PAD_OBJ)
err = proc_obj(bi, obj->type, 0, &addrs[i]);
else if (obj->type != BILBYFS_SUM_OBJ)
err = proc_obj(bi, obj->type, get_obj_id(obj), &addrs[i]);
if (err) {
allocpool_empty(&bi->node_pool);
return err;
}
}
trans_nb_obj = 0;
allocpool_empty(&bi->node_pool);
}
err = PTR_ERR(obj);
return err;
}
int mount_proc_sum(struct bilbyfs_info *bi, u32 lnum, struct obj_sum *sum, u32 maxsz, struct list_obj_del **del_list)
{
int i;
int err;
struct obj_addr addr;
struct obj_sum_entry *entry;
obj_id id;
u32 tot_used = 0;
addr.lnum = lnum;
err = check_obj_header(&sum->ch, maxsz);
if (err)
return err;
if (sum->ch.type != BILBYFS_SUM_OBJ) {
bilbyfs_debug("Summary object expected, found %d\n", sum->ch.type);
return -EINVAL;
}
for (i = 0; i < le32_to_cpu(sum->nb_sum_entry); i++) {
entry = &sum->entries[i];
addr.offs = obj_sum_entry_offs(entry);
addr.len = le32_to_cpu(entry->len);
tot_used += addr.len;
addr.sqnum = le64_to_cpu(entry->sqnum);
id = le64_to_cpu(entry->id);
// bilbyfs_debug("sum_entry{.offs = %u, .len = %u, .sqnum = %llu, .oid = %llu, .is_del=%u}\n", obj_sum_entry_offs(entry), le32_to_cpu(entry->len), le64_to_cpu(entry->sqnum), le64_to_cpu(entry->id), obj_sum_entry_is_del(entry));
if (obj_sum_entry_is_del(entry)) {
err = add_to_del_list(del_list, id, &addr);
if (err)
return err;
} else {
err = proc_obj(bi, type_from_id(id), id, &addr);
if (err)
return err;
}
gim_mark_garbage_cnt(bi, id, &addr, (u32) cpu_to_le16(entry->count), NULL);
}
addr.lnum = lnum;
addr.len = bi->super.leb_size - tot_used;
fsm_mark_dirty(bi, &addr);
return 0;
}
static inline int mount_init_log(struct bilbyfs_info *bi, struct list_obj_del **del_list)
{
struct bilbyfs_rbuf rbuf;
struct obj_addr addr;
int err;
rbuf.buf = bi->wbuf.buf;
rbuf.size = bi->wbuf.size;
rbuf.offs = 0;
bi->fsm_lnum = bi->super.log_lnum;
fsm_mark_used(bi, bi->fsm_lnum);
err = wbuf_read_leb(bi, bi->super.log_lnum, &rbuf);
if (err)
return err;
addr.lnum = bi->fsm_lnum;
addr.offs = 0;
addr.len = 0;
err = mount_scan_rbuf(bi, &addr, &rbuf, del_list);
if (err == -ENOENT) {
bi->wbuf.sync_offs = addr.offs + addr.len;
bi->wbuf.used = addr.offs + addr.len;
bi->wbuf.avail = bi->super.leb_size - bi->wbuf.used;
} else if (err) {
clean_del_list(*del_list);
return err;
} else { /* mount_scan_rbuf didn't fail, erase-block is full of valid data */
bi->wbuf.sync_offs = bi->super.leb_size;
bi->wbuf.used = bi->super.leb_size;
bi->wbuf.avail = 0;
}
bi->wbuf.buf = rbuf.buf;
if (bi->wbuf.sync_offs != bi->super.log_offs) {
bilbyfs_err("super.log_offs is different from the content of the erase-block %d, %d != %d\n", bi->super.log_lnum, bi->wbuf.sync_offs, bi->super.log_offs);
}
return 0;
}
static int mount_scan(struct bilbyfs_info *bi)
{
struct obj_addr addr;
struct list_obj_del *del_list = NULL;
struct list_obj_del *curr;
int err = 0;
u32 sum_offs;
/* struct timeval st, stp; */
bilbyfs_debug("mount_scan(): Scanning each LEB\n");
/*
if (bi->super.log_lnum > 0) {
err = mount_init_log(bi, &del_list);
if (err)
return err;
}
*/
bilbyfs_err("BilbyFs mount start\n");
bi->nb_pages = 0;
bi->nb_extra_pages = 0;
bi->node_pool.tot_nodes_needed = 0;
bi->gim_allocated_for_nothing = 0;
for (addr.lnum = BILBYFS_LOG_FST_LNUM;
addr.lnum < bi->super.leb_cnt;
addr.lnum++) {
if (!ubi_is_mapped(bi->ubi, addr.lnum) /* || addr.lnum == bi->super.log_lnum */)
continue;
fsm_mark_used(bi, addr.lnum);
if (!bi->no_summary) {
/*
do_gettimeofday(&st);
*/
err = wbuf_read_sum(bi, addr.lnum, &bi->rbuf, &sum_offs);
/*
do_gettimeofday(&stp);
pr_err("timed wbuf_read_sum() : %ld sec %ld usec\n", stp.tv_sec - st.tv_sec, stp. tv_usec - st.tv_usec);
*/
if (!err) {
/* do_gettimeofday(&st); */
err = mount_proc_sum(bi, addr.lnum, (struct obj_sum *)(bi->rbuf.buf + sum_offs),
bi->super.leb_size - sum_offs, &del_list);
/*
do_gettimeofday(&stp);
pr_err("timed mount_proc_sum() : %ld sec %ld usec\n", stp.tv_sec - st.tv_sec, stp. tv_usec - st.tv_usec);
*/
if (err)
return err;
continue;
}
bilbyfs_err("Failure to use the summary, fall back to scanning the erase-block (lnum=%d), err = %d\n", addr.lnum, err);
if (err != -ENOENT)
return err;
}
err = wbuf_read_leb(bi, addr.lnum, &bi->rbuf);
if (err) {
clean_del_list(del_list);
return err;
}
addr.offs = 0;
addr.len = 0;
err = mount_scan_rbuf(bi, &addr, &bi->rbuf, &del_list);
if (err == -ENOENT) {
addr.offs += addr.len;
addr.len = bi->super.leb_size - addr.offs;
fsm_mark_dirty(bi, &addr);
} else if (err) {
clean_del_list(del_list);
return err;
}
}
bilbyfs_debug("mount_scan(): Processing deletion objects\n");
/* do_gettimeofday(&st); */
for (curr = del_list; curr; curr = curr->next) {
err = proc_obj(bi, BILBYFS_DEL_OBJ, curr->id, &curr->addr);
if (err) {
clean_del_list(del_list);
return err;
}
}
clean_del_list(del_list);
/*
do_gettimeofday(&stp);
pr_err("timed del_obj_proc() : %ld.%ld\n", stp.tv_sec - st.tv_sec, stp. tv_usec - st.tv_usec);
*/
bilbyfs_err("BilbyFs mount finished pages = %d, extra_pages = %d, tot_nodes = %d, gim_allocated_for nothing = %d\n", bi->nb_pages, bi->nb_extra_pages, bi->node_pool.tot_nodes_needed, bi->gim_allocated_for_nothing);
return 0;
}
/* Move this to wbuf? */
static int check_flash_empty(struct bilbyfs_info *bi)
{
int empty = 1;
int lnum;
int err;
for (lnum = 0; lnum < bi->super.leb_cnt; lnum++) {
err = ubi_is_mapped(bi->ubi, lnum);
if (err < 0)
return err;
if (err == 1) {
empty = 0;
break;
}
}
return empty;
}
int ostore_mount(struct bilbyfs_info *bi)
{
int empty;
int err;
empty = check_flash_empty(bi);
if (empty < 0) {
err = empty;
} else if (empty) {
return -ENODATA;
} else {
err = mount_read_super(bi);
if (!err) {
err = mount_check_super(bi);
if (!err) {
bi->is_mounting = true;
bi->addrs = kmalloc(sizeof(*(bi->addrs)) * BILBYFS_MAX_OBJ_PER_TRANS);
if (!bi->addrs)
return -ENOMEM;
err = allocpool_alloc(&bi->node_pool, 10000, NODE_SIZE);
if (err) {
kfree(bi->addrs);
return err;
}
err = mount_scan(bi);
allocpool_empty(&bi->node_pool);
kfree(bi->addrs);
bi->is_mounting = false;
if (!err)
return 0;
}
} else if (err == -ENOENT) {
bilbyfs_err("Not a BilbyFs file system!\n");
}
}
ostore_unmount(bi);
return err;
}
void ostore_unmount(struct bilbyfs_info *bi)
{
int err;
/* We write a new super object only if we have written objects to the
* media since the last update. (i.e. the last written object is not
* the super block).
*/
if (bi->super.sqnum + 1 < bi->next_sqnum) {
err = ostore_sync(bi, true);
if (err) {
bilbyfs_err("Could not synchronize FS when unmounting.\n");
}
err = ostore_write_super(bi);
if (err)
bilbyfs_err("Could not write the super object %d\n", err);
else
bilbyfs_debug("ostore_unmount(): super block written\n");
}
}