blob: d2b490bce76669c45cdfe1f15ea210676ab6efad [file] [log] [blame] [edit]
/* bfs.c - The Bee File System. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2010,2011 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Based on the book "Practical File System Design by Dominic Giampaolo
with corrections and completitions based on Haiku code.
*/
#include <grub/err.h>
#include <grub/file.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/types.h>
#include <grub/i18n.h>
#include <grub/fshelp.h>
GRUB_MOD_LICENSE ("GPLv3+");
#ifdef MODE_AFS
#define BTREE_ALIGN 4
#define SUPERBLOCK 2
#else
#define BTREE_ALIGN 8
#define SUPERBLOCK 1
#endif
#define grub_bfs_to_cpu16 grub_le_to_cpu16
#define grub_bfs_to_cpu32 grub_le_to_cpu32
#define grub_bfs_to_cpu64 grub_le_to_cpu64
#define grub_cpu_to_bfs32_compile_time grub_cpu_to_le32_compile_time
#ifdef MODE_AFS
#define grub_bfs_to_cpu_treehead grub_bfs_to_cpu32
#else
#define grub_bfs_to_cpu_treehead grub_bfs_to_cpu16
#endif
#ifdef MODE_AFS
#define SUPER_BLOCK_MAGIC1 0x41465331
#else
#define SUPER_BLOCK_MAGIC1 0x42465331
#endif
#define SUPER_BLOCK_MAGIC2 0xdd121031
#define SUPER_BLOCK_MAGIC3 0x15b6830e
#define POINTER_INVALID 0xffffffffffffffffULL
#define ATTR_TYPE 0160000
#define ATTR_REG 0100000
#define ATTR_DIR 0040000
#define ATTR_LNK 0120000
#define DOUBLE_INDIRECT_SHIFT 2
#define LOG_EXTENT_SIZE 3
struct grub_bfs_extent
{
grub_uint32_t ag;
grub_uint16_t start;
grub_uint16_t len;
} GRUB_PACKED;
struct grub_bfs_superblock
{
char label[32];
grub_uint32_t magic1;
grub_uint32_t unused1;
grub_uint32_t bsize;
grub_uint32_t log2_bsize;
grub_uint8_t unused[20];
grub_uint32_t magic2;
grub_uint32_t unused2;
grub_uint32_t log2_ag_size;
grub_uint8_t unused3[32];
grub_uint32_t magic3;
struct grub_bfs_extent root_dir;
} GRUB_PACKED;
struct grub_bfs_inode
{
grub_uint8_t unused[20];
grub_uint32_t mode;
grub_uint32_t flags;
#ifdef MODE_AFS
grub_uint8_t unused2[12];
#else
grub_uint8_t unused2[8];
#endif
grub_uint64_t mtime;
grub_uint8_t unused3[8];
struct grub_bfs_extent attr;
grub_uint8_t unused4[12];
union
{
struct
{
struct grub_bfs_extent direct[12];
grub_uint64_t max_direct_range;
struct grub_bfs_extent indirect;
grub_uint64_t max_indirect_range;
struct grub_bfs_extent double_indirect;
grub_uint64_t max_double_indirect_range;
grub_uint64_t size;
grub_uint32_t pad[4];
} GRUB_PACKED;
char inplace_link[144];
} GRUB_PACKED;
grub_uint8_t small_data[0];
} GRUB_PACKED;
enum
{
LONG_SYMLINK = 0x40
};
struct grub_bfs_small_data_element_header
{
grub_uint32_t type;
grub_uint16_t name_len;
grub_uint16_t value_len;
} GRUB_PACKED;
struct grub_bfs_btree_header
{
grub_uint32_t magic;
#ifdef MODE_AFS
grub_uint64_t root;
grub_uint32_t level;
grub_uint32_t node_size;
grub_uint32_t unused;
#else
grub_uint32_t node_size;
grub_uint32_t level;
grub_uint32_t unused;
grub_uint64_t root;
#endif
grub_uint32_t unused2[2];
} GRUB_PACKED;
struct grub_bfs_btree_node
{
grub_uint64_t unused;
grub_uint64_t right;
grub_uint64_t overflow;
#ifdef MODE_AFS
grub_uint32_t count_keys;
grub_uint32_t total_key_len;
#else
grub_uint16_t count_keys;
grub_uint16_t total_key_len;
#endif
} GRUB_PACKED;
struct grub_bfs_data
{
struct grub_bfs_superblock sb;
struct grub_bfs_inode ino;
};
/* Context for grub_bfs_dir. */
struct grub_bfs_dir_ctx
{
grub_device_t device;
grub_fs_dir_hook_t hook;
void *hook_data;
struct grub_bfs_superblock sb;
};
static grub_err_t
read_extent (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
const struct grub_bfs_extent *in,
grub_off_t off, grub_off_t byteoff, void *buf, grub_size_t len)
{
#ifdef MODE_AFS
return grub_disk_read (disk, ((grub_bfs_to_cpu32 (in->ag)
<< (grub_bfs_to_cpu32 (sb->log2_ag_size)
- GRUB_DISK_SECTOR_BITS))
+ ((grub_bfs_to_cpu16 (in->start) + off)
<< (grub_bfs_to_cpu32 (sb->log2_bsize)
- GRUB_DISK_SECTOR_BITS))),
byteoff, len, buf);
#else
return grub_disk_read (disk, (((grub_bfs_to_cpu32 (in->ag)
<< grub_bfs_to_cpu32 (sb->log2_ag_size))
+ grub_bfs_to_cpu16 (in->start) + off)
<< (grub_bfs_to_cpu32 (sb->log2_bsize)
- GRUB_DISK_SECTOR_BITS)),
byteoff, len, buf);
#endif
}
#ifdef MODE_AFS
#define RANGE_SHIFT grub_bfs_to_cpu32 (sb->log2_bsize)
#else
#define RANGE_SHIFT 0
#endif
static grub_err_t
read_bfs_file (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
const struct grub_bfs_inode *ino,
grub_off_t off, void *buf, grub_size_t len,
grub_disk_read_hook_t read_hook, void *read_hook_data)
{
if (len == 0)
return GRUB_ERR_NONE;
if (off + len > grub_bfs_to_cpu64 (ino->size))
return grub_error (GRUB_ERR_OUT_OF_RANGE,
N_("attempt to read past the end of file"));
if (off < (grub_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT))
{
unsigned i;
grub_uint64_t pos = 0;
for (i = 0; i < ARRAY_SIZE (ino->direct); i++)
{
grub_uint64_t newpos;
newpos = pos + (((grub_uint64_t) grub_bfs_to_cpu16 (ino->direct[i].len))
<< grub_bfs_to_cpu32 (sb->log2_bsize));
if (newpos > off)
{
grub_size_t read_size;
grub_err_t err;
read_size = newpos - off;
if (read_size > len)
read_size = len;
disk->read_hook = read_hook;
disk->read_hook_data = read_hook_data;
err = read_extent (disk, sb, &ino->direct[i], 0, off - pos,
buf, read_size);
disk->read_hook = 0;
if (err)
return err;
off += read_size;
len -= read_size;
buf = (char *) buf + read_size;
if (len == 0)
return GRUB_ERR_NONE;
}
pos = newpos;
}
}
if (off < (grub_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT))
return grub_error (GRUB_ERR_BAD_FS, "incorrect direct blocks");
if (off < (grub_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT))
{
unsigned i;
struct grub_bfs_extent *entries;
grub_size_t nentries;
grub_err_t err;
grub_uint64_t pos = (grub_bfs_to_cpu64 (ino->max_direct_range)
<< RANGE_SHIFT);
nentries = (((grub_size_t) grub_bfs_to_cpu16 (ino->indirect.len))
<< (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE));
entries = grub_malloc (nentries << LOG_EXTENT_SIZE);
if (!entries)
return grub_errno;
err = read_extent (disk, sb, &ino->indirect, 0, 0,
entries, nentries << LOG_EXTENT_SIZE);
for (i = 0; i < nentries; i++)
{
grub_uint64_t newpos;
newpos = pos + (((grub_uint64_t) grub_bfs_to_cpu16 (entries[i].len))
<< grub_bfs_to_cpu32 (sb->log2_bsize));
if (newpos > off)
{
grub_size_t read_size;
read_size = newpos - off;
if (read_size > len)
read_size = len;
disk->read_hook = read_hook;
disk->read_hook_data = read_hook_data;
err = read_extent (disk, sb, &entries[i], 0, off - pos,
buf, read_size);
disk->read_hook = 0;
if (err)
{
grub_free (entries);
return err;
}
off += read_size;
len -= read_size;
buf = (char *) buf + read_size;
if (len == 0)
{
grub_free (entries);
return GRUB_ERR_NONE;
}
}
pos = newpos;
}
grub_free (entries);
}
if (off < (grub_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT))
return grub_error (GRUB_ERR_BAD_FS, "incorrect indirect blocks");
{
struct grub_bfs_extent *l1_entries, *l2_entries;
grub_size_t nl1_entries, nl2_entries;
grub_off_t last_l1n = ~0ULL;
grub_err_t err;
nl1_entries = (((grub_uint64_t) grub_bfs_to_cpu16 (ino->double_indirect.len))
<< (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE));
l1_entries = grub_malloc (nl1_entries << LOG_EXTENT_SIZE);
if (!l1_entries)
return grub_errno;
nl2_entries = 0;
l2_entries = grub_malloc (1 << (DOUBLE_INDIRECT_SHIFT
+ grub_bfs_to_cpu32 (sb->log2_bsize)));
if (!l2_entries)
{
grub_free (l1_entries);
return grub_errno;
}
err = read_extent (disk, sb, &ino->double_indirect, 0, 0,
l1_entries, nl1_entries << LOG_EXTENT_SIZE);
if (err)
{
grub_free (l1_entries);
grub_free (l2_entries);
return err;
}
while (len > 0)
{
grub_off_t boff, l2n, l1n;
grub_size_t read_size;
grub_off_t double_indirect_offset;
double_indirect_offset = off
- grub_bfs_to_cpu64 (ino->max_indirect_range);
boff = (double_indirect_offset
& ((1 << (grub_bfs_to_cpu32 (sb->log2_bsize)
+ DOUBLE_INDIRECT_SHIFT)) - 1));
l2n = ((double_indirect_offset >> (grub_bfs_to_cpu32 (sb->log2_bsize)
+ DOUBLE_INDIRECT_SHIFT))
& ((1 << (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE
+ DOUBLE_INDIRECT_SHIFT)) - 1));
l1n =
(double_indirect_offset >>
(2 * grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE +
2 * DOUBLE_INDIRECT_SHIFT));
if (l1n > nl1_entries)
{
grub_free (l1_entries);
grub_free (l2_entries);
return grub_error (GRUB_ERR_BAD_FS,
"incorrect double-indirect block");
}
if (l1n != last_l1n)
{
nl2_entries = (((grub_uint64_t) grub_bfs_to_cpu16 (l1_entries[l1n].len))
<< (grub_bfs_to_cpu32 (sb->log2_bsize)
- LOG_EXTENT_SIZE));
if (nl2_entries > (1U << (grub_bfs_to_cpu32 (sb->log2_bsize)
- LOG_EXTENT_SIZE
+ DOUBLE_INDIRECT_SHIFT)))
nl2_entries = (1 << (grub_bfs_to_cpu32 (sb->log2_bsize)
- LOG_EXTENT_SIZE
+ DOUBLE_INDIRECT_SHIFT));
err = read_extent (disk, sb, &l1_entries[l1n], 0, 0,
l2_entries, nl2_entries << LOG_EXTENT_SIZE);
if (err)
{
grub_free (l1_entries);
grub_free (l2_entries);
return err;
}
last_l1n = l1n;
}
if (l2n > nl2_entries)
{
grub_free (l1_entries);
grub_free (l2_entries);
return grub_error (GRUB_ERR_BAD_FS,
"incorrect double-indirect block");
}
read_size = (1 << (grub_bfs_to_cpu32 (sb->log2_bsize)
+ DOUBLE_INDIRECT_SHIFT)) - boff;
if (read_size > len)
read_size = len;
disk->read_hook = read_hook;
disk->read_hook_data = read_hook_data;
err = read_extent (disk, sb, &l2_entries[l2n], 0, boff,
buf, read_size);
disk->read_hook = 0;
if (err)
{
grub_free (l1_entries);
grub_free (l2_entries);
return err;
}
off += read_size;
len -= read_size;
buf = (char *) buf + read_size;
}
return GRUB_ERR_NONE;
}
}
static grub_err_t
read_b_node (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
const struct grub_bfs_inode *ino,
grub_uint64_t node_off,
struct grub_bfs_btree_node **node,
char **key_data, grub_uint16_t **keylen_idx,
grub_unaligned_uint64_t **key_values)
{
void *ret;
struct grub_bfs_btree_node node_head;
grub_size_t total_size;
grub_err_t err;
*node = NULL;
*key_data = NULL;
*keylen_idx = NULL;
*key_values = NULL;
err = read_bfs_file (disk, sb, ino, node_off, &node_head, sizeof (node_head),
0, 0);
if (err)
return err;
total_size = ALIGN_UP (sizeof (node_head) +
grub_bfs_to_cpu_treehead
(node_head.total_key_len),
BTREE_ALIGN) +
grub_bfs_to_cpu_treehead (node_head.count_keys) *
sizeof (grub_uint16_t)
+ grub_bfs_to_cpu_treehead (node_head.count_keys) *
sizeof (grub_uint64_t);
ret = grub_malloc (total_size);
if (!ret)
return grub_errno;
err = read_bfs_file (disk, sb, ino, node_off, ret, total_size, 0, 0);
if (err)
{
grub_free (ret);
return err;
}
*node = ret;
*key_data = (char *) ret + sizeof (node_head);
*keylen_idx = (grub_uint16_t *) ret
+ ALIGN_UP (sizeof (node_head) +
grub_bfs_to_cpu_treehead (node_head.total_key_len),
BTREE_ALIGN) / 2;
*key_values = (grub_unaligned_uint64_t *)
(*keylen_idx +
grub_bfs_to_cpu_treehead (node_head.count_keys));
return GRUB_ERR_NONE;
}
static int
iterate_in_b_tree (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
const struct grub_bfs_inode *ino,
int (*hook) (const char *name, grub_uint64_t value,
struct grub_bfs_dir_ctx *ctx),
struct grub_bfs_dir_ctx *ctx)
{
struct grub_bfs_btree_header head;
grub_err_t err;
int level;
grub_uint64_t node_off;
err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0);
if (err)
return 0;
node_off = grub_bfs_to_cpu64 (head.root);
level = grub_bfs_to_cpu32 (head.level) - 1;
while (level--)
{
struct grub_bfs_btree_node node;
grub_uint64_t key_value;
err = read_bfs_file (disk, sb, ino, node_off, &node, sizeof (node),
0, 0);
if (err)
return 0;
err = read_bfs_file (disk, sb, ino, node_off
+ ALIGN_UP (sizeof (node) +
grub_bfs_to_cpu_treehead (node.
total_key_len),
BTREE_ALIGN) +
grub_bfs_to_cpu_treehead (node.count_keys) *
sizeof (grub_uint16_t), &key_value,
sizeof (grub_uint64_t), 0, 0);
if (err)
return 0;
node_off = grub_bfs_to_cpu64 (key_value);
}
while (1)
{
struct grub_bfs_btree_node *node;
char *key_data;
grub_uint16_t *keylen_idx;
grub_unaligned_uint64_t *key_values;
unsigned i;
grub_uint16_t start = 0, end = 0;
err = read_b_node (disk, sb, ino,
node_off,
&node,
&key_data,
&keylen_idx,
&key_values);
if (err)
return 0;
for (i = 0; i < grub_bfs_to_cpu_treehead (node->count_keys); i++)
{
char c;
start = end;
end = grub_bfs_to_cpu16 (keylen_idx[i]);
if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end)
end = grub_bfs_to_cpu_treehead (node->total_key_len);
c = key_data[end];
key_data[end] = 0;
if (hook (key_data + start, grub_bfs_to_cpu64 (key_values[i].val),
ctx))
{
grub_free (node);
return 1;
}
key_data[end] = c;
}
node_off = grub_bfs_to_cpu64 (node->right);
grub_free (node);
if (node_off == POINTER_INVALID)
return 0;
}
}
static int
bfs_strcmp (const char *a, const char *b, grub_size_t alen)
{
char ac, bc;
while (*b && alen)
{
if (*a != *b)
break;
a++;
b++;
alen--;
}
ac = alen ? *a : 0;
bc = *b;
#ifdef MODE_AFS
return (int) (grub_int8_t) ac - (int) (grub_int8_t) bc;
#else
return (int) (grub_uint8_t) ac - (int) (grub_uint8_t) bc;
#endif
}
static grub_err_t
find_in_b_tree (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
const struct grub_bfs_inode *ino, const char *name,
grub_uint64_t * res)
{
struct grub_bfs_btree_header head;
grub_err_t err;
int level;
grub_uint64_t node_off;
err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0);
if (err)
return err;
node_off = grub_bfs_to_cpu64 (head.root);
level = grub_bfs_to_cpu32 (head.level) - 1;
while (1)
{
struct grub_bfs_btree_node *node;
char *key_data;
grub_uint16_t *keylen_idx;
grub_unaligned_uint64_t *key_values;
int lg, j;
unsigned i;
err = read_b_node (disk, sb, ino, node_off, &node, &key_data, &keylen_idx, &key_values);
if (err)
return err;
if (node->count_keys == 0)
{
grub_free (node);
return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"),
name);
}
for (lg = 0; grub_bfs_to_cpu_treehead (node->count_keys) >> lg; lg++);
i = 0;
for (j = lg - 1; j >= 0; j--)
{
int cmp;
grub_uint16_t start = 0, end = 0;
if ((i | (1 << j)) >= grub_bfs_to_cpu_treehead (node->count_keys))
continue;
start = grub_bfs_to_cpu16 (keylen_idx[(i | (1 << j)) - 1]);
end = grub_bfs_to_cpu16 (keylen_idx[(i | (1 << j))]);
if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end)
end = grub_bfs_to_cpu_treehead (node->total_key_len);
cmp = bfs_strcmp (key_data + start, name, end - start);
if (cmp == 0 && level == 0)
{
*res = grub_bfs_to_cpu64 (key_values[i | (1 << j)].val);
grub_free (node);
return GRUB_ERR_NONE;
}
#ifdef MODE_AFS
if (cmp <= 0)
#else
if (cmp < 0)
#endif
i |= (1 << j);
}
if (i == 0)
{
grub_uint16_t end = 0;
int cmp;
end = grub_bfs_to_cpu16 (keylen_idx[0]);
if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end)
end = grub_bfs_to_cpu_treehead (node->total_key_len);
cmp = bfs_strcmp (key_data, name, end);
if (cmp == 0 && level == 0)
{
*res = grub_bfs_to_cpu64 (key_values[0].val);
grub_free (node);
return GRUB_ERR_NONE;
}
#ifdef MODE_AFS
if (cmp > 0 && level != 0)
#else
if (cmp >= 0 && level != 0)
#endif
{
node_off = grub_bfs_to_cpu64 (key_values[0].val);
level--;
grub_free (node);
continue;
}
else if (level != 0
&& grub_bfs_to_cpu_treehead (node->count_keys) >= 2)
{
node_off = grub_bfs_to_cpu64 (key_values[1].val);
level--;
grub_free (node);
continue;
}
}
else if (level != 0
&& i + 1 < grub_bfs_to_cpu_treehead (node->count_keys))
{
node_off = grub_bfs_to_cpu64 (key_values[i + 1].val);
level--;
grub_free (node);
continue;
}
if (node->overflow != POINTER_INVALID)
{
node_off = grub_bfs_to_cpu64 (node->overflow);
/* This level-- isn't specified but is needed. */
level--;
grub_free (node);
continue;
}
grub_free (node);
return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"),
name);
}
}
struct grub_fshelp_node
{
grub_disk_t disk;
const struct grub_bfs_superblock *sb;
struct grub_bfs_inode ino;
};
static grub_err_t
lookup_file (grub_fshelp_node_t dir,
const char *name,
grub_fshelp_node_t *foundnode,
enum grub_fshelp_filetype *foundtype)
{
grub_err_t err;
struct grub_bfs_inode *new_ino;
grub_uint64_t res = 0;
err = find_in_b_tree (dir->disk, dir->sb, &dir->ino, name, &res);
if (err)
return err;
*foundnode = grub_malloc (sizeof (struct grub_fshelp_node));
if (!*foundnode)
return grub_errno;
(*foundnode)->disk = dir->disk;
(*foundnode)->sb = dir->sb;
new_ino = &(*foundnode)->ino;
if (grub_disk_read (dir->disk, res
<< (grub_bfs_to_cpu32 (dir->sb->log2_bsize)
- GRUB_DISK_SECTOR_BITS), 0,
sizeof (*new_ino), (char *) new_ino))
{
grub_free (*foundnode);
return grub_errno;
}
switch (grub_bfs_to_cpu32 (new_ino->mode) & ATTR_TYPE)
{
default:
case ATTR_REG:
*foundtype = GRUB_FSHELP_REG;
break;
case ATTR_DIR:
*foundtype = GRUB_FSHELP_DIR;
break;
case ATTR_LNK:
*foundtype = GRUB_FSHELP_SYMLINK;
break;
}
return GRUB_ERR_NONE;
}
static char *
read_symlink (grub_fshelp_node_t node)
{
char *alloc = NULL;
grub_err_t err;
#ifndef MODE_AFS
if (!(grub_bfs_to_cpu32 (node->ino.flags) & LONG_SYMLINK))
{
alloc = grub_malloc (sizeof (node->ino.inplace_link) + 1);
if (!alloc)
{
return NULL;
}
grub_memcpy (alloc, node->ino.inplace_link,
sizeof (node->ino.inplace_link));
alloc[sizeof (node->ino.inplace_link)] = 0;
}
else
#endif
{
grub_size_t symsize = grub_bfs_to_cpu64 (node->ino.size);
alloc = grub_malloc (symsize + 1);
if (!alloc)
return NULL;
err = read_bfs_file (node->disk, node->sb, &node->ino, 0, alloc, symsize, 0, 0);
if (err)
{
grub_free (alloc);
return NULL;
}
alloc[symsize] = 0;
}
return alloc;
}
static grub_err_t
find_file (const char *path, grub_disk_t disk,
const struct grub_bfs_superblock *sb, struct grub_bfs_inode *ino,
enum grub_fshelp_filetype exptype)
{
grub_err_t err;
struct grub_fshelp_node root = {
.disk = disk,
.sb = sb,
};
struct grub_fshelp_node *found;
err = read_extent (disk, sb, &sb->root_dir, 0, 0, &root.ino,
sizeof (root.ino));
if (err)
return err;
err = grub_fshelp_find_file_lookup (path, &root, &found, lookup_file, read_symlink, exptype);
if (!err)
grub_memcpy (ino, &found->ino, sizeof (*ino));
if (&root != found)
grub_free (found);
return err;
}
static grub_err_t
mount (grub_disk_t disk, struct grub_bfs_superblock *sb)
{
grub_err_t err;
err = grub_disk_read (disk, SUPERBLOCK, 0, sizeof (*sb), sb);
if (err == GRUB_ERR_OUT_OF_RANGE)
return grub_error (GRUB_ERR_BAD_FS,
#ifdef MODE_AFS
"not an AFS filesystem"
#else
"not a BFS filesystem"
#endif
);
if (err)
return err;
if (sb->magic1 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC1)
|| sb->magic2 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC2)
|| sb->magic3 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC3)
|| sb->bsize == 0
|| (grub_bfs_to_cpu32 (sb->bsize)
!= (1U << grub_bfs_to_cpu32 (sb->log2_bsize)))
|| grub_bfs_to_cpu32 (sb->log2_bsize) < GRUB_DISK_SECTOR_BITS)
return grub_error (GRUB_ERR_BAD_FS,
#ifdef MODE_AFS
"not an AFS filesystem"
#else
"not a BFS filesystem"
#endif
);
return GRUB_ERR_NONE;
}
/* Helper for grub_bfs_dir. */
static int
grub_bfs_dir_iter (const char *name, grub_uint64_t value,
struct grub_bfs_dir_ctx *ctx)
{
grub_err_t err2;
struct grub_bfs_inode ino;
struct grub_dirhook_info info;
err2 = grub_disk_read (ctx->device->disk, value
<< (grub_bfs_to_cpu32 (ctx->sb.log2_bsize)
- GRUB_DISK_SECTOR_BITS), 0,
sizeof (ino), (char *) &ino);
if (err2)
{
grub_print_error ();
return 0;
}
info.mtimeset = 1;
#ifdef MODE_AFS
info.mtime =
grub_divmod64 (grub_bfs_to_cpu64 (ino.mtime), 1000000, 0);
#else
info.mtime = grub_bfs_to_cpu64 (ino.mtime) >> 16;
#endif
info.dir = ((grub_bfs_to_cpu32 (ino.mode) & ATTR_TYPE) == ATTR_DIR);
return ctx->hook (name, &info, ctx->hook_data);
}
static grub_err_t
grub_bfs_dir (grub_device_t device, const char *path,
grub_fs_dir_hook_t hook, void *hook_data)
{
struct grub_bfs_dir_ctx ctx = {
.device = device,
.hook = hook,
.hook_data = hook_data
};
grub_err_t err;
err = mount (device->disk, &ctx.sb);
if (err)
return err;
{
struct grub_bfs_inode ino;
err = find_file (path, device->disk, &ctx.sb, &ino, GRUB_FSHELP_DIR);
if (err)
return err;
iterate_in_b_tree (device->disk, &ctx.sb, &ino, grub_bfs_dir_iter,
&ctx);
}
return grub_errno;
}
static grub_err_t
grub_bfs_open (struct grub_file *file, const char *name)
{
struct grub_bfs_superblock sb;
grub_err_t err;
err = mount (file->device->disk, &sb);
if (err)
return err;
{
struct grub_bfs_inode ino;
struct grub_bfs_data *data;
err = find_file (name, file->device->disk, &sb, &ino, GRUB_FSHELP_REG);
if (err)
return err;
data = grub_zalloc (sizeof (struct grub_bfs_data));
if (!data)
return grub_errno;
data->sb = sb;
grub_memcpy (&data->ino, &ino, sizeof (data->ino));
file->data = data;
file->size = grub_bfs_to_cpu64 (ino.size);
}
return GRUB_ERR_NONE;
}
static grub_err_t
grub_bfs_close (grub_file_t file)
{
grub_free (file->data);
return GRUB_ERR_NONE;
}
static grub_ssize_t
grub_bfs_read (grub_file_t file, char *buf, grub_size_t len)
{
grub_err_t err;
struct grub_bfs_data *data = file->data;
err = read_bfs_file (file->device->disk, &data->sb,
&data->ino, file->offset, buf, len,
file->read_hook, file->read_hook_data);
if (err)
return -1;
return len;
}
static grub_err_t
grub_bfs_label (grub_device_t device, char **label)
{
struct grub_bfs_superblock sb;
grub_err_t err;
*label = 0;
err = mount (device->disk, &sb);
if (err)
return err;
*label = grub_strndup (sb.label, sizeof (sb.label));
return GRUB_ERR_NONE;
}
#ifndef MODE_AFS
static grub_ssize_t
read_bfs_attr (grub_disk_t disk,
const struct grub_bfs_superblock *sb,
struct grub_bfs_inode *ino,
const char *name, void *buf, grub_size_t len)
{
grub_uint8_t *ptr = (grub_uint8_t *) ino->small_data;
grub_uint8_t *end = ((grub_uint8_t *) ino + grub_bfs_to_cpu32 (sb->bsize));
while (ptr + sizeof (struct grub_bfs_small_data_element_header) < end)
{
struct grub_bfs_small_data_element_header *el;
char *el_name;
grub_uint8_t *data;
el = (struct grub_bfs_small_data_element_header *) ptr;
if (el->name_len == 0)
break;
el_name = (char *) (el + 1);
data = (grub_uint8_t *) el_name + grub_bfs_to_cpu16 (el->name_len) + 3;
ptr = data + grub_bfs_to_cpu16 (el->value_len) + 1;
if (grub_memcmp (name, el_name, grub_bfs_to_cpu16 (el->name_len)) == 0
&& name[el->name_len] == 0)
{
grub_size_t copy;
copy = len;
if (grub_bfs_to_cpu16 (el->value_len) > copy)
copy = grub_bfs_to_cpu16 (el->value_len);
grub_memcpy (buf, data, copy);
return copy;
}
}
if (ino->attr.len != 0)
{
grub_size_t read;
grub_err_t err;
grub_uint64_t res;
err = read_extent (disk, sb, &ino->attr, 0, 0, ino,
grub_bfs_to_cpu32 (sb->bsize));
if (err)
return -1;
err = find_in_b_tree (disk, sb, ino, name, &res);
if (err)
return -1;
grub_disk_read (disk, res
<< (grub_bfs_to_cpu32 (sb->log2_bsize)
- GRUB_DISK_SECTOR_BITS), 0,
grub_bfs_to_cpu32 (sb->bsize), (char *) ino);
read = grub_bfs_to_cpu64 (ino->size);
if (read > len)
read = len;
err = read_bfs_file (disk, sb, ino, 0, buf, read, 0, 0);
if (err)
return -1;
return read;
}
return -1;
}
static grub_err_t
grub_bfs_uuid (grub_device_t device, char **uuid)
{
struct grub_bfs_superblock sb;
grub_err_t err;
struct grub_bfs_inode *ino;
grub_uint64_t vid;
*uuid = 0;
err = mount (device->disk, &sb);
if (err)
return err;
ino = grub_malloc (grub_bfs_to_cpu32 (sb.bsize));
if (!ino)
return grub_errno;
err = read_extent (device->disk, &sb, &sb.root_dir, 0, 0,
ino, grub_bfs_to_cpu32 (sb.bsize));
if (err)
{
grub_free (ino);
return err;
}
if (read_bfs_attr (device->disk, &sb, ino, "be:volume_id",
&vid, sizeof (vid)) == sizeof (vid))
*uuid =
grub_xasprintf ("%016" PRIxGRUB_UINT64_T, grub_bfs_to_cpu64 (vid));
grub_free (ino);
return GRUB_ERR_NONE;
}
#endif
static struct grub_fs grub_bfs_fs = {
#ifdef MODE_AFS
.name = "afs",
#else
.name = "bfs",
#endif
.dir = grub_bfs_dir,
.open = grub_bfs_open,
.read = grub_bfs_read,
.close = grub_bfs_close,
.label = grub_bfs_label,
#ifndef MODE_AFS
.uuid = grub_bfs_uuid,
#endif
#ifdef GRUB_UTIL
.reserved_first_sector = 1,
.blocklist_install = 1,
#endif
};
#ifdef MODE_AFS
GRUB_MOD_INIT (afs)
#else
GRUB_MOD_INIT (bfs)
#endif
{
COMPILE_TIME_ASSERT (1 << LOG_EXTENT_SIZE ==
sizeof (struct grub_bfs_extent));
grub_fs_register (&grub_bfs_fs);
}
#ifdef MODE_AFS
GRUB_MOD_FINI (afs)
#else
GRUB_MOD_FINI (bfs)
#endif
{
grub_fs_unregister (&grub_bfs_fs);
}