diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 068122f..0ff7b14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,12 +24,12 @@ jobs: sudo: 'sudo ' name: Linux ${{ matrix.volume }}${{ matrix.asroot.name }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: sudo apt-get install -y libparted-dev libudev-dev - - run: make all extra + - run: make all extra BIN_DIR=. - if: matrix.volume == 'relative-path' run: mkdir relative-path @@ -66,12 +66,12 @@ jobs: sudo: 'sudo ' name: MacOS ${{ matrix.volume }}${{ matrix.asroot.name }} - runs-on: macos-12 + runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install argp-standalone - - run: make + - run: make all extra BIN_DIR=. - if: matrix.volume == 'relative-path' run: mkdir relative-path @@ -88,6 +88,13 @@ jobs: - run: ./f3read --help - run: ${{ matrix.asroot.sudo }}./f3read -s 2 -e 4 -r 50000 ${{ matrix.volume }} + - name: Run F3Fix tests + if: matrix.volume == '.' + run: | + brew install gawk + chmod +x tests/test-macos-f3fix.sh + ./tests/test-macos-f3fix.sh + Cygwin: strategy: fail-fast: false @@ -103,7 +110,7 @@ jobs: windows: 'C:\cygwin' name: Cygwin ${{ matrix.volume.cygwin }} - runs-on: windows-2022 + runs-on: windows-latest steps: - uses: actions/checkout@v3 @@ -112,7 +119,7 @@ jobs: with: packages: gcc-core libargp-devel make - - run: "$Env:LDFLAGS = '-Wl,--stack,4000000'; make" + - run: "$Env:LDFLAGS = '-Wl,--stack,4000000 -largp'; make BIN_DIR=." - if: matrix.volume.windows == 'relative-path' run: New-Item -ItemType Directory -Path relative-path diff --git a/.gitignore b/.gitignore index 202f62e..276a600 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ cscope.out *.o *~ -doc/_build \ No newline at end of file +doc/_build +bin/ diff --git a/Makefile b/Makefile index 82b2d72..416ed23 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,162 @@ +# --- Basic Configuration --- CC ?= gcc -CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -ggdb +CFLAGS += -std=c99 -Wall -Wextra -pedantic +CFLAGS += -Iinclude # headers include directory +CFLAGS += -ggdb # keep debug symbols +CFLAGS += -ffunction-sections -fdata-sections # strip dead code +# --- Target Definitions --- TARGETS = f3write f3read EXTRA_TARGETS = f3probe f3brew f3fix +# --- Installation Paths and Tools --- PREFIX = /usr/local INSTALL = install LN = ln -ifndef OS - OS = $(shell uname -s) +# --- OS Detection --- +OS ?= $(shell uname -s) +ifeq ($(OS),Linux) + PLATFORM := linux +else ifeq ($(OS),Darwin) + PLATFORM := darwin + # Check Xcode command line tools + ifeq (,$(shell xcode-select -p)) + $(error Xcode command line tools are not installed) + endif +else + $(warning Unknown OS '$(OS)', defaulting to Linux) + PLATFORM := linux endif -ifneq ($(OS), Linux) - ARGP = /usr/local - ifeq ($(OS), Darwin) - ifneq ($(shell command -v brew),) - ARGP = $(shell brew --prefix) - endif - endif - CFLAGS += -I$(ARGP)/include - LDFLAGS += -L$(ARGP)/lib -largp + +# --- Platform-Specific Flags and Libraries Definitions --- +PLATFORM_CFLAGS = +PLATFORM_LDFLAGS = + +# Target specific libraries and flags +COMMON_LIBS = -lm +F3WRITE_LIBS = $(COMMON_LIBS) +F3READ_LIBS = $(COMMON_LIBS) +F3PROBE_LIBS = $(COMMON_LIBS) +F3BREW_LIBS = $(COMMON_LIBS) +F3FIX_LIBS = $(COMMON_LIBS) + +ifeq ($(PLATFORM), linux) + PLATFORM_DIR = linux + PLATFORM_CFLAGS += -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 + PLATFORM_LDFLAGS += -Wl,--gc-sections # strip dead code + F3PROBE_LIBS += -ludev + F3BREW_LIBS += -ludev + F3FIX_LIBS += -ludev -lparted +endif +ifeq ($(PLATFORM), darwin) + PLATFORM_DIR = darwin + ifneq ($(shell command -v brew),) + ARGP_PREFIX := $(shell brew --prefix) + else + ARGP_PREFIX := /usr/local + endif + PLATFORM_CFLAGS += -D_DARWIN_C_SOURCE + PLATFORM_CFLAGS += -I$(ARGP_PREFIX)/include + PLATFORM_LDFLAGS += -L$(ARGP_PREFIX)/lib -largp + PLATFORM_LDFLAGS += -Wl,-dead_strip # strip dead code + PLATFORM_LDFLAGS += -framework DiskArbitration -framework CoreFoundation endif -all: $(TARGETS) -extra: $(EXTRA_TARGETS) +CFLAGS += $(PLATFORM_CFLAGS) +LDFLAGS += $(PLATFORM_LDFLAGS) -docker: - docker build -f Dockerfile -t f3:latest . +# --- Output Directory Setup --- +BUILD_DIR := build +BIN_DIR := $(BUILD_DIR)/bin +OBJ_DIR := $(BUILD_DIR)/obj + +BIN_TARGETS := $(addprefix $(BIN_DIR)/,$(TARGETS)) +BIN_EXTRAS := $(addprefix $(BIN_DIR)/,$(EXTRA_TARGETS)) + +# Source directories and automatic search rules +SRC_DIRS = src/f3 src/f3-extra +vpath %.c $(SRC_DIRS) + +# Find source files relative to source root and map them to object paths in OBJ_DIR +TARGET_SRCS := $(wildcard $(addsuffix /*.c, $(SRC_DIRS))) +TARGET_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(TARGET_SRCS)) + +# Reusable object lists +F3_LIB_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/f3/lib/*.c)) +F3_EXTRA_LIB_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/f3-extra/lib/*.c)) + +# Platform-specific object lists +PLATFORM_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/*.c)) +PLATFORM_COMPAT_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/platform_compat.c)) +PLATFORM_PARTITION_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,src/platform/$(PLATFORM_DIR)/partition/partition.c) + +ALL_OBJS := $(TARGET_OBJS) $(F3_LIB_OBJS) $(F3_EXTRA_LIB_OBJS) $(PLATFORM_OBJS) $(PLATFORM_PARTITION_OBJS) + +# --- Dependency Inclusion --- +DEPFLAGS = -MMD -MT $@ -MP -MF $(@:.o=.d) +ALL_DEPS := $(ALL_OBJS:.o=.d) +-include $(ALL_DEPS) + +# --- Pattern Rule for Compilation --- +$(OBJ_DIR)/%.o: src/%.c | $(OBJ_DIR) + @mkdir -p $(@D) + $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ + +# --- Main Build Targets --- +all: $(BIN_TARGETS) +extra: $(BIN_EXTRAS) + +# --- Directory Targets --- +$(BUILD_DIR): ; @mkdir -p $@ +$(BIN_DIR): | $(BUILD_DIR) ; @mkdir -p $@ +$(OBJ_DIR): | $(BUILD_DIR) ; @mkdir -p $@ + +# --- Binary Linking Rules --- +$(BIN_DIR)/f3write: $(OBJ_DIR)/f3/f3write.o $(F3_LIB_OBJS) $(PLATFORM_COMPAT_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3WRITE_LIBS) +$(BIN_DIR)/f3read: $(OBJ_DIR)/f3/f3read.o $(F3_LIB_OBJS) $(PLATFORM_COMPAT_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3READ_LIBS) + +$(BIN_DIR)/f3probe: $(OBJ_DIR)/f3-extra/f3probe.o $(F3_EXTRA_LIB_OBJS) $(PLATFORM_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3PROBE_LIBS) + +$(BIN_DIR)/f3brew: $(OBJ_DIR)/f3-extra/f3brew.o $(F3_EXTRA_LIB_OBJS) $(PLATFORM_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3BREW_LIBS) + +$(BIN_DIR)/f3fix: $(OBJ_DIR)/f3-extra/f3fix.o $(F3_EXTRA_LIB_OBJS) $(PLATFORM_OBJS) $(PLATFORM_PARTITION_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3FIX_LIBS) + +# --- Installation Targets --- install: all + @echo "Installing binaries from $(BIN_DIR) to $(DESTDIR)$(PREFIX)/bin" $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(TARGETS) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m755 $(BIN_TARGETS) $(DESTDIR)$(PREFIX)/bin + @echo "Installing man pages to $(DESTDIR)$(PREFIX)/share/man/man1" $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/man/man1 $(INSTALL) -m644 f3read.1 $(DESTDIR)$(PREFIX)/share/man/man1 $(LN) -sf f3read.1 $(DESTDIR)$(PREFIX)/share/man/man1/f3write.1 install-extra: extra + @echo "Installing extra binaries from $(BIN_DIR) to $(DESTDIR)$(PREFIX)/bin" $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(EXTRA_TARGETS) $(DESTDIR)$(PREFIX)/bin - -f3write: utils.o libflow.o f3write.o - $(CC) -o $@ $^ $(LDFLAGS) -lm - -f3read: utils.o libflow.o f3read.o - $(CC) -o $@ $^ $(LDFLAGS) -lm - -f3probe: libutils.o libdevs.o libprobe.o f3probe.o - $(CC) -o $@ $^ $(LDFLAGS) -lm -ludev + $(INSTALL) -m755 $(BIN_EXTRAS) $(DESTDIR)$(PREFIX)/bin -f3brew: libutils.o libdevs.o f3brew.o - $(CC) -o $@ $^ $(LDFLAGS) -lm -ludev - -f3fix: libutils.o f3fix.o - $(CC) -o $@ $^ $(LDFLAGS) -lparted - --include *.d +# --- Development Helper Targets --- +cscope: + cscope -b src/**/*.c include/**/*.h -PHONY: cscope clean +cppcheck: + cppcheck --enable=all --suppress=missingIncludeSystem \ + -Iinclude -Iinclude/devices src include -cscope: - cscope -b *.c *.h +docker: + docker build -f Dockerfile -t f3:latest . +# --- Cleanup and Maintenance Targets --- clean: - rm -f *.o *.d cscope.out $(TARGETS) $(EXTRA_TARGETS) + @rm -rf $(BUILD_DIR) + @rm -f cscope.out + +.PHONY: all extra docker install install-extra cscope cppcheck clean diff --git a/f3fix.c b/f3fix.c deleted file mode 100644 index 061c3a4..0000000 --- a/f3fix.c +++ /dev/null @@ -1,296 +0,0 @@ -#include -#include -#include -#include - -#include "version.h" -#include "libutils.h" - -/* Argp's global variables. */ -const char *argp_program_version = "F3 Fix " F3_STR_VERSION; - -/* Arguments. */ -static char adoc[] = ""; - -static char doc[] = "F3 Fix -- edit the partition table of " - "a fake flash drive to have a single partition that fully covers " - "the real capacity of the drive"; - -static struct argp_option options[] = { - {"disk-type", 'd', "TYPE", 0, - "Disk type of the partition table", 2}, - {"fs-type", 'f', "TYPE", 0, - "Type of the file system of the partition", 0}, - {"boot", 'b', NULL, 0, - "Mark the partition for boot", 0}, - {"no-boot", 'n', NULL, 0, - "Do not mark the partition for boot", 0}, - {"first-sec", 'a', "SEC-NUM", 0, - "Sector where the partition starts", 0}, - {"last-sec", 'l', "SEC-NUM", 0, - "Sector where the partition ends", 0}, - {"list-disk-types", 'k', NULL, 0, - "List all supported disk types", 3}, - {"list-fs-types", 's', NULL, 0, - "List all supported types of file systems", 0}, - { 0 } -}; - -struct args { - bool list_disk_types; - bool list_fs_types; - - bool boot; - - /* 29 free bytes. */ - - const char *dev_filename; - PedDiskType *disk_type; - PedFileSystemType *fs_type; - PedSector first_sec; - PedSector last_sec; -}; - -static long long arg_to_long_long(const struct argp_state *state, - const char *arg) -{ - char *end; - long long ll = strtoll(arg, &end, 0); - if (!arg) - argp_error(state, "An integer must be provided"); - if (!*arg || *end) - argp_error(state, "`%s' is not an integer", arg); - return ll; -} - -static error_t parse_opt(int key, char *arg, struct argp_state *state) -{ - struct args *args = state->input; - long long ll; - - switch (key) { - case 'd': - args->disk_type = ped_disk_type_get(arg); - if (!args->disk_type) - argp_error(state, - "Disk type `%s' is not supported; use --list-disk-types to see the supported types", - arg); - break; - - case 'f': - args->fs_type = ped_file_system_type_get(arg); - if (!args->fs_type) - argp_error(state, - "File system type `%s' is not supported; use --list-fs-types to see the supported types", - arg); - break; - - case 'b': - args->boot = true; - break; - - case 'n': - args->boot = false; - break; - - case 'a': - ll = arg_to_long_long(state, arg); - if (ll < 0) - argp_error(state, - "First sector must be greater or equal to 0"); - args->first_sec = ll; - break; - - case 'l': - ll = arg_to_long_long(state, arg); - if (ll < 0) - argp_error(state, - "Last sector must be greater or equal to 0"); - args->last_sec = ll; - break; - - case 'k': - args->list_disk_types = true; - break; - - case 's': - args->list_fs_types = true; - break; - - case ARGP_KEY_INIT: - args->dev_filename = NULL; - args->last_sec = -1; - break; - - case ARGP_KEY_ARG: - if (args->dev_filename) - argp_error(state, - "Wrong number of arguments; only one is allowed"); - args->dev_filename = arg; - break; - - case ARGP_KEY_END: - if (args->list_disk_types || args->list_fs_types) - break; - - if (!args->dev_filename) - argp_error(state, - "The disk device was not specified"); - - if (args->last_sec < 0) - argp_error(state, - "Option --last-sec is required"); - if (args->first_sec > args->last_sec) - argp_error(state, - "Option --fist_sec must be less or equal to option --last_sec"); - break; - - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - -static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL}; - -static void list_disk_types(void) -{ - PedDiskType *type; - int i = 0; - printf("Disk types:\n"); - for (type = ped_disk_type_get_next(NULL); type; - type = ped_disk_type_get_next(type)) { - printf("%s\t", type->name); - i++; - if (i == 5) { - printf("\n"); - i = 0; - } - } - if (i > 0) - printf("\n"); - printf("\n"); -} - -static void list_fs_types(void) -{ - PedFileSystemType *fs_type; - int i = 0; - printf("File system types:\n"); - for (fs_type = ped_file_system_type_get_next(NULL); fs_type; - fs_type = ped_file_system_type_get_next(fs_type)) { - printf("%s\t", fs_type->name); - i++; - if (i == 5) { - printf("\n"); - i = 0; - } - } - if (i > 0) - printf("\n"); - printf("\n"); -} - -static PedSector map_sector_to_logical_sector(PedSector sector, - int logical_sector_size) -{ - assert(logical_sector_size >= 512); - assert(logical_sector_size % 512 == 0); - return sector / (logical_sector_size / 512); -} - -/* 0 on failure, 1 otherwise. */ -static int fix_disk(PedDevice *dev, PedDiskType *type, - PedFileSystemType *fs_type, int boot, PedSector start, PedSector end) -{ - PedDisk *disk; - PedPartition *part; - PedGeometry *geom; - PedConstraint *constraint; - int ret = 0; - - disk = ped_disk_new_fresh(dev, type); - if (!disk) - goto out; - - start = map_sector_to_logical_sector(start, dev->sector_size); - end = map_sector_to_logical_sector(end, dev->sector_size); - part = ped_partition_new(disk, PED_PARTITION_NORMAL, - fs_type, start, end); - if (!part) - goto disk; - if (boot && !ped_partition_set_flag(part, PED_PARTITION_BOOT, 1)) - goto part; - - geom = ped_geometry_new(dev, start, end - start + 1); - if (!geom) - goto part; - constraint = ped_constraint_exact(geom); - ped_geometry_destroy(geom); - if (!constraint) - goto part; - - ret = ped_disk_add_partition(disk, part, constraint); - ped_constraint_destroy(constraint); - if (!ret) - goto part; - /* ped_disk_print(disk); */ - - ret = ped_disk_commit(disk); - goto disk; - -part: - ped_partition_destroy(part); -disk: - ped_disk_destroy(disk); -out: - return ret; -} - -int main (int argc, char *argv[]) -{ - struct args args = { - /* Defaults. */ - .list_disk_types = false, - .list_fs_types = false, - - .boot = true, - - .disk_type = ped_disk_type_get("msdos"), - .fs_type = ped_file_system_type_get("fat32"), - .first_sec = 2048, /* Skip first 1MB. */ - }; - - PedDevice *dev; - int ret; - - /* Read parameters. */ - argp_parse(&argp, argc, argv, 0, NULL, &args); - print_header(stdout, "fix"); - - if (args.list_disk_types) - list_disk_types(); - - if (args.list_fs_types) - list_fs_types(); - - if (args.list_disk_types || args.list_fs_types) { - /* If the user has asked for the types, - * she doesn't want to fix the drive yet. - */ - return 0; - } - - /* XXX If @dev is a partition, refer the user to - * the disk of this partition. - */ - dev = ped_device_get(args.dev_filename); - if (!dev) - return 1; - - ret = !fix_disk(dev, args.disk_type, args.fs_type, args.boot, - args.first_sec, args.last_sec); - printf("Drive `%s' was successfully fixed\n", args.dev_filename); - ped_device_destroy(dev); - return ret; -} diff --git a/include/f3/devices/file_device.h b/include/f3/devices/file_device.h new file mode 100644 index 0000000..89ce77f --- /dev/null +++ b/include/f3/devices/file_device.h @@ -0,0 +1,23 @@ +#ifndef HEADER_DEVICES_FILE_DEVICE_H +#define HEADER_DEVICES_FILE_DEVICE_H + +#include /* For struct device. */ +#include /* For type uint64_t. */ + +/* Create a file-backed device that masquerades as a block device. + * filename: path to the backing file. + * real_size_byte: actual allocated size on disk. + * fake_size_byte: size exposed via read/write wrappers. + * wrap: wrap-around mask power (bits). + * block_order: log2(block size). + * cache_order: log2(number of cache entries) or -1 for no cache. + * strict_cache: if non-zero, track exact block positions. + * keep_file: if zero, unlink backing file immediately. + * Returns pointer to allocated struct device or NULL on error. + */ + struct device *create_file_device(const char *filename, + uint64_t real_size_byte, uint64_t fake_size_byte, + int wrap, int block_order, int cache_order, + int strict_cache, int keep_file); + +#endif /* HEADER_DEVICES_FILE_DEVICE_H */ diff --git a/include/f3/devices/perf_device.h b/include/f3/devices/perf_device.h new file mode 100644 index 0000000..fe9a33f --- /dev/null +++ b/include/f3/devices/perf_device.h @@ -0,0 +1,16 @@ +#ifndef HEADER_DEVICES_PERF_DEVICE_H +#define HEADER_DEVICES_PERF_DEVICE_H + +#include /* For struct device. */ +#include /* For type uint64_t. */ + +/* Create a performance‐measuring wrapper around a device. */ +struct device *create_perf_device(struct device *dev); + +/* Sample counters: read/write/reset counts and times in microseconds. */ +void perf_device_sample(struct device *dev, + uint64_t *pread_count, uint64_t *pread_time_us, + uint64_t *pwrite_count, uint64_t *pwrite_time_us, + uint64_t *preset_count, uint64_t *preset_time_us); + +#endif /* HEADER_DEVICES_PERF_DEVICE_H */ diff --git a/include/f3/devices/safe_device.h b/include/f3/devices/safe_device.h new file mode 100644 index 0000000..6bc8778 --- /dev/null +++ b/include/f3/devices/safe_device.h @@ -0,0 +1,15 @@ +#ifndef HEADER_DEVICES_SAFE_DEVICE_H +#define HEADER_DEVICES_SAFE_DEVICE_H + +#include /* For struct device. */ +#include /* For type uint64_t. */ + +/* Create a safe device wrapper: snapshots writes for rollback. */ +struct device *create_safe_device(struct device *dev, + uint64_t max_blocks, int min_memory); + +/* Recover and flush saved blocks. */ +void sdev_recover(struct device *dev, uint64_t very_last_pos); +void sdev_flush(struct device *dev); + +#endif /* HEADER_DEVICES_SAFE_DEVICE_H */ diff --git a/libdevs.h b/include/f3/libdevs.h similarity index 65% rename from libdevs.h rename to include/f3/libdevs.h index 1a3f9e4..c1244bc 100644 --- a/libdevs.h +++ b/include/f3/libdevs.h @@ -1,7 +1,7 @@ #ifndef HEADER_LIBDEVS_H #define HEADER_LIBDEVS_H -#include +#include /* For type uint64_t. */ /* * Device model @@ -38,7 +38,18 @@ enum fake_type dev_param_to_type(uint64_t real_size_byte, * Abstract device */ -struct device; +struct device { + uint64_t size_byte; + int block_order; + + int (*read_blocks)(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos); + int (*write_blocks)(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos); + int (*reset)(struct device *dev); + void (*free)(struct device *dev); + const char *(*get_filename)(struct device *dev); +}; /* * Properties @@ -76,11 +87,6 @@ void free_device(struct device *dev); * Concrete devices */ -struct device *create_file_device(const char *filename, - uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, - int block_order, int cache_order, int strict_cache, - int keep_file); - enum reset_type { RT_MANUAL_USB = 0, RT_USB, @@ -88,22 +94,9 @@ enum reset_type { RT_MAX }; -struct device *create_block_device(const char *filename, enum reset_type rt); - -struct device *create_perf_device(struct device *dev); -void perf_device_sample(struct device *dev, - uint64_t *pread_count, uint64_t *pread_time_us, - uint64_t *pwrite_count, uint64_t *pwrite_time_us, - uint64_t *preset_count, uint64_t *preset_time_us); -/* Detach the shadow device of @pdev, free @pdev, and return - * the shadow device. - */ -struct device *pdev_detach_and_free(struct device *dev); - -struct device *create_safe_device(struct device *dev, uint64_t max_blocks, - int min_memory); - -void sdev_recover(struct device *dev, uint64_t very_last_pos); -void sdev_flush(struct device *dev); +#include +#include +#include +#include #endif /* HEADER_LIBDEVS_H */ diff --git a/libflow.h b/include/f3/libflow.h similarity index 100% rename from libflow.h rename to include/f3/libflow.h diff --git a/libprobe.h b/include/f3/libprobe.h similarity index 77% rename from libprobe.h rename to include/f3/libprobe.h index 014afda..920d28b 100644 --- a/libprobe.h +++ b/include/f3/libprobe.h @@ -1,9 +1,8 @@ #ifndef HEADER_LIBPROBE_H #define HEADER_LIBPROBE_H -#include - -#include "libdevs.h" +#include /* For type uint64_t. */ +#include /* For struct device. */ uint64_t probe_device_max_blocks(struct device *dev); diff --git a/libutils.h b/include/f3/libutils.h similarity index 100% rename from libutils.h rename to include/f3/libutils.h diff --git a/include/f3/platform/block_device.h b/include/f3/platform/block_device.h new file mode 100644 index 0000000..114b28b --- /dev/null +++ b/include/f3/platform/block_device.h @@ -0,0 +1,13 @@ +#ifndef HEADER_PLATFORM_BLOCK_DEVICE_H +#define HEADER_PLATFORM_BLOCK_DEVICE_H + +#include /* For device, reset_type. */ + +/* Create a raw block device wrapper. + * filename: path to the block device (e.g., /dev/sdx). + * rt: reset type (use enum reset_type). + * Returns pointer to struct device or NULL on error. + */ + struct device *create_block_device(const char *filename, enum reset_type rt); + +#endif /* HEADER_PLATFORM_BLOCK_DEVICE_H */ diff --git a/include/f3/platform/partition.h b/include/f3/platform/partition.h new file mode 100644 index 0000000..a700a6f --- /dev/null +++ b/include/f3/platform/partition.h @@ -0,0 +1,31 @@ +#ifndef HEADER_PLATFORM_PARTITION_H +#define HEADER_PLATFORM_PARTITION_H + +#include /* For type uint64_t. */ +#include /* For type bool. */ + +/* Partition creation options. */ +struct partition_options { + const char *disk_type; // default: "msdos" + const char *fs_type; // default: "fat32" + bool boot; + uint64_t first_sector; + uint64_t last_sector; +}; + +/* Partition management functions */ +int partition_create(const char *dev_filename, const struct partition_options *options); + +/* Type validation functions */ +bool is_valid_disk_type(const char *disk_type); +bool is_valid_fs_type(const char *fs_type); + +/* List supported disk and file system types + * Returns an array of `count` NUL-terminated strings. + * The caller *must* free the array with partition_free_types_array(). + */ +size_t partition_list_disk_types(char ***out_array); +size_t partition_list_fs_types(char ***out_array); +void partition_free_types_array(char **array); + +#endif /* HEADER_PLATFORM_PARTITION_H */ diff --git a/include/f3/platform/platform_compat.h b/include/f3/platform/platform_compat.h new file mode 100644 index 0000000..e5cd550 --- /dev/null +++ b/include/f3/platform/platform_compat.h @@ -0,0 +1,25 @@ +#ifndef HEADER_PLATFORM_COMPAT_H +#define HEADER_PLATFORM_COMPAT_H + +#include /* For off_t */ + +#define UNUSED(x) ((void)x) + +/* Define compatibility names to avoid clashes with standard library functions if they exist */ +void msleep_compat(double wait_ms); +int fdatasync_compat(int fd); +int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice); + +/* Define POSIX_FADV_* compatibility macros if they are not already defined by + * standard headers included above. + * These provide the interface values for code calling posix_fadvise_compat. + * The compat implementations will map these values to platform specifics. + */ +#if !defined(POSIX_FADV_SEQUENTIAL) +#define POSIX_FADV_SEQUENTIAL 2 /* Expect sequential page references. */ +#endif +#if !defined(POSIX_FADV_DONTNEED) +#define POSIX_FADV_DONTNEED 4 /* Don't need these pages. */ +#endif + +#endif /* HEADER_PLATFORM_COMPAT_H */ diff --git a/utils.h b/include/f3/utils.h similarity index 58% rename from utils.h rename to include/f3/utils.h index 03031e2..5e08ec0 100644 --- a/utils.h +++ b/include/f3/utils.h @@ -5,6 +5,7 @@ #include /* For struct timeval. */ #include /* For type uint64_t. */ #include /* For struct argp_state. */ +#include /* POSIX_FADV_* */ #define SECTOR_SIZE (512) #define GIGABYTES (1024 * 1024 * 1024) @@ -41,36 +42,11 @@ static inline uint64_t random_number(uint64_t prv_number) long arg_to_long(const struct argp_state *state, const char *arg); -#if __APPLE__ && __MACH__ - -#include /* For type off_t. */ - -#define POSIX_FADV_SEQUENTIAL 2 /* Expect sequential page references. */ -#define POSIX_FADV_DONTNEED 4 /* Don't need these pages. */ - -int fdatasync(int fd); -int posix_fadvise(int fd, off_t offset, off_t len, int advice); - -#endif /* Apple Macintosh */ - -#ifdef __FreeBSD__ -#define fdatasync(fd) fsync(fd) -#endif - -#ifdef __OpenBSD__ - -#define POSIX_FADV_SEQUENTIAL 2 /* Expect sequential page references. */ -#define POSIX_FADV_DONTNEED 4 /* Don't need these pages. */ - /* - * OpenBSD doesn't have posix_fadvise() (...). - * There is some code [in F3] to emulate posix_fadvise for MacOS - * but it uses various fcntl(2) commands that we don't have [in OpenBSD]. - * - * -- Stuart Henderson, OpenBSD developer + * These functions are provided to abstract the platform-specific + * implementations of fdatasync(2) and posix_fadvise(2). */ -#define posix_fadvise(fd, offset, len, advice) (0) - -#endif /* OpenBSD */ +int f3_fdatasync(int fd); +int f3_posix_fadvise(int fd, off_t offset, off_t len, int advice); #endif /* HEADER_UTILS_H */ diff --git a/version.h b/include/f3/version.h similarity index 100% rename from version.h rename to include/f3/version.h diff --git a/libdevs.c b/libdevs.c deleted file mode 100644 index 6e39d25..0000000 --- a/libdevs.c +++ /dev/null @@ -1,1408 +0,0 @@ -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200809L -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libutils.h" -#include "libdevs.h" - -static const char * const ftype_to_name[FKTY_MAX] = { - [FKTY_GOOD] = "good", - [FKTY_BAD] = "bad", - [FKTY_LIMBO] = "limbo", - [FKTY_WRAPAROUND] = "wraparound", - [FKTY_CHAIN] = "chain", -}; - -const char *fake_type_to_name(enum fake_type fake_type) -{ - assert(fake_type < FKTY_MAX); - return ftype_to_name[fake_type]; -} - -int dev_param_valid(uint64_t real_size_byte, - uint64_t announced_size_byte, int wrap, int block_order) -{ - int block_size; - - /* Check general ranges. */ - if (real_size_byte > announced_size_byte || wrap < 0 || wrap >= 64 || - block_order < 9 || block_order > 20) - return false; - - /* Check alignment of the sizes. */ - block_size = 1 << block_order; - if (real_size_byte % block_size || announced_size_byte % block_size) - return false; - - /* If good, @wrap must make sense. */ - if (real_size_byte == announced_size_byte) { - uint64_t two_wrap = ((uint64_t)1) << wrap; - return announced_size_byte <= two_wrap; - } - - return true; -} - -enum fake_type dev_param_to_type(uint64_t real_size_byte, - uint64_t announced_size_byte, int wrap, int block_order) -{ - uint64_t two_wrap; - - assert(dev_param_valid(real_size_byte, announced_size_byte, - wrap, block_order)); - - if (real_size_byte == announced_size_byte) - return FKTY_GOOD; - - if (real_size_byte == 0) - return FKTY_BAD; - - /* real_size_byte < announced_size_byte */ - - two_wrap = ((uint64_t)1) << wrap; - if (two_wrap <= real_size_byte) - return FKTY_WRAPAROUND; - if (two_wrap < announced_size_byte) - return FKTY_CHAIN; - return FKTY_LIMBO; -} - -struct device { - uint64_t size_byte; - int block_order; - - int (*read_blocks)(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos); - int (*write_blocks)(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos); - int (*reset)(struct device *dev); - void (*free)(struct device *dev); - const char *(*get_filename)(struct device *dev); -}; - -uint64_t dev_get_size_byte(struct device *dev) -{ - return dev->size_byte; -} - -int dev_get_block_order(struct device *dev) -{ - return dev->block_order; -} - -int dev_get_block_size(struct device *dev) -{ - return 1 << dev->block_order; -} - -const char *dev_get_filename(struct device *dev) -{ - return dev->get_filename(dev); -} - -int dev_read_blocks(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - if (first_pos > last_pos) - return false; - assert(last_pos < (dev->size_byte >> dev->block_order)); - return dev->read_blocks(dev, buf, first_pos, last_pos); -} - -int dev_write_blocks(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - if (first_pos > last_pos) - return false; - assert(last_pos < (dev->size_byte >> dev->block_order)); - return dev->write_blocks(dev, buf, first_pos, last_pos); -} - -int dev_reset(struct device *dev) -{ - return dev->reset ? dev->reset(dev) : 0; -} - -void free_device(struct device *dev) -{ - if (dev->free) - dev->free(dev); - free(dev); -} - -struct file_device { - /* This must be the first field. See dev_fdev() for details. */ - struct device dev; - - const char *filename; - int fd; - uint64_t real_size_byte; - uint64_t address_mask; - uint64_t cache_mask; - uint64_t *cache_entries; - char *cache_blocks; -}; - -static inline struct file_device *dev_fdev(struct device *dev) -{ - return (struct file_device *)dev; -} - -static int fdev_read_block(struct device *dev, char *buf, uint64_t block_pos) -{ - struct file_device *fdev = dev_fdev(dev); - const int block_size = dev_get_block_size(dev); - const int block_order = dev_get_block_order(dev); - off_t off_ret, offset = block_pos << block_order; - int done; - - offset &= fdev->address_mask; - if ((uint64_t)offset >= fdev->real_size_byte) { - uint64_t cache_pos; - - if (!fdev->cache_blocks) - goto no_block; /* No cache available. */ - - cache_pos = block_pos & fdev->cache_mask; - - if (fdev->cache_entries && - fdev->cache_entries[cache_pos] != block_pos) - goto no_block; - - memmove(buf, &fdev->cache_blocks[cache_pos << block_order], - block_size); - return 0; - } - - off_ret = lseek(fdev->fd, offset, SEEK_SET); - if (off_ret < 0) - return - errno; - assert(off_ret == offset); - - done = 0; - do { - ssize_t rc = read(fdev->fd, buf + done, block_size - done); - assert(rc >= 0); - if (!rc) { - /* Tried to read beyond the end of the file. */ - assert(!done); - memset(buf, 0, block_size); - done += block_size; - } - done += rc; - } while (done < block_size); - - return 0; - -no_block: - memset(buf, 0, block_size); - return 0; -} - -static int fdev_read_blocks(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - const int block_size = dev_get_block_size(dev); - uint64_t pos; - - for (pos = first_pos; pos <= last_pos; pos++) { - int rc = fdev_read_block(dev, buf, pos); - if (rc) - return rc; - buf += block_size; - } - return 0; -} - -static int write_all(int fd, const char *buf, size_t count) -{ - size_t done = 0; - do { - ssize_t rc = write(fd, buf + done, count - done); - if (rc < 0) { - /* The write() failed. */ - return errno; - } - done += rc; - } while (done < count); - return 0; -} - -static int fdev_write_block(struct device *dev, const char *buf, - uint64_t block_pos) -{ - struct file_device *fdev = dev_fdev(dev); - const int block_size = dev_get_block_size(dev); - const int block_order = dev_get_block_order(dev); - off_t off_ret, offset = block_pos << block_order; - - offset &= fdev->address_mask; - if ((uint64_t)offset >= fdev->real_size_byte) { - /* Block beyond real memory. */ - uint64_t cache_pos; - - if (!fdev->cache_blocks) - return 0; /* No cache available. */ - cache_pos = block_pos & fdev->cache_mask; - memmove(&fdev->cache_blocks[cache_pos << block_order], - buf, block_size); - - if (fdev->cache_entries) - fdev->cache_entries[cache_pos] = block_pos; - - return 0; - } - - off_ret = lseek(fdev->fd, offset, SEEK_SET); - if (off_ret < 0) - return - errno; - assert(off_ret == offset); - - return write_all(fdev->fd, buf, block_size); -} - -static int fdev_write_blocks(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - const int block_size = dev_get_block_size(dev); - uint64_t pos; - - for (pos = first_pos; pos <= last_pos; pos++) { - int rc = fdev_write_block(dev, buf, pos); - if (rc) - return rc; - buf += block_size; - } - return 0; -} - -static void fdev_free(struct device *dev) -{ - struct file_device *fdev = dev_fdev(dev); - free(fdev->cache_blocks); - free(fdev->cache_entries); - free((void *)fdev->filename); - assert(!close(fdev->fd)); -} - -static const char *fdev_get_filename(struct device *dev) -{ - return dev_fdev(dev)->filename; -} - -struct device *create_file_device(const char *filename, - uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, - int block_order, int cache_order, int strict_cache, - int keep_file) -{ - struct file_device *fdev; - - fdev = malloc(sizeof(*fdev)); - if (!fdev) - goto error; - - fdev->filename = strdup(filename); - if (!fdev->filename) - goto fdev; - - fdev->cache_mask = 0; - fdev->cache_entries = NULL; - fdev->cache_blocks = NULL; - if (cache_order >= 0) { - fdev->cache_mask = (((uint64_t)1) << cache_order) - 1; - if (strict_cache) { - size_t size = sizeof(*fdev->cache_entries) << - cache_order; - fdev->cache_entries = malloc(size); - if (!fdev->cache_entries) - goto cache; - memset(fdev->cache_entries, 0, size); - } - fdev->cache_blocks = malloc(((uint64_t)1) << - (cache_order + block_order)); - if (!fdev->cache_blocks) - goto cache; - } - - fdev->fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); - if (fdev->fd < 0) { - err(errno, "Can't create file `%s'", filename); - goto cache; - } - if (!keep_file) { - /* Unlinking the file now guarantees that it won't exist if - * there is a crash. - */ - assert(!unlink(filename)); - } - - if (!block_order) { - struct stat fd_stat; - blksize_t block_size; - assert(!fstat(fdev->fd, &fd_stat)); - block_size = fd_stat.st_blksize; - block_order = ilog2(block_size); - assert(block_size == (1 << block_order)); - } - - if (!dev_param_valid(real_size_byte, fake_size_byte, wrap, block_order)) - goto keep_file; - - fdev->real_size_byte = real_size_byte; - fdev->address_mask = (((uint64_t)1) << wrap) - 1; - - fdev->dev.size_byte = fake_size_byte; - fdev->dev.block_order = block_order; - fdev->dev.read_blocks = fdev_read_blocks; - fdev->dev.write_blocks = fdev_write_blocks; - fdev->dev.reset = NULL; - fdev->dev.free = fdev_free; - fdev->dev.get_filename = fdev_get_filename; - - return &fdev->dev; - -keep_file: - if (keep_file) - unlink(filename); - assert(!close(fdev->fd)); -cache: - free(fdev->cache_blocks); - free(fdev->cache_entries); -/* filename: this label is not being used. */ - free((void *)fdev->filename); -fdev: - free(fdev); -error: - return NULL; -} - -struct block_device { - /* This must be the first field. See dev_bdev() for details. */ - struct device dev; - - const char *filename; - int fd; -}; - -static inline struct block_device *dev_bdev(struct device *dev) -{ - return (struct block_device *)dev; -} - -static int read_all(int fd, char *buf, size_t count) -{ - size_t done = 0; - do { - ssize_t rc = read(fd, buf + done, count - done); - if (rc < 0) { - if (errno == EINTR) - continue; - if (errno == EIO || errno == ENODATA) { - /* These errors are "expected", - * so ignore them. - */ - } else { - /* Execution should not come here. */ - err(errno, - "%s(): unexpected error code from read(2) = %i", - __func__, errno); - } - return - errno; - } - assert(rc != 0); /* We should never hit the end of the file. */ - done += rc; - } while (done < count); - return 0; -} - -static int bdev_read_blocks(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct block_device *bdev = dev_bdev(dev); - const int block_order = dev_get_block_order(dev); - size_t length = (last_pos - first_pos + 1) << block_order; - off_t offset = first_pos << block_order; - off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); - if (off_ret < 0) - return - errno; - assert(off_ret == offset); - return read_all(bdev->fd, buf, length); -} - -static int bdev_write_blocks(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct block_device *bdev = dev_bdev(dev); - const int block_order = dev_get_block_order(dev); - size_t length = (last_pos - first_pos + 1) << block_order; - off_t offset = first_pos << block_order; - off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); - int rc; - if (off_ret < 0) - return - errno; - assert(off_ret == offset); - rc = write_all(bdev->fd, buf, length); - if (rc) - return rc; - rc = fsync(bdev->fd); - if (rc) - return rc; - return posix_fadvise(bdev->fd, 0, 0, POSIX_FADV_DONTNEED); -} - -static inline int bdev_open(const char *filename) -{ - return open(filename, O_RDWR | O_DIRECT); -} - -static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev) -{ - struct udev_device *usb_dev; - - /* The device pointed to by dev contains information about - * the USB device. - * In order to get information about the USB device, - * get the parent device with the subsystem/devtype pair of - * "usb"/"usb_device". - * This will be several levels up the tree, - * but the function will find it. - */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - dev, "usb", "usb_device"); - - /* @usb_dev is not referenced, and will be freed when - * the child (i.e. @dev) is freed. - * See udev_device_get_parent_with_subsystem_devtype() for - * details. - */ - return udev_device_ref(usb_dev); -} - -static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) -{ - struct stat fd_stat; - - if (fstat(block_fd, &fd_stat)) { - warn("Can't fstat() FD %i", block_fd); - return NULL; - } - - if (!S_ISBLK(fd_stat.st_mode)) { - warnx("FD %i is not a block device", block_fd); - return NULL; - } - - return udev_device_new_from_devnum(udev, 'b', fd_stat.st_rdev); -} - -static struct udev_monitor *create_monitor(struct udev *udev, - const char *subsystem, const char *devtype) -{ - struct udev_monitor *mon; - int mon_fd, flags; - - mon = udev_monitor_new_from_netlink(udev, "udev"); - assert(mon); - assert(!udev_monitor_filter_add_match_subsystem_devtype(mon, - subsystem, devtype)); - assert(!udev_monitor_enable_receiving(mon)); - mon_fd = udev_monitor_get_fd(mon); - assert(mon_fd >= 0); - flags = fcntl(mon_fd, F_GETFL); - assert(flags >= 0); - assert(!fcntl(mon_fd, F_SETFL, flags & ~O_NONBLOCK)); - - return mon; -} - -static uint64_t get_udev_dev_size_byte(struct udev_device *dev) -{ - const char *str_size_sector = - udev_device_get_sysattr_value(dev, "size"); - char *end; - long long size_sector; - if (!str_size_sector) - return 0; - size_sector = strtoll(str_size_sector, &end, 10); - assert(!*end); - return size_sector * 512LL; -} - -static int wait_for_reset(struct udev *udev, const char *id_serial, - uint64_t original_size_byte, const char **pfinal_dev_filename) -{ - bool done = false, went_to_zero = false, already_changed_size = false; - struct udev_monitor *mon; - int rc; - - mon = create_monitor(udev, "block", "disk"); - if (!mon) { - warnx("%s(): Can't instantiate a monitor", __func__); - rc = - ENOMEM; - goto out; - } - - do { - struct udev_device *dev; - const char *dev_id_serial, *action; - uint64_t new_size_byte; - const char *devnode; - - dev = udev_monitor_receive_device(mon); - if (!dev) { - warnx("%s(): Can't monitor device", __func__); - rc = - ENOMEM; - goto mon; - } - dev_id_serial = udev_device_get_property_value(dev, - "ID_SERIAL"); - if (!dev_id_serial || strcmp(dev_id_serial, id_serial)) - goto next; - - action = udev_device_get_action(dev); - new_size_byte = get_udev_dev_size_byte(dev); - if (!strcmp(action, "add")) { - /* Deal with the case in which the user pulls - * the USB device. - * - * DO NOTHING. - */ - } else if (!strcmp(action, "change")) { - /* Deal with the case in which the user pulls - * the memory card from the card reader. - */ - - if (!new_size_byte) { - /* Memory card removed. */ - went_to_zero = true; - goto next; - } - - if (!went_to_zero) - goto next; - } else { - /* Ignore all other actions. */ - goto next; - } - - if (new_size_byte != original_size_byte) { - /* This is an edge case. */ - - if (!already_changed_size) { - already_changed_size = true; - went_to_zero = false; - printf("\nThe drive changed its size of %" - PRIu64 " Bytes to %" PRIu64 - " Bytes after the reset.\nPlease try to unplug and plug it back again...", - original_size_byte, new_size_byte); - fflush(stdout); - goto next; - } - - printf("\nThe reset failed. The drive has not returned to its original size.\n\n"); - fflush(stdout); - rc = - ENXIO; - goto mon; - } - - devnode = strdup(udev_device_get_devnode(dev)); - if (!devnode) { - warnx("%s(): Out of memory", __func__); - rc = - ENOMEM; - goto mon; - } - free((void *)*pfinal_dev_filename); - *pfinal_dev_filename = devnode; - done = true; - -next: - udev_device_unref(dev); - } while (!done); - - rc = 0; - -mon: - assert(!udev_monitor_unref(mon)); -out: - return rc; -} - -static int bdev_manual_usb_reset(struct device *dev) -{ - struct block_device *bdev = dev_bdev(dev); - struct udev *udev; - struct udev_device *udev_dev, *usb_dev; - const char *id_serial; - int rc; - - if (bdev->fd < 0) { - /* We don't have a device open. - * This can happen when the previous reset failed, and - * a reset is being called again. - */ - rc = - EBADF; - goto out; - } - - udev = udev_new(); - if (!udev) { - warnx("Can't load library udev"); - rc = - EOPNOTSUPP; - goto out; - } - - /* Identify which drive we are going to reset. */ - udev_dev = dev_from_block_fd(udev, bdev->fd); - if (!udev_dev) { - warnx("Library udev can't find device `%s'", - dev_get_filename(dev)); - rc = - EINVAL; - goto udev; - } - usb_dev = map_dev_to_usb_dev(udev_dev); - if (!usb_dev) { - warnx("Block device `%s' is not backed by a USB device", - dev_get_filename(dev)); - rc = - EINVAL; - goto udev_dev; - } - id_serial = udev_device_get_property_value(udev_dev, "ID_SERIAL"); - if (!id_serial) { - warnx("%s(): Out of memory", __func__); - rc = - ENOMEM; - goto usb_dev; - } - - /* Close @bdev->fd before the drive is removed to increase - * the chance that the device will receive the same filename. - * The code is robust enough to deal with the case the drive doesn't - * receive the same file name, though. - */ - assert(!close(bdev->fd)); - bdev->fd = -1; - - printf("Please unplug and plug back the USB drive. Waiting..."); - fflush(stdout); - rc = wait_for_reset(udev, id_serial, dev_get_size_byte(dev), - &bdev->filename); - if (rc) { - assert(rc < 0); - goto usb_dev; - } - printf(" Thanks\n\n"); - - bdev->fd = bdev_open(bdev->filename); - if (bdev->fd < 0) { - rc = - errno; - warn("Can't reopen device `%s'", bdev->filename); - goto usb_dev; - } - - rc = 0; - -usb_dev: - udev_device_unref(usb_dev); -udev_dev: - udev_device_unref(udev_dev); -udev: - assert(!udev_unref(udev)); -out: - return rc; -} - -static struct udev_device *map_block_to_usb_dev(struct udev *udev, int block_fd) -{ - struct udev_device *dev, *usb_dev; - - dev = dev_from_block_fd(udev, block_fd); - if (!dev) - return NULL; - usb_dev = map_dev_to_usb_dev(dev); - udev_device_unref(dev); - return usb_dev; -} - -/* Return an open fd to the underlying hardware of the block device. */ -static int usb_fd_from_block_dev(int block_fd, int open_flags) -{ - struct udev *udev; - struct udev_device *usb_dev; - const char *usb_filename; - int usb_fd; - - udev = udev_new(); - if (!udev) { - warnx("Can't load library udev"); - usb_fd = -EOPNOTSUPP; - goto out; - } - - usb_dev = map_block_to_usb_dev(udev, block_fd); - if (!usb_dev) { - warnx("Block device is not backed by a USB device"); - usb_fd = -EINVAL; - goto udev; - } - - usb_filename = udev_device_get_devnode(usb_dev); - if (!usb_filename) { - warnx("%s(): Out of memory", __func__); - usb_fd = -ENOMEM; - goto usb_dev; - } - - usb_fd = open(usb_filename, open_flags | O_NONBLOCK); - if (usb_fd < 0) { - usb_fd = - errno; - warn("Can't open device `%s'", usb_filename); - goto usb_dev; - } - -usb_dev: - udev_device_unref(usb_dev); -udev: - assert(!udev_unref(udev)); -out: - return usb_fd; -} - -static int bdev_usb_reset(struct device *dev) -{ - struct block_device *bdev = dev_bdev(dev); - int usb_fd; - - if (bdev->fd < 0) { - /* We don't have a device open. - * This can happen when the previous reset failed, and - * a reset is being called again. - */ - return - EBADF; - } - - usb_fd = usb_fd_from_block_dev(bdev->fd, O_WRONLY); - if (usb_fd < 0) - return usb_fd; - - assert(!close(bdev->fd)); - bdev->fd = -1; - assert(!ioctl(usb_fd, USBDEVFS_RESET)); - assert(!close(usb_fd)); - bdev->fd = bdev_open(bdev->filename); - if (bdev->fd < 0) { - int rc = - errno; - warn("Can't reopen device `%s'", bdev->filename); - return rc; - } - return 0; -} - -static int bdev_none_reset(struct device *dev) -{ - UNUSED(dev); - return 0; -} - -static void bdev_free(struct device *dev) -{ - struct block_device *bdev = dev_bdev(dev); - if (bdev->fd >= 0) - assert(!close(bdev->fd)); - free((void *)bdev->filename); -} - -static const char *bdev_get_filename(struct device *dev) -{ - return dev_bdev(dev)->filename; -} - -static struct udev_device *map_partition_to_disk(struct udev_device *dev) -{ - struct udev_device *disk_dev; - - disk_dev = udev_device_get_parent_with_subsystem_devtype( - dev, "block", "disk"); - - /* @disk_dev is not referenced, and will be freed when - * the child (i.e. @dev) is freed. - * See udev_device_get_parent_with_subsystem_devtype() for - * details. - */ - return udev_device_ref(disk_dev); -} - -/* XXX This is borrowing from glibc. - * A better solution would be to return proper errors, - * so callers write their own messages. - */ -extern const char *__progname; - -struct device *create_block_device(const char *filename, enum reset_type rt) -{ - struct block_device *bdev; - struct udev *udev; - struct udev_device *fd_dev; - const char *s; - int block_size, block_order; - - bdev = malloc(sizeof(*bdev)); - if (!bdev) - goto error; - - bdev->filename = strdup(filename); - if (!bdev->filename) - goto bdev; - - bdev->fd = bdev_open(filename); - if (bdev->fd < 0) { - if (errno == EACCES && getuid()) { - fprintf(stderr, "Your user doesn't have access to device `%s'.\n" - "Try to run this program as root:\n" - "sudo %s %s\n" - "In case you don't have access to root, use f3write/f3read.\n", - filename, __progname, filename); - } else { - err(errno, "Can't open device `%s'", filename); - } - goto filename; - } - - /* Make sure that @bdev->fd is a disk, not a partition. */ - udev = udev_new(); - if (!udev) { - warnx("Can't load library udev"); - goto fd; - } - fd_dev = dev_from_block_fd(udev, bdev->fd); - if (!fd_dev) { - fprintf(stderr, "Can't create udev device from `%s'\n", - filename); - goto udev; - } - assert(!strcmp(udev_device_get_subsystem(fd_dev), "block")); - s = udev_device_get_devtype(fd_dev); - if (!strcmp(s, "partition")) { - struct udev_device *disk_dev = map_partition_to_disk(fd_dev); - assert(disk_dev); - s = udev_device_get_devnode(disk_dev); - fprintf(stderr, "Device `%s' is a partition of disk device `%s'.\n" - "You must run %s on the disk device as follows:\n" - "%s %s\n", - filename, s, __progname, __progname, s); - udev_device_unref(disk_dev); - goto fd_dev; - } else if (strcmp(s, "disk")) { - fprintf(stderr, "Device `%s' is not a disk, but `%s'", - filename, s); - goto fd_dev; - } - - if (rt != RT_NONE) { - /* Make sure that @bdev->fd is backed by a USB device. */ - struct udev_device *usb_dev = map_dev_to_usb_dev(fd_dev); - if (!usb_dev) { - fprintf(stderr, - "Device `%s' is not backed by a USB device.\n" - "You must disable reset, run %s as follows:\n" - "%s --reset-type=%i %s\n", - filename, __progname, __progname, RT_NONE, - filename); - goto fd_dev; - } - udev_device_unref(usb_dev); - } - udev_device_unref(fd_dev); - assert(!udev_unref(udev)); - - switch (rt) { - case RT_MANUAL_USB: - bdev->dev.reset = bdev_manual_usb_reset; - break; - case RT_USB: - bdev->dev.reset = bdev_usb_reset; - break; - case RT_NONE: - bdev->dev.reset = bdev_none_reset; - break; - default: - assert(0); - } - - assert(!ioctl(bdev->fd, BLKGETSIZE64, &bdev->dev.size_byte)); - - assert(!ioctl(bdev->fd, BLKSSZGET, &block_size)); - block_order = ilog2(block_size); - assert(block_size == (1 << block_order)); - bdev->dev.block_order = block_order; - - bdev->dev.read_blocks = bdev_read_blocks; - bdev->dev.write_blocks = bdev_write_blocks; - bdev->dev.free = bdev_free; - bdev->dev.get_filename = bdev_get_filename; - - return &bdev->dev; - -fd_dev: - udev_device_unref(fd_dev); -udev: - assert(!udev_unref(udev)); -fd: - assert(!close(bdev->fd)); -filename: - free((void *)bdev->filename); -bdev: - free(bdev); -error: - return NULL; -} - -struct perf_device { - /* This must be the first field. See dev_pdev() for details. */ - struct device dev; - - struct device *shadow_dev; - - uint64_t read_count; - uint64_t read_time_us; - uint64_t write_count; - uint64_t write_time_us; - uint64_t reset_count; - uint64_t reset_time_us; -}; - -static inline struct perf_device *dev_pdev(struct device *dev) -{ - return (struct perf_device *)dev; -} - -static int pdev_read_blocks(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct perf_device *pdev = dev_pdev(dev); - struct timeval t1, t2; - int rc; - - assert(!gettimeofday(&t1, NULL)); - rc = pdev->shadow_dev->read_blocks(pdev->shadow_dev, buf, - first_pos, last_pos); - assert(!gettimeofday(&t2, NULL)); - pdev->read_count += last_pos - first_pos + 1; - pdev->read_time_us += diff_timeval_us(&t1, &t2); - return rc; -} - -static int pdev_write_blocks(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct perf_device *pdev = dev_pdev(dev); - struct timeval t1, t2; - int rc; - - assert(!gettimeofday(&t1, NULL)); - rc = pdev->shadow_dev->write_blocks(pdev->shadow_dev, buf, - first_pos, last_pos); - assert(!gettimeofday(&t2, NULL)); - pdev->write_count += last_pos - first_pos + 1; - pdev->write_time_us += diff_timeval_us(&t1, &t2); - return rc; -} - -static int pdev_reset(struct device *dev) -{ - struct perf_device *pdev = dev_pdev(dev); - struct timeval t1, t2; - int rc; - - assert(!gettimeofday(&t1, NULL)); - rc = dev_reset(pdev->shadow_dev); - assert(!gettimeofday(&t2, NULL)); - pdev->reset_count++; - pdev->reset_time_us += diff_timeval_us(&t1, &t2); - return rc; -} - -static void pdev_free(struct device *dev) -{ - struct perf_device *pdev = dev_pdev(dev); - free_device(pdev->shadow_dev); -} - -static const char *pdev_get_filename(struct device *dev) -{ - return dev_get_filename(dev_pdev(dev)->shadow_dev); -} - -struct device *pdev_detach_and_free(struct device *dev) -{ - struct perf_device *pdev = dev_pdev(dev); - struct device *shadow_dev = pdev->shadow_dev; - pdev->shadow_dev = NULL; - pdev->dev.free = NULL; - free_device(&pdev->dev); - return shadow_dev; -} - -struct device *create_perf_device(struct device *dev) -{ - struct perf_device *pdev; - - pdev = malloc(sizeof(*pdev)); - if (!pdev) - return NULL; - - pdev->shadow_dev = dev; - pdev->read_count = 0; - pdev->read_time_us = 0; - pdev->write_count = 0; - pdev->write_time_us = 0; - pdev->reset_count = 0; - pdev->reset_time_us = 0; - - pdev->dev.size_byte = dev->size_byte; - pdev->dev.block_order = dev->block_order; - pdev->dev.read_blocks = pdev_read_blocks; - pdev->dev.write_blocks = pdev_write_blocks; - pdev->dev.reset = pdev_reset; - pdev->dev.free = pdev_free; - pdev->dev.get_filename = pdev_get_filename; - - return &pdev->dev; -} - -void perf_device_sample(struct device *dev, - uint64_t *pread_count, uint64_t *pread_time_us, - uint64_t *pwrite_count, uint64_t *pwrite_time_us, - uint64_t *preset_count, uint64_t *preset_time_us) -{ - struct perf_device *pdev = dev_pdev(dev); - - if (pread_count) - *pread_count = pdev->read_count; - if (pread_time_us) - *pread_time_us = pdev->read_time_us; - - if (pwrite_count) - *pwrite_count = pdev->write_count; - if (pwrite_time_us) - *pwrite_time_us = pdev->write_time_us; - - if (preset_count) - *preset_count = pdev->reset_count; - if (preset_time_us) - *preset_time_us = pdev->reset_time_us; -} - -#define SDEV_BITMAP_WORD long -#define SDEV_BITMAP_BITS_PER_WORD (8*sizeof(SDEV_BITMAP_WORD)) -struct safe_device { - /* This must be the first field. See dev_sdev() for details. */ - struct device dev; - - struct device *shadow_dev; - - char *saved_blocks; - uint64_t *sb_positions; - SDEV_BITMAP_WORD *sb_bitmap; - uint64_t sb_n; - uint64_t sb_max; -}; - -static inline struct safe_device *dev_sdev(struct device *dev) -{ - return (struct safe_device *)dev; -} - -static int sdev_read_blocks(struct device *dev, char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct safe_device *sdev = dev_sdev(dev); - return sdev->shadow_dev->read_blocks(sdev->shadow_dev, buf, - first_pos, last_pos); -} - -static int sdev_is_block_saved(struct safe_device *sdev, uint64_t pos) -{ - lldiv_t idx; - SDEV_BITMAP_WORD set_bit; - - if (!sdev->sb_bitmap) { - uint64_t i; - /* Running without bitmap. */ - for (i = 0; i < sdev->sb_n; i++) - if (sdev->sb_positions[i] == pos) { - /* The block is already saved. */ - return true; - } - return false; - } - - idx = lldiv(pos, SDEV_BITMAP_BITS_PER_WORD); - set_bit = (SDEV_BITMAP_WORD)1 << idx.rem; - return !!(sdev->sb_bitmap[idx.quot] & set_bit); -} - -static void sdev_mark_blocks(struct safe_device *sdev, - uint64_t first_pos, uint64_t last_pos) -{ - uint64_t pos; - - for (pos = first_pos; pos <= last_pos; pos++) { - if (sdev->sb_bitmap) { - lldiv_t idx = lldiv(pos, SDEV_BITMAP_BITS_PER_WORD); - SDEV_BITMAP_WORD set_bit = (SDEV_BITMAP_WORD)1 << - idx.rem; - sdev->sb_bitmap[idx.quot] |= set_bit; - } - sdev->sb_positions[sdev->sb_n] = pos; - sdev->sb_n++; - } -} - -/* Load blocks into cache. */ -static int sdev_load_blocks(struct safe_device *sdev, - uint64_t first_pos, uint64_t last_pos) -{ - const int block_order = dev_get_block_order(sdev->shadow_dev); - char *block_buf = (char *)align_mem(sdev->saved_blocks, block_order) + - (sdev->sb_n << block_order); - int rc; - - assert(sdev->sb_n + (last_pos - first_pos + 1) < sdev->sb_max); - - rc = sdev->shadow_dev->read_blocks(sdev->shadow_dev, block_buf, - first_pos, last_pos); - if (rc) - return rc; - - /* Bookkeeping. */ - sdev_mark_blocks(sdev, first_pos, last_pos); - return 0; -} - -static int sdev_save_block(struct safe_device *sdev, - uint64_t first_pos, uint64_t last_pos) -{ - uint64_t pos, start_pos; - int rc; - - start_pos = first_pos; - for (pos = first_pos; pos <= last_pos; pos++) { - if (sdev_is_block_saved(sdev, pos)) { - if (start_pos < pos) { - /* The blocks haven't been saved before. - * Save them now. - */ - rc = sdev_load_blocks(sdev, start_pos, pos - 1); - if (rc) - return rc; - } else if (start_pos == pos) { - /* Do nothing. */ - } else { - assert(0); - } - start_pos = pos + 1; - } - } - - if (start_pos <= last_pos) { - rc = sdev_load_blocks(sdev, start_pos, last_pos); - if (rc) - return rc; - } - - return 0; -} - -static int sdev_write_blocks(struct device *dev, const char *buf, - uint64_t first_pos, uint64_t last_pos) -{ - struct safe_device *sdev = dev_sdev(dev); - int rc = sdev_save_block(sdev, first_pos, last_pos); - - if (rc) - return rc; - - return sdev->shadow_dev->write_blocks(sdev->shadow_dev, buf, - first_pos, last_pos); -} - -static int sdev_reset(struct device *dev) -{ - return dev_reset(dev_sdev(dev)->shadow_dev); -} - -static void sdev_carefully_recover(struct safe_device *sdev, char *buffer, - uint64_t first_pos, uint64_t last_pos) -{ - const int block_size = dev_get_block_size(sdev->shadow_dev); - uint64_t pos; - int rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, - buffer, first_pos, last_pos); - if (!rc) - return; - - for (pos = first_pos; pos <= last_pos; pos++) { - int rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, - buffer, pos, pos); - if (rc) { - /* Do not abort, try to recover all bocks. */ - warn("Failed to recover block 0x%" PRIx64 - " due to a write error", pos); - } - buffer += block_size; - } -} - -static uint64_t sdev_bitmap_length(struct device *dev) -{ - const int block_order = dev_get_block_order(dev); - lldiv_t idx = lldiv(dev_get_size_byte(dev) >> block_order, - SDEV_BITMAP_BITS_PER_WORD); - return (idx.quot + (idx.rem ? 1 : 0)) * sizeof(SDEV_BITMAP_WORD); -} - -void sdev_recover(struct device *dev, uint64_t very_last_pos) -{ - struct safe_device *sdev = dev_sdev(dev); - const int block_order = dev_get_block_order(sdev->shadow_dev); - char *first_block = align_mem(sdev->saved_blocks, block_order); - uint64_t i, first_pos, last_pos; - char *start_buf; - int has_seq; - - has_seq = false; - for (i = 0; i < sdev->sb_n; i++) { - uint64_t pos = sdev->sb_positions[i]; - - if (!has_seq) { - if (pos > very_last_pos) - continue; - - last_pos = first_pos = pos; - start_buf = first_block + (i << block_order); - has_seq = true; - continue; - } - - if (pos <= very_last_pos && pos == last_pos + 1) { - last_pos++; - continue; - } - - sdev_carefully_recover(sdev, start_buf, first_pos, last_pos); - - has_seq = pos <= very_last_pos; - if (has_seq) { - last_pos = first_pos = pos; - start_buf = first_block + (i << block_order); - } - } - - if (has_seq) { - sdev_carefully_recover(sdev, start_buf, first_pos, last_pos); - has_seq = false; - } -} - -void sdev_flush(struct device *dev) -{ - struct safe_device *sdev = dev_sdev(dev); - - if (sdev->sb_n <= 0) - return; - - sdev->sb_n = 0; - - if (sdev->sb_bitmap) - memset(sdev->sb_bitmap, 0, - sdev_bitmap_length(sdev->shadow_dev)); -} - -static void sdev_free(struct device *dev) -{ - struct safe_device *sdev = dev_sdev(dev); - - sdev_recover(dev, UINT_LEAST64_MAX); - sdev_flush(dev); - - free(sdev->sb_bitmap); - free(sdev->sb_positions); - free(sdev->saved_blocks); - free_device(sdev->shadow_dev); -} - -static const char *sdev_get_filename(struct device *dev) -{ - return dev_get_filename(dev_sdev(dev)->shadow_dev); -} - -struct device *create_safe_device(struct device *dev, uint64_t max_blocks, - int min_memory) -{ - struct safe_device *sdev; - const int block_order = dev_get_block_order(dev); - uint64_t length; - - sdev = malloc(sizeof(*sdev)); - if (!sdev) - goto error; - - length = align_head(block_order) + (max_blocks << block_order); - sdev->saved_blocks = malloc(length); - if (!sdev->saved_blocks) - goto sdev; - - sdev->sb_positions = malloc(max_blocks * sizeof(*sdev->sb_positions)); - if (!sdev->sb_positions) - goto saved_blocks; - - if (!min_memory) { - length = sdev_bitmap_length(dev); - sdev->sb_bitmap = malloc(length); - if (!sdev->sb_bitmap) - goto offsets; - memset(sdev->sb_bitmap, 0, length); - } else { - sdev->sb_bitmap = NULL; - } - - sdev->shadow_dev = dev; - sdev->sb_n = 0; - sdev->sb_max = max_blocks; - - sdev->dev.size_byte = dev->size_byte; - sdev->dev.block_order = block_order; - sdev->dev.read_blocks = sdev_read_blocks; - sdev->dev.write_blocks = sdev_write_blocks; - sdev->dev.reset = sdev_reset; - sdev->dev.free = sdev_free; - sdev->dev.get_filename = sdev_get_filename; - - return &sdev->dev; - -offsets: - free(sdev->sb_positions); -saved_blocks: - free(sdev->saved_blocks); -sdev: - free(sdev); -error: - return NULL; -} diff --git a/f3write.h2w b/scripts/f3write.h2w similarity index 100% rename from f3write.h2w rename to scripts/f3write.h2w diff --git a/log-f3wr b/scripts/log-f3wr similarity index 100% rename from log-f3wr rename to scripts/log-f3wr diff --git a/f3brew.c b/src/f3-extra/f3brew.c similarity index 99% rename from f3brew.c rename to src/f3-extra/f3brew.c index 34047b2..285932b 100644 --- a/f3brew.c +++ b/src/f3-extra/f3brew.c @@ -7,9 +7,9 @@ #include #include -#include "version.h" -#include "libutils.h" -#include "libdevs.h" +#include +#include +#include /* Argp's global variables. */ const char *argp_program_version = "F3 BREW " F3_STR_VERSION; diff --git a/src/f3-extra/f3fix.c b/src/f3-extra/f3fix.c new file mode 100644 index 0000000..dc7de9e --- /dev/null +++ b/src/f3-extra/f3fix.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Sentinel for unset last sector. */ +#define SEC_UNSET ((uint64_t)UINT64_MAX) + +/* Argp's global variables. */ +const char *argp_program_version = "F3 Fix " F3_STR_VERSION; + +/* Arguments. */ +static char adoc[] = ""; + +static char doc[] = "F3 Fix -- edit the partition table of " + "a fake flash drive to have a single partition that fully covers " + "the real capacity of the drive"; + +static struct argp_option options[] = { + {"disk-type", 'd', "TYPE", 0, + "Disk type of the partition table", 2}, + {"fs-type", 'f', "TYPE", 0, + "Type of the file system of the partition", 0}, + {"boot", 'b', NULL, 0, + "Mark the partition for boot", 0}, + {"no-boot", 'n', NULL, 0, + "Do not mark the partition for boot", 0}, + {"first-sec", 'a', "SEC-NUM", 0, + "Sector where the partition starts", 0}, + {"last-sec", 'l', "SEC-NUM", 0, + "Sector where the partition ends", 0}, + {"list-disk-types", 'k', NULL, 0, + "List all supported disk types", 3}, + {"list-fs-types", 's', NULL, 0, + "List all supported types of file systems", 0}, + { 0 } +}; + +struct args { + bool list_disk_types; + bool list_fs_types; + + bool boot; + bool boot_flag_seen; + + /* 28 free bytes. */ + + const char *dev_filename; + const char *disk_type; + const char *fs_type; + uint64_t first_sec; + uint64_t last_sec; +}; + +static uint64_t arg_to_uint64(const struct argp_state *state, const char *arg) +{ + char *end; + errno = 0; + + if (*arg == '-') + argp_error(state, "value must be non-negative"); + + unsigned long long v = strtoull(arg, &end, 0); + + /* Reject empty string, garbage suffix, or overflow. */ + if (end == arg || *end || errno == ERANGE) + argp_error(state, "`%s' is not a valid unsigned integer", arg); + +#if ULLONG_MAX > UINT64_MAX + /* Reject overflow in exotic systems. */ + if (v > UINT64_MAX) + argp_error(state, "`%s' is too large; maximum is %llu", + arg, (unsigned long long)UINT64_MAX); +#endif + return (uint64_t)v; +} + +static error_t parse_opt(int key, char *arg, struct argp_state *state) +{ + struct args *args = state->input; + + switch (key) { + case 'd': /* disk-type */ + args->disk_type = arg; + if (!is_valid_disk_type(arg)) + argp_error(state, + "Disk type `%s' is not supported; " + "use --list-disk-types to see the supported types", + arg); + break; + + case 'f': /* fs-type */ + args->fs_type = arg; + if (!is_valid_fs_type(arg)) + argp_error(state, + "File system type `%s' is not supported; " + "use --list-fs-types to see the supported types", + arg); + break; + + case 'b': /* boot */ + case 'n': /* no-boot */ + if (args->boot_flag_seen) + argp_error(state, + "Options --boot and --no-boot are mutually exclusive " + "and may be given only once"); + args->boot_flag_seen = true; + args->boot = (key == 'b'); + break; + + case 'a': /* first-sec */ + args->first_sec = arg_to_uint64(state, arg); + break; + + case 'l': /* last-sec */ + args->last_sec = arg_to_uint64(state, arg); + break; + + case 'k': /* list-disk-types */ + args->list_disk_types = true; + break; + + case 's': /* list-fs-types */ + args->list_fs_types = true; + break; + + case ARGP_KEY_INIT: + args->dev_filename = NULL; + args->last_sec = SEC_UNSET; + break; + + case ARGP_KEY_ARG: + if (args->dev_filename) + argp_error(state, + "Wrong number of arguments; only one is allowed"); + args->dev_filename = arg; + break; + + case ARGP_KEY_END: + if (args->list_disk_types || args->list_fs_types) + break; + + if (!args->dev_filename) + argp_error(state, + "The disk device was not specified"); + + if (args->last_sec == SEC_UNSET) + argp_error(state, + "Option --last-sec is required"); + if (args->first_sec > args->last_sec) + argp_error(state, + "Option --first_sec must be less or equal to option --last_sec"); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL}; + +static void print_array(const char *title, size_t (*getter)(char ***)) +{ + char **list = NULL; + size_t n = getter(&list); + if (!list) { + fprintf(stderr, "Failed to obtain %s\n", title); + return; + } + + printf("%s:\n", title); + for (size_t i = 0; list[i]; ++i) { + printf("%s\t", list[i]); + if (i % 5 == 4) + putchar('\n'); + } + if (n % 5) + putchar('\n'); + putchar('\n'); + + partition_free_types_array(list); +} + +static void list_disk_types(void) +{ + print_array("Disk types", partition_list_disk_types); +} + +static void list_fs_types(void) +{ + print_array("File system types", partition_list_fs_types); +} + +int main (int argc, char *argv[]) +{ + struct args args = { + /* Defaults. */ + .list_disk_types = false, + .list_fs_types = false, + + .boot = true, + .boot_flag_seen = false, + + .disk_type = "msdos", + .fs_type = "fat32", + .first_sec = 2048, /* Skip first 1MB. */ + }; + + struct device *bdev; + struct partition_options opt; + int err; + + /* Read parameters. */ + argp_parse(&argp, argc, argv, 0, NULL, &args); + print_header(stdout, "fix"); + + if (args.list_disk_types) + list_disk_types(); + + if (args.list_fs_types) + list_fs_types(); + + if (args.list_disk_types || args.list_fs_types) { + /* If the user has asked for the types, + * she doesn't want to fix the drive yet. + */ + return 0; + } + + /* XXX If @dev is a partition, refer the user to + * the disk of this partition. + * create_block_device() will fail if @dev is a partition. + */ + bdev = create_block_device(args.dev_filename, RT_NONE); + if (!bdev) { + fprintf(stderr, "Failed to open device %s\n", args.dev_filename); + return 1; + } + + /* Get and copy the filename before freeing the device. */ + const char *dev_filename = strdup(dev_get_filename(bdev)); + if (!dev_filename) { + fprintf(stderr, "Failed to duplicate device filename\n"); + free_device(bdev); + return 1; + } + free_device(bdev); + + opt = (struct partition_options){ + .disk_type = args.disk_type, + .fs_type = args.fs_type, + .boot = args.boot, + .first_sector = args.first_sec, + .last_sector = args.last_sec, + }; + err = partition_create(dev_filename, &opt); + if (err == 0) { + printf("Drive `%s' was successfully fixed\n", dev_filename); + } else { + fprintf(stderr, "Failed to fix drive `%s'\n", dev_filename); + } + return err; +} diff --git a/f3probe.c b/src/f3-extra/f3probe.c similarity index 99% rename from f3probe.c rename to src/f3-extra/f3probe.c index ef653ab..f003c1b 100644 --- a/f3probe.c +++ b/src/f3-extra/f3probe.c @@ -1,5 +1,3 @@ -#define _POSIX_C_SOURCE 200809L - #include #include #include @@ -9,9 +7,9 @@ #include #include -#include "version.h" -#include "libprobe.h" -#include "libutils.h" +#include +#include +#include /* Argp's global variables. */ const char *argp_program_version = "F3 Probe " F3_STR_VERSION; diff --git a/src/f3-extra/lib/file_device.c b/src/f3-extra/lib/file_device.c new file mode 100644 index 0000000..27f99c3 --- /dev/null +++ b/src/f3-extra/lib/file_device.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct file_device { + /* This must be the first field. See dev_fdev() for details. */ + struct device dev; + + const char *filename; + int fd; + uint64_t real_size_byte; + uint64_t address_mask; + uint64_t cache_mask; + uint64_t *cache_entries; + char *cache_blocks; +}; + +static inline struct file_device *dev_fdev(struct device *dev) +{ + return (struct file_device *)dev; +} + +static int fdev_read_block(struct device *dev, char *buf, uint64_t block_pos) +{ + struct file_device *fdev = dev_fdev(dev); + const int block_size = dev_get_block_size(dev); + const int block_order = dev_get_block_order(dev); + off_t off_ret, offset = block_pos << block_order; + int done; + + offset &= fdev->address_mask; + if ((uint64_t)offset >= fdev->real_size_byte) { + uint64_t cache_pos; + + if (!fdev->cache_blocks) + goto no_block; /* No cache available. */ + + cache_pos = block_pos & fdev->cache_mask; + + if (fdev->cache_entries && + fdev->cache_entries[cache_pos] != block_pos) + goto no_block; + + memmove(buf, &fdev->cache_blocks[cache_pos << block_order], + block_size); + return 0; + } + + off_ret = lseek(fdev->fd, offset, SEEK_SET); + if (off_ret < 0) + return - errno; + assert(off_ret == offset); + + done = 0; + do { + ssize_t rc = read(fdev->fd, buf + done, block_size - done); + assert(rc >= 0); + if (!rc) { + /* Tried to read beyond the end of the file. */ + assert(!done); + memset(buf, 0, block_size); + done += block_size; + } + done += rc; + } while (done < block_size); + + return 0; + +no_block: + memset(buf, 0, block_size); + return 0; +} + +static int fdev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + const int block_size = dev_get_block_size(dev); + uint64_t pos; + + for (pos = first_pos; pos <= last_pos; pos++) { + int rc = fdev_read_block(dev, buf, pos); + if (rc) + return rc; + buf += block_size; + } + return 0; +} + +static int write_all(int fd, const char *buf, size_t count) +{ + size_t done = 0; + do { + ssize_t rc = write(fd, buf + done, count - done); + if (rc < 0) { + /* The write() failed. */ + return errno; + } + done += rc; + } while (done < count); + return 0; +} + +static int fdev_write_block(struct device *dev, const char *buf, + uint64_t block_pos) +{ + struct file_device *fdev = dev_fdev(dev); + const int block_size = dev_get_block_size(dev); + const int block_order = dev_get_block_order(dev); + off_t off_ret, offset = block_pos << block_order; + + offset &= fdev->address_mask; + if ((uint64_t)offset >= fdev->real_size_byte) { + /* Block beyond real memory. */ + uint64_t cache_pos; + + if (!fdev->cache_blocks) + return 0; /* No cache available. */ + cache_pos = block_pos & fdev->cache_mask; + memmove(&fdev->cache_blocks[cache_pos << block_order], + buf, block_size); + + if (fdev->cache_entries) + fdev->cache_entries[cache_pos] = block_pos; + + return 0; + } + + off_ret = lseek(fdev->fd, offset, SEEK_SET); + if (off_ret < 0) + return - errno; + assert(off_ret == offset); + + return write_all(fdev->fd, buf, block_size); +} + +static int fdev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + const int block_size = dev_get_block_size(dev); + uint64_t pos; + + for (pos = first_pos; pos <= last_pos; pos++) { + int rc = fdev_write_block(dev, buf, pos); + if (rc) + return rc; + buf += block_size; + } + return 0; +} + +static void fdev_free(struct device *dev) +{ + struct file_device *fdev = dev_fdev(dev); + free(fdev->cache_blocks); + free(fdev->cache_entries); + free((void *)fdev->filename); + assert(!close(fdev->fd)); +} + +static const char *fdev_get_filename(struct device *dev) +{ + return dev_fdev(dev)->filename; +} + +struct device *create_file_device(const char *filename, + uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, + int block_order, int cache_order, int strict_cache, + int keep_file) +{ + struct file_device *fdev; + + fdev = malloc(sizeof(*fdev)); + if (!fdev) + goto error; + + fdev->filename = strdup(filename); + if (!fdev->filename) + goto fdev; + + fdev->cache_mask = 0; + fdev->cache_entries = NULL; + fdev->cache_blocks = NULL; + if (cache_order >= 0) { + fdev->cache_mask = (((uint64_t)1) << cache_order) - 1; + if (strict_cache) { + size_t size = sizeof(*fdev->cache_entries) << + cache_order; + fdev->cache_entries = malloc(size); + if (!fdev->cache_entries) + goto cache; + memset(fdev->cache_entries, 0, size); + } + fdev->cache_blocks = malloc(((uint64_t)1) << + (cache_order + block_order)); + if (!fdev->cache_blocks) + goto cache; + } + + fdev->fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fdev->fd < 0) { + err(errno, "Can't create file `%s'", filename); + goto cache; + } + if (!keep_file) { + /* Unlinking the file now guarantees that it won't exist if + * there is a crash. + */ + assert(!unlink(filename)); + } + + if (!block_order) { + struct stat fd_stat; + blksize_t block_size; + assert(!fstat(fdev->fd, &fd_stat)); + block_size = fd_stat.st_blksize; + block_order = ilog2(block_size); + assert(block_size == (1 << block_order)); + } + + if (!dev_param_valid(real_size_byte, fake_size_byte, wrap, block_order)) + goto keep_file; + + fdev->real_size_byte = real_size_byte; + fdev->address_mask = (((uint64_t)1) << wrap) - 1; + + fdev->dev.size_byte = fake_size_byte; + fdev->dev.block_order = block_order; + fdev->dev.read_blocks = fdev_read_blocks; + fdev->dev.write_blocks = fdev_write_blocks; + fdev->dev.reset = NULL; + fdev->dev.free = fdev_free; + fdev->dev.get_filename = fdev_get_filename; + + return &fdev->dev; + +keep_file: + if (keep_file) + unlink(filename); + assert(!close(fdev->fd)); +cache: + free(fdev->cache_blocks); + free(fdev->cache_entries); +/* filename: this label is not being used. */ + free((void *)fdev->filename); +fdev: + free(fdev); +error: + return NULL; +} diff --git a/src/f3-extra/lib/libdevs.c b/src/f3-extra/lib/libdevs.c new file mode 100644 index 0000000..c3460de --- /dev/null +++ b/src/f3-extra/lib/libdevs.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Map fake_type to string. */ +static const char * const ftype_to_name[FKTY_MAX] = { + [FKTY_GOOD] = "good", + [FKTY_BAD] = "bad", + [FKTY_LIMBO] = "limbo", + [FKTY_WRAPAROUND] = "wraparound", + [FKTY_CHAIN] = "chain", +}; + +const char *fake_type_to_name(enum fake_type fake_type) +{ + assert(fake_type < FKTY_MAX); + return ftype_to_name[fake_type]; +} + +int dev_param_valid(uint64_t real_size_byte, + uint64_t announced_size_byte, int wrap, int block_order) +{ + int block_size; + + /* Check general ranges. */ + if (real_size_byte > announced_size_byte || wrap < 0 || wrap >= 64 || + block_order < 9 || block_order > 20) + return false; + + /* Check alignment of the sizes. */ + block_size = 1 << block_order; + if (real_size_byte % block_size || announced_size_byte % block_size) + return false; + + /* If good, @wrap must make sense. */ + if (real_size_byte == announced_size_byte) { + uint64_t two_wrap = ((uint64_t)1) << wrap; + return announced_size_byte <= two_wrap; + } + + return true; +} + +enum fake_type dev_param_to_type(uint64_t real_size_byte, + uint64_t announced_size_byte, int wrap, int block_order) +{ + uint64_t two_wrap; + + assert(dev_param_valid(real_size_byte, announced_size_byte, + wrap, block_order)); + + if (real_size_byte == announced_size_byte) + return FKTY_GOOD; + + if (real_size_byte == 0) + return FKTY_BAD; + + /* real_size_byte < announced_size_byte */ + + two_wrap = ((uint64_t)1) << wrap; + if (two_wrap <= real_size_byte) + return FKTY_WRAPAROUND; + if (two_wrap < announced_size_byte) + return FKTY_CHAIN; + return FKTY_LIMBO; +} + +/* Abstract device interface and wrappers. */ +uint64_t dev_get_size_byte(struct device *dev) +{ + return dev->size_byte; +} + +int dev_get_block_order(struct device *dev) +{ + return dev->block_order; +} + +int dev_get_block_size(struct device *dev) +{ + return 1 << dev->block_order; +} + +const char *dev_get_filename(struct device *dev) +{ + return dev->get_filename(dev); +} + +int dev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + if (first_pos > last_pos) + return false; + assert(last_pos < (dev->size_byte >> dev->block_order)); + return dev->read_blocks(dev, buf, first_pos, last_pos); +} + +int dev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + if (first_pos > last_pos) + return false; + assert(last_pos < (dev->size_byte >> dev->block_order)); + return dev->write_blocks(dev, buf, first_pos, last_pos); +} + +int dev_reset(struct device *dev) +{ + return dev->reset ? dev->reset(dev) : 0; +} + +void free_device(struct device *dev) +{ + if (dev->free) + dev->free(dev); + free(dev); +} diff --git a/libprobe.c b/src/f3-extra/lib/libprobe.c similarity index 99% rename from libprobe.c rename to src/f3-extra/lib/libprobe.c index a57fe66..a6c8f33 100644 --- a/libprobe.c +++ b/src/f3-extra/lib/libprobe.c @@ -7,8 +7,8 @@ #include /* For time(). */ #include /* For gettimeofday(). */ -#include "libutils.h" -#include "libprobe.h" +#include +#include static int write_blocks(struct device *dev, uint64_t first_pos, uint64_t last_pos, uint64_t salt) diff --git a/libutils.c b/src/f3-extra/lib/libutils.c similarity index 98% rename from libutils.c rename to src/f3-extra/lib/libutils.c index 6ea3cfd..94b306d 100644 --- a/libutils.c +++ b/src/f3-extra/lib/libutils.c @@ -3,8 +3,8 @@ #include #include -#include "libutils.h" -#include "version.h" +#include +#include /* Count the number of 1 bits. */ static int pop(uint64_t x) @@ -46,7 +46,7 @@ const char *adjust_unit(double *ptr_bytes) int i = 0; double final = *ptr_bytes; - while (i < 7 && final >= 1024) { + while (i < 6 && final >= 1024) { final /= 1024; i++; } diff --git a/src/f3-extra/lib/perf_device.c b/src/f3-extra/lib/perf_device.c new file mode 100644 index 0000000..686dd37 --- /dev/null +++ b/src/f3-extra/lib/perf_device.c @@ -0,0 +1,143 @@ +#include +#include +#include + +#include +#include + +struct perf_device { + /* This must be the first field. See dev_pdev() for details. */ + struct device dev; + + struct device *shadow_dev; + + uint64_t read_count; + uint64_t read_time_us; + uint64_t write_count; + uint64_t write_time_us; + uint64_t reset_count; + uint64_t reset_time_us; +}; + +static inline struct perf_device *dev_pdev(struct device *dev) +{ + return (struct perf_device *)dev; +} + +static int pdev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct perf_device *pdev = dev_pdev(dev); + struct timeval t1, t2; + int rc; + + assert(!gettimeofday(&t1, NULL)); + rc = pdev->shadow_dev->read_blocks(pdev->shadow_dev, buf, + first_pos, last_pos); + assert(!gettimeofday(&t2, NULL)); + pdev->read_count += last_pos - first_pos + 1; + pdev->read_time_us += diff_timeval_us(&t1, &t2); + return rc; +} + +static int pdev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct perf_device *pdev = dev_pdev(dev); + struct timeval t1, t2; + int rc; + + assert(!gettimeofday(&t1, NULL)); + rc = pdev->shadow_dev->write_blocks(pdev->shadow_dev, buf, + first_pos, last_pos); + assert(!gettimeofday(&t2, NULL)); + pdev->write_count += last_pos - first_pos + 1; + pdev->write_time_us += diff_timeval_us(&t1, &t2); + return rc; +} + +static int pdev_reset(struct device *dev) +{ + struct perf_device *pdev = dev_pdev(dev); + struct timeval t1, t2; + int rc; + + assert(!gettimeofday(&t1, NULL)); + rc = dev_reset(pdev->shadow_dev); + assert(!gettimeofday(&t2, NULL)); + pdev->reset_count++; + pdev->reset_time_us += diff_timeval_us(&t1, &t2); + return rc; +} + +static void pdev_free(struct device *dev) +{ + struct perf_device *pdev = dev_pdev(dev); + free_device(pdev->shadow_dev); +} + +static const char *pdev_get_filename(struct device *dev) +{ + return dev_get_filename(dev_pdev(dev)->shadow_dev); +} + +/* Detach underlying device and free the wrapper, returning the original device. */ +static struct device *pdev_detach_and_free(struct device *dev) +{ + struct perf_device *pdev = dev_pdev(dev); + struct device *shadow_dev = pdev->shadow_dev; + pdev->shadow_dev = NULL; + pdev->dev.free = NULL; + free_device(&pdev->dev); + return shadow_dev; +} + +struct device *create_perf_device(struct device *dev) +{ + struct perf_device *pdev; + + pdev = malloc(sizeof(*pdev)); + if (!pdev) + return NULL; + + pdev->shadow_dev = dev; + pdev->read_count = 0; + pdev->read_time_us = 0; + pdev->write_count = 0; + pdev->write_time_us = 0; + pdev->reset_count = 0; + pdev->reset_time_us = 0; + + pdev->dev.size_byte = dev->size_byte; + pdev->dev.block_order = dev->block_order; + pdev->dev.read_blocks = pdev_read_blocks; + pdev->dev.write_blocks = pdev_write_blocks; + pdev->dev.reset = pdev_reset; + pdev->dev.free = pdev_free; + pdev->dev.get_filename = pdev_get_filename; + + return &pdev->dev; +} + +void perf_device_sample(struct device *dev, + uint64_t *pread_count, uint64_t *pread_time_us, + uint64_t *pwrite_count, uint64_t *pwrite_time_us, + uint64_t *preset_count, uint64_t *preset_time_us) +{ + struct perf_device *pdev = dev_pdev(dev); + + if (pread_count) + *pread_count = pdev->read_count; + if (pread_time_us) + *pread_time_us = pdev->read_time_us; + + if (pwrite_count) + *pwrite_count = pdev->write_count; + if (pwrite_time_us) + *pwrite_time_us = pdev->write_time_us; + + if (preset_count) + *preset_count = pdev->reset_count; + if (preset_time_us) + *preset_time_us = pdev->reset_time_us; +} diff --git a/src/f3-extra/lib/safe_device.c b/src/f3-extra/lib/safe_device.c new file mode 100644 index 0000000..0e2a432 --- /dev/null +++ b/src/f3-extra/lib/safe_device.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SDEV_BITMAP_WORD long +#define SDEV_BITMAP_BITS_PER_WORD (8 * sizeof(SDEV_BITMAP_WORD)) + +struct safe_device { + /* This must be the first field. See dev_sdev() for details. */ + struct device dev; + + struct device *shadow_dev; + + char *saved_blocks; + uint64_t *sb_positions; + SDEV_BITMAP_WORD *sb_bitmap; + uint64_t sb_n; + uint64_t sb_max; +}; + +static inline struct safe_device *dev_sdev(struct device *dev) +{ + return (struct safe_device *)dev; +} + +static int sdev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct safe_device *sdev = dev_sdev(dev); + return sdev->shadow_dev->read_blocks(sdev->shadow_dev, buf, + first_pos, last_pos); +} + +static int sdev_is_block_saved(struct safe_device *sdev, uint64_t pos) +{ + lldiv_t idx; + SDEV_BITMAP_WORD set_bit; + + if (!sdev->sb_bitmap) { + uint64_t i; + /* Running without bitmap. */ + for (i = 0; i < sdev->sb_n; i++) + if (sdev->sb_positions[i] == pos) { + /* The block is already saved. */ + return true; + } + return false; + } + + idx = lldiv(pos, SDEV_BITMAP_BITS_PER_WORD); + set_bit = (SDEV_BITMAP_WORD)1 << idx.rem; + return !!(sdev->sb_bitmap[idx.quot] & set_bit); +} + +static void sdev_mark_blocks(struct safe_device *sdev, + uint64_t first_pos, uint64_t last_pos) +{ + uint64_t pos; + + for (pos = first_pos; pos <= last_pos; pos++) { + if (sdev->sb_bitmap) { + lldiv_t idx = lldiv(pos, SDEV_BITMAP_BITS_PER_WORD); + SDEV_BITMAP_WORD set_bit = (SDEV_BITMAP_WORD)1 << + idx.rem; + sdev->sb_bitmap[idx.quot] |= set_bit; + } + sdev->sb_positions[sdev->sb_n] = pos; + sdev->sb_n++; + } +} + +/* Load blocks into cache. */ +static int sdev_load_blocks(struct safe_device *sdev, + uint64_t first_pos, uint64_t last_pos) +{ + const int block_order = dev_get_block_order(sdev->shadow_dev); + char *block_buf = (char *)align_mem(sdev->saved_blocks, block_order) + + (sdev->sb_n << block_order); + int rc; + + assert(sdev->sb_n + (last_pos - first_pos + 1) < sdev->sb_max); + + rc = sdev->shadow_dev->read_blocks(sdev->shadow_dev, block_buf, + first_pos, last_pos); + if (rc) + return rc; + + /* Bookkeeping. */ + sdev_mark_blocks(sdev, first_pos, last_pos); + return 0; +} + +static int sdev_save_block(struct safe_device *sdev, + uint64_t first_pos, uint64_t last_pos) +{ + uint64_t pos, start_pos; + int rc; + + start_pos = first_pos; + for (pos = first_pos; pos <= last_pos; pos++) { + if (sdev_is_block_saved(sdev, pos)) { + if (start_pos < pos) { + /* The blocks haven't been saved before. + * Save them now. + */ + rc = sdev_load_blocks(sdev, start_pos, pos - 1); + if (rc) + return rc; + } else if (start_pos == pos) { + /* Do nothing. */ + } else { + assert(0); + } + start_pos = pos + 1; + } + } + + if (start_pos <= last_pos) { + rc = sdev_load_blocks(sdev, start_pos, last_pos); + if (rc) + return rc; + } + + return 0; +} + +static int sdev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct safe_device *sdev = dev_sdev(dev); + int rc = sdev_save_block(sdev, first_pos, last_pos); + + if (rc) + return rc; + + return sdev->shadow_dev->write_blocks(sdev->shadow_dev, buf, + first_pos, last_pos); +} + +static int sdev_reset(struct device *dev) +{ + return dev_reset(dev_sdev(dev)->shadow_dev); +} + +static void sdev_carefully_recover(struct safe_device *sdev, char *buffer, + uint64_t first_pos, uint64_t last_pos) +{ + const int block_size = dev_get_block_size(sdev->shadow_dev); + uint64_t pos; + int rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, + buffer, first_pos, last_pos); + if (!rc) + return; + + for (pos = first_pos; pos <= last_pos; pos++) { + rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, + buffer, pos, pos); + if (rc) { + /* Do not abort, try to recover all bocks. */ + warn("Failed to recover block 0x%" PRIx64 + " due to a write error", pos); + } + buffer += block_size; + } +} + +static uint64_t sdev_bitmap_length(struct device *dev) +{ + const int block_order = dev_get_block_order(dev); + lldiv_t idx = lldiv(dev_get_size_byte(dev) >> block_order, + SDEV_BITMAP_BITS_PER_WORD); + return (idx.quot + (idx.rem ? 1 : 0)) * sizeof(SDEV_BITMAP_WORD); +} + +void sdev_recover(struct device *dev, uint64_t very_last_pos) +{ + struct safe_device *sdev = dev_sdev(dev); + const int block_order = dev_get_block_order(sdev->shadow_dev); + char *first_block = align_mem(sdev->saved_blocks, block_order); + uint64_t i, first_pos, last_pos; + char *start_buf; + int has_seq; + + has_seq = false; + for (i = 0; i < sdev->sb_n; i++) { + uint64_t pos = sdev->sb_positions[i]; + + if (!has_seq) { + if (pos > very_last_pos) + continue; + + last_pos = first_pos = pos; + start_buf = first_block + (i << block_order); + has_seq = true; + continue; + } + + if (pos <= very_last_pos && pos == last_pos + 1) { + last_pos++; + continue; + } + + sdev_carefully_recover(sdev, start_buf, first_pos, last_pos); + + has_seq = pos <= very_last_pos; + if (has_seq) { + last_pos = first_pos = pos; + start_buf = first_block + (i << block_order); + } + } + + if (has_seq) { + sdev_carefully_recover(sdev, start_buf, first_pos, last_pos); + has_seq = false; + } +} + +void sdev_flush(struct device *dev) +{ + struct safe_device *sdev = dev_sdev(dev); + + if (sdev->sb_n <= 0) + return; + + sdev->sb_n = 0; + + if (sdev->sb_bitmap) + memset(sdev->sb_bitmap, 0, + sdev_bitmap_length(sdev->shadow_dev)); +} + +static void sdev_free(struct device *dev) +{ + struct safe_device *sdev = dev_sdev(dev); + + sdev_recover(dev, UINT_LEAST64_MAX); + sdev_flush(dev); + + free(sdev->sb_bitmap); + free(sdev->sb_positions); + free(sdev->saved_blocks); + free_device(sdev->shadow_dev); +} + +static const char *sdev_get_filename(struct device *dev) +{ + return dev_get_filename(dev_sdev(dev)->shadow_dev); +} + +struct device *create_safe_device(struct device *dev, uint64_t max_blocks, + int min_memory) +{ + struct safe_device *sdev; + const int block_order = dev_get_block_order(dev); + uint64_t length; + + sdev = malloc(sizeof(*sdev)); + if (!sdev) + goto error; + + length = align_head(block_order) + (max_blocks << block_order); + sdev->saved_blocks = malloc(length); + if (!sdev->saved_blocks) + goto sdev; + + sdev->sb_positions = malloc(max_blocks * sizeof(*sdev->sb_positions)); + if (!sdev->sb_positions) + goto saved_blocks; + + if (!min_memory) { + length = sdev_bitmap_length(dev); + sdev->sb_bitmap = malloc(length); + if (!sdev->sb_bitmap) + goto offsets; + memset(sdev->sb_bitmap, 0, length); + } else { + sdev->sb_bitmap = NULL; + } + + sdev->shadow_dev = dev; + sdev->sb_n = 0; + sdev->sb_max = max_blocks; + + sdev->dev.size_byte = dev->size_byte; + sdev->dev.block_order = block_order; + sdev->dev.read_blocks = sdev_read_blocks; + sdev->dev.write_blocks = sdev_write_blocks; + sdev->dev.reset = sdev_reset; + sdev->dev.free = sdev_free; + sdev->dev.get_filename = sdev_get_filename; + + return &sdev->dev; + +offsets: + free(sdev->sb_positions); +saved_blocks: + free(sdev->saved_blocks); +sdev: + free(sdev); +error: + return NULL; +} diff --git a/f3read.c b/src/f3/f3read.c similarity index 97% rename from f3read.c rename to src/f3/f3read.c index 1364218..a2cdf7c 100644 --- a/f3read.c +++ b/src/f3/f3read.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include @@ -18,9 +15,9 @@ #include #include -#include "utils.h" -#include "libflow.h" -#include "version.h" +#include +#include +#include /* Argp's global variables. */ const char *argp_program_version = "F3 Read " F3_STR_VERSION; @@ -259,8 +256,8 @@ static void validate_file(const char *path, int number, struct flow *fw, * even when testing small memory cards without a remount, and * we should have a better reading-speed measurement. */ - if (fdatasync(fd) < 0) { - int saved_errno = errno; + if (f3_fdatasync(fd) < 0) { + saved_errno = errno; /* The issue https://github.com/AltraMayor/f3/issues/211 * motivated the warning below. */ @@ -268,10 +265,10 @@ static void validate_file(const char *path, int number, struct flow *fw, saved_errno, strerror(saved_errno)); exit(saved_errno); } - assert(!posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)); + assert(!f3_posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)); /* Help the kernel to help us. */ - assert(!posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)); + assert(!f3_posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)); dbuf_init(&dbuf); saved_errno = 0; diff --git a/f3write.c b/src/f3/f3write.c similarity index 97% rename from f3write.c rename to src/f3/f3write.c index c3cabed..abf1491 100644 --- a/f3write.c +++ b/src/f3/f3write.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include @@ -17,9 +14,9 @@ #include #include -#include "utils.h" -#include "libflow.h" -#include "version.h" +#include +#include +#include /* Argp's global variables. */ const char *argp_program_version = "F3 Write " F3_STR_VERSION; @@ -268,11 +265,11 @@ static int flush_chunk(const struct flow *fw, int fd) { UNUSED(fw); - if (fdatasync(fd) < 0) + if (f3_fdatasync(fd) < 0) return -1; /* Caller can read errno(3). */ /* Help the kernel to help us. */ - assert(!posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)); + assert(!f3_posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)); return 0; } diff --git a/libflow.c b/src/f3/lib/libflow.c similarity index 98% rename from libflow.c rename to src/f3/lib/libflow.c index 52efb53..873ca4b 100644 --- a/libflow.c +++ b/src/f3/lib/libflow.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include @@ -8,8 +5,8 @@ #include #include -#include "libflow.h" -#include "utils.h" +#include +#include static inline void move_to_inc_at_start(struct flow *fw) { diff --git a/utils.c b/src/f3/lib/utils.c similarity index 71% rename from utils.c rename to src/f3/lib/utils.c index 1abb403..fa8b3e3 100644 --- a/utils.c +++ b/src/f3/lib/utils.c @@ -1,13 +1,3 @@ -#define _GNU_SOURCE - -#if __APPLE__ && __MACH__ - -#define _DARWIN_C_SOURCE - -#include /* For fcntl(). */ - -#endif /* Apple Macintosh */ - #include #include #include @@ -20,8 +10,24 @@ #include #include -#include "version.h" -#include "utils.h" +#include +#include +#include + +void msleep(double wait_ms) +{ + msleep_compat(wait_ms); +} + +int f3_fdatasync(int fd) +{ + return fdatasync_compat(fd); +} + +int f3_posix_fadvise(int fd, off_t offset, off_t len, int advice) +{ + return posix_fadvise_compat(fd, offset, len, advice); +} void adjust_dev_path(const char **dev_path) { @@ -43,7 +49,7 @@ const char *adjust_unit(double *ptr_bytes) int i = 0; double final = *ptr_bytes; - while (i < 7 && final >= 1024) { + while (i < 6 && final >= 1024) { final /= 1024; i++; } @@ -187,9 +193,12 @@ const long *ls_my_files(const char *path, long start_at, long end_at) long arg_to_long(const struct argp_state *state, const char *arg) { char *end; - long l = strtol(arg, &end, 0); + long l; + if (!arg) argp_error(state, "An integer must be provided"); + + l = strtol(arg, &end, 0); if (!*arg || *end) argp_error(state, "`%s' is not an integer", arg); return l; @@ -203,72 +212,3 @@ void print_header(FILE *f, const char *name) "This is free software; see the source for copying conditions.\n" "\n", name); } - -#if __APPLE__ && __MACH__ - -/* This function is a _rough_ approximation of fdatasync(2). */ -int fdatasync(int fd) -{ - return fcntl(fd, F_FULLFSYNC); -} - -/* This function is a _rough_ approximation of posix_fadvise(2). */ -int posix_fadvise(int fd, off_t offset, off_t len, int advice) -{ - UNUSED(offset); - UNUSED(len); - switch (advice) { - case POSIX_FADV_SEQUENTIAL: - return fcntl(fd, F_RDAHEAD, 1); - case POSIX_FADV_DONTNEED: - return fcntl(fd, F_NOCACHE, 1); - default: - assert(0); - } -} - -#endif /* Apple Macintosh */ - -#if (__APPLE__ && __MACH__) || defined(__OpenBSD__) - -void msleep(double wait_ms) -{ - assert(!usleep(wait_ms * 1000)); -} - -#else /* Apple Macintosh / OpenBSD */ - -#include /* For clock_gettime() and clock_nanosleep(). */ - -void msleep(double wait_ms) -{ - struct timespec req; - int ret; - - assert(!clock_gettime(CLOCK_MONOTONIC, &req)); - - /* Add @wait_ms to @req. */ - if (wait_ms > 1000) { - time_t sec = wait_ms / 1000; - wait_ms -= sec * 1000; - assert(wait_ms > 0); - req.tv_sec += sec; - } - req.tv_nsec += wait_ms * 1000000; - - /* Round @req up. */ - if (req.tv_nsec >= 1000000000) { - ldiv_t result = ldiv(req.tv_nsec, 1000000000); - req.tv_sec += result.quot; - req.tv_nsec = result.rem; - } - - do { - ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, - &req, NULL); - } while (ret == EINTR); - - assert(ret == 0); -} - -#endif /* Apple Macintosh / OpenBSD */ diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c new file mode 100644 index 0000000..944ecd2 --- /dev/null +++ b/src/platform/darwin/block_device.c @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../private/block_device_private.h" +#include "../private/usb_reset_private.h" + +/* XXX This is borrowing from glibc. + * A better solution would be to return proper errors, + * so callers write their own messages. + */ +extern const char *__progname; + +static int read_all(int fd, char *buf, size_t count) +{ + size_t done = 0; + do { + ssize_t rc = read(fd, buf + done, count - done); + if (rc < 0) { + if (errno == EINTR) + continue; + if (errno == EIO || errno == ENODATA) { + /* These errors are "expected", + * so ignore them. + */ + } else { + err(errno, + "%s(): unexpected error code from read(2) = %i", + __func__, errno); + } + return - errno; + } + assert(rc != 0); /* We should never hit the end of the file. */ + done += rc; + } while (done < count); + return 0; +} + +static int write_all(int fd, const char *buf, size_t count) +{ + size_t done = 0; + do { + ssize_t rc = write(fd, buf + done, count - done); + if (rc < 0) + return -errno; + done += rc; + } while (done < count); + return 0; +} + +static int bdev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct block_device *bdev = dev_bdev(dev); + const int bo = dev_get_block_order(dev); + size_t length = (last_pos - first_pos + 1) << bo; + off_t offset = first_pos << bo; + off_t ret = lseek(bdev->fd, offset, SEEK_SET); + if (ret < 0) + return - errno; + assert(ret == offset); + return read_all(bdev->fd, buf, length); +} + +static int bdev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct block_device *bdev = dev_bdev(dev); + const int block_order = dev_get_block_order(dev); + size_t length = (last_pos - first_pos + 1) << block_order; + off_t offset = first_pos << block_order; + off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); + int rc; + if (off_ret < 0) + return - errno; + assert(off_ret == offset); + rc = write_all(bdev->fd, buf, length); + if (rc) + return rc; + rc = fsync(bdev->fd); + if (rc) + return rc; + return 0; +} + +static inline int bdev_open(const char *filename) +{ + int fd = open(filename, O_RDWR); + if (fd >= 0) { + fcntl(fd, F_NOCACHE, 1); + } + return fd; +} + +static int bdev_none_reset(struct device *dev) +{ + UNUSED(dev); + return 0; +} + +static void bdev_free(struct device *dev) +{ + struct block_device *bdev = dev_bdev(dev); + if (bdev->fd >= 0) + assert(!close(bdev->fd)); + free((void *)bdev->filename); +} + +static const char *bdev_get_filename(struct device *dev) +{ + return dev_bdev(dev)->filename; +} + +/* Map a disk path to its parent whole disk using Disk Arbitration. + * Returns the parent DADiskRef if the path is a partition, NULL otherwise. + * The caller is responsible for releasing the returned DADiskRef. + */ +static DADiskRef map_partition_to_disk(const char *path) +{ + DASessionRef session = NULL; + DADiskRef disk = NULL; + DADiskRef whole_disk = NULL; + DADiskRef parent_disk_to_return = NULL; + + session = DASessionCreate(kCFAllocatorDefault); + if (!session) { + goto cleanup; + } + + disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, path); + if (!disk) { + goto cleanup; + } + + /* Get the whole disk containing the created disk object. + * If 'disk' is a partition, whole_disk will be its parent. + * If 'disk' is already a whole disk, whole_disk will be the same as disk. + */ + whole_disk = DADiskCopyWholeDisk(disk); + if (!whole_disk) { + goto cleanup; + } + + if (!CFEqual(disk, whole_disk)) { + /* Original disk is a partition and whole_disk is its parent. */ + parent_disk_to_return = whole_disk; + whole_disk = NULL; + } + +cleanup: + if (whole_disk) { CFRelease(whole_disk); } + if (disk) { CFRelease(disk); } + if (session) { CFRelease(session); } + + return parent_disk_to_return; +} + +struct device *create_block_device(const char *filename, enum reset_type rt) +{ + struct block_device *bdev; + DASessionRef session; + DADiskRef disk; + CFDictionaryRef properties; + int block_size, block_order; + uint64_t deviceSize; + + bdev = malloc(sizeof(*bdev)); + if (!bdev) + goto error; + + bdev->filename = strdup(filename); + if (!bdev->filename) + goto bdev; + + bdev->fd = bdev_open(filename); + if (bdev->fd < 0) { + if (errno == EACCES && getuid()) { + fprintf(stderr, "Your user doesn't have access to device `%s'.\n" + "Try to run this program as root:\n" + "sudo %s %s\n" + "In case you don't have access to root, use f3write/f3read.\n", + filename, __progname, filename); + } else { + err(errno, "Can't open device `%s'", filename); + } + goto filename; + } + + /* Get disk properties using Disk Arbitration. */ + session = DASessionCreate(kCFAllocatorDefault); + if (!session) { + fprintf(stderr, "Can't create disk session\n"); + goto fd; + } + + disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, filename); + if (!disk) { + fprintf(stderr, "Can't get disk reference for `%s'\n", filename); + goto da_session; + } + + CFDictionaryRef desc = DADiskCopyDescription(disk); + if (!desc) { + fprintf(stderr, "Can't get disk description for `%s'\n", filename); + goto da_disk; + } + + properties = CFDictionaryCreateCopy(kCFAllocatorDefault, desc); + CFRelease(desc); + desc = NULL; + if (!properties) { + fprintf(stderr, "Can't get disk properties for `%s'\n", filename); + goto da_disk; + } + CFRelease(disk); + CFRelease(session); + disk = NULL; + session = NULL; + + /* Check if this is a partition. */ + DADiskRef parent = map_partition_to_disk(filename); + if (parent) { + fprintf(stderr, "Device `%s' is a partition.\n" + "You must run %s on the parent disk device.\n", + filename, __progname); + CFRelease(parent); + goto da_properties; + } + + /* Get block size and device size using Disk Arbitration. */ + CFNumberRef blockSizeNumber = CFDictionaryGetValue(properties, kDADiskDescriptionMediaBlockSizeKey); + if (!blockSizeNumber || !CFNumberGetValue(blockSizeNumber, kCFNumberIntType, &block_size)) { + fprintf(stderr, "Can't get block size for `%s'\n", filename); + goto da_properties; + } + + block_order = ilog2(block_size); + assert(block_size == (1 << block_order)); + + /* Get device size. */ + CFNumberRef deviceSizeNumber = CFDictionaryGetValue(properties, kDADiskDescriptionMediaSizeKey); + if (!deviceSizeNumber || !CFNumberGetValue(deviceSizeNumber, kCFNumberSInt64Type, &deviceSize)) { + fprintf(stderr, "Can't get device size for `%s'\n", filename); + goto da_properties; + } + CFRelease(properties); + properties = NULL; + + switch (rt) { + case RT_MANUAL_USB: + bdev->dev.reset = bdev_manual_usb_reset; + break; + case RT_USB: + bdev->dev.reset = bdev_usb_reset; + break; + case RT_NONE: + bdev->dev.reset = bdev_none_reset; + break; + default: + assert(0); + } + + bdev->dev.size_byte = deviceSize; + bdev->dev.block_order = block_order; + bdev->dev.read_blocks = bdev_read_blocks; + bdev->dev.write_blocks = bdev_write_blocks; + bdev->dev.free = bdev_free; + bdev->dev.get_filename = bdev_get_filename; + + return &bdev->dev; + +da_properties: + if (properties) { CFRelease(properties); } +da_disk: + if (disk) { CFRelease(disk); } +da_session: + if (session) { CFRelease(session); } +fd: + assert(!close(bdev->fd)); +filename: + free((void *)bdev->filename); +bdev: + free(bdev); +error: + return NULL; +} diff --git a/src/platform/darwin/partition/partition.c b/src/platform/darwin/partition/partition.c new file mode 100644 index 0000000..4c05a59 --- /dev/null +++ b/src/platform/darwin/partition/partition.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include // for _NSGetEnviron() + +#include + +/* Supported types. */ +static const char *const disk_types[] = { "msdos", "mbr", "gpt", NULL }; +static const char *const fs_types[] = { "fat32", "exfat", "hfs+", "apfs", NULL }; + +static const char *scheme_from_type(const char *type) +{ + if (strcasecmp(type, "msdos") == 0) return "MBR"; + if (strcasecmp(type, "mbr") == 0) return "MBR"; + if (strcasecmp(type, "gpt") == 0) return "GPT"; + return NULL; /* unsupported */ +} + +/* Type validation functions */ +bool is_valid_disk_type(const char *t) +{ + for (const char *const *p = disk_types; *p; ++p) + if (strcasecmp(*p, t) == 0) + return true; + return false; +} + +bool is_valid_fs_type(const char *t) +{ + for (const char *const *p = fs_types; *p; ++p) + if (strcasecmp(*p, t) == 0) + return true; + return false; +} + +/* Spawn wrapper. */ +static int run_diskutil(const char *disk, const char *scheme, + const char *start_str, const char *fs_type, + const char *label, const char *size_str) +{ + char **environ = *_NSGetEnviron(); + pid_t pid; + int status; + + char *args[16]; int n = 0; + args[n++] = "/usr/sbin/diskutil"; + args[n++] = "partitionDisk"; + args[n++] = (char *)disk; + args[n++] = (char *)scheme; + if (start_str) { + /* Leading gap */ + args[n++] = "Free Space"; + args[n++] = "gap"; + args[n++] = (char *)start_str; + } + /* Main partition */ + args[n++] = (char *)fs_type; args[n++] = (char *)label; args[n++] = (char *)size_str; + /* Tail gap to consume remainder (size 0) */ + args[n++] = "Free Space"; args[n++] = "tail"; args[n++] = "0"; + args[n++] = NULL; + +#ifdef DEBUG + fprintf(stderr, "DEBUG: "); + for (int i = 0; args[i]; ++i) { + fprintf(stderr, "%s%s", args[i], args[i+1] ? " " : "\n"); + } +#endif + + if (posix_spawn(&pid, args[0], NULL, NULL, args, environ) != 0) + return 1; + if (waitpid(pid, &status, 0) < 0) + return 1; + return (status == 0) ? 0 : 1; +} + +/* Function to unmount a disk using diskutil. */ +static int unmount_disk(const char *disk) { + char cmd[128]; + snprintf(cmd, sizeof(cmd), + "/usr/sbin/diskutil unmountDisk %s", disk); +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s\n", cmd); +#endif + return system(cmd) == 0 ? 0 : 1; +} + +/* Mark first slice bootable (ACTIVE) via fdisk. + * Returns 0 on success. GPT is ignored. + */ +static int mbr_set_active(const char *disk, bool active) +{ + /* fdisk -e -y -u /dev/diskN (then "f 1" or "f 0" + "w" + "q") */ + char cmd[128]; + const char *redirect = "> /dev/null 2>&1"; +#ifdef DEBUG + redirect = ""; +#endif + snprintf(cmd, sizeof cmd, + "echo '%s\nw\nq\n' | fdisk -e -y -u %s %s", + active ? "f 1" : "f 0", disk, redirect); +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s\n", cmd); +#endif + return system(cmd) == 0 ? 0 : 1; +} + +/* Fix disk. */ +int partition_create(const char *dev_filename, const struct partition_options *opt) +{ + const char *label = "f3fix"; + const char *disk = dev_filename; + const char *scheme; + int ret = 1; // assume failure + uint64_t length; + char start_str[32]; + char size_str[32]; + + /* Validate requested types. */ + if (!is_valid_disk_type(opt->disk_type) || !is_valid_fs_type(opt->fs_type)) + goto out; + + /* Device node (e.g. /dev/disk2). */ + if (!disk) + goto out; + + scheme = scheme_from_type(opt->disk_type); + if (!scheme) + goto out; + + /* Compute slice length in sectors. + * GPT requires every user slice start/end on an 8-sector boundary (4 KiB). + * Trim *down* so we never trespass past last_sector even after alignment. + */ + length = opt->last_sector - opt->first_sector + 1ULL; + if (strcasecmp(opt->disk_type, "gpt") == 0) { + /* Round DOWN to 8-sector boundary (clear low 3 bits). */ + length &= ~7ULL; + } + + snprintf(start_str, sizeof start_str, "%lluS", + (unsigned long long)opt->first_sector); + snprintf(size_str, sizeof size_str, "%lluS", + (unsigned long long)length); + + ret = run_diskutil(disk, + scheme, + opt->first_sector > 0 ? start_str : NULL, + opt->fs_type, label, size_str); + if (ret) + goto out; + + unmount_disk(disk); + + if (opt->boot && strcmp(scheme, "MBR") == 0) + ret = mbr_set_active(disk, true); + +out: + return ret; +} + +/* Array helpers. */ +static size_t build_types_array(const char *const src[], char ***out) +{ + char **types = NULL; + size_t count = 0; + size_t i = 0; + + while (src[count]) + ++count; + + types = calloc(count + 1, sizeof(char *)); + if (!types) + goto err; + + for (i = 0; i < count; ++i) { + types[i] = strdup(src[i]); + if (!types[i]) + goto oom; + } + + *out = types; + return count; + +oom: + /* Free partial array. */ + while (i) + free(types[--i]); + free(types); +err: + *out = NULL; + return 0; +} + +/* Public wrappers. */ +size_t partition_list_disk_types(char ***out) +{ + return build_types_array(disk_types, out); +} + +size_t partition_list_fs_types(char ***out) +{ + return build_types_array(fs_types, out); +} + +void partition_free_types_array(char **array) +{ + if (!array) return; + for (char **p = array; *p; ++p) + free(*p); + free(array); +} diff --git a/src/platform/darwin/platform_compat.c b/src/platform/darwin/platform_compat.c new file mode 100644 index 0000000..21858fb --- /dev/null +++ b/src/platform/darwin/platform_compat.c @@ -0,0 +1,31 @@ +#include /* For fcntl(). */ +#include /* For chdir and chroot */ +#include + +#include + +void msleep_compat(double wait_ms) +{ + assert(!usleep(wait_ms * 1000)); +} + +/* This function is a _rough_ approximation of fdatasync(2). */ +int fdatasync_compat(int fd) +{ + return fcntl(fd, F_FULLFSYNC); +} + +/* This function is a _rough_ approximation of posix_fadvise(2). */ +int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice) +{ + UNUSED(offset); + UNUSED(len); + switch (advice) { + case POSIX_FADV_SEQUENTIAL: + return fcntl(fd, F_RDAHEAD, 1); + case POSIX_FADV_DONTNEED: + return fcntl(fd, F_NOCACHE, 1); + default: + assert(0); + } +} diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c new file mode 100644 index 0000000..8768b50 --- /dev/null +++ b/src/platform/darwin/usb_reset.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../private/usb_reset_private.h" + +/* macOS stub for USB reset. */ +int bdev_manual_usb_reset(struct device *dev) +{ + // TODO: Use Disk Arbitration to eject and remount. + warnx("USB reset is not supported on macOS"); + return -ENOSYS; +} + +/* Reset a USB-backed block device by prompting a detach/reattach. */ +int bdev_usb_reset(struct device *dev) +{ + // TODO: Use USBDriverKit > IOUSBHostDevice > Reset() + warnx("USB reset is not supported on macOS"); + return -ENOSYS; +} diff --git a/src/platform/freebsd/platform_compat.c b/src/platform/freebsd/platform_compat.c new file mode 100644 index 0000000..cf2d198 --- /dev/null +++ b/src/platform/freebsd/platform_compat.c @@ -0,0 +1,50 @@ +#include /* fdatasync, posix_fadvise */ +#include /* For fmod */ +#include /* For clock_gettime() and clock_nanosleep(). */ +#include /* For assert() */ +#include /* For EINTR */ + +#include + +void msleep_compat(double wait_ms) +{ + struct timespec req; + int ret; + + assert(!clock_gettime(CLOCK_MONOTONIC, &req)); + + /* Add @wait_ms to @req. */ + if (wait_ms > 1000) { + time_t sec = wait_ms / 1000; + wait_ms -= sec * 1000; + assert(wait_ms > 0); + req.tv_sec += sec; + } + req.tv_nsec += wait_ms * 1000000; + + /* Round @req up. */ + if (req.tv_nsec >= 1000000000) { + ldiv_t result = ldiv(req.tv_nsec, 1000000000); + req.tv_sec += result.quot; + req.tv_nsec = result.rem; + } + + do { + ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, + &req, NULL); + } while (ret == EINTR); + + assert(ret == 0); +} + +/* POSIX function wrappers */ + +int fdatasync_compat(int fd) +{ + return fsync(fd); +} + +int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice) +{ + return posix_fadvise(fd, offset, len, advice); +} diff --git a/src/platform/linux/block_device.c b/src/platform/linux/block_device.c new file mode 100644 index 0000000..bc82089 --- /dev/null +++ b/src/platform/linux/block_device.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../private/block_device_private.h" +#include "../private/usb_reset_private.h" +#include "private/linux_private.h" + +/* XXX This is borrowing from glibc. + * A better solution would be to return proper errors, + * so callers write their own messages. + */ +extern const char *__progname; + +static int read_all(int fd, char *buf, size_t count) +{ + size_t done = 0; + do { + ssize_t rc = read(fd, buf + done, count - done); + if (rc < 0) { + if (errno == EINTR) + continue; + if (errno == EIO || errno == ENODATA) { + /* These errors are "expected", + * so ignore them. + */ + } else { + /* Execution should not come here. */ + err(errno, + "%s(): unexpected error code from read(2) = %i", + __func__, errno); + } + return - errno; + } + assert(rc != 0); /* We should never hit the end of the file. */ + done += rc; + } while (done < count); + return 0; +} + +// Write "count" bytes via repeated write +static int write_all(int fd, const char *buf, size_t count) +{ + size_t done = 0; + do { + ssize_t rc = write(fd, buf + done, count - done); + if (rc < 0) + return -errno; + done += rc; + } while (done < count); + return 0; +} + +static int bdev_read_blocks(struct device *dev, char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct block_device *bdev = dev_bdev(dev); + const int bo = dev_get_block_order(dev); + size_t length = (last_pos - first_pos + 1) << bo; + off_t offset = first_pos << bo; + off_t ret = lseek(bdev->fd, offset, SEEK_SET); + if (ret < 0) + return -errno; + assert(ret == offset); + return read_all(bdev->fd, buf, length); +} + +static int bdev_write_blocks(struct device *dev, const char *buf, + uint64_t first_pos, uint64_t last_pos) +{ + struct block_device *bdev = dev_bdev(dev); + const int block_order = dev_get_block_order(dev); + size_t length = (last_pos - first_pos + 1) << block_order; + off_t offset = first_pos << block_order; + off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); + int rc; + if (off_ret < 0) + return - errno; + assert(off_ret == offset); + rc = write_all(bdev->fd, buf, length); + if (rc) + return rc; + rc = fsync(bdev->fd); + if (rc) + return rc; + return posix_fadvise(bdev->fd, 0, 0, POSIX_FADV_DONTNEED); +} + +static int bdev_none_reset(struct device *dev) +{ + UNUSED(dev); + return 0; +} + +static void bdev_free(struct device *dev) +{ + struct block_device *bdev = dev_bdev(dev); + if (bdev->fd >= 0) + assert(!close(bdev->fd)); + free((void *)bdev->filename); +} + +static const char *bdev_get_filename(struct device *dev) +{ + return dev_bdev(dev)->filename; +} + +// Map a partition udev_device to its parent disk device +static struct udev_device *map_partition_to_disk(struct udev_device *dev) +{ + struct udev_device *disk_dev; + + disk_dev = udev_device_get_parent_with_subsystem_devtype( + dev, "block", "disk"); + + /* @disk_dev is not referenced, and will be freed when + * the child (i.e. @dev) is freed. + * See udev_device_get_parent_with_subsystem_devtype() for + * details. + */ + return udev_device_ref(disk_dev); +} + +struct device *create_block_device(const char *filename, enum reset_type rt) +{ + struct block_device *bdev; + struct udev *udev; + struct udev_device *fd_dev; + const char *s; + int block_size, block_order; + + bdev = malloc(sizeof(*bdev)); + if (!bdev) + goto error; + + bdev->filename = strdup(filename); + if (!bdev->filename) + goto bdev; + + bdev->fd = bdev_open(filename); + if (bdev->fd < 0) { + if (errno == EACCES && getuid()) { + fprintf(stderr, "Your user doesn't have access to device `%s'.\n" + "Try to run this program as root:\n" + "sudo %s %s\n" + "In case you don't have access to root, use f3write/f3read.\n", + filename, __progname, filename); + } else { + err(errno, "Can't open device `%s'", filename); + } + goto filename; + } + + /* Make sure that @bdev->fd is a disk, not a partition. */ + udev = udev_new(); + if (!udev) { + warnx("Can't load library udev"); + goto fd; + } + fd_dev = dev_from_block_fd(udev, bdev->fd); + if (!fd_dev) { + fprintf(stderr, "Can't create udev device from `%s'\n", + filename); + goto udev; + } + assert(!strcmp(udev_device_get_subsystem(fd_dev), "block")); + s = udev_device_get_devtype(fd_dev); + if (!strcmp(s, "partition")) { + struct udev_device *disk_dev = map_partition_to_disk(fd_dev); + assert(disk_dev); + s = udev_device_get_devnode(disk_dev); + fprintf(stderr, "Device `%s' is a partition of disk device `%s'.\n" + "You must run %s on the disk device as follows:\n" + "%s %s\n", + filename, s, __progname, __progname, s); + udev_device_unref(disk_dev); + goto fd_dev; + } else if (strcmp(s, "disk")) { + fprintf(stderr, "Device `%s' is not a disk, but `%s'", + filename, s); + goto fd_dev; + } + + if (rt != RT_NONE) { + /* Make sure that @bdev->fd is backed by a USB device. */ + struct udev_device *usb_dev = map_dev_to_usb_dev(fd_dev); + if (!usb_dev) { + fprintf(stderr, + "Device `%s' is not backed by a USB device.\n" + "You must disable reset, run %s as follows:\n" + "%s --reset-type=%i %s\n", + filename, __progname, __progname, RT_NONE, + filename); + goto fd_dev; + } + udev_device_unref(usb_dev); + } + udev_device_unref(fd_dev); + assert(!udev_unref(udev)); + + switch (rt) { + case RT_MANUAL_USB: + bdev->dev.reset = bdev_manual_usb_reset; + break; + case RT_USB: + bdev->dev.reset = bdev_usb_reset; + break; + case RT_NONE: + bdev->dev.reset = bdev_none_reset; + break; + default: + assert(0); + } + + assert(!ioctl(bdev->fd, BLKGETSIZE64, &bdev->dev.size_byte)); + + assert(!ioctl(bdev->fd, BLKSSZGET, &block_size)); + block_order = ilog2(block_size); + assert(block_size == (1 << block_order)); + bdev->dev.block_order = block_order; + + bdev->dev.read_blocks = bdev_read_blocks; + bdev->dev.write_blocks = bdev_write_blocks; + bdev->dev.free = bdev_free; + bdev->dev.get_filename = bdev_get_filename; + + return &bdev->dev; + +fd_dev: + udev_device_unref(fd_dev); +udev: + assert(!udev_unref(udev)); +fd: + assert(!close(bdev->fd)); +filename: + free((void *)bdev->filename); +bdev: + free(bdev); +error: + return NULL; +} diff --git a/src/platform/linux/partition/partition.c b/src/platform/linux/partition/partition.c new file mode 100644 index 0000000..d35474c --- /dev/null +++ b/src/platform/linux/partition/partition.c @@ -0,0 +1,191 @@ +#include +#include +#include + +#include + +// Convert physical sector to logical sector +static PedSector map_sector_to_logical_sector(PedSector sector, + int logical_sector_size) +{ + assert(logical_sector_size >= 512); + assert(logical_sector_size % 512 == 0); + return sector / (logical_sector_size / 512); +} + +// Original fix_disk function +static int parted_fix_disk(PedDevice *dev, PedDiskType *type, + PedFileSystemType *fs_type, int boot, PedSector start, PedSector end) +{ + PedDisk *disk; + PedPartition *part; + PedGeometry *geom; + PedConstraint *constraint; + int ret = 0; + + disk = ped_disk_new_fresh(dev, type); + if (!disk) + goto out; + + start = map_sector_to_logical_sector(start, dev->sector_size); + end = map_sector_to_logical_sector(end, dev->sector_size); + part = ped_partition_new(disk, PED_PARTITION_NORMAL, + fs_type, start, end); + if (!part) + goto disk; + if (boot && !ped_partition_set_flag(part, PED_PARTITION_BOOT, 1)) + goto part; + + geom = ped_geometry_new(dev, start, end - start + 1); + if (!geom) + goto part; + constraint = ped_constraint_exact(geom); + ped_geometry_destroy(geom); + if (!constraint) + goto part; + + ret = ped_disk_add_partition(disk, part, constraint); + ped_constraint_destroy(constraint); + if (!ret) + goto part; + /* ped_disk_print(disk); */ + + ret = ped_disk_commit(disk); + goto disk; + +part: + ped_partition_destroy(part); +disk: + ped_disk_destroy(disk); +out: + return ret; +} + +// Create partition on a device +int partition_create(const char *dev_filename, const struct partition_options *options) +{ + PedDevice *ped_dev; + PedDiskType *disk_type; + PedFileSystemType *fs_type; + int ret = 1; // assume failure + + // Get parted device + ped_dev = ped_device_get(dev_filename); + if (!ped_dev) { + goto out; + } + + // Get disk type + disk_type = ped_disk_type_get(options->disk_type); + if (!disk_type) { + goto pdev; + } + + // Get file system type + fs_type = ped_file_system_type_get(options->fs_type); + if (!fs_type) { + goto pdev; + } + + // Create the partition + ret = !parted_fix_disk(ped_dev, disk_type, fs_type, options->boot, + options->first_sector, options->last_sector); + + // Cleanup +pdev: + ped_device_destroy(ped_dev); +out: + return ret; +} + +/* Type validation functions */ +bool is_valid_disk_type(const char *disk_type) +{ + return ped_disk_type_get(disk_type) != NULL; +} + +bool is_valid_fs_type(const char *fs_type) +{ + return ped_file_system_type_get(fs_type) != NULL; +} + +/* Generic array helpers */ +typedef const void *(*IterFn)(const void *prev); +typedef const char *(*NameFn)(const void *type); + +static size_t build_types_array(char ***out, IterFn get_next, NameFn name_fn) +{ + const void *type = NULL; + char **types; + size_t count = 0; + size_t i = 0; + + /* First pass: count the number of types */ + while ((type = get_next(type))) + ++count; + + /* Allocate memory for the array (count + 1 for null terminator) */ + types = calloc(count + 1, sizeof(char*)); + if (!types) + goto err; + + /* Second pass: duplicate strings and populate the array */ + for (type = get_next(NULL); type; + type = get_next(type), ++i) { + types[i] = strdup(name_fn(type)); + if (!types[i]) + goto oom; + } + + *out = types; + return count; + +oom: + /* free partial array */ + while (i) + free(types[--i]); + free(types); +err: + *out = NULL; + return 0; +} + +/* Concrete array adapters */ +static const void *disk_next(const void *p) +{ + return ped_disk_type_get_next((const PedDiskType *)p); +} + +static const char *disk_name(const void *p) +{ + return ((const PedDiskType *)p)->name; +} + +static const void *fs_next(const void *p) +{ + return ped_file_system_type_get_next((const PedFileSystemType *)p); +} + +static const char *fs_name(const void *p) +{ + return ((const PedFileSystemType *)p)->name; +} + +/* Public wrappers */ +size_t partition_list_disk_types(char ***out_array) +{ + return build_types_array(out_array, disk_next, disk_name); +} + +size_t partition_list_fs_types(char ***out_array) +{ + return build_types_array(out_array, fs_next, fs_name); +} + +void partition_free_types_array(char **array) +{ + if (!array) return; + for (char **p = array; *p; ++p) + free(*p); + free(array); +} diff --git a/src/platform/linux/platform_compat.c b/src/platform/linux/platform_compat.c new file mode 100644 index 0000000..2b499b9 --- /dev/null +++ b/src/platform/linux/platform_compat.c @@ -0,0 +1,52 @@ +#include /* For ldiv_t and ldiv() */ +#include /* fdatasync, posix_fadvise */ +#include /* For posix_fadvise() and its flags */ +#include /* For fmod */ +#include /* For clock_gettime() and clock_nanosleep(). */ +#include /* For assert() */ +#include /* For EINTR */ + +#include + +void msleep_compat(double wait_ms) +{ + struct timespec req; + int ret; + + assert(!clock_gettime(CLOCK_MONOTONIC, &req)); + + /* Add @wait_ms to @req. */ + if (wait_ms > 1000) { + time_t sec = wait_ms / 1000; + wait_ms -= sec * 1000; + assert(wait_ms > 0); + req.tv_sec += sec; + } + req.tv_nsec += wait_ms * 1000000; + + /* Round @req up. */ + if (req.tv_nsec >= 1000000000) { + ldiv_t result = ldiv(req.tv_nsec, 1000000000); + req.tv_sec += result.quot; + req.tv_nsec = result.rem; + } + + do { + ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, + &req, NULL); + } while (ret == EINTR); + + assert(ret == 0); +} + +/* POSIX function wrappers */ + +int fdatasync_compat(int fd) +{ + return fdatasync(fd); +} + +int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice) +{ + return posix_fadvise(fd, offset, len, advice); +} diff --git a/src/platform/linux/private/linux_private.h b/src/platform/linux/private/linux_private.h new file mode 100644 index 0000000..079e8b6 --- /dev/null +++ b/src/platform/linux/private/linux_private.h @@ -0,0 +1,52 @@ +#ifndef LINUX_PLATFORM_PRIVATE_H +#define LINUX_PLATFORM_PRIVATE_H + +#include /* For struct udev, struct udev_device */ +#include /* For struct stat, S_ISBLK, fstat */ + +static inline int bdev_open(const char *filename) +{ + return open(filename, O_RDWR | O_DIRECT); +} + +static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev) +{ + struct udev_device *usb_dev; + + /* The device pointed to by dev contains information about + * the USB device. + * In order to get information about the USB device, + * get the parent device with the subsystem/devtype pair of + * "usb"/"usb_device". + * This will be several levels up the tree, + * but the function will find it. + */ + usb_dev = udev_device_get_parent_with_subsystem_devtype( + dev, "usb", "usb_device"); + + /* @usb_dev is not referenced, and will be freed when + * the child (i.e. @dev) is freed. + * See udev_device_get_parent_with_subsystem_devtype() for + * details. + */ + return udev_device_ref(usb_dev); +} + +static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) +{ + struct stat fd_stat; + + if (fstat(block_fd, &fd_stat)) { + warn("Can't fstat() FD %i", block_fd); + return NULL; + } + + if (!S_ISBLK(fd_stat.st_mode)) { + warnx("FD %i is not a block device", block_fd); + return NULL; + } + + return udev_device_new_from_devnum(udev, 'b', fd_stat.st_rdev); +} + +#endif /* LINUX_PLATFORM_PRIVATE_H */ diff --git a/src/platform/linux/usb_reset.c b/src/platform/linux/usb_reset.c new file mode 100644 index 0000000..e7f10af --- /dev/null +++ b/src/platform/linux/usb_reset.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../private/usb_reset_private.h" +#include "../private/block_device_private.h" +#include "private/linux_private.h" + +static struct udev_monitor *create_monitor(struct udev *udev, + const char *subsystem, const char *devtype) +{ + struct udev_monitor *mon; + int mon_fd, flags; + + mon = udev_monitor_new_from_netlink(udev, "udev"); + assert(mon); + assert(!udev_monitor_filter_add_match_subsystem_devtype(mon, + subsystem, devtype)); + assert(!udev_monitor_enable_receiving(mon)); + mon_fd = udev_monitor_get_fd(mon); + assert(mon_fd >= 0); + flags = fcntl(mon_fd, F_GETFL); + assert(flags >= 0); + assert(!fcntl(mon_fd, F_SETFL, flags & ~O_NONBLOCK)); + + return mon; +} + +static uint64_t get_udev_dev_size_byte(struct udev_device *dev) +{ + const char *str_size_sector = + udev_device_get_sysattr_value(dev, "size"); + char *end; + long long size_sector; + if (!str_size_sector) + return 0; + size_sector = strtoll(str_size_sector, &end, 10); + assert(!*end); + return size_sector * 512LL; +} + +static int wait_for_reset(struct udev *udev, const char *id_serial, + uint64_t original_size_byte, const char **pfinal_dev_filename) +{ + bool done = false, went_to_zero = false, already_changed_size = false; + struct udev_monitor *mon; + int rc; + + mon = create_monitor(udev, "block", "disk"); + if (!mon) { + warnx("%s(): Can't instantiate a monitor", __func__); + rc = - ENOMEM; + goto out; + } + + do { + struct udev_device *dev; + const char *dev_id_serial, *action; + uint64_t new_size_byte; + const char *devnode; + + dev = udev_monitor_receive_device(mon); + if (!dev) { + warnx("%s(): Can't monitor device", __func__); + rc = - ENOMEM; + goto mon; + } + dev_id_serial = udev_device_get_property_value(dev, + "ID_SERIAL"); + if (!dev_id_serial || strcmp(dev_id_serial, id_serial)) + goto next; + + action = udev_device_get_action(dev); + new_size_byte = get_udev_dev_size_byte(dev); + if (!strcmp(action, "add")) { + /* Deal with the case in which the user pulls + * the USB device. + * + * DO NOTHING. + */ + } else if (!strcmp(action, "change")) { + /* Deal with the case in which the user pulls + * the memory card from the card reader. + */ + + if (!new_size_byte) { + /* Memory card removed. */ + went_to_zero = true; + goto next; + } + + if (!went_to_zero) + goto next; + } else { + /* Ignore all other actions. */ + goto next; + } + + if (new_size_byte != original_size_byte) { + /* This is an edge case. */ + + if (!already_changed_size) { + already_changed_size = true; + went_to_zero = false; + printf("\nThe drive changed its size of %" + PRIu64 " Bytes to %" PRIu64 + " Bytes after the reset.\nPlease try to unplug and plug it back again...", + original_size_byte, new_size_byte); + fflush(stdout); + goto next; + } + + printf("\nThe reset failed. The drive has not returned to its original size.\n\n"); + fflush(stdout); + rc = - ENXIO; + goto mon; + } + + devnode = strdup(udev_device_get_devnode(dev)); + if (!devnode) { + warnx("%s(): Out of memory", __func__); + rc = - ENOMEM; + goto mon; + } + free((void *)*pfinal_dev_filename); + *pfinal_dev_filename = devnode; + done = true; + +next: + udev_device_unref(dev); + } while (!done); + + rc = 0; + +mon: + assert(!udev_monitor_unref(mon)); +out: + return rc; +} + +// Reset a USB-backed block device by prompting a detach/reattach +int bdev_manual_usb_reset(struct device *dev) +{ + struct block_device *bdev = dev_bdev(dev); + struct udev *udev; + struct udev_device *udev_dev, *usb_dev; + const char *id_serial; + int rc; + + if (bdev->fd < 0) { + /* We don't have a device open. + * This can happen when the previous reset failed, and + * a reset is being called again. + */ + rc = - EBADF; + goto out; + } + + udev = udev_new(); + if (!udev) { + warnx("Can't load library udev"); + rc = - EOPNOTSUPP; + goto out; + } + + /* Identify which drive we are going to reset. */ + udev_dev = dev_from_block_fd(udev, bdev->fd); + if (!udev_dev) { + warnx("Library udev can't find device `%s'", + dev_get_filename(dev)); + rc = - EINVAL; + goto udev; + } + usb_dev = map_dev_to_usb_dev(udev_dev); + if (!usb_dev) { + warnx("Block device `%s' is not backed by a USB device", + dev_get_filename(dev)); + rc = - EINVAL; + goto udev_dev; + } + id_serial = udev_device_get_property_value(udev_dev, "ID_SERIAL"); + if (!id_serial) { + warnx("%s(): Out of memory", __func__); + rc = - ENOMEM; + goto usb_dev; + } + + /* Close @bdev->fd before the drive is removed to increase + * the chance that the device will receive the same filename. + * The code is robust enough to deal with the case the drive doesn't + * receive the same file name, though. + */ + assert(!close(bdev->fd)); + bdev->fd = -1; + + printf("Please unplug and plug back the USB drive. Waiting..."); + fflush(stdout); + rc = wait_for_reset(udev, id_serial, dev_get_size_byte(dev), + &bdev->filename); + if (rc) { + assert(rc < 0); + goto usb_dev; + } + printf(" Thanks\n\n"); + + bdev->fd = bdev_open(bdev->filename); + if (bdev->fd < 0) { + rc = - errno; + warn("Can't reopen device `%s'", bdev->filename); + goto usb_dev; + } + + rc = 0; + +usb_dev: + udev_device_unref(usb_dev); +udev_dev: + udev_device_unref(udev_dev); +udev: + assert(!udev_unref(udev)); +out: + return rc; +} + +static struct udev_device *map_block_to_usb_dev(struct udev *udev, int block_fd) +{ + struct udev_device *dev, *usb_dev; + + dev = dev_from_block_fd(udev, block_fd); + if (!dev) + return NULL; + usb_dev = map_dev_to_usb_dev(dev); + udev_device_unref(dev); + return usb_dev; +} + +// Return an open fd to the underlying USB hardware for the block device +static int usb_fd_from_block_dev(int block_fd, int open_flags) +{ + struct udev *udev; + struct udev_device *usb_dev; + const char *usb_filename; + int usb_fd; + + udev = udev_new(); + if (!udev) { + warnx("Can't load library udev"); + usb_fd = -EOPNOTSUPP; + goto out; + } + + usb_dev = map_block_to_usb_dev(udev, block_fd); + if (!usb_dev) { + warnx("Block device is not backed by a USB device"); + usb_fd = -EINVAL; + goto udev; + } + + usb_filename = udev_device_get_devnode(usb_dev); + if (!usb_filename) { + warnx("%s(): Out of memory", __func__); + usb_fd = -ENOMEM; + goto usb_dev; + } + + usb_fd = open(usb_filename, open_flags | O_NONBLOCK); + if (usb_fd < 0) { + usb_fd = - errno; + warn("Can't open device `%s'", usb_filename); + goto usb_dev; + } + +usb_dev: + udev_device_unref(usb_dev); +udev: + assert(!udev_unref(udev)); +out: + return usb_fd; +} + +// Reset a USB-backed block device via USBDEVFS_RESET ioctl +int bdev_usb_reset(struct device *dev) +{ + struct block_device *bdev = dev_bdev(dev); + int usb_fd; + + if (bdev->fd < 0) { + /* We don't have a device open. + * This can happen when the previous reset failed, and + * a reset is being called again. + */ + return - EBADF; + } + + usb_fd = usb_fd_from_block_dev(bdev->fd, O_WRONLY); + if (usb_fd < 0) + return usb_fd; + + assert(!close(bdev->fd)); + bdev->fd = -1; + assert(!ioctl(usb_fd, USBDEVFS_RESET)); + assert(!close(usb_fd)); + bdev->fd = bdev_open(bdev->filename); + if (bdev->fd < 0) { + int rc = - errno; + warn("Can't reopen device `%s'", bdev->filename); + return rc; + } + return 0; +} diff --git a/src/platform/openbsd/platform_compat.c b/src/platform/openbsd/platform_compat.c new file mode 100644 index 0000000..fd49366 --- /dev/null +++ b/src/platform/openbsd/platform_compat.c @@ -0,0 +1,57 @@ +#include /* fdatasync, posix_fadvise */ +#include /* For fmod */ +#include /* For clock_gettime() and clock_nanosleep(). */ +#include /* For assert() */ +#include /* For EINTR */ + +#include + +void msleep_compat(double wait_ms) +{ + struct timespec req; + int ret; + + assert(!clock_gettime(CLOCK_MONOTONIC, &req)); + + /* Add @wait_ms to @req. */ + if (wait_ms > 1000) { + time_t sec = wait_ms / 1000; + wait_ms -= sec * 1000; + assert(wait_ms > 0); + req.tv_sec += sec; + } + req.tv_nsec += wait_ms * 1000000; + + /* Round @req up. */ + if (req.tv_nsec >= 1000000000) { + ldiv_t result = ldiv(req.tv_nsec, 1000000000); + req.tv_sec += result.quot; + req.tv_nsec = result.rem; + } + + do { + ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, + &req, NULL); + } while (ret == EINTR); + + assert(ret == 0); +} + +/* POSIX function wrappers */ + +int fdatasync_compat(int fd) +{ + return fdatasync(fd); +} + +/* + * OpenBSD doesn't have posix_fadvise() (...). + * There is some code [in F3] to emulate posix_fadvise for MacOS + * but it uses various fcntl(2) commands that we don't have [in OpenBSD]. + * + * -- Stuart Henderson, OpenBSD developer + */ +int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice) +{ + return 0; +} diff --git a/src/platform/private/block_device_private.h b/src/platform/private/block_device_private.h new file mode 100644 index 0000000..1212015 --- /dev/null +++ b/src/platform/private/block_device_private.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_PRIVATE_BLOCK_DEVICE_H +#define PLATFORM_PRIVATE_BLOCK_DEVICE_H + +#include /* For struct device. */ + +struct block_device { + /* This must be the first field. See dev_bdev() for details. */ + struct device dev; + + const char *filename; + int fd; +}; + +static inline struct block_device *dev_bdev(struct device *dev) +{ + return (struct block_device *)dev; +} + +#endif /* PLATFORM_PRIVATE_BLOCK_DEVICE_H */ diff --git a/src/platform/private/usb_reset_private.h b/src/platform/private/usb_reset_private.h new file mode 100644 index 0000000..fb3e642 --- /dev/null +++ b/src/platform/private/usb_reset_private.h @@ -0,0 +1,9 @@ +#ifndef HEADER_PLATFORM_PRIVATE_USB_RESET_H +#define HEADER_PLATFORM_PRIVATE_USB_RESET_H + +#include /* For struct device. */ + +int bdev_manual_usb_reset(struct device *dev); +int bdev_usb_reset(struct device *dev); + +#endif /* HEADER_PLATFORM_PRIVATE_USB_RESET_H */ diff --git a/tests/test-macos-f3fix.sh b/tests/test-macos-f3fix.sh new file mode 100755 index 0000000..b4381cc --- /dev/null +++ b/tests/test-macos-f3fix.sh @@ -0,0 +1,156 @@ +#!/bin/bash +set -euo pipefail + +# Determine the script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Change to project root +cd "$PROJECT_ROOT" || log_error "Failed to change to project root: $PROJECT_ROOT" + +# Test configurations +readonly TEST_CASES=( + "--disk-type=msdos --fs-type=fat32" + "--disk-type=gpt --fs-type=fat32" + "--disk-type=mbr --fs-type=hfs+" + "--disk-type=mbr --fs-type=exfat" +) + +# Nice colors for output +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +log() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +log_success() { + log "${GREEN}SUCCESS: $1${NC}" +} + +log_error() { + log "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +log_info() { + log "${YELLOW}INFO: $1${NC}" +} + +check_requirements() { + local commands=("diskutil" "hdiutil" "make" "fdisk" "dd" "awk") + for cmd in "${commands[@]}"; do + if ! command -v "$cmd" &> /dev/null; then + log_error "Required command '$cmd' not found" + fi + done +} + +check_disk_space() { + local required_mb=200 + local available_mb=$(df -m . | awk 'NR==2 {print $4}') + + if [ "$available_mb" -lt "$required_mb" ]; then + log_error "Insufficient disk space. Need at least ${required_mb}MB, but only ${available_mb}MB available." + fi +} + +log_system_info() { + log_info "=== System Information ===" + system_profiler SPSoftwareDataType SPHardwareDataType | grep -E 'System Version|Model Identifier|Cores|Memory' + log_info "Disk space: $(df -h . | awk 'NR==2 {print $4}') available" +} + +cleanup() { + local exit_code=$? + log_info "Cleaning up..." + + if [ -n "${device:-}" ]; then + if ! diskutil unmountDisk "$device" 2>/dev/null; then + log_info "Could not unmount $device, trying to force..." + diskutil unmountDisk force "$device" 2>/dev/null || true + fi + diskutil eject "$device" 2>/dev/null || true + fi + + if [ -f "test.img" ]; then + rm -f test.img + fi + + if [ $exit_code -eq 0 ]; then + log_success "All tests completed successfully!" + else + log_error "Tests failed with exit code $exit_code" + fi +} + +main() { + trap cleanup EXIT + check_requirements + check_disk_space + log_system_info + + local test_start_time=$(date +%s) + + # Clean up from previous runs + rm -f test.img + + log_info "Creating test image..." + if ! dd if=/dev/zero of=test.img bs=1M count=100 status=progress; then + log_error "Failed to create test image" + fi + + log_info "Attaching disk image..." + disk_info=$(hdiutil attach -nomount -readwrite test.img 2>&1) || { + log_error "Failed to attach disk image: $disk_info" + } + + device=$(echo "$disk_info" | awk '{print $1}') + [ -z "$device" ] && log_error "Failed to get device name" + + log_info "Using device: $device" + + log_info "Building F3 with debug flags..." + if ! CFLAGS="-DDEBUG" make -C "$PROJECT_ROOT" clean all extra; then + log_error "Build failed" + fi + + local test_count=0 + local passed_count=0 + + for test_case in "${TEST_CASES[@]}"; do + ((test_count++)) + local start_time=$(date +%s) + + log_info "=== Test $test_count/${#TEST_CASES[@]}: $test_case ===" + + if "$PROJECT_ROOT/build/bin/f3fix" $test_case --first-sec=2048 --last-sec=102400 --boot "$device"; then + log_success "Test passed: $test_case" + ((passed_count++)) + else + log_error "Test failed: $test_case" + fi + + log_info "Current disk layout:" + diskutil list "$device" + fdisk "$device" + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + log_info "Test completed in ${duration}s" + echo "----------------------------------------" + done + + local test_end_time=$(date +%s) + local total_duration=$((test_end_time - test_start_time)) + local minutes=$((total_duration / 60)) + local seconds=$((total_duration % 60)) + + log_info "Test summary: $passed_count/$test_count tests passed" + log_info "Total time: ${minutes}m ${seconds}s" + [ "$passed_count" -eq "$test_count" ] || log_error "Some tests failed" +} + +main