From 464df1d5e5ab1322e2dd0a7796939fff1aeefa9a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:49:25 +0200 Subject: Adding upstream version 1.47.0. Signed-off-by: Daniel Baumann --- contrib/Android.bp | 28 + contrib/add_ext4_encrypt.c | 64 + contrib/android/Android.bp | 78 ++ contrib/android/base_fs.c | 211 +++ contrib/android/base_fs.h | 17 + contrib/android/basefs_allocator.c | 388 ++++++ contrib/android/basefs_allocator.h | 16 + contrib/android/block_list.c | 89 ++ contrib/android/block_list.h | 8 + contrib/android/block_range.c | 80 ++ contrib/android/block_range.h | 29 + contrib/android/e2fsdroid.c | 377 +++++ contrib/android/ext2simg.c | 224 +++ contrib/android/fsmap.c | 169 +++ contrib/android/fsmap.h | 29 + contrib/android/perms.c | 376 +++++ contrib/android/perms.h | 65 + contrib/build-rpm | 45 + contrib/dconf | 118 ++ contrib/dir2fs | 66 + contrib/e2croncheck | 47 + contrib/ext4-ioc.c | 98 ++ contrib/fallocate.c | 190 +++ contrib/fsstress.c | 2708 ++++++++++++++++++++++++++++++++++++ contrib/make-sparse.c | 91 ++ contrib/populate-extfs.sh | 105 ++ contrib/python-uuid/setup.py | 11 + contrib/python-uuid/test.py | 18 + contrib/python-uuid/uuid.c | 23 + contrib/setup-schroot | 46 + contrib/spd_readdir.c | 458 ++++++ 31 files changed, 6272 insertions(+) create mode 100644 contrib/Android.bp create mode 100644 contrib/add_ext4_encrypt.c create mode 100644 contrib/android/Android.bp create mode 100644 contrib/android/base_fs.c create mode 100644 contrib/android/base_fs.h create mode 100644 contrib/android/basefs_allocator.c create mode 100644 contrib/android/basefs_allocator.h create mode 100644 contrib/android/block_list.c create mode 100644 contrib/android/block_list.h create mode 100644 contrib/android/block_range.c create mode 100644 contrib/android/block_range.h create mode 100644 contrib/android/e2fsdroid.c create mode 100644 contrib/android/ext2simg.c create mode 100644 contrib/android/fsmap.c create mode 100644 contrib/android/fsmap.h create mode 100644 contrib/android/perms.c create mode 100644 contrib/android/perms.h create mode 100644 contrib/build-rpm create mode 100644 contrib/dconf create mode 100755 contrib/dir2fs create mode 100755 contrib/e2croncheck create mode 100644 contrib/ext4-ioc.c create mode 100644 contrib/fallocate.c create mode 100644 contrib/fsstress.c create mode 100644 contrib/make-sparse.c create mode 100755 contrib/populate-extfs.sh create mode 100755 contrib/python-uuid/setup.py create mode 100755 contrib/python-uuid/test.py create mode 100644 contrib/python-uuid/uuid.c create mode 100755 contrib/setup-schroot create mode 100644 contrib/spd_readdir.c (limited to 'contrib') diff --git a/contrib/Android.bp b/contrib/Android.bp new file mode 100644 index 0000000..7f9cfe9 --- /dev/null +++ b/contrib/Android.bp @@ -0,0 +1,28 @@ +// Copyright 2017 The Android Open Source Project + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_e2fsprogs_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + // SPDX-license-identifier-GPL + default_applicable_licenses: ["external_e2fsprogs_license"], +} + +subdirs = ["android"] + +//######################################################################## +// Build add_ext4_encrypt + +cc_binary { + name: "add_ext4_encrypt", + host_supported: true, + defaults: ["e2fsprogs-defaults"], + + srcs: ["add_ext4_encrypt.c"], + shared_libs: [ + "libext2fs", + "libext2_com_err", + ], +} diff --git a/contrib/add_ext4_encrypt.c b/contrib/add_ext4_encrypt.c new file mode 100644 index 0000000..133fe25 --- /dev/null +++ b/contrib/add_ext4_encrypt.c @@ -0,0 +1,64 @@ +/* + * Basic program to add ext4 encryption to a file system + * + * Copyright 2015, Google, Inc. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +int main (int argc, char *argv[]) +{ + errcode_t retval = 0; + ext2_filsys fs; + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + initialize_ext2_error_table(); + + if (argc != 2) { + fprintf(stderr, "%s: Usage \n", argv[0]); + exit(1); + } + + retval = ext2fs_open(argv[1], EXT2_FLAG_RW, 0, 0, + unix_io_manager, &fs); + + if (retval) { + com_err(argv[0], retval, "while trying to open '%s'", + argv[1]); + exit(1); + } + if (!ext2fs_has_feature_encrypt(fs->super)) { + ext2fs_set_feature_encrypt(fs->super); + fs->super->s_encrypt_algos[0] = + EXT4_ENCRYPTION_MODE_AES_256_XTS; + fs->super->s_encrypt_algos[1] = + EXT4_ENCRYPTION_MODE_AES_256_CTS; + ext2fs_mark_super_dirty(fs); + printf("Ext4 encryption enabled on %s\n", argv[1]); + } else + printf("Ext4 encryption already enabled on %s\n", argv[1]); + + retval = ext2fs_close(fs); + if (retval) { + com_err(argv[0], retval, "while trying to close '%s'", + argv[1]); + exit(1); + } + return (0); +} + diff --git a/contrib/android/Android.bp b/contrib/android/Android.bp new file mode 100644 index 0000000..6c9dd5c --- /dev/null +++ b/contrib/android/Android.bp @@ -0,0 +1,78 @@ +// Copyright 2017 The Android Open Source Project + +//########################################################################## +// Build e2fsdroid + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_e2fsprogs_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["external_e2fsprogs_license"], +} + +cc_binary { + name: "e2fsdroid", + host_supported: true, + recovery_available: true, + defaults: ["e2fsprogs-defaults"], + + srcs: [ + "e2fsdroid.c", + "block_range.c", + "fsmap.c", + "block_list.c", + "base_fs.c", + "perms.c", + "basefs_allocator.c", + ], + target: { + host: { + static_libs: [ + "libext2_com_err", + "libext2_misc", + "libext2fs", + "libsparse", + "libz", + "libcutils", + "libbase", + "libselinux", + "libcrypto", + "liblog", + ], + }, + android: { + static_libs: [ + "libbase", + ], + shared_libs: [ + "libext2fs", + "libext2_com_err", + "libext2_misc", + "libcutils", + "liblog", + "libselinux", + "libcrypto", + ], + }, + }, + stl: "libc++_static", +} + +//########################################################################## +// Build ext2simg + +cc_binary { + name: "ext2simg", + host_supported: true, + defaults: ["e2fsprogs-defaults"], + + srcs: ["ext2simg.c"], + shared_libs: [ + "libext2fs", + "libext2_com_err", + "libsparse", + "libz", + ], +} diff --git a/contrib/android/base_fs.c b/contrib/android/base_fs.c new file mode 100644 index 0000000..d3e00d1 --- /dev/null +++ b/contrib/android/base_fs.c @@ -0,0 +1,211 @@ +#include "base_fs.h" +#include + +#define BASE_FS_VERSION "Base EXT4 version 1.0" + +struct base_fs { + FILE *file; + const char *mountpoint; + struct basefs_entry entry; +}; + +static FILE *basefs_open(const char *file) +{ + char *line = NULL; + size_t len; + FILE *f = fopen(file, "r"); + if (!f) + return NULL; + + if (getline(&line, &len, f) == -1 || !line) + goto err_getline; + + if (strncmp(line, BASE_FS_VERSION, strlen(BASE_FS_VERSION))) + goto err_header; + + free(line); + return f; + +err_header: + free(line); +err_getline: + fclose(f); + return NULL; +} + +static struct basefs_entry *basefs_readline(FILE *f, const char *mountpoint, + int *err) +{ + char *line = NULL, *saveptr1, *saveptr2, *block_range, *block; + int offset; + size_t len; + struct basefs_entry *entry = NULL; + blk64_t range_start, range_end; + + if (getline(&line, &len, f) == -1) { + if (feof(f)) + goto end; + goto err_getline; + } + + entry = calloc(1, sizeof(*entry)); + if (!entry) + goto err_alloc; + + /* + * With BASEFS version 1.0, a typical line looks like this: + * /bin/mke2fs 5000-5004,8000,9000-9990 + */ + if (sscanf(line, "%ms%n", &entry->path, &offset) != 1) + goto err_sscanf; + len = strlen(mountpoint); + memmove(entry->path, entry->path + len, strlen(entry->path) - len + 1); + + while (line[offset] == ' ') + ++offset; + + block_range = strtok_r(line + offset, ",\n", &saveptr1); + while (block_range) { + block = strtok_r(block_range, "-", &saveptr2); + if (!block) + break; + range_start = atoll(block); + block = strtok_r(NULL, "-", &saveptr2); + range_end = block ? atoll(block) : range_start; + add_blocks_to_range(&entry->blocks, range_start, range_end); + block_range = strtok_r(NULL, ",\n", &saveptr1); + } +end: + *err = 0; + free(line); + return entry; + +err_sscanf: + free(entry); +err_alloc: + free(line); +err_getline: + *err = 1; + return NULL; +} + +static void free_base_fs_entry(void *e) +{ + struct basefs_entry *entry = e; + if (entry) { + free(entry->path); + free(entry); + } +} + +struct ext2fs_hashmap *basefs_parse(const char *file, const char *mountpoint) +{ + int err; + struct ext2fs_hashmap *entries = NULL; + struct basefs_entry *entry; + FILE *f = basefs_open(file); + if (!f) + return NULL; + entries = ext2fs_hashmap_create(ext2fs_djb2_hash, free_base_fs_entry, 1024); + if (!entries) + goto end; + + while ((entry = basefs_readline(f, mountpoint, &err))) { + err = ext2fs_hashmap_add(entries, entry, entry->path, + strlen(entry->path)); + if (err) { + free_base_fs_entry(entry); + fclose(f); + ext2fs_hashmap_free(entries); + return NULL; + } + } + if (err) { + fclose(f); + ext2fs_hashmap_free(entries); + return NULL; + } +end: + fclose(f); + return entries; +} + +static void *init(const char *file, const char *mountpoint) +{ + struct base_fs *params = malloc(sizeof(*params)); + + if (!params) + return NULL; + params->mountpoint = mountpoint; + params->file = fopen(file, "w+"); + if (!params->file) { + free(params); + return NULL; + } + if (fwrite(BASE_FS_VERSION"\n", 1, strlen(BASE_FS_VERSION"\n"), + params->file) != strlen(BASE_FS_VERSION"\n")) { + fclose(params->file); + free(params); + return NULL; + } + return params; +} + +static int start_new_file(char *path, ext2_ino_t ino EXT2FS_ATTR((unused)), + struct ext2_inode *inode, void *data) +{ + struct base_fs *params = data; + + params->entry.path = LINUX_S_ISREG(inode->i_mode) ? path : NULL; + return 0; +} + +static int add_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t blocknr, + int metadata, void *data) +{ + struct base_fs *params = data; + + if (params->entry.path && !metadata) + add_blocks_to_range(¶ms->entry.blocks, blocknr, blocknr); + return 0; +} + +static int inline_data(void *inline_data EXT2FS_ATTR((unused)), + void *data EXT2FS_ATTR((unused))) +{ + return 0; +} + +static int end_new_file(void *data) +{ + struct base_fs *params = data; + + if (!params->entry.path) + return 0; + if (fprintf(params->file, "%s%s ", params->mountpoint, + params->entry.path) < 0 + || write_block_ranges(params->file, params->entry.blocks.head, ",") + || fwrite("\n", 1, 1, params->file) != 1) + return -1; + + delete_block_ranges(¶ms->entry.blocks); + return 0; +} + +static int cleanup(void *data) +{ + struct base_fs *params = data; + + fclose(params->file); + free(params); + return 0; +} + +struct fsmap_format base_fs_format = { + .init = init, + .start_new_file = start_new_file, + .add_block = add_block, + .inline_data = inline_data, + .end_new_file = end_new_file, + .cleanup = cleanup, +}; diff --git a/contrib/android/base_fs.h b/contrib/android/base_fs.h new file mode 100644 index 0000000..f53f1ed --- /dev/null +++ b/contrib/android/base_fs.h @@ -0,0 +1,17 @@ +#ifndef BASE_FS_H +# define BASE_FS_H + +# include "fsmap.h" +# include "hashmap.h" +# include "block_range.h" + +struct basefs_entry { + char *path; + struct block_range_list blocks; +}; + +extern struct fsmap_format base_fs_format; + +struct ext2fs_hashmap *basefs_parse(const char *file, const char *mountpoint); + +#endif /* !BASE_FS_H */ diff --git a/contrib/android/basefs_allocator.c b/contrib/android/basefs_allocator.c new file mode 100644 index 0000000..4f9f5c1 --- /dev/null +++ b/contrib/android/basefs_allocator.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include "basefs_allocator.h" +#include "block_range.h" +#include "hashmap.h" +#include "base_fs.h" + +struct base_fs_allocator { + struct ext2fs_hashmap *entries; + struct basefs_entry *cur_entry; + /* The next expected logical block to allocate for cur_entry. */ + blk64_t next_lblk; + /* Blocks which are definitely owned by a single inode in BaseFS. */ + ext2fs_block_bitmap exclusive_block_map; + /* Blocks which are available to the first inode that requests it. */ + ext2fs_block_bitmap dedup_block_map; +}; + +static errcode_t basefs_block_allocator(ext2_filsys, blk64_t, blk64_t *, + struct blk_alloc_ctx *ctx); + +/* + * Free any reserved, but unconsumed block ranges in the allocator. This both + * frees the block_range_list data structure and unreserves exclusive blocks + * from the block map. + */ +static void fs_free_blocks_range(ext2_filsys fs, + struct base_fs_allocator *allocator, + struct block_range_list *list) +{ + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + + blk64_t block; + while (list->head) { + block = consume_next_block(list); + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(fs->block_map, block); + ext2fs_unmark_block_bitmap2(exclusive_map, block); + } + } +} + +/* + * Free any blocks in the bitmap that were reserved but never used. This is + * needed to free dedup_block_map and ensure the free block bitmap is + * internally consistent. + */ +static void fs_free_blocks_bitmap(ext2_filsys fs, ext2fs_block_bitmap bitmap) +{ + blk64_t block = 0; + blk64_t start = fs->super->s_first_data_block; + blk64_t end = ext2fs_blocks_count(fs->super) - 1; + errcode_t retval; + + for (;;) { + retval = ext2fs_find_first_set_block_bitmap2(bitmap, start, end, + &block); + if (retval) + break; + ext2fs_unmark_block_bitmap2(fs->block_map, block); + start = block + 1; + } +} + +static void basefs_allocator_free(ext2_filsys fs, + struct base_fs_allocator *allocator) +{ + struct basefs_entry *e; + struct ext2fs_hashmap_entry *it = NULL; + struct ext2fs_hashmap *entries = allocator->entries; + + if (entries) { + while ((e = ext2fs_hashmap_iter_in_order(entries, &it))) { + fs_free_blocks_range(fs, allocator, &e->blocks); + delete_block_ranges(&e->blocks); + } + ext2fs_hashmap_free(entries); + } + fs_free_blocks_bitmap(fs, allocator->dedup_block_map); + ext2fs_free_block_bitmap(allocator->exclusive_block_map); + ext2fs_free_block_bitmap(allocator->dedup_block_map); + free(allocator); +} + +/* + * Build a bitmap of which blocks are definitely owned by exactly one file in + * Base FS. Blocks which are not valid or are de-duplicated are skipped. This + * is called during allocator initialization, to ensure that libext2fs does + * not allocate which we want to re-use. + * + * If a block was allocated in the initial filesystem, it can never be re-used, + * so it will appear in neither the exclusive or dedup set. If a block is used + * by multiple files, it will be removed from the owned set and instead added + * to the dedup set. + * + * The dedup set is not removed from fs->block_map. This allows us to re-use + * dedup blocks separately and not have them be allocated outside of file data. + * + * This function returns non-zero if the block was owned, and 0 otherwise. + */ +static int fs_reserve_block(ext2_filsys fs, + struct base_fs_allocator *allocator, + blk64_t block) +{ + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (block >= ext2fs_blocks_count(fs->super)) + return 0; + + if (ext2fs_test_block_bitmap2(fs->block_map, block)) { + if (!ext2fs_test_block_bitmap2(exclusive_map, block)) + return 0; + ext2fs_unmark_block_bitmap2(exclusive_map, block); + ext2fs_mark_block_bitmap2(dedup_map, block); + return 0; + } else { + ext2fs_mark_block_bitmap2(fs->block_map, block); + ext2fs_mark_block_bitmap2(exclusive_map, block); + return 1; + } +} + +/* + * Walk the requested block list and reserve blocks, either into the owned + * pool or the dedup pool as appropriate. We stop once the file has enough + * owned blocks to satisfy |file_size|. This allows any extra blocks to be + * re-used, since otherwise a large block movement between files could + * trigger block allocation errors. + */ +static void fs_reserve_blocks_range(ext2_filsys fs, + struct base_fs_allocator *allocator, + struct block_range_list *list, + off_t file_size) +{ + blk64_t block; + off_t blocks_needed; + off_t blocks_acquired = 0; + struct block_range *blocks = list->head; + + blocks_needed = file_size + (fs->blocksize - 1); + blocks_needed /= fs->blocksize; + + while (blocks) { + for (block = blocks->start; block <= blocks->end; block++) { + if (fs_reserve_block(fs, allocator, block)) + blocks_acquired++; + if (blocks_acquired >= blocks_needed) + return; + } + blocks = blocks->next; + } +} + +/* + * For each file in the base FS map, ensure that its blocks are reserved in + * the actual block map. This prevents libext2fs from allocating them for + * general purpose use, and ensures that if the file needs data blocks, they + * can be re-acquired exclusively for that file. + * + * If a file in the base map is missing, or not a regular file in the new + * filesystem, then it's skipped to ensure that its blocks are reusable. + */ +static errcode_t fs_reserve_blocks(ext2_filsys fs, + struct base_fs_allocator *allocator, + const char *src_dir) +{ + int nbytes; + char full_path[PATH_MAX]; + const char *sep = "/"; + struct stat st; + struct basefs_entry *e; + struct ext2fs_hashmap_entry *it = NULL; + struct ext2fs_hashmap *entries = allocator->entries; + + if (strlen(src_dir) && src_dir[strlen(src_dir) - 1] == '/') + sep = ""; + + while ((e = ext2fs_hashmap_iter_in_order(entries, &it))) { + nbytes = snprintf(full_path, sizeof(full_path), "%s%s%s", + src_dir, sep, e->path); + if (nbytes >= sizeof(full_path)) + return ENAMETOOLONG; + if (lstat(full_path, &st) || !S_ISREG(st.st_mode)) + continue; + fs_reserve_blocks_range(fs, allocator, &e->blocks, st.st_size); + } + return 0; +} + +errcode_t base_fs_alloc_load(ext2_filsys fs, const char *file, + const char *mountpoint, const char *src_dir) +{ + errcode_t retval = 0; + struct base_fs_allocator *allocator; + + allocator = calloc(1, sizeof(*allocator)); + if (!allocator) { + retval = ENOMEM; + goto out; + } + + retval = ext2fs_read_bitmaps(fs); + if (retval) + goto err_load; + + allocator->cur_entry = NULL; + allocator->entries = basefs_parse(file, mountpoint); + if (!allocator->entries) { + retval = EIO; + goto err_load; + } + retval = ext2fs_allocate_block_bitmap(fs, "exclusive map", + &allocator->exclusive_block_map); + if (retval) + goto err_load; + retval = ext2fs_allocate_block_bitmap(fs, "dedup map", + &allocator->dedup_block_map); + if (retval) + goto err_load; + + retval = fs_reserve_blocks(fs, allocator, src_dir); + if (retval) + goto err_load; + + /* Override the default allocator */ + fs->get_alloc_block2 = basefs_block_allocator; + fs->priv_data = allocator; + + goto out; + +err_load: + basefs_allocator_free(fs, allocator); +out: + return retval; +} + +/* Try and acquire the next usable block from the Base FS map. */ +static errcode_t get_next_block(ext2_filsys fs, struct base_fs_allocator *allocator, + struct block_range_list* list, blk64_t *ret) +{ + blk64_t block; + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (!list->head) + return EXT2_ET_BLOCK_ALLOC_FAIL; + + block = consume_next_block(list); + if (block >= ext2fs_blocks_count(fs->super)) + return EXT2_ET_BLOCK_ALLOC_FAIL; + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(exclusive_map, block); + *ret = block; + return 0; + } + if (ext2fs_test_block_bitmap2(dedup_map, block)) { + ext2fs_unmark_block_bitmap2(dedup_map, block); + *ret = block; + return 0; + } + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +/* + * BaseFS lists blocks in logical block order. However, the allocator hook is + * only called if a block needs to be allocated. In the case of a deduplicated + * block, or a hole, the hook is not invoked. This means the next block + * allocation request will be out of sequence. For example, consider if BaseFS + * specifies the following (0 being a hole): + * 1 2 3 0 4 5 + * + * If the new file has a hole at logical block 0, we could accidentally + * shift the entire expected block list as follows: + * 0 1 2 0 3 4 + * + * To account for this, we track the next expected logical block in the + * allocator. If the current request is for a later logical block, we skip and + * free the intermediate physical blocks that would have been allocated. This + * ensures the original block assignment is respected. + */ +static void skip_blocks(ext2_filsys fs, struct base_fs_allocator *allocator, + struct blk_alloc_ctx *ctx) +{ + blk64_t block; + struct block_range_list *list = &allocator->cur_entry->blocks; + ext2fs_block_bitmap exclusive_map = allocator->exclusive_block_map; + + while (list->head && allocator->next_lblk < ctx->lblk) { + block = consume_next_block(list); + if (block >= ext2fs_blocks_count(fs->super)) + continue; + if (ext2fs_test_block_bitmap2(exclusive_map, block)) { + ext2fs_unmark_block_bitmap2(exclusive_map, block); + ext2fs_unmark_block_bitmap2(fs->block_map, block); + } + allocator->next_lblk++; + } +} + +static errcode_t basefs_block_allocator(ext2_filsys fs, blk64_t goal, + blk64_t *ret, struct blk_alloc_ctx *ctx) +{ + errcode_t retval; + struct base_fs_allocator *allocator = fs->priv_data; + struct basefs_entry *e = allocator->cur_entry; + ext2fs_block_bitmap dedup_map = allocator->dedup_block_map; + + if (e && ctx && (ctx->flags & BLOCK_ALLOC_DATA)) { + if (allocator->next_lblk < ctx->lblk) + skip_blocks(fs, allocator, ctx); + allocator->next_lblk = ctx->lblk + 1; + + if (!get_next_block(fs, allocator, &e->blocks, ret)) + return 0; + } + + retval = ext2fs_new_block2(fs, goal, fs->block_map, ret); + if (!retval) { + ext2fs_mark_block_bitmap2(fs->block_map, *ret); + return 0; + } + if (retval != EXT2_ET_BLOCK_ALLOC_FAIL) + return retval; + + /* Try to steal a block from the dedup pool. */ + retval = ext2fs_find_first_set_block_bitmap2(dedup_map, + fs->super->s_first_data_block, + ext2fs_blocks_count(fs->super) - 1, ret); + if (!retval) { + ext2fs_unmark_block_bitmap2(dedup_map, *ret); + return 0; + } + + /* + * As a last resort, take any block from our file's list. This + * risks bloating the diff, but means we are more likely to + * successfully build an image. + */ + while (e->blocks.head) { + if (!get_next_block(fs, allocator, &e->blocks, ret)) + return 0; + } + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +void base_fs_alloc_cleanup(ext2_filsys fs) +{ + basefs_allocator_free(fs, fs->priv_data); + fs->priv_data = NULL; + fs->get_alloc_block2 = NULL; +} + +errcode_t base_fs_alloc_set_target(ext2_filsys fs, const char *target_path, + const char *name EXT2FS_ATTR((unused)), + ext2_ino_t parent_ino EXT2FS_ATTR((unused)), + ext2_ino_t root EXT2FS_ATTR((unused)), mode_t mode) +{ + struct base_fs_allocator *allocator = fs->priv_data; + + if (mode != S_IFREG) + return 0; + + if (allocator) { + allocator->cur_entry = ext2fs_hashmap_lookup(allocator->entries, + target_path, + strlen(target_path)); + allocator->next_lblk = 0; + } + return 0; +} + +errcode_t base_fs_alloc_unset_target(ext2_filsys fs, + const char *target_path EXT2FS_ATTR((unused)), + const char *name EXT2FS_ATTR((unused)), + ext2_ino_t parent_ino EXT2FS_ATTR((unused)), + ext2_ino_t root EXT2FS_ATTR((unused)), mode_t mode) +{ + struct base_fs_allocator *allocator = fs->priv_data; + + if (!allocator || !allocator->cur_entry || mode != S_IFREG) + return 0; + + fs_free_blocks_range(fs, allocator, &allocator->cur_entry->blocks); + delete_block_ranges(&allocator->cur_entry->blocks); + return 0; +} diff --git a/contrib/android/basefs_allocator.h b/contrib/android/basefs_allocator.h new file mode 100644 index 0000000..6d1c65e --- /dev/null +++ b/contrib/android/basefs_allocator.h @@ -0,0 +1,16 @@ +#ifndef BASE_FS_ALLOCATOR_H +# define BASE_FS_ALLOCATOR_H + +# include +# include + +errcode_t base_fs_alloc_load(ext2_filsys fs, const char *file, + const char *mountpoint, const char *src_dir); +void base_fs_alloc_cleanup(ext2_filsys fs); + +errcode_t base_fs_alloc_set_target(ext2_filsys fs, const char *target_path, + const char *name, ext2_ino_t parent_ino, ext2_ino_t root, mode_t mode); +errcode_t base_fs_alloc_unset_target(ext2_filsys fs, const char *target_path, + const char *name, ext2_ino_t parent_ino, ext2_ino_t root, mode_t mode); + +#endif /* !BASE_FS_ALLOCATOR_H */ diff --git a/contrib/android/block_list.c b/contrib/android/block_list.c new file mode 100644 index 0000000..63cc1a2 --- /dev/null +++ b/contrib/android/block_list.c @@ -0,0 +1,89 @@ +#include "block_list.h" +#include "block_range.h" +#include +#include +#include +#include + +struct block_list { + FILE *f; + const char *mountpoint; + + const char *filename; + struct block_range_list blocks; +}; + +static void *init(const char *file, const char *mountpoint) +{ + struct block_list *params = calloc(1, sizeof(*params)); + + if (!params) + return NULL; + params->mountpoint = mountpoint; + params->f = fopen(file, "w+"); + if (!params->f) { + free(params); + return NULL; + } + return params; +} + +static int start_new_file(char *path, ext2_ino_t ino EXT2FS_ATTR((unused)), + struct ext2_inode *inode EXT2FS_ATTR((unused)), + void *data) +{ + struct block_list *params = data; + + params->filename = LINUX_S_ISREG(inode->i_mode) ? path : NULL; + return 0; +} + +static int add_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t blocknr, + int metadata, void *data) +{ + struct block_list *params = data; + + if (params->filename && !metadata) + add_blocks_to_range(¶ms->blocks, blocknr, blocknr); + return 0; +} + +static int inline_data(void *inline_data EXT2FS_ATTR((unused)), + void *data EXT2FS_ATTR((unused))) +{ + return 0; +} + +static int end_new_file(void *data) +{ + struct block_list *params = data; + + if (!params->filename || !params->blocks.head) + return 0; + if (fprintf(params->f, "%s%s ", params->mountpoint, + params->filename) < 0 + || write_block_ranges(params->f, params->blocks.head, " ") + || fwrite("\n", 1, 1, params->f) != 1) + return -1; + + delete_block_ranges(¶ms->blocks); + return 0; +} + +static int cleanup(void *data) +{ + struct block_list *params = data; + + fclose(params->f); + free(params); + return 0; +} + +struct fsmap_format block_list_format = { + .init = init, + .start_new_file = start_new_file, + .add_block = add_block, + .inline_data = inline_data, + .end_new_file = end_new_file, + .cleanup = cleanup, +}; diff --git a/contrib/android/block_list.h b/contrib/android/block_list.h new file mode 100644 index 0000000..47041e4 --- /dev/null +++ b/contrib/android/block_list.h @@ -0,0 +1,8 @@ +#ifndef BLOCK_LIST_H +# define BLOCK_LIST_H + +# include "fsmap.h" + +extern struct fsmap_format block_list_format; + +#endif /* !BLOCK_LIST_H */ diff --git a/contrib/android/block_range.c b/contrib/android/block_range.c new file mode 100644 index 0000000..0a06882 --- /dev/null +++ b/contrib/android/block_range.c @@ -0,0 +1,80 @@ +#define _GNU_SOURCE + +#include "block_range.h" +#include + +struct block_range *new_block_range(blk64_t start, blk64_t end) +{ + struct block_range *range = malloc(sizeof(*range)); + range->start = start; + range->end = end; + range->next = NULL; + return range; +} + +void add_blocks_to_range(struct block_range_list *list, blk64_t blk_start, + blk64_t blk_end) +{ + if (list->head == NULL) + list->head = list->tail = new_block_range(blk_start, blk_end); + else if (list->tail->end + 1 == blk_start) + list->tail->end += (blk_end - blk_start + 1); + else { + struct block_range *range = new_block_range(blk_start, blk_end); + list->tail->next = range; + list->tail = range; + } +} + +static void remove_head(struct block_range_list *list) +{ + struct block_range *next_range = list->head->next; + + free(list->head); + if (next_range == NULL) + list->head = list->tail = NULL; + else + list->head = next_range; +} + +void delete_block_ranges(struct block_range_list *list) +{ + while (list->head) + remove_head(list); +} + +int write_block_ranges(FILE *f, struct block_range *range, + char *sep) +{ + int len; + char *buf; + + while (range) { + if (range->start == range->end) + len = asprintf(&buf, "%llu%s", range->start, sep); + else + len = asprintf(&buf, "%llu-%llu%s", range->start, + range->end, sep); + if (fwrite(buf, 1, len, f) != (size_t)len) { + free(buf); + return -1; + } + free(buf); + range = range->next; + } + + len = strlen(sep); + if (fseek(f, -len, SEEK_CUR) == -len) + return -1; + return 0; +} + +blk64_t consume_next_block(struct block_range_list *list) +{ + blk64_t ret = list->head->start; + + list->head->start += 1; + if (list->head->start > list->head->end) + remove_head(list); + return ret; +} diff --git a/contrib/android/block_range.h b/contrib/android/block_range.h new file mode 100644 index 0000000..cf7971e --- /dev/null +++ b/contrib/android/block_range.h @@ -0,0 +1,29 @@ +#ifndef BLOCK_RANGE_H +# define BLOCK_RANGE_H + +# include +# include + +struct block_range { + blk64_t start; + blk64_t end; + struct block_range *next; +}; + +struct block_range_list { + struct block_range *head; + struct block_range *tail; +}; + +void add_blocks_to_range(struct block_range_list *list, blk64_t blk_start, + blk64_t blk_end); +void delete_block_ranges(struct block_range_list *list); +int write_block_ranges(FILE *f, struct block_range *range, char *sep); + +/* + * Given a non-empty range list, return the next block and remove it from the + * list. + */ +blk64_t consume_next_block(struct block_range_list *list); + +#endif /* !BLOCK_RANGE_H */ diff --git a/contrib/android/e2fsdroid.c b/contrib/android/e2fsdroid.c new file mode 100644 index 0000000..6e51414 --- /dev/null +++ b/contrib/android/e2fsdroid.c @@ -0,0 +1,377 @@ +#define _GNU_SOURCE + +#include "config.h" +#include +#include +#include +#include +#include + +#include "perms.h" +#include "base_fs.h" +#include "block_list.h" +#include "basefs_allocator.h" +#include "create_inode.h" + +#ifndef UID_GID_MAP_MAX_EXTENTS +/* + * The value is defined in linux/user_namspace.h. + * The value is (arbitrarily) 5 in 4.14 and earlier, or 340 in 4.15 and later. + * Here, the bigger value is taken. See also man user_namespace(7). + */ +#define UID_GID_MAP_MAX_EXTENTS 340 +#endif + +static char *prog_name = "e2fsdroid"; +static char *in_file; +static char *block_list; +static char *basefs_out; +static char *basefs_in; +static char *mountpoint = ""; +static time_t fixed_time = -1; +static char *fs_config_file; +static struct selinux_opt seopt_file[8]; +static int max_nr_opt = (int)sizeof(seopt_file) / sizeof(seopt_file[0]); +static char *product_out; +static char *src_dir; +static int android_configure; +static int android_sparse_file = 1; + +static void usage(int ret) +{ + fprintf(stderr, "%s [-B block_list] [-D basefs_out] [-T timestamp]\n" + "\t[-C fs_config] [-S file_contexts] [-p product_out]\n" + "\t[-a mountpoint] [-d basefs_in] [-f src_dir] [-e] [-s]\n" + "\t[-u uid-mapping] [-g gid-mapping] image\n", + prog_name); + exit(ret); +} + +static char *absolute_path(const char *file) +{ + char *ret; + char cwd[PATH_MAX]; + + if (file[0] != '/') { + if (getcwd(cwd, PATH_MAX) == NULL) { + fprintf(stderr, "Failed to getcwd\n"); + exit(EXIT_FAILURE); + } + ret = malloc(strlen(cwd) + 1 + strlen(file) + 1); + if (ret) + sprintf(ret, "%s/%s", cwd, file); + } else + ret = strdup(file); + return ret; +} + +static int parse_ugid_map_entry(char* line, struct ugid_map_entry* result) +{ + char *token, *token_saveptr; + size_t num_tokens; + unsigned int *parsed[] = {&result->child_id, + &result->parent_id, + &result->length}; + for (token = strtok_r(line, " ", &token_saveptr), num_tokens = 0; + token && num_tokens < 3; + token = strtok_r(NULL, " ", &token_saveptr), ++num_tokens) { + char* endptr = NULL; + unsigned long t = strtoul(token, &endptr, 10); + if ((t == ULONG_MAX && errno) || (t > UINT_MAX) || *endptr) { + fprintf(stderr, "Malformed u/gid mapping line\n"); + return 0; + } + *parsed[num_tokens] = (unsigned int) t; + } + if (num_tokens < 3 || strtok_r(NULL, " ", &token_saveptr) != NULL) { + fprintf(stderr, "Malformed u/gid mapping line\n"); + return 0; + } + if (result->child_id + result->length < result->child_id || + result->parent_id + result->length < result->parent_id) { + fprintf(stderr, "u/gid mapping overflow\n"); + return 0; + } + return 1; +} + +/* + * Returns 1 if [begin1, begin1+length1) and [begin2, begin2+length2) have + * overlapping range. Otherwise 0. + */ +static int is_overlapping(unsigned int begin1, unsigned int length1, + unsigned int begin2, unsigned int length2) +{ + unsigned int end1 = begin1 + length1; + unsigned int end2 = begin2 + length2; + return !(end1 <= begin2 || end2 <= begin1); +} + +/* + * Verifies if the given mapping works. + * - Checks if the number of entries is less than or equals to + * UID_GID_MAP_MAX_EXTENTS. + * - Checks if there is no overlapped ranges. + * Returns 1 if valid, otherwise 0. + */ +static int is_valid_ugid_map(const struct ugid_map* mapping) +{ + size_t i, j; + + if (mapping->size > UID_GID_MAP_MAX_EXTENTS) { + fprintf(stderr, "too many u/gid mapping entries\n"); + return 0; + } + + for (i = 0; i < mapping->size; ++i) { + const struct ugid_map_entry *entry1 = &mapping->entries[i]; + for (j = i + 1; j < mapping->size; ++j) { + const struct ugid_map_entry *entry2 = + &mapping->entries[j]; + if (is_overlapping(entry1->child_id, entry1->length, + entry2->child_id, entry2->length)) { + fprintf(stderr, + "Overlapping child u/gid: [%d %d %d]," + " [%d %d %d]\n", + entry1->child_id, entry1->parent_id, + entry1->length, entry2->child_id, + entry2->parent_id, entry2->length); + return 0; + } + if (is_overlapping(entry1->parent_id, entry1->length, + entry2->parent_id, entry2->length)) { + fprintf(stderr, + "Overlapping parent u/gid: [%d %d %d]," + " [%d %d %d]\n", + entry1->child_id, entry1->parent_id, + entry1->length, entry2->child_id, + entry2->parent_id, entry2->length); + return 0; + } + } + } + return 1; +} + +/* + * Parses the UID/GID mapping argument. The argument could be a multi-line + * string (separated by '\n', no trailing '\n' is allowed). Each line must + * contain exact three integer tokens; the first token is |child_id|, + * the second is |parent_id|, and the last is |length| of the mapping range. + * See also user_namespace(7) man page. + * On success, the parsed entries are stored in |result|, and it returns 1. + * Otherwise, returns 0. + */ +static int parse_ugid_map(char* arg, struct ugid_map* result) +{ + int i; + char *line, *line_saveptr; + size_t current_index; + + /* Count the number of lines. */ + result->size = 1; + for (i = 0; arg[i]; ++i) { + if (arg[i] == '\n') + ++result->size; + } + + /* Allocate memory for entries. */ + result->entries = malloc(sizeof(struct ugid_map_entry) * result->size); + if (!result->entries) { + result->size = 0; + return 0; + } + + /* Parse each line */ + for (line = strtok_r(arg, "\n", &line_saveptr), current_index = 0; + line; + line = strtok_r(NULL, "\n", &line_saveptr), ++current_index) { + if (!parse_ugid_map_entry( + line, &result->entries[current_index])) { + return 0; + } + } + + return is_valid_ugid_map(result); +} + +int main(int argc, char *argv[]) +{ + int c; + char *p; + int flags = EXT2_FLAG_RW; + errcode_t retval; + io_manager io_mgr; + ext2_filsys fs = NULL; + struct fs_ops_callbacks fs_callbacks = { NULL, NULL }; + char *token; + int nr_opt = 0; + ext2_ino_t inodes_count; + ext2_ino_t free_inodes_count; + blk64_t blocks_count; + blk64_t free_blocks_count; + struct ugid_map uid_map = { 0, NULL }, gid_map = { 0, NULL }; + + add_error_table(&et_ext2_error_table); + + while ((c = getopt (argc, argv, "T:C:S:p:a:D:d:B:f:esu:g:")) != EOF) { + switch (c) { + case 'T': + fixed_time = strtoul(optarg, &p, 0); + android_configure = 1; + break; + case 'C': + fs_config_file = absolute_path(optarg); + android_configure = 1; + break; + case 'S': + token = strtok(optarg, ","); + while (token) { + if (nr_opt == max_nr_opt) { + fprintf(stderr, "Expected at most %d selinux opts\n", + max_nr_opt); + exit(EXIT_FAILURE); + } + seopt_file[nr_opt].type = SELABEL_OPT_PATH; + seopt_file[nr_opt].value = absolute_path(token); + nr_opt++; + token = strtok(NULL, ","); + } + android_configure = 1; + break; + case 'p': + product_out = absolute_path(optarg); + android_configure = 1; + break; + case 'a': + mountpoint = strdup(optarg); + break; + case 'D': + basefs_out = absolute_path(optarg); + break; + case 'd': + basefs_in = absolute_path(optarg); + break; + case 'B': + block_list = absolute_path(optarg); + break; + case 'f': + src_dir = absolute_path(optarg); + break; + case 'e': + android_sparse_file = 0; + break; + case 's': + flags |= EXT2_FLAG_SHARE_DUP; + break; + case 'u': + if (!parse_ugid_map(optarg, &uid_map)) + exit(EXIT_FAILURE); + android_configure = 1; + break; + case 'g': + if (!parse_ugid_map(optarg, &gid_map)) + exit(EXIT_FAILURE); + android_configure = 1; + break; + default: + usage(EXIT_FAILURE); + } + } + if (optind >= argc) { + fprintf(stderr, "Expected filename after options\n"); + exit(EXIT_FAILURE); + } + + if (android_sparse_file) { + io_mgr = sparse_io_manager; + if (asprintf(&in_file, "(%s)", argv[optind]) == -1) { + fprintf(stderr, "Failed to allocate file name\n"); + exit(EXIT_FAILURE); + } + } else { + io_mgr = unix_io_manager; + in_file = strdup(argv[optind]); + } + retval = ext2fs_open(in_file, flags, 0, 0, io_mgr, &fs); + if (retval) { + com_err(prog_name, retval, "while opening file %s\n", in_file); + return retval; + } + + if (src_dir) { + ext2fs_read_bitmaps(fs); + if (basefs_in) { + retval = base_fs_alloc_load(fs, basefs_in, mountpoint, + src_dir); + if (retval) { + com_err(prog_name, retval, "%s", + "while reading base_fs file"); + exit(1); + } + fs_callbacks.create_new_inode = + base_fs_alloc_set_target; + fs_callbacks.end_create_new_inode = + base_fs_alloc_unset_target; + } + retval = populate_fs2(fs, EXT2_ROOT_INO, src_dir, + EXT2_ROOT_INO, &fs_callbacks); + if (retval) { + com_err(prog_name, retval, "%s", + "while populating file system"); + exit(1); + } + if (basefs_in) + base_fs_alloc_cleanup(fs); + } + + if (android_configure) { + retval = android_configure_fs( + fs, src_dir, product_out, mountpoint, seopt_file, + nr_opt, fs_config_file, fixed_time, &uid_map, &gid_map); + if (retval) { + com_err(prog_name, retval, "%s", + "while configuring the file system"); + exit(1); + } + } + + if (block_list) { + retval = fsmap_iter_filsys(fs, &block_list_format, block_list, + mountpoint); + if (retval) { + com_err(prog_name, retval, "%s", + "while creating the block_list"); + exit(1); + } + } + + if (basefs_out) { + retval = fsmap_iter_filsys(fs, &base_fs_format, + basefs_out, mountpoint); + if (retval) { + com_err(prog_name, retval, "%s", + "while creating the basefs file"); + exit(1); + } + } + + inodes_count = fs->super->s_inodes_count; + free_inodes_count = fs->super->s_free_inodes_count; + blocks_count = ext2fs_blocks_count(fs->super); + free_blocks_count = ext2fs_free_blocks_count(fs->super); + + retval = ext2fs_close_free(&fs); + if (retval) { + com_err(prog_name, retval, "%s", + "while writing superblocks"); + exit(1); + } + + printf("Created filesystem with %u/%u inodes and %llu/%llu blocks\n", + inodes_count - free_inodes_count, inodes_count, + blocks_count - free_blocks_count, blocks_count); + + remove_error_table(&et_ext2_error_table); + return 0; +} diff --git a/contrib/android/ext2simg.c b/contrib/android/ext2simg.c new file mode 100644 index 0000000..017e16f --- /dev/null +++ b/contrib/android/ext2simg.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct { + int crc; + int sparse; + int gzip; + char *in_file; + char *out_file; + bool overwrite_input; +} params = { + .crc = 0, + .sparse = 1, + .gzip = 0, +}; + +#define ext2fs_fatal(Retval, Format, ...) \ + do { \ + com_err("error", Retval, Format, __VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while(0) + +#define sparse_fatal(Format) \ + do { \ + fprintf(stderr, "sparse: "Format); \ + exit(EXIT_FAILURE); \ + } while(0) + +static void usage(char *path) +{ + char *progname = basename(path); + + fprintf(stderr, "%s [ options ] \n" + " -c include CRC block\n" + " -z gzip output\n" + " -S don't use sparse output format\n", progname); +} + +static struct buf_item { + struct buf_item *next; + void *buf[0]; +} *buf_list; + +static void add_chunk(ext2_filsys fs, struct sparse_file *s, blk_t chunk_start, blk_t chunk_end) +{ + int retval; + unsigned int nb_blk = chunk_end - chunk_start; + size_t len = nb_blk * fs->blocksize; + int64_t offset = (int64_t)chunk_start * (int64_t)fs->blocksize; + + if (params.overwrite_input == false) { + if (sparse_file_add_file(s, params.in_file, offset, len, chunk_start) < 0) + sparse_fatal("adding data to the sparse file"); + } else { + /* + * The input file will be overwritten, make a copy of + * the blocks + */ + struct buf_item *bi = calloc(1, sizeof(struct buf_item) + len); + if (buf_list == NULL) + buf_list = bi; + else { + bi->next = buf_list; + buf_list = bi; + } + + retval = io_channel_read_blk64(fs->io, chunk_start, nb_blk, bi->buf); + if (retval < 0) + ext2fs_fatal(retval, "reading block %u - %u", chunk_start, chunk_end); + + if (sparse_file_add_data(s, bi->buf, len, chunk_start) < 0) + sparse_fatal("adding data to the sparse file"); + } +} + +static void free_chunks(void) +{ + struct buf_item *bi; + + while (buf_list) { + bi = buf_list->next; + free(buf_list); + buf_list = bi; + } +} + +static struct sparse_file *ext_to_sparse(const char *in_file) +{ + errcode_t retval; + ext2_filsys fs; + struct sparse_file *s; + int64_t chunk_start = -1; + blk_t first_blk, last_blk, nb_blk, cur_blk; + + retval = ext2fs_open(in_file, 0, 0, 0, unix_io_manager, &fs); + if (retval) + ext2fs_fatal(retval, "while reading %s", in_file); + + retval = ext2fs_read_block_bitmap(fs); + if (retval) + ext2fs_fatal(retval, "while reading block bitmap of %s", in_file); + + first_blk = ext2fs_get_block_bitmap_start2(fs->block_map); + last_blk = ext2fs_get_block_bitmap_end2(fs->block_map); + nb_blk = last_blk - first_blk + 1; + + s = sparse_file_new(fs->blocksize, (uint64_t)fs->blocksize * (uint64_t)nb_blk); + if (!s) + sparse_fatal("creating sparse file"); + + /* + * The sparse format encodes the size of a chunk (and its header) in a + * 32-bit unsigned integer (UINT32_MAX) + * When writing the chunk, the library uses a single call to write(). + * Linux's implementation of the 'write' syscall does not allow transfers + * larger than INT32_MAX (32-bit _and_ 64-bit systems). + * Make sure we do not create chunks larger than this limit. + */ + int64_t max_blk_per_chunk = (INT32_MAX - 12) / fs->blocksize; + + /* Iter on the blocks to merge contiguous chunk */ + for (cur_blk = first_blk; cur_blk <= last_blk; ++cur_blk) { + if (ext2fs_test_block_bitmap2(fs->block_map, cur_blk)) { + if (chunk_start == -1) { + chunk_start = cur_blk; + } else if (cur_blk - chunk_start + 1 == max_blk_per_chunk) { + add_chunk(fs, s, chunk_start, cur_blk); + chunk_start = -1; + } + } else if (chunk_start != -1) { + add_chunk(fs, s, chunk_start, cur_blk); + chunk_start = -1; + } + } + if (chunk_start != -1) + add_chunk(fs, s, chunk_start, cur_blk - 1); + + ext2fs_free(fs); + return s; +} + +static bool same_file(const char *in, const char *out) +{ + struct stat st1, st2; + + if (access(out, F_OK) == -1) + return false; + + if (lstat(in, &st1) == -1) + ext2fs_fatal(errno, "stat %s\n", in); + if (lstat(out, &st2) == -1) + ext2fs_fatal(errno, "stat %s\n", out); + return st1.st_ino == st2.st_ino; +} + +int main(int argc, char *argv[]) +{ + int opt; + int out_fd; + struct sparse_file *s; + + while ((opt = getopt(argc, argv, "czS")) != -1) { + switch(opt) { + case 'c': + params.crc = 1; + break; + case 'z': + params.gzip = 1; + break; + case 'S': + params.sparse = 0; + break; + default: + usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (optind + 1 >= argc) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + params.in_file = strdup(argv[optind++]); + params.out_file = strdup(argv[optind]); + params.overwrite_input = same_file(params.in_file, params.out_file); + + s = ext_to_sparse(params.in_file); + + out_fd = open(params.out_file, O_WRONLY | O_CREAT | O_TRUNC, 0664); + if (out_fd == -1) + ext2fs_fatal(errno, "opening %s\n", params.out_file); + if (sparse_file_write(s, out_fd, params.gzip, params.sparse, params.crc) < 0) + sparse_fatal("writing sparse file"); + + sparse_file_destroy(s); + + free(params.in_file); + free(params.out_file); + free_chunks(); + close(out_fd); + + return 0; +} diff --git a/contrib/android/fsmap.c b/contrib/android/fsmap.c new file mode 100644 index 0000000..9ee8472 --- /dev/null +++ b/contrib/android/fsmap.c @@ -0,0 +1,169 @@ +#include "fsmap.h" +#include +#include +#include +#include +#include "support/nls-enable.h" + +struct walk_ext_priv_data { + char *path; + ext2_filsys fs; + struct fsmap_format *format; +}; + +static int walk_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t *blocknr, + e2_blkcnt_t blockcnt, + blk64_t ref64_blk EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv) +{ + struct walk_ext_priv_data *pdata = priv; + struct fsmap_format *format = pdata->format; + + return format->add_block(fs, *blocknr, blockcnt < 0, format->private); +} + +static errcode_t ino_iter_extents(ext2_filsys fs, ext2_ino_t ino, + ext2_extent_handle_t extents, + struct walk_ext_priv_data *pdata) +{ + blk64_t block; + errcode_t retval; + blk64_t next_lblk = 0; + int op = EXT2_EXTENT_ROOT; + struct ext2fs_extent extent; + struct fsmap_format *format = pdata->format; + + for (;;) { + retval = ext2fs_extent_get(extents, op, &extent); + if (retval) + break; + + op = EXT2_EXTENT_NEXT; + + if ((extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) || + !(extent.e_flags & EXT2_EXTENT_FLAGS_LEAF)) + continue; + + for (; next_lblk < extent.e_lblk; next_lblk++) + format->add_block(fs, 0, 0, format->private); + + block = extent.e_pblk; + for (; next_lblk < extent.e_lblk + extent.e_len; next_lblk++) + format->add_block(fs, block++, 0, format->private); + } + + if (retval == EXT2_ET_EXTENT_NO_NEXT) + retval = 0; + if (retval) { + com_err(__func__, retval, ("getting extents of ino \"%u\""), + ino); + } + return retval; +} + +static errcode_t ino_iter_blocks(ext2_filsys fs, ext2_ino_t ino, + struct walk_ext_priv_data *pdata) +{ + errcode_t retval; + struct ext2_inode inode; + ext2_extent_handle_t extents; + struct fsmap_format *format = pdata->format; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + + if (!ext2fs_inode_has_valid_blocks2(fs, &inode)) + return format->inline_data(&(inode.i_block[0]), + format->private); + + retval = ext2fs_extent_open(fs, ino, &extents); + if (retval == EXT2_ET_INODE_NOT_EXTENT) { + retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY, + NULL, walk_block, pdata); + if (retval) { + com_err(__func__, retval, _("listing blocks of ino \"%u\""), + ino); + } + return retval; + } + + retval = ino_iter_extents(fs, ino, extents, pdata); + + ext2fs_extent_free(extents); + return retval; +} + +static int is_dir(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + + if (ext2fs_read_inode(fs, ino, &inode)) + return 0; + return S_ISDIR(inode.i_mode); +} + +static int walk_ext_dir(ext2_ino_t dir EXT2FS_ATTR((unused)), + int flags EXT2FS_ATTR((unused)), + struct ext2_dir_entry *de, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), void *priv_data) +{ + errcode_t retval; + struct ext2_inode inode; + char *filename, *cur_path, *name = de->name; + int name_len = de->name_len & 0xff; + struct walk_ext_priv_data *pdata = priv_data; + struct fsmap_format *format = pdata->format; + + if (!strncmp(name, ".", name_len) + || !strncmp(name, "..", name_len) + || !strncmp(name, "lost+found", 10)) + return 0; + + if (asprintf(&filename, "%s/%.*s", pdata->path, name_len, name) < 0) + return -ENOMEM; + + retval = ext2fs_read_inode(pdata->fs, de->inode, &inode); + if (retval) { + com_err(__func__, retval, _("reading ino \"%u\""), de->inode); + goto end; + } + format->start_new_file(filename, de->inode, &inode, format->private); + retval = ino_iter_blocks(pdata->fs, de->inode, pdata); + if (retval) + return retval; + format->end_new_file(format->private); + + retval = 0; + if (is_dir(pdata->fs, de->inode)) { + cur_path = pdata->path; + pdata->path = filename; + retval = ext2fs_dir_iterate2(pdata->fs, de->inode, 0, NULL, + walk_ext_dir, pdata); + pdata->path = cur_path; + } + +end: + free(filename); + return retval; +} + +errcode_t fsmap_iter_filsys(ext2_filsys fs, struct fsmap_format *format, + const char *file, const char *mountpoint) +{ + struct walk_ext_priv_data pdata; + errcode_t retval; + + format->private = format->init(file, mountpoint); + pdata.fs = fs; + pdata.path = ""; + pdata.format = format; + + retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_ext_dir, &pdata); + + format->cleanup(format->private); + return retval; +} diff --git a/contrib/android/fsmap.h b/contrib/android/fsmap.h new file mode 100644 index 0000000..9f84a71 --- /dev/null +++ b/contrib/android/fsmap.h @@ -0,0 +1,29 @@ +#ifndef FSMAP_H +# define FSMAP_H + +# ifndef _GNU_SOURCE +# define _GNU_SOURCE // asprintf +# endif +# include +# include +# include +# include +# include + +struct fsmap_format { + void* (* init)(const char *file, const char *mountpoint); + int (* start_new_file)(char *path, ext2_ino_t ino, + struct ext2_inode *inode, void *data); + int (* add_block)(ext2_filsys fs, blk64_t blocknr, int metadata, + void *data); + int (* inline_data)(void *inline_data, void *data); + int (* end_new_file)(void *data); + int (* cleanup)(void *data); + + void *private; +}; + +errcode_t fsmap_iter_filsys(ext2_filsys fs, struct fsmap_format *format, + const char *file, const char *mountpoint); + +#endif /* !FSMAP_H */ diff --git a/contrib/android/perms.c b/contrib/android/perms.c new file mode 100644 index 0000000..dd05644 --- /dev/null +++ b/contrib/android/perms.c @@ -0,0 +1,376 @@ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE //asprintf +#endif +#include "config.h" +#include "perms.h" +#include "support/nls-enable.h" +#include +#include + +#ifndef XATTR_SELINUX_SUFFIX +# define XATTR_SELINUX_SUFFIX "selinux" +#endif +#ifndef XATTR_CAPS_SUFFIX +# define XATTR_CAPS_SUFFIX "capability" +#endif + +struct inode_params { + ext2_filsys fs; + char *path; + char *filename; + char *src_dir; + char *target_out; + char *mountpoint; + fs_config_f fs_config_func; + struct selabel_handle *sehnd; + time_t fixed_time; + const struct ugid_map* uid_map; + const struct ugid_map* gid_map; + errcode_t error; +}; + +static errcode_t ino_add_xattr(ext2_filsys fs, ext2_ino_t ino, const char *name, + const void *value, int value_len) +{ + errcode_t retval, close_retval; + struct ext2_xattr_handle *xhandle; + + retval = ext2fs_xattrs_open(fs, ino, &xhandle); + if (retval) { + com_err(__func__, retval, _("while opening inode %u"), ino); + return retval; + } + retval = ext2fs_xattrs_read(xhandle); + if (retval) { + com_err(__func__, retval, + _("while reading xattrs of inode %u"), ino); + goto xattrs_close; + } + retval = ext2fs_xattr_set(xhandle, name, value, value_len); + if (retval) { + com_err(__func__, retval, + _("while setting xattrs of inode %u"), ino); + goto xattrs_close; + } +xattrs_close: + close_retval = ext2fs_xattrs_close(&xhandle); + if (close_retval) { + com_err(__func__, close_retval, + _("while closing xattrs of inode %u"), ino); + return retval ? retval : close_retval; + } + return retval; +} + +static errcode_t set_selinux_xattr(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + char *secontext = NULL; + struct ext2_inode inode; + + if (params->sehnd == NULL) + return 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while reading inode %u"), ino); + return retval; + } + + retval = selabel_lookup(params->sehnd, &secontext, params->filename, + inode.i_mode); + if (retval < 0) { + int saved_errno = errno; + com_err(__func__, errno, + _("searching for label \"%s\""), params->filename); + return saved_errno; + } + + retval = ino_add_xattr(fs, ino, "security." XATTR_SELINUX_SUFFIX, + secontext, strlen(secontext) + 1); + + freecon(secontext); + return retval; +} + +/* + * Returns mapped UID/GID if there is a corresponding entry in |mapping|. + * Otherwise |id| as is. + */ +static unsigned int resolve_ugid(const struct ugid_map* mapping, + unsigned int id) +{ + size_t i; + for (i = 0; i < mapping->size; ++i) { + const struct ugid_map_entry* entry = &mapping->entries[i]; + if (entry->parent_id <= id && + id < entry->parent_id + entry->length) { + return id + entry->child_id - entry->parent_id; + } + } + + /* No entry is found. */ + return id; +} + +static errcode_t set_perms_and_caps(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + uint64_t capabilities = 0; + struct ext2_inode inode; + struct vfs_cap_data cap_data; + unsigned int uid = 0, gid = 0, imode = 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, _("while reading inode %u"), ino); + return retval; + } + + /* Permissions */ + if (params->fs_config_func != NULL) { + const char *filename = params->filename; + if (strcmp(filename, params->mountpoint) == 0) { + /* The root of the filesystem needs to be an empty string. */ + filename = ""; + } + params->fs_config_func(filename, S_ISDIR(inode.i_mode), + params->target_out, &uid, &gid, &imode, + &capabilities); + uid = resolve_ugid(params->uid_map, uid); + gid = resolve_ugid(params->gid_map, gid); + inode.i_uid = (__u16) uid; + inode.i_gid = (__u16) gid; + ext2fs_set_i_uid_high(inode, (__u16) (uid >> 16)); + ext2fs_set_i_gid_high(inode, (__u16) (gid >> 16)); + inode.i_mode = (inode.i_mode & S_IFMT) | (imode & 0xffff); + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while writing inode %u"), ino); + return retval; + } + } + + /* Capabilities */ + if (!capabilities) + return 0; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t) (capabilities & 0xffffffff); + cap_data.data[1].permitted = (uint32_t) (capabilities >> 32); + return ino_add_xattr(fs, ino, "security." XATTR_CAPS_SUFFIX, + &cap_data, sizeof(cap_data)); +} + +static errcode_t set_timestamp(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + struct ext2_inode inode; + struct stat stat; + char *src_filename = NULL; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while reading inode %u"), ino); + return retval; + } + + if (params->fixed_time == -1 && params->src_dir) { + /* replace mountpoint from filename with src_dir */ + if (asprintf(&src_filename, "%s/%s", params->src_dir, + params->filename + strlen(params->mountpoint)) < 0) { + return -ENOMEM; + } + retval = lstat(src_filename, &stat); + if (retval < 0) { + com_err(__func__, errno, + _("while lstat file %s"), src_filename); + goto end; + } + inode.i_atime = inode.i_ctime = inode.i_mtime = stat.st_mtime; + } else { + inode.i_atime = inode.i_ctime = inode.i_mtime = params->fixed_time; + } + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(__func__, retval, + _("while writing inode %u"), ino); + goto end; + } + +end: + free(src_filename); + return retval; +} + +static int is_dir(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + + if (ext2fs_read_inode(fs, ino, &inode)) + return 0; + return S_ISDIR(inode.i_mode); +} + +static errcode_t androidify_inode(ext2_filsys fs, ext2_ino_t ino, + struct inode_params *params) +{ + errcode_t retval; + + retval = set_timestamp(fs, ino, params); + if (retval) + return retval; + + retval = set_selinux_xattr(fs, ino, params); + if (retval) + return retval; + + return set_perms_and_caps(fs, ino, params); +} + +static int walk_dir(ext2_ino_t dir EXT2FS_ATTR((unused)), + int flags EXT2FS_ATTR((unused)), + struct ext2_dir_entry *de, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), void *priv_data) +{ + __u16 name_len; + errcode_t retval; + struct inode_params *params = (struct inode_params *)priv_data; + + name_len = de->name_len & 0xff; + if (!strncmp(de->name, ".", name_len) + || (!strncmp(de->name, "..", name_len))) + return 0; + + if (asprintf(¶ms->filename, "%s/%.*s", params->path, name_len, + de->name) < 0) { + params->error = ENOMEM; + return -ENOMEM; + } + + if (!strncmp(de->name, "lost+found", 10)) { + retval = set_selinux_xattr(params->fs, de->inode, params); + if (retval) + goto end; + } else { + retval = androidify_inode(params->fs, de->inode, params); + if (retval) + goto end; + if (is_dir(params->fs, de->inode)) { + char *cur_path = params->path; + char *cur_filename = params->filename; + params->path = params->filename; + retval = ext2fs_dir_iterate2(params->fs, de->inode, 0, NULL, + walk_dir, params); + if (retval) + goto end; + params->path = cur_path; + params->filename = cur_filename; + } + } + +end: + free(params->filename); + params->error |= retval; + return retval; +} + +errcode_t __android_configure_fs(ext2_filsys fs, char *src_dir, + char *target_out, + char *mountpoint, + fs_config_f fs_config_func, + struct selabel_handle *sehnd, + time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map) +{ + errcode_t retval; + struct inode_params params = { + .fs = fs, + .src_dir = src_dir, + .target_out = target_out, + .fs_config_func = fs_config_func, + .sehnd = sehnd, + .fixed_time = fixed_time, + .path = mountpoint, + .filename = mountpoint, + .mountpoint = mountpoint, + .uid_map = uid_map, + .gid_map = gid_map, + .error = 0 + }; + + /* walk_dir will add the "/". Don't add it twice. */ + if (strlen(mountpoint) == 1 && mountpoint[0] == '/') + params.path = ""; + + retval = androidify_inode(fs, EXT2_ROOT_INO, ¶ms); + if (retval) + return retval; + + retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_dir, + ¶ms); + if (retval) + return retval; + return params.error; +} + +errcode_t android_configure_fs(ext2_filsys fs, char *src_dir, char *target_out, + char *mountpoint, + struct selinux_opt *seopts EXT2FS_ATTR((unused)), + unsigned int nopt EXT2FS_ATTR((unused)), + char *fs_config_file, time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map) +{ + errcode_t retval; + fs_config_f fs_config_func = NULL; + struct selabel_handle *sehnd = NULL; + + /* Retrieve file contexts */ +#if !defined(__ANDROID__) + if (nopt > 0) { + sehnd = selabel_open(SELABEL_CTX_FILE, seopts, nopt); + if (!sehnd) { + int saved_errno = errno; + com_err(__func__, errno, + _("while opening file contexts \"%s\""), + seopts[0].value); + return saved_errno; + } + } +#else + sehnd = selinux_android_file_context_handle(); + if (!sehnd) { + com_err(__func__, EINVAL, + _("while opening android file_contexts")); + return EINVAL; + } +#endif + + /* Load the FS config */ + if (fs_config_file) { + retval = load_canned_fs_config(fs_config_file); + if (retval < 0) { + com_err(__func__, retval, + _("while loading fs_config \"%s\""), + fs_config_file); + return retval; + } + fs_config_func = canned_fs_config; + } else if (mountpoint) + fs_config_func = fs_config; + + return __android_configure_fs(fs, src_dir, target_out, mountpoint, + fs_config_func, sehnd, fixed_time, + uid_map, gid_map); +} diff --git a/contrib/android/perms.h b/contrib/android/perms.h new file mode 100644 index 0000000..9ea3f95 --- /dev/null +++ b/contrib/android/perms.h @@ -0,0 +1,65 @@ +#ifndef ANDROID_PERMS_H +# define ANDROID_PERMS_H + +# include + +typedef void (*fs_config_f)(const char *path, int dir, + const char *target_out_path, + unsigned *uid, unsigned *gid, + unsigned *mode, uint64_t *capabilities); + +/* + * Represents a range of UID/GID mapping. + * This maps the id in [|parent_id|, |parent_id| + |length|) into + * [|child_id|, |child_id| + |length|) + */ +struct ugid_map_entry { + unsigned int child_id; + unsigned int parent_id; + unsigned int length; +}; + +struct ugid_map { + /* The number of elements in |entries|. */ + size_t size; + + /* An array of entries. If |size| is 0, this is a null pointer. */ + struct ugid_map_entry* entries; +}; + +# ifdef _WIN32 +struct selabel_handle; +static inline errcode_t android_configure_fs(ext2_filsys fs, + char *src_dir, + char *target_out, + char *mountpoint, + void *seopts, + unsigned int nopt, + char *fs_config_file, + time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugdi_map* gid_map) +{ + return 0; +} +# else +# include +# include +# if defined(__ANDROID__) +# include +# endif +# include +# include +# include + +errcode_t android_configure_fs(ext2_filsys fs, char *src_dir, + char *target_out, + char *mountpoint, + struct selinux_opt *seopts, + unsigned int nopt, + char *fs_config_file, time_t fixed_time, + const struct ugid_map* uid_map, + const struct ugid_map* gid_map); + +# endif +#endif /* !ANDROID_PERMS_H */ diff --git a/contrib/build-rpm b/contrib/build-rpm new file mode 100644 index 0000000..0b06071 --- /dev/null +++ b/contrib/build-rpm @@ -0,0 +1,45 @@ +#!/bin/sh + +# enable xtrace output if requested +if [ -n ${ENABLE_XTRACE:-''} ]; then + set -x +fi + +# Build an e2fsprogs RPM from cvs + +pwd=`pwd` +currdir=`basename $pwd` +pkgname=`grep Name: e2fsprogs.spec | awk '{print $2;}'` +pkgvers=`grep Version: e2fsprogs.spec | awk '{print $2;}'` +builddir=${pkgname}-${pkgvers} + +# ensure that $TMP is set to something +TMP=${TMP:-'/tmp'} + +cd .. +tmpdir=`mktemp -d ${RPM_TMPDIR:-$TMP}/rpmtmp.XXXXXX` + +# We need to build a tarball for the SRPM using $builddir as the +# directory name (since that's what RPM will expect it to unpack +# into). That may require a symlink. + +# Make a recursive-symlink copy of the source dir +cp -sR `pwd`/$currdir $tmpdir/$builddir || exit 1 + +# Remove any build files from the temporary tarball directory +[ -f $tmpdir/$builddir/Makefile ] && make -C $tmpdir/$builddir distclean + +EXCLUDE="--exclude .hg* --exclude .pc*" +(cd $tmpdir && tar czfh ${builddir}.tar.gz $EXCLUDE $builddir) + +[ "`rpmbuild --version 2> /dev/null`" ] && RPM=rpmbuild || RPM=rpm + +$RPM --define "_sourcedir $tmpdir" \ + --define "_topdir ${RPM_TOPDIR:-$(rpm -E %_topdir)}" \ + --define "_tmpdir ${RPM_TMPDIR:-$TMP}" \ + --define "extra_config_flags ${EXTRA_CONFIG_FLAGS:-''}" \ + -ba $currdir/e2fsprogs.spec + +rpm_exit=$? +rm -rf $tmpdir +exit $rpm_exit diff --git a/contrib/dconf b/contrib/dconf new file mode 100644 index 0000000..cef4cc6 --- /dev/null +++ b/contrib/dconf @@ -0,0 +1,118 @@ +#!/bin/sh +#$Id$ +# Create Adobe-PostScript file that graphically displays the output of +# dumpe2fs(8). Use "dumpe2fs | dconf" to create a PostScript file on stdout. +# Developed and tested for Linux 1.0. +# Copyright (c) 1994 +# Ulrich Windl +# ALte Regensburger Strasse 11a +# D-93149 Nittenau, Germany +# +SELF=`basename $0` +AWKFILE=/tmp/${SELF}.awk +TEMPFILE=/tmp/${SELF}.tmp +echo ' +BEGIN { + print "B" +} +/^Inode count:/ { + ic=$3; next +} +/^Block count:/ { + bc=$3; next +} +/^First block:/ { + fb=$3; next +} +/^Block size:/ { + bs=$3; next +} +/^Blocks per group:/ { + bpg=$4 + printf("BC %d\n", bpg) + printf("GC %d\n", (bc + bpg - 1) / bpg) + next +} +/^Inodes per group:/ { + ipg=$4; next +} +/^Last write time:/ { + lwtime=$0; gsub("Last write time:[ ]+", "", lwtime) + printf("T %s\n", lwtime) + next +} +/^Group [0-9]+:/ { + group=$2; gsub(":", "", group) + block="" + group_start=group*bpg+fb + group_end=group_start+bpg + printf("G %d : %d - %d\n", group, group_start, group_end) + next +} +/^[ ]+Free blocks: / { + for ( i=3; i < NF; ++i ) { + block=$i; gsub(",", "", block) + if ( index(block, "-") == 0 ) block=block "-" block + pos=index(block, "-") + printf("FB %d-%d\n", + substr(block, 0, pos) - group_start, + substr(block, pos + 1) - group_start) + } + if ( block == "" ) printf("Group %d is full\n", group) + print "----" + next +} +END { + printf("E %s\n", lwtime) +}' >$AWKFILE +awk -f $AWKFILE $* >$TEMPFILE +echo ' +BEGIN { + printf("%%!PS-Adobe\n") + printf("%%%%BoundingBox: 0 0 1 1\n") + printf("/rect {/y2 exch def /x2 exch def /y1 exch def /x1 exch def\n") + printf(" newpath x1 y1 moveto x2 y1 lineto x2 y2 lineto\n") + printf(" x1 y2 lineto closepath} def\n") + printf("/fb {rect gsave 1.0 setgray fill grestore} def\n") + printf("/dg {rect gsave gsave 0.0 setgray fill grestore\n") + printf(" 0.5 setgray stroke grestore} def\n") + printf("/textxy {moveto show} bind def\n") + printf("0.0001 setlinewidth\n") +} +$1 == "GC" && NF == 2 { + number_of_groups=$2 + printf("/Times-Roman findfont %g scalefont setfont\n", + 1.0 / number_of_groups) + next +} +$1 == "BC" && NF == 2 { + blocks_per_group=$2; next +} +$1 == "T" && NF > 1 { + printf("(%s) %g %g textxy\n", + substr($0, 2), 0, 1.02) + next +} +$1 == "G" && NF == 6 && $3 == ":" && $5 == "-" { + group_index=$2 + gs=$4 + ge=$6 + height=1.0 / number_of_groups + vstart=group_index * height + printf("%% group %d of %d:\n0 %g 1 %g dg\n", + group_index, number_of_groups, vstart, vstart + height) + printf("(Group %s) 1.02 %g textxy\n", group_index, vstart) + next +} +$1 == "FB" && NF == 2 { + pos = index($2, "-") + printf("%% hole %s\n%g %g %g %g fb\n", + $2, substr($2, 0, pos) / blocks_per_group, vstart, + (substr($2, pos + 1) + 1) / blocks_per_group, vstart + height) + next +} +END { + printf("%%%%EOF\n") +} +' >$AWKFILE +awk -f $AWKFILE $TEMPFILE diff --git a/contrib/dir2fs b/contrib/dir2fs new file mode 100755 index 0000000..abcecb3 --- /dev/null +++ b/contrib/dir2fs @@ -0,0 +1,66 @@ +#!/bin/sh + +dir="$1" +dev="$2" + +if [ "$1" = "--help" ] || [ ! -d "${dir}" ]; then + echo "Usage: $0 dir [mke2fs args] dev" + exit 1 +fi + +shift + +# Goal: Put all the files at the beginning (which mke2fs does) and minimize +# the number of free inodes given the minimum number of blocks required. +# Hence all this math to get the inode ratio just right. + +bytes="$(du -ks "${dir}" | awk '{print $1}')" +bytes="$((bytes * 1024))" +inodes="$(find "${dir}" -print0 | xargs -0 stat -c '%i' | sort -g | uniq | wc -l)" +block_sz=4096 +inode_sz=256 +sb_overhead=4096 +blocks_per_group="$((block_sz * 8))" +bytes_per_group="$((blocks_per_group * block_sz))" +inode_bytes="$((inodes * inode_sz))" + +# Estimate overhead with the minimum number of groups... +nr_groups="$(( (bytes + inode_bytes + bytes_per_group - 1) / bytes_per_group))" +inode_bytes_per_group="$((inode_bytes / nr_groups))" +inode_blocks_per_group="$(( (inode_bytes_per_group + (block_sz - 1)) / block_sz ))" +per_grp_overhead="$(( ((3 + inode_blocks_per_group) * block_sz) + 64 ))" +overhead="$(( sb_overhead + (per_grp_overhead * nr_groups) ))" +used_bytes="$((bytes + overhead))" + +# Then do it again with the real number of groups. +nr_groups="$(( (used_bytes + (bytes_per_group - 1)) / bytes_per_group))" +tot_blocks="$((nr_groups * blocks_per_group))" +tot_bytes="$((tot_blocks * block_sz))" + +ratio="$((bytes / inodes))" +mkfs_blocks="$((tot_blocks * 4 / 3))" + +mke2fs -i "${ratio}" -T ext4 -d "${dir}" -O ^resize_inode,sparse_super2,metadata_csum,64bit,^has_journal -E packed_meta_blocks=1,num_backup_sb=0 -b "${block_sz}" -I "${inodesz}" -F "${dev}" "${mkfs_blocks}" || exit + +e2fsck -fyD "${dev}" + +blocks="$(dumpe2fs -h "${dev}" 2>&1 | grep 'Block count:' | awk '{print $3}')" +while resize2fs -f -M "${dev}"; do + new_blocks="$(dumpe2fs -h "${dev}" 2>&1 | grep 'Block count:' | awk '{print $3}')" + if [ "${new_blocks}" -eq "${blocks}" ]; then + break; + fi + blocks="${new_blocks}" +done + +if [ ! -b "${dev}" ]; then + truncate -s "$((blocks * block_sz))" "${dev}" || (e2image -ar "${dev}" "${dev}.min"; mv "${dev}.min" "${dev}") +fi + +e2fsck -fy "${dev}" + +dir_blocks="$((bytes / block_sz))" +overhead="$((blocks - dir_blocks))" +echo "Minimized image overhead: $((100 * overhead / dir_blocks))%" + +exit 0 diff --git a/contrib/e2croncheck b/contrib/e2croncheck new file mode 100755 index 0000000..de0b41f --- /dev/null +++ b/contrib/e2croncheck @@ -0,0 +1,47 @@ +#!/bin/sh +# +# e2croncheck -- run e2fsck automatically out of /etc/cron.weekly +# +# This script is intended to be run by the system administrator +# periodically from the command line, or to be run once a week +# or so by the cron daemon to check a mounted filesystem (normally +# the root filesystem, but it could be used to check other filesystems +# that are always mounted when the system is booted). +# +# Make sure you customize "VG" so it is your LVM volume group name, +# "VOLUME" so it is the name of the filesystem's logical volume, +# and "EMAIL" to be your e-mail address +# +# Written by Theodore Ts'o, Copyright 2007, 2008, 2009. +# +# This file may be redistributed under the terms of the +# GNU Public License, version 2. +# + +VG=ssd +VOLUME=root +SNAPSIZE=100m +EMAIL=sysadmin@example.com + +TMPFILE=`mktemp ${TMPDIR:-/tmp}/e2fsck.log.XXXXXXXXXX` + +OPTS="-Fttv -C0" +#OPTS="-Fttv -E fragcheck" + +set -e +START="$(date +'%Y%m%d%H%M%S')" +lvcreate -s -L ${SNAPSIZE} -n "${VOLUME}-snap" "${VG}/${VOLUME}" +if nice logsave -as $TMPFILE e2fsck -p $OPTS "/dev/${VG}/${VOLUME}-snap" && \ + nice logsave -as $TMPFILE e2fsck -fy $OPTS "/dev/${VG}/${VOLUME}-snap" ; then + echo 'Background scrubbing succeeded!' + tune2fs -C 0 -T "${START}" "/dev/${VG}/${VOLUME}" +else + echo 'Background scrubbing failed! Reboot to fsck soon!' + tune2fs -C 16000 -T "19000101" "/dev/${VG}/${VOLUME}" + if test -n "$RPT-EMAIL"; then + mail -s "E2fsck of /dev/${VG}/${VOLUME} failed!" $EMAIL < $TMPFILE + fi +fi +lvremove -f "${VG}/${VOLUME}-snap" +rm $TMPFILE + diff --git a/contrib/ext4-ioc.c b/contrib/ext4-ioc.c new file mode 100644 index 0000000..42f022d --- /dev/null +++ b/contrib/ext4-ioc.c @@ -0,0 +1,98 @@ +/* + * Test program to trigger various ext4 ioctl's + */ + +#include +#include +#include +#include +#include +#include + +#if (!defined(EXT4_IOC_ALLOC_DA_BLKS) && defined(__linux__)) +#define EXT4_IOC_ALLOC_DA_BLKS _IO('f', 12) +#endif + +#if (!defined(EXT4_IOC_SWAP_BOOT) && defined(__linux__)) +#define EXT4_IOC_SWAP_BOOT _IO('f', 17) +#endif + +#if (!defined(EXT4_IOC_PRECACHE_EXTENTS) && defined(__linux__)) +#define EXT4_IOC_PRECACHE_EXTENTS _IO('f', 18) +#endif + +#if (!defined(EXT4_IOC_CLEAR_ES_CACHE) && defined(__linux__)) +#define EXT4_IOC_CLEAR_ES_CACHE _IO('f', 40) +#endif + + +#define EXT4_F_RW 0x0001 + +struct cmd { + const char *cmd; + unsigned long ioc; + int flags; +}; + +struct cmd cmds[] = { + { "alloc_da_blks", EXT4_IOC_ALLOC_DA_BLKS, EXT4_F_RW }, + { "precache", EXT4_IOC_PRECACHE_EXTENTS, 0 }, + { "swap_boot", EXT4_IOC_SWAP_BOOT, EXT4_F_RW }, + { "clear_es_cache", EXT4_IOC_CLEAR_ES_CACHE, EXT4_F_RW }, + { NULL, 0 } +}; + +const char *progname; + +void usage() +{ + struct cmd *p; + + fprintf(stderr, "Usage: %s \n\n", progname); + fprintf(stderr, "Available commands:\n"); + for (p = cmds; p->cmd; p++) { + fprintf(stderr, "\t%s\n", p->cmd); + } + exit(1); +} + +int do_single_cmd(const char *fn, struct cmd *p) +{ + int fd; + int oflags = O_RDONLY; + + if (p->flags & EXT4_F_RW) + oflags = O_RDWR; + fd = open(fn, oflags, 0); + if (fd < 0) { + perror("open"); + return 1; + } + if (ioctl(fd, p->ioc) < 0) { + perror("ioctl"); + return 1; + } + close(fd); + return 0; +} + +int main(int argc, char **argv) +{ + int i, fails = 0; + struct cmd *p; + + progname = argv[0]; + if (argc < 3 || strcmp(argv[1], "help") == 0) + usage(); + for (p = cmds; p->cmd; p++) { + if (strcmp(argv[1], p->cmd) == 0) + break; + } + if (p->cmd == NULL) { + fprintf(stderr, "Invalid command: %s\n", argv[1]); + usage(); + } + for (i = 2; i < argc; i++) + fails += do_single_cmd(argv[i], p); + return fails; +} diff --git a/contrib/fallocate.c b/contrib/fallocate.c new file mode 100644 index 0000000..16c08ab --- /dev/null +++ b/contrib/fallocate.c @@ -0,0 +1,190 @@ +/* + * fallocate - utility to use the fallocate system call + * + * Copyright (C) 2008 Red Hat, Inc. All rights reserved. + * Written by Eric Sandeen + * + * cvtnum routine taken from xfsprogs, + * Copyright (c) 2003-2005 Silicon Graphics, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE +#endif +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +// #include +#define FALLOC_FL_KEEP_SIZE 0x01 +#define FALLOC_FL_PUNCH_HOLE 0x02 /* de-allocates range */ +#define FALLOC_FL_COLLAPSE_RANGE 0x08 +#define FALLOC_FL_ZERO_RANGE 0x10 + +void usage(void) +{ + printf("Usage: fallocate [-npt] [-o offset] -l length filename\n"); + exit(EXIT_FAILURE); +} + +#define EXABYTES(x) ((long long)(x) << 60) +#define PETABYTES(x) ((long long)(x) << 50) +#define TERABYTES(x) ((long long)(x) << 40) +#define GIGABYTES(x) ((long long)(x) << 30) +#define MEGABYTES(x) ((long long)(x) << 20) +#define KILOBYTES(x) ((long long)(x) << 10) + +long long +cvtnum(char *s) +{ + long long i; + char *sp; + int c; + + i = strtoll(s, &sp, 0); + if (i == 0 && sp == s) + return -1LL; + if (*sp == '\0') + return i; + if (sp[1] != '\0') + return -1LL; + + c = tolower(*sp); + switch (c) { + case 'k': + return KILOBYTES(i); + case 'm': + return MEGABYTES(i); + case 'g': + return GIGABYTES(i); + case 't': + return TERABYTES(i); + case 'p': + return PETABYTES(i); + case 'e': + return EXABYTES(i); + } + + return -1LL; +} + +int main(int argc, char **argv) +{ + int fd; + char *fname; + int opt; + ext2_loff_t length = -2LL; + ext2_loff_t offset = 0; + int falloc_mode = 0; + int error; + int tflag = 0; + + while ((opt = getopt(argc, argv, "npl:o:tzc")) != -1) { + switch(opt) { + case 'n': + /* do not change filesize */ + falloc_mode = FALLOC_FL_KEEP_SIZE; + break; + case 'p': + /* punch mode */ + falloc_mode = (FALLOC_FL_PUNCH_HOLE | + FALLOC_FL_KEEP_SIZE); + break; + case 'c': + /* collapse range mode */ + falloc_mode = (FALLOC_FL_COLLAPSE_RANGE | + FALLOC_FL_KEEP_SIZE); + break; + case 'z': + /* zero range mode */ + falloc_mode = (FALLOC_FL_ZERO_RANGE | + FALLOC_FL_KEEP_SIZE); + break; + case 'l': + length = cvtnum(optarg); + break; + case 'o': + offset = cvtnum(optarg); + break; + case 't': + tflag++; + break; + default: + usage(); + } + } + + if (length == -2LL) { + printf("Error: no length argument specified\n"); + usage(); + } + + if (length <= 0) { + printf("Error: invalid length value specified\n"); + usage(); + } + + if (offset < 0) { + printf("Error: invalid offset value specified\n"); + usage(); + } + + if (tflag && (falloc_mode & FALLOC_FL_KEEP_SIZE)) { + printf("-n and -t options incompatible\n"); + usage(); + } + + if (tflag && offset) { + printf("-n and -o options incompatible\n"); + usage(); + } + + if (optind == argc) { + printf("Error: no filename specified\n"); + usage(); + } + + fname = argv[optind++]; + + /* Should we create the file if it doesn't already exist? */ + fd = open(fname, O_WRONLY|O_LARGEFILE); + if (fd < 0) { + perror("Error opening file"); + exit(EXIT_FAILURE); + } + + if (tflag) + error = ftruncate(fd, length); + else + error = syscall(SYS_fallocate, fd, falloc_mode, offset, length); + + if (error < 0) { + perror("fallocate failed"); + exit(EXIT_FAILURE); + } + + close(fd); + return 0; +} diff --git a/contrib/fsstress.c b/contrib/fsstress.c new file mode 100644 index 0000000..2136a90 --- /dev/null +++ b/contrib/fsstress.c @@ -0,0 +1,2708 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ + */ + +#define NO_XFS +#define HAVE_SYS_PRCTL_H +#define _LARGEFILE64_SOURCE + +#define MAXNAMELEN 1024 +struct dioattr { + int d_miniosz, d_maxiosz, d_mem; +}; + +#define MIN(a,b) ((a)<(b) ? (a):(b)) +#define MAX(a,b) ((a)>(b) ? (a):(b)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef O_DIRECT +#define O_DIRECT 040000 +#endif + +#ifdef HAVE_SYS_PRCTL_H +# include +#endif + +#define XFS_ERRTAG_MAX 17 + +typedef enum { +#ifndef NO_XFS + OP_ALLOCSP, + OP_ATTR_REMOVE, + OP_ATTR_SET, + OP_BULKSTAT, + OP_BULKSTAT1, +#endif + OP_CHOWN, + OP_CREAT, + OP_DREAD, + OP_DWRITE, + OP_FDATASYNC, +#ifndef NO_XFS + OP_FREESP, +#endif + OP_FSYNC, + OP_GETDENTS, + OP_LINK, + OP_MKDIR, + OP_MKNOD, + OP_READ, + OP_READLINK, + OP_RENAME, +#ifndef NO_XFS + OP_RESVSP, +#endif + OP_RMDIR, + OP_STAT, + OP_SYMLINK, + OP_SYNC, + OP_TRUNCATE, + OP_UNLINK, +#ifndef NO_XFS + OP_UNRESVSP, +#endif + OP_WRITE, + OP_LAST +} opty_t; + +typedef void (*opfnc_t) (int, long); + +typedef struct opdesc { + opty_t op; + char *name; + opfnc_t func; + int freq; + int iswrite; + int isxfs; +} opdesc_t; + +typedef struct fent { + int id; + int parent; +} fent_t; + +typedef struct flist { + int nfiles; + int nslots; + int tag; + fent_t *fents; +} flist_t; + +typedef struct pathname { + int len; + char *path; +} pathname_t; + +#define FT_DIR 0 +#define FT_DIRm (1 << FT_DIR) +#define FT_REG 1 +#define FT_REGm (1 << FT_REG) +#define FT_SYM 2 +#define FT_SYMm (1 << FT_SYM) +#define FT_DEV 3 +#define FT_DEVm (1 << FT_DEV) +#define FT_RTF 4 +#define FT_RTFm (1 << FT_RTF) +#define FT_nft 5 +#define FT_ANYm ((1 << FT_nft) - 1) +#define FT_REGFILE (FT_REGm | FT_RTFm) +#define FT_NOTDIR (FT_ANYm & ~FT_DIRm) + +#define FLIST_SLOT_INCR 16 +#define NDCACHE 64 + +#define MAXFSIZE ((1ULL << 63) - 1ULL) +#define MAXFSIZE32 ((1ULL << 40) - 1ULL) + +void allocsp_f(int, long); +void attr_remove_f(int, long); +void attr_set_f(int, long); +void bulkstat_f(int, long); +void bulkstat1_f(int, long); +void chown_f(int, long); +void creat_f(int, long); +void dread_f(int, long); +void dwrite_f(int, long); +void fdatasync_f(int, long); +void freesp_f(int, long); +void fsync_f(int, long); +void getdents_f(int, long); +void link_f(int, long); +void mkdir_f(int, long); +void mknod_f(int, long); +void read_f(int, long); +void readlink_f(int, long); +void rename_f(int, long); +void resvsp_f(int, long); +void rmdir_f(int, long); +void stat_f(int, long); +void symlink_f(int, long); +void sync_f(int, long); +void truncate_f(int, long); +void unlink_f(int, long); +void unresvsp_f(int, long); +void write_f(int, long); + +opdesc_t ops[] = { +#ifndef NO_XFS + {OP_ALLOCSP, "allocsp", allocsp_f, 1, 1, 1}, + {OP_ATTR_REMOVE, "attr_remove", attr_remove_f, /* 1 */ 0, 1, 1}, + {OP_ATTR_SET, "attr_set", attr_set_f, /* 2 */ 0, 1, 1}, + {OP_BULKSTAT, "bulkstat", bulkstat_f, 1, 0, 1}, + {OP_BULKSTAT1, "bulkstat1", bulkstat1_f, 1, 0, 1}, +#endif + {OP_CHOWN, "chown", chown_f, 3, 1, 0}, + {OP_CREAT, "creat", creat_f, 4, 1, 0}, + {OP_DREAD, "dread", dread_f, 4, 0, 0}, + {OP_DWRITE, "dwrite", dwrite_f, 4, 1, 0}, + {OP_FDATASYNC, "fdatasync", fdatasync_f, 1, 1, 0}, +#ifndef NO_XFS + {OP_FREESP, "freesp", freesp_f, 1, 1, 1}, +#endif + {OP_FSYNC, "fsync", fsync_f, 1, 1, 0}, + {OP_GETDENTS, "getdents", getdents_f, 1, 0, 0}, + {OP_LINK, "link", link_f, 1, 1, 0}, + {OP_MKDIR, "mkdir", mkdir_f, 2, 1, 0}, + {OP_MKNOD, "mknod", mknod_f, 2, 1, 0}, + {OP_READ, "read", read_f, 1, 0, 0}, + {OP_READLINK, "readlink", readlink_f, 1, 0, 0}, + {OP_RENAME, "rename", rename_f, 2, 1, 0}, +#ifndef NO_XFS + {OP_RESVSP, "resvsp", resvsp_f, 1, 1, 1}, +#endif + {OP_RMDIR, "rmdir", rmdir_f, 1, 1, 0}, + {OP_STAT, "stat", stat_f, 1, 0, 0}, + {OP_SYMLINK, "symlink", symlink_f, 2, 1, 0}, + {OP_SYNC, "sync", sync_f, 1, 0, 0}, + {OP_TRUNCATE, "truncate", truncate_f, 2, 1, 0}, + {OP_UNLINK, "unlink", unlink_f, 1, 1, 0}, +#ifndef NO_XFS + {OP_UNRESVSP, "unresvsp", unresvsp_f, 1, 1, 1}, +#endif + {OP_WRITE, "write", write_f, 4, 1, 0}, +}, *ops_end; + +flist_t flist[FT_nft] = { + {0, 0, 'd', NULL}, + {0, 0, 'f', NULL}, + {0, 0, 'l', NULL}, + {0, 0, 'c', NULL}, + {0, 0, 'r', NULL}, +}; + +int dcache[NDCACHE]; +int errrange; +int errtag; +opty_t *freq_table; +int freq_table_size; +#ifndef NO_XFS +xfs_fsop_geom_t geom; +#endif +char *homedir; +int *ilist; +int ilistlen; +off64_t maxfsize; +char *myprog; +int namerand; +int nameseq; +int nops; +int nproc = 1; +int operations = 1; +int procid; +int rtpct; +unsigned long seed = 0; +ino_t top_ino; +int verbose = 0; +#ifndef NO_XFS +int no_xfs = 0; +#else +int no_xfs = 1; +#endif +sig_atomic_t should_stop = 0; + +void add_to_flist(int, int, int); +void append_pathname(pathname_t *, char *); +#ifndef NO_XFS +int attr_list_path(pathname_t *, char *, const int, int, attrlist_cursor_t *); +int attr_remove_path(pathname_t *, const char *, int); +int attr_set_path(pathname_t *, const char *, const char *, const int, int); +#endif +void check_cwd(void); +int creat_path(pathname_t *, mode_t); +void dcache_enter(int, int); +void dcache_init(void); +fent_t *dcache_lookup(int); +void dcache_purge(int); +void del_from_flist(int, int); +int dirid_to_name(char *, int); +void doproc(void); +void fent_to_name(pathname_t *, flist_t *, fent_t *); +void fix_parent(int, int); +void free_pathname(pathname_t *); +int generate_fname(fent_t *, int, pathname_t *, int *, int *); +int get_fname(int, long, pathname_t *, flist_t **, fent_t **, int *); +void init_pathname(pathname_t *); +int lchown_path(pathname_t *, uid_t, gid_t); +int link_path(pathname_t *, pathname_t *); +int lstat64_path(pathname_t *, struct stat64 *); +void make_freq_table(void); +int mkdir_path(pathname_t *, mode_t); +int mknod_path(pathname_t *, mode_t, dev_t); +void namerandpad(int, char *, int); +int open_path(pathname_t *, int); +DIR *opendir_path(pathname_t *); +void process_freq(char *); +int readlink_path(pathname_t *, char *, size_t); +int rename_path(pathname_t *, pathname_t *); +int rmdir_path(pathname_t *); +void separate_pathname(pathname_t *, char *, pathname_t *); +void show_ops(int, char *); +int stat64_path(pathname_t *, struct stat64 *); +int symlink_path(const char *, pathname_t *); +int truncate64_path(pathname_t *, off64_t); +int unlink_path(pathname_t *); +void usage(void); +void write_freq(void); +void zero_freq(void); + +void sg_handler(int signum) +{ + should_stop = 1; +} + +int main(int argc, char **argv) +{ + char buf[10]; + int c; + char *dirname = NULL; + int fd; + int i; + int cleanup = 0; + int loops = 1; + int loopcntr = 1; + char cmd[256]; +#ifndef NO_XFS + int j; +#endif + char *p; + int stat; + struct timeval t; +#ifndef NO_XFS + ptrdiff_t srval; +#endif + int nousage = 0; +#ifndef NO_XFS + xfs_error_injection_t err_inj; +#endif + struct sigaction action; + + errrange = errtag = 0; + umask(0); + nops = sizeof(ops) / sizeof(ops[0]); + ops_end = &ops[nops]; + myprog = argv[0]; + while ((c = getopt(argc, argv, "cd:e:f:i:l:n:p:rs:vwzHSX")) != -1) { + switch (c) { + case 'c': + /*Don't cleanup */ + cleanup = 1; + break; + case 'd': + dirname = optarg; + break; + case 'e': + sscanf(optarg, "%d", &errtag); + if (errtag < 0) { + errtag = -errtag; + errrange = 1; + } else if (errtag == 0) + errtag = -1; + if (errtag >= XFS_ERRTAG_MAX) { + fprintf(stderr, + "error tag %d too large (max %d)\n", + errtag, XFS_ERRTAG_MAX - 1); + exit(1); + } + break; + case 'f': + process_freq(optarg); + break; + case 'i': + ilist = realloc(ilist, ++ilistlen * sizeof(*ilist)); + ilist[ilistlen - 1] = strtol(optarg, &p, 16); + break; + case 'l': + loops = atoi(optarg); + break; + case 'n': + operations = atoi(optarg); + break; + case 'p': + nproc = atoi(optarg); + break; + case 'r': + namerand = 1; + break; + case 's': + seed = strtoul(optarg, NULL, 0); + break; + case 'v': + verbose = 1; + break; + case 'w': + write_freq(); + break; + case 'z': + zero_freq(); + break; + case 'S': + show_ops(0, NULL); + printf("\n"); + nousage = 1; + break; + case '?': + fprintf(stderr, "%s - invalid parameters\n", myprog); + /* fall through */ + case 'H': + usage(); + exit(1); + case 'X': + no_xfs = 1; + break; + } + } + + if (no_xfs && errtag) { + fprintf(stderr, "error injection only works on XFS\n"); + exit(1); + } + + if (no_xfs) { + int i; + for (i = 0; ops + i < ops_end; ++i) { + if (ops[i].isxfs) + ops[i].freq = 0; + } + } + + make_freq_table(); + + while (((loopcntr <= loops) || (loops == 0)) && !should_stop) { + if (!dirname) { + /* no directory specified */ + if (!nousage) + usage(); + exit(1); + } + + (void)mkdir(dirname, 0777); + if (chdir(dirname) < 0) { + perror(dirname); + exit(1); + } + sprintf(buf, "fss%x", getpid()); + fd = creat(buf, 0666); + if (lseek64(fd, (off64_t) (MAXFSIZE32 + 1ULL), SEEK_SET) < 0) + maxfsize = (off64_t) MAXFSIZE32; + else + maxfsize = (off64_t) MAXFSIZE; + dcache_init(); + setlinebuf(stdout); + if (!seed) { + gettimeofday(&t, NULL); + seed = (int)t.tv_sec ^ (int)t.tv_usec; + printf("seed = %ld\n", seed); + } +#ifndef NO_XFS + if (!no_xfs) { + memset(&geom, 0, sizeof(geom)); + i = ioctl(fd, XFS_IOC_FSGEOMETRY, &geom); + if (i >= 0 && geom.rtblocks) + rtpct = MIN(MAX(geom.rtblocks * 100 / + (geom.rtblocks + + geom.datablocks), 1), 99); + else + rtpct = 0; + } + if (errtag != 0) { + if (errrange == 0) { + if (errtag <= 0) { + srandom(seed); + j = random() % 100; + + for (i = 0; i < j; i++) + (void)random(); + + errtag = + (random() % (XFS_ERRTAG_MAX - 1)) + + 1; + } + } else { + srandom(seed); + j = random() % 100; + + for (i = 0; i < j; i++) + (void)random(); + + errtag += + (random() % (XFS_ERRTAG_MAX - errtag)); + } + printf("Injecting failure on tag #%d\n", errtag); + memset(&err_inj, 0, sizeof(err_inj)); + err_inj.errtag = errtag; + err_inj.fd = fd; + srval = ioctl(fd, XFS_IOC_ERROR_INJECTION, &err_inj); + if (srval < -1) { + perror + ("fsstress - XFS_SYSSGI error injection call"); + close(fd); + unlink(buf); + exit(1); + } + } else +#endif + close(fd); + unlink(buf); + + + if (nproc == 1) { + procid = 0; + doproc(); + } else { + setpgid(0, 0); + action.sa_handler = sg_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + if (sigaction(SIGTERM, &action, 0)) { + perror("sigaction failed"); + exit(1); + } + + for (i = 0; i < nproc; i++) { + if (fork() == 0) { + + action.sa_handler = SIG_DFL; + sigemptyset(&action.sa_mask); + if (sigaction(SIGTERM, &action, 0)) + return 1; +#ifdef HAVE_SYS_PRCTL_H + prctl(PR_SET_PDEATHSIG, SIGKILL); + if (getppid() == 1) /* parent died already? */ + return 0; +#endif + procid = i; + doproc(); + return 0; + } + } + while (wait(&stat) > 0 && !should_stop) { + continue; + } + if (should_stop) { + action.sa_flags = SA_RESTART; + sigaction(SIGTERM, &action, 0); + kill(-getpid(), SIGTERM); + while (wait(&stat) > 0) + continue; + } + } +#ifndef NO_XFS + if (errtag != 0) { + memset(&err_inj, 0, sizeof(err_inj)); + err_inj.errtag = 0; + err_inj.fd = fd; + if ((srval = + ioctl(fd, XFS_IOC_ERROR_CLEARALL, + &err_inj)) != 0) { + fprintf(stderr, "Bad ej clear on %d (%d).\n", + fd, errno); + perror + ("fsstress - XFS_SYSSGI clear error injection call"); + close(fd); + exit(1); + } + close(fd); + } +#endif + if (cleanup == 0) { + sprintf(cmd, "rm -rf %s/*", dirname); + system(cmd); + for (i = 0; i < FT_nft; i++) { + flist[i].nslots = 0; + flist[i].nfiles = 0; + free(flist[i].fents); + flist[i].fents = NULL; + } + } + loopcntr++; + } + return 0; +} + +void add_to_flist(int ft, int id, int parent) +{ + fent_t *fep; + flist_t *ftp; + + ftp = &flist[ft]; + if (ftp->nfiles == ftp->nslots) { + ftp->nslots += FLIST_SLOT_INCR; + ftp->fents = realloc(ftp->fents, ftp->nslots * sizeof(fent_t)); + } + fep = &ftp->fents[ftp->nfiles++]; + fep->id = id; + fep->parent = parent; +} + +void append_pathname(pathname_t * name, char *str) +{ + int len; + char *path; + + len = strlen(str); +#ifdef DEBUG + if (len && *str == '/' && name->len == 0) { + fprintf(stderr, "fsstress: append_pathname failure\n"); + chdir(homedir); + abort(); + + } +#endif + path = realloc(name->path, name->len + 1 + len); + if (path == NULL) { + fprintf(stderr, "fsstress: append_pathname realloc failed\n"); + chdir(homedir); + abort(); + } + name->path = path; + strcpy(&name->path[name->len], str); + name->len += len; +} + +#ifndef NO_XFS +int +attr_list_path(pathname_t * name, char *buffer, const int buffersize, int flags, + attrlist_cursor_t * cursor) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = attr_list(name->path, buffer, buffersize, flags, cursor); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = attr_list_path(&newname, buffer, buffersize, flags, + cursor); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int attr_remove_path(pathname_t * name, const char *attrname, int flags) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = attr_remove(name->path, attrname, flags); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = attr_remove_path(&newname, attrname, flags); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int +attr_set_path(pathname_t * name, const char *attrname, const char *attrvalue, + const int valuelength, int flags) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = attr_set(name->path, attrname, attrvalue, valuelength, flags); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = attr_set_path(&newname, attrname, attrvalue, valuelength, + flags); + chdir(".."); + } + free_pathname(&newname); + return rval; +} +#endif + +void check_cwd(void) +{ +#ifdef DEBUG + struct stat64 statbuf; + + if (stat64(".", &statbuf) == 0 && statbuf.st_ino == top_ino) + return; + chdir(homedir); + fprintf(stderr, "fsstress: check_cwd failure\n"); + abort(); + +#endif +} + +int creat_path(pathname_t * name, mode_t mode) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = creat(name->path, mode); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = creat_path(&newname, mode); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void dcache_enter(int dirid, int slot) +{ + dcache[dirid % NDCACHE] = slot; +} + +void dcache_init(void) +{ + int i; + + for (i = 0; i < NDCACHE; i++) + dcache[i] = -1; +} + +fent_t *dcache_lookup(int dirid) +{ + fent_t *fep; + int i; + + i = dcache[dirid % NDCACHE]; + if (i >= 0 && (fep = &flist[FT_DIR].fents[i])->id == dirid) + return fep; + return NULL; +} + +void dcache_purge(int dirid) +{ + int *dcp; + + dcp = &dcache[dirid % NDCACHE]; + if (*dcp >= 0 && flist[FT_DIR].fents[*dcp].id == dirid) + *dcp = -1; +} + +void del_from_flist(int ft, int slot) +{ + flist_t *ftp; + + ftp = &flist[ft]; + if (ft == FT_DIR) + dcache_purge(ftp->fents[slot].id); + if (slot != ftp->nfiles - 1) { + if (ft == FT_DIR) + dcache_purge(ftp->fents[ftp->nfiles - 1].id); + ftp->fents[slot] = ftp->fents[--ftp->nfiles]; + } else + ftp->nfiles--; +} + +fent_t *dirid_to_fent(int dirid) +{ + fent_t *efep; + fent_t *fep; + flist_t *flp; + + if ((fep = dcache_lookup(dirid))) + return fep; + flp = &flist[FT_DIR]; + for (fep = flp->fents, efep = &fep[flp->nfiles]; fep < efep; fep++) { + if (fep->id == dirid) { + dcache_enter(dirid, fep - flp->fents); + return fep; + } + } + return NULL; +} + +void doproc(void) +{ + struct stat64 statbuf; + char buf[10]; + int opno; + int rval; + opdesc_t *p; + + sprintf(buf, "p%x", procid); + (void)mkdir(buf, 0777); + if (chdir(buf) < 0 || stat64(".", &statbuf) < 0) { + perror(buf); + _exit(1); + } + top_ino = statbuf.st_ino; + homedir = getcwd(NULL, -1); + seed += procid; + srandom(seed); + if (namerand) + namerand = random(); + for (opno = 0; opno < operations; opno++) { + p = &ops[freq_table[random() % freq_table_size]]; + if ((unsigned long)p->func < 4096) + abort(); + + p->func(opno, random()); + /* + * test for forced shutdown by stat'ing the test + * directory. If this stat returns EIO, assume + * the forced shutdown happened. + */ + if (errtag != 0 && opno % 100 == 0) { + rval = stat64(".", &statbuf); + if (rval == EIO) { + fprintf(stderr, "Detected EIO\n"); + return; + } + } + } +} + +void fent_to_name(pathname_t * name, flist_t * flp, fent_t * fep) +{ + char buf[MAXNAMELEN]; + int i; + fent_t *pfep; + + if (fep == NULL) + return; + if (fep->parent != -1) { + pfep = dirid_to_fent(fep->parent); + fent_to_name(name, &flist[FT_DIR], pfep); + append_pathname(name, "/"); + } + i = sprintf(buf, "%c%x", flp->tag, fep->id); + namerandpad(fep->id, buf, i); + append_pathname(name, buf); +} + +void fix_parent(int oldid, int newid) +{ + fent_t *fep; + flist_t *flp; + int i; + int j; + + for (i = 0, flp = flist; i < FT_nft; i++, flp++) { + for (j = 0, fep = flp->fents; j < flp->nfiles; j++, fep++) { + if (fep->parent == oldid) + fep->parent = newid; + } + } +} + +void free_pathname(pathname_t * name) +{ + if (name->path) { + free(name->path); + name->path = NULL; + name->len = 0; + } +} + +int generate_fname(fent_t * fep, int ft, pathname_t * name, int *idp, int *v) +{ + char buf[MAXNAMELEN]; + flist_t *flp; + int id; + int j; + int len; + + flp = &flist[ft]; + len = sprintf(buf, "%c%x", flp->tag, id = nameseq++); + namerandpad(id, buf, len); + if (fep) { + fent_to_name(name, &flist[FT_DIR], fep); + append_pathname(name, "/"); + } + append_pathname(name, buf); + *idp = id; + *v = verbose; + for (j = 0; !*v && j < ilistlen; j++) { + if (ilist[j] == id) { + *v = 1; + break; + } + } + return 1; +} + +int +get_fname(int which, long r, pathname_t * name, flist_t ** flpp, fent_t ** fepp, + int *v) +{ + int c; + fent_t *fep; + flist_t *flp; + int i; + int j; + int x; + + for (i = 0, c = 0, flp = flist; i < FT_nft; i++, flp++) { + if (which & (1 << i)) + c += flp->nfiles; + } + if (c == 0) { + if (flpp) + *flpp = NULL; + if (fepp) + *fepp = NULL; + *v = verbose; + return 0; + } + x = (int)(r % c); + for (i = 0, c = 0, flp = flist; i < FT_nft; i++, flp++) { + if (which & (1 << i)) { + if (x < c + flp->nfiles) { + fep = &flp->fents[x - c]; + if (name) + fent_to_name(name, flp, fep); + if (flpp) + *flpp = flp; + if (fepp) + *fepp = fep; + *v = verbose; + for (j = 0; !*v && j < ilistlen; j++) { + if (ilist[j] == fep->id) { + *v = 1; + break; + } + } + return 1; + } + c += flp->nfiles; + } + } +#ifdef DEBUG + fprintf(stderr, "fsstress: get_fname failure\n"); + abort(); +#endif + return -1; + +} + +void init_pathname(pathname_t * name) +{ + name->len = 0; + name->path = NULL; +} + +int lchown_path(pathname_t * name, uid_t owner, gid_t group) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = lchown(name->path, owner, group); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = lchown_path(&newname, owner, group); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int link_path(pathname_t * name1, pathname_t * name2) +{ + char buf1[MAXNAMELEN]; + char buf2[MAXNAMELEN]; + int down1; + pathname_t newname1; + pathname_t newname2; + int rval; + + rval = link(name1->path, name2->path); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name1, buf1, &newname1); + separate_pathname(name2, buf2, &newname2); + if (strcmp(buf1, buf2) == 0) { + if (chdir(buf1) == 0) { + rval = link_path(&newname1, &newname2); + chdir(".."); + } + } else { + if (strcmp(buf1, "..") == 0) + down1 = 0; + else if (strcmp(buf2, "..") == 0) + down1 = 1; + else if (strlen(buf1) == 0) + down1 = 0; + else if (strlen(buf2) == 0) + down1 = 1; + else + down1 = MAX(newname1.len, 3 + name2->len) <= + MAX(3 + name1->len, newname2.len); + if (down1) { + free_pathname(&newname2); + append_pathname(&newname2, "../"); + append_pathname(&newname2, name2->path); + if (chdir(buf1) == 0) { + rval = link_path(&newname1, &newname2); + chdir(".."); + } + } else { + free_pathname(&newname1); + append_pathname(&newname1, "../"); + append_pathname(&newname1, name1->path); + if (chdir(buf2) == 0) { + rval = link_path(&newname1, &newname2); + chdir(".."); + } + } + } + free_pathname(&newname1); + free_pathname(&newname2); + return rval; +} + +int lstat64_path(pathname_t * name, struct stat64 *sbuf) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = lstat64(name->path, sbuf); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = lstat64_path(&newname, sbuf); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void make_freq_table(void) +{ + int f; + int i; + opdesc_t *p; + + for (p = ops, f = 0; p < ops_end; p++) + f += p->freq; + freq_table = malloc(f * sizeof(*freq_table)); + freq_table_size = f; + for (p = ops, i = 0; p < ops_end; p++) { + for (f = 0; f < p->freq; f++, i++) + freq_table[i] = p->op; + } +} + +int mkdir_path(pathname_t * name, mode_t mode) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = mkdir(name->path, mode); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = mkdir_path(&newname, mode); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int mknod_path(pathname_t * name, mode_t mode, dev_t dev) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = mknod(name->path, mode, dev); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = mknod_path(&newname, mode, dev); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void namerandpad(int id, char *buf, int i) +{ + int bucket; + static int buckets[] = { 2, 4, 8, 16, 32, 64, 128, MAXNAMELEN - 1 }; + int padlen; + int padmod; + + if (namerand == 0) + return; + bucket = (id ^ namerand) % (sizeof(buckets) / sizeof(buckets[0])); + padmod = buckets[bucket] + 1 - i; + if (padmod <= 0) + return; + padlen = (id ^ namerand) % padmod; + if (padlen) { + memset(&buf[i], 'X', padlen); + buf[i + padlen] = '\0'; + } +} + +int open_path(pathname_t * name, int oflag) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = open(name->path, oflag); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = open_path(&newname, oflag); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +DIR *opendir_path(pathname_t * name) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + DIR *rval; + + rval = opendir(name->path); + if (rval || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = opendir_path(&newname); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void process_freq(char *arg) +{ + opdesc_t *p; + char *s; + + s = strchr(arg, '='); + if (s == NULL) { + fprintf(stderr, "bad argument '%s'\n", arg); + exit(1); + } + *s++ = '\0'; + for (p = ops; p < ops_end; p++) { + if (strcmp(arg, p->name) == 0) { + p->freq = atoi(s); + return; + } + } + fprintf(stderr, "can't find op type %s for -f\n", arg); + exit(1); +} + +int readlink_path(pathname_t * name, char *lbuf, size_t lbufsiz) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = readlink(name->path, lbuf, lbufsiz); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = readlink_path(&newname, lbuf, lbufsiz); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int rename_path(pathname_t * name1, pathname_t * name2) +{ + char buf1[MAXNAMELEN]; + char buf2[MAXNAMELEN]; + int down1; + pathname_t newname1; + pathname_t newname2; + int rval; + + rval = rename(name1->path, name2->path); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name1, buf1, &newname1); + separate_pathname(name2, buf2, &newname2); + if (strcmp(buf1, buf2) == 0) { + if (chdir(buf1) == 0) { + rval = rename_path(&newname1, &newname2); + chdir(".."); + } + } else { + if (strcmp(buf1, "..") == 0) + down1 = 0; + else if (strcmp(buf2, "..") == 0) + down1 = 1; + else if (strlen(buf1) == 0) + down1 = 0; + else if (strlen(buf2) == 0) + down1 = 1; + else + down1 = MAX(newname1.len, 3 + name2->len) <= + MAX(3 + name1->len, newname2.len); + if (down1) { + free_pathname(&newname2); + append_pathname(&newname2, "../"); + append_pathname(&newname2, name2->path); + if (chdir(buf1) == 0) { + rval = rename_path(&newname1, &newname2); + chdir(".."); + } + } else { + free_pathname(&newname1); + append_pathname(&newname1, "../"); + append_pathname(&newname1, name1->path); + if (chdir(buf2) == 0) { + rval = rename_path(&newname1, &newname2); + chdir(".."); + } + } + } + free_pathname(&newname1); + free_pathname(&newname2); + return rval; +} + +int rmdir_path(pathname_t * name) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = rmdir(name->path); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = rmdir_path(&newname); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void separate_pathname(pathname_t * name, char *buf, pathname_t * newname) +{ + char *slash; + + init_pathname(newname); + slash = strchr(name->path, '/'); + if (slash == NULL) { + buf[0] = '\0'; + return; + } + *slash = '\0'; + strcpy(buf, name->path); + *slash = '/'; + append_pathname(newname, slash + 1); +} + +#define WIDTH 80 + +void show_ops(int flag, char *lead_str) +{ + opdesc_t *p; + + if (flag < 0) { + /* print in list form */ + int x = WIDTH; + + for (p = ops; p < ops_end; p++) { + if (lead_str != NULL + && x + strlen(p->name) >= WIDTH - 5) + x = printf("%s%s", (p == ops) ? "" : "\n", + lead_str); + x += printf("%s ", p->name); + } + printf("\n"); + } else { + int f; + for (f = 0, p = ops; p < ops_end; p++) + f += p->freq; + + if (f == 0) + flag = 1; + + for (p = ops; p < ops_end; p++) { + if (flag != 0 || p->freq > 0) { + if (lead_str != NULL) + printf("%s", lead_str); + printf("%20s %d/%d %s\n", + p->name, p->freq, f, + (p->iswrite == 0) ? " " : "write op"); + } + } + } +} + +int stat64_path(pathname_t * name, struct stat64 *sbuf) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = stat64(name->path, sbuf); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = stat64_path(&newname, sbuf); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int symlink_path(const char *name1, pathname_t * name) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + if (!strcmp(name1, name->path)) { + printf("yikes! %s %s\n", name1, name->path); + return 0; + } + + rval = symlink(name1, name->path); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = symlink_path(name1, &newname); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int truncate64_path(pathname_t * name, off64_t length) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = truncate64(name->path, length); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = truncate64_path(&newname, length); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +int unlink_path(pathname_t * name) +{ + char buf[MAXNAMELEN]; + pathname_t newname; + int rval; + + rval = unlink(name->path); + if (rval >= 0 || errno != ENAMETOOLONG) + return rval; + separate_pathname(name, buf, &newname); + if (chdir(buf) == 0) { + rval = unlink_path(&newname); + chdir(".."); + } + free_pathname(&newname); + return rval; +} + +void usage(void) +{ + printf("Usage: %s -H or\n", myprog); + printf + (" %s [-c][-d dir][-e errtg][-f op_name=freq][-l loops][-n nops]\n", + myprog); + printf(" [-p nproc][-r len][-s seed][-v][-w][-z][-S]\n"); + printf("where\n"); + printf + (" -c specifies not to remove files(cleanup) after execution\n"); + printf + (" -d dir specifies the base directory for operations\n"); + printf(" -e errtg specifies error injection stuff\n"); + printf + (" -f op_name=freq changes the frequency of option name to freq\n"); + printf(" the valid operation names are:\n"); + show_ops(-1, " "); + printf + (" -l loops specifies the no. of times the testrun should loop.\n"); + printf(" *use 0 for infinite (default 1)\n"); + printf + (" -n nops specifies the no. of operations per process (default 1)\n"); + printf + (" -p nproc specifies the no. of processes (default 1)\n"); + printf(" -r specifies random name padding\n"); + printf + (" -s seed specifies the seed for the random generator (default random)\n"); + printf(" -v specifies verbose mode\n"); + printf + (" -w zeros frequencies of non-write operations\n"); + printf(" -z zeros frequencies of all operations\n"); + printf + (" -S prints the table of operations (omitting zero frequency)\n"); + printf(" -H prints usage and exits\n"); + printf + (" -X don't do anything XFS specific (default with -DNO_XFS)\n"); +} + +void write_freq(void) +{ + opdesc_t *p; + + for (p = ops; p < ops_end; p++) { + if (!p->iswrite) + p->freq = 0; + } +} + +void zero_freq(void) +{ + opdesc_t *p; + + for (p = ops; p < ops_end; p++) + p->freq = 0; +} + +#ifndef NO_XFS + +void allocsp_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + struct xfs_flock64 fl; + __s64 lr; + __s64 off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: allocsp - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDWR); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: allocsp - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: allocsp - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + lr = ((__s64) random() << 32) + random(); + off = lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE); + off %= maxfsize; + memset(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = 0; + e = ioctl(fd, XFS_IOC_ALLOCSP64, &fl) < 0 ? errno : 0; + if (v) + printf("%d/%d: ioctl(XFS_IOC_ALLOCSP64) %s %lld 0 %d\n", + procid, opno, f.path, (long long)off, e); + free_pathname(&f); + close(fd); +} + +void attr_remove_f(int opno, long r) +{ + attrlist_ent_t *aep; + attrlist_t *alist; + char *aname; + char buf[4096]; + attrlist_cursor_t cursor; + int e; + int ent; + pathname_t f; + int total; + int v; + int which; + + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, NULL, NULL, &v)) + append_pathname(&f, "."); + total = 0; + memset(&cursor, 0x00, sizeof(cursor)); + do { + e = attr_list_path(&f, buf, sizeof(buf), ATTR_DONTFOLLOW, + &cursor); + check_cwd(); + if (e) + break; + alist = (attrlist_t *) buf; + total += alist->al_count; + } while (alist->al_more); + if (total == 0) { + if (v) + printf("%d/%d: attr_remove - no attrs for %s\n", + procid, opno, f.path); + free_pathname(&f); + return; + } + which = (int)(random() % total); + memset(&cursor, 0x00, sizeof(cursor)); + ent = 0; + aname = NULL; + do { + e = attr_list_path(&f, buf, sizeof(buf), ATTR_DONTFOLLOW, + &cursor); + check_cwd(); + if (e) + break; + alist = (attrlist_t *) buf; + if (which < ent + alist->al_count) { + aep = (attrlist_ent_t *) + & buf[alist->al_offset[which - ent]]; + aname = aep->a_name; + break; + } + ent += alist->al_count; + } while (alist->al_more); + if (aname == NULL) { + if (v) + printf("%d/%d: attr_remove - name %d not found at %s\n", + procid, opno, which, f.path); + free_pathname(&f); + return; + } + e = attr_remove_path(&f, aname, ATTR_DONTFOLLOW) < 0 ? errno : 0; + check_cwd(); + if (v) + printf("%d/%d: attr_remove %s %s %d\n", + procid, opno, f.path, aname, e); + free_pathname(&f); +} + +void attr_set_f(int opno, long r) +{ + char aname[10]; + char *aval; + int e; + pathname_t f; + int len; + static int lengths[] = { 10, 100, 1000, 10000 }; + int li; + int v; + + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, NULL, NULL, &v)) + append_pathname(&f, "."); + sprintf(aname, "a%x", nameseq++); + li = (int)(random() % (sizeof(lengths) / sizeof(lengths[0]))); + len = (int)(random() % lengths[li]); + if (len == 0) + len = 1; + aval = malloc(len); + memset(aval, nameseq & 0xff, len); + e = attr_set_path(&f, aname, aval, len, ATTR_DONTFOLLOW) < 0 ? + errno : 0; + check_cwd(); + free(aval); + if (v) + printf("%d/%d: attr_set %s %s %d\n", procid, opno, f.path, + aname, e); + free_pathname(&f); +} + +void bulkstat_f(int opno, long r) +{ + __s32 count; + int fd; + __u64 last; + __s32 nent; + xfs_bstat_t *t; + __int64_t total; + xfs_fsop_bulkreq_t bsr; + + last = 0; + nent = (r % 999) + 2; + t = malloc(nent * sizeof(*t)); + fd = open(".", O_RDONLY); + total = 0; + + memset(&bsr, 0, sizeof(bsr)); + bsr.lastip = &last; + bsr.icount = nent; + bsr.ubuffer = t; + bsr.ocount = &count; + + while (ioctl(fd, XFS_IOC_FSBULKSTAT, &bsr) == 0 && count > 0) + total += count; + free(t); + if (verbose) + printf("%d/%d: bulkstat nent %d total %lld\n", + procid, opno, (int)nent, (long long)total); + close(fd); +} + +void bulkstat1_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + int good; + __u64 ino; + struct stat64 s; + xfs_bstat_t t; + int v; + xfs_fsop_bulkreq_t bsr; + + good = random() & 1; + if (good) { + /* use an inode we know exists */ + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, NULL, NULL, &v)) + append_pathname(&f, "."); + ino = stat64_path(&f, &s) < 0 ? (ino64_t) r : s.st_ino; + check_cwd(); + free_pathname(&f); + } else { + /* + * pick a random inode + * + * note this can generate kernel warning messages + * since bulkstat_one will read the disk block that + * would contain a given inode even if that disk + * block doesn't contain inodes. + * + * this is detected later, but not until after the + * warning is displayed. + * + * "XFS: device 0x825- bad inode magic/vsn daddr 0x0 #0" + * + */ + ino = (ino64_t) r; + v = verbose; + } + fd = open(".", O_RDONLY); + + memset(&bsr, 0, sizeof(bsr)); + bsr.lastip = &ino; + bsr.icount = 1; + bsr.ubuffer = &t; + bsr.ocount = NULL; + + e = ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bsr) < 0 ? errno : 0; + if (v) + printf("%d/%d: bulkstat1 %s ino %lld %d\n", + procid, opno, good ? "real" : "random", + (long long)ino, e); + close(fd); +} + +#endif + +void chown_f(int opno, long r) +{ + int e; + pathname_t f; + int nbits; + uid_t u; + int v; + + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, NULL, NULL, &v)) + append_pathname(&f, "."); + u = (uid_t) random(); + nbits = (int)(random() % 32); + u &= (1 << nbits) - 1; + e = lchown_path(&f, u, -1) < 0 ? errno : 0; + check_cwd(); + if (v) + printf("%d/%d: chown %s %d %d\n", procid, opno, f.path, u, e); + free_pathname(&f); +} + +void creat_f(int opno, long r) +{ + int e; + int e1; + int extsize; + pathname_t f; + int fd; + fent_t *fep; + int id; + int parid; + int type; + int v; + int v1; + int esz = 0; + + if (!get_fname(FT_DIRm, r, NULL, NULL, &fep, &v1)) + parid = -1; + else + parid = fep->id; + init_pathname(&f); + type = rtpct ? ((random() % 100) > rtpct ? FT_REG : FT_RTF) : FT_REG; + if (type == FT_RTF) + extsize = (random() % 10) + 1; + else + extsize = 0; + e = generate_fname(fep, type, &f, &id, &v); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&f, &flist[FT_DIR], fep); + printf("%d/%d: creat - no filename from %s\n", + procid, opno, f.path); + } + free_pathname(&f); + return; + } + fd = creat_path(&f, 0666); + e = fd < 0 ? errno : 0; + e1 = 0; + check_cwd(); + esz = 0; + if (fd >= 0) { +#ifndef NO_XFS + struct fsxattr a; + memset(&a, 0, sizeof(a)); + if (extsize && ioctl(fd, XFS_IOC_FSGETXATTR, &a) >= 0) { + a.fsx_xflags |= XFS_XFLAG_REALTIME; + a.fsx_extsize = + geom.rtextsize * geom.blocksize * extsize; + if (ioctl(fd, XFS_IOC_FSSETXATTR, &a) < 0) + e1 = errno; + esz = a.fsx_extsize; + + } +#endif + add_to_flist(type, id, parid); + close(fd); + } + if (v) + printf("%d/%d: creat %s x:%d %d %d\n", procid, opno, f.path, + esz, e, e1); + free_pathname(&f); +} + +int setdirect(int fd) +{ + static int no_direct; + int flags; + + if (no_direct) + return 0; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return 0; + + if (fcntl(fd, F_SETFL, flags | O_DIRECT) < 0) { + if (no_xfs) { + no_direct = 1; + return 0; + } + printf("cannot set O_DIRECT: %s\n", strerror(errno)); + return 0; + } + + return 1; +} + +void dread_f(int opno, long r) +{ + __int64_t align; + char *buf = NULL; + struct dioattr diob; + int e; + pathname_t f; + int fd; + size_t len; + __int64_t lr; + off64_t off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: dread - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDONLY); + + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: dread - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + + if (!setdirect(fd)) { + close(fd); + free_pathname(&f); + return; + } + + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: dread - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + if (stb.st_size == 0) { + if (v) + printf("%d/%d: dread - %s zero size\n", procid, opno, + f.path); + free_pathname(&f); + close(fd); + return; + } + + memset(&diob, 0, sizeof(diob)); + if (no_xfs) { + diob.d_miniosz = stb.st_blksize; + diob.d_maxiosz = stb.st_blksize * 256; /* good number ? */ + diob.d_mem = stb.st_blksize; + } +#ifndef NO_XFS + else if (ioctl(fd, XFS_IOC_DIOINFO, &diob) < 0) { + if (v) + printf + ("%d/%d: dread - ioctl(fd, XFS_IOC_DIOINFO) %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } +#endif + align = (__int64_t) diob.d_miniosz; + lr = ((__int64_t) random() << 32) + random(); + off = (off64_t) (lr % stb.st_size); + off -= (off % align); + lseek64(fd, off, SEEK_SET); + len = (random() % (getpagesize() * 32)) + 1; + len -= (len % align); + if (len <= 0) + len = align; + else if (len > diob.d_maxiosz) + len = diob.d_maxiosz; + if ((e = posix_memalign((void **)&buf, diob.d_mem, len)) != 0) { + fprintf(stderr, "posix_memalign: %s\n", strerror(e)); + exit(1); + } + if (buf == NULL) { + fprintf(stderr, "posix_memalign: buf is NULL\n"); + exit(1); + } + e = read(fd, buf, len) < 0 ? errno : 0; + free(buf); + if (v) + printf("%d/%d: dread %s [%lld,%ld] %d\n", + procid, opno, f.path, (long long int)off, (long)len, e); + free_pathname(&f); + close(fd); +} + +void dwrite_f(int opno, long r) +{ + __int64_t align; + char *buf = NULL; + struct dioattr diob; + int e; + pathname_t f; + int fd; + size_t len; + __int64_t lr; + off64_t off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: dwrite - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_WRONLY); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: dwrite - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + + if (!setdirect(fd)) { + close(fd); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: dwrite - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + memset(&diob, 0, sizeof(diob)); + if (no_xfs) { + diob.d_miniosz = stb.st_blksize; + diob.d_maxiosz = stb.st_blksize * 256; /* good number ? */ + diob.d_mem = stb.st_blksize; + } +#ifndef NO_XFS + else if (ioctl(fd, XFS_IOC_DIOINFO, &diob) < 0) { + if (v) + printf + ("%d/%d: dwrite - ioctl(fd, XFS_IOC_DIOINFO) %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } +#endif + align = (__int64_t) diob.d_miniosz; + lr = ((__int64_t) random() << 32) + random(); + off = (off64_t) (lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE)); + off -= (off % align); + lseek64(fd, off, SEEK_SET); + len = (random() % (getpagesize() * 32)) + 1; + len -= (len % align); + if (len <= 0) + len = align; + else if (len > diob.d_maxiosz) + len = diob.d_maxiosz; + if ((e = posix_memalign((void **)&buf, diob.d_mem, len)) != 0) { + fprintf(stderr, "posix_memalign: %s\n", strerror(e)); + exit(1); + } + if (buf == NULL) { + fprintf(stderr, "posix_memalign: buf is NULL\n"); + exit(1); + } + off %= maxfsize; + lseek64(fd, off, SEEK_SET); + memset(buf, nameseq & 0xff, len); + e = write(fd, buf, len) < 0 ? errno : 0; + free(buf); + if (v) + printf("%d/%d: dwrite %s [%lld,%ld] %d\n", + procid, opno, f.path, (long long)off, (long int)len, e); + free_pathname(&f); + close(fd); +} + +void fdatasync_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: fdatasync - no filename\n", + procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_WRONLY); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: fdatasync - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + e = fdatasync(fd) < 0 ? errno : 0; + if (v) + printf("%d/%d: fdatasync %s %d\n", procid, opno, f.path, e); + free_pathname(&f); + close(fd); +} + +#ifndef NO_XFS +void freesp_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + struct xfs_flock64 fl; + __s64 lr; + __s64 off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: freesp - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDWR); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: freesp - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: freesp - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + lr = ((__s64) random() << 32) + random(); + off = lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE); + off %= maxfsize; + memset(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = 0; + e = ioctl(fd, XFS_IOC_FREESP64, &fl) < 0 ? errno : 0; + if (v) + printf("%d/%d: ioctl(XFS_IOC_FREESP64) %s %lld 0 %d\n", + procid, opno, f.path, (long long)off, e); + free_pathname(&f); + close(fd); +} + +#endif + +void fsync_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: fsync - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_WRONLY); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: fsync - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + e = fsync(fd) < 0 ? errno : 0; + if (v) + printf("%d/%d: fsync %s %d\n", procid, opno, f.path, e); + free_pathname(&f); + close(fd); +} + +void getdents_f(int opno, long r) +{ + DIR *dir; + pathname_t f; + int v; + + init_pathname(&f); + if (!get_fname(FT_DIRm, r, &f, NULL, NULL, &v)) + append_pathname(&f, "."); + dir = opendir_path(&f); + check_cwd(); + if (dir == NULL) { + if (v) + printf("%d/%d: getdents - can't open %s\n", + procid, opno, f.path); + free_pathname(&f); + return; + } + while (readdir64(dir) != NULL) + continue; + if (v) + printf("%d/%d: getdents %s 0\n", procid, opno, f.path); + free_pathname(&f); + closedir(dir); +} + +void link_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + flist_t *flp; + int id; + pathname_t l; + int parid; + int v; + int v1; + + init_pathname(&f); + if (!get_fname(FT_NOTDIR, r, &f, &flp, NULL, &v1)) { + if (v1) + printf("%d/%d: link - no file\n", procid, opno); + free_pathname(&f); + return; + } + if (!get_fname(FT_DIRm, random(), NULL, NULL, &fep, &v)) + parid = -1; + else + parid = fep->id; + v |= v1; + init_pathname(&l); + e = generate_fname(fep, flp - flist, &l, &id, &v1); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&l, &flist[FT_DIR], fep); + printf("%d/%d: link - no filename from %s\n", + procid, opno, l.path); + } + free_pathname(&l); + free_pathname(&f); + return; + } + e = link_path(&f, &l) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + add_to_flist(flp - flist, id, parid); + if (v) + printf("%d/%d: link %s %s %d\n", procid, opno, f.path, l.path, + e); + free_pathname(&l); + free_pathname(&f); +} + +void mkdir_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + int id; + int parid; + int v; + int v1; + + if (!get_fname(FT_DIRm, r, NULL, NULL, &fep, &v)) + parid = -1; + else + parid = fep->id; + init_pathname(&f); + e = generate_fname(fep, FT_DIR, &f, &id, &v1); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&f, &flist[FT_DIR], fep); + printf("%d/%d: mkdir - no filename from %s\n", + procid, opno, f.path); + } + free_pathname(&f); + return; + } + e = mkdir_path(&f, 0777) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + add_to_flist(FT_DIR, id, parid); + if (v) + printf("%d/%d: mkdir %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +void mknod_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + int id; + int parid; + int v; + int v1; + + if (!get_fname(FT_DIRm, r, NULL, NULL, &fep, &v)) + parid = -1; + else + parid = fep->id; + init_pathname(&f); + e = generate_fname(fep, FT_DEV, &f, &id, &v1); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&f, &flist[FT_DIR], fep); + printf("%d/%d: mknod - no filename from %s\n", + procid, opno, f.path); + } + free_pathname(&f); + return; + } + e = mknod_path(&f, S_IFCHR | 0444, 0) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + add_to_flist(FT_DEV, id, parid); + if (v) + printf("%d/%d: mknod %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +void read_f(int opno, long r) +{ + char *buf; + int e; + pathname_t f; + int fd; + size_t len; + __int64_t lr; + off64_t off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: read - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDONLY); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: read - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: read - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + if (stb.st_size == 0) { + if (v) + printf("%d/%d: read - %s zero size\n", procid, opno, + f.path); + free_pathname(&f); + close(fd); + return; + } + lr = ((__int64_t) random() << 32) + random(); + off = (off64_t) (lr % stb.st_size); + lseek64(fd, off, SEEK_SET); + len = (random() % (getpagesize() * 32)) + 1; + buf = malloc(len); + e = read(fd, buf, len) < 0 ? errno : 0; + free(buf); + if (v) + printf("%d/%d: read %s [%lld,%ld] %d\n", + procid, opno, f.path, (long long)off, (long int)len, e); + free_pathname(&f); + close(fd); +} + +void readlink_f(int opno, long r) +{ + char buf[PATH_MAX]; + int e; + pathname_t f; + int v; + + init_pathname(&f); + if (!get_fname(FT_SYMm, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: readlink - no filename\n", procid, opno); + free_pathname(&f); + return; + } + e = readlink_path(&f, buf, PATH_MAX) < 0 ? errno : 0; + check_cwd(); + if (v) + printf("%d/%d: readlink %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +void rename_f(int opno, long r) +{ + fent_t *dfep; + int e; + pathname_t f; + fent_t *fep; + flist_t *flp; + int id; + pathname_t newf; + int oldid; + int parid; + int v; + int v1; + + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, &flp, &fep, &v1)) { + if (v1) + printf("%d/%d: rename - no filename\n", procid, opno); + free_pathname(&f); + return; + } + if (!get_fname(FT_DIRm, random(), NULL, NULL, &dfep, &v)) + parid = -1; + else + parid = dfep->id; + v |= v1; + init_pathname(&newf); + e = generate_fname(dfep, flp - flist, &newf, &id, &v1); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&f, &flist[FT_DIR], dfep); + printf("%d/%d: rename - no filename from %s\n", + procid, opno, f.path); + } + free_pathname(&newf); + free_pathname(&f); + return; + } + e = rename_path(&f, &newf) < 0 ? errno : 0; + check_cwd(); + if (e == 0) { + if (flp - flist == FT_DIR) { + oldid = fep->id; + fix_parent(oldid, id); + } + del_from_flist(flp - flist, fep - flp->fents); + add_to_flist(flp - flist, id, parid); + } + if (v) + printf("%d/%d: rename %s to %s %d\n", procid, opno, f.path, + newf.path, e); + free_pathname(&newf); + free_pathname(&f); +} + +#ifndef NO_XFS +void resvsp_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + struct xfs_flock64 fl; + __s64 lr; + __s64 off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: resvsp - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDWR); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: resvsp - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: resvsp - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + lr = ((__s64) random() << 32) + random(); + off = lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE); + off %= maxfsize; + memset(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = (__s64) (random() % (1024 * 1024)); + e = ioctl(fd, XFS_IOC_RESVSP64, &fl) < 0 ? errno : 0; + if (v) + printf("%d/%d: ioctl(XFS_IOC_RESVSP64) %s %lld %lld %d\n", + procid, opno, f.path, (long long)off, + (long long)fl.l_len, e); + free_pathname(&f); + close(fd); +} +#endif + +void rmdir_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + int v; + + init_pathname(&f); + if (!get_fname(FT_DIRm, r, &f, NULL, &fep, &v)) { + if (v) + printf("%d/%d: rmdir - no directory\n", procid, opno); + free_pathname(&f); + return; + } + e = rmdir_path(&f) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + del_from_flist(FT_DIR, fep - flist[FT_DIR].fents); + if (v) + printf("%d/%d: rmdir %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +void stat_f(int opno, long r) +{ + int e; + pathname_t f; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_ANYm, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: stat - no entries\n", procid, opno); + free_pathname(&f); + return; + } + e = lstat64_path(&f, &stb) < 0 ? errno : 0; + check_cwd(); + if (v) + printf("%d/%d: stat %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +void symlink_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + int i; + int id; + int len; + int parid; + int v; + int v1; + char *val; + + if (!get_fname(FT_DIRm, r, NULL, NULL, &fep, &v)) + parid = -1; + else + parid = fep->id; + init_pathname(&f); + e = generate_fname(fep, FT_SYM, &f, &id, &v1); + v |= v1; + if (!e) { + if (v) { + fent_to_name(&f, &flist[FT_DIR], fep); + printf("%d/%d: symlink - no filename from %s\n", + procid, opno, f.path); + } + free_pathname(&f); + return; + } + len = (int)(random() % PATH_MAX); + val = malloc(len + 1); + if (len) + memset(val, 'x', len); + val[len] = '\0'; + for (i = 10; i < len - 1; i += 10) + val[i] = '/'; + e = symlink_path(val, &f) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + add_to_flist(FT_SYM, id, parid); + free(val); + if (v) + printf("%d/%d: symlink %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +/* ARGSUSED */ +void sync_f(int opno, long r) +{ + sync(); + if (verbose) + printf("%d/%d: sync\n", procid, opno); +} + +void truncate_f(int opno, long r) +{ + int e; + pathname_t f; + __int64_t lr; + off64_t off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: truncate - no filename\n", procid, opno); + free_pathname(&f); + return; + } + e = stat64_path(&f, &stb) < 0 ? errno : 0; + check_cwd(); + if (e > 0) { + if (v) + printf("%d/%d: truncate - stat64 %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + lr = ((__int64_t) random() << 32) + random(); + off = lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE); + off %= maxfsize; + e = truncate64_path(&f, off) < 0 ? errno : 0; + check_cwd(); + if (v) + printf("%d/%d: truncate %s %lld %d\n", procid, opno, f.path, + (long long)off, e); + free_pathname(&f); +} + +void unlink_f(int opno, long r) +{ + int e; + pathname_t f; + fent_t *fep; + flist_t *flp; + int v; + + init_pathname(&f); + if (!get_fname(FT_NOTDIR, r, &f, &flp, &fep, &v)) { + if (v) + printf("%d/%d: unlink - no file\n", procid, opno); + free_pathname(&f); + return; + } + e = unlink_path(&f) < 0 ? errno : 0; + check_cwd(); + if (e == 0) + del_from_flist(flp - flist, fep - flp->fents); + if (v) + printf("%d/%d: unlink %s %d\n", procid, opno, f.path, e); + free_pathname(&f); +} + +#ifndef NO_XFS +void unresvsp_f(int opno, long r) +{ + int e; + pathname_t f; + int fd; + struct xfs_flock64 fl; + __s64 lr; + __s64 off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGFILE, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: unresvsp - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_RDWR); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: unresvsp - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: unresvsp - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + lr = ((__s64) random() << 32) + random(); + off = lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE); + off %= maxfsize; + memset(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = (__s64) (random() % (1 << 20)); + e = ioctl(fd, XFS_IOC_UNRESVSP64, &fl) < 0 ? errno : 0; + if (v) + printf("%d/%d: ioctl(XFS_IOC_UNRESVSP64) %s %lld %lld %d\n", + procid, opno, f.path, (long long)off, + (long long)fl.l_len, e); + free_pathname(&f); + close(fd); +} +#endif + +void write_f(int opno, long r) +{ + char *buf; + int e; + pathname_t f; + int fd; + size_t len; + __int64_t lr; + off64_t off; + struct stat64 stb; + int v; + + init_pathname(&f); + if (!get_fname(FT_REGm, r, &f, NULL, NULL, &v)) { + if (v) + printf("%d/%d: write - no filename\n", procid, opno); + free_pathname(&f); + return; + } + fd = open_path(&f, O_WRONLY); + e = fd < 0 ? errno : 0; + check_cwd(); + if (fd < 0) { + if (v) + printf("%d/%d: write - open %s failed %d\n", + procid, opno, f.path, e); + free_pathname(&f); + return; + } + if (fstat64(fd, &stb) < 0) { + if (v) + printf("%d/%d: write - fstat64 %s failed %d\n", + procid, opno, f.path, errno); + free_pathname(&f); + close(fd); + return; + } + lr = ((__int64_t) random() << 32) + random(); + off = (off64_t) (lr % MIN(stb.st_size + (1024 * 1024), MAXFSIZE)); + off %= maxfsize; + lseek64(fd, off, SEEK_SET); + len = (random() % (getpagesize() * 32)) + 1; + buf = malloc(len); + memset(buf, nameseq & 0xff, len); + e = write(fd, buf, len) < 0 ? errno : 0; + free(buf); + if (v) + printf("%d/%d: write %s [%lld,%ld] %d\n", + procid, opno, f.path, (long long)off, (long int)len, e); + free_pathname(&f); + close(fd); +} diff --git a/contrib/make-sparse.c b/contrib/make-sparse.c new file mode 100644 index 0000000..98b86b0 --- /dev/null +++ b/contrib/make-sparse.c @@ -0,0 +1,91 @@ +/* + * make-sparse.c --- make a sparse file from stdin + * + * Copyright 2004 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE +#endif +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +int full_read(int fd, char *buf, size_t count) +{ + int got, total = 0; + int pass = 0; + + while (count > 0) { + got = read(fd, buf, count); + if (got == -1) { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; + return total ? total : -1; + } + if (got == 0) { + if (pass++ >= 3) + return total; + continue; + } + pass = 0; + buf += got; + total += got; + count -= got; + } + return total; +} + +int main(int argc, char **argv) +{ + int fd, got, i; + int zflag = 0; + char buf[1024]; + + if (argc != 2) { + fprintf(stderr, "Usage: make-sparse out-file\n"); + exit(1); + } + fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0777); + if (fd < 0) { + perror(argv[1]); + exit(1); + } + while (1) { + got = full_read(0, buf, sizeof(buf)); + if (got == 0) + break; + if (got == sizeof(buf)) { + for (i=0; i < sizeof(buf); i++) + if (buf[i]) + break; + if (i == sizeof(buf)) { + lseek(fd, sizeof(buf), SEEK_CUR); + zflag = 1; + continue; + } + } + zflag = 0; + write(fd, buf, got); + } + if (zflag) { + lseek(fd, -1, SEEK_CUR); + buf[0] = 0; + write(fd, buf, 1); + } + return 0; +} + diff --git a/contrib/populate-extfs.sh b/contrib/populate-extfs.sh new file mode 100755 index 0000000..b1d3d1f --- /dev/null +++ b/contrib/populate-extfs.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# +# This script uses debugfs command to populate the ext2/3/4 filesystem +# from a given directory. +# + +do_usage () { + cat << _EOF +Usage: populate-extfs.sh +Create an ext2/ext3/ext4 filesystem from a directory or file + + source: The source directory or file + device: The target device + +_EOF + exit 1 +} + +[ $# -ne 2 ] && do_usage + +SRCDIR=${1%%/} +DEVICE=$2 + +# Find where is the debugfs command if not found in the env. +if [ -z "$DEBUGFS" ]; then + CONTRIB_DIR=$(dirname $(readlink -f $0)) + DEBUGFS="$CONTRIB_DIR/../debugfs/debugfs" +fi + +{ + CWD="/" + find $SRCDIR | while read FILE; do + TGT="${FILE##*/}" + DIR="${FILE#$SRCDIR}" + DIR="${DIR%$TGT}" + + # Skip the root dir + [ ! -z "$DIR" ] || continue + [ ! -z "$TGT" ] || continue + + if [ "$DIR" != "$CWD" ]; then + echo "cd $DIR" + CWD="$DIR" + fi + + # Only stat once since stat is a time consuming command + STAT=$(stat -c "TYPE=\"%F\";DEVNO=\"0x%t 0x%T\";MODE=\"%f\";U=\"%u\";G=\"%g\"" $FILE) + eval $STAT + + case $TYPE in + "directory") + echo "mkdir $TGT" + ;; + "regular file" | "regular empty file") + echo "write $FILE $TGT" + ;; + "symbolic link") + LINK_TGT=$(readlink $FILE) + echo "symlink $TGT $LINK_TGT" + ;; + "block special file") + echo "mknod $TGT b $DEVNO" + ;; + "character special file") + echo "mknod $TGT c $DEVNO" + ;; + "fifo") + echo "mknod $TGT p" + ;; + *) + echo "Unknown/unhandled file type '$TYPE' file: $FILE" 1>&2 + ;; + esac + + # Set the file mode + echo "sif $TGT mode 0x$MODE" + + # Set uid and gid + echo "sif $TGT uid $U" + echo "sif $TGT gid $G" + done + + # Handle the hard links. + # Save the hard links to a file, use the inode number as the filename, for example: + # If a and b's inode number is 6775928, save a and b to /tmp/tmp.VrCwHh5gdt/6775928. + INODE_DIR=`mktemp -d` || exit 1 + for i in `find $SRCDIR -type f -links +1 -printf 'INODE=%i###FN=%p\n'`; do + eval `echo $i | sed 's$###$ $'` + echo ${FN#$SRCDIR} >>$INODE_DIR/$INODE + done + # Use the debugfs' ln and "sif links_count" to handle them. + for i in `ls $INODE_DIR`; do + # The link source + SRC=`head -1 $INODE_DIR/$i` + # Remove the files and link them again except the first one + for TGT in `sed -n -e '1!p' $INODE_DIR/$i`; do + echo "rm $TGT" + echo "ln $SRC $TGT" + done + LN_CNT=`cat $INODE_DIR/$i | wc -l` + # Set the links count + echo "sif $SRC links_count $LN_CNT" + done + rm -fr $INODE_DIR +} | $DEBUGFS -w -f - $DEVICE diff --git a/contrib/python-uuid/setup.py b/contrib/python-uuid/setup.py new file mode 100755 index 0000000..3934d17 --- /dev/null +++ b/contrib/python-uuid/setup.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +from distutils.core import setup, Extension + +uuid = Extension('e2fsprogs_uuid', + sources = ['uuid.c'], + libraries = ['uuid']) + +setup (name = 'e2fsprogs_uuid', + version = '1.0', + description = 'This is python uuid interface', + ext_modules = [uuid]) diff --git a/contrib/python-uuid/test.py b/contrib/python-uuid/test.py new file mode 100755 index 0000000..2264f62 --- /dev/null +++ b/contrib/python-uuid/test.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +import e2fsprogs_uuid +import time + +print "Generating uuid...", +try: + time = time.time() + u = e2fsprogs_uuid.generate() +except: + u = "FAIL" +print u, "...", time + +print "Calling generate with param...", +try: + e2fsprogs_uuid.generate("param") + print "FAIL." +except: + print "OK" diff --git a/contrib/python-uuid/uuid.c b/contrib/python-uuid/uuid.c new file mode 100644 index 0000000..34dd56a --- /dev/null +++ b/contrib/python-uuid/uuid.c @@ -0,0 +1,23 @@ +#include +#include +#include + +static PyObject * _uuid_generate(PyObject *self, PyObject *args) +{ + uuid_t u; + char uuid[37]; + if (!PyArg_ParseTuple(args, "")) return NULL; + uuid_generate(u); + uuid_unparse(u, uuid); + return Py_BuildValue("s", uuid); +} + +static PyMethodDef _uuid_methods[] = { + {"generate", _uuid_generate, METH_VARARGS, "Generate UUID"}, + {NULL, NULL, 0, NULL} +}; + +void inite2fsprogs_uuid(void) +{ + (void) Py_InitModule("e2fsprogs_uuid", _uuid_methods); +} diff --git a/contrib/setup-schroot b/contrib/setup-schroot new file mode 100755 index 0000000..f3f45c6 --- /dev/null +++ b/contrib/setup-schroot @@ -0,0 +1,46 @@ +#!/bin/bash +# +# This script sets up a schroot suitable for building e2fsprogs +# on a Debian portable box + +while [ "$1" != "" ]; +do + case "$1" in + --base) shift + BASE_CHROOT="$1" + ;; + --chroot) shift + CHROOT="$1" + ;; + --help|help) + echo "Usage: setup-schroot [--base ] [--chroot ]" + exit 0 + ;; + *) + echo "unknown option: $1" + exit 1 + ;; + esac + shift +done + +if test -z "$BASE_CHROOT" ; then + BASE_CHROOT=sid +fi + +if test -z "$CHROOT" ; then + CHROOT="$USER-$BASE_CHROOT" +fi + +echo "Setting up $CHROOT using $BASE_CHROOT..." +schroot -b -n "$CHROOT" -c "$BASE_CHROOT" +dd-schroot-cmd -c "$CHROOT" apt-get update +dd-schroot-cmd -c "$CHROOT" -y apt-get upgrade +dd-schroot-cmd -c "$CHROOT" -y apt-get build-dep e2fsprogs +dd-schroot-cmd -c "$CHROOT" -y apt-get install git gdb emacs-nox lintian \ + acl libreadline-dev dh-exec cron +dd-schroot-cmd -c "$CHROOT" -y apt-get install udev systemd +echo " " +echo "Start chroot by running: " +echo "schroot -r -c $CHROOT" +echo " " diff --git a/contrib/spd_readdir.c b/contrib/spd_readdir.c new file mode 100644 index 0000000..8345fa1 --- /dev/null +++ b/contrib/spd_readdir.c @@ -0,0 +1,458 @@ +/* + * readdir accelerator + * + * (C) Copyright 2003, 2004, 2008 by Theodore Ts'o. + * + * 2008-06-08 Modified by Ross Boylan + * Added support for readdir_r and readdir64_r calls. Note + * this has not been tested on anything other than GNU/Linux i386, + * and that the regular readdir wrapper will take slightly more + * space than Ted's original since it now includes a lock. + * + * Compile using the command: + * + * gcc -o spd_readdir.so -shared -fpic spd_readdir.c -ldl + * + * Use it by setting the LD_PRELOAD environment variable: + * + * export LD_PRELOAD=/usr/local/sbin/spd_readdir.so + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#define ALLOC_STEPSIZE 100 +#define MAX_DIRSIZE 0 + +#define DEBUG +/* Util we autoconfiscate spd_readdir... */ +#define HAVE___SECURE_GETENV 1 +#define HAVE_PRCTL 1 +#define HAVE_SYS_PRCTL_H 1 + +#ifdef DEBUG +#define DEBUG_DIR(x) {if (do_debug) { x; }} +#else +#define DEBUG_DIR(x) +#endif + +#define _GNU_SOURCE +#define __USE_LARGEFILE64 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_PRCTL_H +#include +#else +#define PR_GET_DUMPABLE 3 +#endif +#include + +struct dirent_s { + unsigned long long d_ino; + long long d_off; + unsigned short int d_reclen; + unsigned char d_type; + char *d_name; +}; + +struct dir_s { + DIR *dir; + pthread_mutex_t lock; /* Mutex lock for this structure. */ + int num; + int max; + struct dirent_s *dp; + int pos; + int direct; + struct dirent ret_dir; + struct dirent64 ret_dir64; +}; + +static int (*real_closedir)(DIR *dir) = 0; +static DIR *(*real_opendir)(const char *name) = 0; +static DIR *(*real_fdopendir)(int fd) = 0; +static void *(*real_rewinddir)(DIR *dirp) = 0; +static struct dirent *(*real_readdir)(DIR *dir) = 0; +static int (*real_readdir_r)(DIR *dir, struct dirent *entry, + struct dirent **result) = 0; +static struct dirent64 *(*real_readdir64)(DIR *dir) = 0; +static int (*real_readdir64_r)(DIR *dir, struct dirent64 *entry, + struct dirent64 **result) = 0; +static off_t (*real_telldir)(DIR *dir) = 0; +static void (*real_seekdir)(DIR *dir, off_t offset) = 0; +static int (*real_dirfd)(DIR *dir) = 0; +static unsigned long max_dirsize = MAX_DIRSIZE; +static int num_open = 0; +#ifdef DEBUG +static int do_debug = 0; +#endif + +static char *safe_getenv(const char *arg) +{ + if ((getuid() != geteuid()) || (getgid() != getegid())) + return NULL; +#if HAVE_PRCTL + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) + return NULL; +#else +#if (defined(linux) && defined(SYS_prctl)) + if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) + return NULL; +#endif +#endif + +#if HAVE___SECURE_GETENV + return __secure_getenv(arg); +#else + return getenv(arg); +#endif +} + +static void setup_ptr() +{ + char *cp; + + real_opendir = dlsym(RTLD_NEXT, "opendir"); + real_fdopendir = dlsym(RTLD_NEXT, "fdopendir"); + real_closedir = dlsym(RTLD_NEXT, "closedir"); + real_rewinddir = dlsym(RTLD_NEXT, "rewinddir"); + real_readdir = dlsym(RTLD_NEXT, "readdir"); + real_readdir_r = dlsym(RTLD_NEXT, "readdir_r"); + real_readdir64 = dlsym(RTLD_NEXT, "readdir64"); + real_readdir64_r = dlsym(RTLD_NEXT, "readdir64_r"); + real_telldir = dlsym(RTLD_NEXT, "telldir"); + real_seekdir = dlsym(RTLD_NEXT, "seekdir"); + real_dirfd = dlsym(RTLD_NEXT, "dirfd"); + if ((cp = safe_getenv("SPD_READDIR_MAX_SIZE")) != NULL) { + max_dirsize = atol(cp); + } +#ifdef DEBUG + if (safe_getenv("SPD_READDIR_DEBUG")) { + printf("initialized!\n"); + do_debug++; + } +#endif +} + +static void free_cached_dir(struct dir_s *dirstruct) +{ + int i; + + pthread_mutex_destroy(&(dirstruct->lock)); + + if (!dirstruct->dp) + return; + + for (i=0; i < dirstruct->num; i++) { + free(dirstruct->dp[i].d_name); + } + free(dirstruct->dp); + dirstruct->dp = 0; + dirstruct->max = dirstruct->num = 0; +} + +static int ino_cmp(const void *a, const void *b) +{ + const struct dirent_s *ds_a = (const struct dirent_s *) a; + const struct dirent_s *ds_b = (const struct dirent_s *) b; + ino_t i_a, i_b; + + i_a = ds_a->d_ino; + i_b = ds_b->d_ino; + + if (ds_a->d_name[0] == '.') { + if (ds_a->d_name[1] == 0) + i_a = 0; + else if ((ds_a->d_name[1] == '.') && (ds_a->d_name[2] == 0)) + i_a = 1; + } + if (ds_b->d_name[0] == '.') { + if (ds_b->d_name[1] == 0) + i_b = 0; + else if ((ds_b->d_name[1] == '.') && (ds_b->d_name[2] == 0)) + i_b = 1; + } + + return (i_a - i_b); +} + +static struct dir_s *alloc_dirstruct(DIR *dir) +{ + struct dir_s *dirstruct; + static pthread_mutexattr_t mutexattr; + mutexattr.__align = PTHREAD_MUTEX_RECURSIVE; + + dirstruct = malloc(sizeof(struct dir_s)); + if (dirstruct) + memset(dirstruct, 0, sizeof(struct dir_s)); + dirstruct->dir = dir; + pthread_mutex_init(&(dirstruct->lock), &mutexattr); + return dirstruct; +} + +static void cache_dirstruct(struct dir_s *dirstruct) +{ + struct dirent_s *ds, *dnew; + struct dirent64 *d; + + while ((d = (*real_readdir64)(dirstruct->dir)) != NULL) { + if (dirstruct->num >= dirstruct->max) { + dirstruct->max += ALLOC_STEPSIZE; + DEBUG_DIR(printf("Reallocating to size %d\n", + dirstruct->max)); + dnew = realloc(dirstruct->dp, + dirstruct->max * sizeof(struct dir_s)); + if (!dnew) + goto nomem; + dirstruct->dp = dnew; + } + ds = &dirstruct->dp[dirstruct->num++]; + ds->d_ino = d->d_ino; + ds->d_off = d->d_off; + ds->d_reclen = d->d_reclen; + ds->d_type = d->d_type; + if ((ds->d_name = malloc(strlen(d->d_name)+1)) == NULL) { + dirstruct->num--; + goto nomem; + } + strcpy(ds->d_name, d->d_name); + DEBUG_DIR(printf("readdir: %lu %s\n", + (unsigned long) d->d_ino, d->d_name)); + } + qsort(dirstruct->dp, dirstruct->num, sizeof(struct dirent_s), ino_cmp); + return; +nomem: + DEBUG_DIR(printf("No memory, backing off to direct readdir\n")); + free_cached_dir(dirstruct); + dirstruct->direct = 1; +} + +DIR *opendir(const char *name) +{ + DIR *dir; + struct dir_s *dirstruct; + struct stat st; + + if (!real_opendir) + setup_ptr(); + + DEBUG_DIR(printf("Opendir(%s) (%d open)\n", name, num_open++)); + dir = (*real_opendir)(name); + if (!dir) + return NULL; + + dirstruct = alloc_dirstruct(dir); + if (!dirstruct) { + (*real_closedir)(dir); + errno = -ENOMEM; + return NULL; + } + + if (max_dirsize && (stat(name, &st) == 0) && + (st.st_size > max_dirsize)) { + DEBUG_DIR(printf("Directory size %ld, using direct readdir\n", + st.st_size)); + dirstruct->direct = 1; + return (DIR *) dirstruct; + } + + cache_dirstruct(dirstruct); + return ((DIR *) dirstruct); +} + +DIR *fdopendir(int fd) +{ + DIR *dir; + struct dir_s *dirstruct; + struct stat st; + + if (!real_fdopendir) + setup_ptr(); + + DEBUG_DIR(printf("fdopendir(%d) (%d open)\n", fd, num_open++)); + dir = (*real_fdopendir)(fd); + if (!dir) + return NULL; + + dirstruct = alloc_dirstruct(dir); + if (!dirstruct) { + (*real_closedir)(dir); + errno = -ENOMEM; + return NULL; + } + + if (max_dirsize && (fstat(fd, &st) == 0) && + (st.st_size > max_dirsize)) { + DEBUG_DIR(printf("Directory size %ld, using direct readdir\n", + st.st_size)); + dirstruct->dir = dir; + dirstruct->direct = 1; + return (DIR *) dirstruct; + } + + cache_dirstruct(dirstruct); + return ((DIR *) dirstruct); +} + +int closedir(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + + DEBUG_DIR(printf("Closedir (%d open)\n", --num_open)); + if (dirstruct->dir) + (*real_closedir)(dirstruct->dir); + + free_cached_dir(dirstruct); + free(dirstruct); + return 0; +} + +struct dirent *readdir(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + struct dirent_s *ds; + + if (dirstruct->direct) + return (*real_readdir)(dirstruct->dir); + + if (dirstruct->pos >= dirstruct->num) + return NULL; + + ds = &dirstruct->dp[dirstruct->pos++]; + dirstruct->ret_dir.d_ino = ds->d_ino; + dirstruct->ret_dir.d_off = ds->d_off; + dirstruct->ret_dir.d_reclen = ds->d_reclen; + dirstruct->ret_dir.d_type = ds->d_type; + strncpy(dirstruct->ret_dir.d_name, ds->d_name, + sizeof(dirstruct->ret_dir.d_name)); + + return (&dirstruct->ret_dir); +} + +int readdir_r(DIR *dir, struct dirent *entry, struct dirent **result) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + struct dirent_s *ds; + + if (dirstruct->direct) + return (*real_readdir_r)(dirstruct->dir, entry, result); + + pthread_mutex_lock(&(dirstruct->lock)); + if (dirstruct->pos >= dirstruct->num) { + *result = NULL; + } else { + ds = &dirstruct->dp[dirstruct->pos++]; + entry->d_ino = ds->d_ino; + entry->d_off = ds->d_off; + entry->d_reclen = ds->d_reclen; + entry->d_type = ds->d_type; + strncpy(entry->d_name, ds->d_name, sizeof(entry->d_name)); + *result = entry; + } + pthread_mutex_unlock(&(dirstruct->lock)); + return 0; +} + +struct dirent64 *readdir64(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + struct dirent_s *ds; + + if (dirstruct->direct) + return (*real_readdir64)(dirstruct->dir); + + if (dirstruct->pos >= dirstruct->num) + return NULL; + + ds = &dirstruct->dp[dirstruct->pos++]; + dirstruct->ret_dir64.d_ino = ds->d_ino; + dirstruct->ret_dir64.d_off = ds->d_off; + dirstruct->ret_dir64.d_reclen = ds->d_reclen; + dirstruct->ret_dir64.d_type = ds->d_type; + strncpy(dirstruct->ret_dir64.d_name, ds->d_name, + sizeof(dirstruct->ret_dir64.d_name)); + + return (&dirstruct->ret_dir64); +} + +int readdir64_r (DIR *__restrict dir, + struct dirent64 *__restrict entry, + struct dirent64 **__restrict result) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + struct dirent_s *ds; + + if (dirstruct->direct) + return (*real_readdir64_r)(dir, entry, result); + pthread_mutex_lock(&(dirstruct->lock)); + if (dirstruct->pos >= dirstruct->num) { + *result = NULL; + } else { + ds = &dirstruct->dp[dirstruct->pos++]; + entry->d_ino = ds->d_ino; + entry->d_off = ds->d_off; + entry->d_reclen = ds->d_reclen; + entry->d_type = ds->d_type; + strncpy(entry->d_name, ds->d_name, + sizeof(entry->d_name)); + *result = entry; + } + pthread_mutex_unlock(&(dirstruct->lock)); + return 0; +} + +off_t telldir(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + + if (dirstruct->direct) + return (*real_telldir)(dirstruct->dir); + + return ((off_t) dirstruct->pos); +} + +void seekdir(DIR *dir, off_t offset) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + + if (dirstruct->direct) { + (*real_seekdir)(dirstruct->dir, offset); + return; + } + + dirstruct->pos = offset; +} + +void rewinddir(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + + (*real_rewinddir)(dirstruct->dir); + if (dirstruct->direct) + return; + + pthread_mutex_lock(&(dirstruct->lock)); + dirstruct->pos = 0; + free_cached_dir(dirstruct); + cache_dirstruct(dirstruct); + pthread_mutex_unlock(&(dirstruct->lock)); +} + +int dirfd(DIR *dir) +{ + struct dir_s *dirstruct = (struct dir_s *) dir; + int fd = (*real_dirfd)(dirstruct->dir); + + DEBUG_DIR(printf("dirfd %d, %p\n", fd, real_dirfd)); + return fd; +} -- cgit v1.2.3