summaryrefslogtreecommitdiffstats
path: root/contrib/android
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/android')
-rw-r--r--contrib/android/Android.bp78
-rw-r--r--contrib/android/base_fs.c211
-rw-r--r--contrib/android/base_fs.h17
-rw-r--r--contrib/android/basefs_allocator.c388
-rw-r--r--contrib/android/basefs_allocator.h16
-rw-r--r--contrib/android/block_list.c89
-rw-r--r--contrib/android/block_list.h8
-rw-r--r--contrib/android/block_range.c80
-rw-r--r--contrib/android/block_range.h29
-rw-r--r--contrib/android/e2fsdroid.c377
-rw-r--r--contrib/android/ext2simg.c224
-rw-r--r--contrib/android/fsmap.c169
-rw-r--r--contrib/android/fsmap.h29
-rw-r--r--contrib/android/perms.c376
-rw-r--r--contrib/android/perms.h65
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(&params->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(&params->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(&params->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(&params->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(&params->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, &params);
+ if (retval)
+ return retval;
+
+ retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_dir,
+ &params);
+ 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 */