diff options
-rw-r--r-- | core/diskstart.inc | 21 | ||||
-rw-r--r-- | core/fs/btrfs/btrfs.c | 100 | ||||
-rw-r--r-- | core/fs/btrfs/btrfs.h | 7 | ||||
-rw-r--r-- | core/include/core.h | 1 | ||||
-rw-r--r-- | extlinux/main.c | 52 | ||||
-rw-r--r-- | libinstaller/syslxint.h | 2 |
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(®s, 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; }; |