btrfs: add btrfs file system support to extlinux syslinux-4.00-pre7
authorAlek Du <alek.du@intel.com>
Thu, 17 Dec 2009 03:14:48 +0000 (11:14 +0800)
committerH. Peter Anvin <hpa@zytor.com>
Tue, 22 Dec 2009 17:46:49 +0000 (09:46 -0800)
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>
core/extlinux.asm
core/fs.c
core/fs/btrfs/btrfs.c [new file with mode: 0644]
core/fs/btrfs/btrfs.h [new file with mode: 0644]
core/fs/btrfs/crc32c.h [new file with mode: 0644]
core/fs/ext2/ext2.c
core/ldlinux.asm
extlinux/btrfs.h [new file with mode: 0644]
extlinux/main.c

index 1f5423b..c7cea51 100644 (file)
 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"
index a3407cf..4c8c153 100644 (file)
--- a/core/fs.c
+++ b/core/fs.c
@@ -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 (file)
index 0000000..2092e29
--- /dev/null
@@ -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(&regs, 0, sizeof regs);
+    regs.edi.w[0] = OFFS_WRT(ConfigName, 0);
+    call16(core_open, &regs, &regs);
+
+    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 (file)
index 0000000..9ee9092
--- /dev/null
@@ -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 (file)
index 0000000..44960df
--- /dev/null
@@ -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;
+}
index 8c44b3a..dde5571 100644 (file)
@@ -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;
index 20f0ee9..599d760 100644 (file)
@@ -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 (file)
index 0000000..39a861a
--- /dev/null
@@ -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
index 79ead18..7eb59da 100644 (file)
@@ -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;
 }