diff options
author | Alek Du <alek.du@intel.com> | 2009-12-17 11:14:48 +0800 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2009-12-22 09:46:49 -0800 |
commit | 1cd67f6eb8617be02f3a1af104100eb5869b6e20 (patch) | |
tree | 11cda66bb07adb1cf934a5770698acbcc36944bc | |
parent | 9e25d6029b8c1568a74a7d9b5bc07f22b0fb8ade (diff) | |
download | syslinux-1cd67f6eb8617be02f3a1af104100eb5869b6e20.tar.gz |
btrfs: add btrfs file system support to extlinuxsyslinux-4.00-pre7
the extlinux.sys will be installed in btrfs first 64K blank area, and the
extlinux.conf must be in root dir...
Signed-off-by: Alek Du <alek.du@intel.com>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
-rw-r--r-- | core/extlinux.asm | 7 | ||||
-rw-r--r-- | core/fs.c | 44 | ||||
-rw-r--r-- | core/fs/btrfs/btrfs.c | 645 | ||||
-rw-r--r-- | core/fs/btrfs/btrfs.h | 275 | ||||
-rw-r--r-- | core/fs/btrfs/crc32c.h | 101 | ||||
-rw-r--r-- | core/fs/ext2/ext2.c | 3 | ||||
-rw-r--r-- | core/ldlinux.asm | 4 | ||||
-rw-r--r-- | extlinux/btrfs.h | 22 | ||||
-rw-r--r-- | extlinux/main.c | 264 |
9 files changed, 1290 insertions, 75 deletions
diff --git a/core/extlinux.asm b/core/extlinux.asm index 1f5423b2..c7cea51a 100644 --- a/core/extlinux.asm +++ b/core/extlinux.asm @@ -25,6 +25,11 @@ my_id equ extlinux_id extern ext2_fs_ops -ROOT_FS_OPS equ ext2_fs_ops + extern btrfs_fs_ops + + section .rodata +ROOT_FS_OPS dd ext2_fs_ops + dd btrfs_fs_ops + dd 0 %include "diskfs.inc" @@ -6,7 +6,7 @@ /* The currently mounted filesystem */ struct fs_info *this_fs = NULL; -struct fs_info fs; +static struct fs_info fs; /* Actual file structures (we don't have malloc yet...) */ struct file files[MAX_OPEN]; @@ -166,24 +166,34 @@ void close_file(com32sys_t *regs) */ void fs_init(com32sys_t *regs) { - int blk_shift; - const struct fs_ops *ops = (const struct fs_ops *)regs->eax.l; + int blk_shift = -1; + /* ops is a ptr list for several fs_ops */ + const struct fs_ops **ops = (const struct fs_ops **)regs->eax.l; - /* set up the fs stucture */ - fs.fs_ops = ops; - - /* this is a total hack... */ - if (ops->fs_flags & FS_NODEV) - fs.fs_dev = NULL; /* Non-device filesystem */ - else - fs.fs_dev = device_init(regs->edx.b[0], regs->edx.b[1], regs->ecx.l, - regs->esi.w[0], regs->edi.w[0]); - + while ((blk_shift < 0) && *ops) { + /* set up the fs stucture */ + fs.fs_ops = *ops; + /* this is a total hack... */ + if (fs.fs_ops->fs_flags & FS_NODEV) + fs.fs_dev = NULL; + else { + static struct device *dev; + + if (!dev) + dev = device_init(regs->edx.b[0], regs->edx.b[1], + regs->ecx.l, regs->esi.w[0], regs->edi.w[0]); + fs.fs_dev = dev; + } + /* invoke the fs-specific init code */ + blk_shift = fs.fs_ops->fs_init(&fs); + ops++; + } + if (blk_shift < 0) { + printf("No valid file system found!\n"); + while (1) + ; + } this_fs = &fs; - - /* invoke the fs-specific init code */ - blk_shift = fs.fs_ops->fs_init(&fs); - /* initialize the cache */ if (fs.fs_dev && fs.fs_dev->cache_data) cache_init(fs.fs_dev, blk_shift); diff --git a/core/fs/btrfs/btrfs.c b/core/fs/btrfs/btrfs.c new file mode 100644 index 00000000..2092e29b --- /dev/null +++ b/core/fs/btrfs/btrfs.c @@ -0,0 +1,645 @@ +/* + * btrfs.c -- readonly btrfs support for syslinux + * Some data structures are derivated from btrfs-tools-0.19 ctree.h + * Copyright 2009 Intel Corporation; author: alek.du@intel.com + * + * This program 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + */ + +#include <stdio.h> +#include <string.h> +#include <cache.h> +#include <core.h> +#include <disk.h> +#include <fs.h> +#include "btrfs.h" + +/* compare function used for bin_search */ +typedef int (*cmp_func)(void *ptr1, void *ptr2); + +/* simple but useful bin search, used for chunk search and btree search */ +static int bin_search(void *ptr, int item_size, void *cmp_item, cmp_func func, + int min, int max, int *slot) +{ + int low = min; + int high = max; + int mid; + int ret; + unsigned long offset; + void *item; + + while (low < high) { + mid = (low + high) / 2; + offset = mid * item_size; + + item = ptr + offset; + ret = func(item, cmp_item); + + if (ret < 0) + low = mid + 1; + else if (ret > 0) + high = mid; + else { + *slot = mid; + return 0; + } + } + *slot = low; + return 1; +} + +struct open_file_t { + u64 devid; /* always 1 if allocated, reserved for multi disks */ + u64 bytenr; /* start offset in devid */ + u64 pos; /* current offset in file */ +}; + +static struct open_file_t Files[MAX_OPEN]; +static int cache_ready; +static struct fs_info *fs; +static struct btrfs_chunk_map chunk_map; +static struct btrfs_super_block sb; +/* used for small chunk read for btrfs_read */ +#define RAW_BUF_SIZE 4096 +static u8 raw_buf[RAW_BUF_SIZE]; +static u64 fs_tree; + +static int btrfs_comp_chunk_map(struct btrfs_chunk_map_item *m1, + struct btrfs_chunk_map_item *m2) +{ + if (m1->logical > m2->logical) + return 1; + if (m1->logical < m2->logical) + return -1; + return 0; +} + +/* insert a new chunk mapping item */ +static void insert_map(struct btrfs_chunk_map_item *item) +{ + int ret; + int slot; + int i; + + ret = bin_search(chunk_map.map, sizeof(*item), item, + (cmp_func)btrfs_comp_chunk_map, 0, + chunk_map.cur_length, &slot); + if (ret == 0)/* already in map */ + return; + if (chunk_map.cur_length == BTRFS_MAX_CHUNK_ENTRIES) { + /* should be impossible */ + printf("too many chunk items\n"); + return; + } + for (i = chunk_map.cur_length; i > slot; i--) + chunk_map.map[i] = chunk_map.map[i-1]; + chunk_map.map[slot] = *item; + chunk_map.cur_length++; +} + +/* + * from sys_chunk_array or chunk_tree, we can convert a logical address to + * a physical address we can not support multi device case yet + */ +static u64 logical_physical(u64 logical) +{ + struct btrfs_chunk_map_item item; + int slot, ret; + + item.logical = logical; + ret = bin_search(chunk_map.map, sizeof(*chunk_map.map), &item, + (cmp_func)btrfs_comp_chunk_map, 0, + chunk_map.cur_length, &slot); + if (ret == 0) + slot++; + else if (slot == 0) + return -1; + if (logical >= + chunk_map.map[slot-1].logical + chunk_map.map[slot-1].length) + return -1; + return chunk_map.map[slot-1].physical + logical - + chunk_map.map[slot-1].logical; +} + +/* raw read from disk, offset and count are bytes */ +static int raw_read(char *buf, u64 offset, u64 count) +{ + struct disk *disk = fs->fs_dev->disk; + size_t max = RAW_BUF_SIZE >> disk->sector_shift; + size_t off, cnt, done, total; + sector_t sec; + + total = count; + while (count > 0) { + sec = offset >> disk->sector_shift; + off = offset - (sec << disk->sector_shift); + done = disk->rdwr_sectors(disk, raw_buf, sec, max, 0); + if (done == 0)/* no data */ + break; + cnt = (done << disk->sector_shift) - off; + if (cnt > count) + cnt = count; + memcpy(buf, raw_buf + off, cnt); + count -= cnt; + buf += cnt; + offset += cnt; + if (done != max)/* no enough sectors */ + break; + } + return total - count; +} + +/* cache read from disk, offset and count are bytes */ +static int cache_read(char *buf, u64 offset, u64 count) +{ + size_t block_size = fs->fs_dev->cache_block_size; + struct cache_struct *cs; + size_t off, cnt, total; + block_t block; + + total = count; + while (count > 0) { + block = offset / block_size; + off = offset % block_size; + cs = get_cache_block(fs->fs_dev, block); + if (cs == NULL)/* no data */ + break; + cnt = block_size - off; + if (cnt > count) + cnt = count; + memcpy(buf, cs->data + off, cnt); + count -= cnt; + buf += cnt; + offset += cnt; + } + return total - count; +} + +static int btrfs_read(char *buf, u64 offset, u64 count) +{ + if (cache_ready) + return cache_read(buf, offset, count); + return raw_read(buf, offset, count); +} + +/* btrfs has several super block mirrors, need to calculate their location */ +static inline u64 btrfs_sb_offset(int mirror) +{ + u64 start = 16 * 1024; + if (mirror) + return start << (BTRFS_SUPER_MIRROR_SHIFT * mirror); + return BTRFS_SUPER_INFO_OFFSET; +} + +/* find the most recent super block */ +static void btrfs_read_super_block() +{ + int i; + int ret; + u8 fsid[BTRFS_FSID_SIZE]; + u64 offset; + u64 transid = 0; + struct btrfs_super_block buf; + + /* find most recent super block */ + for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) { + offset = btrfs_sb_offset(i); + ret = btrfs_read((char *)&buf, offset, sizeof(buf)); + if (ret < sizeof(buf)) + break; + + if (buf.bytenr != offset || + strncmp((char *)(&buf.magic), BTRFS_MAGIC, + sizeof(buf.magic))) + continue; + + if (i == 0) + memcpy(fsid, buf.fsid, sizeof(fsid)); + else if (memcmp(fsid, buf.fsid, sizeof(fsid))) + continue; + + if (buf.generation > transid) { + memcpy(&sb, &buf, sizeof(sb)); + transid = buf.generation; + } + } +} + +static inline unsigned long btrfs_chunk_item_size(int num_stripes) +{ + return sizeof(struct btrfs_chunk) + + sizeof(struct btrfs_stripe) * (num_stripes - 1); +} + +static void clear_path(struct btrfs_path *path) +{ + memset(path, 0, sizeof(*path)); +} + +static int btrfs_comp_keys(struct btrfs_disk_key *k1, struct btrfs_disk_key *k2) +{ + if (k1->objectid > k2->objectid) + return 1; + if (k1->objectid < k2->objectid) + return -1; + if (k1->type > k2->type) + return 1; + if (k1->type < k2->type) + return -1; + if (k1->offset > k2->offset) + return 1; + if (k1->offset < k2->offset) + return -1; + return 0; +} + +/* seach tree directly on disk ... */ +static int search_tree(u64 loffset, struct btrfs_disk_key *key, + struct btrfs_path *path) +{ + u8 buf[BTRFS_MAX_LEAF_SIZE]; + struct btrfs_header *header = (struct btrfs_header *)buf; + struct btrfs_node *node = (struct btrfs_node *)buf; + struct btrfs_leaf *leaf = (struct btrfs_leaf *)buf; + int slot, ret; + u64 offset; + + offset = logical_physical(loffset); + btrfs_read((char *)header, offset, sizeof(*header)); + if (header->level) {/*node*/ + btrfs_read((char *)&node->ptrs[0], offset + sizeof(*header), + sb.nodesize - sizeof(*header)); + path->itemsnr[header->level] = header->nritems; + path->offsets[header->level] = loffset; + ret = bin_search(&node->ptrs[0], sizeof(struct btrfs_key_ptr), + key, (cmp_func)btrfs_comp_keys, + path->slots[header->level], header->nritems, &slot); + if (ret && slot > path->slots[header->level]) + slot--; + path->slots[header->level] = slot; + ret = search_tree(node->ptrs[slot].blockptr, key, path); + } else {/*leaf*/ + btrfs_read((char *)&leaf->items, offset + sizeof(*header), + sb.leafsize - sizeof(*header)); + path->itemsnr[header->level] = header->nritems; + path->offsets[0] = loffset; + ret = bin_search(&leaf->items[0], sizeof(struct btrfs_item), + key, (cmp_func)btrfs_comp_keys, path->slots[0], + header->nritems, &slot); + if (ret && slot > path->slots[header->level]) + slot--; + path->slots[0] = slot; + path->item = leaf->items[slot]; + btrfs_read((char *)&path->data, + offset + sizeof(*header) + leaf->items[slot].offset, + leaf->items[slot].size); + } + return ret; +} + +/* return 0 if leaf found */ +static int next_leaf(struct btrfs_disk_key *key, struct btrfs_path *path) +{ + int slot; + int level = 1; + + while (level < BTRFS_MAX_LEVEL) { + if (!path->itemsnr[level]) /* no more nodes */ + return 1; + slot = path->slots[level] + 1; + if (slot >= path->itemsnr[level]) { + level++; + continue;; + } + path->slots[level] = slot; + path->slots[level-1] = 0; /* reset low level slots info */ + search_tree(path->offsets[level], key, path); + break; + } + if (level == BTRFS_MAX_LEVEL) + return 1; + return 0; +} + +/* return 0 if slot found */ +static int next_slot(struct btrfs_disk_key *key, struct btrfs_path *path) +{ + int slot; + + if (!path->itemsnr[0]) + return 1; + slot = path->slots[0] + 1; + if (slot >= path->itemsnr[0]) + return 1; + path->slots[0] = slot; + search_tree(path->offsets[0], key, path); + return 0; +} + +/* + * read chunk_array in super block + */ +static void btrfs_read_sys_chunk_array() +{ + struct btrfs_chunk_map_item item; + struct btrfs_disk_key *key; + struct btrfs_chunk *chunk; + int cur; + + /* read chunk array in superblock */ + cur = 0; + while (cur < sb.sys_chunk_array_size) { + key = (struct btrfs_disk_key *)(sb.sys_chunk_array + cur); + cur += sizeof(*key); + chunk = (struct btrfs_chunk *)(sb.sys_chunk_array + cur); + cur += btrfs_chunk_item_size(chunk->num_stripes); + /* insert to mapping table, ignore multi stripes */ + item.logical = key->offset; + item.length = chunk->length; + item.devid = chunk->stripe.devid; + item.physical = chunk->stripe.offset;/*ignore other stripes */ + insert_map(&item); + } +} + +/* read chunk items from chunk_tree and insert them to chunk map */ +static void btrfs_read_chunk_tree() +{ + struct btrfs_disk_key search_key; + struct btrfs_chunk *chunk; + struct btrfs_chunk_map_item item; + struct btrfs_path path; + + if (!(sb.flags & BTRFS_SUPER_FLAG_METADUMP)) { + if (sb.num_devices > 1) + printf("warning: only support single device btrfs\n"); + /* read chunk from chunk_tree */ + search_key.objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID; + search_key.type = BTRFS_CHUNK_ITEM_KEY; + search_key.offset = 0; + clear_path(&path); + search_tree(sb.chunk_root, &search_key, &path); + do { + do { + if (path.item.key.objectid != + BTRFS_FIRST_CHUNK_TREE_OBJECTID) + break; + chunk = (struct btrfs_chunk *)(path.data); + /* insert to mapping table, ignore stripes */ + item.logical = path.item.key.offset; + item.length = chunk->length; + item.devid = chunk->stripe.devid; + item.physical = chunk->stripe.offset; + insert_map(&item); + } while (!next_slot(&search_key, &path)); + if (path.item.key.objectid != + BTRFS_FIRST_CHUNK_TREE_OBJECTID) + break; + } while (!next_leaf(&search_key, &path)); + } +} + +static inline u64 btrfs_name_hash(const char *name, int len) +{ + return btrfs_crc32c((u32)~1, name, len); +} + +/* search a file with full path in fs_tree, do not support ../ ./ style path */ +static int btrfs_search_fs_tree(const char *fullpath, u64 *offset, + u64 *size, u8 *type) +{ + char name[256]; + char *tmp = name; + u64 objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID; + int ret; + struct btrfs_disk_key search_key; + struct btrfs_path path; + struct btrfs_dir_item dir_item; + struct btrfs_inode_item inode_item; + struct btrfs_file_extent_item extent_item; + + *tmp = '\0'; + while (1) { + char c = *(fullpath++); + + *(tmp++) = c; + if (!c) + break; + if (c == '/') { + *(tmp-1) = '\0'; + if (strlen(name)) {/* a "real" dir */ + search_key.objectid = objectid; + search_key.type = BTRFS_DIR_ITEM_KEY; + search_key.offset = + btrfs_name_hash(name, strlen(name)); + clear_path(&path); + ret = search_tree(fs_tree, &search_key, &path); + if (ret) + return ret; /* not found */ + dir_item = *(struct btrfs_dir_item *)path.data; + /* found the name but it is not a dir ? */ + if (dir_item.type != BTRFS_FT_DIR) { + printf("%s is not a dir\n", name); + return -1; + } + objectid = dir_item.location.objectid; + } + tmp = name; + *tmp = '\0'; + } + } + /* get file dir_item */ + if (!strlen(name))/* no file part */ + return -1; + search_key.objectid = objectid; + search_key.type = BTRFS_DIR_ITEM_KEY; + search_key.offset = btrfs_name_hash(name, strlen(name)); + clear_path(&path); + ret = search_tree(fs_tree, &search_key, &path); + if (ret) + return ret; /* not found */ + dir_item = *(struct btrfs_dir_item *)path.data; + *type = dir_item.type; + /* found the name but it is not a file ? */ + if (*type != BTRFS_FT_REG_FILE && *type != BTRFS_FT_SYMLINK) { + printf("%s is not a file\n", name); + return -1; + } + + /* get inode */ + search_key = dir_item.location; + clear_path(&path); + ret = search_tree(fs_tree, &search_key, &path); + if (ret) + return ret; /* not found */ + inode_item = *(struct btrfs_inode_item *)path.data; + + /* get file_extent_item */ + search_key.objectid = dir_item.location.objectid; + search_key.type = BTRFS_EXTENT_DATA_KEY; + search_key.offset = 0; + clear_path(&path); + ret = search_tree(fs_tree, &search_key, &path); + if (ret) + return ret; /* not found */ + extent_item = *(struct btrfs_file_extent_item *)path.data; + *size = inode_item.size; + if (extent_item.type == BTRFS_FILE_EXTENT_INLINE)/* inline file */ + *offset = path.offsets[0] + sizeof(struct btrfs_header) + + path.item.offset + + offsetof(struct btrfs_file_extent_item, disk_bytenr); + else + *offset = extent_item.disk_bytenr; + + return 0; +} + +static struct open_file_t *alloc_file(void) +{ + struct open_file_t *file = Files; + int i; + + for (i = 0; i < MAX_OPEN; i++) { + if (file->devid == 0) /* found it */ + return file; + file++; + } + + return NULL; /* not found */ +} + +static inline void close_pvt(struct open_file_t *of) +{ + of->devid = 0; +} + +static void btrfs_close_file(struct file *file) +{ + close_pvt(file->open_file); +} + +static void btrfs_searchdir(char *filename, struct file *file) +{ + struct open_file_t *open_file; + u64 offset, size; + char name[FILENAME_MAX]; + char *fname = filename; + u8 type; + int ret; + + file->open_file = NULL; + file->file_len = 0; + do { + ret = btrfs_search_fs_tree(fname, &offset, &size, &type); + if (ret) + break; + if (type == BTRFS_FT_SYMLINK) { + btrfs_read(name, logical_physical(offset), size); + name[size] = '\0'; + fname = name; + continue; + } + open_file = alloc_file(); + file->open_file = (void *)open_file; + if (open_file) { + /* we may support multi devices later on */ + open_file->devid = 1; + open_file->bytenr = offset; + open_file->pos = 0; + file->file_len = size; + } + break; + } while (1); +} + +/* Load the config file, return 1 if failed, or 0 */ +static int btrfs_load_config(void) +{ + char *config_name = "extlinux.conf";/* use same config name as ext2 too? */ + com32sys_t regs; + + strcpy(ConfigName, config_name); + *(uint16_t *)CurrentDirName = ROOT_DIR_WORD; + + memset(®s, 0, sizeof regs); + regs.edi.w[0] = OFFS_WRT(ConfigName, 0); + call16(core_open, ®s, ®s); + + return !!(regs.eflags.l & EFLAGS_ZF); +} + +static uint32_t btrfs_getfssec(struct file *gfile, char *buf, int sectors, + bool *have_more) +{ + struct disk *disk = fs->fs_dev->disk; + struct open_file_t *file = gfile->open_file; + u32 sec_shift = fs->fs_dev->disk->sector_shift; + u32 phy = logical_physical(file->bytenr + file->pos); + u32 sec = phy >> sec_shift; + u32 off = phy - (sec << sec_shift); + u32 remain = gfile->file_len - file->pos; + u32 remain_sec = (remain + (1 << sec_shift) - 1) >> sec_shift; + u32 size; + + if (sectors > remain_sec) + sectors = remain_sec; + /* btrfs extent is continus */ + disk->rdwr_sectors(disk, buf, sec, sectors, 0); + size = sectors << sec_shift; + if (size > remain) + size = remain; + file->pos += size; + *have_more = remain - size; + + if (off)/* inline file is not started with sector boundary */ + memcpy(buf, buf + off, size); + + return size; +} + +static void btrfs_get_fs_tree(void) +{ + struct btrfs_disk_key search_key; + struct btrfs_path path; + struct btrfs_root_item *tree; + + /* find fs_tree from tree_root */ + search_key.objectid = BTRFS_FS_TREE_OBJECTID; + search_key.type = BTRFS_ROOT_ITEM_KEY; + search_key.offset = -1; + clear_path(&path); + search_tree(sb.root, &search_key, &path); + tree = (struct btrfs_root_item *)path.data; + fs_tree = tree->bytenr; +} + +/* init. the fs meta data, return the block size shift bits. */ +static int btrfs_fs_init(struct fs_info *_fs) +{ + fs = _fs; + btrfs_read_super_block(); + if (strncmp((char *)(&sb.magic), BTRFS_MAGIC, sizeof(sb.magic))) + return -1; + btrfs_read_sys_chunk_array(); + btrfs_read_chunk_tree(); + btrfs_get_fs_tree(); + cache_ready = 1; + return BTRFS_BLOCK_SHIFT;/* to determine cache size */ +} + +const struct fs_ops btrfs_fs_ops = { + .fs_name = "btrfs", + .fs_flags = 0, + .fs_init = btrfs_fs_init, + .searchdir = btrfs_searchdir, + .getfssec = btrfs_getfssec, + .close_file = btrfs_close_file, + .mangle_name = generic_mangle_name, + .unmangle_name = generic_unmangle_name, + .load_config = btrfs_load_config +}; diff --git a/core/fs/btrfs/btrfs.h b/core/fs/btrfs/btrfs.h new file mode 100644 index 00000000..9ee90926 --- /dev/null +++ b/core/fs/btrfs/btrfs.h @@ -0,0 +1,275 @@ +#ifndef _BTRFS_H_ +#define _BTRFS_H_ + +#include <stdint.h> +#include <zconf.h> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +/* type that store on disk, but it is same as cpu type for i386 arch */ +typedef u16 __le16; +typedef u32 __le32; +typedef u64 __le64; + +#include "crc32c.h" +#define btrfs_crc32c crc32c_le + +#define BTRFS_SUPER_INFO_OFFSET (64 * 1024) +#define BTRFS_SUPER_INFO_SIZE 4096 +#define BTRFS_MAX_LEAF_SIZE 4096 +#define BTRFS_BLOCK_SHIFT 12 + +#define BTRFS_SUPER_MIRROR_MAX 3 +#define BTRFS_SUPER_MIRROR_SHIFT 12 +#define BTRFS_CSUM_SIZE 32 +#define BTRFS_FSID_SIZE 16 +#define BTRFS_LABEL_SIZE 256 +#define BTRFS_SYSTEM_CHUNK_ARRAY_SIZE 2048 +#define BTRFS_UUID_SIZE 16 + +#define BTRFS_MAGIC "_BHRfS_M" + +#define BTRFS_SUPER_FLAG_METADUMP (1ULL << 33) + +#define BTRFS_DEV_ITEM_KEY 216 +#define BTRFS_CHUNK_ITEM_KEY 228 +#define BTRFS_ROOT_ITEM_KEY 132 +#define BTRFS_EXTENT_DATA_KEY 108 +#define BTRFS_DIR_ITEM_KEY 84 + +#define BTRFS_EXTENT_TREE_OBJECTID 2ULL +#define BTRFS_FS_TREE_OBJECTID 5ULL + +#define BTRFS_FIRST_CHUNK_TREE_OBJECTID 256ULL + +#define BTRFS_FILE_EXTENT_INLINE 0 +#define BTRFS_FILE_EXTENT_REG 1 +#define BTRFS_FILE_EXTENT_PREALLOC 2 + +#define BTRFS_MAX_LEVEL 8 +#define BTRFS_MAX_CHUNK_ENTRIES 256 + +#define BTRFS_FT_REG_FILE 1 +#define BTRFS_FT_DIR 2 +#define BTRFS_FT_SYMLINK 7 + +#define ROOT_DIR_WORD 0x002f + +struct btrfs_dev_item { + __le64 devid; + __le64 total_bytes; + __le64 bytes_used; + __le32 io_align; + __le32 io_width; + __le32 sector_size; + __le64 type; + __le64 generation; + __le64 start_offset; + __le32 dev_group; + u8 seek_speed; + u8 bandwidth; + u8 uuid[BTRFS_UUID_SIZE]; + u8 fsid[BTRFS_UUID_SIZE]; +} __attribute__ ((__packed__)); + +struct btrfs_super_block { + u8 csum[BTRFS_CSUM_SIZE]; + /* the first 3 fields must match struct btrfs_header */ + u8 fsid[BTRFS_FSID_SIZE]; /* FS specific uuid */ + __le64 bytenr; /* this block number */ + __le64 flags; + + /* allowed to be different from the btrfs_header from here own down */ + __le64 magic; + __le64 generation; + __le64 root; + __le64 chunk_root; + __le64 log_root; + + /* this will help find the new super based on the log root */ + __le64 log_root_transid; + __le64 total_bytes; + __le64 bytes_used; + __le64 root_dir_objectid; + __le64 num_devices; + __le32 sectorsize; + __le32 nodesize; + __le32 leafsize; + __le32 stripesize; + __le32 sys_chunk_array_size; + __le64 chunk_root_generation; + __le64 compat_flags; + __le64 compat_ro_flags; + __le64 incompat_flags; + __le16 csum_type; + u8 root_level; + u8 chunk_root_level; + u8 log_root_level; + struct btrfs_dev_item dev_item; + + char label[BTRFS_LABEL_SIZE]; + + /* future expansion */ + __le64 reserved[32]; + u8 sys_chunk_array[BTRFS_SYSTEM_CHUNK_ARRAY_SIZE]; +} __attribute__ ((__packed__)); + +struct btrfs_disk_key { + __le64 objectid; + u8 type; + __le64 offset; +} __attribute__ ((__packed__)); + +struct btrfs_stripe { + __le64 devid; + __le64 offset; + u8 dev_uuid[BTRFS_UUID_SIZE]; +} __attribute__ ((__packed__)); + +struct btrfs_chunk { + __le64 length; + __le64 owner; + __le64 stripe_len; + __le64 type; + __le32 io_align; + __le32 io_width; + __le32 sector_size; + __le16 num_stripes; + __le16 sub_stripes; + struct btrfs_stripe stripe; +} __attribute__ ((__packed__)); + +struct btrfs_header { + /* these first four must match the super block */ + u8 csum[BTRFS_CSUM_SIZE]; + u8 fsid[BTRFS_FSID_SIZE]; /* FS specific uuid */ + __le64 bytenr; /* which block this node is supposed to live in */ + __le64 flags; + + /* allowed to be different from the super from here on down */ + u8 chunk_tree_uuid[BTRFS_UUID_SIZE]; + __le64 generation; + __le64 owner; + __le32 nritems; + u8 level; +} __attribute__ ((__packed__)); + +struct btrfs_item { + struct btrfs_disk_key key; + __le32 offset; + __le32 size; +} __attribute__ ((__packed__)); + +struct btrfs_leaf { + struct btrfs_header header; + struct btrfs_item items[]; +} __attribute__ ((__packed__)); + +struct btrfs_key_ptr { + struct btrfs_disk_key key; + __le64 blockptr; + __le64 generation; +} __attribute__ ((__packed__)); + +struct btrfs_node { + struct btrfs_header header; + struct btrfs_key_ptr ptrs[]; +} __attribute__ ((__packed__)); + +/* remember how we get to a node/leaf */ +struct btrfs_path { + u64 offsets[BTRFS_MAX_LEVEL]; + int itemsnr[BTRFS_MAX_LEVEL]; + int slots[BTRFS_MAX_LEVEL]; + /* remember last slot's item and data */ + struct btrfs_item item; + u8 data[BTRFS_MAX_LEAF_SIZE]; +}; + +/* store logical offset to physical offset mapping */ +struct btrfs_chunk_map_item { + u64 logical; + u64 length; + u64 devid; + u64 physical; +}; + +struct btrfs_chunk_map { + struct btrfs_chunk_map_item map[BTRFS_MAX_CHUNK_ENTRIES]; + u32 map_length; + u32 cur_length; +}; + +struct btrfs_timespec { + __le64 sec; + __le32 nsec; +} __attribute__ ((__packed__)); + +struct btrfs_inode_item { + /* nfs style generation number */ + __le64 generation; + /* transid that last touched this inode */ + __le64 transid; + __le64 size; + __le64 nbytes; + __le64 block_group; + __le32 nlink; + __le32 uid; + __le32 gid; + __le32 mode; + __le64 rdev; + __le64 flags; + + /* modification sequence number for NFS */ + __le64 sequence; + + /* + * a little future expansion, for more than this we can + * just grow the inode item and version it + */ + __le64 reserved[4]; + struct btrfs_timespec atime; + struct btrfs_timespec ctime; + struct btrfs_timespec mtime; + struct btrfs_timespec otime; +} __attribute__ ((__packed__)); + +struct btrfs_root_item { + struct btrfs_inode_item inode; + __le64 generation; + __le64 root_dirid; + __le64 bytenr; + __le64 byte_limit; + __le64 bytes_used; + __le64 last_snapshot; + __le64 flags; + __le32 refs; + struct btrfs_disk_key drop_progress; + u8 drop_level; + u8 level; +} __attribute__ ((__packed__)); + +struct btrfs_dir_item { + struct btrfs_disk_key location; + __le64 transid; + __le16 data_len; + __le16 name_len; + u8 type; +} __attribute__ ((__packed__)); + +struct btrfs_file_extent_item { + __le64 generation; + __le64 ram_bytes; + u8 compression; + u8 encryption; + __le16 other_encoding; /* spare for later use */ + u8 type; + __le64 disk_bytenr; + __le64 disk_num_bytes; + __le64 offset; + __le64 num_bytes; +} __attribute__ ((__packed__)); + +#endif diff --git a/core/fs/btrfs/crc32c.h b/core/fs/btrfs/crc32c.h new file mode 100644 index 00000000..44960df2 --- /dev/null +++ b/core/fs/btrfs/crc32c.h @@ -0,0 +1,101 @@ +/* + * Copied from Linux kernel crypto/crc32c.c + * Copyright (c) 2004 Cisco Systems, Inc. + * Copyright (c) 2008 Herbert Xu <herbert@gondor.apana.org.au> + * + * This program 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 2 of the License, or (at your option) + * any later version. + * + */ + +/* + * This is the CRC-32C table + * Generated with: + * width = 32 bits + * poly = 0x1EDC6F41 + * reflect input bytes = true + * reflect output bytes = true + */ + +static const u32 crc32c_table[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +/* + * Steps through buffer one byte at at time, calculates reflected + * crc using table. + */ + +inline u32 crc32c_le(u32 crc, const char *data, size_t length) +{ + while (length--) + crc = + crc32c_table[(crc ^ *data++) & 0xFFL] ^ (crc >> 8); + + return crc; +} diff --git a/core/fs/ext2/ext2.c b/core/fs/ext2/ext2.c index 8c44b3ae..dde5571e 100644 --- a/core/fs/ext2/ext2.c +++ b/core/fs/ext2/ext2.c @@ -706,6 +706,9 @@ static int ext2_fs_init(struct fs_info *fs) /* read the super block */ disk->rdwr_sectors(disk, &sb, 2, 2, 0); + /* check if it is ext2, since we also support btrfs now */ + if (sb.s_magic != EXT2_SUPER_MAGIC) + return -1; ClustByteShift = sb.s_log_block_size + 10; ClustSize = 1 << ClustByteShift; ClustShift = ClustByteShift - SECTOR_SHIFT; diff --git a/core/ldlinux.asm b/core/ldlinux.asm index 20f0ee94..599d760a 100644 --- a/core/ldlinux.asm +++ b/core/ldlinux.asm @@ -31,6 +31,8 @@ my_id equ syslinux_id extern vfat_fs_ops -ROOT_FS_OPS equ vfat_fs_ops + section .rodata +ROOT_FS_OPS dd vfat_fs_ops + dd 0 %include "diskfs.inc" diff --git a/extlinux/btrfs.h b/extlinux/btrfs.h new file mode 100644 index 00000000..39a861a5 --- /dev/null +++ b/extlinux/btrfs.h @@ -0,0 +1,22 @@ +#ifndef _BTRFS_H_ +#define _BTRFS_H_ + +#define BTRFS_SUPER_MAGIC 0x9123683E +#define BTRFS_SUPER_INFO_OFFSET (64 * 1024) +#define BTRFS_SUPER_INFO_SIZE 4096 +#define BTRFS_MAGIC "_BHRfS_M" +#define BTRFS_CSUM_SIZE 32 +#define BTRFS_FSID_SIZE 16 + +struct btrfs_super_block { + unsigned char csum[BTRFS_CSUM_SIZE]; + /* the first 3 fields must match struct btrfs_header */ + unsigned char fsid[BTRFS_FSID_SIZE]; /* FS specific uuid */ + u64 bytenr; /* this block number */ + u64 flags; + + /* allowed to be different from the btrfs_header from here own down */ + u64 magic; +} __attribute__ ((__packed__)); + +#endif diff --git a/extlinux/main.c b/extlinux/main.c index 79ead181..7eb59dae 100644 --- a/extlinux/main.c +++ b/extlinux/main.c @@ -14,7 +14,7 @@ /* * extlinux.c * - * Install the extlinux boot block on an ext2/3/4 filesystem + * Install the extlinux boot block on an ext2/3/4 and btrfs filesystem */ #define _GNU_SOURCE /* Enable everything */ @@ -29,6 +29,7 @@ typedef uint64_t u64; #ifndef __KLIBC__ #include <mntent.h> #endif +#include <stdbool.h> #include <stdlib.h> #include <string.h> #include <getopt.h> @@ -46,6 +47,7 @@ typedef uint64_t u64; #undef statfs #include "ext2_fs.h" +#include "btrfs.h" #include "../version.h" #include "syslxint.h" @@ -56,6 +58,10 @@ typedef uint64_t u64; #endif /* Global option handling */ +/* Global fs_type for handling ext2/3/4 vs btrfs */ +#define EXT2 1 +#define BTRFS 2 +int fs_type; const char *program; @@ -130,6 +136,9 @@ static const char short_options[] = "iUuzS:H:rvho:O"; #define EXT2_SUPER_OFFSET 1024 #endif +/* the btrfs partition first 64K blank area is used to store boot sector and + boot image, the boot sector is from 0~512, the boot image starts at 2K */ +#define BTRFS_EXTLINUX_OFFSET (2*1024) /* * Boot block */ @@ -359,10 +368,11 @@ int patch_file_and_bootblock(int fd, int dirfd, int devfd) uint32_t csum; int secptroffset; - if (fstat(dirfd, &dirst)) { - perror("fstat dirfd"); - exit(255); /* This should never happen */ - } + if (fs_type == EXT2) + if (fstat(dirfd, &dirst)) { + perror("fstat dirfd"); + exit(255); /* This should never happen */ + } totalbytes = get_size(devfd); get_geometry(devfd, totalbytes, &geo); @@ -406,14 +416,20 @@ int patch_file_and_bootblock(int fd, int dirfd, int devfd) nsect = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT; nsect += 2; /* Two sectors for the ADV */ sectp = alloca(sizeof(uint32_t) * nsect); - if (sectmap(fd, sectp, nsect)) { - perror("bmap"); - exit(1); + if (fs_type == EXT2) { + if (sectmap(fd, sectp, nsect)) { + perror("bmap"); + exit(1); + } + } else if (fs_type == BTRFS) { + int i; + + for (i = 0; i < nsect; i++) + *(sectp + i) = BTRFS_EXTLINUX_OFFSET/SECTOR_SIZE + i; } /* First sector need pointer in boot sector */ set_32(&bs->NextSector, *sectp++); - /* Stupid mode? */ if (opt.stupid_mode) set_16(&bs->MaxTransfer, 1); @@ -455,13 +471,21 @@ int patch_file_and_bootblock(int fd, int dirfd, int devfd) * Returns -1 on fatal errors, 0 if ADV is okay, and 1 if no valid * ADV was found. */ -int read_adv(const char *path) +int read_adv(const char *path, int devfd) { char *file; int fd = -1; struct stat st; int err = 0; + if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */ + if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE, + BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) { + perror("writing adv"); + return 1; + } + return 0; + } asprintf(&file, "%s%sextlinux.sys", path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/"); @@ -505,7 +529,7 @@ int read_adv(const char *path) /* * Update the ADV in an existing installation. */ -int write_adv(const char *path) +int write_adv(const char *path, int devfd) { unsigned char advtmp[2 * ADV_SIZE]; char *file; @@ -514,6 +538,14 @@ int write_adv(const char *path) int err = 0; int flags, nflags; + if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */ + if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE, + BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) { + perror("writing adv"); + return 1; + } + return 0; + } asprintf(&file, "%s%sextlinux.sys", path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/"); @@ -611,17 +643,30 @@ int modify_adv(void) int install_bootblock(int fd, const char *device) { struct ext2_super_block sb; + struct btrfs_super_block sb2; + bool ok = false; - if (xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb) { - perror("reading superblock"); - return 1; + if (fs_type == EXT2) { + if (xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb) { + perror("reading superblock"); + return 1; + } + if (sb.s_magic == EXT2_SUPER_MAGIC) + ok = true; + } else if (fs_type == BTRFS) { + if (xpread(fd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET) + != sizeof sb2) { + perror("reading superblock"); + return 1; + } + if (sb2.magic == *(u64 *)BTRFS_MAGIC) + ok = true; } - - if (sb.s_magic != EXT2_SUPER_MAGIC) { - fprintf(stderr, "no ext2/3/4 superblock found on %s\n", device); + if (!ok) { + fprintf(stderr, "no ext2/3/4 or btrfs superblock found on %s\n", + device); return 1; } - if (xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len) { perror("writing bootblock"); return 1; @@ -630,7 +675,7 @@ int install_bootblock(int fd, const char *device) return 0; } -int install_file(const char *path, int devfd, struct stat *rst) +int ext2_install_file(const char *path, int devfd, struct stat *rst) { char *file; int fd = -1, dirfd = -1, flags; @@ -721,6 +766,40 @@ bail: return 1; } +/* btrfs has to install the extlinux.sys in the first 64K blank area, which + is not managered by btrfs tree, so actually this is not installed as files. + since the cow feature of btrfs will move the extlinux.sys every where */ +int btrfs_install_file(const char *path, int devfd, struct stat *rst) +{ + patch_file_and_bootblock(-1, -1, devfd); + if (xpwrite(devfd, boot_image, boot_image_len, BTRFS_EXTLINUX_OFFSET) + != boot_image_len) { + perror("writing bootblock"); + return 1; + } + printf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET); + if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE, + BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) { + perror("writing adv"); + return 1; + } + printf("write adv to 0x%x\n", BTRFS_EXTLINUX_OFFSET + boot_image_len); + if (stat(path, rst)) { + perror(path); + return 1; + } + return 0; +} + +int install_file(const char *path, int devfd, struct stat *rst) +{ + if (fs_type == EXT2) + return ext2_install_file(path, devfd, rst); + else if (fs_type == BTRFS) + return btrfs_install_file(path, devfd, rst); + return 1; +} + /* EXTLINUX installs the string 'EXTLINUX' at offset 3 in the boot sector; this is consistent with FAT filesystems. */ int already_installed(int devfd) @@ -745,10 +824,13 @@ static void device_cleanup(void) static int validate_device(const char *path, int devfd) { struct stat pst, dst; + struct statfs sfs; - if (stat(path, &pst) || fstat(devfd, &dst)) + if (stat(path, &pst) || fstat(devfd, &dst) || statfs(path, &sfs)) return -1; - + /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */ + if (fs_type == BTRFS && sfs.f_type == BTRFS_SUPER_MAGIC) + return 0; return (pst.st_dev == dst.st_rdev) ? 0 : -1; } @@ -759,18 +841,35 @@ static const char *find_device(const char *mtab_file, dev_t dev) struct stat dst; FILE *mtab; const char *devname = NULL; + bool done; mtab = setmntent(mtab_file, "r"); if (!mtab) return NULL; + done = false; while ((mnt = getmntent(mtab))) { - if ((!strcmp(mnt->mnt_type, "ext2") || - !strcmp(mnt->mnt_type, "ext3") || - !strcmp(mnt->mnt_type, "ext4")) && - !stat(mnt->mnt_fsname, &dst) && dst.st_rdev == dev) { - devname = strdup(mnt->mnt_fsname); - break; + /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */ + switch (fs_type) { + case BTRFS: + if (!strcmp(mnt->mnt_type, "btrfs") && + !stat(mnt->mnt_dir, &dst) && + dst.st_dev == dev) + done = true; + break; + case EXT2: + if ((!strcmp(mnt->mnt_type, "ext2") || + !strcmp(mnt->mnt_type, "ext3") || + !strcmp(mnt->mnt_type, "ext4")) && + !stat(mnt->mnt_fsname, &dst) && + dst.st_rdev == dev) { + done = true; + break; + } + } + if (done) { + devname = strdup(mnt->mnt_fsname); + break; } } endmntent(mtab); @@ -779,30 +878,20 @@ static const char *find_device(const char *mtab_file, dev_t dev) } #endif -int install_loader(const char *path, int update_only) +static const char *get_devname(const char *path) { - struct stat st, fst; - int devfd, rv; const char *devname = NULL; + struct stat st; struct statfs sfs; if (stat(path, &st) || !S_ISDIR(st.st_mode)) { fprintf(stderr, "%s: Not a directory: %s\n", program, path); - return 1; + return devname; } - if (statfs(path, &sfs)) { fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno)); - return 1; - } - - if (sfs.f_type != EXT2_SUPER_MAGIC) { - fprintf(stderr, "%s: not an ext2/3/4 filesystem: %s\n", program, path); - return 1; + return devname; } - - devfd = -1; - #ifdef __KLIBC__ /* klibc doesn't have getmntent and friends; instead, just create @@ -812,7 +901,7 @@ int install_loader(const char *path, int update_only) if (mknod(devname_buf, S_IFBLK | 0600, st.st_dev)) { fprintf(stderr, "%s: cannot create device %s\n", program, devname); - return 1; + return devname; } atexit(device_cleanup); /* unlink the device node on exit */ @@ -827,46 +916,99 @@ int install_loader(const char *path, int update_only) } if (!devname) { fprintf(stderr, "%s: cannot find device for path %s\n", program, path); - return 1; + return devname; } fprintf(stderr, "%s is device %s\n", path, devname); #endif + return devname; +} + +static int open_device(const char *path, struct stat *st, const char **_devname) +{ + int devfd; + const char *devname = NULL; + struct statfs sfs; + + if (st) + if (stat(path, st) || !S_ISDIR(st->st_mode)) { + fprintf(stderr, "%s: Not a directory: %s\n", program, path); + return -1; + } + + if (statfs(path, &sfs)) { + fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno)); + return -1; + } + if (sfs.f_type == EXT2_SUPER_MAGIC) + fs_type = EXT2; + else if (sfs.f_type == BTRFS_SUPER_MAGIC) + fs_type = BTRFS; + + if (!fs_type) { + fprintf(stderr, "%s: not an ext2/3/4 or btrfs filesystem: %s\n", + program, path); + return -1; + } + + devfd = -1; + devname = get_devname(path); + if (_devname) + *_devname = devname; if ((devfd = open(devname, O_RDWR | O_SYNC)) < 0) { fprintf(stderr, "%s: cannot open device %s\n", program, devname); - return 1; + return -1; } /* Verify that the device we opened is the device intended */ if (validate_device(path, devfd)) { fprintf(stderr, "%s: path %s doesn't match device %s\n", program, path, devname); - return 1; + close(devfd); + return -1; } + return devfd; +} + +int install_loader(const char *path, int update_only) +{ + struct stat st, fst; + int devfd, rv; + const char *devname; + + devfd = open_device(path, &st, &devname); + if (devfd < 0) + return 1; if (update_only && !already_installed(devfd)) { fprintf(stderr, "%s: no previous extlinux boot sector found\n", program); + close(devfd); return 1; } /* Read a pre-existing ADV, if already installed */ if (opt.reset_adv) syslinux_reset_adv(syslinux_adv); - else if (read_adv(path) < 0) + else if (read_adv(path, devfd) < 0) { + close(devfd); return 1; - - if (modify_adv() < 0) + } + if (modify_adv() < 0) { + close(devfd); return 1; + } /* Install extlinux.sys */ - if (install_file(path, devfd, &fst)) + if (install_file(path, devfd, &fst)) { + close(devfd); return 1; - + } if (fst.st_dev != st.st_dev) { fprintf(stderr, "%s: file system changed under us - aborting!\n", program); + close(devfd); return 1; } @@ -883,17 +1025,27 @@ int install_loader(const char *path, int update_only) */ int modify_existing_adv(const char *path) { + int devfd; + + devfd = open_device(path, NULL, NULL); + if (devfd < 0) + return 1; + if (opt.reset_adv) syslinux_reset_adv(syslinux_adv); - else if (read_adv(path) < 0) + else if (read_adv(path, devfd) < 0) { + close(devfd); return 1; - - if (modify_adv() < 0) + } + if (modify_adv() < 0) { + close(devfd); return 1; - - if (write_adv(path) < 0) + } + if (write_adv(path, devfd) < 0) { + close(devfd); return 1; - + } + close(devfd); return 0; } |