diff options
Diffstat (limited to 'contrib/android')
-rw-r--r-- | contrib/android/Android.bp | 78 | ||||
-rw-r--r-- | contrib/android/base_fs.c | 211 | ||||
-rw-r--r-- | contrib/android/base_fs.h | 17 | ||||
-rw-r--r-- | contrib/android/basefs_allocator.c | 388 | ||||
-rw-r--r-- | contrib/android/basefs_allocator.h | 16 | ||||
-rw-r--r-- | contrib/android/block_list.c | 89 | ||||
-rw-r--r-- | contrib/android/block_list.h | 8 | ||||
-rw-r--r-- | contrib/android/block_range.c | 80 | ||||
-rw-r--r-- | contrib/android/block_range.h | 29 | ||||
-rw-r--r-- | contrib/android/e2fsdroid.c | 377 | ||||
-rw-r--r-- | contrib/android/ext2simg.c | 224 | ||||
-rw-r--r-- | contrib/android/fsmap.c | 169 | ||||
-rw-r--r-- | contrib/android/fsmap.h | 29 | ||||
-rw-r--r-- | contrib/android/perms.c | 376 | ||||
-rw-r--r-- | contrib/android/perms.h | 65 |
15 files changed, 2156 insertions, 0 deletions
diff --git a/contrib/android/Android.bp b/contrib/android/Android.bp new file mode 100644 index 0000000..6c9dd5c --- /dev/null +++ b/contrib/android/Android.bp @@ -0,0 +1,78 @@ +// Copyright 2017 The Android Open Source Project + +//########################################################################## +// Build e2fsdroid + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_e2fsprogs_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["external_e2fsprogs_license"], +} + +cc_binary { + name: "e2fsdroid", + host_supported: true, + recovery_available: true, + defaults: ["e2fsprogs-defaults"], + + srcs: [ + "e2fsdroid.c", + "block_range.c", + "fsmap.c", + "block_list.c", + "base_fs.c", + "perms.c", + "basefs_allocator.c", + ], + target: { + host: { + static_libs: [ + "libext2_com_err", + "libext2_misc", + "libext2fs", + "libsparse", + "libz", + "libcutils", + "libbase", + "libselinux", + "libcrypto", + "liblog", + ], + }, + android: { + static_libs: [ + "libbase", + ], + shared_libs: [ + "libext2fs", + "libext2_com_err", + "libext2_misc", + "libcutils", + "liblog", + "libselinux", + "libcrypto", + ], + }, + }, + stl: "libc++_static", +} + +//########################################################################## +// Build ext2simg + +cc_binary { + name: "ext2simg", + host_supported: true, + defaults: ["e2fsprogs-defaults"], + + srcs: ["ext2simg.c"], + shared_libs: [ + "libext2fs", + "libext2_com_err", + "libsparse", + "libz", + ], +} diff --git a/contrib/android/base_fs.c b/contrib/android/base_fs.c new file mode 100644 index 0000000..d3e00d1 --- /dev/null +++ b/contrib/android/base_fs.c @@ -0,0 +1,211 @@ +#include "base_fs.h" +#include <stdio.h> + +#define BASE_FS_VERSION "Base EXT4 version 1.0" + +struct base_fs { + FILE *file; + const char *mountpoint; + struct basefs_entry entry; +}; + +static FILE *basefs_open(const char *file) +{ + char *line = NULL; + size_t len; + FILE *f = fopen(file, "r"); + if (!f) + return NULL; + + if (getline(&line, &len, f) == -1 || !line) + goto err_getline; + + if (strncmp(line, BASE_FS_VERSION, strlen(BASE_FS_VERSION))) + goto err_header; + + free(line); + return f; + +err_header: + free(line); +err_getline: + fclose(f); + return NULL; +} + +static struct basefs_entry *basefs_readline(FILE *f, const char *mountpoint, + int *err) +{ + char *line = NULL, *saveptr1, *saveptr2, *block_range, *block; + int offset; + size_t len; + struct basefs_entry *entry = NULL; + blk64_t range_start, range_end; + + if (getline(&line, &len, f) == -1) { + if (feof(f)) + goto end; + goto err_getline; + } + + entry = calloc(1, sizeof(*entry)); + if (!entry) + goto err_alloc; + + /* + * With BASEFS version 1.0, a typical line looks like this: + * /bin/mke2fs 5000-5004,8000,9000-9990 + */ + if (sscanf(line, "%ms%n", &entry->path, &offset) != 1) + goto err_sscanf; + len = strlen(mountpoint); + memmove(entry->path, entry->path + len, strlen(entry->path) - len + 1); + + while (line[offset] == ' ') + ++offset; + + block_range = strtok_r(line + offset, ",\n", &saveptr1); + while (block_range) { + block = strtok_r(block_range, "-", &saveptr2); + if (!block) + break; + range_start = atoll(block); + block = strtok_r(NULL, "-", &saveptr2); + range_end = block ? atoll(block) : range_start; + add_blocks_to_range(&entry->blocks, range_start, range_end); + block_range = strtok_r(NULL, ",\n", &saveptr1); + } +end: + *err = 0; + free(line); + return entry; + +err_sscanf: + free(entry); +err_alloc: + free(line); +err_getline: + *err = 1; + return NULL; +} + +static void free_base_fs_entry(void *e) +{ + struct basefs_entry *entry = e; + if (entry) { + free(entry->path); + free(entry); + } +} + +struct ext2fs_hashmap *basefs_parse(const char *file, const char *mountpoint) +{ + int err; + struct ext2fs_hashmap *entries = NULL; + struct basefs_entry *entry; + FILE *f = basefs_open(file); + if (!f) + return NULL; + entries = ext2fs_hashmap_create(ext2fs_djb2_hash, free_base_fs_entry, 1024); + if (!entries) + goto end; + + while ((entry = basefs_readline(f, mountpoint, &err))) { + err = ext2fs_hashmap_add(entries, entry, entry->path, + strlen(entry->path)); + if (err) { + free_base_fs_entry(entry); + fclose(f); + ext2fs_hashmap_free(entries); + return NULL; + } + } + if (err) { + fclose(f); + ext2fs_hashmap_free(entries); + return NULL; + } +end: + fclose(f); + return entries; +} + +static void *init(const char *file, const char *mountpoint) +{ + struct base_fs *params = malloc(sizeof(*params)); + + if (!params) + return NULL; + params->mountpoint = mountpoint; + params->file = fopen(file, "w+"); + if (!params->file) { + free(params); + return NULL; + } + if (fwrite(BASE_FS_VERSION"\n", 1, strlen(BASE_FS_VERSION"\n"), + params->file) != strlen(BASE_FS_VERSION"\n")) { + fclose(params->file); + free(params); + return NULL; + } + return params; +} + +static int start_new_file(char *path, ext2_ino_t ino EXT2FS_ATTR((unused)), + struct ext2_inode *inode, void *data) +{ + struct base_fs *params = data; + + params->entry.path = LINUX_S_ISREG(inode->i_mode) ? path : NULL; + return 0; +} + +static int add_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t blocknr, + int metadata, void *data) +{ + struct base_fs *params = data; + + if (params->entry.path && !metadata) + add_blocks_to_range(¶ms->entry.blocks, blocknr, blocknr); + return 0; +} + +static int inline_data(void *inline_data EXT2FS_ATTR((unused)), + void *data EXT2FS_ATTR((unused))) +{ + return 0; +} + +static int end_new_file(void *data) +{ + struct base_fs *params = data; + + if (!params->entry.path) + return 0; + if (fprintf(params->file, "%s%s ", params->mountpoint, + params->entry.path) < 0 + || write_block_ranges(params->file, params->entry.blocks.head, ",") + || fwrite("\n", 1, 1, params->file) != 1) + return -1; + + delete_block_ranges(¶ms->entry.blocks); + return 0; +} + +static int cleanup(void *data) +{ + struct base_fs *params = data; + + fclose(params->file); + free(params); + return 0; +} + +struct fsmap_format base_fs_format = { + .init = init, + .start_new_file = start_new_file, + .add_block = add_block, + .inline_data = inline_data, + .end_new_file = end_new_file, + .cleanup = cleanup, +}; diff --git a/contrib/android/base_fs.h b/contrib/android/base_fs.h new file mode 100644 index 0000000..f53f1ed --- /dev/null +++ b/contrib/android/base_fs.h @@ -0,0 +1,17 @@ +#ifndef BASE_FS_H +# define BASE_FS_H + +# include "fsmap.h" +# include "hashmap.h" +# include "block_range.h" + +struct basefs_entry { + char *path; + struct block_range_list blocks; +}; + +extern struct fsmap_format base_fs_format; + +struct ext2fs_hashmap *basefs_parse(const char *file, const char *mountpoint); + +#endif /* !BASE_FS_H */ diff --git a/contrib/android/basefs_allocator.c b/contrib/android/basefs_allocator.c new file mode 100644 index 0000000..4f9f5c1 --- /dev/null +++ b/contrib/android/basefs_allocator.c @@ -0,0 +1,388 @@ +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "basefs_allocator.h" +#include "block_range.h" +#include "hashmap.h" +#include "base_fs.h" + +struct base_fs_allocator { + struct ext2fs_hashmap *entries; + struct basefs_entry *cur_entry; + /* The next expected logical block to allocate for cur_entry. */ + blk64_t next_lblk; + /* Blocks which are definitely owned by a single inode in BaseFS. */ + ext2fs_block_bitmap exclusive_block_map; + /* Blocks which are available to the first inode that requests it. */ + ext2fs_block_bitmap dedup_block_map; +}; + +static errcode_t basefs_block_allocator(ext2_filsys, blk64_t, blk64_t *, + struct blk_alloc_ctx *ctx); + +/* + * Free any reserved, but unconsumed block ranges in the allocator. This both + * frees the block_range_list data structure and unreserves exclusive blocks + * from the block map. + */ +static void fs_free_blocks_range(ext2_filsys fs, + struct base_fs_allocator *allocator, + struct block_range_list *list) +{ + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + + blk64_t block; + while (list->head) { + block = consume_next_block(list); + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(fs->block_map, block); + ext2fs_unmark_block_bitmap2(exclusive_map, block); + } + } +} + +/* + * Free any blocks in the bitmap that were reserved but never used. This is + * needed to free dedup_block_map and ensure the free block bitmap is + * internally consistent. + */ +static void fs_free_blocks_bitmap(ext2_filsys fs, ext2fs_block_bitmap bitmap) +{ + blk64_t block = 0; + blk64_t start = fs->super->s_first_data_block; + blk64_t end = ext2fs_blocks_count(fs->super) - 1; + errcode_t retval; + + for (;;) { + retval = ext2fs_find_first_set_block_bitmap2(bitmap, start, end, + &block); + if (retval) + break; + ext2fs_unmark_block_bitmap2(fs->block_map, block); + start = block + 1; + } +} + +static void basefs_allocator_free(ext2_filsys fs, + struct base_fs_allocator *allocator) +{ + struct basefs_entry *e; + struct ext2fs_hashmap_entry *it = NULL; + struct ext2fs_hashmap *entries = allocator->entries; + + if (entries) { + while ((e = ext2fs_hashmap_iter_in_order(entries, &it))) { + fs_free_blocks_range(fs, allocator, &e->blocks); + delete_block_ranges(&e->blocks); + } + ext2fs_hashmap_free(entries); + } + fs_free_blocks_bitmap(fs, allocator->dedup_block_map); + ext2fs_free_block_bitmap(allocator->exclusive_block_map); + ext2fs_free_block_bitmap(allocator->dedup_block_map); + free(allocator); +} + +/* + * Build a bitmap of which blocks are definitely owned by exactly one file in + * Base FS. Blocks which are not valid or are de-duplicated are skipped. This + * is called during allocator initialization, to ensure that libext2fs does + * not allocate which we want to re-use. + * + * If a block was allocated in the initial filesystem, it can never be re-used, + * so it will appear in neither the exclusive or dedup set. If a block is used + * by multiple files, it will be removed from the owned set and instead added + * to the dedup set. + * + * The dedup set is not removed from fs->block_map. This allows us to re-use + * dedup blocks separately and not have them be allocated outside of file data. + * + * This function returns non-zero if the block was owned, and 0 otherwise. + */ +static int fs_reserve_block(ext2_filsys fs, + struct base_fs_allocator *allocator, + blk64_t block) +{ + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (block >= ext2fs_blocks_count(fs->super)) + return 0; + + if (ext2fs_test_block_bitmap2(fs->block_map, block)) { + if (!ext2fs_test_block_bitmap2(exclusive_map, block)) + return 0; + ext2fs_unmark_block_bitmap2(exclusive_map, block); + ext2fs_mark_block_bitmap2(dedup_map, block); + return 0; + } else { + ext2fs_mark_block_bitmap2(fs->block_map, block); + ext2fs_mark_block_bitmap2(exclusive_map, block); + return 1; + } +} + +/* + * Walk the requested block list and reserve blocks, either into the owned + * pool or the dedup pool as appropriate. We stop once the file has enough + * owned blocks to satisfy |file_size|. This allows any extra blocks to be + * re-used, since otherwise a large block movement between files could + * trigger block allocation errors. + */ +static void fs_reserve_blocks_range(ext2_filsys fs, + struct base_fs_allocator *allocator, + struct block_range_list *list, + off_t file_size) +{ + blk64_t block; + off_t blocks_needed; + off_t blocks_acquired = 0; + struct block_range *blocks = list->head; + + blocks_needed = file_size + (fs->blocksize - 1); + blocks_needed /= fs->blocksize; + + while (blocks) { + for (block = blocks->start; block <= blocks->end; block++) { + if (fs_reserve_block(fs, allocator, block)) + blocks_acquired++; + if (blocks_acquired >= blocks_needed) + return; + } + blocks = blocks->next; + } +} + +/* + * For each file in the base FS map, ensure that its blocks are reserved in + * the actual block map. This prevents libext2fs from allocating them for + * general purpose use, and ensures that if the file needs data blocks, they + * can be re-acquired exclusively for that file. + * + * If a file in the base map is missing, or not a regular file in the new + * filesystem, then it's skipped to ensure that its blocks are reusable. + */ +static errcode_t fs_reserve_blocks(ext2_filsys fs, + struct base_fs_allocator *allocator, + const char *src_dir) +{ + int nbytes; + char full_path[PATH_MAX]; + const char *sep = "/"; + struct stat st; + struct basefs_entry *e; + struct ext2fs_hashmap_entry *it = NULL; + struct ext2fs_hashmap *entries = allocator->entries; + + if (strlen(src_dir) && src_dir[strlen(src_dir) - 1] == '/') + sep = ""; + + while ((e = ext2fs_hashmap_iter_in_order(entries, &it))) { + nbytes = snprintf(full_path, sizeof(full_path), "%s%s%s", + src_dir, sep, e->path); + if (nbytes >= sizeof(full_path)) + return ENAMETOOLONG; + if (lstat(full_path, &st) || !S_ISREG(st.st_mode)) + continue; + fs_reserve_blocks_range(fs, allocator, &e->blocks, st.st_size); + } + return 0; +} + +errcode_t base_fs_alloc_load(ext2_filsys fs, const char *file, + const char *mountpoint, const char *src_dir) +{ + errcode_t retval = 0; + struct base_fs_allocator *allocator; + + allocator = calloc(1, sizeof(*allocator)); + if (!allocator) { + retval = ENOMEM; + goto out; + } + + retval = ext2fs_read_bitmaps(fs); + if (retval) + goto err_load; + + allocator->cur_entry = NULL; + allocator->entries = basefs_parse(file, mountpoint); + if (!allocator->entries) { + retval = EIO; + goto err_load; + } + retval = ext2fs_allocate_block_bitmap(fs, "exclusive map", + &allocator->exclusive_block_map); + if (retval) + goto err_load; + retval = ext2fs_allocate_block_bitmap(fs, "dedup map", + &allocator->dedup_block_map); + if (retval) + goto err_load; + + retval = fs_reserve_blocks(fs, allocator, src_dir); + if (retval) + goto err_load; + + /* Override the default allocator */ + fs->get_alloc_block2 = basefs_block_allocator; + fs->priv_data = allocator; + + goto out; + +err_load: + basefs_allocator_free(fs, allocator); +out: + return retval; +} + +/* Try and acquire the next usable block from the Base FS map. */ +static errcode_t get_next_block(ext2_filsys fs, struct base_fs_allocator *allocator, + struct block_range_list* list, blk64_t *ret) +{ + blk64_t block; + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (!list->head) + return EXT2_ET_BLOCK_ALLOC_FAIL; + + block = consume_next_block(list); + if (block >= ext2fs_blocks_count(fs->super)) + return EXT2_ET_BLOCK_ALLOC_FAIL; + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(exclusive_map, block); + *ret = block; + return 0; + } + if (ext2fs_test_block_bitmap2(dedup_map, block)) { + ext2fs_unmark_block_bitmap2(dedup_map, block); + *ret = block; + return 0; + } + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +/* + * BaseFS lists blocks in logical block order. However, the allocator hook is + * only called if a block needs to be allocated. In the case of a deduplicated + * block, or a hole, the hook is not invoked. This means the next block + * allocation request will be out of sequence. For example, consider if BaseFS + * specifies the following (0 being a hole): + * 1 2 3 0 4 5 + * + * If the new file has a hole at logical block 0, we could accidentally + * shift the entire expected block list as follows: + * 0 1 2 0 3 4 + * + * To account for this, we track the next expected logical block in the + * allocator. If the current request is for a later logical block, we skip and + * free the intermediate physical blocks that would have been allocated. This + * ensures the original block assignment is respected. + */ +static void skip_blocks(ext2_filsys fs, struct base_fs_allocator *allocator, + struct blk_alloc_ctx *ctx) +{ + blk64_t block; + struct block_range_list *list = &allocator->cur_entry->blocks; + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + + while (list->head && allocator->next_lblk < ctx->lblk) { + block = consume_next_block(list); + if (block >= ext2fs_blocks_count(fs->super)) + continue; + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(exclusive_map, block); + ext2fs_unmark_block_bitmap2(fs->block_map, block); + } + allocator->next_lblk++; + } +} + +static errcode_t basefs_block_allocator(ext2_filsys fs, blk64_t goal, + blk64_t *ret, struct blk_alloc_ctx *ctx) +{ + errcode_t retval; + struct base_fs_allocator *allocator = fs->priv_data; + struct basefs_entry *e = allocator->cur_entry; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (e && ctx && (ctx->flags & BLOCK_ALLOC_DATA)) { + if (allocator->next_lblk < ctx->lblk) + skip_blocks(fs, allocator, ctx); + allocator->next_lblk = ctx->lblk + 1; + + if (!get_next_block(fs, allocator, &e->blocks, ret)) + return 0; + } + + retval = ext2fs_new_block2(fs, goal, fs->block_map, ret); + if (!retval) { + ext2fs_mark_block_bitmap2(fs->block_map, *ret); + return 0; + } + if (retval != EXT2_ET_BLOCK_ALLOC_FAIL) + return retval; + + /* Try to steal a block from the dedup pool. */ + retval = ext2fs_find_first_set_block_bitmap2(dedup_map, + fs->super->s_first_data_block, + ext2fs_blocks_count(fs->super) - 1, ret); + if (!retval) { + ext2fs_unmark_block_bitmap2(dedup_map, *ret); + return 0; + } + + /* + * As a last resort, take any block from our file's list. This + * risks bloating the diff, but means we are more likely to + * successfully build an image. + */ + while (e->blocks.head) { + if (!get_next_block(fs, allocator, &e->blocks, ret)) + return 0; + } + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +void base_fs_alloc_cleanup(ext2_filsys fs) +{ + basefs_allocator_free(fs, fs->priv_data); + fs->priv_data = NULL; + fs->get_alloc_block2 = NULL; +} + +errcode_t base_fs_alloc_set_target(ext2_filsys fs, const char *target_path, + const char *name EXT2FS_ATTR((unused)), + ext2_ino_t parent_ino EXT2FS_ATTR((unused)), + ext2_ino_t root EXT2FS_ATTR((unused)), mode_t mode) +{ + struct base_fs_allocator *allocator = fs->priv_data; + + if (mode != S_IFREG) + return 0; + + if (allocator) { + allocator->cur_entry = ext2fs_hashmap_lookup(allocator->entries, + target_path, + strlen(target_path)); + allocator->next_lblk = 0; + } + return 0; +} + +errcode_t base_fs_alloc_unset_target(ext2_filsys fs, + const char *target_path EXT2FS_ATTR((unused)), + const char *name EXT2FS_ATTR((unused)), + ext2_ino_t parent_ino EXT2FS_ATTR((unused)), + ext2_ino_t root EXT2FS_ATTR((unused)), mode_t mode) +{ + struct base_fs_allocator *allocator = fs->priv_data; + + if (!allocator || !allocator->cur_entry || mode != S_IFREG) + return 0; + + fs_free_blocks_range(fs, allocator, &allocator->cur_entry->blocks); + delete_block_ranges(&allocator->cur_entry->blocks); + return 0; +} diff --git a/contrib/android/basefs_allocator.h b/contrib/android/basefs_allocator.h new file mode 100644 index 0000000..6d1c65e --- /dev/null +++ b/contrib/android/basefs_allocator.h @@ -0,0 +1,16 @@ +#ifndef BASE_FS_ALLOCATOR_H +# define BASE_FS_ALLOCATOR_H + +# include <time.h> +# include <ext2fs/ext2fs.h> + +errcode_t base_fs_alloc_load(ext2_filsys fs, const char *file, + const char *mountpoint, const char *src_dir); +void base_fs_alloc_cleanup(ext2_filsys fs); + +errcode_t base_fs_alloc_set_target(ext2_filsys fs, const char *target_path, + const char *name, ext2_ino_t parent_ino, ext2_ino_t root, mode_t mode); +errcode_t base_fs_alloc_unset_target(ext2_filsys fs, const char *target_path, + const char *name, ext2_ino_t parent_ino, ext2_ino_t root, mode_t mode); + +#endif /* !BASE_FS_ALLOCATOR_H */ diff --git a/contrib/android/block_list.c b/contrib/android/block_list.c new file mode 100644 index 0000000..63cc1a2 --- /dev/null +++ b/contrib/android/block_list.c @@ -0,0 +1,89 @@ +#include "block_list.h" +#include "block_range.h" +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +struct block_list { + FILE *f; + const char *mountpoint; + + const char *filename; + struct block_range_list blocks; +}; + +static void *init(const char *file, const char *mountpoint) +{ + struct block_list *params = calloc(1, sizeof(*params)); + + if (!params) + return NULL; + params->mountpoint = mountpoint; + params->f = fopen(file, "w+"); + if (!params->f) { + free(params); + return NULL; + } + return params; +} + +static int start_new_file(char *path, ext2_ino_t ino EXT2FS_ATTR((unused)), + struct ext2_inode *inode EXT2FS_ATTR((unused)), + void *data) +{ + struct block_list *params = data; + + params->filename = LINUX_S_ISREG(inode->i_mode) ? path : NULL; + return 0; +} + +static int add_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t blocknr, + int metadata, void *data) +{ + struct block_list *params = data; + + if (params->filename && !metadata) + add_blocks_to_range(¶ms->blocks, blocknr, blocknr); + return 0; +} + +static int inline_data(void *inline_data EXT2FS_ATTR((unused)), + void *data EXT2FS_ATTR((unused))) +{ + return 0; +} + +static int end_new_file(void *data) +{ + struct block_list *params = data; + + if (!params->filename || !params->blocks.head) + return 0; + if (fprintf(params->f, "%s%s ", params->mountpoint, + params->filename) < 0 + || write_block_ranges(params->f, params->blocks.head, " ") + || fwrite("\n", 1, 1, params->f) != 1) + return -1; + + delete_block_ranges(¶ms->blocks); + return 0; +} + +static int cleanup(void *data) +{ + struct block_list *params = data; + + fclose(params->f); + free(params); + return 0; +} + +struct fsmap_format block_list_format = { + .init = init, + .start_new_file = start_new_file, + .add_block = add_block, + .inline_data = inline_data, + .end_new_file = end_new_file, + .cleanup = cleanup, +}; diff --git a/contrib/android/block_list.h b/contrib/android/block_list.h new file mode 100644 index 0000000..47041e4 --- /dev/null +++ b/contrib/android/block_list.h @@ -0,0 +1,8 @@ +#ifndef BLOCK_LIST_H +# define BLOCK_LIST_H + +# include "fsmap.h" + +extern struct fsmap_format block_list_format; + +#endif /* !BLOCK_LIST_H */ diff --git a/contrib/android/block_range.c b/contrib/android/block_range.c new file mode 100644 index 0000000..0a06882 --- /dev/null +++ b/contrib/android/block_range.c @@ -0,0 +1,80 @@ +#define _GNU_SOURCE + +#include "block_range.h" +#include <stdio.h> + +struct block_range *new_block_range(blk64_t start, blk64_t end) +{ + struct block_range *range = malloc(sizeof(*range)); + range->start = start; + range->end = end; + range->next = NULL; + return range; +} + +void add_blocks_to_range(struct block_range_list *list, blk64_t blk_start, + blk64_t blk_end) +{ + if (list->head == NULL) + list->head = list->tail = new_block_range(blk_start, blk_end); + else if (list->tail->end + 1 == blk_start) + list->tail->end += (blk_end - blk_start + 1); + else { + struct block_range *range = new_block_range(blk_start, blk_end); + list->tail->next = range; + list->tail = range; + } +} + +static void remove_head(struct block_range_list *list) +{ + struct block_range *next_range = list->head->next; + + free(list->head); + if (next_range == NULL) + list->head = list->tail = NULL; + else + list->head = next_range; +} + +void delete_block_ranges(struct block_range_list *list) +{ + while (list->head) + remove_head(list); +} + +int write_block_ranges(FILE *f, struct block_range *range, + char *sep) +{ + int len; + char *buf; + + while (range) { + if (range->start == range->end) + len = asprintf(&buf, "%llu%s", range->start, sep); + else + len = asprintf(&buf, "%llu-%llu%s", range->start, + range->end, sep); + if (fwrite(buf, 1, len, f) != (size_t)len) { + free(buf); + return -1; + } + free(buf); + range = range->next; + } + + len = strlen(sep); + if (fseek(f, -len, SEEK_CUR) == -len) + return -1; + return 0; +} + +blk64_t consume_next_block(struct block_range_list *list) +{ + blk64_t ret = list->head->start; + + list->head->start += 1; + if (list->head->start > list->head->end) + remove_head(list); + return ret; +} diff --git a/contrib/android/block_range.h b/contrib/android/block_range.h new file mode 100644 index 0000000..cf7971e --- /dev/null +++ b/contrib/android/block_range.h @@ -0,0 +1,29 @@ +#ifndef BLOCK_RANGE_H +# define BLOCK_RANGE_H + +# include <sys/types.h> +# include <ext2fs/ext2fs.h> + +struct block_range { + blk64_t start; + blk64_t end; + struct block_range *next; +}; + +struct block_range_list { + struct block_range *head; + struct block_range *tail; +}; + +void add_blocks_to_range(struct block_range_list *list, blk64_t blk_start, + blk64_t blk_end); +void delete_block_ranges(struct block_range_list *list); +int write_block_ranges(FILE *f, struct block_range *range, char *sep); + +/* + * Given a non-empty range list, return the next block and remove it from the + * list. + */ +blk64_t consume_next_block(struct block_range_list *list); + +#endif /* !BLOCK_RANGE_H */ diff --git a/contrib/android/e2fsdroid.c b/contrib/android/e2fsdroid.c new file mode 100644 index 0000000..6e51414 --- /dev/null +++ b/contrib/android/e2fsdroid.c @@ -0,0 +1,377 @@ +#define _GNU_SOURCE + +#include "config.h" +#include <stdio.h> +#include <getopt.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "perms.h" +#include "base_fs.h" +#include "block_list.h" +#include "basefs_allocator.h" +#include "create_inode.h" + +#ifndef UID_GID_MAP_MAX_EXTENTS +/* + * The value is defined in linux/user_namspace.h. + * The value is (arbitrarily) 5 in 4.14 and earlier, or 340 in 4.15 and later. + * Here, the bigger value is taken. See also man user_namespace(7). + */ +#define UID_GID_MAP_MAX_EXTENTS 340 +#endif + +static char *prog_name = "e2fsdroid"; +static char *in_file; +static char *block_list; +static char *basefs_out; +static char *basefs_in; +static char *mountpoint = ""; +static time_t fixed_time = -1; +static char *fs_config_file; +static struct selinux_opt seopt_file[8]; +static int max_nr_opt = (int)sizeof(seopt_file) / sizeof(seopt_file[0]); +static char *product_out; +static char *src_dir; +static int android_configure; +static int android_sparse_file = 1; + +static void usage(int ret) +{ + fprintf(stderr, "%s [-B block_list] [-D basefs_out] [-T timestamp]\n" + "\t[-C fs_config] [-S file_contexts] [-p product_out]\n" + "\t[-a mountpoint] [-d basefs_in] [-f src_dir] [-e] [-s]\n" + "\t[-u uid-mapping] [-g gid-mapping] image\n", + prog_name); + exit(ret); +} + +static char *absolute_path(const char *file) +{ + char *ret; + char cwd[PATH_MAX]; + + if (file[0] != '/') { + if (getcwd(cwd, PATH_MAX) == NULL) { + fprintf(stderr, "Failed to getcwd\n"); + exit(EXIT_FAILURE); + } + ret = malloc(strlen(cwd) + 1 + strlen(file) + 1); + if (ret) + sprintf(ret, "%s/%s", cwd, file); + } else + ret = strdup(file); + return ret; +} + +static int parse_ugid_map_entry(char* line, struct ugid_map_entry* result) +{ + char *token, *token_saveptr; + size_t num_tokens; + unsigned int *parsed[] = {&result->child_id, + &result->parent_id, + &result->length}; + for (token = strtok_r(line, " ", &token_saveptr), num_tokens = 0; + token && num_tokens < 3; + token = strtok_r(NULL, " ", &token_saveptr), ++num_tokens) { + char* endptr = NULL; + unsigned long t = strtoul(token, &endptr, 10); + if ((t == ULONG_MAX && errno) || (t > UINT_MAX) || *endptr) { + fprintf(stderr, "Malformed u/gid mapping line\n"); + return 0; + } + *parsed[num_tokens] = (unsigned int) t; + } + if (num_tokens < 3 || strtok_r(NULL, " ", &token_saveptr) != NULL) { + fprintf(stderr, "Malformed u/gid mapping line\n"); + return 0; + } + if (result->child_id + result->length < result->child_id || + result->parent_id + result->length < result->parent_id) { + fprintf(stderr, "u/gid mapping overflow\n"); + return 0; + } + return 1; +} + +/* + * Returns 1 if [begin1, begin1+length1) and [begin2, begin2+length2) have + * overlapping range. Otherwise 0. + */ +static int is_overlapping(unsigned int begin1, unsigned int length1, + unsigned int begin2, unsigned int length2) +{ + unsigned int end1 = begin1 + length1; + unsigned int end2 = begin2 + length2; + return !(end1 <= begin2 || end2 <= begin1); +} + +/* + * Verifies if the given mapping works. + * - Checks if the number of entries is less than or equals to + * UID_GID_MAP_MAX_EXTENTS. + * - Checks if there is no overlapped ranges. + * Returns 1 if valid, otherwise 0. + */ +static int is_valid_ugid_map(const struct ugid_map* mapping) +{ + size_t i, j; + + if (mapping->size > UID_GID_MAP_MAX_EXTENTS) { + fprintf(stderr, "too many u/gid mapping entries\n"); + return 0; + } + + for (i = 0; i < mapping->size; ++i) { + const struct ugid_map_entry *entry1 = &mapping->entries[i]; + for (j = i + 1; j < mapping->size; ++j) { + const struct ugid_map_entry *entry2 = + &mapping->entries[j]; + if (is_overlapping(entry1->child_id, entry1->length, + entry2->child_id, entry2->length)) { + fprintf(stderr, + "Overlapping child u/gid: [%d %d %d]," + " [%d %d %d]\n", + entry1->child_id, entry1->parent_id, + entry1->length, entry2->child_id, + entry2->parent_id, entry2->length); + return 0; + } + if (is_overlapping(entry1->parent_id, entry1->length, + entry2->parent_id, entry2->length)) { + fprintf(stderr, + "Overlapping parent u/gid: [%d %d %d]," + " [%d %d %d]\n", + entry1->child_id, entry1->parent_id, + entry1->length, entry2->child_id, + entry2->parent_id, entry2->length); + return 0; + } + } + } + return 1; +} + +/* + * Parses the UID/GID mapping argument. The argument could be a multi-line + * string (separated by '\n', no trailing '\n' is allowed). Each line must + * contain exact three integer tokens; the first token is |child_id|, + * the second is |parent_id|, and the last is |length| of the mapping range. + * See also user_namespace(7) man page. + * On success, the parsed entries are stored in |result|, and it returns 1. + * Otherwise, returns 0. + */ +static int parse_ugid_map(char* arg, struct ugid_map* result) +{ + int i; + char *line, *line_saveptr; + size_t current_index; + + /* Count the number of lines. */ + result->size = 1; + for (i = 0; arg[i]; ++i) { + if (arg[i] == '\n') + ++result->size; + } + + /* Allocate memory for entries. */ + result->entries = malloc(sizeof(struct ugid_map_entry) * result->size); + if (!result->entries) { + result->size = 0; + return 0; + } + + /* Parse each line */ + for (line = strtok_r(arg, "\n", &line_saveptr), current_index = 0; + line; + line = strtok_r(NULL, "\n", &line_saveptr), ++current_index) { + if (!parse_ugid_map_entry( + line, &result->entries[current_index])) { + return 0; + } + } + + return is_valid_ugid_map(result); +} + +int main(int argc, char *argv[]) +{ + int c; + char *p; + int flags = EXT2_FLAG_RW; + errcode_t retval; + io_manager io_mgr; + ext2_filsys fs = NULL; + struct fs_ops_callbacks fs_callbacks = { NULL, NULL }; + char *token; + int nr_opt = 0; + ext2_ino_t inodes_count; + ext2_ino_t free_inodes_count; + blk64_t blocks_count; + blk64_t free_blocks_count; + struct ugid_map uid_map = { 0, NULL }, gid_map = { 0, NULL }; + + add_error_table(&et_ext2_error_table); + + while ((c = getopt (argc, argv, "T:C:S:p:a:D:d:B:f:esu:g:")) != EOF) { + switch (c) { + case 'T': + fixed_time = strtoul(optarg, &p, 0); + android_configure = 1; + break; + case 'C': + fs_config_file = absolute_path(optarg); + android_configure = 1; + break; + case 'S': + token = strtok(optarg, ","); + while (token) { + if (nr_opt == max_nr_opt) { + fprintf(stderr, "Expected at most %d selinux opts\n", + max_nr_opt); + exit(EXIT_FAILURE); + } + seopt_file[nr_opt].type = SELABEL_OPT_PATH; + seopt_file[nr_opt].value = absolute_path(token); + nr_opt++; + token = strtok(NULL, ","); + } + android_configure = 1; + break; + case 'p': + product_out = absolute_path(optarg); + android_configure = 1; + break; + case 'a': + mountpoint = strdup(optarg); + break; + case 'D': + basefs_out = absolute_path(optarg); + break; + case 'd': + basefs_in = absolute_path(optarg); + break; + case 'B': + block_list = absolute_path(optarg); + break; + case 'f': + src_dir = absolute_path(optarg); + break; + case 'e': + android_sparse_file = 0; + break; + case 's': + flags |= EXT2_FLAG_SHARE_DUP; + break; + case 'u': + if (!parse_ugid_map(optarg, &uid_map)) + exit(EXIT_FAILURE); + android_configure = 1; + break; + case 'g': + if (!parse_ugid_map(optarg, &gid_map)) + exit(EXIT_FAILURE); + android_configure = 1; + break; + default: + usage(EXIT_FAILURE); + } + } + if (optind >= argc) { + fprintf(stderr, "Expected filename after options\n"); + exit(EXIT_FAILURE); + } + + if (android_sparse_file) { + io_mgr = sparse_io_manager; + if (asprintf(&in_file, "(%s)", argv[optind]) == -1) { + fprintf(stderr, "Failed to allocate file name\n"); + exit(EXIT_FAILURE); + } + } else { + io_mgr = unix_io_manager; + in_file = strdup(argv[optind]); + } + retval = ext2fs_open(in_file, flags, 0, 0, io_mgr, &fs); + if (retval) { + com_err(prog_name, retval, "while opening file %s\n", in_file); + return retval; + } + + if (src_dir) { + ext2fs_read_bitmaps(fs); + if (basefs_in) { + retval = base_fs_alloc_load(fs, basefs_in, mountpoint, + src_dir); + if (retval) { + com_err(prog_name, retval, "%s", + "while reading base_fs file"); + exit(1); + } + fs_callbacks.create_new_inode = + base_fs_alloc_set_target; + fs_callbacks.end_create_new_inode = + base_fs_alloc_unset_target; + } + retval = populate_fs2(fs, EXT2_ROOT_INO, src_dir, + EXT2_ROOT_INO, &fs_callbacks); + if (retval) { + com_err(prog_name, retval, "%s", + "while populating file system"); + exit(1); + } + if (basefs_in) + base_fs_alloc_cleanup(fs); + } + + if (android_configure) { + retval = android_configure_fs( + fs, src_dir, product_out, mountpoint, seopt_file, + nr_opt, fs_config_file, fixed_time, &uid_map, &gid_map); + if (retval) { + com_err(prog_name, retval, "%s", + "while configuring the file system"); + exit(1); + } + } + + if (block_list) { + retval = fsmap_iter_filsys(fs, &block_list_format, block_list, + mountpoint); + if (retval) { + com_err(prog_name, retval, "%s", + "while creating the block_list"); + exit(1); + } + } + + if (basefs_out) { + retval = fsmap_iter_filsys(fs, &base_fs_format, + basefs_out, mountpoint); + if (retval) { + com_err(prog_name, retval, "%s", + "while creating the basefs file"); + exit(1); + } + } + + inodes_count = fs->super->s_inodes_count; + free_inodes_count = fs->super->s_free_inodes_count; + blocks_count = ext2fs_blocks_count(fs->super); + free_blocks_count = ext2fs_free_blocks_count(fs->super); + + retval = ext2fs_close_free(&fs); + if (retval) { + com_err(prog_name, retval, "%s", + "while writing superblocks"); + exit(1); + } + + printf("Created filesystem with %u/%u inodes and %llu/%llu blocks\n", + inodes_count - free_inodes_count, inodes_count, + blocks_count - free_blocks_count, blocks_count); + + remove_error_table(&et_ext2_error_table); + return 0; +} diff --git a/contrib/android/ext2simg.c b/contrib/android/ext2simg.c new file mode 100644 index 0000000..017e16f --- /dev/null +++ b/contrib/android/ext2simg.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <libgen.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ext2fs/ext2fs.h> +#include <et/com_err.h> +#include <sparse/sparse.h> + +struct { + int crc; + int sparse; + int gzip; + char *in_file; + char *out_file; + bool overwrite_input; +} params = { + .crc = 0, + .sparse = 1, + .gzip = 0, +}; + +#define ext2fs_fatal(Retval, Format, ...) \ + do { \ + com_err("error", Retval, Format, __VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while(0) + +#define sparse_fatal(Format) \ + do { \ + fprintf(stderr, "sparse: "Format); \ + exit(EXIT_FAILURE); \ + } while(0) + +static void usage(char *path) +{ + char *progname = basename(path); + + fprintf(stderr, "%s [ options ] <image or block device> <output image>\n" + " -c include CRC block\n" + " -z gzip output\n" + " -S don't use sparse output format\n", progname); +} + +static struct buf_item { + struct buf_item *next; + void *buf[0]; +} *buf_list; + +static void add_chunk(ext2_filsys fs, struct sparse_file *s, blk_t chunk_start, blk_t chunk_end) +{ + int retval; + unsigned int nb_blk = chunk_end - chunk_start; + size_t len = nb_blk * fs->blocksize; + int64_t offset = (int64_t)chunk_start * (int64_t)fs->blocksize; + + if (params.overwrite_input == false) { + if (sparse_file_add_file(s, params.in_file, offset, len, chunk_start) < 0) + sparse_fatal("adding data to the sparse file"); + } else { + /* + * The input file will be overwritten, make a copy of + * the blocks + */ + struct buf_item *bi = calloc(1, sizeof(struct buf_item) + len); + if (buf_list == NULL) + buf_list = bi; + else { + bi->next = buf_list; + buf_list = bi; + } + + retval = io_channel_read_blk64(fs->io, chunk_start, nb_blk, bi->buf); + if (retval < 0) + ext2fs_fatal(retval, "reading block %u - %u", chunk_start, chunk_end); + + if (sparse_file_add_data(s, bi->buf, len, chunk_start) < 0) + sparse_fatal("adding data to the sparse file"); + } +} + +static void free_chunks(void) +{ + struct buf_item *bi; + + while (buf_list) { + bi = buf_list->next; + free(buf_list); + buf_list = bi; + } +} + +static struct sparse_file *ext_to_sparse(const char *in_file) +{ + errcode_t retval; + ext2_filsys fs; + struct sparse_file *s; + int64_t chunk_start = -1; + blk_t first_blk, last_blk, nb_blk, cur_blk; + + retval = ext2fs_open(in_file, 0, 0, 0, unix_io_manager, &fs); + if (retval) + ext2fs_fatal(retval, "while reading %s", in_file); + + retval = ext2fs_read_block_bitmap(fs); + if (retval) + ext2fs_fatal(retval, "while reading block bitmap of %s", in_file); + + first_blk = ext2fs_get_block_bitmap_start2(fs->block_map); + last_blk = ext2fs_get_block_bitmap_end2(fs->block_map); + nb_blk = last_blk - first_blk + 1; + + s = sparse_file_new(fs->blocksize, (uint64_t)fs->blocksize * (uint64_t)nb_blk); + if (!s) + sparse_fatal("creating sparse file"); + + /* + * The sparse format encodes the size of a chunk (and its header) in a + * 32-bit unsigned integer (UINT32_MAX) + * When writing the chunk, the library uses a single call to write(). + * Linux's implementation of the 'write' syscall does not allow transfers + * larger than INT32_MAX (32-bit _and_ 64-bit systems). + * Make sure we do not create chunks larger than this limit. + */ + int64_t max_blk_per_chunk = (INT32_MAX - 12) / fs->blocksize; + + /* Iter on the blocks to merge contiguous chunk */ + for (cur_blk = first_blk; cur_blk <= last_blk; ++cur_blk) { + if (ext2fs_test_block_bitmap2(fs->block_map, cur_blk)) { + if (chunk_start == -1) { + chunk_start = cur_blk; + } else if (cur_blk - chunk_start + 1 == max_blk_per_chunk) { + add_chunk(fs, s, chunk_start, cur_blk); + chunk_start = -1; + } + } else if (chunk_start != -1) { + add_chunk(fs, s, chunk_start, cur_blk); + chunk_start = -1; + } + } + if (chunk_start != -1) + add_chunk(fs, s, chunk_start, cur_blk - 1); + + ext2fs_free(fs); + return s; +} + +static bool same_file(const char *in, const char *out) +{ + struct stat st1, st2; + + if (access(out, F_OK) == -1) + return false; + + if (lstat(in, &st1) == -1) + ext2fs_fatal(errno, "stat %s\n", in); + if (lstat(out, &st2) == -1) + ext2fs_fatal(errno, "stat %s\n", out); + return st1.st_ino == st2.st_ino; +} + +int main(int argc, char *argv[]) +{ + int opt; + int out_fd; + struct sparse_file *s; + + while ((opt = getopt(argc, argv, "czS")) != -1) { + switch(opt) { + case 'c': + params.crc = 1; + break; + case 'z': + params.gzip = 1; + break; + case 'S': + params.sparse = 0; + break; + default: + usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (optind + 1 >= argc) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + params.in_file = strdup(argv[optind++]); + params.out_file = strdup(argv[optind]); + params.overwrite_input = same_file(params.in_file, params.out_file); + + s = ext_to_sparse(params.in_file); + + out_fd = open(params.out_file, O_WRONLY | O_CREAT | O_TRUNC, 0664); + if (out_fd == -1) + ext2fs_fatal(errno, "opening %s\n", params.out_file); + if (sparse_file_write(s, out_fd, params.gzip, params.sparse, params.crc) < 0) + sparse_fatal("writing sparse file"); + + sparse_file_destroy(s); + + free(params.in_file); + free(params.out_file); + free_chunks(); + close(out_fd); + + return 0; +} diff --git a/contrib/android/fsmap.c b/contrib/android/fsmap.c new file mode 100644 index 0000000..9ee8472 --- /dev/null +++ b/contrib/android/fsmap.c @@ -0,0 +1,169 @@ +#include "fsmap.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "support/nls-enable.h" + +struct walk_ext_priv_data { + char *path; + ext2_filsys fs; + struct fsmap_format *format; +}; + +static int walk_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t *blocknr, + e2_blkcnt_t blockcnt, + blk64_t ref64_blk EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv) +{ + struct walk_ext_priv_data *pdata = priv; + struct fsmap_format *format = pdata->format; + + return format->add_block(fs, *blocknr, blockcnt < 0, format->private); +} + +static errcode_t ino_iter_extents(ext2_filsys fs, ext2_ino_t ino, + ext2_extent_handle_t extents, + struct walk_ext_priv_data *pdata) +{ + blk64_t block; + errcode_t retval; + blk64_t next_lblk = 0; + int op = EXT2_EXTENT_ROOT; + struct ext2fs_extent extent; + struct fsmap_format *format = pdata->format; + + for (;;) { + retval = ext2fs_extent_get(extents, op, &extent); + if (retval) + break; + + op = EXT2_EXTENT_NEXT; + + if ((extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) || + !(extent.e_flags & EXT2_EXTENT_FLAGS_LEAF)) + continue; + + for (; next_lblk < extent.e_lblk; next_lblk++) + format->add_block(fs, 0, 0, format->private); + + block = extent.e_pblk; + for (; next_lblk < extent.e_lblk + extent.e_len; next_lblk++) + format->add_block(fs, block++, 0, format->private); + } + + if (retval == EXT2_ET_EXTENT_NO_NEXT) + retval = 0; + if (retval) { + com_err(__func__, retval, ("getting extents of ino \"%u\""), + ino); + } + return retval; +} + +static errcode_t ino_iter_blocks(ext2_filsys fs, ext2_ino_t ino, + struct walk_ext_priv_data *pdata) +{ + errcode_t retval; + struct ext2_inode inode; + ext2_extent_handle_t extents; + struct fsmap_format *format = pdata->format; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + + if (!ext2fs_inode_has_valid_blocks2(fs, &inode)) + return format->inline_data(&(inode.i_block[0]), + format->private); + + retval = ext2fs_extent_open(fs, ino, &extents); + if (retval == EXT2_ET_INODE_NOT_EXTENT) { + retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY, + NULL, walk_block, pdata); + if (retval) { + com_err(__func__, retval, _("listing blocks of ino \"%u\""), + ino); + } + return retval; + } + + retval = ino_iter_extents(fs, ino, extents, pdata); + + ext2fs_extent_free(extents); + return retval; +} + +static int is_dir(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + + if (ext2fs_read_inode(fs, ino, &inode)) + return 0; + return S_ISDIR(inode.i_mode); +} + +static int walk_ext_dir(ext2_ino_t dir EXT2FS_ATTR((unused)), + int flags EXT2FS_ATTR((unused)), + struct ext2_dir_entry *de, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), void *priv_data) +{ + errcode_t retval; + struct ext2_inode inode; + char *filename, *cur_path, *name = de->name; + int name_len = de->name_len & 0xff; + struct walk_ext_priv_data *pdata = priv_data; + struct fsmap_format *format = pdata->format; + + if (!strncmp(name, ".", name_len) + || !strncmp(name, "..", name_len) + || !strncmp(name, "lost+found", 10)) + return 0; + + if (asprintf(&filename, "%s/%.*s", pdata->path, name_len, name) < 0) + return -ENOMEM; + + retval = ext2fs_read_inode(pdata->fs, de->inode, &inode); + if (retval) { + com_err(__func__, retval, _("reading ino \"%u\""), de->inode); + goto end; + } + format->start_new_file(filename, de->inode, &inode, format->private); + retval = ino_iter_blocks(pdata->fs, de->inode, pdata); + if (retval) + return retval; + format->end_new_file(format->private); + + retval = 0; + if (is_dir(pdata->fs, de->inode)) { + cur_path = pdata->path; + pdata->path = filename; + retval = ext2fs_dir_iterate2(pdata->fs, de->inode, 0, NULL, + walk_ext_dir, pdata); + pdata->path = cur_path; + } + +end: + free(filename); + return retval; +} + +errcode_t fsmap_iter_filsys(ext2_filsys fs, struct fsmap_format *format, + const char *file, const char *mountpoint) +{ + struct walk_ext_priv_data pdata; + errcode_t retval; + + format->private = format->init(file, mountpoint); + pdata.fs = fs; + pdata.path = ""; + pdata.format = format; + + retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_ext_dir, &pdata); + + format->cleanup(format->private); + return retval; +} diff --git a/contrib/android/fsmap.h b/contrib/android/fsmap.h new file mode 100644 index 0000000..9f84a71 --- /dev/null +++ b/contrib/android/fsmap.h @@ -0,0 +1,29 @@ +#ifndef FSMAP_H +# define FSMAP_H + +# ifndef _GNU_SOURCE +# define _GNU_SOURCE // asprintf +# endif +# include <stdio.h> +# include <stdint.h> +# include <stdbool.h> +# include <sys/types.h> +# include <ext2fs/ext2fs.h> + +struct fsmap_format { + void* (* init)(const char *file, const char *mountpoint); + int (* start_new_file)(char *path, ext2_ino_t ino, + struct ext2_inode *inode, void *data); + int (* add_block)(ext2_filsys fs, blk64_t blocknr, int metadata, + void *data); + int (* inline_data)(void *inline_data, void *data); + int (* end_new_file)(void *data); + int (* cleanup)(void *data); + + void *private; +}; + +errcode_t fsmap_iter_filsys(ext2_filsys fs, struct fsmap_format *format, + const char *file, const char *mountpoint); + +#endif /* !FSMAP_H */ diff --git a/contrib/android/perms.c b/contrib/android/perms.c new file mode 100644 index 0000000..dd05644 --- /dev/null +++ b/contrib/android/perms.c @@ -0,0 +1,376 @@ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE //asprintf +#endif +#include "config.h" +#include "perms.h" +#include "support/nls-enable.h" +#include <time.h> +#include <sys/stat.h> + +#ifndef XATTR_SELINUX_SUFFIX +# define XATTR_SELINUX_SUFFIX "selinux" +#endif +#ifndef XATTR_CAPS_SUFFIX +# define XATTR_CAPS_SUFFIX "capability" +#endif + +struct inode_params { + ext2_filsys fs; + char *path; + char *filename; + char *src_dir; + char *target_out; + char *mountpoint; + fs_config_f fs_config_func; + struct selabel_handle *sehnd; + time_t fixed_time; + const struct ugid_map* uid_map; + const struct ugid_map* gid_map; + errcode_t error; +}; + +static errcode_t ino_add_xattr(ext2_filsys fs, ext2_ino_t ino, const char *name, + const void *value, int value_len) +{ + errcode_t retval, close_retval; + struct ext2_xattr_handle *xhandle; + + retval = ext2fs_xattrs_open(fs, ino, &xhandle); + if (retval) { + com_err(__func__, retval, _("while opening inode %u"), ino); + return retval; + } + retval = ext2fs_xattrs_read(xhandle); + if (retval) { + com_err(__func__, retval, + _("while reading xattrs of inode %u"), ino); + goto xattrs_close; + } + retval = ext2fs_xattr_set(xhandle, name, value, value_len); + if (retval) { + com_err(__func__, retval, + _("while setting xattrs of inode %u"), ino); + goto xattrs_close; + } +xattrs_close: + close_retval = ext2fs_xattrs_close(&xhandle); + if (close_retval) { + com_err(__func__, close_retval, + _("while closing xattrs of inode %u"), ino); + return retval ? retval : close_retval; + } + return retval; +} + +static errcode_t set_selinux_xattr(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + char *secontext = NULL; + struct ext2_inode inode; + + if (params->sehnd == NULL) + return 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while reading inode %u"), ino); + return retval; + } + + retval = selabel_lookup(params->sehnd, &secontext, params->filename, + inode.i_mode); + if (retval < 0) { + int saved_errno = errno; + com_err(__func__, errno, + _("searching for label \"%s\""), params->filename); + return saved_errno; + } + + retval = ino_add_xattr(fs, ino, "security." XATTR_SELINUX_SUFFIX, + secontext, strlen(secontext) + 1); + + freecon(secontext); + return retval; +} + +/* + * Returns mapped UID/GID if there is a corresponding entry in |mapping|. + * Otherwise |id| as is. + */ +static unsigned int resolve_ugid(const struct ugid_map* mapping, + unsigned int id) +{ + size_t i; + for (i = 0; i < mapping->size; ++i) { + const struct ugid_map_entry* entry = &mapping->entries[i]; + if (entry->parent_id <= id && + id < entry->parent_id + entry->length) { + return id + entry->child_id - entry->parent_id; + } + } + + /* No entry is found. */ + return id; +} + +static errcode_t set_perms_and_caps(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + uint64_t capabilities = 0; + struct ext2_inode inode; + struct vfs_cap_data cap_data; + unsigned int uid = 0, gid = 0, imode = 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, _("while reading inode %u"), ino); + return retval; + } + + /* Permissions */ + if (params->fs_config_func != NULL) { + const char *filename = params->filename; + if (strcmp(filename, params->mountpoint) == 0) { + /* The root of the filesystem needs to be an empty string. */ + filename = ""; + } + params->fs_config_func(filename, S_ISDIR(inode.i_mode), + params->target_out, &uid, &gid, &imode, + &capabilities); + uid = resolve_ugid(params->uid_map, uid); + gid = resolve_ugid(params->gid_map, gid); + inode.i_uid = (__u16) uid; + inode.i_gid = (__u16) gid; + ext2fs_set_i_uid_high(inode, (__u16) (uid >> 16)); + ext2fs_set_i_gid_high(inode, (__u16) (gid >> 16)); + inode.i_mode = (inode.i_mode & S_IFMT) | (imode & 0xffff); + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while writing inode %u"), ino); + return retval; + } + } + + /* Capabilities */ + if (!capabilities) + return 0; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (capabilities & 0xffffffff); + cap_data.data[1].permitted = (uint32_t) (capabilities >> 32); + return ino_add_xattr(fs, ino, "security." XATTR_CAPS_SUFFIX, + &cap_data, sizeof(cap_data)); +} + +static errcode_t set_timestamp(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + struct ext2_inode inode; + struct stat stat; + char *src_filename = NULL; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while reading inode %u"), ino); + return retval; + } + + if (params->fixed_time == -1 && params->src_dir) { + /* replace mountpoint from filename with src_dir */ + if (asprintf(&src_filename, "%s/%s", params->src_dir, + params->filename + strlen(params->mountpoint)) < 0) { + return -ENOMEM; + } + retval = lstat(src_filename, &stat); + if (retval < 0) { + com_err(__func__, errno, + _("while lstat file %s"), src_filename); + goto end; + } + inode.i_atime = inode.i_ctime = inode.i_mtime = stat.st_mtime; + } else { + inode.i_atime = inode.i_ctime = inode.i_mtime = params->fixed_time; + } + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while writing inode %u"), ino); + goto end; + } + +end: + free(src_filename); + return retval; +} + +static int is_dir(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + + if (ext2fs_read_inode(fs, ino, &inode)) + return 0; + return S_ISDIR(inode.i_mode); +} + +static errcode_t androidify_inode(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + + retval = set_timestamp(fs, ino, params); + if (retval) + return retval; + + retval = set_selinux_xattr(fs, ino, params); + if (retval) + return retval; + + return set_perms_and_caps(fs, ino, params); +} + +static int walk_dir(ext2_ino_t dir EXT2FS_ATTR((unused)), + int flags EXT2FS_ATTR((unused)), + struct ext2_dir_entry *de, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), void *priv_data) +{ + __u16 name_len; + errcode_t retval; + struct inode_params *params = (struct inode_params *)priv_data; + + name_len = de->name_len & 0xff; + if (!strncmp(de->name, ".", name_len) + || (!strncmp(de->name, "..", name_len))) + return 0; + + if (asprintf(¶ms->filename, "%s/%.*s", params->path, name_len, + de->name) < 0) { + params->error = ENOMEM; + return -ENOMEM; + } + + if (!strncmp(de->name, "lost+found", 10)) { + retval = set_selinux_xattr(params->fs, de->inode, params); + if (retval) + goto end; + } else { + retval = androidify_inode(params->fs, de->inode, params); + if (retval) + goto end; + if (is_dir(params->fs, de->inode)) { + char *cur_path = params->path; + char *cur_filename = params->filename; + params->path = params->filename; + retval = ext2fs_dir_iterate2(params->fs, de->inode, 0, NULL, + walk_dir, params); + if (retval) + goto end; + params->path = cur_path; + params->filename = cur_filename; + } + } + +end: + free(params->filename); + params->error |= retval; + return retval; +} + +errcode_t __android_configure_fs(ext2_filsys fs, char *src_dir, + char *target_out, + char *mountpoint, + fs_config_f fs_config_func, + struct selabel_handle *sehnd, + time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map) +{ + errcode_t retval; + struct inode_params params = { + .fs = fs, + .src_dir = src_dir, + .target_out = target_out, + .fs_config_func = fs_config_func, + .sehnd = sehnd, + .fixed_time = fixed_time, + .path = mountpoint, + .filename = mountpoint, + .mountpoint = mountpoint, + .uid_map = uid_map, + .gid_map = gid_map, + .error = 0 + }; + + /* walk_dir will add the "/". Don't add it twice. */ + if (strlen(mountpoint) == 1 && mountpoint[0] == '/') + params.path = ""; + + retval = androidify_inode(fs, EXT2_ROOT_INO, ¶ms); + if (retval) + return retval; + + retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_dir, + ¶ms); + if (retval) + return retval; + return params.error; +} + +errcode_t android_configure_fs(ext2_filsys fs, char *src_dir, char *target_out, + char *mountpoint, + struct selinux_opt *seopts EXT2FS_ATTR((unused)), + unsigned int nopt EXT2FS_ATTR((unused)), + char *fs_config_file, time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map) +{ + errcode_t retval; + fs_config_f fs_config_func = NULL; + struct selabel_handle *sehnd = NULL; + + /* Retrieve file contexts */ +#if !defined(__ANDROID__) + if (nopt > 0) { + sehnd = selabel_open(SELABEL_CTX_FILE, seopts, nopt); + if (!sehnd) { + int saved_errno = errno; + com_err(__func__, errno, + _("while opening file contexts \"%s\""), + seopts[0].value); + return saved_errno; + } + } +#else + sehnd = selinux_android_file_context_handle(); + if (!sehnd) { + com_err(__func__, EINVAL, + _("while opening android file_contexts")); + return EINVAL; + } +#endif + + /* Load the FS config */ + if (fs_config_file) { + retval = load_canned_fs_config(fs_config_file); + if (retval < 0) { + com_err(__func__, retval, + _("while loading fs_config \"%s\""), + fs_config_file); + return retval; + } + fs_config_func = canned_fs_config; + } else if (mountpoint) + fs_config_func = fs_config; + + return __android_configure_fs(fs, src_dir, target_out, mountpoint, + fs_config_func, sehnd, fixed_time, + uid_map, gid_map); +} diff --git a/contrib/android/perms.h b/contrib/android/perms.h new file mode 100644 index 0000000..9ea3f95 --- /dev/null +++ b/contrib/android/perms.h @@ -0,0 +1,65 @@ +#ifndef ANDROID_PERMS_H +# define ANDROID_PERMS_H + +# include <ext2fs/ext2fs.h> + +typedef void (*fs_config_f)(const char *path, int dir, + const char *target_out_path, + unsigned *uid, unsigned *gid, + unsigned *mode, uint64_t *capabilities); + +/* + * Represents a range of UID/GID mapping. + * This maps the id in [|parent_id|, |parent_id| + |length|) into + * [|child_id|, |child_id| + |length|) + */ +struct ugid_map_entry { + unsigned int child_id; + unsigned int parent_id; + unsigned int length; +}; + +struct ugid_map { + /* The number of elements in |entries|. */ + size_t size; + + /* An array of entries. If |size| is 0, this is a null pointer. */ + struct ugid_map_entry* entries; +}; + +# ifdef _WIN32 +struct selabel_handle; +static inline errcode_t android_configure_fs(ext2_filsys fs, + char *src_dir, + char *target_out, + char *mountpoint, + void *seopts, + unsigned int nopt, + char *fs_config_file, + time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugdi_map* gid_map) +{ + return 0; +} +# else +# include <selinux/selinux.h> +# include <selinux/label.h> +# if defined(__ANDROID__) +# include <selinux/android.h> +# endif +# include <private/android_filesystem_config.h> +# include <private/canned_fs_config.h> +# include <private/fs_config.h> + +errcode_t android_configure_fs(ext2_filsys fs, char *src_dir, + char *target_out, + char *mountpoint, + struct selinux_opt *seopts, + unsigned int nopt, + char *fs_config_file, time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map); + +# endif +#endif /* !ANDROID_PERMS_H */ |