summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/diskstart.inc21
-rw-r--r--core/fs/btrfs/btrfs.c100
-rw-r--r--core/fs/btrfs/btrfs.h7
-rw-r--r--core/include/core.h1
-rw-r--r--extlinux/main.c52
-rw-r--r--libinstaller/syslxint.h2
6 files changed, 156 insertions, 27 deletions
diff --git a/core/diskstart.inc b/core/diskstart.inc
index 3bffc896..ca627b1a 100644
--- a/core/diskstart.inc
+++ b/core/diskstart.inc
@@ -494,19 +494,11 @@ CheckSum dd 0 ; Checksum starting at ldlinux_sys
; value = LDLINUX_MAGIC - [sum of dwords]
CurrentDirPtr dw CurrentDirName-LDLINUX_SYS ; Current directory name string
CurrentDirLen dw FILENAME_MAX
+SubvolPtr dw SubvolName - LDLINUX_SYS
+SubvolLen dw 64 ; Should be enough
SecPtrOffset dw SectorPtrs - LDLINUX_SYS
SecPtrCnt dw (SectorPtrsEnd - SectorPtrs) >> 2
-;
-; Installer pokes the base directory here, needs to be in .text16 so the pointer
-; address can be calculated by the assembler.
-;
-%define HAVE_CURRENTDIRNAME
- section .data16
- global CurrentDirName
-CurrentDirName times FILENAME_MAX db 0
-
- section .text16
ldlinux_ent:
;
; Note that some BIOSes are buggy and run the boot sector at 07C0:0000
@@ -699,6 +691,15 @@ SectorPtrsEnd equ $
; End of code and data that have to be in the first sector
; ----------------------------------------------------------------------------
+;
+; Installer pokes the base directory here, needs to be in .text16 so the pointer
+; address can be calculated by the assembler.
+;
+%define HAVE_CURRENTDIRNAME
+ section .data16
+ global CurrentDirName, SubvolName
+CurrentDirName times FILENAME_MAX db 0
+SubvolName times 64 db 0
section .text16
all_read:
;
diff --git a/core/fs/btrfs/btrfs.c b/core/fs/btrfs/btrfs.c
index 31e24f77..ee042601 100644
--- a/core/fs/btrfs/btrfs.c
+++ b/core/fs/btrfs/btrfs.c
@@ -266,6 +266,21 @@ static int btrfs_comp_keys(struct btrfs_disk_key *k1, struct btrfs_disk_key *k2)
return 0;
}
+/* compare keys but ignore offset, is useful to enumerate all same kind keys */
+static int btrfs_comp_keys_type(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;
+ return 0;
+}
+
/* seach tree directly on disk ... */
static int search_tree(u64 loffset, struct btrfs_disk_key *key,
struct btrfs_path *path)
@@ -394,8 +409,8 @@ static void btrfs_read_chunk_tree(void)
search_tree(sb.chunk_root, &search_key, &path);
do {
do {
- if (path.item.key.objectid !=
- BTRFS_FIRST_CHUNK_TREE_OBJECTID)
+ if (btrfs_comp_keys_type(&search_key,
+ &path.item.key))
break;
chunk = (struct btrfs_chunk *)(path.data);
/* insert to mapping table, ignore stripes */
@@ -405,8 +420,7 @@ static void btrfs_read_chunk_tree(void)
item.physical = chunk->stripe.offset;
insert_map(&item);
} while (!next_slot(&search_key, &path));
- if (path.item.key.objectid !=
- BTRFS_FIRST_CHUNK_TREE_OBJECTID)
+ if (btrfs_comp_keys_type(&search_key, &path.item.key))
break;
} while (!next_leaf(&search_key, &path));
}
@@ -417,13 +431,16 @@ 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,
+/* search a file with full path or relative path */
+static int btrfs_search_fs_tree(const char *fpath, u64 *offset,
u64 *size, u8 *type)
{
char name[256];
char *tmp = name;
- u64 objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
+ /* this can be used as cwd during the file searching */
+ static u64 objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
+ static u8 level; /* dir level */
+ static u64 objs[16]; /* history for ../ operation */
int ret;
struct btrfs_disk_key search_key;
struct btrfs_path path;
@@ -431,16 +448,26 @@ static int btrfs_search_fs_tree(const char *fullpath, u64 *offset,
struct btrfs_inode_item inode_item;
struct btrfs_file_extent_item extent_item;
+ if (*fpath == '/' || *fpath == '\0') { /* full or null path */
+ objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
+ level = 0;
+ objs[level] = objectid;
+ }
*tmp = '\0';
while (1) {
- char c = *(fullpath++);
+ char c = *(fpath++);
*(tmp++) = c;
if (!c)
break;
if (c == '/') {
*(tmp-1) = '\0';
- if (strlen(name)) {/* a "real" dir */
+ if (!strcmp(name, "..")) {
+ if (level)
+ level--;
+ objectid = objs[level];
+ } else if (strlen(name) && strcmp(name, ".")) {
+ /* a "real" dir */
search_key.objectid = objectid;
search_key.type = BTRFS_DIR_ITEM_KEY;
search_key.offset =
@@ -456,6 +483,12 @@ static int btrfs_search_fs_tree(const char *fullpath, u64 *offset,
return -1;
}
objectid = dir_item.location.objectid;
+ level++;
+ if (level >= 16) {
+ printf("too many dir levels(>16)\n");
+ return -1;
+ }
+ objs[level] = objectid;
}
tmp = name;
*tmp = '\0';
@@ -531,6 +564,16 @@ static void btrfs_close_file(struct file *file)
close_pvt(file->open_file);
}
+/* set the cwd to the global Path in patcharea */
+static void btrfs_set_cwd(void)
+{
+ u64 tmp;
+ char path[FILENAME_MAX];
+
+ sprintf(path, "%s/", CurrentDirName);
+ btrfs_search_fs_tree(path, &tmp, &tmp, (u8 *)&tmp);
+}
+
static void btrfs_searchdir(char *filename, struct file *file)
{
struct open_file_t *open_file;
@@ -563,6 +606,8 @@ static void btrfs_searchdir(char *filename, struct file *file)
}
break;
} while (1);
+ /* restore the cwd, since the above search may change dir */
+ btrfs_set_cwd();
}
/* Load the config file, return 1 if failed, or 0 */
@@ -572,7 +617,6 @@ static int btrfs_load_config(void)
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);
@@ -615,9 +659,42 @@ static void btrfs_get_fs_tree(void)
struct btrfs_disk_key search_key;
struct btrfs_path path;
struct btrfs_root_item *tree;
+ bool subvol_ok = false;
+
+ /* check if subvol is filled by installer */
+ if (*SubvolName) {
+ search_key.objectid = BTRFS_FS_TREE_OBJECTID;
+ search_key.type = BTRFS_ROOT_REF_KEY;
+ search_key.offset = 0;
+ clear_path(&path);
+ if (search_tree(sb.root, &search_key, &path))
+ next_slot(&search_key, &path);
+ do {
+ do {
+ struct btrfs_root_ref *ref;
+ if (btrfs_comp_keys_type(&search_key,
+ &path.item.key))
+ break;
+ ref = (struct btrfs_root_ref *)path.data;
+ if (!strcmp((char*)(ref + 1), SubvolName)) {
+ subvol_ok = true;
+ break;
+ }
+ } while (!next_slot(&search_key, &path));
+ if (subvol_ok)
+ break;
+ if (btrfs_comp_keys_type(&search_key, &path.item.key))
+ break;
+ } while (!next_leaf(&search_key, &path));
+ if (!subvol_ok) /* should be impossible */
+ printf("no subvol found!\n");
+ }
/* find fs_tree from tree_root */
- search_key.objectid = BTRFS_FS_TREE_OBJECTID;
+ if (subvol_ok)
+ search_key.objectid = path.item.key.offset;
+ else /* "default" volume */
+ search_key.objectid = BTRFS_FS_TREE_OBJECTID;
search_key.type = BTRFS_ROOT_ITEM_KEY;
search_key.offset = -1;
clear_path(&path);
@@ -638,6 +715,7 @@ static int btrfs_fs_init(struct fs_info *_fs)
btrfs_read_sys_chunk_array();
btrfs_read_chunk_tree();
btrfs_get_fs_tree();
+ btrfs_set_cwd();
cache_ready = 1;
return BTRFS_BLOCK_SHIFT;/* to determine cache size */
}
diff --git a/core/fs/btrfs/btrfs.h b/core/fs/btrfs/btrfs.h
index 8ec8afe3..eacdeb5f 100644
--- a/core/fs/btrfs/btrfs.h
+++ b/core/fs/btrfs/btrfs.h
@@ -35,6 +35,7 @@ typedef u64 __le64;
#define BTRFS_DEV_ITEM_KEY 216
#define BTRFS_CHUNK_ITEM_KEY 228
+#define BTRFS_ROOT_REF_KEY 156
#define BTRFS_ROOT_ITEM_KEY 132
#define BTRFS_EXTENT_DATA_KEY 108
#define BTRFS_DIR_ITEM_KEY 84
@@ -272,4 +273,10 @@ struct btrfs_file_extent_item {
__le64 num_bytes;
} __attribute__ ((__packed__));
+struct btrfs_root_ref {
+ __le64 dirid;
+ __le64 sequence;
+ __le16 name_len;
+} __attribute__ ((__packed__));
+
#endif
diff --git a/core/include/core.h b/core/include/core.h
index 1a9e1b96..f54fcf8f 100644
--- a/core/include/core.h
+++ b/core/include/core.h
@@ -8,6 +8,7 @@ extern char core_xfer_buf[65536];
extern char core_cache_buf[65536];
extern char trackbuf[];
extern char CurrentDirName[];
+extern char SubvolName[];
extern char ConfigName[];
extern char KernelName[];
diff --git a/extlinux/main.c b/extlinux/main.c
index 05b390f3..c29e3b0b 100644
--- a/extlinux/main.c
+++ b/extlinux/main.c
@@ -139,6 +139,8 @@ static const char short_options[] = "iUuzS:H:rvho:O";
/* 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)
+#define BTRFS_SUBVOL_OPT "subvol="
+static char subvol[64];
/*
* Boot block
*/
@@ -366,7 +368,7 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
struct patch_area *patcharea;
int i, dw, nptrs;
uint32_t csum;
- int secptroffset, diroffset, dirlen;
+ int secptroffset, diroffset, dirlen, subvoloffset, subvollen;
char *dirpath, *subpath;
dirpath = realpath(dir, NULL);
@@ -487,6 +489,16 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
}
strncpy((char *)boot_image + diroffset, subpath, dirlen);
free(dirpath);
+ /* write subvol info if we have */
+ if (*subvol) {
+ subvoloffset = get_16(&patcharea->subvoloffset);
+ subvollen = get_16(&patcharea->subvollen);
+ if (subvollen <= strlen(subvol)) {
+ fprintf(stderr, "Subvol name too long... aborting install!\n");
+ exit(1);
+ }
+ strncpy((char *)boot_image + subvoloffset, subvol, subvollen);
+ }
/* Now produce a checksum */
set_32(&patcharea->checksum, 0);
@@ -888,8 +900,22 @@ static const char *find_device(const char *mtab_file, dev_t dev)
case BTRFS:
if (!strcmp(mnt->mnt_type, "btrfs") &&
!stat(mnt->mnt_dir, &dst) &&
- dst.st_dev == dev)
- done = true;
+ dst.st_dev == dev) {
+ char *opt = strstr(mnt->mnt_opts, BTRFS_SUBVOL_OPT);
+
+ if (opt) {
+ if (!subvol[0]) {
+ char *tmp;
+
+ strcpy(subvol, opt + sizeof(BTRFS_SUBVOL_OPT) - 1);
+ tmp = strchr(subvol, 32);
+ if (tmp)
+ *tmp = '\0';
+ }
+ break; /* should break and let upper layer try again */
+ } else
+ done = true;
+ }
break;
case EXT2:
if ((!strcmp(mnt->mnt_type, "ext2") ||
@@ -943,10 +969,24 @@ static const char *get_devname(const char *path)
#else
- devname = find_device("/proc/mounts", st.st_dev);
+ /* check /etc/mtab first, since btrfs subvol info is only in here */
+ devname = find_device("/etc/mtab", st.st_dev);
+ if (subvol[0] && !devname) { /* we just find it is a btrfs subvol */
+ char parent[256];
+ char *tmp;
+
+ strcpy(parent, path);
+ tmp = strrchr(parent, '/');
+ if (tmp) {
+ *tmp = '\0';
+ fprintf(stderr, "%s is subvol, try its parent dir %s\n", path, parent);
+ devname = get_devname(parent);
+ } else
+ devname = NULL;
+ }
if (!devname) {
- /* Didn't find it in /proc/mounts, try /etc/mtab */
- devname = find_device("/etc/mtab", st.st_dev);
+ /* Didn't find it in /etc/mtab, try /proc/mounts */
+ devname = find_device("/proc/mounts", st.st_dev);
}
if (!devname) {
fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
diff --git a/libinstaller/syslxint.h b/libinstaller/syslxint.h
index ba3e501e..7f8d3041 100644
--- a/libinstaller/syslxint.h
+++ b/libinstaller/syslxint.h
@@ -89,6 +89,8 @@ struct patch_area {
uint32_t checksum;
uint16_t diroffset;
uint16_t dirlen;
+ uint16_t subvoloffset;
+ uint16_t subvollen;
uint16_t secptroffset;
uint16_t secptrcnt;
};