From fb5c011b4912fb14cfa87a06a5542c06aecc9278 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sat, 4 Apr 2015 22:26:16 +0200 Subject: [PATCH] Import make_ext4fs sources Signed-off-by: Jo-Philipp Wich --- Android.mk | 154 ++++++++ MODULE_LICENSE_APACHE2 | 0 NOTICE | 190 ++++++++++ allocate.c | 781 +++++++++++++++++++++++++++++++++++++++ allocate.h | 74 ++++ canned_fs_config.c | 109 ++++++ canned_fs_config.h | 26 ++ contents.c | 490 +++++++++++++++++++++++++ contents.h | 45 +++ crc16.c | 58 +++ e4crypt_static.c | 147 ++++++++ ext2simg.c | 195 ++++++++++ ext4.h | 577 +++++++++++++++++++++++++++++ ext4_crypt.cpp | 120 ++++++ ext4_crypt.h | 50 +++ ext4_crypt_init_extensions.cpp | 269 ++++++++++++++ ext4_extents.h | 88 +++++ ext4_kernel_headers.h | 59 +++ ext4_sb.c | 47 +++ ext4_sb.h | 52 +++ ext4_utils.c | 544 +++++++++++++++++++++++++++ ext4_utils.h | 170 +++++++++ ext4fixup.c | 811 +++++++++++++++++++++++++++++++++++++++++ ext4fixup.h | 20 + ext4fixup_main.c | 68 ++++ extent.c | 232 ++++++++++++ extent.h | 30 ++ indirect.c | 514 ++++++++++++++++++++++++++ indirect.h | 29 ++ jbd2.h | 141 +++++++ make_ext4fs.c | 671 ++++++++++++++++++++++++++++++++++ make_ext4fs.h | 35 ++ make_ext4fs_main.c | 237 ++++++++++++ mkuserimg.sh | 106 ++++++ setup_fs.c | 85 +++++ sha1.c | 273 ++++++++++++++ sha1.h | 43 +++ test_ext4fixup | 71 ++++ unencrypted_properties.cpp | 86 +++++ unencrypted_properties.h | 70 ++++ uuid.c | 65 ++++ uuid.h | 24 ++ wipe.c | 76 ++++ wipe.h | 33 ++ xattr.h | 45 +++ 45 files changed, 8010 insertions(+) create mode 100644 Android.mk create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 NOTICE create mode 100644 allocate.c create mode 100644 allocate.h create mode 100644 canned_fs_config.c create mode 100644 canned_fs_config.h create mode 100644 contents.c create mode 100644 contents.h create mode 100644 crc16.c create mode 100644 e4crypt_static.c create mode 100644 ext2simg.c create mode 100644 ext4.h create mode 100644 ext4_crypt.cpp create mode 100644 ext4_crypt.h create mode 100644 ext4_crypt_init_extensions.cpp create mode 100644 ext4_extents.h create mode 100644 ext4_kernel_headers.h create mode 100644 ext4_sb.c create mode 100644 ext4_sb.h create mode 100644 ext4_utils.c create mode 100644 ext4_utils.h create mode 100644 ext4fixup.c create mode 100644 ext4fixup.h create mode 100644 ext4fixup_main.c create mode 100644 extent.c create mode 100644 extent.h create mode 100644 indirect.c create mode 100644 indirect.h create mode 100644 jbd2.h create mode 100644 make_ext4fs.c create mode 100644 make_ext4fs.h create mode 100644 make_ext4fs_main.c create mode 100755 mkuserimg.sh create mode 100644 setup_fs.c create mode 100644 sha1.c create mode 100644 sha1.h create mode 100755 test_ext4fixup create mode 100644 unencrypted_properties.cpp create mode 100644 unencrypted_properties.h create mode 100644 uuid.c create mode 100644 uuid.h create mode 100644 wipe.c create mode 100644 wipe.h create mode 100644 xattr.h diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..9f56cca --- /dev/null +++ b/Android.mk @@ -0,0 +1,154 @@ +# Copyright 2010 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) + +libext4_utils_src_files := \ + make_ext4fs.c \ + ext4fixup.c \ + ext4_utils.c \ + allocate.c \ + contents.c \ + extent.c \ + indirect.c \ + uuid.c \ + sha1.c \ + wipe.c \ + crc16.c \ + ext4_sb.c + +# +# -- All host/targets including windows +# + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(libext4_utils_src_files) +LOCAL_MODULE := libext4_utils_host +LOCAL_STATIC_LIBRARIES := \ + libsparse_host \ + libz +ifneq ($(HOST_OS),windows) + LOCAL_STATIC_LIBRARIES += libselinux +endif +include $(BUILD_HOST_STATIC_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c +LOCAL_MODULE := make_ext4fs +LOCAL_STATIC_LIBRARIES += \ + libcutils \ + libext4_utils_host \ + libsparse_host \ + libz +ifeq ($(HOST_OS),windows) + LOCAL_LDLIBS += -lws2_32 +else + LOCAL_STATIC_LIBRARIES += libselinux + LOCAL_CFLAGS := -DHOST +endif +include $(BUILD_HOST_EXECUTABLE) + + +# +# -- All host/targets excluding windows +# + +libext4_utils_src_files += \ + ext4_crypt.cpp \ + e4crypt_static.c \ + unencrypted_properties.cpp + +ifneq ($(HOST_OS),windows) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(libext4_utils_src_files) +LOCAL_MODULE := libext4_utils +LOCAL_C_INCLUDES += system/core/logwrapper/include +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libselinux \ + libsparse \ + libz +include $(BUILD_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(libext4_utils_src_files) \ + ext4_crypt_init_extensions.cpp +LOCAL_MODULE := libext4_utils_static +LOCAL_STATIC_LIBRARIES := \ + libsparse_static +include $(BUILD_STATIC_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c +LOCAL_MODULE := make_ext4fs +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libext4_utils \ + libselinux \ + libz +include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := ext2simg.c +LOCAL_MODULE := ext2simg +LOCAL_SHARED_LIBRARIES += \ + libext4_utils \ + libselinux \ + libsparse \ + libz +include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := ext2simg.c +LOCAL_MODULE := ext2simg +LOCAL_STATIC_LIBRARIES += \ + libext4_utils_host \ + libselinux \ + libsparse_host \ + libz +include $(BUILD_HOST_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := setup_fs.c +LOCAL_MODULE := setup_fs +LOCAL_SHARED_LIBRARIES += libcutils +include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := ext4fixup_main.c +LOCAL_MODULE := ext4fixup +LOCAL_SHARED_LIBRARIES += \ + libext4_utils \ + libsparse \ + libz +include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := ext4fixup_main.c +LOCAL_MODULE := ext4fixup +LOCAL_STATIC_LIBRARIES += \ + libext4_utils_host \ + libsparse_host \ + libz +include $(BUILD_HOST_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_MODULE := mkuserimg.sh +LOCAL_SRC_FILES := mkuserimg.sh +LOCAL_MODULE_CLASS := EXECUTABLES +# We don't need any additional suffix. +LOCAL_MODULE_SUFFIX := +LOCAL_BUILT_MODULE_STEM := $(notdir $(LOCAL_SRC_FILES)) +LOCAL_IS_HOST_MODULE := true +include $(BUILD_PREBUILT) + +endif diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..5d14293 --- /dev/null +++ b/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2010, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/allocate.c b/allocate.c new file mode 100644 index 0000000..cca3dc1 --- /dev/null +++ b/allocate.c @@ -0,0 +1,781 @@ +/* + * Copyright (C) 2010 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 "ext4_utils.h" +#include "allocate.h" + +#include + +#include +#include + +struct region { + u32 block; + u32 len; + int bg; + struct region *next; + struct region *prev; +}; + +struct block_group_info { + u32 first_block; + int header_blocks; + int data_blocks_used; + int has_superblock; + u8 *bitmaps; + u8 *block_bitmap; + u8 *inode_bitmap; + u8 *inode_table; + u32 free_blocks; + u32 first_free_block; + u32 free_inodes; + u32 first_free_inode; + u16 flags; + u16 used_dirs; +}; + +struct xattr_list_element { + struct ext4_inode *inode; + struct ext4_xattr_header *header; + struct xattr_list_element *next; +}; + +struct block_allocation *create_allocation() +{ + struct block_allocation *alloc = malloc(sizeof(struct block_allocation)); + alloc->list.first = NULL; + alloc->list.last = NULL; + alloc->oob_list.first = NULL; + alloc->oob_list.last = NULL; + alloc->list.iter = NULL; + alloc->list.partial_iter = 0; + alloc->oob_list.iter = NULL; + alloc->oob_list.partial_iter = 0; + alloc->filename = NULL; + alloc->next = NULL; + return alloc; +} + +static struct ext4_xattr_header *xattr_list_find(struct ext4_inode *inode) +{ + struct xattr_list_element *element; + for (element = aux_info.xattrs; element != NULL; element = element->next) { + if (element->inode == inode) + return element->header; + } + return NULL; +} + +static void xattr_list_insert(struct ext4_inode *inode, struct ext4_xattr_header *header) +{ + struct xattr_list_element *element = malloc(sizeof(struct xattr_list_element)); + element->inode = inode; + element->header = header; + element->next = aux_info.xattrs; + aux_info.xattrs = element; +} + +static void region_list_remove(struct region_list *list, struct region *reg) +{ + if (reg->prev) + reg->prev->next = reg->next; + + if (reg->next) + reg->next->prev = reg->prev; + + if (list->first == reg) + list->first = reg->next; + + if (list->last == reg) + list->last = reg->prev; + + reg->next = NULL; + reg->prev = NULL; +} + +static void region_list_append(struct region_list *list, struct region *reg) +{ + if (list->first == NULL) { + list->first = reg; + list->last = reg; + list->iter = reg; + list->partial_iter = 0; + reg->prev = NULL; + } else { + list->last->next = reg; + reg->prev = list->last; + list->last = reg; + } + reg->next = NULL; +} + +#if 0 +static void dump_starting_from(struct region *reg) +{ + for (; reg; reg = reg->next) { + printf("%p: Blocks %d-%d (%d)\n", reg, + reg->block, reg->block + reg->len - 1, reg->len) + } +} + +static void dump_region_lists(struct block_allocation *alloc) { + + printf("Main list:\n"); + dump_starting_from(alloc->list.first); + + printf("OOB list:\n"); + dump_starting_from(alloc->oob_list.first); +} +#endif + +void print_blocks(FILE* f, struct block_allocation *alloc) +{ + struct region *reg; + for (reg = alloc->list.first; reg; reg = reg->next) { + if (reg->len == 1) { + fprintf(f, " %d", reg->block); + } else { + fprintf(f, " %d-%d", reg->block, reg->block + reg->len - 1); + } + } + fputc('\n', f); +} + +void append_region(struct block_allocation *alloc, + u32 block, u32 len, int bg_num) +{ + struct region *reg; + reg = malloc(sizeof(struct region)); + reg->block = block; + reg->len = len; + reg->bg = bg_num; + reg->next = NULL; + + region_list_append(&alloc->list, reg); +} + +static void allocate_bg_inode_table(struct block_group_info *bg) +{ + if (bg->inode_table != NULL) + return; + + u32 block = bg->first_block + 2; + + if (bg->has_superblock) + block += aux_info.bg_desc_blocks + info.bg_desc_reserve_blocks + 1; + + bg->inode_table = calloc(aux_info.inode_table_blocks, info.block_size); + if (bg->inode_table == NULL) + critical_error_errno("calloc"); + + sparse_file_add_data(ext4_sparse_file, bg->inode_table, + aux_info.inode_table_blocks * info.block_size, block); + + bg->flags &= ~EXT4_BG_INODE_UNINIT; +} + +static int bitmap_set_bit(u8 *bitmap, u32 bit) +{ + if (bitmap[bit / 8] & 1 << (bit % 8)) + return 1; + + bitmap[bit / 8] |= 1 << (bit % 8); + return 0; +} + +static int bitmap_set_8_bits(u8 *bitmap, u32 bit) +{ + int ret = bitmap[bit / 8]; + bitmap[bit / 8] = 0xFF; + return ret; +} + +/* Marks a the first num_blocks blocks in a block group as used, and accounts + for them in the block group free block info. */ +static int reserve_blocks(struct block_group_info *bg, u32 start, u32 num) +{ + unsigned int i = 0; + + u32 block = start; + if (num > bg->free_blocks) + return -1; + + for (i = 0; i < num && block % 8 != 0; i++, block++) { + if (bitmap_set_bit(bg->block_bitmap, block)) { + error("attempted to reserve already reserved block"); + return -1; + } + } + + for (; i + 8 <= (num & ~7); i += 8, block += 8) { + if (bitmap_set_8_bits(bg->block_bitmap, block)) { + error("attempted to reserve already reserved block"); + return -1; + } + } + + for (; i < num; i++, block++) { + if (bitmap_set_bit(bg->block_bitmap, block)) { + error("attempted to reserve already reserved block"); + return -1; + } + } + + bg->free_blocks -= num; + if (start == bg->first_free_block) + bg->first_free_block = start + num; + + return 0; +} + +static void free_blocks(struct block_group_info *bg, u32 num_blocks) +{ + unsigned int i; + u32 block = bg->first_free_block - 1; + for (i = 0; i < num_blocks; i++, block--) + bg->block_bitmap[block / 8] &= ~(1 << (block % 8)); + bg->free_blocks += num_blocks; + bg->first_free_block -= num_blocks; +} + +/* Reduces an existing allocation by len blocks by return the last blocks + to the free pool in their block group. Assumes that the blocks being + returned were the last ones allocated out of the block group */ +void reduce_allocation(struct block_allocation *alloc, u32 len) +{ + while (len) { + struct region *last_reg = alloc->list.last; + + if (last_reg->len > len) { + free_blocks(&aux_info.bgs[last_reg->bg], len); + last_reg->len -= len; + len = 0; + } else { + struct region *reg = alloc->list.last->prev; + free_blocks(&aux_info.bgs[last_reg->bg], last_reg->len); + len -= last_reg->len; + if (reg) { + reg->next = NULL; + } else { + alloc->list.first = NULL; + alloc->list.last = NULL; + alloc->list.iter = NULL; + alloc->list.partial_iter = 0; + } + free(last_reg); + } + } +} + +static void init_bg(struct block_group_info *bg, unsigned int i) +{ + int header_blocks = 2 + aux_info.inode_table_blocks; + + bg->has_superblock = ext4_bg_has_super_block(i); + + if (bg->has_superblock) + header_blocks += 1 + aux_info.bg_desc_blocks + info.bg_desc_reserve_blocks; + + bg->bitmaps = calloc(info.block_size, 2); + bg->block_bitmap = bg->bitmaps; + bg->inode_bitmap = bg->bitmaps + info.block_size; + + bg->header_blocks = header_blocks; + bg->first_block = aux_info.first_data_block + i * info.blocks_per_group; + + u32 block = bg->first_block; + if (bg->has_superblock) + block += 1 + aux_info.bg_desc_blocks + info.bg_desc_reserve_blocks; + sparse_file_add_data(ext4_sparse_file, bg->bitmaps, 2 * info.block_size, + block); + + bg->data_blocks_used = 0; + bg->free_blocks = info.blocks_per_group; + bg->first_free_block = 0; + bg->free_inodes = info.inodes_per_group; + bg->first_free_inode = 1; + bg->flags = EXT4_BG_INODE_UNINIT; + + if (reserve_blocks(bg, bg->first_free_block, bg->header_blocks) < 0) + error("failed to reserve %u blocks in block group %u\n", bg->header_blocks, i); + + if (bg->first_block + info.blocks_per_group > aux_info.len_blocks) { + u32 overrun = bg->first_block + info.blocks_per_group - aux_info.len_blocks; + reserve_blocks(bg, info.blocks_per_group - overrun, overrun); + } +} + +void block_allocator_init() +{ + unsigned int i; + + aux_info.bgs = calloc(sizeof(struct block_group_info), aux_info.groups); + if (aux_info.bgs == NULL) + critical_error_errno("calloc"); + + for (i = 0; i < aux_info.groups; i++) + init_bg(&aux_info.bgs[i], i); +} + +void block_allocator_free() +{ + unsigned int i; + + for (i = 0; i < aux_info.groups; i++) { + free(aux_info.bgs[i].bitmaps); + free(aux_info.bgs[i].inode_table); + } + free(aux_info.bgs); +} + +static u32 ext4_allocate_blocks_from_block_group(u32 len, int bg_num) +{ + if (get_free_blocks(bg_num) < len) + return EXT4_ALLOCATE_FAILED; + + u32 block = aux_info.bgs[bg_num].first_free_block; + struct block_group_info *bg = &aux_info.bgs[bg_num]; + if (reserve_blocks(bg, bg->first_free_block, len) < 0) { + error("failed to reserve %u blocks in block group %u\n", len, bg_num); + return EXT4_ALLOCATE_FAILED; + } + + aux_info.bgs[bg_num].data_blocks_used += len; + + return bg->first_block + block; +} + +/* Allocate a single block and return its block number */ +u32 allocate_block() +{ + unsigned int i; + for (i = 0; i < aux_info.groups; i++) { + u32 block = ext4_allocate_blocks_from_block_group(1, i); + + if (block != EXT4_ALLOCATE_FAILED) + return block; + } + + return EXT4_ALLOCATE_FAILED; +} + +static struct region *ext4_allocate_best_fit_partial(u32 len) +{ + unsigned int i; + unsigned int found_bg = 0; + u32 found_bg_len = 0; + + for (i = 0; i < aux_info.groups; i++) { + u32 bg_len = aux_info.bgs[i].free_blocks; + + if ((len <= bg_len && (found_bg_len == 0 || bg_len < found_bg_len)) || + (len > found_bg_len && bg_len > found_bg_len)) { + found_bg = i; + found_bg_len = bg_len; + } + } + + if (found_bg_len) { + u32 allocate_len = min(len, found_bg_len); + struct region *reg; + u32 block = ext4_allocate_blocks_from_block_group(allocate_len, found_bg); + if (block == EXT4_ALLOCATE_FAILED) { + error("failed to allocate %d blocks in block group %d", allocate_len, found_bg); + return NULL; + } + reg = malloc(sizeof(struct region)); + reg->block = block; + reg->len = allocate_len; + reg->next = NULL; + reg->prev = NULL; + reg->bg = found_bg; + return reg; + } else { + error("failed to allocate %u blocks, out of space?", len); + } + + return NULL; +} + +static struct region *ext4_allocate_best_fit(u32 len) +{ + struct region *first_reg = NULL; + struct region *prev_reg = NULL; + struct region *reg; + + while (len > 0) { + reg = ext4_allocate_best_fit_partial(len); + if (reg == NULL) + return NULL; + + if (first_reg == NULL) + first_reg = reg; + + if (prev_reg) { + prev_reg->next = reg; + reg->prev = prev_reg; + } + + prev_reg = reg; + len -= reg->len; + } + + return first_reg; +} + +/* Allocate len blocks. The blocks may be spread across multiple block groups, + and are returned in a linked list of the blocks in each block group. The + allocation algorithm is: + 1. If the remaining allocation is larger than any available contiguous region, + allocate the largest contiguous region and loop + 2. Otherwise, allocate the smallest contiguous region that it fits in +*/ +struct block_allocation *allocate_blocks(u32 len) +{ + struct region *reg = ext4_allocate_best_fit(len); + + if (reg == NULL) + return NULL; + + struct block_allocation *alloc = create_allocation(); + alloc->list.first = reg; + alloc->list.last = reg; + alloc->list.iter = alloc->list.first; + alloc->list.partial_iter = 0; + return alloc; +} + +/* Returns the number of discontiguous regions used by an allocation */ +int block_allocation_num_regions(struct block_allocation *alloc) +{ + unsigned int i; + struct region *reg = alloc->list.first; + + for (i = 0; reg != NULL; reg = reg->next) + i++; + + return i; +} + +int block_allocation_len(struct block_allocation *alloc) +{ + unsigned int i; + struct region *reg = alloc->list.first; + + for (i = 0; reg != NULL; reg = reg->next) + i += reg->len; + + return i; +} + +/* Returns the block number of the block'th block in an allocation */ +u32 get_block(struct block_allocation *alloc, u32 block) +{ + struct region *reg = alloc->list.iter; + block += alloc->list.partial_iter; + + for (; reg; reg = reg->next) { + if (block < reg->len) + return reg->block + block; + block -= reg->len; + } + return EXT4_ALLOCATE_FAILED; +} + +u32 get_oob_block(struct block_allocation *alloc, u32 block) +{ + struct region *reg = alloc->oob_list.iter; + block += alloc->oob_list.partial_iter; + + for (; reg; reg = reg->next) { + if (block < reg->len) + return reg->block + block; + block -= reg->len; + } + return EXT4_ALLOCATE_FAILED; +} + +/* Gets the starting block and length in blocks of the first region + of an allocation */ +void get_region(struct block_allocation *alloc, u32 *block, u32 *len) +{ + *block = alloc->list.iter->block; + *len = alloc->list.iter->len - alloc->list.partial_iter; +} + +/* Move to the next region in an allocation */ +void get_next_region(struct block_allocation *alloc) +{ + alloc->list.iter = alloc->list.iter->next; + alloc->list.partial_iter = 0; +} + +/* Returns the number of free blocks in a block group */ +u32 get_free_blocks(u32 bg) +{ + return aux_info.bgs[bg].free_blocks; +} + +int last_region(struct block_allocation *alloc) +{ + return (alloc->list.iter == NULL); +} + +void rewind_alloc(struct block_allocation *alloc) +{ + alloc->list.iter = alloc->list.first; + alloc->list.partial_iter = 0; +} + +static struct region *do_split_allocation(struct block_allocation *alloc, u32 len) +{ + struct region *reg = alloc->list.iter; + struct region *new; + struct region *tmp; + + while (reg && len >= reg->len) { + len -= reg->len; + reg = reg->next; + } + + if (reg == NULL && len > 0) + return NULL; + + if (len > 0) { + new = malloc(sizeof(struct region)); + + new->bg = reg->bg; + new->block = reg->block + len; + new->len = reg->len - len; + new->next = reg->next; + new->prev = reg; + + reg->next = new; + reg->len = len; + + tmp = alloc->list.iter; + alloc->list.iter = new; + return tmp; + } else { + return reg; + } +} + +/* Splits an allocation into two allocations. The returned allocation will + point to the first half, and the original allocation ptr will point to the + second half. */ +static struct region *split_allocation(struct block_allocation *alloc, u32 len) +{ + /* First make sure there is a split at the current ptr */ + do_split_allocation(alloc, alloc->list.partial_iter); + + /* Then split off len blocks */ + struct region *middle = do_split_allocation(alloc, len); + alloc->list.partial_iter = 0; + return middle; +} + +/* Reserve the next blocks for oob data (indirect or extent blocks) */ +int reserve_oob_blocks(struct block_allocation *alloc, int blocks) +{ + struct region *oob = split_allocation(alloc, blocks); + struct region *next; + + if (oob == NULL) + return -1; + + while (oob && oob != alloc->list.iter) { + next = oob->next; + region_list_remove(&alloc->list, oob); + region_list_append(&alloc->oob_list, oob); + oob = next; + } + + return 0; +} + +static int advance_list_ptr(struct region_list *list, int blocks) +{ + struct region *reg = list->iter; + + while (reg != NULL && blocks > 0) { + if (reg->len > list->partial_iter + blocks) { + list->partial_iter += blocks; + return 0; + } + + blocks -= (reg->len - list->partial_iter); + list->partial_iter = 0; + reg = reg->next; + } + + if (blocks > 0) + return -1; + + return 0; +} + +/* Move the allocation pointer forward */ +int advance_blocks(struct block_allocation *alloc, int blocks) +{ + return advance_list_ptr(&alloc->list, blocks); +} + +int advance_oob_blocks(struct block_allocation *alloc, int blocks) +{ + return advance_list_ptr(&alloc->oob_list, blocks); +} + +int append_oob_allocation(struct block_allocation *alloc, u32 len) +{ + struct region *reg = ext4_allocate_best_fit(len); + + if (reg == NULL) { + error("failed to allocate %d blocks", len); + return -1; + } + + for (; reg; reg = reg->next) + region_list_append(&alloc->oob_list, reg); + + return 0; +} + +/* Returns an ext4_inode structure for an inode number */ +struct ext4_inode *get_inode(u32 inode) +{ + inode -= 1; + int bg = inode / info.inodes_per_group; + inode %= info.inodes_per_group; + + allocate_bg_inode_table(&aux_info.bgs[bg]); + return (struct ext4_inode *)(aux_info.bgs[bg].inode_table + inode * + info.inode_size); +} + +struct ext4_xattr_header *get_xattr_block_for_inode(struct ext4_inode *inode) +{ + struct ext4_xattr_header *block = xattr_list_find(inode); + if (block != NULL) + return block; + + u32 block_num = allocate_block(); + block = calloc(info.block_size, 1); + if (block == NULL) { + error("get_xattr: failed to allocate %d", info.block_size); + return NULL; + } + + block->h_magic = cpu_to_le32(EXT4_XATTR_MAGIC); + block->h_refcount = cpu_to_le32(1); + block->h_blocks = cpu_to_le32(1); + inode->i_blocks_lo = cpu_to_le32(le32_to_cpu(inode->i_blocks_lo) + (info.block_size / 512)); + inode->i_file_acl_lo = cpu_to_le32(block_num); + + int result = sparse_file_add_data(ext4_sparse_file, block, info.block_size, block_num); + if (result != 0) { + error("get_xattr: sparse_file_add_data failure %d", result); + free(block); + return NULL; + } + xattr_list_insert(inode, block); + return block; +} + +/* Mark the first len inodes in a block group as used */ +u32 reserve_inodes(int bg, u32 num) +{ + unsigned int i; + u32 inode; + + if (get_free_inodes(bg) < num) + return EXT4_ALLOCATE_FAILED; + + for (i = 0; i < num; i++) { + inode = aux_info.bgs[bg].first_free_inode + i - 1; + aux_info.bgs[bg].inode_bitmap[inode / 8] |= 1 << (inode % 8); + } + + inode = aux_info.bgs[bg].first_free_inode; + + aux_info.bgs[bg].first_free_inode += num; + aux_info.bgs[bg].free_inodes -= num; + + return inode; +} + +/* Returns the first free inode number + TODO: Inodes should be allocated in the block group of the data? */ +u32 allocate_inode() +{ + unsigned int bg; + u32 inode; + + for (bg = 0; bg < aux_info.groups; bg++) { + inode = reserve_inodes(bg, 1); + if (inode != EXT4_ALLOCATE_FAILED) + return bg * info.inodes_per_group + inode; + } + + return EXT4_ALLOCATE_FAILED; +} + +/* Returns the number of free inodes in a block group */ +u32 get_free_inodes(u32 bg) +{ + return aux_info.bgs[bg].free_inodes; +} + +/* Increments the directory count of the block group that contains inode */ +void add_directory(u32 inode) +{ + int bg = (inode - 1) / info.inodes_per_group; + aux_info.bgs[bg].used_dirs += 1; +} + +/* Returns the number of inodes in a block group that are directories */ +u16 get_directories(int bg) +{ + return aux_info.bgs[bg].used_dirs; +} + +/* Returns the flags for a block group */ +u16 get_bg_flags(int bg) +{ + return aux_info.bgs[bg].flags; +} + +/* Frees the memory used by a linked list of allocation regions */ +void free_alloc(struct block_allocation *alloc) +{ + struct region *reg; + + reg = alloc->list.first; + while (reg) { + struct region *next = reg->next; + free(reg); + reg = next; + } + + reg = alloc->oob_list.first; + while (reg) { + struct region *next = reg->next; + free(reg); + reg = next; + } + + free(alloc); +} diff --git a/allocate.h b/allocate.h new file mode 100644 index 0000000..5c26792 --- /dev/null +++ b/allocate.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _ALLOCATE_H_ +#define _ALLOCATE_H_ + +#define EXT4_ALLOCATE_FAILED (u32)(~0) + +#include "ext4_utils.h" + +struct region; + +struct region_list { + struct region *first; + struct region *last; + struct region *iter; + u32 partial_iter; +}; + +struct block_allocation { + struct region_list list; + struct region_list oob_list; + char* filename; + struct block_allocation* next; +}; + + +void block_allocator_init(); +void block_allocator_free(); +u32 allocate_block(); +struct block_allocation *allocate_blocks(u32 len); +int block_allocation_num_regions(struct block_allocation *alloc); +int block_allocation_len(struct block_allocation *alloc); +struct ext4_inode *get_inode(u32 inode); +struct ext4_xattr_header *get_xattr_block_for_inode(struct ext4_inode *inode); +void reduce_allocation(struct block_allocation *alloc, u32 len); +u32 get_block(struct block_allocation *alloc, u32 block); +u32 get_oob_block(struct block_allocation *alloc, u32 block); +void get_next_region(struct block_allocation *alloc); +void get_region(struct block_allocation *alloc, u32 *block, u32 *len); +u32 get_free_blocks(u32 bg); +u32 get_free_inodes(u32 bg); +u32 reserve_inodes(int bg, u32 inodes); +void add_directory(u32 inode); +u16 get_directories(int bg); +u16 get_bg_flags(int bg); +void init_unused_inode_tables(void); +u32 allocate_inode(); +void free_alloc(struct block_allocation *alloc); +int reserve_oob_blocks(struct block_allocation *alloc, int blocks); +int advance_blocks(struct block_allocation *alloc, int blocks); +int advance_oob_blocks(struct block_allocation *alloc, int blocks); +int last_region(struct block_allocation *alloc); +void rewind_alloc(struct block_allocation *alloc); +void append_region(struct block_allocation *alloc, + u32 block, u32 len, int bg); +struct block_allocation *create_allocation(); +int append_oob_allocation(struct block_allocation *alloc, u32 len); +void print_blocks(FILE* f, struct block_allocation *alloc); + +#endif diff --git a/canned_fs_config.c b/canned_fs_config.c new file mode 100644 index 0000000..69646b1 --- /dev/null +++ b/canned_fs_config.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 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 +#include +#include +#include +#include + +#include "private/android_filesystem_config.h" + +#include "canned_fs_config.h" + +typedef struct { + const char* path; + unsigned uid; + unsigned gid; + unsigned mode; + uint64_t capabilities; +} Path; + +static Path* canned_data = NULL; +static int canned_alloc = 0; +static int canned_used = 0; + +static int path_compare(const void* a, const void* b) { + return strcmp(((Path*)a)->path, ((Path*)b)->path); +} + +int load_canned_fs_config(const char* fn) { + FILE* f = fopen(fn, "r"); + if (f == NULL) { + fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno)); + return -1; + } + + char line[PATH_MAX + 200]; + while (fgets(line, sizeof(line), f)) { + while (canned_used >= canned_alloc) { + canned_alloc = (canned_alloc+1) * 2; + canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path)); + } + Path* p = canned_data + canned_used; + p->path = strdup(strtok(line, " ")); + p->uid = atoi(strtok(NULL, " ")); + p->gid = atoi(strtok(NULL, " ")); + p->mode = strtol(strtok(NULL, " "), NULL, 8); // mode is in octal + p->capabilities = 0; + + char* token = NULL; + do { + token = strtok(NULL, " "); + if (token && strncmp(token, "capabilities=", 13) == 0) { + p->capabilities = strtoll(token+13, NULL, 0); + break; + } + } while (token); + + canned_used++; + } + + fclose(f); + + qsort(canned_data, canned_used, sizeof(Path), path_compare); + printf("loaded %d fs_config entries\n", canned_used); + + return 0; +} + +void canned_fs_config(const char* path, int dir, + unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) { + Path key; + key.path = path+1; // canned paths lack the leading '/' + Path* p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare); + if (p == NULL) { + fprintf(stderr, "failed to find [%s] in canned fs_config\n", path); + exit(1); + } + *uid = p->uid; + *gid = p->gid; + *mode = p->mode; + *capabilities = p->capabilities; + +#if 0 + // for debugging, run the built-in fs_config and compare the results. + + unsigned c_uid, c_gid, c_mode; + uint64_t c_capabilities; + fs_config(path, dir, &c_uid, &c_gid, &c_mode, &c_capabilities); + + if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid); + if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid); + if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode); + if (c_capabilities != *capabilities) printf("%s capabilities %llx %llx\n", path, *capabilities, c_capabilities); +#endif +} diff --git a/canned_fs_config.h b/canned_fs_config.h new file mode 100644 index 0000000..aec923b --- /dev/null +++ b/canned_fs_config.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef _CANNED_FS_CONFIG_H +#define _CANNED_FS_CONFIG_H + +#include + +int load_canned_fs_config(const char* fn); +void canned_fs_config(const char* path, int dir, + unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities); + +#endif diff --git a/contents.c b/contents.c new file mode 100644 index 0000000..3144de9 --- /dev/null +++ b/contents.c @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2010 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 +#include +#include + +#ifdef HAVE_ANDROID_OS +#include +#else +#include +#endif + +#define XATTR_SELINUX_SUFFIX "selinux" +#define XATTR_CAPS_SUFFIX "capability" + +#include "ext4_utils.h" +#include "make_ext4fs.h" +#include "allocate.h" +#include "contents.h" +#include "extent.h" +#include "indirect.h" + +#ifdef USE_MINGW +#define S_IFLNK 0 /* used by make_link, not needed under mingw */ +#endif + +static struct block_allocation* saved_allocation_head = NULL; + +struct block_allocation* get_saved_allocation_chain() { + return saved_allocation_head; +} + +static u32 dentry_size(u32 entries, struct dentry *dentries) +{ + u32 len = 24; + unsigned int i; + unsigned int dentry_len; + + for (i = 0; i < entries; i++) { + dentry_len = 8 + EXT4_ALIGN(strlen(dentries[i].filename), 4); + if (len % info.block_size + dentry_len > info.block_size) + len += info.block_size - (len % info.block_size); + len += dentry_len; + } + + return len; +} + +static struct ext4_dir_entry_2 *add_dentry(u8 *data, u32 *offset, + struct ext4_dir_entry_2 *prev, u32 inode, const char *name, + u8 file_type) +{ + u8 name_len = strlen(name); + u16 rec_len = 8 + EXT4_ALIGN(name_len, 4); + struct ext4_dir_entry_2 *dentry; + + u32 start_block = *offset / info.block_size; + u32 end_block = (*offset + rec_len - 1) / info.block_size; + if (start_block != end_block) { + /* Adding this dentry will cross a block boundary, so pad the previous + dentry to the block boundary */ + if (!prev) + critical_error("no prev"); + prev->rec_len += end_block * info.block_size - *offset; + *offset = end_block * info.block_size; + } + + dentry = (struct ext4_dir_entry_2 *)(data + *offset); + dentry->inode = inode; + dentry->rec_len = rec_len; + dentry->name_len = name_len; + dentry->file_type = file_type; + memcpy(dentry->name, name, name_len); + + *offset += rec_len; + return dentry; +} + +/* Creates a directory structure for an array of directory entries, dentries, + and stores the location of the structure in an inode. The new inode's + .. link is set to dir_inode_num. Stores the location of the inode number + of each directory entry into dentries[i].inode, to be filled in later + when the inode for the entry is allocated. Returns the inode number of the + new directory */ +u32 make_directory(u32 dir_inode_num, u32 entries, struct dentry *dentries, + u32 dirs) +{ + struct ext4_inode *inode; + u32 blocks; + u32 len; + u32 offset = 0; + u32 inode_num; + u8 *data; + unsigned int i; + struct ext4_dir_entry_2 *dentry; + + blocks = DIV_ROUND_UP(dentry_size(entries, dentries), info.block_size); + len = blocks * info.block_size; + + if (dir_inode_num) { + inode_num = allocate_inode(info); + } else { + dir_inode_num = EXT4_ROOT_INO; + inode_num = EXT4_ROOT_INO; + } + + if (inode_num == EXT4_ALLOCATE_FAILED) { + error("failed to allocate inode\n"); + return EXT4_ALLOCATE_FAILED; + } + + add_directory(inode_num); + + inode = get_inode(inode_num); + if (inode == NULL) { + error("failed to get inode %u", inode_num); + return EXT4_ALLOCATE_FAILED; + } + + data = inode_allocate_data_extents(inode, len, len); + if (data == NULL) { + error("failed to allocate %u extents", len); + return EXT4_ALLOCATE_FAILED; + } + + inode->i_mode = S_IFDIR; + inode->i_links_count = dirs + 2; + inode->i_flags |= aux_info.default_i_flags; + + dentry = NULL; + + dentry = add_dentry(data, &offset, NULL, inode_num, ".", EXT4_FT_DIR); + if (!dentry) { + error("failed to add . directory"); + return EXT4_ALLOCATE_FAILED; + } + + dentry = add_dentry(data, &offset, dentry, dir_inode_num, "..", EXT4_FT_DIR); + if (!dentry) { + error("failed to add .. directory"); + return EXT4_ALLOCATE_FAILED; + } + + for (i = 0; i < entries; i++) { + dentry = add_dentry(data, &offset, dentry, 0, + dentries[i].filename, dentries[i].file_type); + if (offset > len || (offset == len && i != entries - 1)) + critical_error("internal error: dentry for %s ends at %d, past %d\n", + dentries[i].filename, offset, len); + dentries[i].inode = &dentry->inode; + if (!dentry) { + error("failed to add directory"); + return EXT4_ALLOCATE_FAILED; + } + } + + /* pad the last dentry out to the end of the block */ + dentry->rec_len += len - offset; + + return inode_num; +} + +/* Creates a file on disk. Returns the inode number of the new file */ +u32 make_file(const char *filename, u64 len) +{ + struct ext4_inode *inode; + u32 inode_num; + + inode_num = allocate_inode(info); + if (inode_num == EXT4_ALLOCATE_FAILED) { + error("failed to allocate inode\n"); + return EXT4_ALLOCATE_FAILED; + } + + inode = get_inode(inode_num); + if (inode == NULL) { + error("failed to get inode %u", inode_num); + return EXT4_ALLOCATE_FAILED; + } + + if (len > 0) { + struct block_allocation* alloc = inode_allocate_file_extents(inode, len, filename); + if (alloc) { + alloc->filename = strdup(filename); + alloc->next = saved_allocation_head; + saved_allocation_head = alloc; + } + } + + inode->i_mode = S_IFREG; + inode->i_links_count = 1; + inode->i_flags |= aux_info.default_i_flags; + + return inode_num; +} + +/* Creates a file on disk. Returns the inode number of the new file */ +u32 make_link(const char *link) +{ + struct ext4_inode *inode; + u32 inode_num; + u32 len = strlen(link); + + inode_num = allocate_inode(info); + if (inode_num == EXT4_ALLOCATE_FAILED) { + error("failed to allocate inode\n"); + return EXT4_ALLOCATE_FAILED; + } + + inode = get_inode(inode_num); + if (inode == NULL) { + error("failed to get inode %u", inode_num); + return EXT4_ALLOCATE_FAILED; + } + + inode->i_mode = S_IFLNK; + inode->i_links_count = 1; + inode->i_flags |= aux_info.default_i_flags; + inode->i_size_lo = len; + + if (len + 1 <= sizeof(inode->i_block)) { + /* Fast symlink */ + memcpy((char*)inode->i_block, link, len); + } else { + u8 *data = inode_allocate_data_indirect(inode, info.block_size, info.block_size); + memcpy(data, link, len); + inode->i_blocks_lo = info.block_size / 512; + } + + return inode_num; +} + +int inode_set_permissions(u32 inode_num, u16 mode, u16 uid, u16 gid, u32 mtime) +{ + struct ext4_inode *inode = get_inode(inode_num); + + if (!inode) + return -1; + + inode->i_mode |= mode; + inode->i_uid = uid; + inode->i_gid = gid; + inode->i_mtime = mtime; + inode->i_atime = mtime; + inode->i_ctime = mtime; + + return 0; +} + +/* + * Returns the amount of free space available in the specified + * xattr region + */ +static size_t xattr_free_space(struct ext4_xattr_entry *entry, char *end) +{ + while(!IS_LAST_ENTRY(entry) && (((char *) entry) < end)) { + end -= EXT4_XATTR_SIZE(le32_to_cpu(entry->e_value_size)); + entry = EXT4_XATTR_NEXT(entry); + } + + if (((char *) entry) > end) { + error("unexpected read beyond end of xattr space"); + return 0; + } + + return end - ((char *) entry); +} + +/* + * Returns a pointer to the free space immediately after the + * last xattr element + */ +static struct ext4_xattr_entry* xattr_get_last(struct ext4_xattr_entry *entry) +{ + for (; !IS_LAST_ENTRY(entry); entry = EXT4_XATTR_NEXT(entry)) { + // skip entry + } + return entry; +} + +/* + * assert that the elements in the ext4 xattr section are in sorted order + * + * The ext4 filesystem requires extended attributes to be sorted when + * they're not stored in the inode. The kernel ext4 code uses the following + * sorting algorithm: + * + * 1) First sort extended attributes by their name_index. For example, + * EXT4_XATTR_INDEX_USER (1) comes before EXT4_XATTR_INDEX_SECURITY (6). + * 2) If the name_indexes are equal, then sorting is based on the length + * of the name. For example, XATTR_SELINUX_SUFFIX ("selinux") comes before + * XATTR_CAPS_SUFFIX ("capability") because "selinux" is shorter than "capability" + * 3) If the name_index and name_length are equal, then memcmp() is used to determine + * which name comes first. For example, "selinux" would come before "yelinux". + * + * This method is intended to implement the sorting function defined in + * the Linux kernel file fs/ext4/xattr.c function ext4_xattr_find_entry(). + */ +static void xattr_assert_sane(struct ext4_xattr_entry *entry) +{ + for( ; !IS_LAST_ENTRY(entry); entry = EXT4_XATTR_NEXT(entry)) { + struct ext4_xattr_entry *next = EXT4_XATTR_NEXT(entry); + if (IS_LAST_ENTRY(next)) { + return; + } + + int cmp = next->e_name_index - entry->e_name_index; + if (cmp == 0) + cmp = next->e_name_len - entry->e_name_len; + if (cmp == 0) + cmp = memcmp(next->e_name, entry->e_name, next->e_name_len); + if (cmp < 0) { + error("BUG: extended attributes are not sorted\n"); + return; + } + if (cmp == 0) { + error("BUG: duplicate extended attributes detected\n"); + return; + } + } +} + +#define NAME_HASH_SHIFT 5 +#define VALUE_HASH_SHIFT 16 + +static void ext4_xattr_hash_entry(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + u32 hash = 0; + char *name = entry->e_name; + int n; + + for (n = 0; n < entry->e_name_len; n++) { + hash = (hash << NAME_HASH_SHIFT) ^ + (hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^ + *name++; + } + + if (entry->e_value_block == 0 && entry->e_value_size != 0) { + u32 *value = (u32 *)((char *)header + + le16_to_cpu(entry->e_value_offs)); + for (n = (le32_to_cpu(entry->e_value_size) + + EXT4_XATTR_ROUND) >> EXT4_XATTR_PAD_BITS; n; n--) { + hash = (hash << VALUE_HASH_SHIFT) ^ + (hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^ + le32_to_cpu(*value++); + } + } + entry->e_hash = cpu_to_le32(hash); +} + +#undef NAME_HASH_SHIFT +#undef VALUE_HASH_SHIFT + +static struct ext4_xattr_entry* xattr_addto_range( + void *block_start, + void *block_end, + struct ext4_xattr_entry *first, + int name_index, + const char *name, + const void *value, + size_t value_len) +{ + size_t name_len = strlen(name); + if (name_len > 255) + return NULL; + + size_t available_size = xattr_free_space(first, block_end); + size_t needed_size = EXT4_XATTR_LEN(name_len) + EXT4_XATTR_SIZE(value_len); + + if (needed_size > available_size) + return NULL; + + struct ext4_xattr_entry *new_entry = xattr_get_last(first); + memset(new_entry, 0, EXT4_XATTR_LEN(name_len)); + + new_entry->e_name_len = name_len; + new_entry->e_name_index = name_index; + memcpy(new_entry->e_name, name, name_len); + new_entry->e_value_block = 0; + new_entry->e_value_size = cpu_to_le32(value_len); + + char *val = (char *) new_entry + available_size - EXT4_XATTR_SIZE(value_len); + size_t e_value_offs = val - (char *) block_start; + + new_entry->e_value_offs = cpu_to_le16(e_value_offs); + memset(val, 0, EXT4_XATTR_SIZE(value_len)); + memcpy(val, value, value_len); + + xattr_assert_sane(first); + return new_entry; +} + +static int xattr_addto_inode(struct ext4_inode *inode, int name_index, + const char *name, const void *value, size_t value_len) +{ + struct ext4_xattr_ibody_header *hdr = (struct ext4_xattr_ibody_header *) (inode + 1); + struct ext4_xattr_entry *first = (struct ext4_xattr_entry *) (hdr + 1); + char *block_end = ((char *) inode) + info.inode_size; + + struct ext4_xattr_entry *result = + xattr_addto_range(first, block_end, first, name_index, name, value, value_len); + + if (result == NULL) + return -1; + + hdr->h_magic = cpu_to_le32(EXT4_XATTR_MAGIC); + inode->i_extra_isize = cpu_to_le16(sizeof(struct ext4_inode) - EXT4_GOOD_OLD_INODE_SIZE); + + return 0; +} + +static int xattr_addto_block(struct ext4_inode *inode, int name_index, + const char *name, const void *value, size_t value_len) +{ + struct ext4_xattr_header *header = get_xattr_block_for_inode(inode); + if (!header) + return -1; + + struct ext4_xattr_entry *first = (struct ext4_xattr_entry *) (header + 1); + char *block_end = ((char *) header) + info.block_size; + + struct ext4_xattr_entry *result = + xattr_addto_range(header, block_end, first, name_index, name, value, value_len); + + if (result == NULL) + return -1; + + ext4_xattr_hash_entry(header, result); + return 0; +} + + +static int xattr_add(u32 inode_num, int name_index, const char *name, + const void *value, size_t value_len) +{ + if (!value) + return 0; + + struct ext4_inode *inode = get_inode(inode_num); + + if (!inode) + return -1; + + int result = xattr_addto_inode(inode, name_index, name, value, value_len); + if (result != 0) { + result = xattr_addto_block(inode, name_index, name, value, value_len); + } + return result; +} + +int inode_set_selinux(u32 inode_num, const char *secon) +{ + if (!secon) + return 0; + + return xattr_add(inode_num, EXT4_XATTR_INDEX_SECURITY, + XATTR_SELINUX_SUFFIX, secon, strlen(secon) + 1); +} + +int inode_set_capabilities(u32 inode_num, uint64_t capabilities) { + if (capabilities == 0) + return 0; + + struct vfs_cap_data cap_data; + memset(&cap_data, 0, sizeof(cap_data)); + + cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (capabilities & 0xffffffff); + cap_data.data[0].inheritable = 0; + cap_data.data[1].permitted = (uint32_t) (capabilities >> 32); + cap_data.data[1].inheritable = 0; + + return xattr_add(inode_num, EXT4_XATTR_INDEX_SECURITY, + XATTR_CAPS_SUFFIX, &cap_data, sizeof(cap_data)); +} diff --git a/contents.h b/contents.h new file mode 100644 index 0000000..e57687e --- /dev/null +++ b/contents.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +struct dentry { + char *path; + char *full_path; + const char *filename; + char *link; + unsigned long size; + u8 file_type; + u16 mode; + u16 uid; + u16 gid; + u32 *inode; + u32 mtime; + char *secon; + uint64_t capabilities; +}; + +u32 make_directory(u32 dir_inode_num, u32 entries, struct dentry *dentries, + u32 dirs); +u32 make_file(const char *filename, u64 len); +u32 make_link(const char *link); +int inode_set_permissions(u32 inode_num, u16 mode, u16 uid, u16 gid, u32 mtime); +int inode_set_selinux(u32 inode_num, const char *secon); +int inode_set_capabilities(u32 inode_num, uint64_t capabilities); +struct block_allocation* get_saved_allocation_chain(); + +#endif diff --git a/crc16.c b/crc16.c new file mode 100644 index 0000000..2575812 --- /dev/null +++ b/crc16.c @@ -0,0 +1,58 @@ +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* CRC32 code derived from work by Gary S. Brown. */ + +/* Code taken from FreeBSD 8 */ + +/* Converted to crc16 */ + +#include "ext4_utils.h" + +static u16 crc16_tab[] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040, +}; + +u16 ext4_crc16(u16 crc_in, const void *buf, int size) +{ + const u8 *p = buf; + u16 crc = crc_in; + + while (size--) + crc = crc16_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return crc; +} diff --git a/e4crypt_static.c b/e4crypt_static.c new file mode 100644 index 0000000..1a62ce4 --- /dev/null +++ b/e4crypt_static.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2015 Google, Inc. + */ + +#define TAG "ext4_utils" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ext4_crypt.h" + +/* keyring keyctl commands */ +#define KEYCTL_SETPERM 5 /* set permissions for a key in a keyring */ +#define KEYCTL_UNLINK 9 /* unlink a key from a keyring */ +#define KEYCTL_SEARCH 10 /* search for a key in a keyring */ + +#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy" +#define EXT4_KEYREF_DELIMITER ((char)'.') + +/* Validate that all path items are available and accessible. */ +static int is_path_valid(const char *path) +{ + if (access(path, W_OK)) { + KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path); + return 0; + } + + return 1; +} + +/* Checks whether the policy provided is valid */ +static int is_keyref_valid(const char *keyref) +{ + char *period = 0; + size_t key_location_len = 0; + + /* Key ref must have a key and location delimiter character. */ + period = strchr(keyref, EXT4_KEYREF_DELIMITER); + if (!period) { + return 0; + } + + /* period must be >= keyref. */ + key_location_len = period - keyref; + + if (strncmp(keyref, "@t", key_location_len) == 0 || + strncmp(keyref, "@p", key_location_len) == 0 || + strncmp(keyref, "@s", key_location_len) == 0 || + strncmp(keyref, "@u", key_location_len) == 0 || + strncmp(keyref, "@g", key_location_len) == 0 || + strncmp(keyref, "@us", key_location_len) == 0) + return 1; + + return 0; +} + +static int is_dir_empty(const char *dirname) +{ + int n = 0; + struct dirent *d; + DIR *dir; + + dir = opendir(dirname); + while ((d = readdir(dir)) != NULL) { + if (strcmp(d->d_name, "lost+found") == 0) { + // Skip lost+found directory + } else if (++n > 2) { + break; + } + } + closedir(dir); + return n <= 2; +} + +int do_policy_set(const char *directory, const char *policy) +{ + struct stat st; + ssize_t ret; + + if (!is_keyref_valid(policy)) { + KLOG_ERROR(TAG, "Policy has invalid format.\n"); + return -EINVAL; + } + + if (!is_path_valid(directory)) { + return -EINVAL; + } + + stat(directory, &st); + if (!S_ISDIR(st.st_mode)) { + KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory); + return -EINVAL; + } + + if (!is_dir_empty(directory)) { + KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n", directory); + return -EINVAL; + } + + ret = lsetxattr(directory, XATTR_NAME_ENCRYPTION_POLICY, policy, + strlen(policy), 0); + + if (ret) { + KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n", + directory, strerror(errno)); + return -EINVAL; + } + + KLOG_INFO(TAG, "Encryption policy for %s is set to %s\n", directory, policy); + return 0; +} + +static long keyctl(int cmd, ...) +{ + va_list va; + unsigned long arg2, arg3, arg4, arg5; + + va_start(va, cmd); + arg2 = va_arg(va, unsigned long); + arg3 = va_arg(va, unsigned long); + arg4 = va_arg(va, unsigned long); + arg5 = va_arg(va, unsigned long); + va_end(va); + return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); +} + +key_serial_t add_key(const char *type, + const char *description, + const void *payload, + size_t plen, + key_serial_t ringid) +{ + return syscall(__NR_add_key, type, description, payload, plen, ringid); +} + +long keyctl_setperm(key_serial_t id, int permissions) +{ + return keyctl(KEYCTL_SETPERM, id, permissions); +} diff --git a/ext2simg.c b/ext2simg.c new file mode 100644 index 0000000..e5b36c4 --- /dev/null +++ b/ext2simg.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 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. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ext4_utils.h" +#include "make_ext4fs.h" +#include "allocate.h" + +#if defined(__APPLE__) && defined(__MACH__) +#define off64_t off_t +#endif + +#ifndef USE_MINGW /* O_BINARY is windows-specific flag */ +#define O_BINARY 0 +#endif + +extern struct fs_info info; + +static int verbose = 0; + +static void usage(char *path) +{ + fprintf(stderr, "%s [ options ] \n", path); + fprintf(stderr, "\n"); + fprintf(stderr, " -c include CRC block\n"); + fprintf(stderr, " -v verbose output\n"); + fprintf(stderr, " -z gzip output\n"); + fprintf(stderr, " -S don't use sparse output format\n"); +} + +static int build_sparse_ext(int fd, const char *filename) +{ + unsigned int i; + unsigned int block; + int start_contiguous_block; + u8 *block_bitmap; + off64_t ret; + + block_bitmap = malloc(info.block_size); + if (!block_bitmap) + critical_error("failed to allocate block bitmap"); + + if (aux_info.first_data_block > 0) + sparse_file_add_file(ext4_sparse_file, filename, 0, + info.block_size * aux_info.first_data_block, 0); + + for (i = 0; i < aux_info.groups; i++) { + u32 first_block = aux_info.first_data_block + i * info.blocks_per_group; + u32 last_block = min(info.blocks_per_group, aux_info.len_blocks - first_block); + + ret = lseek64(fd, (u64)info.block_size * aux_info.bg_desc[i].bg_block_bitmap, + SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to block group bitmap %d", i); + + ret = read(fd, block_bitmap, info.block_size); + if (ret < 0) + critical_error_errno("failed to read block group bitmap %d", i); + if (ret != (int)info.block_size) + critical_error("failed to read all of block group bitmap %d", i); + + start_contiguous_block = -1; + for (block = 0; block < last_block; block++) { + if (start_contiguous_block >= 0) { + if (!bitmap_get_bit(block_bitmap, block)) { + u32 start_block = first_block + start_contiguous_block; + u32 len_blocks = block - start_contiguous_block; + + sparse_file_add_file(ext4_sparse_file, filename, + (u64)info.block_size * start_block, + info.block_size * len_blocks, start_block); + start_contiguous_block = -1; + } + } else { + if (bitmap_get_bit(block_bitmap, block)) + start_contiguous_block = block; + } + } + + if (start_contiguous_block >= 0) { + u32 start_block = first_block + start_contiguous_block; + u32 len_blocks = last_block - start_contiguous_block; + sparse_file_add_file(ext4_sparse_file, filename, + (u64)info.block_size * start_block, + info.block_size * len_blocks, start_block); + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + int opt; + const char *in = NULL; + const char *out = NULL; + int gzip = 0; + int sparse = 1; + int infd, outfd; + int crc = 0; + + while ((opt = getopt(argc, argv, "cvzS")) != -1) { + switch (opt) { + case 'c': + crc = 1; + break; + case 'v': + verbose = 1; + break; + case 'z': + gzip = 1; + break; + case 'S': + sparse = 0; + break; + } + } + + if (optind >= argc) { + fprintf(stderr, "Expected image or block device after options\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + in = argv[optind++]; + + if (optind >= argc) { + fprintf(stderr, "Expected output image after input image\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + out = argv[optind++]; + + if (optind < argc) { + fprintf(stderr, "Unexpected argument: %s\n", argv[optind]); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + infd = open(in, O_RDONLY); + + if (infd < 0) + critical_error_errno("failed to open input image"); + + read_ext(infd, verbose); + + ext4_sparse_file = sparse_file_new(info.block_size, info.len); + + build_sparse_ext(infd, in); + + close(infd); + + if (strcmp(out, "-")) { + outfd = open(out, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (outfd < 0) { + error_errno("open"); + return EXIT_FAILURE; + } + } else { + outfd = STDOUT_FILENO; + } + + write_ext4_image(outfd, gzip, sparse, crc); + close(outfd); + + sparse_file_destroy(ext4_sparse_file); + + return 0; +} diff --git a/ext4.h b/ext4.h new file mode 100644 index 0000000..ac6f97e --- /dev/null +++ b/ext4.h @@ -0,0 +1,577 @@ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _EXT4_H +#define _EXT4_H + +#include + +#undef EXT4FS_DEBUG + +#ifdef EXT4FS_DEBUG +#define ext4_debug(f, a...) do { printk(KERN_DEBUG "EXT4-fs DEBUG (%s, %d): %s:", __FILE__, __LINE__, __func__); printk(KERN_DEBUG f, ## a); } while (0) +#else +#define ext4_debug(f, a...) do {} while (0) +#endif + +#define EXT4_ERROR_INODE(inode, fmt, a...) ext4_error_inode(__func__, (inode), (fmt), ## a); + +#define EXT4_ERROR_FILE(file, fmt, a...) ext4_error_file(__func__, (file), (fmt), ## a); + +typedef int ext4_grpblk_t; + +typedef unsigned long long ext4_fsblk_t; + +typedef __u32 ext4_lblk_t; + +typedef unsigned int ext4_group_t; + +#define EXT4_MB_HINT_MERGE 0x0001 + +#define EXT4_MB_HINT_RESERVED 0x0002 + +#define EXT4_MB_HINT_METADATA 0x0004 + +#define EXT4_MB_HINT_FIRST 0x0008 + +#define EXT4_MB_HINT_BEST 0x0010 + +#define EXT4_MB_HINT_DATA 0x0020 + +#define EXT4_MB_HINT_NOPREALLOC 0x0040 + +#define EXT4_MB_HINT_GROUP_ALLOC 0x0080 + +#define EXT4_MB_HINT_GOAL_ONLY 0x0100 + +#define EXT4_MB_HINT_TRY_GOAL 0x0200 + +#define EXT4_MB_DELALLOC_RESERVED 0x0400 + +#define EXT4_MB_STREAM_ALLOC 0x0800 + +struct ext4_allocation_request { + + struct inode *inode; + + unsigned int len; + + ext4_lblk_t logical; + + ext4_lblk_t lleft; + + ext4_lblk_t lright; + + ext4_fsblk_t goal; + + ext4_fsblk_t pleft; + + ext4_fsblk_t pright; + + unsigned int flags; +}; + +#define EXT4_BAD_INO 1 +#define EXT4_ROOT_INO 2 +#define EXT4_BOOT_LOADER_INO 5 +#define EXT4_UNDEL_DIR_INO 6 +#define EXT4_RESIZE_INO 7 +#define EXT4_JOURNAL_INO 8 + +#define EXT4_GOOD_OLD_FIRST_INO 11 + +#define EXT4_LINK_MAX 65000 + +#define EXT4_MIN_BLOCK_SIZE 1024 +#define EXT4_MAX_BLOCK_SIZE 65536 +#define EXT4_MIN_BLOCK_LOG_SIZE 10 +#define EXT4_BLOCK_SIZE(s) (EXT4_MIN_BLOCK_SIZE << (s)->s_log_block_size) +#define EXT4_ADDR_PER_BLOCK(s) (EXT4_BLOCK_SIZE(s) / sizeof(__u32)) +#define EXT4_BLOCK_SIZE_BITS(s) ((s)->s_log_block_size + 10) +#define EXT4_INODE_SIZE(s) (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_INODE_SIZE : (s)->s_inode_size) +#define EXT4_FIRST_INO(s) (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_FIRST_INO : (s)->s_first_ino) +#define EXT4_BLOCK_ALIGN(size, blkbits) EXT4_ALIGN((size), (1 << (blkbits))) + +struct ext4_group_desc +{ + __le32 bg_block_bitmap_lo; + __le32 bg_inode_bitmap_lo; + __le32 bg_inode_table_lo; + __le16 bg_free_blocks_count_lo; + __le16 bg_free_inodes_count_lo; + __le16 bg_used_dirs_count_lo; + __le16 bg_flags; + __u32 bg_reserved[2]; + __le16 bg_itable_unused_lo; + __le16 bg_checksum; + __le32 bg_block_bitmap_hi; + __le32 bg_inode_bitmap_hi; + __le32 bg_inode_table_hi; + __le16 bg_free_blocks_count_hi; + __le16 bg_free_inodes_count_hi; + __le16 bg_used_dirs_count_hi; + __le16 bg_itable_unused_hi; + __u32 bg_reserved2[3]; +}; + +#define EXT4_BG_INODE_UNINIT 0x0001 +#define EXT4_BG_BLOCK_UNINIT 0x0002 +#define EXT4_BG_INODE_ZEROED 0x0004 + +#define EXT4_MIN_DESC_SIZE 32 +#define EXT4_MIN_DESC_SIZE_64BIT 64 +#define EXT4_MAX_DESC_SIZE EXT4_MIN_BLOCK_SIZE +#define EXT4_DESC_SIZE(s) (EXT4_SB(s)->s_desc_size) +#define EXT4_BLOCKS_PER_GROUP(s) ((s)->s_blocks_per_group) +#define EXT4_DESC_PER_BLOCK(s) (EXT4_BLOCK_SIZE(s) / EXT4_DESC_SIZE(s)) +#define EXT4_INODES_PER_GROUP(s) ((s)->s_inodes_per_group) + +#define EXT4_NDIR_BLOCKS 12 +#define EXT4_IND_BLOCK EXT4_NDIR_BLOCKS +#define EXT4_DIND_BLOCK (EXT4_IND_BLOCK + 1) +#define EXT4_TIND_BLOCK (EXT4_DIND_BLOCK + 1) +#define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1) + +#define EXT4_SECRM_FL 0x00000001 +#define EXT4_UNRM_FL 0x00000002 +#define EXT4_COMPR_FL 0x00000004 +#define EXT4_SYNC_FL 0x00000008 +#define EXT4_IMMUTABLE_FL 0x00000010 +#define EXT4_APPEND_FL 0x00000020 +#define EXT4_NODUMP_FL 0x00000040 +#define EXT4_NOATIME_FL 0x00000080 + +#define EXT4_DIRTY_FL 0x00000100 +#define EXT4_COMPRBLK_FL 0x00000200 +#define EXT4_NOCOMPR_FL 0x00000400 +#define EXT4_ECOMPR_FL 0x00000800 + +#define EXT4_INDEX_FL 0x00001000 +#define EXT4_IMAGIC_FL 0x00002000 +#define EXT4_JOURNAL_DATA_FL 0x00004000 +#define EXT4_NOTAIL_FL 0x00008000 +#define EXT4_DIRSYNC_FL 0x00010000 +#define EXT4_TOPDIR_FL 0x00020000 +#define EXT4_HUGE_FILE_FL 0x00040000 +#define EXT4_EXTENTS_FL 0x00080000 +#define EXT4_EA_INODE_FL 0x00200000 +#define EXT4_EOFBLOCKS_FL 0x00400000 +#define EXT4_RESERVED_FL 0x80000000 + +#define EXT4_FL_USER_VISIBLE 0x004BDFFF +#define EXT4_FL_USER_MODIFIABLE 0x004B80FF + +#define EXT4_FL_INHERITED (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL | EXT4_SYNC_FL | EXT4_IMMUTABLE_FL | EXT4_APPEND_FL | EXT4_NODUMP_FL | EXT4_NOATIME_FL | EXT4_NOCOMPR_FL | EXT4_JOURNAL_DATA_FL | EXT4_NOTAIL_FL | EXT4_DIRSYNC_FL) + +#define EXT4_REG_FLMASK (~(EXT4_DIRSYNC_FL | EXT4_TOPDIR_FL)) + +#define EXT4_OTHER_FLMASK (EXT4_NODUMP_FL | EXT4_NOATIME_FL) + +struct ext4_new_group_data { + __u32 group; + __u64 block_bitmap; + __u64 inode_bitmap; + __u64 inode_table; + __u32 blocks_count; + __u16 reserved_blocks; + __u16 unused; + __u32 free_blocks_count; +}; + +#define EXT4_GET_BLOCKS_CREATE 0x0001 + +#define EXT4_GET_BLOCKS_UNINIT_EXT 0x0002 +#define EXT4_GET_BLOCKS_CREATE_UNINIT_EXT (EXT4_GET_BLOCKS_UNINIT_EXT| EXT4_GET_BLOCKS_CREATE) + +#define EXT4_GET_BLOCKS_DELALLOC_RESERVE 0x0004 + +#define EXT4_GET_BLOCKS_PRE_IO 0x0008 +#define EXT4_GET_BLOCKS_CONVERT 0x0010 +#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_PRE_IO| EXT4_GET_BLOCKS_CREATE_UNINIT_EXT) + +#define EXT4_GET_BLOCKS_IO_CONVERT_EXT (EXT4_GET_BLOCKS_CONVERT| EXT4_GET_BLOCKS_CREATE_UNINIT_EXT) + +#define EXT4_FREE_BLOCKS_METADATA 0x0001 +#define EXT4_FREE_BLOCKS_FORGET 0x0002 +#define EXT4_FREE_BLOCKS_VALIDATED 0x0004 + +#define EXT4_IOC_GETFLAGS FS_IOC_GETFLAGS +#define EXT4_IOC_SETFLAGS FS_IOC_SETFLAGS +#define EXT4_IOC_GETVERSION _IOR('f', 3, long) +#define EXT4_IOC_SETVERSION _IOW('f', 4, long) +#define EXT4_IOC_GETVERSION_OLD FS_IOC_GETVERSION +#define EXT4_IOC_SETVERSION_OLD FS_IOC_SETVERSION +#define EXT4_IOC_GETRSVSZ _IOR('f', 5, long) +#define EXT4_IOC_SETRSVSZ _IOW('f', 6, long) +#define EXT4_IOC_GROUP_EXTEND _IOW('f', 7, unsigned long) +#define EXT4_IOC_GROUP_ADD _IOW('f', 8, struct ext4_new_group_input) +#define EXT4_IOC_MIGRATE _IO('f', 9) + +#define EXT4_IOC_ALLOC_DA_BLKS _IO('f', 12) +#define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent) + +#define EXT4_IOC32_GETFLAGS FS_IOC32_GETFLAGS +#define EXT4_IOC32_SETFLAGS FS_IOC32_SETFLAGS +#define EXT4_IOC32_GETVERSION _IOR('f', 3, int) +#define EXT4_IOC32_SETVERSION _IOW('f', 4, int) +#define EXT4_IOC32_GETRSVSZ _IOR('f', 5, int) +#define EXT4_IOC32_SETRSVSZ _IOW('f', 6, int) +#define EXT4_IOC32_GROUP_EXTEND _IOW('f', 7, unsigned int) +#define EXT4_IOC32_GETVERSION_OLD FS_IOC32_GETVERSION +#define EXT4_IOC32_SETVERSION_OLD FS_IOC32_SETVERSION + +#define EXT4_MAX_BLOCK_FILE_PHYS 0xFFFFFFFF + +struct ext4_inode { + __le16 i_mode; + __le16 i_uid; + __le32 i_size_lo; + __le32 i_atime; + __le32 i_ctime; + __le32 i_mtime; + __le32 i_dtime; + __le16 i_gid; + __le16 i_links_count; + __le32 i_blocks_lo; + __le32 i_flags; + union { + struct { + __le32 l_i_version; + } linux1; + struct { + __u32 h_i_translator; + } hurd1; + struct { + __u32 m_i_reserved1; + } masix1; + } osd1; + __le32 i_block[EXT4_N_BLOCKS]; + __le32 i_generation; + __le32 i_file_acl_lo; + __le32 i_size_high; + __le32 i_obso_faddr; + union { + struct { + __le16 l_i_blocks_high; + __le16 l_i_file_acl_high; + __le16 l_i_uid_high; + __le16 l_i_gid_high; + __u32 l_i_reserved2; + } linux2; + struct { + __le16 h_i_reserved1; + __u16 h_i_mode_high; + __u16 h_i_uid_high; + __u16 h_i_gid_high; + __u32 h_i_author; + } hurd2; + struct { + __le16 h_i_reserved1; + __le16 m_i_file_acl_high; + __u32 m_i_reserved2[2]; + } masix2; + } osd2; + __le16 i_extra_isize; + __le16 i_pad1; + __le32 i_ctime_extra; + __le32 i_mtime_extra; + __le32 i_atime_extra; + __le32 i_crtime; + __le32 i_crtime_extra; + __le32 i_version_hi; +}; + +struct move_extent { + __u32 reserved; + __u32 donor_fd; + __u64 orig_start; + __u64 donor_start; + __u64 len; + __u64 moved_len; +}; + +#define EXT4_EPOCH_BITS 2 +#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1) +#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS) + +#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) ((offsetof(typeof(*ext4_inode), field) + sizeof((ext4_inode)->field)) <= (EXT4_GOOD_OLD_INODE_SIZE + (einode)->i_extra_isize)) +#define EXT4_INODE_SET_XTIME(xtime, inode, raw_inode) do { (raw_inode)->xtime = cpu_to_le32((inode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(inode)->xtime); } while (0) +#define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (raw_inode)->xtime = cpu_to_le32((einode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(einode)->xtime); } while (0) +#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode) do { (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) ext4_decode_extra_time(&(inode)->xtime, raw_inode->xtime ## _extra); } while (0) +#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (einode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) ext4_decode_extra_time(&(einode)->xtime, raw_inode->xtime ## _extra); } while (0) +#define i_disk_version osd1.linux1.l_i_version + +#define i_reserved1 osd1.linux1.l_i_reserved1 +#define i_file_acl_high osd2.linux2.l_i_file_acl_high +#define i_blocks_high osd2.linux2.l_i_blocks_high +#define i_uid_low i_uid +#define i_gid_low i_gid +#define i_uid_high osd2.linux2.l_i_uid_high +#define i_gid_high osd2.linux2.l_i_gid_high +#define i_reserved2 osd2.linux2.l_i_reserved2 + +#define EXT4_VALID_FS 0x0001 +#define EXT4_ERROR_FS 0x0002 +#define EXT4_ORPHAN_FS 0x0004 + +#define EXT2_FLAGS_SIGNED_HASH 0x0001 +#define EXT2_FLAGS_UNSIGNED_HASH 0x0002 +#define EXT2_FLAGS_TEST_FILESYS 0x0004 + +#define EXT4_MOUNT_OLDALLOC 0x00002 +#define EXT4_MOUNT_GRPID 0x00004 +#define EXT4_MOUNT_DEBUG 0x00008 +#define EXT4_MOUNT_ERRORS_CONT 0x00010 +#define EXT4_MOUNT_ERRORS_RO 0x00020 +#define EXT4_MOUNT_ERRORS_PANIC 0x00040 +#define EXT4_MOUNT_MINIX_DF 0x00080 +#define EXT4_MOUNT_NOLOAD 0x00100 +#define EXT4_MOUNT_DATA_FLAGS 0x00C00 +#define EXT4_MOUNT_JOURNAL_DATA 0x00400 +#define EXT4_MOUNT_ORDERED_DATA 0x00800 +#define EXT4_MOUNT_WRITEBACK_DATA 0x00C00 +#define EXT4_MOUNT_UPDATE_JOURNAL 0x01000 +#define EXT4_MOUNT_NO_UID32 0x02000 +#define EXT4_MOUNT_XATTR_USER 0x04000 +#define EXT4_MOUNT_POSIX_ACL 0x08000 +#define EXT4_MOUNT_NO_AUTO_DA_ALLOC 0x10000 +#define EXT4_MOUNT_BARRIER 0x20000 +#define EXT4_MOUNT_NOBH 0x40000 +#define EXT4_MOUNT_QUOTA 0x80000 +#define EXT4_MOUNT_USRQUOTA 0x100000 +#define EXT4_MOUNT_GRPQUOTA 0x200000 +#define EXT4_MOUNT_DIOREAD_NOLOCK 0x400000 +#define EXT4_MOUNT_JOURNAL_CHECKSUM 0x800000 +#define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT 0x1000000 +#define EXT4_MOUNT_I_VERSION 0x2000000 +#define EXT4_MOUNT_DELALLOC 0x8000000 +#define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000 +#define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000 +#define EXT4_MOUNT_DISCARD 0x40000000 + +#define clear_opt(o, opt) o &= ~EXT4_MOUNT_##opt +#define set_opt(o, opt) o |= EXT4_MOUNT_##opt +#define test_opt(sb, opt) (EXT4_SB(sb)->s_mount_opt & EXT4_MOUNT_##opt) + +#define ext4_set_bit ext2_set_bit +#define ext4_set_bit_atomic ext2_set_bit_atomic +#define ext4_clear_bit ext2_clear_bit +#define ext4_clear_bit_atomic ext2_clear_bit_atomic +#define ext4_test_bit ext2_test_bit +#define ext4_find_first_zero_bit ext2_find_first_zero_bit +#define ext4_find_next_zero_bit ext2_find_next_zero_bit +#define ext4_find_next_bit ext2_find_next_bit + +#define EXT4_DFL_MAX_MNT_COUNT 20 +#define EXT4_DFL_CHECKINTERVAL 0 + +#define EXT4_ERRORS_CONTINUE 1 +#define EXT4_ERRORS_RO 2 +#define EXT4_ERRORS_PANIC 3 +#define EXT4_ERRORS_DEFAULT EXT4_ERRORS_CONTINUE + +struct ext4_super_block { + __le32 s_inodes_count; + __le32 s_blocks_count_lo; + __le32 s_r_blocks_count_lo; + __le32 s_free_blocks_count_lo; + __le32 s_free_inodes_count; + __le32 s_first_data_block; + __le32 s_log_block_size; + __le32 s_obso_log_frag_size; + __le32 s_blocks_per_group; + __le32 s_obso_frags_per_group; + __le32 s_inodes_per_group; + __le32 s_mtime; + __le32 s_wtime; + __le16 s_mnt_count; + __le16 s_max_mnt_count; + __le16 s_magic; + __le16 s_state; + __le16 s_errors; + __le16 s_minor_rev_level; + __le32 s_lastcheck; + __le32 s_checkinterval; + __le32 s_creator_os; + __le32 s_rev_level; + __le16 s_def_resuid; + __le16 s_def_resgid; + + __le32 s_first_ino; + __le16 s_inode_size; + __le16 s_block_group_nr; + __le32 s_feature_compat; + __le32 s_feature_incompat; + __le32 s_feature_ro_compat; + __u8 s_uuid[16]; + char s_volume_name[16]; + char s_last_mounted[64]; + __le32 s_algorithm_usage_bitmap; + + __u8 s_prealloc_blocks; + __u8 s_prealloc_dir_blocks; + __le16 s_reserved_gdt_blocks; + + __u8 s_journal_uuid[16]; + __le32 s_journal_inum; + __le32 s_journal_dev; + __le32 s_last_orphan; + __le32 s_hash_seed[4]; + __u8 s_def_hash_version; + __u8 s_reserved_char_pad; + __le16 s_desc_size; + __le32 s_default_mount_opts; + __le32 s_first_meta_bg; + __le32 s_mkfs_time; + __le32 s_jnl_blocks[17]; + + __le32 s_blocks_count_hi; + __le32 s_r_blocks_count_hi; + __le32 s_free_blocks_count_hi; + __le16 s_min_extra_isize; + __le16 s_want_extra_isize; + __le32 s_flags; + __le16 s_raid_stride; + __le16 s_mmp_interval; + __le64 s_mmp_block; + __le32 s_raid_stripe_width; + __u8 s_log_groups_per_flex; + __u8 s_reserved_char_pad2; + __le16 s_reserved_pad; + __le64 s_kbytes_written; + __u32 s_reserved[160]; +}; + +#define EXT4_SB(sb) (sb) + +#define NEXT_ORPHAN(inode) EXT4_I(inode)->i_dtime + +#define EXT4_OS_LINUX 0 +#define EXT4_OS_HURD 1 +#define EXT4_OS_MASIX 2 +#define EXT4_OS_FREEBSD 3 +#define EXT4_OS_LITES 4 + +#define EXT4_GOOD_OLD_REV 0 +#define EXT4_DYNAMIC_REV 1 + +#define EXT4_CURRENT_REV EXT4_GOOD_OLD_REV +#define EXT4_MAX_SUPP_REV EXT4_DYNAMIC_REV + +#define EXT4_GOOD_OLD_INODE_SIZE 128 + +#define EXT4_HAS_COMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_compat & cpu_to_le32(mask)) != 0) +#define EXT4_HAS_RO_COMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_ro_compat & cpu_to_le32(mask)) != 0) +#define EXT4_HAS_INCOMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_incompat & cpu_to_le32(mask)) != 0) +#define EXT4_SET_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_compat |= cpu_to_le32(mask) +#define EXT4_SET_RO_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_ro_compat |= cpu_to_le32(mask) +#define EXT4_SET_INCOMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_incompat |= cpu_to_le32(mask) +#define EXT4_CLEAR_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_compat &= ~cpu_to_le32(mask) +#define EXT4_CLEAR_RO_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_ro_compat &= ~cpu_to_le32(mask) +#define EXT4_CLEAR_INCOMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_incompat &= ~cpu_to_le32(mask) + +#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x0001 +#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x0002 +#define EXT4_FEATURE_COMPAT_HAS_JOURNAL 0x0004 +#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x0008 +#define EXT4_FEATURE_COMPAT_RESIZE_INODE 0x0010 +#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x0020 + +#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 +#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008 +#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010 +#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020 +#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040 + +#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x0001 +#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002 +#define EXT4_FEATURE_INCOMPAT_RECOVER 0x0004 +#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 +#define EXT4_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040 +#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080 +#define EXT4_FEATURE_INCOMPAT_MMP 0x0100 +#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200 +#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x0400 +#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x1000 + +#define EXT4_FEATURE_COMPAT_SUPP EXT2_FEATURE_COMPAT_EXT_ATTR +#define EXT4_FEATURE_INCOMPAT_SUPP (EXT4_FEATURE_INCOMPAT_FILETYPE| EXT4_FEATURE_INCOMPAT_RECOVER| EXT4_FEATURE_INCOMPAT_META_BG| EXT4_FEATURE_INCOMPAT_EXTENTS| EXT4_FEATURE_INCOMPAT_64BIT| EXT4_FEATURE_INCOMPAT_FLEX_BG) +#define EXT4_FEATURE_RO_COMPAT_SUPP (EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| EXT4_FEATURE_RO_COMPAT_LARGE_FILE| EXT4_FEATURE_RO_COMPAT_GDT_CSUM| EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE | EXT4_FEATURE_RO_COMPAT_BTREE_DIR | EXT4_FEATURE_RO_COMPAT_HUGE_FILE) + +#define EXT4_DEF_RESUID 0 +#define EXT4_DEF_RESGID 0 + +#define EXT4_DEF_INODE_READAHEAD_BLKS 32 + +#define EXT4_DEFM_DEBUG 0x0001 +#define EXT4_DEFM_BSDGROUPS 0x0002 +#define EXT4_DEFM_XATTR_USER 0x0004 +#define EXT4_DEFM_ACL 0x0008 +#define EXT4_DEFM_UID16 0x0010 +#define EXT4_DEFM_JMODE 0x0060 +#define EXT4_DEFM_JMODE_DATA 0x0020 +#define EXT4_DEFM_JMODE_ORDERED 0x0040 +#define EXT4_DEFM_JMODE_WBACK 0x0060 + +#define EXT4_DEF_MIN_BATCH_TIME 0 +#define EXT4_DEF_MAX_BATCH_TIME 15000 + +#define EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME 4 + +#define EXT4_NAME_LEN 255 + +struct ext4_dir_entry { + __le32 inode; + __le16 rec_len; + __le16 name_len; + char name[EXT4_NAME_LEN]; +}; + +struct ext4_dir_entry_2 { + __le32 inode; + __le16 rec_len; + __u8 name_len; + __u8 file_type; + char name[EXT4_NAME_LEN]; +}; + +#define EXT4_FT_UNKNOWN 0 +#define EXT4_FT_REG_FILE 1 +#define EXT4_FT_DIR 2 +#define EXT4_FT_CHRDEV 3 +#define EXT4_FT_BLKDEV 4 +#define EXT4_FT_FIFO 5 +#define EXT4_FT_SOCK 6 +#define EXT4_FT_SYMLINK 7 + +#define EXT4_FT_MAX 8 + +#define EXT4_DIR_PAD 4 +#define EXT4_DIR_ROUND (EXT4_DIR_PAD - 1) +#define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & ~EXT4_DIR_ROUND) +#define EXT4_MAX_REC_LEN ((1<<16)-1) + +#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE(dir->i_sb, EXT4_FEATURE_COMPAT_DIR_INDEX) && (EXT4_I(dir)->i_flags & EXT4_INDEX_FL)) +#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX) +#define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1) + +#define DX_HASH_LEGACY 0 +#define DX_HASH_HALF_MD4 1 +#define DX_HASH_TEA 2 +#define DX_HASH_LEGACY_UNSIGNED 3 +#define DX_HASH_HALF_MD4_UNSIGNED 4 +#define DX_HASH_TEA_UNSIGNED 5 + +#endif + diff --git a/ext4_crypt.cpp b/ext4_crypt.cpp new file mode 100644 index 0000000..bb57332 --- /dev/null +++ b/ext4_crypt.cpp @@ -0,0 +1,120 @@ +#define TAG "ext4_utils" + +#include "ext4_crypt.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include "unencrypted_properties.h" + +namespace { + std::map s_password_store; +} + +bool e4crypt_non_default_key(const char* dir) +{ + int type = e4crypt_get_password_type(dir); + + // ext4enc:TODO Use consts, not 1 here + return type != -1 && type != 1; +} + +int e4crypt_get_password_type(const char* path) +{ + UnencryptedProperties props(path); + if (props.Get(properties::key).empty()) { + KLOG_INFO(TAG, "No master key, so not ext4enc\n"); + return -1; + } + + return props.Get(properties::type, 1); +} + +int e4crypt_change_password(const char* path, int crypt_type, + const char* password) +{ + // ext4enc:TODO Encrypt master key with password securely. Store hash of + // master key for validation + UnencryptedProperties props(path); + if ( props.Set(properties::password, password) + && props.Set(properties::type, crypt_type)) + return 0; + return -1; +} + +int e4crypt_crypto_complete(const char* path) +{ + KLOG_INFO(TAG, "ext4 crypto complete called on %s\n", path); + if (UnencryptedProperties(path).Get(properties::key).empty()) { + KLOG_INFO(TAG, "No master key, so not ext4enc\n"); + return -1; + } + + return 0; +} + +int e4crypt_check_passwd(const char* path, const char* password) +{ + UnencryptedProperties props(path); + if (props.Get(properties::key).empty()) { + KLOG_INFO(TAG, "No master key, so not ext4enc\n"); + return -1; + } + + auto actual_password = props.Get(properties::password); + + if (actual_password == password) { + s_password_store[path] = password; + return 0; + } else { + return -1; + } +} + +int e4crypt_restart(const char* path) +{ + int rc = 0; + + KLOG_INFO(TAG, "ext4 restart called on %s\n", path); + property_set("vold.decrypt", "trigger_reset_main"); + KLOG_INFO(TAG, "Just asked init to shut down class main\n"); + sleep(2); + + std::string tmp_path = std::string() + path + "/tmp_mnt"; + + // ext4enc:TODO add retry logic + rc = umount(tmp_path.c_str()); + if (rc) { + KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n", + tmp_path.c_str(), rc, strerror(errno)); + return rc; + } + + // ext4enc:TODO add retry logic + rc = umount(path); + if (rc) { + KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n", + path, rc, strerror(errno)); + return rc; + } + + return 0; +} + +const char* e4crypt_get_password(const char* path) +{ + // ext4enc:TODO scrub password after timeout + auto i = s_password_store.find(path); + if (i == s_password_store.end()) { + return 0; + } else { + return i->second.c_str(); + } +} diff --git a/ext4_crypt.h b/ext4_crypt.h new file mode 100644 index 0000000..cc69273 --- /dev/null +++ b/ext4_crypt.h @@ -0,0 +1,50 @@ +#include +#include +#include + +__BEGIN_DECLS +// These functions assume they are being called from init +// They will not operate properly outside of init +int e4crypt_install_keyring(); +int e4crypt_install_key(const char* dir); +int e4crypt_create_device_key(const char* dir, + int ensure_dir_exists(const char* dir)); + +// General functions +bool e4crypt_non_default_key(const char* dir); +int e4crypt_set_directory_policy(const char* dir); +int e4crypt_main(int argc, char* argv[]); +int e4crypt_change_password(const char* path, int crypt_type, + const char* password); +int e4crypt_get_password_type(const char* path); +int e4crypt_crypto_complete(const char* dir); +int e4crypt_check_passwd(const char* dir, const char* password); +const char* e4crypt_get_password(const char* dir); +int e4crypt_restart(const char* dir); + +// Key functions. ext4enc:TODO Move to own file + +// ext4enc:TODO - get these keyring standard definitions from proper system file +// keyring serial number type +typedef int32_t key_serial_t; + +// special process keyring shortcut IDs +#define KEY_SPEC_THREAD_KEYRING -1 // key ID for thread-specific keyring +#define KEY_SPEC_PROCESS_KEYRING -2 // key ID for process-specific keyring +#define KEY_SPEC_SESSION_KEYRING -3 // key ID for session-specific keyring +#define KEY_SPEC_USER_KEYRING -4 // key ID for UID-specific keyring +#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring +#define KEY_SPEC_GROUP_KEYRING -6 // key ID for GID-specific keyring + +key_serial_t add_key(const char *type, + const char *description, + const void *payload, + size_t plen, + key_serial_t ringid); + +long keyctl_setperm(key_serial_t id, int permissions); + +// Set policy on directory +int do_policy_set(const char *directory, const char *policy); + +__END_DECLS diff --git a/ext4_crypt_init_extensions.cpp b/ext4_crypt_init_extensions.cpp new file mode 100644 index 0000000..1585911 --- /dev/null +++ b/ext4_crypt_init_extensions.cpp @@ -0,0 +1,269 @@ +#define TAG "ext4_utils" + +#include "ext4_crypt.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "unencrypted_properties.h" + +// ext4enc:TODO Include structure from somewhere sensible +// MUST be in sync with ext4_crypto.c in kernel +#define EXT4_MAX_KEY_SIZE 76 +struct ext4_encryption_key { + uint32_t mode; + char raw[EXT4_MAX_KEY_SIZE]; + uint32_t size; +}; + +static const std::string keyring = "@s"; +static const std::string arbitrary_sequence_number = "42"; + +static key_serial_t device_keyring = -1; + +static std::string vold_command(std::string const& command) +{ + KLOG_INFO(TAG, "Running command %s\n", command.c_str()); + int sock = socket_local_client("vold", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + + if (sock < 0) { + KLOG_INFO(TAG, "Cannot open vold, failing command\n"); + return ""; + } + + class CloseSocket + { + int sock_; + public: + CloseSocket(int sock) : sock_(sock) {} + ~CloseSocket() { close(sock_); } + }; + + CloseSocket cs(sock); + + // Use arbitrary sequence number. This should only be used when the + // framework is down, so this is (mostly) OK. + std::string actual_command = arbitrary_sequence_number + " " + command; + if (write(sock, actual_command.c_str(), actual_command.size() + 1) < 0) { + KLOG_ERROR(TAG, "Cannot write command\n"); + return ""; + } + + while (1) { + struct timeval to; + to.tv_sec = 10; + to.tv_usec = 0; + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(sock, &read_fds); + + int rc = select(sock + 1, &read_fds, NULL, NULL, &to); + if (rc < 0) { + KLOG_ERROR(TAG, "Error in select %s\n", strerror(errno)); + return ""; + } else if (!rc) { + KLOG_ERROR(TAG, "Timeout\n"); + return ""; + } else if (FD_ISSET(sock, &read_fds)) { + char buffer[4096]; + memset(buffer, 0, sizeof(buffer)); + rc = read(sock, buffer, sizeof(buffer)); + if (rc <= 0) { + if (rc == 0) { + KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n"); + } else { + KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno)); + } + return ""; + } + + // We don't truly know that this is the correct result. However, + // since this will only be used when the framework is down, + // it should be OK unless someone is running vdc at the same time. + // Worst case we force a reboot in the very rare synchronization + // error + return std::string(buffer, rc); + } + } +} + +int e4crypt_create_device_key(const char* dir, + int ensure_dir_exists(const char*)) +{ + // Make sure folder exists. Use make_dir to set selinux permissions. + KLOG_INFO(TAG, "Creating test device key\n"); + UnencryptedProperties props(dir); + if (ensure_dir_exists(props.GetPath().c_str())) { + KLOG_ERROR(TAG, "Failed to create %s with error %s\n", + props.GetPath().c_str(), strerror(errno)); + return -1; + } + + if (props.Get(properties::key).empty()) { + // Create new key since it doesn't already exist + std::ifstream urandom("/dev/urandom", std::ifstream::binary); + if (!urandom) { + KLOG_ERROR(TAG, "Failed to open /dev/urandom\n"); + return -1; + } + + // ext4enc:TODO Don't hardcode 32 + std::string key_material(32, '\0'); + urandom.read(&key_material[0], key_material.length()); + if (!urandom) { + KLOG_ERROR(TAG, "Failed to read random bytes\n"); + return -1; + } + + if (!props.Set(properties::key, key_material)) { + KLOG_ERROR(TAG, "Failed to write key material\n"); + return -1; + } + } + + if (!props.Remove(properties::ref)) { + KLOG_ERROR(TAG, "Failed to remove key ref\n"); + return -1; + } + + return 0; +} + +int e4crypt_install_keyring() +{ + device_keyring = add_key("keyring", + "e4crypt", + 0, + 0, + KEY_SPEC_SESSION_KEYRING); + + if (device_keyring == -1) { + KLOG_ERROR(TAG, "Failed to create keyring\n"); + return -1; + } + + KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n", + device_keyring, getpid()); + + // ext4enc:TODO set correct permissions + long result = keyctl_setperm(device_keyring, 0x3f3f3f3f); + if (result) { + KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result); + return -1; + } + + return 0; +} + +int e4crypt_install_key(const char* dir) +{ + UnencryptedProperties props(dir); + auto key = props.Get(properties::key); + + // Get password to decrypt as needed + if (e4crypt_non_default_key(dir)) { + std::string result = vold_command("cryptfs getpw"); + // result is either + // 200 0 -1 + // or + // 200 0 {{sensitive}} 0001020304 + // where 0001020304 is hex encoding of password + std::istringstream i(result); + std::string bit; + i >> bit; + if (bit != "200") { + KLOG_ERROR(TAG, "Expecting 200\n"); + return -1; + } + + i >> bit; + if (bit != arbitrary_sequence_number) { + KLOG_ERROR(TAG, "Expecting %s\n", arbitrary_sequence_number.c_str()); + return -1; + } + + i >> bit; + if (bit != "{{sensitive}}") { + KLOG_INFO(TAG, "Not encrypted\n"); + return -1; + } + + i >> bit; + } + + // Add key to keyring + ext4_encryption_key ext4_key = {0, {0}, 0}; + if (key.length() > sizeof(ext4_key.raw)) { + KLOG_ERROR(TAG, "Key too long\n"); + return -1; + } + + ext4_key.mode = 0; + memcpy(ext4_key.raw, &key[0], key.length()); + ext4_key.size = key.length(); + + // ext4enc:TODO Use better reference not 1234567890 + key_serial_t key_id = add_key("logon", "ext4-key:1234567890", + (void*)&ext4_key, sizeof(ext4_key), + device_keyring); + + if (key_id == -1) { + KLOG_ERROR(TAG, "Failed to insert key into keyring with error %s\n", + strerror(errno)); + return -1; + } + + KLOG_INFO(TAG, "Added key %d to keyring %d in process %d\n", + key_id, device_keyring, getpid()); + + // ext4enc:TODO set correct permissions + long result = keyctl_setperm(key_id, 0x3f3f3f3f); + if (result) { + KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result); + return -1; + } + + // Save reference to key so we can set policy later + if (!props.Set(properties::ref, "ext4-key:1234567890")) { + KLOG_ERROR(TAG, "Cannot save key reference\n"); + return -1; + } + + return 0; +} + +int e4crypt_set_directory_policy(const char* dir) +{ + // Only set policy on first level /data directories + // To make this less restrictive, consider using a policy file. + // However this is overkill for as long as the policy is simply + // to apply a global policy to all /data folders created via makedir + if (!dir || strncmp(dir, "/data/", 6) || strchr(dir + 6, '/')) { + return 0; + } + + UnencryptedProperties props("/data"); + std::string ref = props.Get(properties::ref); + std::string policy = keyring + "." + ref; + KLOG_INFO(TAG, "Setting policy %s\n", policy.c_str()); + int result = do_policy_set(dir, policy.c_str()); + if (result) { + KLOG_ERROR(TAG, "Setting policy on %s failed!\n", dir); + return -1; + } + + return 0; +} diff --git a/ext4_extents.h b/ext4_extents.h new file mode 100644 index 0000000..b1290f4 --- /dev/null +++ b/ext4_extents.h @@ -0,0 +1,88 @@ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _EXT4_EXTENTS +#define _EXT4_EXTENTS + +#include "ext4.h" + +#define AGGRESSIVE_TEST_ + +#define EXTENTS_STATS__ + +#define CHECK_BINSEARCH__ + +#define EXT_DEBUG__ +#ifdef EXT_DEBUG +#define ext_debug(a...) printk(a) +#else +#define ext_debug(a...) +#endif + +#define EXT_STATS_ + +struct ext4_extent { + __le32 ee_block; + __le16 ee_len; + __le16 ee_start_hi; + __le32 ee_start_lo; +}; + +struct ext4_extent_idx { + __le32 ei_block; + __le32 ei_leaf_lo; + __le16 ei_leaf_hi; + __u16 ei_unused; +}; + +struct ext4_extent_header { + __le16 eh_magic; + __le16 eh_entries; + __le16 eh_max; + __le16 eh_depth; + __le32 eh_generation; +}; + +#define EXT4_EXT_MAGIC 0xf30a + +struct ext4_ext_path { + ext4_fsblk_t p_block; + __u16 p_depth; + struct ext4_extent *p_ext; + struct ext4_extent_idx *p_idx; + struct ext4_extent_header *p_hdr; + struct buffer_head *p_bh; +}; + +#define EXT4_EXT_CACHE_NO 0 +#define EXT4_EXT_CACHE_GAP 1 +#define EXT4_EXT_CACHE_EXTENT 2 + +#define EXT_CONTINUE 0 +#define EXT_BREAK 1 +#define EXT_REPEAT 2 + +#define EXT_MAX_BLOCK 0xffffffff + +#define EXT_INIT_MAX_LEN (1UL << 15) +#define EXT_UNINIT_MAX_LEN (EXT_INIT_MAX_LEN - 1) + +#define EXT_FIRST_EXTENT(__hdr__) ((struct ext4_extent *) (((char *) (__hdr__)) + sizeof(struct ext4_extent_header))) +#define EXT_FIRST_INDEX(__hdr__) ((struct ext4_extent_idx *) (((char *) (__hdr__)) + sizeof(struct ext4_extent_header))) +#define EXT_HAS_FREE_INDEX(__path__) (le16_to_cpu((__path__)->p_hdr->eh_entries) < le16_to_cpu((__path__)->p_hdr->eh_max)) +#define EXT_LAST_EXTENT(__hdr__) (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1) +#define EXT_LAST_INDEX(__hdr__) (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1) +#define EXT_MAX_EXTENT(__hdr__) (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1) +#define EXT_MAX_INDEX(__hdr__) (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1) + +#endif + + diff --git a/ext4_kernel_headers.h b/ext4_kernel_headers.h new file mode 100644 index 0000000..4b24dce --- /dev/null +++ b/ext4_kernel_headers.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef _EXT4_UTILS_EXT4_KERNEL_HEADERS_H_ +#define _EXT4_UTILS_EXT4_KERNEL_HEADERS_H_ + +#include + +#ifdef __BIONIC__ +#include +#else +#define __le64 uint64_t +#define __le32 uint32_t +#define __le16 uint16_t + +#define __be64 uint64_t +#define __be32 uint32_t +#define __be16 uint16_t + +#define __u64 uint64_t +#define __u32 uint32_t +#define __u16 uint16_t +#define __u8 uint8_t +#endif + +#include "ext4.h" +#include "xattr.h" +#include "ext4_extents.h" +#include "jbd2.h" + +#ifndef __BIONIC__ +#undef __le64 +#undef __le32 +#undef __le16 + +#undef __be64 +#undef __be32 +#undef __be16 + +#undef __u64 +#undef __u32 +#undef __u16 +#undef __u8 +#endif + +#endif diff --git a/ext4_sb.c b/ext4_sb.c new file mode 100644 index 0000000..1d527a1 --- /dev/null +++ b/ext4_sb.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 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 + +#include "ext4_sb.h" + +int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info) +{ + uint64_t len_blocks; + + if (sb->s_magic != EXT4_SUPER_MAGIC) + return -EINVAL; + + if ((sb->s_state & EXT4_VALID_FS) != EXT4_VALID_FS) + return -EINVAL; + + info->block_size = 1024 << sb->s_log_block_size; + info->blocks_per_group = sb->s_blocks_per_group; + info->inodes_per_group = sb->s_inodes_per_group; + info->inode_size = sb->s_inode_size; + info->inodes = sb->s_inodes_count; + info->feat_ro_compat = sb->s_feature_ro_compat; + info->feat_compat = sb->s_feature_compat; + info->feat_incompat = sb->s_feature_incompat; + info->bg_desc_reserve_blocks = sb->s_reserved_gdt_blocks; + info->label = sb->s_volume_name; + + len_blocks = ((uint64_t)sb->s_blocks_count_hi << 32) + + sb->s_blocks_count_lo; + info->len = (uint64_t)info->block_size * len_blocks; + + return 0; +} diff --git a/ext4_sb.h b/ext4_sb.h new file mode 100644 index 0000000..832fa33 --- /dev/null +++ b/ext4_sb.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef _EXT4_UTILS_EXT4_SB_H_ +#define _EXT4_UTILS_EXT4_SB_H_ + +#include "ext4_kernel_headers.h" + +#define EXT4_SUPER_MAGIC 0xEF53 + +#ifdef __cplusplus +extern "C" { +#endif + +struct fs_info { + int64_t len; /* If set to 0, ask the block device for the size, + * if less than 0, reserve that much space at the + * end of the partition, else use the size given. */ + uint32_t block_size; + uint32_t blocks_per_group; + uint32_t inodes_per_group; + uint32_t inode_size; + uint32_t inodes; + uint32_t journal_blocks; + uint16_t feat_ro_compat; + uint16_t feat_compat; + uint16_t feat_incompat; + uint32_t bg_desc_reserve_blocks; + const char *label; + uint8_t no_journal; +}; + +int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext4_utils.c b/ext4_utils.c new file mode 100644 index 0000000..ad0491f --- /dev/null +++ b/ext4_utils.c @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2010 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 "ext4_utils.h" +#include "uuid.h" +#include "allocate.h" +#include "indirect.h" +#include "extent.h" + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef USE_MINGW +#include +#else +#include +#include +#endif + +#if defined(__linux__) +#include +#elif defined(__APPLE__) && defined(__MACH__) +#include +#endif + +int force = 0; +struct fs_info info; +struct fs_aux_info aux_info; +struct sparse_file *ext4_sparse_file; + +jmp_buf setjmp_env; + +/* returns 1 if a is a power of b */ +static int is_power_of(int a, int b) +{ + while (a > b) { + if (a % b) + return 0; + a /= b; + } + + return (a == b) ? 1 : 0; +} + +int bitmap_get_bit(u8 *bitmap, u32 bit) +{ + if (bitmap[bit / 8] & (1 << (bit % 8))) + return 1; + + return 0; +} + +void bitmap_clear_bit(u8 *bitmap, u32 bit) +{ + bitmap[bit / 8] &= ~(1 << (bit % 8)); + + return; +} + +/* Returns 1 if the bg contains a backup superblock. On filesystems with + the sparse_super feature, only block groups 0, 1, and powers of 3, 5, + and 7 have backup superblocks. Otherwise, all block groups have backup + superblocks */ +int ext4_bg_has_super_block(int bg) +{ + /* Without sparse_super, every block group has a superblock */ + if (!(info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + + if (bg == 0 || bg == 1) + return 1; + + if (is_power_of(bg, 3) || is_power_of(bg, 5) || is_power_of(bg, 7)) + return 1; + + return 0; +} + +/* Function to read the primary superblock */ +void read_sb(int fd, struct ext4_super_block *sb) +{ + off64_t ret; + + ret = lseek64(fd, 1024, SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to superblock"); + + ret = read(fd, sb, sizeof(*sb)); + if (ret < 0) + critical_error_errno("failed to read superblock"); + if (ret != sizeof(*sb)) + critical_error("failed to read all of superblock"); +} + +/* Function to write a primary or backup superblock at a given offset */ +void write_sb(int fd, unsigned long long offset, struct ext4_super_block *sb) +{ + off64_t ret; + + ret = lseek64(fd, offset, SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to superblock"); + + ret = write(fd, sb, sizeof(*sb)); + if (ret < 0) + critical_error_errno("failed to write superblock"); + if (ret != sizeof(*sb)) + critical_error("failed to write all of superblock"); +} + +/* Write the filesystem image to a file */ +void write_ext4_image(int fd, int gz, int sparse, int crc) +{ + sparse_file_write(ext4_sparse_file, fd, gz, sparse, crc); +} + +/* Compute the rest of the parameters of the filesystem from the basic info */ +void ext4_create_fs_aux_info() +{ + aux_info.first_data_block = (info.block_size > 1024) ? 0 : 1; + aux_info.len_blocks = info.len / info.block_size; + aux_info.inode_table_blocks = DIV_ROUND_UP(info.inodes_per_group * info.inode_size, + info.block_size); + aux_info.groups = DIV_ROUND_UP(aux_info.len_blocks - aux_info.first_data_block, + info.blocks_per_group); + aux_info.blocks_per_ind = info.block_size / sizeof(u32); + aux_info.blocks_per_dind = aux_info.blocks_per_ind * aux_info.blocks_per_ind; + aux_info.blocks_per_tind = aux_info.blocks_per_dind * aux_info.blocks_per_dind; + + aux_info.bg_desc_blocks = + DIV_ROUND_UP(aux_info.groups * sizeof(struct ext2_group_desc), + info.block_size); + + aux_info.default_i_flags = EXT4_NOATIME_FL; + + u32 last_group_size = aux_info.len_blocks % info.blocks_per_group; + u32 last_header_size = 2 + aux_info.inode_table_blocks; + if (ext4_bg_has_super_block(aux_info.groups - 1)) + last_header_size += 1 + aux_info.bg_desc_blocks + + info.bg_desc_reserve_blocks; + if (last_group_size > 0 && last_group_size < last_header_size) { + aux_info.groups--; + aux_info.len_blocks -= last_group_size; + } + + aux_info.sb = calloc(info.block_size, 1); + /* Alloc an array to hold the pointers to the backup superblocks */ + aux_info.backup_sb = calloc(aux_info.groups, sizeof(char *)); + + if (!aux_info.sb) + critical_error_errno("calloc"); + + aux_info.bg_desc = calloc(info.block_size, aux_info.bg_desc_blocks); + if (!aux_info.bg_desc) + critical_error_errno("calloc"); + aux_info.xattrs = NULL; +} + +void ext4_free_fs_aux_info() +{ + unsigned int i; + + for (i=0; is_inodes_count = info.inodes_per_group * aux_info.groups; + sb->s_blocks_count_lo = aux_info.len_blocks; + sb->s_r_blocks_count_lo = 0; + sb->s_free_blocks_count_lo = 0; + sb->s_free_inodes_count = 0; + sb->s_first_data_block = aux_info.first_data_block; + sb->s_log_block_size = log_2(info.block_size / 1024); + sb->s_obso_log_frag_size = log_2(info.block_size / 1024); + sb->s_blocks_per_group = info.blocks_per_group; + sb->s_obso_frags_per_group = info.blocks_per_group; + sb->s_inodes_per_group = info.inodes_per_group; + sb->s_mtime = 0; + sb->s_wtime = 0; + sb->s_mnt_count = 0; + sb->s_max_mnt_count = 0xFFFF; + sb->s_magic = EXT4_SUPER_MAGIC; + sb->s_state = EXT4_VALID_FS; + sb->s_errors = EXT4_ERRORS_RO; + sb->s_minor_rev_level = 0; + sb->s_lastcheck = 0; + sb->s_checkinterval = 0; + sb->s_creator_os = EXT4_OS_LINUX; + sb->s_rev_level = EXT4_DYNAMIC_REV; + sb->s_def_resuid = EXT4_DEF_RESUID; + sb->s_def_resgid = EXT4_DEF_RESGID; + + sb->s_first_ino = EXT4_GOOD_OLD_FIRST_INO; + sb->s_inode_size = info.inode_size; + sb->s_block_group_nr = 0; + sb->s_feature_compat = info.feat_compat; + sb->s_feature_incompat = info.feat_incompat; + sb->s_feature_ro_compat = info.feat_ro_compat; + generate_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid); + memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name)); + strncpy(sb->s_volume_name, info.label, sizeof(sb->s_volume_name)); + memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted)); + sb->s_algorithm_usage_bitmap = 0; + + sb->s_reserved_gdt_blocks = info.bg_desc_reserve_blocks; + sb->s_prealloc_blocks = 0; + sb->s_prealloc_dir_blocks = 0; + + //memcpy(sb->s_journal_uuid, sb->s_uuid, sizeof(sb->s_journal_uuid)); + if (info.feat_compat & EXT4_FEATURE_COMPAT_HAS_JOURNAL) + sb->s_journal_inum = EXT4_JOURNAL_INO; + sb->s_journal_dev = 0; + sb->s_last_orphan = 0; + sb->s_hash_seed[0] = 0; /* FIXME */ + sb->s_def_hash_version = DX_HASH_TEA; + sb->s_reserved_char_pad = EXT4_JNL_BACKUP_BLOCKS; + sb->s_desc_size = sizeof(struct ext2_group_desc); + sb->s_default_mount_opts = 0; /* FIXME */ + sb->s_first_meta_bg = 0; + sb->s_mkfs_time = 0; + //sb->s_jnl_blocks[17]; /* FIXME */ + + sb->s_blocks_count_hi = aux_info.len_blocks >> 32; + sb->s_r_blocks_count_hi = 0; + sb->s_free_blocks_count_hi = 0; + sb->s_min_extra_isize = sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE; + sb->s_want_extra_isize = sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE; + sb->s_flags = 2; + sb->s_raid_stride = 0; + sb->s_mmp_interval = 0; + sb->s_mmp_block = 0; + sb->s_raid_stripe_width = 0; + sb->s_log_groups_per_flex = 0; + sb->s_kbytes_written = 0; + + for (i = 0; i < aux_info.groups; i++) { + u64 group_start_block = aux_info.first_data_block + i * + info.blocks_per_group; + u32 header_size = 0; + if (ext4_bg_has_super_block(i)) { + if (i != 0) { + aux_info.backup_sb[i] = calloc(info.block_size, 1); + memcpy(aux_info.backup_sb[i], sb, info.block_size); + /* Update the block group nr of this backup superblock */ + aux_info.backup_sb[i]->s_block_group_nr = i; + sparse_file_add_data(ext4_sparse_file, aux_info.backup_sb[i], + info.block_size, group_start_block); + } + sparse_file_add_data(ext4_sparse_file, aux_info.bg_desc, + aux_info.bg_desc_blocks * info.block_size, + group_start_block + 1); + header_size = 1 + aux_info.bg_desc_blocks + info.bg_desc_reserve_blocks; + } + + aux_info.bg_desc[i].bg_block_bitmap = group_start_block + header_size; + aux_info.bg_desc[i].bg_inode_bitmap = group_start_block + header_size + 1; + aux_info.bg_desc[i].bg_inode_table = group_start_block + header_size + 2; + + aux_info.bg_desc[i].bg_free_blocks_count = sb->s_blocks_per_group; + aux_info.bg_desc[i].bg_free_inodes_count = sb->s_inodes_per_group; + aux_info.bg_desc[i].bg_used_dirs_count = 0; + } +} + +void ext4_queue_sb(void) +{ + /* The write_data* functions expect only block aligned calls. + * This is not an issue, except when we write out the super + * block on a system with a block size > 1K. So, we need to + * deal with that here. + */ + if (info.block_size > 1024) { + u8 *buf = calloc(info.block_size, 1); + memcpy(buf + 1024, (u8*)aux_info.sb, 1024); + sparse_file_add_data(ext4_sparse_file, buf, info.block_size, 0); + } else { + sparse_file_add_data(ext4_sparse_file, aux_info.sb, 1024, 1); + } +} + +void ext4_parse_sb_info(struct ext4_super_block *sb) +{ + if (sb->s_magic != EXT4_SUPER_MAGIC) + error("superblock magic incorrect"); + + if ((sb->s_state & EXT4_VALID_FS) != EXT4_VALID_FS) + error("filesystem state not valid"); + + ext4_parse_sb(sb, &info); + + ext4_create_fs_aux_info(); + + memcpy(aux_info.sb, sb, sizeof(*sb)); + + if (aux_info.first_data_block != sb->s_first_data_block) + critical_error("first data block does not match"); +} + +void ext4_create_resize_inode() +{ + struct block_allocation *reserve_inode_alloc = create_allocation(); + u32 reserve_inode_len = 0; + unsigned int i; + + struct ext4_inode *inode = get_inode(EXT4_RESIZE_INO); + if (inode == NULL) { + error("failed to get resize inode"); + return; + } + + for (i = 0; i < aux_info.groups; i++) { + if (ext4_bg_has_super_block(i)) { + u64 group_start_block = aux_info.first_data_block + i * + info.blocks_per_group; + u32 reserved_block_start = group_start_block + 1 + + aux_info.bg_desc_blocks; + u32 reserved_block_len = info.bg_desc_reserve_blocks; + append_region(reserve_inode_alloc, reserved_block_start, + reserved_block_len, i); + reserve_inode_len += reserved_block_len; + } + } + + inode_attach_resize(inode, reserve_inode_alloc); + + inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR; + inode->i_links_count = 1; + + free_alloc(reserve_inode_alloc); +} + +/* Allocate the blocks to hold a journal inode and connect them to the + reserved journal inode */ +void ext4_create_journal_inode() +{ + struct ext4_inode *inode = get_inode(EXT4_JOURNAL_INO); + if (inode == NULL) { + error("failed to get journal inode"); + return; + } + + u8 *journal_data = inode_allocate_data_extents(inode, + info.journal_blocks * info.block_size, + info.journal_blocks * info.block_size); + if (!journal_data) { + error("failed to allocate extents for journal data"); + return; + } + + inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR; + inode->i_links_count = 1; + + journal_superblock_t *jsb = (journal_superblock_t *)journal_data; + jsb->s_header.h_magic = htonl(JBD2_MAGIC_NUMBER); + jsb->s_header.h_blocktype = htonl(JBD2_SUPERBLOCK_V2); + jsb->s_blocksize = htonl(info.block_size); + jsb->s_maxlen = htonl(info.journal_blocks); + jsb->s_nr_users = htonl(1); + jsb->s_first = htonl(1); + jsb->s_sequence = htonl(1); + + memcpy(aux_info.sb->s_jnl_blocks, &inode->i_block, sizeof(inode->i_block)); +} + +/* Update the number of free blocks and inodes in the filesystem and in each + block group */ +void ext4_update_free() +{ + u32 i; + + for (i = 0; i < aux_info.groups; i++) { + u32 bg_free_blocks = get_free_blocks(i); + u32 bg_free_inodes = get_free_inodes(i); + u16 crc; + + aux_info.bg_desc[i].bg_free_blocks_count = bg_free_blocks; + aux_info.sb->s_free_blocks_count_lo += bg_free_blocks; + + aux_info.bg_desc[i].bg_free_inodes_count = bg_free_inodes; + aux_info.sb->s_free_inodes_count += bg_free_inodes; + + aux_info.bg_desc[i].bg_used_dirs_count += get_directories(i); + + aux_info.bg_desc[i].bg_flags = get_bg_flags(i); + + crc = ext4_crc16(~0, aux_info.sb->s_uuid, sizeof(aux_info.sb->s_uuid)); + crc = ext4_crc16(crc, &i, sizeof(i)); + crc = ext4_crc16(crc, &aux_info.bg_desc[i], offsetof(struct ext2_group_desc, bg_checksum)); + aux_info.bg_desc[i].bg_checksum = crc; + } +} + +u64 get_block_device_size(int fd) +{ + u64 size = 0; + int ret; + +#if defined(__linux__) + ret = ioctl(fd, BLKGETSIZE64, &size); +#elif defined(__APPLE__) && defined(__MACH__) + ret = ioctl(fd, DKIOCGETBLOCKCOUNT, &size); +#else + close(fd); + return 0; +#endif + + if (ret) + return 0; + + return size; +} + +int is_block_device_fd(int fd) +{ +#ifdef USE_MINGW + return 0; +#else + struct stat st; + int ret = fstat(fd, &st); + if (ret < 0) + return 0; + + return S_ISBLK(st.st_mode); +#endif +} + +u64 get_file_size(int fd) +{ + struct stat buf; + int ret; + u64 reserve_len = 0; + s64 computed_size; + + ret = fstat(fd, &buf); + if (ret) + return 0; + + if (info.len < 0) + reserve_len = -info.len; + + if (S_ISREG(buf.st_mode)) + computed_size = buf.st_size - reserve_len; + else if (S_ISBLK(buf.st_mode)) + computed_size = get_block_device_size(fd) - reserve_len; + else + computed_size = 0; + + if (computed_size < 0) { + warn("Computed filesystem size less than 0"); + computed_size = 0; + } + + return computed_size; +} + +u64 parse_num(const char *arg) +{ + char *endptr; + u64 num = strtoull(arg, &endptr, 10); + if (*endptr == 'k' || *endptr == 'K') + num *= 1024LL; + else if (*endptr == 'm' || *endptr == 'M') + num *= 1024LL * 1024LL; + else if (*endptr == 'g' || *endptr == 'G') + num *= 1024LL * 1024LL * 1024LL; + + return num; +} + +int read_ext(int fd, int verbose) +{ + off64_t ret; + struct ext4_super_block sb; + + read_sb(fd, &sb); + + ext4_parse_sb_info(&sb); + + ret = lseek64(fd, info.len, SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to end of input image"); + + ret = lseek64(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to block group descriptors"); + + ret = read(fd, aux_info.bg_desc, info.block_size * aux_info.bg_desc_blocks); + if (ret < 0) + critical_error_errno("failed to read block group descriptors"); + if (ret != (int)info.block_size * (int)aux_info.bg_desc_blocks) + critical_error("failed to read all of block group descriptors"); + + if (verbose) { + printf("Found filesystem with parameters:\n"); + printf(" Size: %"PRIu64"\n", info.len); + printf(" Block size: %d\n", info.block_size); + printf(" Blocks per group: %d\n", info.blocks_per_group); + printf(" Inodes per group: %d\n", info.inodes_per_group); + printf(" Inode size: %d\n", info.inode_size); + printf(" Label: %s\n", info.label); + printf(" Blocks: %"PRIu64"\n", aux_info.len_blocks); + printf(" Block groups: %d\n", aux_info.groups); + printf(" Reserved block group size: %d\n", info.bg_desc_reserve_blocks); + printf(" Used %d/%d inodes and %d/%d blocks\n", + aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count, + aux_info.sb->s_inodes_count, + aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo, + aux_info.sb->s_blocks_count_lo); + } + + return 0; +} + diff --git a/ext4_utils.h b/ext4_utils.h new file mode 100644 index 0000000..499753f --- /dev/null +++ b/ext4_utils.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _EXT4_UTILS_H_ +#define _EXT4_UTILS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define ftruncate64 ftruncate +#define mmap64 mmap +#define off64_t off_t +#endif + +#include "ext4_sb.h" + +extern int force; + +#define warn(fmt, args...) do { fprintf(stderr, "warning: %s: " fmt "\n", __func__, ## args); } while (0) +#define error(fmt, args...) do { fprintf(stderr, "error: %s: " fmt "\n", __func__, ## args); if (!force) longjmp(setjmp_env, EXIT_FAILURE); } while (0) +#define error_errno(s, args...) error(s ": %s", ##args, strerror(errno)) +#define critical_error(fmt, args...) do { fprintf(stderr, "critical error: %s: " fmt "\n", __func__, ## args); longjmp(setjmp_env, EXIT_FAILURE); } while (0) +#define critical_error_errno(s, args...) critical_error(s ": %s", ##args, strerror(errno)) + +#define EXT4_JNL_BACKUP_BLOCKS 1 + +#ifndef min /* already defined by windows.h */ +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y)) +#define EXT4_ALIGN(x, y) ((y) * DIV_ROUND_UP((x), (y))) + +/* XXX */ +#define cpu_to_le32(x) (x) +#define cpu_to_le16(x) (x) +#define le32_to_cpu(x) (x) +#define le16_to_cpu(x) (x) + +#ifdef __LP64__ +typedef unsigned long u64; +typedef signed long s64; +#else +typedef unsigned long long u64; +typedef signed long long s64; +#endif +typedef unsigned int u32; +typedef unsigned short int u16; +typedef unsigned char u8; + +struct block_group_info; +struct xattr_list_element; + +struct ext2_group_desc { + u32 bg_block_bitmap; + u32 bg_inode_bitmap; + u32 bg_inode_table; + u16 bg_free_blocks_count; + u16 bg_free_inodes_count; + u16 bg_used_dirs_count; + u16 bg_flags; + u32 bg_reserved[2]; + u16 bg_reserved16; + u16 bg_checksum; +}; + +struct fs_aux_info { + struct ext4_super_block *sb; + struct ext4_super_block **backup_sb; + struct ext2_group_desc *bg_desc; + struct block_group_info *bgs; + struct xattr_list_element *xattrs; + u32 first_data_block; + u64 len_blocks; + u32 inode_table_blocks; + u32 groups; + u32 bg_desc_blocks; + u32 default_i_flags; + u32 blocks_per_ind; + u32 blocks_per_dind; + u32 blocks_per_tind; +}; + +extern struct fs_info info; +extern struct fs_aux_info aux_info; +extern struct sparse_file *ext4_sparse_file; + +extern jmp_buf setjmp_env; + +static inline int log_2(int j) +{ + int i; + + for (i = 0; j > 0; i++) + j >>= 1; + + return i - 1; +} + +int bitmap_get_bit(u8 *bitmap, u32 bit); +void bitmap_clear_bit(u8 *bitmap, u32 bit); +int ext4_bg_has_super_block(int bg); +void read_sb(int fd, struct ext4_super_block *sb); +void write_sb(int fd, unsigned long long offset, struct ext4_super_block *sb); +void write_ext4_image(int fd, int gz, int sparse, int crc); +void ext4_create_fs_aux_info(void); +void ext4_free_fs_aux_info(void); +void ext4_fill_in_sb(void); +void ext4_create_resize_inode(void); +void ext4_create_journal_inode(void); +void ext4_update_free(void); +void ext4_queue_sb(void); +u64 get_block_device_size(int fd); +int is_block_device_fd(int fd); +u64 get_file_size(int fd); +u64 parse_num(const char *arg); +void ext4_parse_sb_info(struct ext4_super_block *sb); +u16 ext4_crc16(u16 crc_in, const void *buf, int size); + +typedef void (*fs_config_func_t)(const char *path, int dir, unsigned *uid, unsigned *gid, + unsigned *mode, uint64_t *capabilities); + +struct selabel_handle; + +int make_ext4fs_internal(int fd, const char *directory, + const char *mountpoint, fs_config_func_t fs_config_func, int gzip, + int sparse, int crc, int wipe, + struct selabel_handle *sehnd, int verbose, time_t fixed_time, + FILE* block_list_file); + +int read_ext(int fd, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext4fixup.c b/ext4fixup.c new file mode 100644 index 0000000..184cd0d --- /dev/null +++ b/ext4fixup.c @@ -0,0 +1,811 @@ +/* + * Copyright (C) 2010 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 "ext4_utils.h" +#include "make_ext4fs.h" +#include "ext4_extents.h" +#include "allocate.h" +#include "ext4fixup.h" + +#include + +#include +#include +#include +#include +#include + +#ifndef USE_MINGW +#include +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define off64_t off_t +#endif + +/* The inode block count for a file/directory is in units of 512 byte blocks, + * _NOT_ the filesystem block size! + */ +#define INODE_BLOCK_SIZE 512 + +#define MAX_EXT4_BLOCK_SIZE 4096 + +/* The two modes the recurse_dir() can be in */ +#define SANITY_CHECK_PASS 1 +#define MARK_INODE_NUMS 2 +#define UPDATE_INODE_NUMS 3 + +/* Magic numbers to indicate what state the update process is in */ +#define MAGIC_STATE_MARKING_INUMS 0x7000151515565512ll +#define MAGIC_STATE_UPDATING_INUMS 0x6121131211735123ll +#define MAGIC_STATE_UPDATING_SB 0x15e1715151558477ll + +/* Internal state variables corresponding to the magic numbers */ +#define STATE_UNSET 0 +#define STATE_MARKING_INUMS 1 +#define STATE_UPDATING_INUMS 2 +#define STATE_UPDATING_SB 3 + +/* Used for automated testing of this programs ability to stop and be restarted wthout error */ +static int bail_phase = 0; +static int bail_loc = 0; +static int bail_count = 0; +static int count = 0; + +/* global flags */ +static int verbose = 0; +static int no_write = 0; + +static int new_inodes_per_group = 0; + +static int no_write_fixup_state = 0; + +static int compute_new_inum(unsigned int old_inum) +{ + unsigned int group, offset; + + group = (old_inum - 1) / info.inodes_per_group; + offset = (old_inum -1) % info.inodes_per_group; + + return (group * new_inodes_per_group) + offset + 1; +} + +static int get_fs_fixup_state(int fd) +{ + unsigned long long magic; + int ret, len; + + if (no_write) { + return no_write_fixup_state; + } + + lseek64(fd, 0, SEEK_SET); + len = read(fd, &magic, sizeof(magic)); + if (len != sizeof(magic)) { + critical_error("cannot read fixup_state\n"); + } + + switch (magic) { + case MAGIC_STATE_MARKING_INUMS: + ret = STATE_MARKING_INUMS; + break; + case MAGIC_STATE_UPDATING_INUMS: + ret = STATE_UPDATING_INUMS; + break; + case MAGIC_STATE_UPDATING_SB: + ret = STATE_UPDATING_SB; + break; + default: + ret = STATE_UNSET; + } + return ret; +} + +static int set_fs_fixup_state(int fd, int state) +{ + unsigned long long magic; + struct ext4_super_block sb; + int len; + + if (no_write) { + no_write_fixup_state = state; + return 0; + } + + switch (state) { + case STATE_MARKING_INUMS: + magic = MAGIC_STATE_MARKING_INUMS; + break; + case STATE_UPDATING_INUMS: + magic = MAGIC_STATE_UPDATING_INUMS; + break; + case STATE_UPDATING_SB: + magic = MAGIC_STATE_UPDATING_SB; + break; + case STATE_UNSET: + default: + magic = 0ll; + break; + } + + lseek64(fd, 0, SEEK_SET); + len = write(fd, &magic, sizeof(magic)); + if (len != sizeof(magic)) { + critical_error("cannot write fixup_state\n"); + } + + read_sb(fd, &sb); + if (magic) { + /* If we are in the process of updating the filesystem, make it unmountable */ + sb.s_desc_size |= 1; + } else { + /* we are done, so make the filesystem mountable again */ + sb.s_desc_size &= ~1; + } + + if (!no_write) { + write_sb(fd, 1024, &sb); + } + + return 0; +} + +static int read_inode(int fd, unsigned int inum, struct ext4_inode *inode) +{ + unsigned int bg_num, bg_offset; + off64_t inode_offset; + int len; + + bg_num = (inum-1) / info.inodes_per_group; + bg_offset = (inum-1) % info.inodes_per_group; + + inode_offset = ((unsigned long long)aux_info.bg_desc[bg_num].bg_inode_table * info.block_size) + + (bg_offset * info.inode_size); + + if (lseek64(fd, inode_offset, SEEK_SET) < 0) { + critical_error_errno("failed to seek to inode %d\n", inum); + } + + len=read(fd, inode, sizeof(*inode)); + if (len != sizeof(*inode)) { + critical_error_errno("failed to read inode %d\n", inum); + } + + return 0; +} + +static int read_block(int fd, unsigned long long block_num, void *block) +{ + off64_t off; + unsigned int len; + + off = block_num * info.block_size; + + if (lseek64(fd, off, SEEK_SET) , 0) { + critical_error_errno("failed to seek to block %lld\n", block_num); + } + + len=read(fd, block, info.block_size); + if (len != info.block_size) { + critical_error_errno("failed to read block %lld\n", block_num); + } + + return 0; +} + +static int write_block(int fd, unsigned long long block_num, void *block) +{ + off64_t off; + unsigned int len; + + if (no_write) { + return 0; + } + + off = block_num * info.block_size; + + if (lseek64(fd, off, SEEK_SET) < 0) { + critical_error_errno("failed to seek to block %lld\n", block_num); + } + + len=write(fd, block, info.block_size); + if (len != info.block_size) { + critical_error_errno("failed to write block %lld\n", block_num); + } + + return 0; +} + +static void check_inode_bitmap(int fd, unsigned int bg_num) +{ + unsigned int inode_bitmap_block_num; + unsigned char block[MAX_EXT4_BLOCK_SIZE]; + int i, bitmap_updated = 0; + + /* Using the bg_num, aux_info.bg_desc[], info.inodes_per_group and + * new_inodes_per_group, retrieve the inode bitmap, and make sure + * the bits between the old and new size are clear + */ + inode_bitmap_block_num = aux_info.bg_desc[bg_num].bg_inode_bitmap; + + read_block(fd, inode_bitmap_block_num, block); + + for (i = info.inodes_per_group; i < new_inodes_per_group; i++) { + if (bitmap_get_bit(block, i)) { + bitmap_clear_bit(block, i); + bitmap_updated = 1; + } + } + + if (bitmap_updated) { + if (verbose) { + printf("Warning: updated inode bitmap for block group %d\n", bg_num); + } + write_block(fd, inode_bitmap_block_num, block); + } + + return; +} + +/* Update the superblock and bgdesc of the specified block group */ +static int update_superblocks_and_bg_desc(int fd, int state) +{ + off64_t ret; + struct ext4_super_block sb; + unsigned int num_block_groups, total_new_inodes; + unsigned int i; + + + read_sb(fd, &sb); + + /* Compute how many more inodes are now available */ + num_block_groups = DIV_ROUND_UP(aux_info.len_blocks, info.blocks_per_group); + total_new_inodes = num_block_groups * (new_inodes_per_group - sb.s_inodes_per_group); + + if (verbose) { + printf("created %d additional inodes\n", total_new_inodes); + } + + /* Update the free inodes count in each block group descriptor */ + for (i = 0; i < num_block_groups; i++) { + if (state == STATE_UPDATING_SB) { + aux_info.bg_desc[i].bg_free_inodes_count += (new_inodes_per_group - sb.s_inodes_per_group); + } + check_inode_bitmap(fd, i); + } + + /* First some sanity checks */ + if ((sb.s_inodes_count + total_new_inodes) != (new_inodes_per_group * num_block_groups)) { + critical_error("Failed sanity check on new inode count\n"); + } + if (new_inodes_per_group % (info.block_size/info.inode_size)) { + critical_error("Failed sanity check on new inode per group alignment\n"); + } + + /* Update the free inodes count in the superblock */ + sb.s_inodes_count += total_new_inodes; + sb.s_free_inodes_count += total_new_inodes; + sb.s_inodes_per_group = new_inodes_per_group; + + for (i = 0; i < aux_info.groups; i++) { + if (ext4_bg_has_super_block(i)) { + unsigned int sb_offset; + + if (i == 0) { + /* The first superblock is offset by 1K to leave room for boot sectors */ + sb_offset = 1024; + } else { + sb_offset = 0; + } + + sb.s_block_group_nr = i; + /* Don't write out the backup superblocks with the bit set in the s_desc_size + * which prevents the filesystem from mounting. The bit for the primary + * superblock will be cleared on the final call to set_fs_fixup_state() */ + if (i != 0) { + sb.s_desc_size &= ~1; + } + + if (!no_write) { + write_sb(fd, + (unsigned long long)i + * info.blocks_per_group * info.block_size + + sb_offset, + &sb); + } + + ret = lseek64(fd, ((unsigned long long)i * info.blocks_per_group * info.block_size) + + (info.block_size * (aux_info.first_data_block + 1)), SEEK_SET); + if (ret < 0) + critical_error_errno("failed to seek to block group descriptors"); + + if (!no_write) { + ret = write(fd, aux_info.bg_desc, info.block_size * aux_info.bg_desc_blocks); + if (ret < 0) + critical_error_errno("failed to write block group descriptors"); + if (ret != (int)info.block_size * (int)aux_info.bg_desc_blocks) + critical_error("failed to write all of block group descriptors"); + } + } + if ((bail_phase == 4) && ((unsigned int)bail_count == i)) { + critical_error("bailing at phase 4\n"); + } + } + + return 0; +} + + +static int get_direct_blocks(struct ext4_inode *inode, unsigned long long *block_list, + unsigned int *count) +{ + unsigned int i = 0; + unsigned int ret = 0; + unsigned int sectors_per_block; + + sectors_per_block = info.block_size / INODE_BLOCK_SIZE; + while ((i < (inode->i_blocks_lo / sectors_per_block)) && (i < EXT4_NDIR_BLOCKS)) { + block_list[i] = inode->i_block[i]; + i++; + } + + *count += i; + + if ((inode->i_blocks_lo / sectors_per_block) > EXT4_NDIR_BLOCKS) { + ret = 1; + } + + return ret; +} + +static int get_indirect_blocks(int fd, struct ext4_inode *inode, + unsigned long long *block_list, unsigned int *count) +{ + unsigned int i; + unsigned int *indirect_block; + unsigned int sectors_per_block; + + sectors_per_block = info.block_size / INODE_BLOCK_SIZE; + + indirect_block = (unsigned int *)malloc(info.block_size); + if (indirect_block == 0) { + critical_error("failed to allocate memory for indirect_block\n"); + } + + read_block(fd, inode->i_block[EXT4_NDIR_BLOCKS], indirect_block); + + for(i = 0; i < (inode->i_blocks_lo / sectors_per_block - EXT4_NDIR_BLOCKS); i++) { + block_list[EXT4_NDIR_BLOCKS+i] = indirect_block[i]; + } + + *count += i; + + free(indirect_block); + + return 0; +} + +static int get_block_list_indirect(int fd, struct ext4_inode *inode, unsigned long long *block_list) +{ + unsigned int count=0; + + if (get_direct_blocks(inode, block_list, &count)) { + get_indirect_blocks(fd, inode, block_list, &count); + } + + return count; +} + +static int get_extent_ents(struct ext4_extent_header *ext_hdr, unsigned long long *block_list) +{ + int i, j; + struct ext4_extent *extent; + off64_t fs_block_num; + + if (ext_hdr->eh_depth != 0) { + critical_error("get_extent_ents called with eh_depth != 0\n"); + } + + /* The extent entries immediately follow the header, so add 1 to the pointer + * and cast it to an extent pointer. + */ + extent = (struct ext4_extent *)(ext_hdr + 1); + + for (i = 0; i < ext_hdr->eh_entries; i++) { + fs_block_num = ((off64_t)extent->ee_start_hi << 32) | extent->ee_start_lo; + for (j = 0; j < extent->ee_len; j++) { + block_list[extent->ee_block+j] = fs_block_num+j; + } + extent++; + } + + return 0; +} + +static int get_extent_idx(int fd, struct ext4_extent_header *ext_hdr, unsigned long long *block_list) +{ + int i; + struct ext4_extent_idx *extent_idx; + struct ext4_extent_header *tmp_ext_hdr; + off64_t fs_block_num; + unsigned char block[MAX_EXT4_BLOCK_SIZE]; + + /* Sanity check */ + if (ext_hdr->eh_depth == 0) { + critical_error("get_extent_idx called with eh_depth == 0\n"); + } + + /* The extent entries immediately follow the header, so add 1 to the pointer + * and cast it to an extent pointer. + */ + extent_idx = (struct ext4_extent_idx *)(ext_hdr + 1); + + for (i = 0; i < ext_hdr->eh_entries; i++) { + fs_block_num = ((off64_t)extent_idx->ei_leaf_hi << 32) | extent_idx->ei_leaf_lo; + read_block(fd, fs_block_num, block); + tmp_ext_hdr = (struct ext4_extent_header *)block; + + if (tmp_ext_hdr->eh_depth == 0) { + get_extent_ents(tmp_ext_hdr, block_list); /* leaf node, fill in block_list */ + } else { + get_extent_idx(fd, tmp_ext_hdr, block_list); /* recurse down the tree */ + } + } + + return 0; +} + +static int get_block_list_extents(int fd, struct ext4_inode *inode, unsigned long long *block_list) +{ + struct ext4_extent_header *extent_hdr; + + extent_hdr = (struct ext4_extent_header *)inode->i_block; + + if (extent_hdr->eh_magic != EXT4_EXT_MAGIC) { + critical_error("extent header has unexpected magic value 0x%4.4x\n", + extent_hdr->eh_magic); + } + + if (extent_hdr->eh_depth == 0) { + get_extent_ents((struct ext4_extent_header *)inode->i_block, block_list); + return 0; + } + + get_extent_idx(fd, (struct ext4_extent_header *)inode->i_block, block_list); + + return 0; +} + +static int is_entry_dir(int fd, struct ext4_dir_entry_2 *dirp, int pass) +{ + struct ext4_inode inode; + int ret = 0; + + if (dirp->file_type == EXT4_FT_DIR) { + ret = 1; + } else if (dirp->file_type == EXT4_FT_UNKNOWN) { + /* Somebody was too lazy to fill in the dir entry, + * so we have to go fetch it from the inode. Grrr. + */ + /* if UPDATE_INODE_NUMS pass and the inode high bit is not + * set return false so we don't recurse down the tree that is + * already updated. Otherwise, fetch inode, and return answer. + */ + if ((pass == UPDATE_INODE_NUMS) && !(dirp->inode & 0x80000000)) { + ret = 0; + } else { + read_inode(fd, (dirp->inode & 0x7fffffff), &inode); + if (S_ISDIR(inode.i_mode)) { + ret = 1; + } + } + } + + return ret; +} + +static int recurse_dir(int fd, struct ext4_inode *inode, char *dirbuf, int dirsize, int mode) +{ + unsigned long long *block_list; + unsigned int num_blocks; + struct ext4_dir_entry_2 *dirp, *prev_dirp = 0; + char name[256]; + unsigned int i, leftover_space, is_dir; + struct ext4_inode tmp_inode; + int tmp_dirsize; + char *tmp_dirbuf; + + switch (mode) { + case SANITY_CHECK_PASS: + case MARK_INODE_NUMS: + case UPDATE_INODE_NUMS: + break; + default: + critical_error("recurse_dir() called witn unknown mode!\n"); + } + + if (dirsize % info.block_size) { + critical_error("dirsize %d not a multiple of block_size %d. This is unexpected!\n", + dirsize, info.block_size); + } + + num_blocks = dirsize / info.block_size; + + block_list = malloc((num_blocks + 1) * sizeof(*block_list)); + if (block_list == 0) { + critical_error("failed to allocate memory for block_list\n"); + } + + if (inode->i_flags & EXT4_EXTENTS_FL) { + get_block_list_extents(fd, inode, block_list); + } else { + /* A directory that requires doubly or triply indirect blocks in huge indeed, + * and will almost certainly not exist, especially since make_ext4fs only creates + * directories with extents, and the kernel will too, but check to make sure the + * directory is not that big and give an error if so. Our limit is 12 direct blocks, + * plus block_size/4 singly indirect blocks, which for a filesystem with 4K blocks + * is a directory 1036 blocks long, or 4,243,456 bytes long! Assuming an average + * filename length of 20 (which I think is generous) thats 20 + 8 bytes overhead + * per entry, or 151,552 entries in the directory! + */ + if (num_blocks > (info.block_size / 4 + EXT4_NDIR_BLOCKS)) { + critical_error("Non-extent based directory is too big!\n"); + } + get_block_list_indirect(fd, inode, block_list); + } + + /* Read in all the blocks for this directory */ + for (i = 0; i < num_blocks; i++) { + read_block(fd, block_list[i], dirbuf + (i * info.block_size)); + } + + dirp = (struct ext4_dir_entry_2 *)dirbuf; + while (dirp < (struct ext4_dir_entry_2 *)(dirbuf + dirsize)) { + count++; + leftover_space = (char *)(dirbuf + dirsize) - (char *)dirp; + if (((mode == SANITY_CHECK_PASS) || (mode == UPDATE_INODE_NUMS)) && + (leftover_space <= 8) && prev_dirp) { + /* This is a bug in an older version of make_ext4fs, where it + * didn't properly include the rest of the block in rec_len. + * Update rec_len on the previous entry to include the rest of + * the block and exit the loop. + */ + if (verbose) { + printf("fixing up short rec_len for diretory entry for %s\n", name); + } + prev_dirp->rec_len += leftover_space; + break; + } + + if (dirp->inode == 0) { + /* This is the last entry in the directory */ + break; + } + + strncpy(name, dirp->name, dirp->name_len); + name[dirp->name_len]='\0'; + + /* Only recurse on pass UPDATE_INODE_NUMS if the high bit is set. + * Otherwise, this inode entry has already been updated + * and we'll do the wrong thing. Also don't recurse on . or .., + * and certainly not on non-directories! + */ + /* Hrm, looks like filesystems made by fastboot on stingray set the file_type + * flag, but the lost+found directory has the type set to Unknown, which + * seems to imply I need to read the inode and get it. + */ + is_dir = is_entry_dir(fd, dirp, mode); + if ( is_dir && (strcmp(name, ".") && strcmp(name, "..")) && + ((mode == SANITY_CHECK_PASS) || (mode == MARK_INODE_NUMS) || + ((mode == UPDATE_INODE_NUMS) && (dirp->inode & 0x80000000))) ) { + /* A directory! Recurse! */ + read_inode(fd, dirp->inode & 0x7fffffff, &tmp_inode); + + if (!S_ISDIR(tmp_inode.i_mode)) { + critical_error("inode %d for name %s does not point to a directory\n", + dirp->inode & 0x7fffffff, name); + } + if (verbose) { + printf("inode %d %s use extents\n", dirp->inode & 0x7fffffff, + (tmp_inode.i_flags & EXT4_EXTENTS_FL) ? "does" : "does not"); + } + + tmp_dirsize = tmp_inode.i_blocks_lo * INODE_BLOCK_SIZE; + if (verbose) { + printf("dir size = %d bytes\n", tmp_dirsize); + } + + tmp_dirbuf = malloc(tmp_dirsize); + if (tmp_dirbuf == 0) { + critical_error("failed to allocate memory for tmp_dirbuf\n"); + } + + recurse_dir(fd, &tmp_inode, tmp_dirbuf, tmp_dirsize, mode); + + free(tmp_dirbuf); + } + + if (verbose) { + if (is_dir) { + printf("Directory %s\n", name); + } else { + printf("Non-directory %s\n", name); + } + } + + /* Process entry based on current mode. Either set high bit or change inode number */ + if (mode == MARK_INODE_NUMS) { + dirp->inode |= 0x80000000; + } else if (mode == UPDATE_INODE_NUMS) { + if (dirp->inode & 0x80000000) { + dirp->inode = compute_new_inum(dirp->inode & 0x7fffffff); + } + } + + if ((bail_phase == mode) && (bail_loc == 1) && (bail_count == count)) { + critical_error("Bailing at phase %d, loc 1 and count %d\n", mode, count); + } + + /* Point dirp at the next entry */ + prev_dirp = dirp; + dirp = (struct ext4_dir_entry_2*)((char *)dirp + dirp->rec_len); + } + + /* Write out all the blocks for this directory */ + for (i = 0; i < num_blocks; i++) { + write_block(fd, block_list[i], dirbuf + (i * info.block_size)); + if ((bail_phase == mode) && (bail_loc == 2) && (bail_count <= count)) { + critical_error("Bailing at phase %d, loc 2 and count %d\n", mode, count); + } + } + + free(block_list); + + return 0; +} + +int ext4fixup(char *fsdev) +{ + return ext4fixup_internal(fsdev, 0, 0, 0, 0, 0); +} + +int ext4fixup_internal(char *fsdev, int v_flag, int n_flag, + int stop_phase, int stop_loc, int stop_count) +{ + int fd; + struct ext4_inode root_inode; + unsigned int dirsize; + char *dirbuf; + + if (setjmp(setjmp_env)) + return EXIT_FAILURE; /* Handle a call to longjmp() */ + + verbose = v_flag; + no_write = n_flag; + + bail_phase = stop_phase; + bail_loc = stop_loc; + bail_count = stop_count; + + fd = open(fsdev, O_RDWR); + + if (fd < 0) + critical_error_errno("failed to open filesystem image"); + + read_ext(fd, verbose); + + if (info.feat_incompat & EXT4_FEATURE_INCOMPAT_RECOVER) { + critical_error("Filesystem needs recovery first, mount and unmount to do that\n"); + } + + /* Clear the low bit which is set while this tool is in progress. + * If the tool crashes, it will still be set when we restart. + * The low bit is set to make the filesystem unmountable while + * it is being fixed up. Also allow 0, which means the old ext2 + * size is in use. + */ + if (((aux_info.sb->s_desc_size & ~1) != sizeof(struct ext2_group_desc)) && + ((aux_info.sb->s_desc_size & ~1) != 0)) + critical_error("error: bg_desc_size != sizeof(struct ext2_group_desc)\n"); + + if ((info.feat_incompat & EXT4_FEATURE_INCOMPAT_FILETYPE) == 0) { + critical_error("Expected filesystem to have filetype flag set\n"); + } + +#if 0 // If we have to fix the directory rec_len issue, we can't use this check + /* Check to see if the inodes/group is copacetic */ + if (info.inodes_per_blockgroup % (info.block_size/info.inode_size) == 0) { + /* This filesystem has either already been updated, or was + * made correctly. + */ + if (verbose) { + printf("%s: filesystem correct, no work to do\n", me); + } + exit(0); + } +#endif + + /* Compute what the new value of inodes_per_blockgroup will be when we're done */ + new_inodes_per_group=EXT4_ALIGN(info.inodes_per_group,(info.block_size/info.inode_size)); + + read_inode(fd, EXT4_ROOT_INO, &root_inode); + + if (!S_ISDIR(root_inode.i_mode)) { + critical_error("root inode %d does not point to a directory\n", EXT4_ROOT_INO); + } + if (verbose) { + printf("inode %d %s use extents\n", EXT4_ROOT_INO, + (root_inode.i_flags & EXT4_EXTENTS_FL) ? "does" : "does not"); + } + + dirsize = root_inode.i_blocks_lo * INODE_BLOCK_SIZE; + if (verbose) { + printf("root dir size = %d bytes\n", dirsize); + } + + dirbuf = malloc(dirsize); + if (dirbuf == 0) { + critical_error("failed to allocate memory for dirbuf\n"); + } + + /* Perform a sanity check pass first, try to catch any errors that will occur + * before we actually change anything, so we don't leave a filesystem in a + * corrupted, unrecoverable state. Set no_write, make it quiet, and do a recurse + * pass and a update_superblock pass. Set flags back to requested state when done. + * Only perform sanity check if the state is unset. If the state is _NOT_ unset, + * then the tool has already been run and interrupted, and it presumably ran and + * passed sanity checked before it got interrupted. It is _NOT_ safe to run sanity + * check if state is unset because it assumes inodes are to be computed using the + * old inodes/group, but some inode numbers may be updated to the new number. + */ + if (get_fs_fixup_state(fd) == STATE_UNSET) { + verbose = 0; + no_write = 1; + recurse_dir(fd, &root_inode, dirbuf, dirsize, SANITY_CHECK_PASS); + update_superblocks_and_bg_desc(fd, STATE_UNSET); + verbose = v_flag; + no_write = n_flag; + + set_fs_fixup_state(fd, STATE_MARKING_INUMS); + } + + if (get_fs_fixup_state(fd) == STATE_MARKING_INUMS) { + count = 0; /* Reset debugging counter */ + if (!recurse_dir(fd, &root_inode, dirbuf, dirsize, MARK_INODE_NUMS)) { + set_fs_fixup_state(fd, STATE_UPDATING_INUMS); + } + } + + if (get_fs_fixup_state(fd) == STATE_UPDATING_INUMS) { + count = 0; /* Reset debugging counter */ + if (!recurse_dir(fd, &root_inode, dirbuf, dirsize, UPDATE_INODE_NUMS)) { + set_fs_fixup_state(fd, STATE_UPDATING_SB); + } + } + + if (get_fs_fixup_state(fd) == STATE_UPDATING_SB) { + /* set the new inodes/blockgroup number, + * and sets the state back to 0. + */ + if (!update_superblocks_and_bg_desc(fd, STATE_UPDATING_SB)) { + set_fs_fixup_state(fd, STATE_UNSET); + } + } + + close(fd); + + return 0; +} diff --git a/ext4fixup.h b/ext4fixup.h new file mode 100644 index 0000000..6ea2113 --- /dev/null +++ b/ext4fixup.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2010 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. + */ + +int ext4fixup(char *fsdev); +int ext4fixup_internal(char *fsdev, int v_flag, int n_flag, + int stop_phase, int stop_loc, int stop_count); + diff --git a/ext4fixup_main.c b/ext4fixup_main.c new file mode 100644 index 0000000..47c7e65 --- /dev/null +++ b/ext4fixup_main.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 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 +#include +#include +#include +#include "ext4fixup.h" + +static void usage(char *me) +{ + fprintf(stderr, "%s: usage: %s [-vn] \n", me, me); +} + +int main(int argc, char **argv) +{ + int opt; + int verbose = 0; + int no_write = 0; + char *fsdev; + char *me; + int stop_phase = 0, stop_loc = 0, stop_count = 0; + + me = basename(argv[0]); + + while ((opt = getopt(argc, argv, "vnd:")) != -1) { + switch (opt) { + case 'v': + verbose = 1; + break; + case 'n': + no_write = 1; + break; + case 'd': + sscanf(optarg, "%d,%d,%d", &stop_phase, &stop_loc, &stop_count); + break; + } + } + + if (optind >= argc) { + fprintf(stderr, "expected image or block device after options\n"); + usage(me); + exit(EXIT_FAILURE); + } + + fsdev = argv[optind++]; + + if (optind < argc) { + fprintf(stderr, "Unexpected argument: %s\n", argv[optind]); + usage(me); + exit(EXIT_FAILURE); + } + + return ext4fixup_internal(fsdev, verbose, no_write, stop_phase, stop_loc, stop_count); +} diff --git a/extent.c b/extent.c new file mode 100644 index 0000000..1900b10 --- /dev/null +++ b/extent.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2010 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 "ext4_utils.h" +#include "extent.h" + +#include + +#include +#include +#include + + +/* Creates data buffers for the first backing_len bytes of a block allocation + and queues them to be written */ +static u8 *extent_create_backing(struct block_allocation *alloc, + u64 backing_len) +{ + u8 *data = calloc(backing_len, 1); + if (!data) + critical_error_errno("calloc"); + + u8 *ptr = data; + for (; alloc != NULL && backing_len > 0; get_next_region(alloc)) { + u32 region_block; + u32 region_len; + u32 len; + get_region(alloc, ®ion_block, ®ion_len); + + len = min(region_len * info.block_size, backing_len); + + sparse_file_add_data(ext4_sparse_file, ptr, len, region_block); + ptr += len; + backing_len -= len; + } + + return data; +} + +/* Queues each chunk of a file to be written to contiguous data block + regions */ +static void extent_create_backing_file(struct block_allocation *alloc, + u64 backing_len, const char *filename) +{ + off64_t offset = 0; + for (; alloc != NULL && backing_len > 0; get_next_region(alloc)) { + u32 region_block; + u32 region_len; + u32 len; + get_region(alloc, ®ion_block, ®ion_len); + + len = min(region_len * info.block_size, backing_len); + + sparse_file_add_file(ext4_sparse_file, filename, offset, len, + region_block); + offset += len; + backing_len -= len; + } +} + +static struct block_allocation *do_inode_allocate_extents( + struct ext4_inode *inode, u64 len) +{ + u32 block_len = DIV_ROUND_UP(len, info.block_size); + struct block_allocation *alloc = allocate_blocks(block_len + 1); + u32 extent_block = 0; + u32 file_block = 0; + struct ext4_extent *extent; + u64 blocks; + + if (alloc == NULL) { + error("Failed to allocate %d blocks\n", block_len + 1); + return NULL; + } + + int allocation_len = block_allocation_num_regions(alloc); + if (allocation_len <= 3) { + reduce_allocation(alloc, 1); + } else { + reserve_oob_blocks(alloc, 1); + extent_block = get_oob_block(alloc, 0); + } + + if (!extent_block) { + struct ext4_extent_header *hdr = + (struct ext4_extent_header *)&inode->i_block[0]; + hdr->eh_magic = EXT4_EXT_MAGIC; + hdr->eh_entries = allocation_len; + hdr->eh_max = 3; + hdr->eh_generation = 0; + hdr->eh_depth = 0; + + extent = (struct ext4_extent *)&inode->i_block[3]; + } else { + struct ext4_extent_header *hdr = + (struct ext4_extent_header *)&inode->i_block[0]; + hdr->eh_magic = EXT4_EXT_MAGIC; + hdr->eh_entries = 1; + hdr->eh_max = 3; + hdr->eh_generation = 0; + hdr->eh_depth = 1; + + struct ext4_extent_idx *idx = + (struct ext4_extent_idx *)&inode->i_block[3]; + idx->ei_block = 0; + idx->ei_leaf_lo = extent_block; + idx->ei_leaf_hi = 0; + idx->ei_unused = 0; + + u8 *data = calloc(info.block_size, 1); + if (!data) + critical_error_errno("calloc"); + + sparse_file_add_data(ext4_sparse_file, data, info.block_size, + extent_block); + + if (((int)(info.block_size - sizeof(struct ext4_extent_header) / + sizeof(struct ext4_extent))) < allocation_len) { + error("File size %"PRIu64" is too big to fit in a single extent block\n", + len); + return NULL; + } + + hdr = (struct ext4_extent_header *)data; + hdr->eh_magic = EXT4_EXT_MAGIC; + hdr->eh_entries = allocation_len; + hdr->eh_max = (info.block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); + hdr->eh_generation = 0; + hdr->eh_depth = 0; + + extent = (struct ext4_extent *)(data + + sizeof(struct ext4_extent_header)); + } + + for (; !last_region(alloc); extent++, get_next_region(alloc)) { + u32 region_block; + u32 region_len; + + get_region(alloc, ®ion_block, ®ion_len); + extent->ee_block = file_block; + extent->ee_len = region_len; + extent->ee_start_hi = 0; + extent->ee_start_lo = region_block; + file_block += region_len; + } + + if (extent_block) + block_len += 1; + + blocks = (u64)block_len * info.block_size / 512; + + inode->i_flags |= EXT4_EXTENTS_FL; + inode->i_size_lo = len; + inode->i_size_high = len >> 32; + inode->i_blocks_lo = blocks; + inode->osd2.linux2.l_i_blocks_high = blocks >> 32; + + rewind_alloc(alloc); + + return alloc; +} + +/* Allocates enough blocks to hold len bytes, with backing_len bytes in a data + buffer, and connects them to an inode. Returns a pointer to the data + buffer. */ +u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len, + u64 backing_len) +{ + struct block_allocation *alloc; + u8 *data = NULL; + + alloc = do_inode_allocate_extents(inode, len); + if (alloc == NULL) { + error("failed to allocate extents for %"PRIu64" bytes", len); + return NULL; + } + + if (backing_len) { + data = extent_create_backing(alloc, backing_len); + if (!data) + error("failed to create backing for %"PRIu64" bytes", backing_len); + } + + free_alloc(alloc); + + return data; +} + +/* Allocates enough blocks to hold len bytes, queues them to be written + from a file, and connects them to an inode. */ +struct block_allocation* inode_allocate_file_extents(struct ext4_inode *inode, u64 len, + const char *filename) +{ + struct block_allocation *alloc; + + alloc = do_inode_allocate_extents(inode, len); + if (alloc == NULL) { + error("failed to allocate extents for %"PRIu64" bytes", len); + return NULL; + } + + extent_create_backing_file(alloc, len, filename); + return alloc; +} + +/* Allocates enough blocks to hold len bytes and connects them to an inode */ +void inode_allocate_extents(struct ext4_inode *inode, u64 len) +{ + struct block_allocation *alloc; + + alloc = do_inode_allocate_extents(inode, len); + if (alloc == NULL) { + error("failed to allocate extents for %"PRIu64" bytes", len); + return; + } + + free_alloc(alloc); +} diff --git a/extent.h b/extent.h new file mode 100644 index 0000000..a78a7b0 --- /dev/null +++ b/extent.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _EXTENT_H_ +#define _EXTENT_H_ + +#include "allocate.h" +#include "ext4_utils.h" + +void inode_allocate_extents(struct ext4_inode *inode, u64 len); +struct block_allocation* inode_allocate_file_extents( + struct ext4_inode *inode, u64 len, const char *filename); +u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len, + u64 backing_len); +void free_extent_blocks(); + +#endif diff --git a/indirect.c b/indirect.c new file mode 100644 index 0000000..cd85a43 --- /dev/null +++ b/indirect.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2010 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 "ext4_utils.h" +#include "indirect.h" +#include "allocate.h" + +#include + +#include +#include + +/* Creates data buffers for the first backing_len bytes of a block allocation + and queues them to be written */ +static u8 *create_backing(struct block_allocation *alloc, + unsigned long backing_len) +{ + if (DIV_ROUND_UP(backing_len, info.block_size) > EXT4_NDIR_BLOCKS) + critical_error("indirect backing larger than %d blocks", EXT4_NDIR_BLOCKS); + + u8 *data = calloc(backing_len, 1); + if (!data) + critical_error_errno("calloc"); + + u8 *ptr = data; + for (; alloc != NULL && backing_len > 0; get_next_region(alloc)) { + u32 region_block; + u32 region_len; + u32 len; + get_region(alloc, ®ion_block, ®ion_len); + + len = min(region_len * info.block_size, backing_len); + + sparse_file_add_data(ext4_sparse_file, ptr, len, region_block); + ptr += len; + backing_len -= len; + } + + return data; +} + +static void reserve_indirect_block(struct block_allocation *alloc, int len) +{ + if (reserve_oob_blocks(alloc, 1)) { + error("failed to reserve oob block"); + return; + } + + if (advance_blocks(alloc, len)) { + error("failed to advance %d blocks", len); + return; + } +} + +static void reserve_dindirect_block(struct block_allocation *alloc, int len) +{ + if (reserve_oob_blocks(alloc, 1)) { + error("failed to reserve oob block"); + return; + } + + while (len > 0) { + int ind_block_len = min((int)aux_info.blocks_per_ind, len); + + reserve_indirect_block(alloc, ind_block_len); + + len -= ind_block_len; + } + +} + +static void reserve_tindirect_block(struct block_allocation *alloc, int len) +{ + if (reserve_oob_blocks(alloc, 1)) { + error("failed to reserve oob block"); + return; + } + + while (len > 0) { + int dind_block_len = min((int)aux_info.blocks_per_dind, len); + + reserve_dindirect_block(alloc, dind_block_len); + + len -= dind_block_len; + } +} + +static void fill_indirect_block(u32 *ind_block, int len, struct block_allocation *alloc) +{ + int i; + for (i = 0; i < len; i++) { + ind_block[i] = get_block(alloc, i); + } +} + +static void fill_dindirect_block(u32 *dind_block, int len, struct block_allocation *alloc) +{ + int i; + u32 ind_block; + + for (i = 0; len > 0; i++) { + ind_block = get_oob_block(alloc, 0); + if (advance_oob_blocks(alloc, 1)) { + error("failed to reserve oob block"); + return; + } + + dind_block[i] = ind_block; + + u32 *ind_block_data = calloc(info.block_size, 1); + sparse_file_add_data(ext4_sparse_file, ind_block_data, info.block_size, + ind_block); + int ind_block_len = min((int)aux_info.blocks_per_ind, len); + + fill_indirect_block(ind_block_data, ind_block_len, alloc); + + if (advance_blocks(alloc, ind_block_len)) { + error("failed to advance %d blocks", ind_block_len); + return; + } + + len -= ind_block_len; + } +} + +static void fill_tindirect_block(u32 *tind_block, int len, struct block_allocation *alloc) +{ + int i; + u32 dind_block; + + for (i = 0; len > 0; i++) { + dind_block = get_oob_block(alloc, 0); + if (advance_oob_blocks(alloc, 1)) { + error("failed to reserve oob block"); + return; + } + + tind_block[i] = dind_block; + + u32 *dind_block_data = calloc(info.block_size, 1); + sparse_file_add_data(ext4_sparse_file, dind_block_data, info.block_size, + dind_block); + int dind_block_len = min((int)aux_info.blocks_per_dind, len); + + fill_dindirect_block(dind_block_data, dind_block_len, alloc); + + len -= dind_block_len; + } +} + +/* Given an allocation, attach as many blocks as possible to direct inode + blocks, and return the rest */ +static int inode_attach_direct_blocks(struct ext4_inode *inode, + struct block_allocation *alloc, u32 *block_len) +{ + int len = min(*block_len, EXT4_NDIR_BLOCKS); + int i; + + for (i = 0; i < len; i++) { + inode->i_block[i] = get_block(alloc, i); + } + + if (advance_blocks(alloc, len)) { + error("failed to advance %d blocks", len); + return -1; + } + + *block_len -= len; + return 0; +} + +/* Given an allocation, attach as many blocks as possible to indirect blocks, + and return the rest + Assumes that the blocks necessary to hold the indirect blocks were included + as part of the allocation */ +static int inode_attach_indirect_blocks(struct ext4_inode *inode, + struct block_allocation *alloc, u32 *block_len) +{ + int len = min(*block_len, aux_info.blocks_per_ind); + + int ind_block = get_oob_block(alloc, 0); + inode->i_block[EXT4_IND_BLOCK] = ind_block; + + if (advance_oob_blocks(alloc, 1)) { + error("failed to advance oob block"); + return -1; + } + + u32 *ind_block_data = calloc(info.block_size, 1); + sparse_file_add_data(ext4_sparse_file, ind_block_data, info.block_size, + ind_block); + + fill_indirect_block(ind_block_data, len, alloc); + + if (advance_blocks(alloc, len)) { + error("failed to advance %d blocks", len); + return -1; + } + + *block_len -= len; + return 0; +} + +/* Given an allocation, attach as many blocks as possible to doubly indirect + blocks, and return the rest. + Assumes that the blocks necessary to hold the indirect and doubly indirect + blocks were included as part of the allocation */ +static int inode_attach_dindirect_blocks(struct ext4_inode *inode, + struct block_allocation *alloc, u32 *block_len) +{ + int len = min(*block_len, aux_info.blocks_per_dind); + + int dind_block = get_oob_block(alloc, 0); + inode->i_block[EXT4_DIND_BLOCK] = dind_block; + + if (advance_oob_blocks(alloc, 1)) { + error("failed to advance oob block"); + return -1; + } + + u32 *dind_block_data = calloc(info.block_size, 1); + sparse_file_add_data(ext4_sparse_file, dind_block_data, info.block_size, + dind_block); + + fill_dindirect_block(dind_block_data, len, alloc); + + if (advance_blocks(alloc, len)) { + error("failed to advance %d blocks", len); + return -1; + } + + *block_len -= len; + return 0; +} + +/* Given an allocation, attach as many blocks as possible to triply indirect + blocks, and return the rest. + Assumes that the blocks necessary to hold the indirect, doubly indirect and + triply indirect blocks were included as part of the allocation */ +static int inode_attach_tindirect_blocks(struct ext4_inode *inode, + struct block_allocation *alloc, u32 *block_len) +{ + int len = min(*block_len, aux_info.blocks_per_tind); + + int tind_block = get_oob_block(alloc, 0); + inode->i_block[EXT4_TIND_BLOCK] = tind_block; + + if (advance_oob_blocks(alloc, 1)) { + error("failed to advance oob block"); + return -1; + } + + u32 *tind_block_data = calloc(info.block_size, 1); + sparse_file_add_data(ext4_sparse_file, tind_block_data, info.block_size, + tind_block); + + fill_tindirect_block(tind_block_data, len, alloc); + + if (advance_blocks(alloc, len)) { + error("failed to advance %d blocks", len); + return -1; + } + + *block_len -= len; + return 0; +} + +static void reserve_all_indirect_blocks(struct block_allocation *alloc, u32 len) +{ + if (len <= EXT4_NDIR_BLOCKS) + return; + + len -= EXT4_NDIR_BLOCKS; + advance_blocks(alloc, EXT4_NDIR_BLOCKS); + + u32 ind_block_len = min(aux_info.blocks_per_ind, len); + reserve_indirect_block(alloc, ind_block_len); + + len -= ind_block_len; + if (len == 0) + return; + + u32 dind_block_len = min(aux_info.blocks_per_dind, len); + reserve_dindirect_block(alloc, dind_block_len); + + len -= dind_block_len; + if (len == 0) + return; + + u32 tind_block_len = min(aux_info.blocks_per_tind, len); + reserve_tindirect_block(alloc, tind_block_len); + + len -= tind_block_len; + if (len == 0) + return; + + error("%d blocks remaining", len); +} + +static u32 indirect_blocks_needed(u32 len) +{ + u32 ind = 0; + + if (len <= EXT4_NDIR_BLOCKS) + return ind; + + len -= EXT4_NDIR_BLOCKS; + + /* We will need an indirect block for the rest of the blocks */ + ind += DIV_ROUND_UP(len, aux_info.blocks_per_ind); + + if (len <= aux_info.blocks_per_ind) + return ind; + + len -= aux_info.blocks_per_ind; + + ind += DIV_ROUND_UP(len, aux_info.blocks_per_dind); + + if (len <= aux_info.blocks_per_dind) + return ind; + + len -= aux_info.blocks_per_dind; + + ind += DIV_ROUND_UP(len, aux_info.blocks_per_tind); + + if (len <= aux_info.blocks_per_tind) + return ind; + + critical_error("request too large"); + return 0; +} + +static int do_inode_attach_indirect(struct ext4_inode *inode, + struct block_allocation *alloc, u32 block_len) +{ + u32 count = block_len; + + if (inode_attach_direct_blocks(inode, alloc, &count)) { + error("failed to attach direct blocks to inode"); + return -1; + } + + if (count > 0) { + if (inode_attach_indirect_blocks(inode, alloc, &count)) { + error("failed to attach indirect blocks to inode"); + return -1; + } + } + + if (count > 0) { + if (inode_attach_dindirect_blocks(inode, alloc, &count)) { + error("failed to attach dindirect blocks to inode"); + return -1; + } + } + + if (count > 0) { + if (inode_attach_tindirect_blocks(inode, alloc, &count)) { + error("failed to attach tindirect blocks to inode"); + return -1; + } + } + + if (count) { + error("blocks left after triply-indirect allocation"); + return -1; + } + + rewind_alloc(alloc); + + return 0; +} + +static struct block_allocation *do_inode_allocate_indirect( + u32 block_len) +{ + u32 indirect_len = indirect_blocks_needed(block_len); + + struct block_allocation *alloc = allocate_blocks(block_len + indirect_len); + + if (alloc == NULL) { + error("Failed to allocate %d blocks", block_len + indirect_len); + return NULL; + } + + return alloc; +} + +/* Allocates enough blocks to hold len bytes and connects them to an inode */ +void inode_allocate_indirect(struct ext4_inode *inode, unsigned long len) +{ + struct block_allocation *alloc; + u32 block_len = DIV_ROUND_UP(len, info.block_size); + u32 indirect_len = indirect_blocks_needed(block_len); + + alloc = do_inode_allocate_indirect(block_len); + if (alloc == NULL) { + error("failed to allocate extents for %lu bytes", len); + return; + } + + reserve_all_indirect_blocks(alloc, block_len); + rewind_alloc(alloc); + + if (do_inode_attach_indirect(inode, alloc, block_len)) + error("failed to attach blocks to indirect inode"); + + inode->i_flags = 0; + inode->i_blocks_lo = (block_len + indirect_len) * info.block_size / 512; + inode->i_size_lo = len; + + free_alloc(alloc); +} + +void inode_attach_resize(struct ext4_inode *inode, + struct block_allocation *alloc) +{ + u32 block_len = block_allocation_len(alloc); + u32 superblocks = block_len / info.bg_desc_reserve_blocks; + u32 i, j; + u64 blocks; + u64 size; + + if (block_len % info.bg_desc_reserve_blocks) + critical_error("reserved blocks not a multiple of %d", + info.bg_desc_reserve_blocks); + + append_oob_allocation(alloc, 1); + u32 dind_block = get_oob_block(alloc, 0); + + u32 *dind_block_data = calloc(info.block_size, 1); + if (!dind_block_data) + critical_error_errno("calloc"); + sparse_file_add_data(ext4_sparse_file, dind_block_data, info.block_size, + dind_block); + + u32 *ind_block_data = calloc(info.block_size, info.bg_desc_reserve_blocks); + if (!ind_block_data) + critical_error_errno("calloc"); + sparse_file_add_data(ext4_sparse_file, ind_block_data, + info.block_size * info.bg_desc_reserve_blocks, + get_block(alloc, 0)); + + for (i = 0; i < info.bg_desc_reserve_blocks; i++) { + int r = (i - aux_info.bg_desc_blocks) % info.bg_desc_reserve_blocks; + if (r < 0) + r += info.bg_desc_reserve_blocks; + + dind_block_data[i] = get_block(alloc, r); + + for (j = 1; j < superblocks; j++) { + u32 b = j * info.bg_desc_reserve_blocks + r; + ind_block_data[r * aux_info.blocks_per_ind + j - 1] = get_block(alloc, b); + } + } + + u32 last_block = EXT4_NDIR_BLOCKS + aux_info.blocks_per_ind + + aux_info.blocks_per_ind * (info.bg_desc_reserve_blocks - 1) + + superblocks - 2; + + blocks = ((u64)block_len + 1) * info.block_size / 512; + size = (u64)last_block * info.block_size; + + inode->i_block[EXT4_DIND_BLOCK] = dind_block; + inode->i_flags = 0; + inode->i_blocks_lo = blocks; + inode->osd2.linux2.l_i_blocks_high = blocks >> 32; + inode->i_size_lo = size; + inode->i_size_high = size >> 32; +} + +/* Allocates enough blocks to hold len bytes, with backing_len bytes in a data + buffer, and connects them to an inode. Returns a pointer to the data + buffer. */ +u8 *inode_allocate_data_indirect(struct ext4_inode *inode, unsigned long len, + unsigned long backing_len) +{ + struct block_allocation *alloc; + u32 block_len = DIV_ROUND_UP(len, info.block_size); + u8 *data = NULL; + + alloc = do_inode_allocate_indirect(block_len); + if (alloc == NULL) { + error("failed to allocate extents for %lu bytes", len); + return NULL; + } + + if (backing_len) { + data = create_backing(alloc, backing_len); + if (!data) + error("failed to create backing for %lu bytes", backing_len); + } + + rewind_alloc(alloc); + if (do_inode_attach_indirect(inode, alloc, block_len)) + error("failed to attach blocks to indirect inode"); + + free_alloc(alloc); + + return data; +} diff --git a/indirect.h b/indirect.h new file mode 100644 index 0000000..cee8979 --- /dev/null +++ b/indirect.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _INDIRECT_H_ +#define _INDIRECT_H_ + +#include "allocate.h" + +void inode_allocate_indirect(struct ext4_inode *inode, unsigned long len); +u8 *inode_allocate_data_indirect(struct ext4_inode *inode, unsigned long len, + unsigned long backing_len); +void inode_attach_resize(struct ext4_inode *inode, + struct block_allocation *alloc); +void free_indirect_blocks(); + +#endif diff --git a/jbd2.h b/jbd2.h new file mode 100644 index 0000000..bac58c2 --- /dev/null +++ b/jbd2.h @@ -0,0 +1,141 @@ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _LINUX_JBD2_H +#define _LINUX_JBD2_H + +#define JBD2_DEBUG +#define jfs_debug jbd_debug + +#define journal_oom_retry 1 + +#undef JBD2_PARANOID_IOFAIL + +#define JBD2_DEFAULT_MAX_COMMIT_AGE 5 + +#define jbd_debug(f, a...) + +#define JBD2_MIN_JOURNAL_BLOCKS 1024 + +#define JBD2_MAGIC_NUMBER 0xc03b3998U + +#define JBD2_DESCRIPTOR_BLOCK 1 +#define JBD2_COMMIT_BLOCK 2 +#define JBD2_SUPERBLOCK_V1 3 +#define JBD2_SUPERBLOCK_V2 4 +#define JBD2_REVOKE_BLOCK 5 + +typedef struct journal_header_s +{ + __be32 h_magic; + __be32 h_blocktype; + __be32 h_sequence; +} journal_header_t; + +#define JBD2_CRC32_CHKSUM 1 +#define JBD2_MD5_CHKSUM 2 +#define JBD2_SHA1_CHKSUM 3 + +#define JBD2_CRC32_CHKSUM_SIZE 4 + +#define JBD2_CHECKSUM_BYTES (32 / sizeof(__u32)) + +struct commit_header { + __be32 h_magic; + __be32 h_blocktype; + __be32 h_sequence; + unsigned char h_chksum_type; + unsigned char h_chksum_size; + unsigned char h_padding[2]; + __be32 h_chksum[JBD2_CHECKSUM_BYTES]; + __be64 h_commit_sec; + __be32 h_commit_nsec; +}; + +typedef struct journal_block_tag_s +{ + __be32 t_blocknr; + __be32 t_flags; + __be32 t_blocknr_high; +} journal_block_tag_t; + +#define JBD2_TAG_SIZE32 (offsetof(journal_block_tag_t, t_blocknr_high)) +#define JBD2_TAG_SIZE64 (sizeof(journal_block_tag_t)) + +typedef struct jbd2_journal_revoke_header_s +{ + journal_header_t r_header; + __be32 r_count; +} jbd2_journal_revoke_header_t; + +#define JBD2_FLAG_ESCAPE 1 +#define JBD2_FLAG_SAME_UUID 2 +#define JBD2_FLAG_DELETED 4 +#define JBD2_FLAG_LAST_TAG 8 + +typedef struct journal_superblock_s +{ + + journal_header_t s_header; + + __be32 s_blocksize; + __be32 s_maxlen; + __be32 s_first; + + __be32 s_sequence; + __be32 s_start; + + __be32 s_errno; + + __be32 s_feature_compat; + __be32 s_feature_incompat; + __be32 s_feature_ro_compat; + + __u8 s_uuid[16]; + + __be32 s_nr_users; + + __be32 s_dynsuper; + + __be32 s_max_transaction; + __be32 s_max_trans_data; + + __u32 s_padding[44]; + + __u8 s_users[16*48]; + +} journal_superblock_t; + +#define JBD2_HAS_COMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_compat & cpu_to_be32((mask)))) +#define JBD2_HAS_RO_COMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask)))) +#define JBD2_HAS_INCOMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask)))) + +#define JBD2_FEATURE_COMPAT_CHECKSUM 0x00000001 + +#define JBD2_FEATURE_INCOMPAT_REVOKE 0x00000001 +#define JBD2_FEATURE_INCOMPAT_64BIT 0x00000002 +#define JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT 0x00000004 + +#define JBD2_KNOWN_COMPAT_FEATURES JBD2_FEATURE_COMPAT_CHECKSUM +#define JBD2_KNOWN_ROCOMPAT_FEATURES 0 +#define JBD2_KNOWN_INCOMPAT_FEATURES (JBD2_FEATURE_INCOMPAT_REVOKE | JBD2_FEATURE_INCOMPAT_64BIT | JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT) + +#define BJ_None 0 +#define BJ_Metadata 1 +#define BJ_Forget 2 +#define BJ_IO 3 +#define BJ_Shadow 4 +#define BJ_LogCtl 5 +#define BJ_Reserved 6 +#define BJ_Types 7 + +#endif + diff --git a/make_ext4fs.c b/make_ext4fs.c new file mode 100644 index 0000000..62a3f1a --- /dev/null +++ b/make_ext4fs.c @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2010 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 "make_ext4fs.h" +#include "ext4_utils.h" +#include "allocate.h" +#include "contents.h" +#include "uuid.h" +#include "wipe.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_MINGW + +#include + +/* These match the Linux definitions of these flags. + L_xx is defined to avoid conflicting with the win32 versions. +*/ +#define L_S_IRUSR 00400 +#define L_S_IWUSR 00200 +#define L_S_IXUSR 00100 +#define S_IRWXU (L_S_IRUSR | L_S_IWUSR | L_S_IXUSR) +#define S_IRGRP 00040 +#define S_IWGRP 00020 +#define S_IXGRP 00010 +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +#define S_IROTH 00004 +#define S_IWOTH 00002 +#define S_IXOTH 00001 +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#define S_ISUID 0004000 +#define S_ISGID 0002000 +#define S_ISVTX 0001000 + +#else + +#include +#include +#include + +#define O_BINARY 0 + +#endif + +/* TODO: Not implemented: + Allocating blocks in the same block group as the file inode + Hash or binary tree directories + Special files: sockets, devices, fifos + */ + +static int filter_dot(const struct dirent *d) +{ + return (strcmp(d->d_name, "..") && strcmp(d->d_name, ".")); +} + +static u32 build_default_directory_structure(const char *dir_path, + struct selabel_handle *sehnd) +{ + u32 inode; + u32 root_inode; + struct dentry dentries = { + .filename = "lost+found", + .file_type = EXT4_FT_DIR, + .mode = S_IRWXU, + .uid = 0, + .gid = 0, + .mtime = 0, + }; + root_inode = make_directory(0, 1, &dentries, 1); + inode = make_directory(root_inode, 0, NULL, 0); + *dentries.inode = inode; + inode_set_permissions(inode, dentries.mode, + dentries.uid, dentries.gid, dentries.mtime); + +#ifndef USE_MINGW + if (sehnd) { + char *path = NULL; + char *secontext = NULL; + + asprintf(&path, "%slost+found", dir_path); + if (selabel_lookup(sehnd, &secontext, path, S_IFDIR) < 0) { + error("cannot lookup security context for %s", path); + } else { + inode_set_selinux(inode, secontext); + freecon(secontext); + } + free(path); + } +#endif + + return root_inode; +} + +#ifndef USE_MINGW +/* Read a local directory and create the same tree in the generated filesystem. + Calls itself recursively with each directory in the given directory. + full_path is an absolute or relative path, with a trailing slash, to the + directory on disk that should be copied, or NULL if this is a directory + that does not exist on disk (e.g. lost+found). + dir_path is an absolute path, with trailing slash, to the same directory + if the image were mounted at the specified mount point */ +static u32 build_directory_structure(const char *full_path, const char *dir_path, + u32 dir_inode, fs_config_func_t fs_config_func, + struct selabel_handle *sehnd, int verbose, time_t fixed_time) +{ + int entries = 0; + struct dentry *dentries; + struct dirent **namelist = NULL; + struct stat stat; + int ret; + int i; + u32 inode; + u32 entry_inode; + u32 dirs = 0; + bool needs_lost_and_found = false; + + if (full_path) { + entries = scandir(full_path, &namelist, filter_dot, (void*)alphasort); + if (entries < 0) { +#ifdef __GLIBC__ + /* The scandir function implemented in glibc has a bug that makes it + erroneously fail with ENOMEM under certain circumstances. + As a workaround we can retry the scandir call with the same arguments. + GLIBC BZ: https://sourceware.org/bugzilla/show_bug.cgi?id=17804 */ + if (errno == ENOMEM) + entries = scandir(full_path, &namelist, filter_dot, (void*)alphasort); +#endif + if (entries < 0) { + error_errno("scandir"); + return EXT4_ALLOCATE_FAILED; + } + } + } + + if (dir_inode == 0) { + /* root directory, check if lost+found already exists */ + for (i = 0; i < entries; i++) + if (strcmp(namelist[i]->d_name, "lost+found") == 0) + break; + if (i == entries) + needs_lost_and_found = true; + } + + dentries = calloc(entries, sizeof(struct dentry)); + if (dentries == NULL) + critical_error_errno("malloc"); + + for (i = 0; i < entries; i++) { + dentries[i].filename = strdup(namelist[i]->d_name); + if (dentries[i].filename == NULL) + critical_error_errno("strdup"); + + asprintf(&dentries[i].path, "%s%s", dir_path, namelist[i]->d_name); + asprintf(&dentries[i].full_path, "%s%s", full_path, namelist[i]->d_name); + + free(namelist[i]); + + ret = lstat(dentries[i].full_path, &stat); + if (ret < 0) { + error_errno("lstat"); + i--; + entries--; + continue; + } + + dentries[i].size = stat.st_size; + dentries[i].mode = stat.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); + if (fixed_time == -1) { + dentries[i].mtime = stat.st_mtime; + } else { + dentries[i].mtime = fixed_time; + } + uint64_t capabilities; + if (fs_config_func != NULL) { +#ifdef ANDROID + unsigned int mode = 0; + unsigned int uid = 0; + unsigned int gid = 0; + int dir = S_ISDIR(stat.st_mode); + fs_config_func(dentries[i].path, dir, &uid, &gid, &mode, &capabilities); + dentries[i].mode = mode; + dentries[i].uid = uid; + dentries[i].gid = gid; + dentries[i].capabilities = capabilities; +#else + error("can't set android permissions - built without android support"); +#endif + } +#ifndef USE_MINGW + if (sehnd) { + if (selabel_lookup(sehnd, &dentries[i].secon, dentries[i].path, stat.st_mode) < 0) { + error("cannot lookup security context for %s", dentries[i].path); + } + + if (dentries[i].secon && verbose) + printf("Labeling %s as %s\n", dentries[i].path, dentries[i].secon); + } +#endif + + if (S_ISREG(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_REG_FILE; + } else if (S_ISDIR(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_DIR; + dirs++; + } else if (S_ISCHR(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_CHRDEV; + } else if (S_ISBLK(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_BLKDEV; + } else if (S_ISFIFO(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_FIFO; + } else if (S_ISSOCK(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_SOCK; + } else if (S_ISLNK(stat.st_mode)) { + dentries[i].file_type = EXT4_FT_SYMLINK; + dentries[i].link = calloc(info.block_size, 1); + readlink(dentries[i].full_path, dentries[i].link, info.block_size - 1); + } else { + error("unknown file type on %s", dentries[i].path); + i--; + entries--; + } + } + free(namelist); + + if (needs_lost_and_found) { + /* insert a lost+found directory at the beginning of the dentries */ + struct dentry *tmp = calloc(entries + 1, sizeof(struct dentry)); + memset(tmp, 0, sizeof(struct dentry)); + memcpy(tmp + 1, dentries, entries * sizeof(struct dentry)); + dentries = tmp; + + dentries[0].filename = strdup("lost+found"); + asprintf(&dentries[0].path, "%slost+found", dir_path); + dentries[0].full_path = NULL; + dentries[0].size = 0; + dentries[0].mode = S_IRWXU; + dentries[0].file_type = EXT4_FT_DIR; + dentries[0].uid = 0; + dentries[0].gid = 0; + if (sehnd) { + if (selabel_lookup(sehnd, &dentries[0].secon, dentries[0].path, dentries[0].mode) < 0) + error("cannot lookup security context for %s", dentries[0].path); + } + entries++; + dirs++; + } + + inode = make_directory(dir_inode, entries, dentries, dirs); + + for (i = 0; i < entries; i++) { + if (dentries[i].file_type == EXT4_FT_REG_FILE) { + entry_inode = make_file(dentries[i].full_path, dentries[i].size); + } else if (dentries[i].file_type == EXT4_FT_DIR) { + char *subdir_full_path = NULL; + char *subdir_dir_path; + if (dentries[i].full_path) { + ret = asprintf(&subdir_full_path, "%s/", dentries[i].full_path); + if (ret < 0) + critical_error_errno("asprintf"); + } + ret = asprintf(&subdir_dir_path, "%s/", dentries[i].path); + if (ret < 0) + critical_error_errno("asprintf"); + entry_inode = build_directory_structure(subdir_full_path, + subdir_dir_path, inode, fs_config_func, sehnd, verbose, fixed_time); + free(subdir_full_path); + free(subdir_dir_path); + } else if (dentries[i].file_type == EXT4_FT_SYMLINK) { + entry_inode = make_link(dentries[i].link); + } else { + error("unknown file type on %s", dentries[i].path); + entry_inode = 0; + } + *dentries[i].inode = entry_inode; + + ret = inode_set_permissions(entry_inode, dentries[i].mode, + dentries[i].uid, dentries[i].gid, + dentries[i].mtime); + if (ret) + error("failed to set permissions on %s\n", dentries[i].path); + + /* + * It's important to call inode_set_selinux() before + * inode_set_capabilities(). Extended attributes need to + * be stored sorted order, and we guarantee this by making + * the calls in the proper order. + * Please see xattr_assert_sane() in contents.c + */ + ret = inode_set_selinux(entry_inode, dentries[i].secon); + if (ret) + error("failed to set SELinux context on %s\n", dentries[i].path); + ret = inode_set_capabilities(entry_inode, dentries[i].capabilities); + if (ret) + error("failed to set capability on %s\n", dentries[i].path); + + free(dentries[i].path); + free(dentries[i].full_path); + free(dentries[i].link); + free((void *)dentries[i].filename); + free(dentries[i].secon); + } + + free(dentries); + return inode; +} +#endif + +static u32 compute_block_size() +{ + return 4096; +} + +static u32 compute_journal_blocks() +{ + u32 journal_blocks = DIV_ROUND_UP(info.len, info.block_size) / 64; + if (journal_blocks < 1024) + journal_blocks = 1024; + if (journal_blocks > 32768) + journal_blocks = 32768; + return journal_blocks; +} + +static u32 compute_blocks_per_group() +{ + return info.block_size * 8; +} + +static u32 compute_inodes() +{ + return DIV_ROUND_UP(info.len, info.block_size) / 4; +} + +static u32 compute_inodes_per_group() +{ + u32 blocks = DIV_ROUND_UP(info.len, info.block_size); + u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group); + u32 inodes = DIV_ROUND_UP(info.inodes, block_groups); + inodes = EXT4_ALIGN(inodes, (info.block_size / info.inode_size)); + + /* After properly rounding up the number of inodes/group, + * make sure to update the total inodes field in the info struct. + */ + info.inodes = inodes * block_groups; + + return inodes; +} + +static u32 compute_bg_desc_reserve_blocks() +{ + u32 blocks = DIV_ROUND_UP(info.len, info.block_size); + u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group); + u32 bg_desc_blocks = DIV_ROUND_UP(block_groups * sizeof(struct ext2_group_desc), + info.block_size); + + u32 bg_desc_reserve_blocks = + DIV_ROUND_UP(block_groups * 1024 * sizeof(struct ext2_group_desc), + info.block_size) - bg_desc_blocks; + + if (bg_desc_reserve_blocks > info.block_size / sizeof(u32)) + bg_desc_reserve_blocks = info.block_size / sizeof(u32); + + return bg_desc_reserve_blocks; +} + +void reset_ext4fs_info() { + // Reset all the global data structures used by make_ext4fs so it + // can be called again. + memset(&info, 0, sizeof(info)); + memset(&aux_info, 0, sizeof(aux_info)); + + if (ext4_sparse_file) { + sparse_file_destroy(ext4_sparse_file); + ext4_sparse_file = NULL; + } +} + +int make_ext4fs_sparse_fd(int fd, long long len, + const char *mountpoint, struct selabel_handle *sehnd) +{ + reset_ext4fs_info(); + info.len = len; + + return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, sehnd, 0, -1, NULL); +} + +int make_ext4fs(const char *filename, long long len, + const char *mountpoint, struct selabel_handle *sehnd) +{ + int fd; + int status; + + reset_ext4fs_info(); + info.len = len; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) { + error_errno("open"); + return EXIT_FAILURE; + } + + status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, sehnd, 0, -1, NULL); + close(fd); + + return status; +} + +/* return a newly-malloc'd string that is a copy of str. The new string + is guaranteed to have a trailing slash. If absolute is true, the new string + is also guaranteed to have a leading slash. +*/ +static char *canonicalize_slashes(const char *str, bool absolute) +{ + char *ret; + int len = strlen(str); + int newlen = len; + char *ptr; + + if (len == 0) { + if (absolute) + return strdup("/"); + else + return strdup(""); + } + + if (str[0] != '/' && absolute) { + newlen++; + } + if (str[len - 1] != '/') { + newlen++; + } + ret = malloc(newlen + 1); + if (!ret) { + critical_error("malloc"); + } + + ptr = ret; + if (str[0] != '/' && absolute) { + *ptr++ = '/'; + } + + strcpy(ptr, str); + ptr += len; + + if (str[len - 1] != '/') { + *ptr++ = '/'; + } + + if (ptr != ret + newlen) { + critical_error("assertion failed\n"); + } + + *ptr = '\0'; + + return ret; +} + +static char *canonicalize_abs_slashes(const char *str) +{ + return canonicalize_slashes(str, true); +} + +static char *canonicalize_rel_slashes(const char *str) +{ + return canonicalize_slashes(str, false); +} + +int make_ext4fs_internal(int fd, const char *_directory, + const char *_mountpoint, fs_config_func_t fs_config_func, int gzip, + int sparse, int crc, int wipe, + struct selabel_handle *sehnd, int verbose, time_t fixed_time, + FILE* block_list_file) +{ + u32 root_inode_num; + u16 root_mode; + char *mountpoint; + char *directory = NULL; + + if (setjmp(setjmp_env)) + return EXIT_FAILURE; /* Handle a call to longjmp() */ + + if (_mountpoint == NULL) { + mountpoint = strdup(""); + } else { + mountpoint = canonicalize_abs_slashes(_mountpoint); + } + + if (_directory) { + directory = canonicalize_rel_slashes(_directory); + } + + if (info.len <= 0) + info.len = get_file_size(fd); + + if (info.len <= 0) { + fprintf(stderr, "Need size of filesystem\n"); + return EXIT_FAILURE; + } + + if (info.block_size <= 0) + info.block_size = compute_block_size(); + + /* Round down the filesystem length to be a multiple of the block size */ + info.len &= ~((u64)info.block_size - 1); + + if (info.journal_blocks == 0) + info.journal_blocks = compute_journal_blocks(); + + if (info.no_journal == 0) + info.feat_compat = EXT4_FEATURE_COMPAT_HAS_JOURNAL; + else + info.journal_blocks = 0; + + if (info.blocks_per_group <= 0) + info.blocks_per_group = compute_blocks_per_group(); + + if (info.inodes <= 0) + info.inodes = compute_inodes(); + + if (info.inode_size <= 0) + info.inode_size = 256; + + if (info.label == NULL) + info.label = ""; + + info.inodes_per_group = compute_inodes_per_group(); + + info.feat_compat |= + EXT4_FEATURE_COMPAT_RESIZE_INODE | + EXT4_FEATURE_COMPAT_EXT_ATTR; + + info.feat_ro_compat |= + EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER | + EXT4_FEATURE_RO_COMPAT_LARGE_FILE | + EXT4_FEATURE_RO_COMPAT_GDT_CSUM; + + info.feat_incompat |= + EXT4_FEATURE_INCOMPAT_EXTENTS | + EXT4_FEATURE_INCOMPAT_FILETYPE; + + + info.bg_desc_reserve_blocks = compute_bg_desc_reserve_blocks(); + + printf("Creating filesystem with parameters:\n"); + printf(" Size: %"PRIu64"\n", info.len); + printf(" Block size: %d\n", info.block_size); + printf(" Blocks per group: %d\n", info.blocks_per_group); + printf(" Inodes per group: %d\n", info.inodes_per_group); + printf(" Inode size: %d\n", info.inode_size); + printf(" Journal blocks: %d\n", info.journal_blocks); + printf(" Label: %s\n", info.label); + + ext4_create_fs_aux_info(); + + printf(" Blocks: %"PRIu64"\n", aux_info.len_blocks); + printf(" Block groups: %d\n", aux_info.groups); + printf(" Reserved block group size: %d\n", info.bg_desc_reserve_blocks); + + ext4_sparse_file = sparse_file_new(info.block_size, info.len); + + block_allocator_init(); + + ext4_fill_in_sb(); + + if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED) + error("failed to reserve first 10 inodes"); + + if (info.feat_compat & EXT4_FEATURE_COMPAT_HAS_JOURNAL) + ext4_create_journal_inode(); + + if (info.feat_compat & EXT4_FEATURE_COMPAT_RESIZE_INODE) + ext4_create_resize_inode(); + +#ifdef USE_MINGW + // Windows needs only 'create an empty fs image' functionality + assert(!directory); + root_inode_num = build_default_directory_structure(mountpoint, sehnd); +#else + if (directory) + root_inode_num = build_directory_structure(directory, mountpoint, 0, + fs_config_func, sehnd, verbose, fixed_time); + else + root_inode_num = build_default_directory_structure(mountpoint, sehnd); +#endif + + root_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + inode_set_permissions(root_inode_num, root_mode, 0, 0, 0); + +#ifndef USE_MINGW + if (sehnd) { + char *secontext = NULL; + + if (selabel_lookup(sehnd, &secontext, mountpoint, S_IFDIR) < 0) { + error("cannot lookup security context for %s", mountpoint); + } + if (secontext) { + if (verbose) { + printf("Labeling %s as %s\n", mountpoint, secontext); + } + inode_set_selinux(root_inode_num, secontext); + } + freecon(secontext); + } +#endif + + ext4_update_free(); + + ext4_queue_sb(); + + if (block_list_file) { + size_t dirlen = directory ? strlen(directory) : 0; + struct block_allocation* p = get_saved_allocation_chain(); + while (p) { + if (directory && strncmp(p->filename, directory, dirlen) == 0) { + // substitute mountpoint for the leading directory in the filename, in the output file + fprintf(block_list_file, "%s%s", mountpoint, p->filename + dirlen); + } else { + fprintf(block_list_file, "%s", p->filename); + } + print_blocks(block_list_file, p); + struct block_allocation* pn = p->next; + free_alloc(p); + p = pn; + } + } + + printf("Created filesystem with %d/%d inodes and %d/%d blocks\n", + aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count, + aux_info.sb->s_inodes_count, + aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo, + aux_info.sb->s_blocks_count_lo); + + if (wipe && WIPE_IS_SUPPORTED) { + wipe_block_device(fd, info.len); + } + + write_ext4_image(fd, gzip, sparse, crc); + + sparse_file_destroy(ext4_sparse_file); + ext4_sparse_file = NULL; + + free(mountpoint); + free(directory); + + return 0; +} diff --git a/make_ext4fs.h b/make_ext4fs.h new file mode 100644 index 0000000..3784a9e --- /dev/null +++ b/make_ext4fs.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _MAKE_EXT4FS_H_ +#define _MAKE_EXT4FS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct selabel_handle; + +int make_ext4fs(const char *filename, long long len, + const char *mountpoint, struct selabel_handle *sehnd); +int make_ext4fs_sparse_fd(int fd, long long len, + const char *mountpoint, struct selabel_handle *sehnd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/make_ext4fs_main.c b/make_ext4fs_main.c new file mode 100644 index 0000000..a6c5f61 --- /dev/null +++ b/make_ext4fs_main.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2010 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 +#include +#include +#include + +#if defined(__linux__) +#include +#elif defined(__APPLE__) && defined(__MACH__) +#include +#endif + +#ifdef ANDROID +#include +#endif + +#ifndef USE_MINGW +#include +#include +#include +#else +struct selabel_handle; +#endif + +#include "make_ext4fs.h" +#include "ext4_utils.h" +#include "canned_fs_config.h" + +#ifndef USE_MINGW /* O_BINARY is windows-specific flag */ +#define O_BINARY 0 +#endif + +extern struct fs_info info; + + +static void usage(char *path) +{ + fprintf(stderr, "%s [ -l ] [ -j ] [ -b ]\n", basename(path)); + fprintf(stderr, " [ -g ] [ -i ] [ -I ]\n"); + fprintf(stderr, " [ -L