/* * Copyright (C) 2010 Jeroen Oortwijn * * Partly based on the Haiku BFS driver by * Axel Dörfler * * Also inspired by the Linux BeFS driver by * Will Dyson , et al. * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ #include #include #include #include #include #include "superblocks.h" #define B_OS_NAME_LENGTH 0x20 #define SUPER_BLOCK_MAGIC1 0x42465331 /* BFS1 */ #define SUPER_BLOCK_MAGIC2 0xdd121031 #define SUPER_BLOCK_MAGIC3 0x15b6830e #define SUPER_BLOCK_FS_ENDIAN 0x42494745 /* BIGE */ #define INODE_MAGIC1 0x3bbe0ad9 #define BPLUSTREE_MAGIC 0x69f6c2e8 #define BPLUSTREE_NULL -1LL #define NUM_DIRECT_BLOCKS 12 #define B_UINT64_TYPE 0x554c4c47 /* ULLG */ #define KEY_NAME "be:volume_id" #define KEY_SIZE 8 #define FS16_TO_CPU(value, fs_is_le) (fs_is_le ? le16_to_cpu(value) \ : be16_to_cpu(value)) #define FS32_TO_CPU(value, fs_is_le) (fs_is_le ? le32_to_cpu(value) \ : be32_to_cpu(value)) #define FS64_TO_CPU(value, fs_is_le) (fs_is_le ? le64_to_cpu(value) \ : be64_to_cpu(value)) typedef struct block_run { int32_t allocation_group; uint16_t start; uint16_t len; } __attribute__((packed)) block_run, inode_addr; struct befs_super_block { char name[B_OS_NAME_LENGTH]; int32_t magic1; int32_t fs_byte_order; uint32_t block_size; uint32_t block_shift; int64_t num_blocks; int64_t used_blocks; int32_t inode_size; int32_t magic2; int32_t blocks_per_ag; int32_t ag_shift; int32_t num_ags; int32_t flags; block_run log_blocks; int64_t log_start; int64_t log_end; int32_t magic3; inode_addr root_dir; inode_addr indices; int32_t pad[8]; } __attribute__((packed)); typedef struct data_stream { block_run direct[NUM_DIRECT_BLOCKS]; int64_t max_direct_range; block_run indirect; int64_t max_indirect_range; block_run double_indirect; int64_t max_double_indirect_range; int64_t size; } __attribute__((packed)) data_stream; struct befs_inode { int32_t magic1; inode_addr inode_num; int32_t uid; int32_t gid; int32_t mode; int32_t flags; int64_t create_time; int64_t last_modified_time; inode_addr parent; inode_addr attributes; uint32_t type; int32_t inode_size; uint32_t etc; data_stream data; int32_t pad[4]; int32_t small_data[0]; } __attribute__((packed)); struct small_data { uint32_t type; uint16_t name_size; uint16_t data_size; char name[0]; } __attribute__((packed)); struct bplustree_header { uint32_t magic; uint32_t node_size; uint32_t max_number_of_levels; uint32_t data_type; int64_t root_node_pointer; int64_t free_node_pointer; int64_t maximum_size; } __attribute__((packed)); struct bplustree_node { int64_t left_link; int64_t right_link; int64_t overflow_link; uint16_t all_key_count; uint16_t all_key_length; char name[0]; } __attribute__((packed)); static unsigned char *get_block_run(blkid_probe pr, const struct befs_super_block *bs, const struct block_run *br, int fs_le) { return blkid_probe_get_buffer(pr, ((uint64_t) FS32_TO_CPU(br->allocation_group, fs_le) << FS32_TO_CPU(bs->ag_shift, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) + ((uint64_t) FS16_TO_CPU(br->start, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)), (uint64_t) FS16_TO_CPU(br->len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)); } static unsigned char *get_custom_block_run(blkid_probe pr, const struct befs_super_block *bs, const struct block_run *br, int64_t offset, uint32_t length, int fs_le) { if (offset + length > (int64_t) FS16_TO_CPU(br->len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) return NULL; return blkid_probe_get_buffer(pr, ((uint64_t) FS32_TO_CPU(br->allocation_group, fs_le) << FS32_TO_CPU(bs->ag_shift, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) + ((uint64_t) FS16_TO_CPU(br->start, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) + offset, length); } static unsigned char *get_tree_node(blkid_probe pr, const struct befs_super_block *bs, const struct data_stream *ds, int64_t start, uint32_t length, int fs_le) { if (start < (int64_t) FS64_TO_CPU(ds->max_direct_range, fs_le)) { int64_t br_len; size_t i; for (i = 0; i < NUM_DIRECT_BLOCKS; i++) { br_len = (int64_t) FS16_TO_CPU(ds->direct[i].len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le); if (start < br_len) return get_custom_block_run(pr, bs, &ds->direct[i], start, length, fs_le); else start -= br_len; } } else if (start < (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le)) { struct block_run *br; int64_t max_br, br_len, i; start -= FS64_TO_CPU(ds->max_direct_range, fs_le); max_br = ((int64_t) FS16_TO_CPU(ds->indirect.len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) / sizeof(struct block_run); br = (struct block_run *) get_block_run(pr, bs, &ds->indirect, fs_le); if (!br) return NULL; for (i = 0; i < max_br; i++) { br_len = (int64_t) FS16_TO_CPU(br[i].len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le); if (start < br_len) return get_custom_block_run(pr, bs, &br[i], start, length, fs_le); else start -= br_len; } } else if (start < (int64_t) FS64_TO_CPU(ds->max_double_indirect_range, fs_le)) { struct block_run *br; int64_t max_br, di_br_size, br_per_di_br, di_index, i_index; start -= (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le); di_br_size = (int64_t) FS16_TO_CPU(ds->double_indirect.len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le); if (di_br_size == 0) return NULL; br_per_di_br = di_br_size / sizeof(struct block_run); if (br_per_di_br == 0) return NULL; di_index = start / (br_per_di_br * di_br_size); i_index = (start % (br_per_di_br * di_br_size)) / di_br_size; start = (start % (br_per_di_br * di_br_size)) % di_br_size; if (di_index >= br_per_di_br) return NULL; /* Corrupt? */ br = (struct block_run *) get_block_run(pr, bs, &ds->double_indirect, fs_le); if (!br) return NULL; max_br = ((int64_t)FS16_TO_CPU(br[di_index].len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le)) / sizeof(struct block_run); if (i_index >= max_br) return NULL; /* Corrupt? */ br = (struct block_run *) get_block_run(pr, bs, &br[di_index], fs_le); if (!br) return NULL; return get_custom_block_run(pr, bs, &br[i_index], start, length, fs_le); } return NULL; } #define BAD_KEYS -2 static int32_t compare_keys(const char keys1[], uint16_t keylengths1[], int32_t index, const char *key2, uint16_t keylength2, uint16_t all_key_length, int fs_le) { const char *key1; uint16_t keylength1, keystart1; int32_t result; keystart1 = index == 0 ? 0 : FS16_TO_CPU(keylengths1[index - 1], fs_le); keylength1 = FS16_TO_CPU(keylengths1[index], fs_le) - keystart1; if (keystart1 + keylength1 > all_key_length) return BAD_KEYS; /* Corrupt? */ key1 = &keys1[keystart1]; result = strncmp(key1, key2, min(keylength1, keylength2)); if (result == 0) return keylength1 - keylength2; if (result < 0) /* Don't clash with BAD_KEYS */ result = -1; return result; } static int64_t get_key_value(blkid_probe pr, const struct befs_super_block *bs, const struct befs_inode *bi, const char *key, int fs_le) { struct bplustree_header *bh; struct bplustree_node *bn; uint16_t *keylengths; int64_t *values; int64_t node_pointer; uint32_t bn_size, all_key_count, all_key_length; uint32_t keylengths_offset, values_offset; int32_t first, last, mid, cmp; int loop_detect = 0; errno = 0; bh = (struct bplustree_header *) get_tree_node(pr, bs, &bi->data, 0, sizeof(struct bplustree_header), fs_le); if (!bh) return errno ? -errno : -ENOENT; if ((int32_t) FS32_TO_CPU(bh->magic, fs_le) != BPLUSTREE_MAGIC) return -ENOENT; node_pointer = FS64_TO_CPU(bh->root_node_pointer, fs_le); bn_size = FS32_TO_CPU(bh->node_size, fs_le); if (bn_size < sizeof(struct bplustree_node)) return -ENOENT; /* Corrupt? */ do { errno = 0; bn = (struct bplustree_node *) get_tree_node(pr, bs, &bi->data, node_pointer, bn_size, fs_le); if (!bn) return errno ? -errno : -ENOENT; all_key_count = FS16_TO_CPU(bn->all_key_count, fs_le); all_key_length = FS16_TO_CPU(bn->all_key_length, fs_le); keylengths_offset = (sizeof(struct bplustree_node) + all_key_length + sizeof(int64_t) - 1) & ~(sizeof(int64_t) - 1); values_offset = keylengths_offset + all_key_count * sizeof(uint16_t); if (values_offset + all_key_count * sizeof(uint64_t) > bn_size) return -ENOENT; /* Corrupt? */ keylengths = (uint16_t *) ((uint8_t *) bn + keylengths_offset); values = (int64_t *) ((uint8_t *) bn + values_offset); first = 0; mid = 0; last = all_key_count - 1; cmp = compare_keys(bn->name, keylengths, last, key, strlen(key), all_key_length, fs_le); if (cmp == BAD_KEYS) return -ENOENT; if (cmp == 0) { if ((int64_t) FS64_TO_CPU(bn->overflow_link, fs_le) == BPLUSTREE_NULL) return FS64_TO_CPU(values[last], fs_le); else node_pointer = FS64_TO_CPU(values[last], fs_le); } else if (cmp < 0) node_pointer = FS64_TO_CPU(bn->overflow_link, fs_le); else { while (first <= last) { mid = (first + last) / 2; cmp = compare_keys(bn->name, keylengths, mid, key, strlen(key), all_key_length, fs_le); if (cmp == BAD_KEYS) return -ENOENT; if (cmp == 0) { if ((int64_t) FS64_TO_CPU(bn->overflow_link, fs_le) == BPLUSTREE_NULL) return FS64_TO_CPU(values[mid], fs_le); else break; } else if (cmp < 0) first = mid + 1; else last = mid - 1; } if (cmp < 0) node_pointer = FS64_TO_CPU(values[mid + 1], fs_le); else node_pointer = FS64_TO_CPU(values[mid], fs_le); } } while (++loop_detect < 100 && (int64_t) FS64_TO_CPU(bn->overflow_link, fs_le) != BPLUSTREE_NULL); return 0; } static int get_uuid(blkid_probe pr, const struct befs_super_block *bs, uint64_t * const uuid, int fs_le) { struct befs_inode *bi; struct small_data *sd; uint64_t bi_size, offset, sd_size, sd_total_size; bi = (struct befs_inode *) get_block_run(pr, bs, &bs->root_dir, fs_le); if (!bi) return errno ? -errno : BLKID_PROBE_NONE; if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1) return BLKID_PROBE_NONE; bi_size = (uint64_t)FS16_TO_CPU(bs->root_dir.len, fs_le) << FS32_TO_CPU(bs->block_shift, fs_le); sd_total_size = min(bi_size - sizeof(struct befs_inode), (uint64_t)FS32_TO_CPU(bi->inode_size, fs_le)); offset = 0; while (offset + sizeof(struct small_data) <= sd_total_size) { sd = (struct small_data *) ((uint8_t *)bi->small_data + offset); sd_size = sizeof(struct small_data) + FS16_TO_CPU(sd->name_size, fs_le) + 3 + FS16_TO_CPU(sd->data_size, fs_le) + 1; if (offset + sd_size > sd_total_size) break; if (FS32_TO_CPU(sd->type, fs_le) == B_UINT64_TYPE && FS16_TO_CPU(sd->name_size, fs_le) == strlen(KEY_NAME) && FS16_TO_CPU(sd->data_size, fs_le) == KEY_SIZE && strcmp(sd->name, KEY_NAME) == 0) { memcpy(uuid, sd->name + FS16_TO_CPU(sd->name_size, fs_le) + 3, sizeof(uint64_t)); break; } else if (FS32_TO_CPU(sd->type, fs_le) == 0 && FS16_TO_CPU(sd->name_size, fs_le) == 0 && FS16_TO_CPU(sd->data_size, fs_le) == 0) break; offset += sd_size; } if (*uuid == 0 && (FS32_TO_CPU(bi->attributes.allocation_group, fs_le) != 0 || FS16_TO_CPU(bi->attributes.start, fs_le) != 0 || FS16_TO_CPU(bi->attributes.len, fs_le) != 0)) { int64_t value; bi = (struct befs_inode *) get_block_run(pr, bs, &bi->attributes, fs_le); if (!bi) return errno ? -errno : BLKID_PROBE_NONE; if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1) return BLKID_PROBE_NONE; value = get_key_value(pr, bs, bi, KEY_NAME, fs_le); if (value < 0) return value == -ENOENT ? BLKID_PROBE_NONE : value; else if (value > 0) { bi = (struct befs_inode *) blkid_probe_get_buffer(pr, value << FS32_TO_CPU(bs->block_shift, fs_le), FS32_TO_CPU(bs->block_size, fs_le)); if (!bi) return errno ? -errno : BLKID_PROBE_NONE; if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1) return 1; if (FS32_TO_CPU(bi->type, fs_le) == B_UINT64_TYPE && FS64_TO_CPU(bi->data.size, fs_le) == KEY_SIZE && FS16_TO_CPU(bi->data.direct[0].len, fs_le) == 1) { uint64_t *attr_data; attr_data = (uint64_t *) get_block_run(pr, bs, &bi->data.direct[0], fs_le); if (!attr_data) return errno ? -errno : BLKID_PROBE_NONE; *uuid = *attr_data; } } } return 0; } static int probe_befs(blkid_probe pr, const struct blkid_idmag *mag) { struct befs_super_block *bs; const char *version = NULL; uint64_t volume_id = 0; uint32_t block_size, block_shift; int fs_le, ret; bs = (struct befs_super_block *) blkid_probe_get_buffer(pr, mag->sboff - B_OS_NAME_LENGTH, sizeof(struct befs_super_block)); if (!bs) return errno ? -errno : BLKID_PROBE_NONE; if (le32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1 && le32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2 && le32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3 && le32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) { fs_le = 1; version = "little-endian"; } else if (be32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1 && be32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2 && be32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3 && be32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) { fs_le = 0; version = "big-endian"; } else return BLKID_PROBE_NONE; block_size = FS32_TO_CPU(bs->block_size, fs_le); block_shift = FS32_TO_CPU(bs->block_shift, fs_le); if (block_shift < 10 || block_shift > 13 || block_size != 1U << block_shift) return BLKID_PROBE_NONE; ret = get_uuid(pr, bs, &volume_id, fs_le); if (ret != 0) return ret; /* * all checks pass, set LABEL, VERSION and UUID */ if (*bs->name != '\0') blkid_probe_set_label(pr, (unsigned char *) bs->name, sizeof(bs->name)); if (version) blkid_probe_set_version(pr, version); if (volume_id) blkid_probe_sprintf_uuid(pr, (unsigned char *) &volume_id, sizeof(volume_id), "%016" PRIx64, FS64_TO_CPU(volume_id, fs_le)); return BLKID_PROBE_OK; } const struct blkid_idinfo befs_idinfo = { .name = "befs", .usage = BLKID_USAGE_FILESYSTEM, .probefunc = probe_befs, .minsz = 1024 * 1440, .magics = { { .magic = "BFS1", .len = 4, .sboff = B_OS_NAME_LENGTH }, { .magic = "1SFB", .len = 4, .sboff = B_OS_NAME_LENGTH }, { .magic = "BFS1", .len = 4, .sboff = 0x200 + B_OS_NAME_LENGTH }, { .magic = "1SFB", .len = 4, .sboff = 0x200 + B_OS_NAME_LENGTH }, { NULL } } };