diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/android/e2fsdroid.c | 377 |
1 files changed, 377 insertions, 0 deletions
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; +} |