From f1d043d244768303ed9bba8917e78c7cde10afbd Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Tue, 29 Apr 2025 14:53:23 -0300 Subject: [PATCH 01/20] New source structure for multi platform --- Makefile | 104 +- include/devices/block_device.h | 14 + include/devices/file_device.h | 23 + include/devices/perf_device.h | 19 + include/devices/safe_device.h | 15 + include/devices/usb_reset.h | 9 + libdevs.h => include/libdevs.h | 39 +- libflow.h => include/libflow.h | 0 libprobe.h => include/libprobe.h | 0 libutils.h => include/libutils.h | 0 utils.h => include/utils.h | 0 version.h => include/version.h | 0 libdevs.c | 1408 --------------------------- f3brew.c => src/commands/f3brew.c | 0 f3fix.c => src/commands/f3fix.c | 0 f3probe.c => src/commands/f3probe.c | 0 f3read.c => src/commands/f3read.c | 0 f3write.c => src/commands/f3write.c | 0 src/core/libdevs.c | 127 +++ libflow.c => src/core/libflow.c | 0 libprobe.c => src/core/libprobe.c | 0 libutils.c => src/core/libutils.c | 0 utils.c => src/core/utils.c | 0 src/devices/file_device.c | 258 +++++ src/devices/perf_device.c | 143 +++ src/devices/safe_device.c | 310 ++++++ src/platform/darwin/block_device.c | 12 + src/platform/darwin/usb_reset.c | 11 + src/platform/linux/block_device.c | 266 +++++ src/platform/linux/usb_reset.c | 364 +++++++ 30 files changed, 1668 insertions(+), 1454 deletions(-) create mode 100644 include/devices/block_device.h create mode 100644 include/devices/file_device.h create mode 100644 include/devices/perf_device.h create mode 100644 include/devices/safe_device.h create mode 100644 include/devices/usb_reset.h rename libdevs.h => include/libdevs.h (66%) rename libflow.h => include/libflow.h (100%) rename libprobe.h => include/libprobe.h (100%) rename libutils.h => include/libutils.h (100%) rename utils.h => include/utils.h (100%) rename version.h => include/version.h (100%) delete mode 100644 libdevs.c rename f3brew.c => src/commands/f3brew.c (100%) rename f3fix.c => src/commands/f3fix.c (100%) rename f3probe.c => src/commands/f3probe.c (100%) rename f3read.c => src/commands/f3read.c (100%) rename f3write.c => src/commands/f3write.c (100%) create mode 100644 src/core/libdevs.c rename libflow.c => src/core/libflow.c (100%) rename libprobe.c => src/core/libprobe.c (100%) rename libutils.c => src/core/libutils.c (100%) rename utils.c => src/core/utils.c (100%) create mode 100644 src/devices/file_device.c create mode 100644 src/devices/perf_device.c create mode 100644 src/devices/safe_device.c create mode 100644 src/platform/darwin/block_device.c create mode 100644 src/platform/darwin/usb_reset.c create mode 100644 src/platform/linux/block_device.c create mode 100644 src/platform/linux/usb_reset.c diff --git a/Makefile b/Makefile index 82b2d72..8ef773f 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ CC ?= gcc CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -ggdb +CFLAGS += -Iinclude TARGETS = f3write f3read +BIN_TARGETS := $(addprefix bin/,$(TARGETS)) EXTRA_TARGETS = f3probe f3brew f3fix +BIN_EXTRAS := $(addprefix bin/,$(EXTRA_TARGETS)) PREFIX = /usr/local INSTALL = install @@ -11,17 +14,65 @@ LN = ln ifndef OS OS = $(shell uname -s) 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 +ifneq ($(filter $(OS),Linux Darwin),$(OS)) + $(warning Unknown OS '$(OS)', defaulting to Linux) + OS := Linux endif +# Platform-specific include and linker flags +PLATFORM_CFLAGS = +PLATFORM_LDFLAGS = + +ifeq ($(OS), Linux) + PLATFORM_DIR = linux + PLATFORM_CFLAGS += + PLATFORM_LDFLAGS += +endif +ifeq ($(OS), Darwin) + PLATFORM_DIR = darwin + ifneq ($(shell command -v brew),) + ARGP_PREFIX := $(shell brew --prefix) + else + ARGP_PREFIX := /usr/local + endif + PLATFORM_CFLAGS += -I$(ARGP_PREFIX)/include + PLATFORM_LDFLAGS += -L$(ARGP_PREFIX)/lib -largp +endif + +CFLAGS += $(PLATFORM_CFLAGS) +LDFLAGS += $(PLATFORM_LDFLAGS) + +# Common libraries used by all platforms +COMMON_LIBS = -lm + +# OS-specific libraries and flags +F3PROBE_LIBS = $(COMMON_LIBS) +F3BREW_LIBS = $(COMMON_LIBS) +F3FIX_LIBS = + +ifeq ($(OS), Linux) + F3PROBE_LIBS += -ludev + F3BREW_LIBS += -ludev + F3FIX_LIBS += -lparted +endif +ifeq ($(OS), Darwin) + F3PROBE_LIBS += + F3BREW_LIBS += + F3FIX_LIBS += +endif + +# source directories and automatic rules +SRC_DIRS = src/commands src/core src/devices src/platform/$(PLATFORM_DIR) +vpath %.c $(SRC_DIRS) + +DEVICE_SRCS := $(wildcard src/devices/*.c) +DEVICE_OBJS := $(DEVICE_SRCS:.c=.o) +PLATFORM_SRCS := $(wildcard src/platform/$(PLATFORM_DIR)/*.c) +PLATFORM_OBJS := $(PLATFORM_SRCS:.c=.o) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + all: $(TARGETS) extra: $(EXTRA_TARGETS) @@ -30,36 +81,41 @@ docker: install: all $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(TARGETS) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m755 $(BIN_TARGETS) $(DESTDIR)$(PREFIX)/bin $(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 $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(EXTRA_TARGETS) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m755 $(BIN_EXTRAS) $(DESTDIR)$(PREFIX)/bin -f3write: utils.o libflow.o f3write.o - $(CC) -o $@ $^ $(LDFLAGS) -lm +# command targets +f3write: src/commands/f3write.o src/core/utils.o src/core/libflow.o + $(CC) -o bin/$@ $^ $(LDFLAGS) $(COMMON_LIBS) -f3read: utils.o libflow.o f3read.o - $(CC) -o $@ $^ $(LDFLAGS) -lm +f3read: src/commands/f3read.o src/core/utils.o src/core/libflow.o + $(CC) -o bin/$@ $^ $(LDFLAGS) $(COMMON_LIBS) -f3probe: libutils.o libdevs.o libprobe.o f3probe.o - $(CC) -o $@ $^ $(LDFLAGS) -lm -ludev +f3probe: src/commands/f3probe.o src/core/libutils.o src/core/libdevs.o src/core/libprobe.o $(DEVICE_OBJS) $(PLATFORM_OBJS) + $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3PROBE_LIBS) -f3brew: libutils.o libdevs.o f3brew.o - $(CC) -o $@ $^ $(LDFLAGS) -lm -ludev +f3brew: src/commands/f3brew.o src/core/libutils.o src/core/libdevs.o $(DEVICE_OBJS) $(PLATFORM_OBJS) + $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3BREW_LIBS) -f3fix: libutils.o f3fix.o - $(CC) -o $@ $^ $(LDFLAGS) -lparted +f3fix: src/commands/f3fix.o src/core/libutils.o + $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3FIX_LIBS) -include *.d -PHONY: cscope clean +.PHONY: cscope cppcheck clean cscope: - cscope -b *.c *.h + cscope -b src/**/*.c include/**/*.h + +cppcheck: + cppcheck --enable=all --suppress=missingIncludeSystem \ + -Iinclude -Iinclude/devices src include clean: - rm -f *.o *.d cscope.out $(TARGETS) $(EXTRA_TARGETS) + rm -f src/**/**/*.o src/**/**/*.d cscope.out $(BIN_TARGETS) $(BIN_EXTRAS) diff --git a/include/devices/block_device.h b/include/devices/block_device.h new file mode 100644 index 0000000..e703aa2 --- /dev/null +++ b/include/devices/block_device.h @@ -0,0 +1,14 @@ +#ifndef INCLUDE_DEVICES_BLOCK_DEVICE_H +#define INCLUDE_DEVICES_BLOCK_DEVICE_H + +#include "libdevs.h" +#include + +/* 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 // INCLUDE_DEVICES_BLOCK_DEVICE_H diff --git a/include/devices/file_device.h b/include/devices/file_device.h new file mode 100644 index 0000000..3dc4a1c --- /dev/null +++ b/include/devices/file_device.h @@ -0,0 +1,23 @@ +#ifndef INCLUDE_DEVICES_FILE_DEVICE_H +#define INCLUDE_DEVICES_FILE_DEVICE_H + +#include "libdevs.h" +#include + +/* 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 // INCLUDE_DEVICES_FILE_DEVICE_H diff --git a/include/devices/perf_device.h b/include/devices/perf_device.h new file mode 100644 index 0000000..00da8ef --- /dev/null +++ b/include/devices/perf_device.h @@ -0,0 +1,19 @@ +#ifndef INCLUDE_DEVICES_PERF_DEVICE_H +#define INCLUDE_DEVICES_PERF_DEVICE_H + +#include "libdevs.h" +#include + +/* 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); + +/* Detach underlying device and free the wrapper, returning the original device. */ +struct device *pdev_detach_and_free(struct device *dev); + +#endif // INCLUDE_DEVICES_PERF_DEVICE_H diff --git a/include/devices/safe_device.h b/include/devices/safe_device.h new file mode 100644 index 0000000..9875981 --- /dev/null +++ b/include/devices/safe_device.h @@ -0,0 +1,15 @@ +#ifndef INCLUDE_DEVICES_SAFE_DEVICE_H +#define INCLUDE_DEVICES_SAFE_DEVICE_H + +#include "libdevs.h" +#include + +/* 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 // INCLUDE_DEVICES_SAFE_DEVICE_H diff --git a/include/devices/usb_reset.h b/include/devices/usb_reset.h new file mode 100644 index 0000000..6cd1d6b --- /dev/null +++ b/include/devices/usb_reset.h @@ -0,0 +1,9 @@ +#ifndef USB_RESET_H +#define USB_RESET_H + +#include "devices/block_device.h" + +int bdev_manual_usb_reset(struct device *dev); +int bdev_usb_reset(struct device *dev); + +#endif /* USB_RESET_H */ diff --git a/libdevs.h b/include/libdevs.h similarity index 66% rename from libdevs.h rename to include/libdevs.h index 1a3f9e4..229ee6c 100644 --- a/libdevs.h +++ b/include/libdevs.h @@ -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,10 +87,7 @@ 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); +// Device builders moved to devices/ headers enum reset_type { RT_MANUAL_USB = 0, @@ -88,22 +96,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 "devices/file_device.h" +#include "devices/block_device.h" +#include "devices/perf_device.h" +#include "devices/safe_device.h" #endif /* HEADER_LIBDEVS_H */ diff --git a/libflow.h b/include/libflow.h similarity index 100% rename from libflow.h rename to include/libflow.h diff --git a/libprobe.h b/include/libprobe.h similarity index 100% rename from libprobe.h rename to include/libprobe.h diff --git a/libutils.h b/include/libutils.h similarity index 100% rename from libutils.h rename to include/libutils.h diff --git a/utils.h b/include/utils.h similarity index 100% rename from utils.h rename to include/utils.h diff --git a/version.h b/include/version.h similarity index 100% rename from version.h rename to include/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/f3brew.c b/src/commands/f3brew.c similarity index 100% rename from f3brew.c rename to src/commands/f3brew.c diff --git a/f3fix.c b/src/commands/f3fix.c similarity index 100% rename from f3fix.c rename to src/commands/f3fix.c diff --git a/f3probe.c b/src/commands/f3probe.c similarity index 100% rename from f3probe.c rename to src/commands/f3probe.c diff --git a/f3read.c b/src/commands/f3read.c similarity index 100% rename from f3read.c rename to src/commands/f3read.c diff --git a/f3write.c b/src/commands/f3write.c similarity index 100% rename from f3write.c rename to src/commands/f3write.c diff --git a/src/core/libdevs.c b/src/core/libdevs.c new file mode 100644 index 0000000..3707b3f --- /dev/null +++ b/src/core/libdevs.c @@ -0,0 +1,127 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include + +#include "libdevs.h" + +// 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/libflow.c b/src/core/libflow.c similarity index 100% rename from libflow.c rename to src/core/libflow.c diff --git a/libprobe.c b/src/core/libprobe.c similarity index 100% rename from libprobe.c rename to src/core/libprobe.c diff --git a/libutils.c b/src/core/libutils.c similarity index 100% rename from libutils.c rename to src/core/libutils.c diff --git a/utils.c b/src/core/utils.c similarity index 100% rename from utils.c rename to src/core/utils.c diff --git a/src/devices/file_device.c b/src/devices/file_device.c new file mode 100644 index 0000000..26c6514 --- /dev/null +++ b/src/devices/file_device.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libdevs.h" +#include "devices/file_device.h" +#include "libutils.h" + +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/devices/perf_device.c b/src/devices/perf_device.c new file mode 100644 index 0000000..eafee1e --- /dev/null +++ b/src/devices/perf_device.c @@ -0,0 +1,143 @@ +#include +#include +#include + +#include "libdevs.h" +#include "devices/perf_device.h" +#include "libutils.h" + +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; +} diff --git a/src/devices/safe_device.c b/src/devices/safe_device.c new file mode 100644 index 0000000..db1616d --- /dev/null +++ b/src/devices/safe_device.c @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "libdevs.h" +#include "devices/safe_device.h" +#include "libutils.h" + +#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/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c new file mode 100644 index 0000000..933a563 --- /dev/null +++ b/src/platform/darwin/block_device.c @@ -0,0 +1,12 @@ +#include "devices/block_device.h" +#include "libdevs.h" +#include +#include + +// macOS stub for raw block device +struct device *create_block_device(const char *filename, enum reset_type rt) +{ + // Not implemented for macOS + fprintf(stderr, "[macOS] create_block_device: Not implemented yet.\n"); + return NULL; +} diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c new file mode 100644 index 0000000..a37d156 --- /dev/null +++ b/src/platform/darwin/usb_reset.c @@ -0,0 +1,11 @@ +#include "devices/block_device.h" +#include "libdevs.h" +#include + +// macOS stub for USB reset +int bdev_manual_usb_reset(struct device *dev) +{ + // Not implemented for macOS + fprintf(stderr, "[macOS] usb_reset: Not implemented yet.\n"); + return -1; +} diff --git a/src/platform/linux/block_device.c b/src/platform/linux/block_device.c new file mode 100644 index 0000000..529ce1f --- /dev/null +++ b/src/platform/linux/block_device.c @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices/block_device.h" +#include "devices/usb_reset.h" +#include "libutils.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; + +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; +} + +// 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 inline int bdev_open(const char *filename) +{ + return open(filename, O_RDWR | O_DIRECT); +} + +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/usb_reset.c b/src/platform/linux/usb_reset.c new file mode 100644 index 0000000..b7ce2fb --- /dev/null +++ b/src/platform/linux/usb_reset.c @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices/block_device.h" +#include "libdevs.h" +#include "devices/usb_reset.h" + +static inline struct block_device *dev_bdev(struct device *dev) +{ + return (struct block_device *)dev; +} + +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; +} + +// 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; +} From 76ed90d629cb6f35a69a4406885756d251c01915 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Wed, 30 Apr 2025 16:44:33 -0300 Subject: [PATCH 02/20] Add macos block device handling --- Makefile | 1 + include/libdevs.h | 2 - src/platform/darwin/block_device.c | 290 ++++++++++++++++++++++++++++- src/platform/darwin/usb_reset.c | 25 ++- 4 files changed, 307 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 8ef773f..e77dba5 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ ifeq ($(OS), Darwin) endif PLATFORM_CFLAGS += -I$(ARGP_PREFIX)/include PLATFORM_LDFLAGS += -L$(ARGP_PREFIX)/lib -largp + PLATFORM_LDFLAGS += -framework DiskArbitration -framework CoreFoundation endif CFLAGS += $(PLATFORM_CFLAGS) diff --git a/include/libdevs.h b/include/libdevs.h index 229ee6c..0ab44b0 100644 --- a/include/libdevs.h +++ b/include/libdevs.h @@ -87,8 +87,6 @@ void free_device(struct device *dev); * Concrete devices */ -// Device builders moved to devices/ headers - enum reset_type { RT_MANUAL_USB = 0, RT_USB, diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c index 933a563..9eb5d85 100644 --- a/src/platform/darwin/block_device.c +++ b/src/platform/darwin/block_device.c @@ -1,12 +1,292 @@ -#include "devices/block_device.h" -#include "libdevs.h" #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include "devices/block_device.h" +#include "devices/usb_reset.h" +#include "libutils.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; + +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 { + 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 partition to its parent disk using Disk Arbitration +static DADiskRef map_partition_to_disk(const char *path) +{ + DASessionRef session = DASessionCreate(kCFAllocatorDefault); + DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, path); + CFDictionaryRef desc = DADiskCopyDescription(disk); + + // Check if this is a partition + CFStringRef bsdName = CFDictionaryGetValue(desc, kDADiskDescriptionMediaBSDNameKey); + if (CFStringHasSuffix(bsdName, CFSTR("s"))) { + // This is a partition, get the parent disk + CFStringRef parentPath = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, + CFSTR("/dev/rdisk%s"), + CFStringGetCStringPtr(bsdName, kCFStringEncodingUTF8)); + DADiskRef parent = DADiskCreateFromBSDName(kCFAllocatorDefault, session, CFStringGetCStringPtr(parentPath, kCFStringEncodingUTF8)); + CFRelease(parentPath); + CFRelease(desc); + CFRelease(disk); + CFRelease(session); + return parent; + } + CFRelease(desc); + CFRelease(disk); + CFRelease(session); + return NULL; +} -// macOS stub for raw block device struct device *create_block_device(const char *filename, enum reset_type rt) { - // Not implemented for macOS - fprintf(stderr, "[macOS] create_block_device: Not implemented yet.\n"); + 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/usb_reset.c b/src/platform/darwin/usb_reset.c index a37d156..0f7347e 100644 --- a/src/platform/darwin/usb_reset.c +++ b/src/platform/darwin/usb_reset.c @@ -1,11 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "devices/block_device.h" #include "libdevs.h" -#include +#include "devices/usb_reset.h" // macOS stub for USB reset int bdev_manual_usb_reset(struct device *dev) { - // Not implemented for macOS - fprintf(stderr, "[macOS] usb_reset: Not implemented yet.\n"); - return -1; + 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) +{ + warnx("USB reset is not supported on macOS"); + return -ENOSYS; } From b1075595633a27040259e815202fa30e81c35697 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Thu, 1 May 2025 10:27:22 -0300 Subject: [PATCH 03/20] Refactor f3fix to allow multi-platform --- .gitignore | 3 +- include/devices/partition.h | 31 ++++++ src/commands/f3fix.c | 159 ++++++++++----------------- src/platform/linux/partition.c | 195 +++++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 105 deletions(-) create mode 100644 include/devices/partition.h create mode 100644 src/platform/linux/partition.c 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/include/devices/partition.h b/include/devices/partition.h new file mode 100644 index 0000000..e6a8464 --- /dev/null +++ b/include/devices/partition.h @@ -0,0 +1,31 @@ +#ifndef INCLUDE_DEVICES_PARTITION_H +#define INCLUDE_DEVICES_PARTITION_H + +#include "libdevs.h" +#include + +/* 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(struct device *dev, 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 // INCLUDE_DEVICES_PARTITION_H diff --git a/src/commands/f3fix.c b/src/commands/f3fix.c index 061c3a4..64c5f0e 100644 --- a/src/commands/f3fix.c +++ b/src/commands/f3fix.c @@ -1,10 +1,10 @@ +#include #include -#include #include -#include #include "version.h" #include "libutils.h" +#include "devices/partition.h" /* Argp's global variables. */ const char *argp_program_version = "F3 Fix " F3_STR_VERSION; @@ -45,10 +45,10 @@ struct args { /* 29 free bytes. */ const char *dev_filename; - PedDiskType *disk_type; - PedFileSystemType *fs_type; - PedSector first_sec; - PedSector last_sec; + const char *disk_type; + const char *fs_type; + uint64_t first_sec; + uint64_t last_sec; }; static long long arg_to_long_long(const struct argp_state *state, @@ -70,16 +70,16 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) switch (key) { case 'd': - args->disk_type = ped_disk_type_get(arg); - if (!args->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': - args->fs_type = ped_file_system_type_get(arg); - if (!args->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); @@ -142,7 +142,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *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"); + "Option --first_sec must be less or equal to option --last_sec"); break; default: @@ -153,98 +153,36 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL}; -static void list_disk_types(void) +static void print_array(const char *title, size_t (*getter)(char ***)) { - 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; - } + char **list = NULL; + size_t n = getter(&list); + if (!list) { + fprintf(stderr, "Failed to obtain %s\n", title); + return; } - 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; - } + printf("%s:\n", title); + for (size_t i = 0; list[i]; ++i) { + printf("%s\t", list[i]); + if (i % 5 == 4) + putchar('\n'); } - if (i > 0) - printf("\n"); - printf("\n"); + if (n % 5) + putchar('\n'); + putchar('\n'); + + partition_free_types_array(list); } -static PedSector map_sector_to_logical_sector(PedSector sector, - int logical_sector_size) +static void list_disk_types(void) { - assert(logical_sector_size >= 512); - assert(logical_sector_size % 512 == 0); - return sector / (logical_sector_size / 512); + print_array("Disk types", partition_list_disk_types); } -/* 0 on failure, 1 otherwise. */ -static int fix_disk(PedDevice *dev, PedDiskType *type, - PedFileSystemType *fs_type, int boot, PedSector start, PedSector end) +static void list_fs_types(void) { - 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; + print_array("File system types", partition_list_fs_types); } int main (int argc, char *argv[]) @@ -256,13 +194,14 @@ int main (int argc, char *argv[]) .boot = true, - .disk_type = ped_disk_type_get("msdos"), - .fs_type = ped_file_system_type_get("fat32"), + .disk_type = "msdos", + .fs_type = "fat32", .first_sec = 2048, /* Skip first 1MB. */ }; - PedDevice *dev; - int ret; + struct device *bdev; + struct partition_options opt; + int err; /* Read parameters. */ argp_parse(&argp, argc, argv, 0, NULL, &args); @@ -284,13 +223,25 @@ int main (int argc, char *argv[]) /* XXX If @dev is a partition, refer the user to * the disk of this partition. */ - dev = ped_device_get(args.dev_filename); - if (!dev) + bdev = create_block_device(args.dev_filename, RT_NONE); + if (!bdev) { + fprintf(stderr, "Failed to open device %s\n", args.dev_filename); 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; + 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(bdev, &opt); + if (err == 0) { + printf("Drive `%s' was successfully fixed\n", args.dev_filename); + } else { + fprintf(stderr, "Failed to fix drive `%s'\n", args.dev_filename); + } + free_device(bdev); + return err; } diff --git a/src/platform/linux/partition.c b/src/platform/linux/partition.c new file mode 100644 index 0000000..fe501f5 --- /dev/null +++ b/src/platform/linux/partition.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include "devices/partition.h" +#include "devices/block_device.h" +#include "libutils.h" + +// 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(struct device *dev, const struct partition_options *options) +{ + PedDevice *ped_dev; + PedDiskType *disk_type; + PedFileSystemType *fs_type; + int ret; + + // Get parted device + ped_dev = ped_device_get(bdev_get_filename(dev)); + if (!ped_dev) { + ret = 1; + goto out; + } + + // Get disk type + disk_type = ped_disk_type_get(options->disk_type); + if (!disk_type) { + ret = 1; + goto pdev; + } + + // Get file system type + fs_type = ped_file_system_type_get(options->fs_type); + if (!fs_type) { + ret = 1; + 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); +} From fbeebc2887626bc1047d71c32df57321a3dd2fbd Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Thu, 1 May 2025 11:32:04 -0300 Subject: [PATCH 04/20] Correctly parse f3fix args --- Makefile | 2 +- src/commands/f3fix.c | 87 +++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index e77dba5..32fb216 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ cscope: cscope -b src/**/*.c include/**/*.h cppcheck: - cppcheck --enable=all --suppress=missingIncludeSystem \ + cppcheck --enable=all --suppress=missingIncludeSystem --check-level=exhaustive \ -Iinclude -Iinclude/devices src include clean: diff --git a/src/commands/f3fix.c b/src/commands/f3fix.c index 64c5f0e..3598b6a 100644 --- a/src/commands/f3fix.c +++ b/src/commands/f3fix.c @@ -1,11 +1,16 @@ #include +#include #include +#include #include #include "version.h" #include "libutils.h" #include "devices/partition.h" +/* 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; @@ -41,8 +46,9 @@ struct args { bool list_fs_types; bool boot; + bool boot_flag_seen; - /* 29 free bytes. */ + /* 28 free bytes. */ const char *dev_filename; const char *disk_type; @@ -51,75 +57,81 @@ struct args { uint64_t last_sec; }; -static long long arg_to_long_long(const struct argp_state *state, - const char *arg) +static uint64_t arg_to_uint64(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; + 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; - long long ll; switch (key) { - case 'd': + 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", + "Disk type `%s' is not supported; " + "use --list-disk-types to see the supported types", arg); break; - case 'f': + 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", + "File system type `%s' is not supported; " + "use --list-fs-types to see the supported types", arg); break; - case 'b': - args->boot = true; + 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 'n': - args->boot = false; + case 'a': /* first-sec */ + args->first_sec = arg_to_uint64(state, arg); 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; + case 'l': /* last-sec */ + args->last_sec = arg_to_uint64(state, arg); break; - case 'k': + case 'k': /* list-disk-types */ args->list_disk_types = true; break; - case 's': + case 's': /* list-fs-types */ args->list_fs_types = true; break; case ARGP_KEY_INIT: args->dev_filename = NULL; - args->last_sec = -1; + args->last_sec = SEC_UNSET; break; case ARGP_KEY_ARG: @@ -137,7 +149,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) argp_error(state, "The disk device was not specified"); - if (args->last_sec < 0) + if (args->last_sec == SEC_UNSET) argp_error(state, "Option --last-sec is required"); if (args->first_sec > args->last_sec) @@ -193,6 +205,7 @@ int main (int argc, char *argv[]) .list_fs_types = false, .boot = true, + .boot_flag_seen = false, .disk_type = "msdos", .fs_type = "fat32", From 818c3c35c3ce14a42492dc4d8d6c00a5b217e0ad Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Fri, 2 May 2025 20:37:03 -0300 Subject: [PATCH 05/20] Add f3fix partition support for macos Launches diskutil process to perform the actual partition changes. --- Makefile | 2 +- include/devices/partition.h | 2 +- src/commands/f3fix.c | 37 ++++-- src/platform/darwin/partition.c | 216 ++++++++++++++++++++++++++++++++ src/platform/linux/partition.c | 10 +- 5 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 src/platform/darwin/partition.c diff --git a/Makefile b/Makefile index 32fb216..01af43b 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ f3probe: src/commands/f3probe.o src/core/libutils.o src/core/libdevs.o src/core/ f3brew: src/commands/f3brew.o src/core/libutils.o src/core/libdevs.o $(DEVICE_OBJS) $(PLATFORM_OBJS) $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3BREW_LIBS) -f3fix: src/commands/f3fix.o src/core/libutils.o +f3fix: src/commands/f3fix.o src/core/libutils.o src/core/libdevs.o $(DEVICE_OBJS) $(PLATFORM_OBJS) $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3FIX_LIBS) -include *.d diff --git a/include/devices/partition.h b/include/devices/partition.h index e6a8464..9cf820a 100644 --- a/include/devices/partition.h +++ b/include/devices/partition.h @@ -14,7 +14,7 @@ struct partition_options { }; /* Partition management functions */ -int partition_create(struct device *dev, const struct partition_options *options); +int partition_create(const char *dev_filename, const struct partition_options *options); /* Type validation functions */ bool is_valid_disk_type(const char *disk_type); diff --git a/src/commands/f3fix.c b/src/commands/f3fix.c index 3598b6a..7576aea 100644 --- a/src/commands/f3fix.c +++ b/src/commands/f3fix.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -59,25 +60,25 @@ struct args { static uint64_t arg_to_uint64(const struct argp_state *state, const char *arg) { - char *end; - errno = 0; + 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); + /* 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); + 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; + return (uint64_t)v; } static error_t parse_opt(int key, char *arg, struct argp_state *state) @@ -235,6 +236,7 @@ int main (int argc, char *argv[]) /* 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) { @@ -242,6 +244,16 @@ int main (int argc, char *argv[]) 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); // Safe to free now that we have our copy + opt = (struct partition_options){ .disk_type = args.disk_type, .fs_type = args.fs_type, @@ -249,12 +261,11 @@ int main (int argc, char *argv[]) .first_sector = args.first_sec, .last_sector = args.last_sec, }; - err = partition_create(bdev, &opt); + err = partition_create(dev_filename, &opt); if (err == 0) { - printf("Drive `%s' was successfully fixed\n", args.dev_filename); + printf("Drive `%s' was successfully fixed\n", dev_filename); } else { - fprintf(stderr, "Failed to fix drive `%s'\n", args.dev_filename); + fprintf(stderr, "Failed to fix drive `%s'\n", dev_filename); } - free_device(bdev); return err; } diff --git a/src/platform/darwin/partition.c b/src/platform/darwin/partition.c new file mode 100644 index 0000000..259ffe2 --- /dev/null +++ b/src/platform/darwin/partition.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include // for _NSGetEnviron() + +#include "devices/partition.h" + +/* 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 */ +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/linux/partition.c b/src/platform/linux/partition.c index fe501f5..caa33fc 100644 --- a/src/platform/linux/partition.c +++ b/src/platform/linux/partition.c @@ -1,6 +1,7 @@ #include #include #include + #include "devices/partition.h" #include "devices/block_device.h" #include "libutils.h" @@ -63,31 +64,28 @@ static int parted_fix_disk(PedDevice *dev, PedDiskType *type, } // Create partition on a device -int partition_create(struct device *dev, const struct partition_options *options) +int partition_create(const char *dev_filename, const struct partition_options *options) { PedDevice *ped_dev; PedDiskType *disk_type; PedFileSystemType *fs_type; - int ret; + int ret = 1; // assume failure // Get parted device - ped_dev = ped_device_get(bdev_get_filename(dev)); + ped_dev = ped_device_get(dev_filename); if (!ped_dev) { - ret = 1; goto out; } // Get disk type disk_type = ped_disk_type_get(options->disk_type); if (!disk_type) { - ret = 1; goto pdev; } // Get file system type fs_type = ped_file_system_type_get(options->fs_type); if (!fs_type) { - ret = 1; goto pdev; } From be2f53bd2455372de8bedb89cbb2f537273e085f Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Fri, 2 May 2025 21:00:47 -0300 Subject: [PATCH 06/20] Style fix for refactored sources Apply tabs (display size 8) and comments styling. Also refactor darwin/block_device.c map_partition_to_disk with a cleaner implementation. --- include/devices/block_device.h | 2 +- include/devices/file_device.h | 8 +- include/devices/partition.h | 14 +- include/devices/perf_device.h | 8 +- include/devices/safe_device.h | 4 +- include/devices/usb_reset.h | 2 +- include/libdevs.h | 6 +- src/commands/f3fix.c | 7 +- src/core/libdevs.c | 34 ++-- src/devices/file_device.c | 2 +- src/platform/darwin/block_device.c | 307 +++++++++++++++-------------- src/platform/darwin/partition.c | 276 +++++++++++++------------- src/platform/darwin/usb_reset.c | 12 +- src/platform/linux/block_device.c | 76 +++---- src/platform/linux/partition.c | 230 ++++++++++----------- src/platform/linux/usb_reset.c | 30 +-- 16 files changed, 517 insertions(+), 501 deletions(-) diff --git a/include/devices/block_device.h b/include/devices/block_device.h index e703aa2..ab1d8ca 100644 --- a/include/devices/block_device.h +++ b/include/devices/block_device.h @@ -11,4 +11,4 @@ */ struct device *create_block_device(const char *filename, enum reset_type rt); -#endif // INCLUDE_DEVICES_BLOCK_DEVICE_H +#endif /* INCLUDE_DEVICES_BLOCK_DEVICE_H */ diff --git a/include/devices/file_device.h b/include/devices/file_device.h index 3dc4a1c..e31adb5 100644 --- a/include/devices/file_device.h +++ b/include/devices/file_device.h @@ -16,8 +16,8 @@ * 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); + 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 // INCLUDE_DEVICES_FILE_DEVICE_H +#endif /* INCLUDE_DEVICES_FILE_DEVICE_H */ diff --git a/include/devices/partition.h b/include/devices/partition.h index 9cf820a..e475103 100644 --- a/include/devices/partition.h +++ b/include/devices/partition.h @@ -4,13 +4,13 @@ #include "libdevs.h" #include -/* Partition creation options */ +/* 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; + 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 */ @@ -28,4 +28,4 @@ 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 // INCLUDE_DEVICES_PARTITION_H +#endif /* INCLUDE_DEVICES_PARTITION_H */ diff --git a/include/devices/perf_device.h b/include/devices/perf_device.h index 00da8ef..475a56e 100644 --- a/include/devices/perf_device.h +++ b/include/devices/perf_device.h @@ -9,11 +9,11 @@ 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); + 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 underlying device and free the wrapper, returning the original device. */ struct device *pdev_detach_and_free(struct device *dev); -#endif // INCLUDE_DEVICES_PERF_DEVICE_H +#endif /* INCLUDE_DEVICES_PERF_DEVICE_H */ diff --git a/include/devices/safe_device.h b/include/devices/safe_device.h index 9875981..50e9d0d 100644 --- a/include/devices/safe_device.h +++ b/include/devices/safe_device.h @@ -6,10 +6,10 @@ /* Create a safe device wrapper: snapshots writes for rollback. */ struct device *create_safe_device(struct device *dev, - uint64_t max_blocks, int min_memory); + 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 // INCLUDE_DEVICES_SAFE_DEVICE_H +#endif /* INCLUDE_DEVICES_SAFE_DEVICE_H */ diff --git a/include/devices/usb_reset.h b/include/devices/usb_reset.h index 6cd1d6b..905ac25 100644 --- a/include/devices/usb_reset.h +++ b/include/devices/usb_reset.h @@ -6,4 +6,4 @@ int bdev_manual_usb_reset(struct device *dev); int bdev_usb_reset(struct device *dev); -#endif /* USB_RESET_H */ +#endif /* USB_RESET_H */ diff --git a/include/libdevs.h b/include/libdevs.h index 0ab44b0..776d3a0 100644 --- a/include/libdevs.h +++ b/include/libdevs.h @@ -39,10 +39,10 @@ enum fake_type dev_param_to_type(uint64_t real_size_byte, */ struct device { - uint64_t size_byte; - int block_order; + uint64_t size_byte; + int block_order; - int (*read_blocks)(struct device *dev, char *buf, + 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); diff --git a/src/commands/f3fix.c b/src/commands/f3fix.c index 7576aea..86fc43d 100644 --- a/src/commands/f3fix.c +++ b/src/commands/f3fix.c @@ -68,7 +68,7 @@ static uint64_t arg_to_uint64(const struct argp_state *state, const char *arg) unsigned long long v = strtoull(arg, &end, 0); - /* reject empty string, garbage suffix, or overflow */ + /* Reject empty string, garbage suffix, or overflow. */ if (end == arg || *end || errno == ERANGE) argp_error(state, "`%s' is not a valid unsigned integer", arg); @@ -244,15 +244,14 @@ int main (int argc, char *argv[]) return 1; } - // Get and copy the filename before freeing the device + /* 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); // Safe to free now that we have our copy + free_device(bdev); opt = (struct partition_options){ .disk_type = args.disk_type, diff --git a/src/core/libdevs.c b/src/core/libdevs.c index 3707b3f..addbd4c 100644 --- a/src/core/libdevs.c +++ b/src/core/libdevs.c @@ -12,19 +12,19 @@ #include "libdevs.h" -// Map fake_type to string +/* 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", + [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]; + assert(fake_type < FKTY_MAX); + return ftype_to_name[fake_type]; } int dev_param_valid(uint64_t real_size_byte, @@ -75,25 +75,25 @@ enum fake_type dev_param_to_type(uint64_t real_size_byte, return FKTY_LIMBO; } -// Abstract device interface and wrappers +/* Abstract device interface and wrappers. */ uint64_t dev_get_size_byte(struct device *dev) { - return dev->size_byte; + return dev->size_byte; } int dev_get_block_order(struct device *dev) { - return dev->block_order; + return dev->block_order; } int dev_get_block_size(struct device *dev) { - return 1 << dev->block_order; + return 1 << dev->block_order; } const char *dev_get_filename(struct device *dev) { - return dev->get_filename(dev); + return dev->get_filename(dev); } int dev_read_blocks(struct device *dev, char *buf, @@ -116,12 +116,12 @@ int dev_write_blocks(struct device *dev, const char *buf, int dev_reset(struct device *dev) { - return dev->reset ? dev->reset(dev) : 0; + return dev->reset ? dev->reset(dev) : 0; } void free_device(struct device *dev) { - if (dev->free) - dev->free(dev); - free(dev); + if (dev->free) + dev->free(dev); + free(dev); } diff --git a/src/devices/file_device.c b/src/devices/file_device.c index 26c6514..3a53683 100644 --- a/src/devices/file_device.c +++ b/src/devices/file_device.c @@ -26,7 +26,7 @@ struct file_device { static inline struct file_device *dev_fdev(struct device *dev) { - return (struct file_device *)dev; + return (struct file_device *)dev; } static int fdev_read_block(struct device *dev, char *buf, uint64_t block_pos) diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c index 9eb5d85..8285cca 100644 --- a/src/platform/darwin/block_device.c +++ b/src/platform/darwin/block_device.c @@ -31,7 +31,7 @@ struct block_device { static inline struct block_device *dev_bdev(struct device *dev) { - return (struct block_device *)dev; + return (struct block_device *)dev; } static int read_all(int fd, char *buf, size_t count) @@ -61,34 +61,34 @@ static int read_all(int fd, char *buf, size_t count) 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; + 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) + 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); + 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) + uint64_t first_pos, uint64_t last_pos) { - struct block_device *bdev = dev_bdev(dev); + 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; @@ -103,16 +103,16 @@ static int bdev_write_blocks(struct device *dev, const char *buf, rc = fsync(bdev->fd); if (rc) return rc; - return 0; + 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; + int fd = open(filename, O_RDWR); + if (fd >= 0) { + fcntl(fd, F_NOCACHE, 1); + } + return fd; } static int bdev_none_reset(struct device *dev) @@ -131,44 +131,61 @@ static void bdev_free(struct device *dev) static const char *bdev_get_filename(struct device *dev) { - return dev_bdev(dev)->filename; + return dev_bdev(dev)->filename; } -// Map a partition to its parent disk using Disk Arbitration +/* 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 = DASessionCreate(kCFAllocatorDefault); - DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, path); - CFDictionaryRef desc = DADiskCopyDescription(disk); + DASessionRef session = NULL; + DADiskRef disk = NULL; + DADiskRef whole_disk = NULL; + DADiskRef parent_disk_to_return = NULL; - // Check if this is a partition - CFStringRef bsdName = CFDictionaryGetValue(desc, kDADiskDescriptionMediaBSDNameKey); - if (CFStringHasSuffix(bsdName, CFSTR("s"))) { - // This is a partition, get the parent disk - CFStringRef parentPath = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, - CFSTR("/dev/rdisk%s"), - CFStringGetCStringPtr(bsdName, kCFStringEncodingUTF8)); - DADiskRef parent = DADiskCreateFromBSDName(kCFAllocatorDefault, session, CFStringGetCStringPtr(parentPath, kCFStringEncodingUTF8)); - CFRelease(parentPath); - CFRelease(desc); - CFRelease(disk); - CFRelease(session); - return parent; + session = DASessionCreate(kCFAllocatorDefault); + if (!session) { + goto cleanup; } - CFRelease(desc); - CFRelease(disk); - CFRelease(session); - return NULL; + + 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; + DADiskRef disk; + CFDictionaryRef properties; int block_size, block_order; - uint64_t deviceSize; + uint64_t deviceSize; bdev = malloc(sizeof(*bdev)); if (!bdev) @@ -180,113 +197,113 @@ struct device *create_block_device(const char *filename, enum reset_type rt) 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); - } + 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) { + } + + /* 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); + 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; + /* 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); } + if (properties) { CFRelease(properties); } da_disk: - if (disk) { CFRelease(disk); } + if (disk) { CFRelease(disk); } da_session: - if (session) { CFRelease(session); } + if (session) { CFRelease(session); } fd: assert(!close(bdev->fd)); filename: - free((void *)bdev->filename); + free((void *)bdev->filename); bdev: - free(bdev); + free(bdev); error: - return NULL; + return NULL; } diff --git a/src/platform/darwin/partition.c b/src/platform/darwin/partition.c index 259ffe2..3162ca1 100644 --- a/src/platform/darwin/partition.c +++ b/src/platform/darwin/partition.c @@ -9,208 +9,208 @@ #include "devices/partition.h" -/* Supported types */ +/* 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 */ + 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; + 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; + for (const char *const *p = fs_types; *p; ++p) + if (strcasecmp(*p, t) == 0) + return true; + return false; } -/* Spawn wrapper */ +/* 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) + 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; + 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"); - } + 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; + 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 */ +/* Function to unmount a disk using diskutil. */ int unmount_disk(const char *disk) { - char cmd[128]; - snprintf(cmd, sizeof(cmd), - "/usr/sbin/diskutil unmountDisk %s", disk); + char cmd[128]; + snprintf(cmd, sizeof(cmd), + "/usr/sbin/diskutil unmountDisk %s", disk); #ifdef DEBUG - fprintf(stderr, "DEBUG: %s\n", cmd); + fprintf(stderr, "DEBUG: %s\n", cmd); #endif - return system(cmd) == 0 ? 0 : 1; + return system(cmd) == 0 ? 0 : 1; } -/* Mark first slice bootable (ACTIVE) via fdisk +/* 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"; + /* 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 = ""; + redirect = ""; #endif - snprintf(cmd, sizeof cmd, - "echo '%s\nw\nq\n' | fdisk -e -y -u %s %s", - active ? "f 1" : "f 0", disk, redirect); + 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); + fprintf(stderr, "DEBUG: %s\n", cmd); #endif - return system(cmd) == 0 ? 0 : 1; + return system(cmd) == 0 ? 0 : 1; } -/* Fix disk */ +/* 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); + 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; + return ret; } -/* Array helpers */ +/* 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; + char **types = NULL; + size_t count = 0; + size_t i = 0; - while (src[count]) - ++count; + while (src[count]) + ++count; - types = calloc(count + 1, sizeof(char *)); - if (!types) - goto err; + 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; - } + for (i = 0; i < count; ++i) { + types[i] = strdup(src[i]); + if (!types[i]) + goto oom; + } - *out = types; - return count; + *out = types; + return count; oom: - /* free partial array */ - while (i) - free(types[--i]); - free(types); + /* Free partial array. */ + while (i) + free(types[--i]); + free(types); err: - *out = NULL; - return 0; + *out = NULL; + return 0; } -/* Public wrappers */ +/* Public wrappers. */ size_t partition_list_disk_types(char ***out) { - return build_types_array(disk_types, out); + return build_types_array(disk_types, out); } size_t partition_list_fs_types(char ***out) { - return build_types_array(fs_types, 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); + if (!array) return; + for (char **p = array; *p; ++p) + free(*p); + free(array); } diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c index 0f7347e..6240472 100644 --- a/src/platform/darwin/usb_reset.c +++ b/src/platform/darwin/usb_reset.c @@ -13,16 +13,16 @@ #include "libdevs.h" #include "devices/usb_reset.h" -// macOS stub for USB reset +/* macOS stub for USB reset. */ int bdev_manual_usb_reset(struct device *dev) { - warnx("USB reset is not supported on macOS"); - return -ENOSYS; + warnx("USB reset is not supported on macOS"); + return -ENOSYS; } -// Reset a USB-backed block device by prompting a detach/reattach +/* Reset a USB-backed block device by prompting a detach/reattach. */ int bdev_usb_reset(struct device *dev) { - warnx("USB reset is not supported on macOS"); - return -ENOSYS; + warnx("USB reset is not supported on macOS"); + return -ENOSYS; } diff --git a/src/platform/linux/block_device.c b/src/platform/linux/block_device.c index 529ce1f..51b0b17 100644 --- a/src/platform/linux/block_device.c +++ b/src/platform/linux/block_device.c @@ -29,7 +29,7 @@ struct block_device { static inline struct block_device *dev_bdev(struct device *dev) { - return (struct block_device *)dev; + return (struct block_device *)dev; } static int read_all(int fd, char *buf, size_t count) @@ -61,54 +61,54 @@ static int read_all(int fd, char *buf, size_t count) // 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; + 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) + 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); + 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) + 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); + 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); + return open(filename, O_RDWR | O_DIRECT); } static int bdev_none_reset(struct device *dev) @@ -127,7 +127,7 @@ static void bdev_free(struct device *dev) static const char *bdev_get_filename(struct device *dev) { - return dev_bdev(dev)->filename; + return dev_bdev(dev)->filename; } // Map a partition udev_device to its parent disk device diff --git a/src/platform/linux/partition.c b/src/platform/linux/partition.c index caa33fc..d57db35 100644 --- a/src/platform/linux/partition.c +++ b/src/platform/linux/partition.c @@ -8,107 +8,107 @@ // Convert physical sector to logical sector static PedSector map_sector_to_logical_sector(PedSector sector, - int logical_sector_size) + int logical_sector_size) { - assert(logical_sector_size >= 512); - assert(logical_sector_size % 512 == 0); - return sector / (logical_sector_size / 512); + 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) + 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; + 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); + ped_partition_destroy(part); disk: - ped_disk_destroy(disk); + ped_disk_destroy(disk); out: - return ret; + 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 + 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); + ped_device_destroy(ped_dev); out: - return ret; + return ret; } /* Type validation functions */ bool is_valid_disk_type(const char *disk_type) { - return ped_disk_type_get(disk_type) != NULL; + 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; + return ped_file_system_type_get(fs_type) != NULL; } /* Generic array helpers */ @@ -117,77 +117,77 @@ 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; + 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); + /* free partial array */ + while (i) + free(types[--i]); + free(types); err: - *out = NULL; - return 0; + *out = NULL; + return 0; } /* Concrete array adapters */ static const void *disk_next(const void *p) { - return ped_disk_type_get_next((const PedDiskType *)p); + return ped_disk_type_get_next((const PedDiskType *)p); } static const char *disk_name(const void *p) { - return ((const PedDiskType *)p)->name; + return ((const PedDiskType *)p)->name; } static const void *fs_next(const void *p) { - return ped_file_system_type_get_next((const PedFileSystemType *)p); + return ped_file_system_type_get_next((const PedFileSystemType *)p); } static const char *fs_name(const void *p) { - return ((const PedFileSystemType *)p)->name; + 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); + 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); + 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); + if (!array) return; + for (char **p = array; *p; ++p) + free(*p); + free(array); } diff --git a/src/platform/linux/usb_reset.c b/src/platform/linux/usb_reset.c index b7ce2fb..7430159 100644 --- a/src/platform/linux/usb_reset.c +++ b/src/platform/linux/usb_reset.c @@ -17,7 +17,7 @@ static inline struct block_device *dev_bdev(struct device *dev) { - return (struct block_device *)dev; + return (struct block_device *)dev; } static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev) @@ -40,7 +40,7 @@ static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev) * See udev_device_get_parent_with_subsystem_devtype() for * details. */ - return udev_device_ref(usb_dev); + return udev_device_ref(usb_dev); } static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) @@ -49,35 +49,35 @@ static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) if (fstat(block_fd, &fd_stat)) { warn("Can't fstat() FD %i", block_fd); - return NULL; - } + return NULL; + } if (!S_ISBLK(fd_stat.st_mode)) { warnx("FD %i is not a block device", block_fd); - return NULL; - } + 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) + 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(mon); assert(!udev_monitor_filter_add_match_subsystem_devtype(mon, subsystem, devtype)); - assert(!udev_monitor_enable_receiving(mon)); + 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(flags >= 0); assert(!fcntl(mon_fd, F_SETFL, flags & ~O_NONBLOCK)); - return mon; + return mon; } static uint64_t get_udev_dev_size_byte(struct udev_device *dev) @@ -87,9 +87,9 @@ static uint64_t get_udev_dev_size_byte(struct udev_device *dev) char *end; long long size_sector; if (!str_size_sector) - return 0; + return 0; size_sector = strtoll(str_size_sector, &end, 10); - assert(!*end); + assert(!*end); return size_sector * 512LL; } @@ -178,10 +178,10 @@ static int wait_for_reset(struct udev *udev, const char *id_serial, } free((void *)*pfinal_dev_filename); *pfinal_dev_filename = devnode; - done = true; + done = true; next: - udev_device_unref(dev); + udev_device_unref(dev); } while (!done); rc = 0; From 2390b8350dc3de9b13cf16c2ac16a7de182bee00 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Tue, 6 May 2025 23:35:41 -0300 Subject: [PATCH 07/20] Update Makefile to build objs out of src tree --- Makefile | 168 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index 01af43b..e9fbc24 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,53 @@ +# --- Basic Configuration --- CC ?= gcc -CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -ggdb +# Standard CFLAGS: C99, warnings, pedantic, dependency generation, debug info +# -MMD: Generate dependency file as a side effect +# -MP: Add phony targets for headers to avoid errors if headers are deleted +# -MF $(@:.o=.d): Name the dependency file based on the object file name, +# placing it next to the object file in the build directory. +CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -MP -MF $(@:.o=.d) -ggdb CFLAGS += -Iinclude +# --- Target Definitions --- TARGETS = f3write f3read -BIN_TARGETS := $(addprefix bin/,$(TARGETS)) EXTRA_TARGETS = f3probe f3brew f3fix -BIN_EXTRAS := $(addprefix bin/,$(EXTRA_TARGETS)) +# --- Installation Paths and Tools --- PREFIX = /usr/local INSTALL = install LN = ln -ifndef OS - OS = $(shell uname -s) -endif -ifneq ($(filter $(OS),Linux Darwin),$(OS)) +# --- OS Detection --- +OS ?= $(shell uname -s) +ifneq ($(filter $(OS),Linux Darwin),) + # Supported OS - continue +else $(warning Unknown OS '$(OS)', defaulting to Linux) OS := Linux endif -# Platform-specific include and linker flags +# --- 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 = + ifeq ($(OS), Linux) PLATFORM_DIR = linux - PLATFORM_CFLAGS += + PLATFORM_CFLAGS += -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 PLATFORM_LDFLAGS += + F3PROBE_LIBS += -ludev + F3BREW_LIBS += -ludev + F3FIX_LIBS += -lparted endif ifeq ($(OS), Darwin) - PLATFORM_DIR = darwin + PLATFORM_DIR = darwin ifneq ($(shell command -v brew),) ARGP_PREFIX := $(shell brew --prefix) else @@ -43,80 +61,116 @@ endif CFLAGS += $(PLATFORM_CFLAGS) LDFLAGS += $(PLATFORM_LDFLAGS) -# Common libraries used by all platforms -COMMON_LIBS = -lm +# --- Output Directory Setup --- +BUILD_DIR := build +BIN_DIR := $(BUILD_DIR)/bin +OBJ_DIR := $(BUILD_DIR)/obj -# OS-specific libraries and flags -F3PROBE_LIBS = $(COMMON_LIBS) -F3BREW_LIBS = $(COMMON_LIBS) -F3FIX_LIBS = - -ifeq ($(OS), Linux) - F3PROBE_LIBS += -ludev - F3BREW_LIBS += -ludev - F3FIX_LIBS += -lparted -endif -ifeq ($(OS), Darwin) - F3PROBE_LIBS += - F3BREW_LIBS += - F3FIX_LIBS += -endif +BIN_TARGETS := $(addprefix $(BIN_DIR)/,$(TARGETS)) +BIN_EXTRAS := $(addprefix $(BIN_DIR)/,$(EXTRA_TARGETS)) -# source directories and automatic rules +# Source directories and automatic search rules SRC_DIRS = src/commands src/core src/devices src/platform/$(PLATFORM_DIR) vpath %.c $(SRC_DIRS) +# Find source files relative to source root +COMMAND_SRCS := $(wildcard src/commands/*.c) +CORE_SRCS := $(wildcard src/core/*.c) DEVICE_SRCS := $(wildcard src/devices/*.c) -DEVICE_OBJS := $(DEVICE_SRCS:.c=.o) PLATFORM_SRCS := $(wildcard src/platform/$(PLATFORM_DIR)/*.c) -PLATFORM_OBJS := $(PLATFORM_SRCS:.c=.o) -%.o: %.c +# Map source paths to object paths in OBJ_DIR +ALL_SRCS := $(COMMAND_SRCS) $(CORE_SRCS) $(DEVICE_SRCS) $(PLATFORM_SRCS) +ALL_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(ALL_SRCS:.c=.o))) + +# Define reusable object lists +DEVICE_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(DEVICE_SRCS:.c=.o))) +PLATFORM_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(PLATFORM_SRCS:.c=.o))) +LIBDEVS_OBJS := $(addprefix $(OBJ_DIR)/,libdevs.o libutils.o) +LIBDEVICE_PLATFORM_OBJS := $(DEVICE_OBJS) $(PLATFORM_OBJS) $(LIBDEVS_OBJS) +LIBFLOW_OBJS := $(addprefix $(OBJ_DIR)/,libflow.o utils.o) + +# --- Pattern Rule for Compilation --- +# This rule tells make how to build an object file at a path like build/obj/src/commands/%.o +# from a source file named %.c (which make finds using vpath). +# The target explicitly defines the output location in OBJ_DIR. +# Make finds the prerequisite %.c using the vpath directive. +$(OBJ_DIR)/%.o: %.c + @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ -all: $(TARGETS) -extra: $(EXTRA_TARGETS) +# --- Main Build Targets --- +all: $(BIN_TARGETS) $(BIN_DIR) +extra: $(BIN_EXTRAS) $(BIN_DIR) -docker: - docker build -f Dockerfile -t f3:latest . +# --- Directory Targets --- +# Explicit targets for the output directories to make them order-only prerequisites +$(BUILD_DIR): + @mkdir -p $@ + +$(OBJ_DIR): | $(BUILD_DIR) + @mkdir -p $@ + +$(BIN_DIR): | $(BUILD_DIR) + @mkdir -p $@ + +# --- Binary Linking Rules (In BIN_DIR) --- +$(BIN_DIR)/f3write: $(OBJ_DIR)/f3write.o $(LIBFLOW_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3WRITE_LIBS) + +$(BIN_DIR)/f3read: $(OBJ_DIR)/f3read.o $(LIBFLOW_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3READ_LIBS) + +$(BIN_DIR)/f3probe: $(OBJ_DIR)/f3probe.o $(OBJ_DIR)/libprobe.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3PROBE_LIBS) + +$(BIN_DIR)/f3brew: $(OBJ_DIR)/f3brew.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3BREW_LIBS) + +$(BIN_DIR)/f3fix: $(OBJ_DIR)/f3fix.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) + $(CC) -o $@ $^ $(LDFLAGS) $(F3FIX_LIBS) + +# --- Dependency Inclusion --- +# Include the generated dependency files. +# With -MF $(@:.o=.d), the .d files are created alongside the .o files. +# So we include them from the OBJ_DIR. +-include $(ALL_OBJS:.o=.d) +# --- Installation Targets --- +# Install binaries from the BIN_DIR to the system PREFIX/bin install: all + @echo "Installing binaries from $(BIN_DIR) to $(DESTDIR)$(PREFIX)/bin" $(INSTALL) -d $(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 $(BIN_EXTRAS) $(DESTDIR)$(PREFIX)/bin -# command targets -f3write: src/commands/f3write.o src/core/utils.o src/core/libflow.o - $(CC) -o bin/$@ $^ $(LDFLAGS) $(COMMON_LIBS) - -f3read: src/commands/f3read.o src/core/utils.o src/core/libflow.o - $(CC) -o bin/$@ $^ $(LDFLAGS) $(COMMON_LIBS) - -f3probe: src/commands/f3probe.o src/core/libutils.o src/core/libdevs.o src/core/libprobe.o $(DEVICE_OBJS) $(PLATFORM_OBJS) - $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3PROBE_LIBS) - -f3brew: src/commands/f3brew.o src/core/libutils.o src/core/libdevs.o $(DEVICE_OBJS) $(PLATFORM_OBJS) - $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3BREW_LIBS) - -f3fix: src/commands/f3fix.o src/core/libutils.o src/core/libdevs.o $(DEVICE_OBJS) $(PLATFORM_OBJS) - $(CC) -o bin/$@ $^ $(LDFLAGS) $(F3FIX_LIBS) - --include *.d - -.PHONY: cscope cppcheck clean - +# --- Development Helper Targets --- cscope: cscope -b src/**/*.c include/**/*.h cppcheck: - cppcheck --enable=all --suppress=missingIncludeSystem --check-level=exhaustive \ + cppcheck --enable=all --suppress=missingIncludeSystem \ -Iinclude -Iinclude/devices src include +docker: + docker build -f Dockerfile -t f3:latest . + +# --- Clean Target --- +# The clean target removes the entire build directory clean: - rm -f src/**/**/*.o src/**/**/*.d cscope.out $(BIN_TARGETS) $(BIN_EXTRAS) + @rm -rf $(BUILD_DIR) + @rm -f cscope.out + +# --- Phony Targets --- +# Declare targets that do not produce files of the same name as phony +.PHONY: all extra docker install install-extra cscope cppcheck clean +# Add directory targets to PHONY to ensure they are always considered +.PHONY: $(BUILD_DIR) $(OBJ_DIR) $(BIN_DIR) From 7993c56efcf723d2405e14e2533caf8c0c940dcb Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Wed, 7 May 2025 11:34:12 -0300 Subject: [PATCH 08/20] Update the Makefile to use subdirs in build/obj Simplify and cleanup the Makefile a bit. Good reference on dependency files generation: https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ --- Makefile | 105 +++++++++++++++++++++---------------------------------- 1 file changed, 40 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index e9fbc24..bbc9396 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,6 @@ # --- Basic Configuration --- CC ?= gcc -# Standard CFLAGS: C99, warnings, pedantic, dependency generation, debug info -# -MMD: Generate dependency file as a side effect -# -MP: Add phony targets for headers to avoid errors if headers are deleted -# -MF $(@:.o=.d): Name the dependency file based on the object file name, -# placing it next to the object file in the build directory. -CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -MP -MF $(@:.o=.d) -ggdb +CFLAGS += -std=c99 -Wall -Wextra -pedantic -ggdb CFLAGS += -Iinclude # --- Target Definitions --- @@ -19,11 +14,17 @@ LN = ln # --- OS Detection --- OS ?= $(shell uname -s) -ifneq ($(filter $(OS),Linux Darwin),) - # Supported OS - continue +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) - OS := Linux + PLATFORM := linux endif # --- Platform-Specific Flags and Libraries Definitions --- @@ -38,7 +39,7 @@ F3PROBE_LIBS = $(COMMON_LIBS) F3BREW_LIBS = $(COMMON_LIBS) F3FIX_LIBS = -ifeq ($(OS), Linux) +ifeq ($(PLATFORM), linux) PLATFORM_DIR = linux PLATFORM_CFLAGS += -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 PLATFORM_LDFLAGS += @@ -46,7 +47,7 @@ ifeq ($(OS), Linux) F3BREW_LIBS += -ludev F3FIX_LIBS += -lparted endif -ifeq ($(OS), Darwin) +ifeq ($(PLATFORM), darwin) PLATFORM_DIR = darwin ifneq ($(shell command -v brew),) ARGP_PREFIX := $(shell brew --prefix) @@ -68,76 +69,55 @@ 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/commands src/core src/devices src/platform/$(PLATFORM_DIR) vpath %.c $(SRC_DIRS) - -# Find source files relative to source root -COMMAND_SRCS := $(wildcard src/commands/*.c) -CORE_SRCS := $(wildcard src/core/*.c) -DEVICE_SRCS := $(wildcard src/devices/*.c) -PLATFORM_SRCS := $(wildcard src/platform/$(PLATFORM_DIR)/*.c) - -# Map source paths to object paths in OBJ_DIR -ALL_SRCS := $(COMMAND_SRCS) $(CORE_SRCS) $(DEVICE_SRCS) $(PLATFORM_SRCS) -ALL_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(ALL_SRCS:.c=.o))) - -# Define reusable object lists -DEVICE_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(DEVICE_SRCS:.c=.o))) -PLATFORM_OBJS := $(addprefix $(OBJ_DIR)/,$(notdir $(PLATFORM_SRCS:.c=.o))) -LIBDEVS_OBJS := $(addprefix $(OBJ_DIR)/,libdevs.o libutils.o) +# Find source files relative to source root and map them to object paths in OBJ_DIR +ALL_SRCS := $(wildcard $(addsuffix /*.c, $(SRC_DIRS))) +ALL_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(ALL_SRCS)) +# Device and platform-specific object lists +DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/devices/*.c)) +PLATFORM_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/*.c)) +# Reusable object lists +LIBDEVS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libdevs.c src/core/libutils.c) +LIBFLOW_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libflow.c src/core/utils.c) LIBDEVICE_PLATFORM_OBJS := $(DEVICE_OBJS) $(PLATFORM_OBJS) $(LIBDEVS_OBJS) -LIBFLOW_OBJS := $(addprefix $(OBJ_DIR)/,libflow.o utils.o) + +# --- Dependency Inclusion --- +DEPFLAGS = -MMD -MT $@ -MP -MF $(@:.o=.d) +-include $(ALL_OBJS:.o=.d) # --- Pattern Rule for Compilation --- -# This rule tells make how to build an object file at a path like build/obj/src/commands/%.o -# from a source file named %.c (which make finds using vpath). -# The target explicitly defines the output location in OBJ_DIR. -# Make finds the prerequisite %.c using the vpath directive. -$(OBJ_DIR)/%.o: %.c - @mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ +$(OBJ_DIR)/%.o: src/%.c | $(OBJ_DIR) + @mkdir -p $(@D) + $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ # --- Main Build Targets --- -all: $(BIN_TARGETS) $(BIN_DIR) -extra: $(BIN_EXTRAS) $(BIN_DIR) +all: $(BIN_TARGETS) +extra: $(BIN_EXTRAS) # --- Directory Targets --- -# Explicit targets for the output directories to make them order-only prerequisites -$(BUILD_DIR): - @mkdir -p $@ - -$(OBJ_DIR): | $(BUILD_DIR) - @mkdir -p $@ - -$(BIN_DIR): | $(BUILD_DIR) - @mkdir -p $@ +$(BUILD_DIR): ; @mkdir -p $@ +$(BIN_DIR): | $(BUILD_DIR) ; @mkdir -p $@ +$(OBJ_DIR): | $(BUILD_DIR) ; @mkdir -p $@ -# --- Binary Linking Rules (In BIN_DIR) --- -$(BIN_DIR)/f3write: $(OBJ_DIR)/f3write.o $(LIBFLOW_OBJS) | $(BIN_DIR) +# --- Binary Linking Rules --- +$(BIN_DIR)/f3write: $(OBJ_DIR)/commands/f3write.o $(LIBFLOW_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3WRITE_LIBS) -$(BIN_DIR)/f3read: $(OBJ_DIR)/f3read.o $(LIBFLOW_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3read: $(OBJ_DIR)/commands/f3read.o $(LIBFLOW_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3READ_LIBS) -$(BIN_DIR)/f3probe: $(OBJ_DIR)/f3probe.o $(OBJ_DIR)/libprobe.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3probe: $(OBJ_DIR)/commands/f3probe.o $(OBJ_DIR)/core/libprobe.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3PROBE_LIBS) -$(BIN_DIR)/f3brew: $(OBJ_DIR)/f3brew.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3brew: $(OBJ_DIR)/commands/f3brew.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3BREW_LIBS) -$(BIN_DIR)/f3fix: $(OBJ_DIR)/f3fix.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3fix: $(OBJ_DIR)/commands/f3fix.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3FIX_LIBS) -# --- Dependency Inclusion --- -# Include the generated dependency files. -# With -MF $(@:.o=.d), the .d files are created alongside the .o files. -# So we include them from the OBJ_DIR. --include $(ALL_OBJS:.o=.d) - # --- Installation Targets --- -# Install binaries from the BIN_DIR to the system PREFIX/bin install: all @echo "Installing binaries from $(BIN_DIR) to $(DESTDIR)$(PREFIX)/bin" $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin @@ -163,14 +143,9 @@ cppcheck: docker: docker build -f Dockerfile -t f3:latest . -# --- Clean Target --- -# The clean target removes the entire build directory +# --- Cleanup and Maintenance Targets --- clean: @rm -rf $(BUILD_DIR) @rm -f cscope.out -# --- Phony Targets --- -# Declare targets that do not produce files of the same name as phony .PHONY: all extra docker install install-extra cscope cppcheck clean -# Add directory targets to PHONY to ensure they are always considered -.PHONY: $(BUILD_DIR) $(OBJ_DIR) $(BIN_DIR) From dd0b85e16ede6c0452e9131c860b37df00af1c27 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Wed, 7 May 2025 11:47:52 -0300 Subject: [PATCH 09/20] Avoid hardcoded feature macros Mixing feature macros when linking could be an issue. If we want to keep _XOPEN_SOURCE=600 we should update the Makefile with rules per target. --- src/commands/f3probe.c | 2 -- src/commands/f3read.c | 3 --- src/commands/f3write.c | 3 --- src/core/libdevs.c | 4 ---- src/core/libflow.c | 3 --- src/core/utils.c | 10 ---------- 6 files changed, 25 deletions(-) diff --git a/src/commands/f3probe.c b/src/commands/f3probe.c index ef653ab..1f564d8 100644 --- a/src/commands/f3probe.c +++ b/src/commands/f3probe.c @@ -1,5 +1,3 @@ -#define _POSIX_C_SOURCE 200809L - #include #include #include diff --git a/src/commands/f3read.c b/src/commands/f3read.c index 1364218..bc13ce0 100644 --- a/src/commands/f3read.c +++ b/src/commands/f3read.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include diff --git a/src/commands/f3write.c b/src/commands/f3write.c index c3cabed..ed9e37d 100644 --- a/src/commands/f3write.c +++ b/src/commands/f3write.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include diff --git a/src/core/libdevs.c b/src/core/libdevs.c index addbd4c..722ab29 100644 --- a/src/core/libdevs.c +++ b/src/core/libdevs.c @@ -1,7 +1,3 @@ -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200809L -#define _FILE_OFFSET_BITS 64 - #include #include #include diff --git a/src/core/libflow.c b/src/core/libflow.c index 52efb53..d6e85dd 100644 --- a/src/core/libflow.c +++ b/src/core/libflow.c @@ -1,6 +1,3 @@ -#define _POSIX_C_SOURCE 200112L -#define _XOPEN_SOURCE 600 - #include #include #include diff --git a/src/core/utils.c b/src/core/utils.c index 1abb403..9945dda 100644 --- a/src/core/utils.c +++ b/src/core/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 From 8aa1335d9a6d8fbcee5db439f08f3b5e5f0278d9 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Wed, 7 May 2025 21:08:33 -0300 Subject: [PATCH 10/20] Add private headers to share platform specific functions Rework the compilation to build the correct objects for each target. --- Makefile | 41 ++++++++++------ src/core/utils.c | 4 ++ src/platform/darwin/block_device.c | 15 +----- src/platform/darwin/usb_reset.c | 5 +- src/platform/linux/block_device.c | 26 +++-------- src/platform/linux/private/private.h | 52 +++++++++++++++++++++ src/platform/linux/usb_reset.c | 52 +++------------------ src/platform/private/block_device_private.h | 19 ++++++++ 8 files changed, 120 insertions(+), 94 deletions(-) create mode 100644 src/platform/linux/private/private.h create mode 100644 src/platform/private/block_device_private.h diff --git a/Makefile b/Makefile index bbc9396..b656053 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ # --- Basic Configuration --- CC ?= gcc -CFLAGS += -std=c99 -Wall -Wextra -pedantic -ggdb -CFLAGS += -Iinclude +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 @@ -42,10 +44,10 @@ F3FIX_LIBS = ifeq ($(PLATFORM), linux) PLATFORM_DIR = linux PLATFORM_CFLAGS += -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 - PLATFORM_LDFLAGS += + PLATFORM_LDFLAGS += -Wl,--gc-sections # strip dead code F3PROBE_LIBS += -ludev F3BREW_LIBS += -ludev - F3FIX_LIBS += -lparted + F3FIX_LIBS += -ludev -lparted endif ifeq ($(PLATFORM), darwin) PLATFORM_DIR = darwin @@ -54,8 +56,10 @@ ifeq ($(PLATFORM), darwin) 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 @@ -76,12 +80,18 @@ vpath %.c $(SRC_DIRS) ALL_SRCS := $(wildcard $(addsuffix /*.c, $(SRC_DIRS))) ALL_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(ALL_SRCS)) # Device and platform-specific object lists -DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/devices/*.c)) -PLATFORM_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/*.c)) +DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/devices/*.c)) +PLATFORM_DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/block_device.c src/platform/$(PLATFORM_DIR)/usb_reset.c)) +PLATFORM_PARTITION_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,src/platform/$(PLATFORM_DIR)/partition.c) # Reusable object lists -LIBDEVS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libdevs.c src/core/libutils.c) -LIBFLOW_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libflow.c src/core/utils.c) -LIBDEVICE_PLATFORM_OBJS := $(DEVICE_OBJS) $(PLATFORM_OBJS) $(LIBDEVS_OBJS) +LIBDEVS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libdevs.c) +LIBFLOW_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libflow.c) +LIBPROBE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libprobe.c) +LIBUTILS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libutils.c) +CORE_UTILS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/utils.c) +# Object bundles +DEVICE_PLATFORM_LIB_OBJS := $(DEVICE_OBJS) $(PLATFORM_DEVICE_OBJS) $(LIBDEVS_OBJS) $(LIBUTILS_OBJS) +PARTITION_LIB_OBJS := $(PLATFORM_PARTITION_OBJS) $(LIBUTILS_OBJS) # --- Dependency Inclusion --- DEPFLAGS = -MMD -MT $@ -MP -MF $(@:.o=.d) @@ -101,20 +111,23 @@ $(BUILD_DIR): ; @mkdir -p $@ $(BIN_DIR): | $(BUILD_DIR) ; @mkdir -p $@ $(OBJ_DIR): | $(BUILD_DIR) ; @mkdir -p $@ +# --- Private Platform Headers --- +$(PLATFORM_DEVICE_OBJS): CFLAGS += -Isrc/platform/private -Isrc/platform/$(PLATFORM_DIR)/ + # --- Binary Linking Rules --- -$(BIN_DIR)/f3write: $(OBJ_DIR)/commands/f3write.o $(LIBFLOW_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3write: $(OBJ_DIR)/commands/f3write.o $(LIBFLOW_OBJS) $(CORE_UTILS_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3WRITE_LIBS) -$(BIN_DIR)/f3read: $(OBJ_DIR)/commands/f3read.o $(LIBFLOW_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3read: $(OBJ_DIR)/commands/f3read.o $(LIBFLOW_OBJS) $(CORE_UTILS_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3READ_LIBS) -$(BIN_DIR)/f3probe: $(OBJ_DIR)/commands/f3probe.o $(OBJ_DIR)/core/libprobe.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3probe: $(OBJ_DIR)/commands/f3probe.o $(LIBPROBE_OBJS) $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3PROBE_LIBS) -$(BIN_DIR)/f3brew: $(OBJ_DIR)/commands/f3brew.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3brew: $(OBJ_DIR)/commands/f3brew.o $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3BREW_LIBS) -$(BIN_DIR)/f3fix: $(OBJ_DIR)/commands/f3fix.o $(LIBDEVICE_PLATFORM_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3fix: $(OBJ_DIR)/commands/f3fix.o $(PARTITION_LIB_OBJS) $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3FIX_LIBS) # --- Installation Targets --- diff --git a/src/core/utils.c b/src/core/utils.c index 9945dda..1ced3d7 100644 --- a/src/core/utils.c +++ b/src/core/utils.c @@ -1,3 +1,7 @@ +#if __APPLE__ && __MACH__ +#include /* For fcntl(). */ +#endif /* Apple Macintosh */ + #include #include #include diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c index 8285cca..59873a5 100644 --- a/src/platform/darwin/block_device.c +++ b/src/platform/darwin/block_device.c @@ -15,25 +15,14 @@ #include "devices/usb_reset.h" #include "libutils.h" +#include "block_device_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; -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; diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c index 6240472..b238239 100644 --- a/src/platform/darwin/usb_reset.c +++ b/src/platform/darwin/usb_reset.c @@ -9,13 +9,13 @@ #include #include -#include "devices/block_device.h" -#include "libdevs.h" #include "devices/usb_reset.h" +#include "devices/block_device.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; } @@ -23,6 +23,7 @@ int bdev_manual_usb_reset(struct device *dev) /* 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/linux/block_device.c b/src/platform/linux/block_device.c index 51b0b17..4b6a16a 100644 --- a/src/platform/linux/block_device.c +++ b/src/platform/linux/block_device.c @@ -3,35 +3,28 @@ #include #include #include -#include #include +#include +#include #include #include +#include +#include #include #include "devices/block_device.h" #include "devices/usb_reset.h" #include "libutils.h" +#include "block_device_private.h" +#include "private/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; -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; @@ -106,11 +99,6 @@ static int bdev_write_blocks(struct device *dev, const char *buf, 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 int bdev_none_reset(struct device *dev) { UNUSED(dev); diff --git a/src/platform/linux/private/private.h b/src/platform/linux/private/private.h new file mode 100644 index 0000000..eac2311 --- /dev/null +++ b/src/platform/linux/private/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 index 7430159..f8eef83 100644 --- a/src/platform/linux/usb_reset.c +++ b/src/platform/linux/usb_reset.c @@ -1,5 +1,8 @@ +#include #include +#include #include +#include #include #include #include @@ -11,54 +14,11 @@ #include #include -#include "devices/block_device.h" -#include "libdevs.h" #include "devices/usb_reset.h" +#include "devices/block_device.h" -static inline struct block_device *dev_bdev(struct device *dev) -{ - return (struct block_device *)dev; -} - -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); -} +#include "block_device_private.h" +#include "private/private.h" static struct udev_monitor *create_monitor(struct udev *udev, const char *subsystem, const char *devtype) diff --git a/src/platform/private/block_device_private.h b/src/platform/private/block_device_private.h new file mode 100644 index 0000000..e3eda24 --- /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 "libdevs.h" + +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 */ From 6f51d970a38f40bed1b3b88412fc0f78d87620ba Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Thu, 8 May 2025 20:47:04 -0300 Subject: [PATCH 11/20] Follow headers code style --- include/devices/block_device.h | 7 +++---- include/devices/file_device.h | 8 ++++---- include/devices/partition.h | 9 +++++---- include/devices/perf_device.h | 8 ++++---- include/devices/safe_device.h | 8 ++++---- include/devices/usb_reset.h | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/include/devices/block_device.h b/include/devices/block_device.h index ab1d8ca..f79a695 100644 --- a/include/devices/block_device.h +++ b/include/devices/block_device.h @@ -1,8 +1,7 @@ -#ifndef INCLUDE_DEVICES_BLOCK_DEVICE_H -#define INCLUDE_DEVICES_BLOCK_DEVICE_H +#ifndef HEADER_DEVICES_BLOCK_DEVICE_H +#define HEADER_DEVICES_BLOCK_DEVICE_H #include "libdevs.h" -#include /* Create a raw block device wrapper. * filename: path to the block device (e.g., /dev/sdx). @@ -11,4 +10,4 @@ */ struct device *create_block_device(const char *filename, enum reset_type rt); -#endif /* INCLUDE_DEVICES_BLOCK_DEVICE_H */ +#endif /* HEADER_DEVICES_BLOCK_DEVICE_H */ diff --git a/include/devices/file_device.h b/include/devices/file_device.h index e31adb5..4b25b7e 100644 --- a/include/devices/file_device.h +++ b/include/devices/file_device.h @@ -1,8 +1,8 @@ -#ifndef INCLUDE_DEVICES_FILE_DEVICE_H -#define INCLUDE_DEVICES_FILE_DEVICE_H +#ifndef HEADER_DEVICES_FILE_DEVICE_H +#define HEADER_DEVICES_FILE_DEVICE_H #include "libdevs.h" -#include +#include /* For type uint64_t. */ /* Create a file-backed device that masquerades as a block device. * filename: path to the backing file. @@ -20,4 +20,4 @@ int wrap, int block_order, int cache_order, int strict_cache, int keep_file); -#endif /* INCLUDE_DEVICES_FILE_DEVICE_H */ +#endif /* HEADER_DEVICES_FILE_DEVICE_H */ diff --git a/include/devices/partition.h b/include/devices/partition.h index e475103..76c501b 100644 --- a/include/devices/partition.h +++ b/include/devices/partition.h @@ -1,8 +1,9 @@ -#ifndef INCLUDE_DEVICES_PARTITION_H -#define INCLUDE_DEVICES_PARTITION_H +#ifndef HEADER_DEVICES_PARTITION_H +#define HEADER_DEVICES_PARTITION_H #include "libdevs.h" -#include +#include /* For type uint64_t. */ +#include /* For type bool. */ /* Partition creation options. */ struct partition_options { @@ -28,4 +29,4 @@ 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 /* INCLUDE_DEVICES_PARTITION_H */ +#endif /* HEADER_DEVICES_PARTITION_H */ diff --git a/include/devices/perf_device.h b/include/devices/perf_device.h index 475a56e..b1cf0b7 100644 --- a/include/devices/perf_device.h +++ b/include/devices/perf_device.h @@ -1,8 +1,8 @@ -#ifndef INCLUDE_DEVICES_PERF_DEVICE_H -#define INCLUDE_DEVICES_PERF_DEVICE_H +#ifndef HEADER_DEVICES_PERF_DEVICE_H +#define HEADER_DEVICES_PERF_DEVICE_H #include "libdevs.h" -#include +#include /* For type uint64_t. */ /* Create a performance‐measuring wrapper around a device. */ struct device *create_perf_device(struct device *dev); @@ -16,4 +16,4 @@ void perf_device_sample(struct device *dev, /* Detach underlying device and free the wrapper, returning the original device. */ struct device *pdev_detach_and_free(struct device *dev); -#endif /* INCLUDE_DEVICES_PERF_DEVICE_H */ +#endif /* HEADER_DEVICES_PERF_DEVICE_H */ diff --git a/include/devices/safe_device.h b/include/devices/safe_device.h index 50e9d0d..d121fb3 100644 --- a/include/devices/safe_device.h +++ b/include/devices/safe_device.h @@ -1,8 +1,8 @@ -#ifndef INCLUDE_DEVICES_SAFE_DEVICE_H -#define INCLUDE_DEVICES_SAFE_DEVICE_H +#ifndef HEADER_DEVICES_SAFE_DEVICE_H +#define HEADER_DEVICES_SAFE_DEVICE_H #include "libdevs.h" -#include +#include /* For type uint64_t. */ /* Create a safe device wrapper: snapshots writes for rollback. */ struct device *create_safe_device(struct device *dev, @@ -12,4 +12,4 @@ struct device *create_safe_device(struct device *dev, void sdev_recover(struct device *dev, uint64_t very_last_pos); void sdev_flush(struct device *dev); -#endif /* INCLUDE_DEVICES_SAFE_DEVICE_H */ +#endif /* HEADER_DEVICES_SAFE_DEVICE_H */ diff --git a/include/devices/usb_reset.h b/include/devices/usb_reset.h index 905ac25..0c43eea 100644 --- a/include/devices/usb_reset.h +++ b/include/devices/usb_reset.h @@ -1,9 +1,9 @@ -#ifndef USB_RESET_H -#define USB_RESET_H +#ifndef HEADER_DEVICES_USB_RESET_H +#define HEADER_DEVICES_USB_RESET_H -#include "devices/block_device.h" +#include "libdevs.h" int bdev_manual_usb_reset(struct device *dev); int bdev_usb_reset(struct device *dev); -#endif /* USB_RESET_H */ +#endif /* HEADER_DEVICES_USB_RESET_H */ From 38894b6b6ec28a3486c581f32607460b466cf5a7 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Thu, 8 May 2025 20:48:44 -0300 Subject: [PATCH 12/20] Initial utils.c platform code refactor --- include/platform_compat.h | 24 +++++++++ src/core/utils.c | 73 --------------------------- src/platform/darwin/platform_compat.c | 31 ++++++++++++ src/platform/linux/platform_compat.c | 50 ++++++++++++++++++ 4 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 include/platform_compat.h create mode 100644 src/platform/darwin/platform_compat.c create mode 100644 src/platform/linux/platform_compat.c diff --git a/include/platform_compat.h b/include/platform_compat.h new file mode 100644 index 0000000..b8200c9 --- /dev/null +++ b/include/platform_compat.h @@ -0,0 +1,24 @@ +#ifndef HEADER_PLATFORM_COMPAT_H +#define HEADER_PLATFORM_COMPAT_H + +#include /* For off_t */ + +/* Define compatibility names to avoid clashes with standard library functions if they exist */ +void adjust_dev_path_compat(const char **dev_path); +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/src/core/utils.c b/src/core/utils.c index 1ced3d7..b24f7fa 100644 --- a/src/core/utils.c +++ b/src/core/utils.c @@ -1,7 +1,3 @@ -#if __APPLE__ && __MACH__ -#include /* For fcntl(). */ -#endif /* Apple Macintosh */ - #include #include #include @@ -197,72 +193,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/platform_compat.c b/src/platform/darwin/platform_compat.c new file mode 100644 index 0000000..74c61c6 --- /dev/null +++ b/src/platform/darwin/platform_compat.c @@ -0,0 +1,31 @@ +#include /* For fcntl(). */ +#include /* For chdir and chroot */ +#include + +#include "platform_compat.h" + +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/linux/platform_compat.c b/src/platform/linux/platform_compat.c new file mode 100644 index 0000000..73085b3 --- /dev/null +++ b/src/platform/linux/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 "platform_compat.h" + +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); +} From c987cb2cd3af0f9263e271cdb0e45969cb50cf34 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 17:07:29 -0300 Subject: [PATCH 13/20] Refactor source tree into f3 and f3-extra --- Makefile | 45 +++++++++---------- include/devices/usb_reset.h | 9 ---- include/{ => f3}/devices/file_device.h | 2 +- include/{ => f3}/devices/perf_device.h | 2 +- include/{ => f3}/devices/safe_device.h | 2 +- include/{ => f3}/libdevs.h | 10 ++--- include/{ => f3}/libflow.h | 0 include/{ => f3}/libprobe.h | 5 +-- include/{ => f3}/libutils.h | 0 .../{devices => f3/platform}/block_device.h | 8 ++-- include/{devices => f3/platform}/partition.h | 11 +++-- include/{ => f3/platform}/platform_compat.h | 3 +- include/{ => f3}/utils.h | 0 include/{ => f3}/version.h | 0 src/{commands => f3-extra}/f3brew.c | 6 +-- src/{commands => f3-extra}/f3fix.c | 6 +-- src/{commands => f3-extra}/f3probe.c | 6 +-- src/{devices => f3-extra/lib}/file_device.c | 5 +-- src/{core => f3-extra/lib}/libdevs.c | 2 +- src/{core => f3-extra/lib}/libprobe.c | 4 +- src/{core => f3-extra/lib}/libutils.c | 4 +- src/{devices => f3-extra/lib}/perf_device.c | 5 +-- src/{devices => f3-extra/lib}/safe_device.c | 5 +-- src/{commands => f3}/f3read.c | 6 +-- src/{commands => f3}/f3write.c | 6 +-- src/{core => f3/lib}/libflow.c | 4 +- src/{core => f3/lib}/utils.c | 4 +- src/platform/darwin/block_device.c | 8 ++-- .../darwin/{ => partition}/partition.c | 2 +- src/platform/darwin/platform_compat.c | 2 +- src/platform/darwin/usb_reset.c | 3 +- src/platform/linux/block_device.c | 9 ++-- .../linux/{ => partition}/partition.c | 4 +- src/platform/linux/platform_compat.c | 2 +- .../private/{private.h => linux_private.h} | 6 +-- src/platform/linux/usb_reset.c | 8 ++-- src/platform/private/block_device_private.h | 2 +- src/platform/private/usb_reset_private.h | 9 ++++ 38 files changed, 100 insertions(+), 115 deletions(-) delete mode 100644 include/devices/usb_reset.h rename include/{ => f3}/devices/file_device.h (94%) rename include/{ => f3}/devices/perf_device.h (93%) rename include/{ => f3}/devices/safe_device.h (90%) rename include/{ => f3}/libdevs.h (91%) rename include/{ => f3}/libflow.h (100%) rename include/{ => f3}/libprobe.h (77%) rename include/{ => f3}/libutils.h (100%) rename include/{devices => f3/platform}/block_device.h (60%) rename include/{devices => f3/platform}/partition.h (78%) rename include/{ => f3/platform}/platform_compat.h (90%) rename include/{ => f3}/utils.h (100%) rename include/{ => f3}/version.h (100%) rename src/{commands => f3-extra}/f3brew.c (99%) rename src/{commands => f3-extra}/f3fix.c (98%) rename src/{commands => f3-extra}/f3probe.c (99%) rename src/{devices => f3-extra/lib}/file_device.c (98%) rename src/{core => f3-extra/lib}/libdevs.c (99%) rename src/{core => f3-extra/lib}/libprobe.c (99%) rename src/{core => f3-extra/lib}/libutils.c (98%) rename src/{devices => f3-extra/lib}/perf_device.c (97%) rename src/{devices => f3-extra/lib}/safe_device.c (98%) rename src/{commands => f3}/f3read.c (99%) rename src/{commands => f3}/f3write.c (99%) rename src/{core => f3/lib}/libflow.c (99%) rename src/{core => f3/lib}/utils.c (98%) rename src/platform/darwin/{ => partition}/partition.c (99%) rename src/platform/linux/{ => partition}/partition.c (97%) rename src/platform/linux/private/{private.h => linux_private.h} (88%) create mode 100644 src/platform/private/usb_reset_private.h diff --git a/Makefile b/Makefile index b656053..430e716 100644 --- a/Makefile +++ b/Makefile @@ -73,29 +73,29 @@ 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/commands src/core src/devices src/platform/$(PLATFORM_DIR) +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 -ALL_SRCS := $(wildcard $(addsuffix /*.c, $(SRC_DIRS))) -ALL_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(ALL_SRCS)) -# Device and platform-specific object lists -DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/devices/*.c)) -PLATFORM_DEVICE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/platform/$(PLATFORM_DIR)/block_device.c src/platform/$(PLATFORM_DIR)/usb_reset.c)) -PLATFORM_PARTITION_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,src/platform/$(PLATFORM_DIR)/partition.c) +TARGET_SRCS := $(wildcard $(addsuffix /*.c, $(SRC_DIRS))) +TARGET_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(TARGET_SRCS)) + # Reusable object lists -LIBDEVS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libdevs.c) -LIBFLOW_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libflow.c) -LIBPROBE_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libprobe.c) -LIBUTILS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/libutils.c) -CORE_UTILS_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o, src/core/utils.c) -# Object bundles -DEVICE_PLATFORM_LIB_OBJS := $(DEVICE_OBJS) $(PLATFORM_DEVICE_OBJS) $(LIBDEVS_OBJS) $(LIBUTILS_OBJS) -PARTITION_LIB_OBJS := $(PLATFORM_PARTITION_OBJS) $(LIBUTILS_OBJS) +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_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) --include $(ALL_OBJS:.o=.d) +ALL_DEPS := $(ALL_OBJS:.o=.d) +-include $(ALL_DEPS) # --- Pattern Rule for Compilation --- $(OBJ_DIR)/%.o: src/%.c | $(OBJ_DIR) @@ -111,23 +111,20 @@ $(BUILD_DIR): ; @mkdir -p $@ $(BIN_DIR): | $(BUILD_DIR) ; @mkdir -p $@ $(OBJ_DIR): | $(BUILD_DIR) ; @mkdir -p $@ -# --- Private Platform Headers --- -$(PLATFORM_DEVICE_OBJS): CFLAGS += -Isrc/platform/private -Isrc/platform/$(PLATFORM_DIR)/ - # --- Binary Linking Rules --- -$(BIN_DIR)/f3write: $(OBJ_DIR)/commands/f3write.o $(LIBFLOW_OBJS) $(CORE_UTILS_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3write: $(OBJ_DIR)/f3/f3write.o $(F3_LIB_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3WRITE_LIBS) -$(BIN_DIR)/f3read: $(OBJ_DIR)/commands/f3read.o $(LIBFLOW_OBJS) $(CORE_UTILS_OBJS) | $(BIN_DIR) +$(BIN_DIR)/f3read: $(OBJ_DIR)/f3/f3read.o $(F3_LIB_OBJS) | $(BIN_DIR) $(CC) -o $@ $^ $(LDFLAGS) $(F3READ_LIBS) -$(BIN_DIR)/f3probe: $(OBJ_DIR)/commands/f3probe.o $(LIBPROBE_OBJS) $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) +$(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)/commands/f3brew.o $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) +$(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)/commands/f3fix.o $(PARTITION_LIB_OBJS) $(DEVICE_PLATFORM_LIB_OBJS) | $(BIN_DIR) +$(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 --- diff --git a/include/devices/usb_reset.h b/include/devices/usb_reset.h deleted file mode 100644 index 0c43eea..0000000 --- a/include/devices/usb_reset.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef HEADER_DEVICES_USB_RESET_H -#define HEADER_DEVICES_USB_RESET_H - -#include "libdevs.h" - -int bdev_manual_usb_reset(struct device *dev); -int bdev_usb_reset(struct device *dev); - -#endif /* HEADER_DEVICES_USB_RESET_H */ diff --git a/include/devices/file_device.h b/include/f3/devices/file_device.h similarity index 94% rename from include/devices/file_device.h rename to include/f3/devices/file_device.h index 4b25b7e..89ce77f 100644 --- a/include/devices/file_device.h +++ b/include/f3/devices/file_device.h @@ -1,7 +1,7 @@ #ifndef HEADER_DEVICES_FILE_DEVICE_H #define HEADER_DEVICES_FILE_DEVICE_H -#include "libdevs.h" +#include /* For struct device. */ #include /* For type uint64_t. */ /* Create a file-backed device that masquerades as a block device. diff --git a/include/devices/perf_device.h b/include/f3/devices/perf_device.h similarity index 93% rename from include/devices/perf_device.h rename to include/f3/devices/perf_device.h index b1cf0b7..085381b 100644 --- a/include/devices/perf_device.h +++ b/include/f3/devices/perf_device.h @@ -1,7 +1,7 @@ #ifndef HEADER_DEVICES_PERF_DEVICE_H #define HEADER_DEVICES_PERF_DEVICE_H -#include "libdevs.h" +#include /* For struct device. */ #include /* For type uint64_t. */ /* Create a performance‐measuring wrapper around a device. */ diff --git a/include/devices/safe_device.h b/include/f3/devices/safe_device.h similarity index 90% rename from include/devices/safe_device.h rename to include/f3/devices/safe_device.h index d121fb3..6bc8778 100644 --- a/include/devices/safe_device.h +++ b/include/f3/devices/safe_device.h @@ -1,7 +1,7 @@ #ifndef HEADER_DEVICES_SAFE_DEVICE_H #define HEADER_DEVICES_SAFE_DEVICE_H -#include "libdevs.h" +#include /* For struct device. */ #include /* For type uint64_t. */ /* Create a safe device wrapper: snapshots writes for rollback. */ diff --git a/include/libdevs.h b/include/f3/libdevs.h similarity index 91% rename from include/libdevs.h rename to include/f3/libdevs.h index 776d3a0..c1244bc 100644 --- a/include/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 @@ -94,9 +94,9 @@ enum reset_type { RT_MAX }; -#include "devices/file_device.h" -#include "devices/block_device.h" -#include "devices/perf_device.h" -#include "devices/safe_device.h" +#include +#include +#include +#include #endif /* HEADER_LIBDEVS_H */ diff --git a/include/libflow.h b/include/f3/libflow.h similarity index 100% rename from include/libflow.h rename to include/f3/libflow.h diff --git a/include/libprobe.h b/include/f3/libprobe.h similarity index 77% rename from include/libprobe.h rename to include/f3/libprobe.h index 014afda..920d28b 100644 --- a/include/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/include/libutils.h b/include/f3/libutils.h similarity index 100% rename from include/libutils.h rename to include/f3/libutils.h diff --git a/include/devices/block_device.h b/include/f3/platform/block_device.h similarity index 60% rename from include/devices/block_device.h rename to include/f3/platform/block_device.h index f79a695..114b28b 100644 --- a/include/devices/block_device.h +++ b/include/f3/platform/block_device.h @@ -1,7 +1,7 @@ -#ifndef HEADER_DEVICES_BLOCK_DEVICE_H -#define HEADER_DEVICES_BLOCK_DEVICE_H +#ifndef HEADER_PLATFORM_BLOCK_DEVICE_H +#define HEADER_PLATFORM_BLOCK_DEVICE_H -#include "libdevs.h" +#include /* For device, reset_type. */ /* Create a raw block device wrapper. * filename: path to the block device (e.g., /dev/sdx). @@ -10,4 +10,4 @@ */ struct device *create_block_device(const char *filename, enum reset_type rt); -#endif /* HEADER_DEVICES_BLOCK_DEVICE_H */ +#endif /* HEADER_PLATFORM_BLOCK_DEVICE_H */ diff --git a/include/devices/partition.h b/include/f3/platform/partition.h similarity index 78% rename from include/devices/partition.h rename to include/f3/platform/partition.h index 76c501b..a700a6f 100644 --- a/include/devices/partition.h +++ b/include/f3/platform/partition.h @@ -1,9 +1,8 @@ -#ifndef HEADER_DEVICES_PARTITION_H -#define HEADER_DEVICES_PARTITION_H +#ifndef HEADER_PLATFORM_PARTITION_H +#define HEADER_PLATFORM_PARTITION_H -#include "libdevs.h" -#include /* For type uint64_t. */ -#include /* For type bool. */ +#include /* For type uint64_t. */ +#include /* For type bool. */ /* Partition creation options. */ struct partition_options { @@ -29,4 +28,4 @@ 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_DEVICES_PARTITION_H */ +#endif /* HEADER_PLATFORM_PARTITION_H */ diff --git a/include/platform_compat.h b/include/f3/platform/platform_compat.h similarity index 90% rename from include/platform_compat.h rename to include/f3/platform/platform_compat.h index b8200c9..2ad8623 100644 --- a/include/platform_compat.h +++ b/include/f3/platform/platform_compat.h @@ -4,7 +4,6 @@ #include /* For off_t */ /* Define compatibility names to avoid clashes with standard library functions if they exist */ -void adjust_dev_path_compat(const char **dev_path); 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); @@ -21,4 +20,4 @@ int posix_fadvise_compat(int fd, off_t offset, off_t len, int advice); #define POSIX_FADV_DONTNEED 4 /* Don't need these pages. */ #endif -#endif /* HEADER_PLATFORM_COMPAT_H */ +#endif /* HEADER_PLATFORM_COMPAT_H */ diff --git a/include/utils.h b/include/f3/utils.h similarity index 100% rename from include/utils.h rename to include/f3/utils.h diff --git a/include/version.h b/include/f3/version.h similarity index 100% rename from include/version.h rename to include/f3/version.h diff --git a/src/commands/f3brew.c b/src/f3-extra/f3brew.c similarity index 99% rename from src/commands/f3brew.c rename to src/f3-extra/f3brew.c index 34047b2..285932b 100644 --- a/src/commands/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/commands/f3fix.c b/src/f3-extra/f3fix.c similarity index 98% rename from src/commands/f3fix.c rename to src/f3-extra/f3fix.c index 86fc43d..b68744f 100644 --- a/src/commands/f3fix.c +++ b/src/f3-extra/f3fix.c @@ -5,9 +5,9 @@ #include #include -#include "version.h" -#include "libutils.h" -#include "devices/partition.h" +#include +#include +#include /* Sentinel for unset last sector. */ #define SEC_UNSET ((uint64_t)UINT64_MAX) diff --git a/src/commands/f3probe.c b/src/f3-extra/f3probe.c similarity index 99% rename from src/commands/f3probe.c rename to src/f3-extra/f3probe.c index 1f564d8..f003c1b 100644 --- a/src/commands/f3probe.c +++ b/src/f3-extra/f3probe.c @@ -7,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/devices/file_device.c b/src/f3-extra/lib/file_device.c similarity index 98% rename from src/devices/file_device.c rename to src/f3-extra/lib/file_device.c index 3a53683..27f99c3 100644 --- a/src/devices/file_device.c +++ b/src/f3-extra/lib/file_device.c @@ -7,9 +7,8 @@ #include #include -#include "libdevs.h" -#include "devices/file_device.h" -#include "libutils.h" +#include +#include struct file_device { /* This must be the first field. See dev_fdev() for details. */ diff --git a/src/core/libdevs.c b/src/f3-extra/lib/libdevs.c similarity index 99% rename from src/core/libdevs.c rename to src/f3-extra/lib/libdevs.c index 722ab29..c3460de 100644 --- a/src/core/libdevs.c +++ b/src/f3-extra/lib/libdevs.c @@ -6,7 +6,7 @@ #include #include -#include "libdevs.h" +#include /* Map fake_type to string. */ static const char * const ftype_to_name[FKTY_MAX] = { diff --git a/src/core/libprobe.c b/src/f3-extra/lib/libprobe.c similarity index 99% rename from src/core/libprobe.c rename to src/f3-extra/lib/libprobe.c index a57fe66..a6c8f33 100644 --- a/src/core/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/src/core/libutils.c b/src/f3-extra/lib/libutils.c similarity index 98% rename from src/core/libutils.c rename to src/f3-extra/lib/libutils.c index 6ea3cfd..305714c 100644 --- a/src/core/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) diff --git a/src/devices/perf_device.c b/src/f3-extra/lib/perf_device.c similarity index 97% rename from src/devices/perf_device.c rename to src/f3-extra/lib/perf_device.c index eafee1e..4eef047 100644 --- a/src/devices/perf_device.c +++ b/src/f3-extra/lib/perf_device.c @@ -2,9 +2,8 @@ #include #include -#include "libdevs.h" -#include "devices/perf_device.h" -#include "libutils.h" +#include +#include struct perf_device { /* This must be the first field. See dev_pdev() for details. */ diff --git a/src/devices/safe_device.c b/src/f3-extra/lib/safe_device.c similarity index 98% rename from src/devices/safe_device.c rename to src/f3-extra/lib/safe_device.c index db1616d..840332c 100644 --- a/src/devices/safe_device.c +++ b/src/f3-extra/lib/safe_device.c @@ -6,9 +6,8 @@ #include #include -#include "libdevs.h" -#include "devices/safe_device.h" -#include "libutils.h" +#include +#include #define SDEV_BITMAP_WORD long #define SDEV_BITMAP_BITS_PER_WORD (8 * sizeof(SDEV_BITMAP_WORD)) diff --git a/src/commands/f3read.c b/src/f3/f3read.c similarity index 99% rename from src/commands/f3read.c rename to src/f3/f3read.c index bc13ce0..3f71b22 100644 --- a/src/commands/f3read.c +++ b/src/f3/f3read.c @@ -15,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; diff --git a/src/commands/f3write.c b/src/f3/f3write.c similarity index 99% rename from src/commands/f3write.c rename to src/f3/f3write.c index ed9e37d..753b3f8 100644 --- a/src/commands/f3write.c +++ b/src/f3/f3write.c @@ -14,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; diff --git a/src/core/libflow.c b/src/f3/lib/libflow.c similarity index 99% rename from src/core/libflow.c rename to src/f3/lib/libflow.c index d6e85dd..873ca4b 100644 --- a/src/core/libflow.c +++ b/src/f3/lib/libflow.c @@ -5,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/src/core/utils.c b/src/f3/lib/utils.c similarity index 98% rename from src/core/utils.c rename to src/f3/lib/utils.c index b24f7fa..f5fc54d 100644 --- a/src/core/utils.c +++ b/src/f3/lib/utils.c @@ -10,8 +10,8 @@ #include #include -#include "version.h" -#include "utils.h" +#include +#include void adjust_dev_path(const char **dev_path) { diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c index 59873a5..9c2fdf2 100644 --- a/src/platform/darwin/block_device.c +++ b/src/platform/darwin/block_device.c @@ -11,11 +11,11 @@ #include #include -#include "devices/block_device.h" -#include "devices/usb_reset.h" -#include "libutils.h" +#include +#include +#include -#include "block_device_private.h" +#include "../private/block_device_private.h" /* XXX This is borrowing from glibc. * A better solution would be to return proper errors, diff --git a/src/platform/darwin/partition.c b/src/platform/darwin/partition/partition.c similarity index 99% rename from src/platform/darwin/partition.c rename to src/platform/darwin/partition/partition.c index 3162ca1..5c49327 100644 --- a/src/platform/darwin/partition.c +++ b/src/platform/darwin/partition/partition.c @@ -7,7 +7,7 @@ #include #include // for _NSGetEnviron() -#include "devices/partition.h" +#include /* Supported types. */ static const char *const disk_types[] = { "msdos", "mbr", "gpt", NULL }; diff --git a/src/platform/darwin/platform_compat.c b/src/platform/darwin/platform_compat.c index 74c61c6..21858fb 100644 --- a/src/platform/darwin/platform_compat.c +++ b/src/platform/darwin/platform_compat.c @@ -2,7 +2,7 @@ #include /* For chdir and chroot */ #include -#include "platform_compat.h" +#include void msleep_compat(double wait_ms) { diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c index b238239..978d916 100644 --- a/src/platform/darwin/usb_reset.c +++ b/src/platform/darwin/usb_reset.c @@ -9,8 +9,7 @@ #include #include -#include "devices/usb_reset.h" -#include "devices/block_device.h" +#include /* macOS stub for USB reset. */ int bdev_manual_usb_reset(struct device *dev) diff --git a/src/platform/linux/block_device.c b/src/platform/linux/block_device.c index 4b6a16a..cbf25e2 100644 --- a/src/platform/linux/block_device.c +++ b/src/platform/linux/block_device.c @@ -12,12 +12,11 @@ #include #include -#include "devices/block_device.h" -#include "devices/usb_reset.h" -#include "libutils.h" +#include +#include -#include "block_device_private.h" -#include "private/private.h" +#include "../private/block_device_private.h" +#include "private/linux_private.h" /* XXX This is borrowing from glibc. * A better solution would be to return proper errors, diff --git a/src/platform/linux/partition.c b/src/platform/linux/partition/partition.c similarity index 97% rename from src/platform/linux/partition.c rename to src/platform/linux/partition/partition.c index d57db35..d35474c 100644 --- a/src/platform/linux/partition.c +++ b/src/platform/linux/partition/partition.c @@ -2,9 +2,7 @@ #include #include -#include "devices/partition.h" -#include "devices/block_device.h" -#include "libutils.h" +#include // Convert physical sector to logical sector static PedSector map_sector_to_logical_sector(PedSector sector, diff --git a/src/platform/linux/platform_compat.c b/src/platform/linux/platform_compat.c index 73085b3..3890b66 100644 --- a/src/platform/linux/platform_compat.c +++ b/src/platform/linux/platform_compat.c @@ -4,7 +4,7 @@ #include /* For assert() */ #include /* For EINTR */ -#include "platform_compat.h" +#include void msleep_compat(double wait_ms) { diff --git a/src/platform/linux/private/private.h b/src/platform/linux/private/linux_private.h similarity index 88% rename from src/platform/linux/private/private.h rename to src/platform/linux/private/linux_private.h index eac2311..079e8b6 100644 --- a/src/platform/linux/private/private.h +++ b/src/platform/linux/private/linux_private.h @@ -1,8 +1,8 @@ #ifndef LINUX_PLATFORM_PRIVATE_H #define LINUX_PLATFORM_PRIVATE_H -#include // For struct udev, struct udev_device -#include // For struct stat, S_ISBLK, fstat +#include /* For struct udev, struct udev_device */ +#include /* For struct stat, S_ISBLK, fstat */ static inline int bdev_open(const char *filename) { @@ -49,4 +49,4 @@ static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) return udev_device_new_from_devnum(udev, 'b', fd_stat.st_rdev); } -#endif /* LINUX_PLATFORM_PRIVATE_H */ +#endif /* LINUX_PLATFORM_PRIVATE_H */ diff --git a/src/platform/linux/usb_reset.c b/src/platform/linux/usb_reset.c index f8eef83..e7f10af 100644 --- a/src/platform/linux/usb_reset.c +++ b/src/platform/linux/usb_reset.c @@ -14,11 +14,9 @@ #include #include -#include "devices/usb_reset.h" -#include "devices/block_device.h" - -#include "block_device_private.h" -#include "private/private.h" +#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) diff --git a/src/platform/private/block_device_private.h b/src/platform/private/block_device_private.h index e3eda24..1212015 100644 --- a/src/platform/private/block_device_private.h +++ b/src/platform/private/block_device_private.h @@ -1,7 +1,7 @@ #ifndef PLATFORM_PRIVATE_BLOCK_DEVICE_H #define PLATFORM_PRIVATE_BLOCK_DEVICE_H -#include "libdevs.h" +#include /* For struct device. */ struct block_device { /* This must be the first field. See dev_bdev() for details. */ 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 */ From 2d69856b1afb31116fae5e6755c038b5150ba112 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 20:11:10 -0300 Subject: [PATCH 14/20] Refactor utils.c platform code and fix build --- Makefile | 7 ++-- include/f3/platform/platform_compat.h | 2 + include/f3/utils.h | 34 +++------------ src/f3-extra/f3fix.c | 1 + src/f3/f3read.c | 6 +-- src/f3/f3write.c | 4 +- src/f3/lib/utils.c | 18 +++++++- src/platform/darwin/block_device.c | 2 +- src/platform/darwin/usb_reset.c | 2 +- src/platform/freebsd/platform_compat.c | 50 ++++++++++++++++++++++ src/platform/linux/block_device.c | 1 + src/platform/linux/platform_compat.c | 10 +++-- src/platform/openbsd/platform_compat.c | 57 ++++++++++++++++++++++++++ 13 files changed, 150 insertions(+), 44 deletions(-) create mode 100644 src/platform/freebsd/platform_compat.c create mode 100644 src/platform/openbsd/platform_compat.c diff --git a/Makefile b/Makefile index 430e716..416ed23 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ F3WRITE_LIBS = $(COMMON_LIBS) F3READ_LIBS = $(COMMON_LIBS) F3PROBE_LIBS = $(COMMON_LIBS) F3BREW_LIBS = $(COMMON_LIBS) -F3FIX_LIBS = +F3FIX_LIBS = $(COMMON_LIBS) ifeq ($(PLATFORM), linux) PLATFORM_DIR = linux @@ -88,6 +88,7 @@ F3_EXTRA_LIB_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(wildcard src/f3-extra/l # 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) @@ -112,10 +113,10 @@ $(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) | $(BIN_DIR) +$(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) | $(BIN_DIR) +$(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) diff --git a/include/f3/platform/platform_compat.h b/include/f3/platform/platform_compat.h index 2ad8623..e5cd550 100644 --- a/include/f3/platform/platform_compat.h +++ b/include/f3/platform/platform_compat.h @@ -3,6 +3,8 @@ #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); diff --git a/include/f3/utils.h b/include/f3/utils.h index 03031e2..5e08ec0 100644 --- a/include/f3/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/src/f3-extra/f3fix.c b/src/f3-extra/f3fix.c index b68744f..dc7de9e 100644 --- a/src/f3-extra/f3fix.c +++ b/src/f3-extra/f3fix.c @@ -8,6 +8,7 @@ #include #include #include +#include /* Sentinel for unset last sector. */ #define SEC_UNSET ((uint64_t)UINT64_MAX) diff --git a/src/f3/f3read.c b/src/f3/f3read.c index 3f71b22..c05b5b7 100644 --- a/src/f3/f3read.c +++ b/src/f3/f3read.c @@ -256,7 +256,7 @@ 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) { + if (f3_fdatasync(fd) < 0) { int saved_errno = errno; /* The issue https://github.com/AltraMayor/f3/issues/211 * motivated the warning below. @@ -265,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/src/f3/f3write.c b/src/f3/f3write.c index 753b3f8..abf1491 100644 --- a/src/f3/f3write.c +++ b/src/f3/f3write.c @@ -265,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/src/f3/lib/utils.c b/src/f3/lib/utils.c index f5fc54d..1f54c9d 100644 --- a/src/f3/lib/utils.c +++ b/src/f3/lib/utils.c @@ -10,8 +10,24 @@ #include #include -#include #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) { diff --git a/src/platform/darwin/block_device.c b/src/platform/darwin/block_device.c index 9c2fdf2..944ecd2 100644 --- a/src/platform/darwin/block_device.c +++ b/src/platform/darwin/block_device.c @@ -12,10 +12,10 @@ #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, diff --git a/src/platform/darwin/usb_reset.c b/src/platform/darwin/usb_reset.c index 978d916..8768b50 100644 --- a/src/platform/darwin/usb_reset.c +++ b/src/platform/darwin/usb_reset.c @@ -9,7 +9,7 @@ #include #include -#include +#include "../private/usb_reset_private.h" /* macOS stub for USB reset. */ int bdev_manual_usb_reset(struct device *dev) 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 index cbf25e2..bc82089 100644 --- a/src/platform/linux/block_device.c +++ b/src/platform/linux/block_device.c @@ -16,6 +16,7 @@ #include #include "../private/block_device_private.h" +#include "../private/usb_reset_private.h" #include "private/linux_private.h" /* XXX This is borrowing from glibc. diff --git a/src/platform/linux/platform_compat.c b/src/platform/linux/platform_compat.c index 3890b66..2b499b9 100644 --- a/src/platform/linux/platform_compat.c +++ b/src/platform/linux/platform_compat.c @@ -1,8 +1,10 @@ -#include /* fdatasync, posix_fadvise */ -#include /* For fmod */ +#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 /* For assert() */ +#include /* For EINTR */ #include 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; +} From d5378ee13abb2655f49f90aa43f39f21caef707e Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 20:59:19 -0300 Subject: [PATCH 15/20] Fix cppcheck warnings --- include/f3/devices/perf_device.h | 3 --- src/f3-extra/lib/libutils.c | 2 +- src/f3-extra/lib/perf_device.c | 3 ++- src/f3-extra/lib/safe_device.c | 2 +- src/f3/f3read.c | 2 +- src/f3/lib/utils.c | 7 +++++-- src/platform/darwin/partition/partition.c | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/include/f3/devices/perf_device.h b/include/f3/devices/perf_device.h index 085381b..fe9a33f 100644 --- a/include/f3/devices/perf_device.h +++ b/include/f3/devices/perf_device.h @@ -13,7 +13,4 @@ void perf_device_sample(struct device *dev, uint64_t *pwrite_count, uint64_t *pwrite_time_us, uint64_t *preset_count, uint64_t *preset_time_us); -/* Detach underlying device and free the wrapper, returning the original device. */ -struct device *pdev_detach_and_free(struct device *dev); - #endif /* HEADER_DEVICES_PERF_DEVICE_H */ diff --git a/src/f3-extra/lib/libutils.c b/src/f3-extra/lib/libutils.c index 305714c..94b306d 100644 --- a/src/f3-extra/lib/libutils.c +++ b/src/f3-extra/lib/libutils.c @@ -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 index 4eef047..686dd37 100644 --- a/src/f3-extra/lib/perf_device.c +++ b/src/f3-extra/lib/perf_device.c @@ -81,7 +81,8 @@ 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) +/* 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; diff --git a/src/f3-extra/lib/safe_device.c b/src/f3-extra/lib/safe_device.c index 840332c..0e2a432 100644 --- a/src/f3-extra/lib/safe_device.c +++ b/src/f3-extra/lib/safe_device.c @@ -160,7 +160,7 @@ static void sdev_carefully_recover(struct safe_device *sdev, char *buffer, return; for (pos = first_pos; pos <= last_pos; pos++) { - int rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, + rc = sdev->shadow_dev->write_blocks(sdev->shadow_dev, buffer, pos, pos); if (rc) { /* Do not abort, try to recover all bocks. */ diff --git a/src/f3/f3read.c b/src/f3/f3read.c index c05b5b7..a2cdf7c 100644 --- a/src/f3/f3read.c +++ b/src/f3/f3read.c @@ -257,7 +257,7 @@ static void validate_file(const char *path, int number, struct flow *fw, * we should have a better reading-speed measurement. */ if (f3_fdatasync(fd) < 0) { - int saved_errno = errno; + saved_errno = errno; /* The issue https://github.com/AltraMayor/f3/issues/211 * motivated the warning below. */ diff --git a/src/f3/lib/utils.c b/src/f3/lib/utils.c index 1f54c9d..fa8b3e3 100644 --- a/src/f3/lib/utils.c +++ b/src/f3/lib/utils.c @@ -49,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++; } @@ -193,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; diff --git a/src/platform/darwin/partition/partition.c b/src/platform/darwin/partition/partition.c index 5c49327..4c05a59 100644 --- a/src/platform/darwin/partition/partition.c +++ b/src/platform/darwin/partition/partition.c @@ -79,7 +79,7 @@ static int run_diskutil(const char *disk, const char *scheme, } /* Function to unmount a disk using diskutil. */ -int unmount_disk(const char *disk) { +static int unmount_disk(const char *disk) { char cmd[128]; snprintf(cmd, sizeof(cmd), "/usr/sbin/diskutil unmountDisk %s", disk); From ed2245c64e536e5adf0fc1d1211c2c7b6bd96bcf Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 21:53:37 -0300 Subject: [PATCH 16/20] Update github tests and add a macos test script --- .github/workflows/test.yml | 6 ++--- f3write.h2w => scripts/f3write.h2w | 0 log-f3wr => scripts/log-f3wr | 0 tests/test-macos-f3fix.sh | 43 ++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) rename f3write.h2w => scripts/f3write.h2w (100%) rename log-f3wr => scripts/log-f3wr (100%) create mode 100644 tests/test-macos-f3fix.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 068122f..25abfa1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: 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 @@ -71,7 +71,7 @@ jobs: 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 @@ -112,7 +112,7 @@ jobs: with: packages: gcc-core libargp-devel make - - run: "$Env:LDFLAGS = '-Wl,--stack,4000000'; make" + - run: "$Env:LDFLAGS = '-Wl,--stack,4000000'; make BIN_DIR=." - if: matrix.volume.windows == 'relative-path' run: New-Item -ItemType Directory -Path relative-path 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/tests/test-macos-f3fix.sh b/tests/test-macos-f3fix.sh new file mode 100644 index 0000000..1a03015 --- /dev/null +++ b/tests/test-macos-f3fix.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e # Exit on error + +echo "=== Starting F3 Test Suite on macOS ===" + +# Clean up from previous runs +rm -f test.img + +# Create test image +echo "Creating test image..." +dd if=/dev/zero of=test.img bs=1M count=100 + +# Attach disk image +echo "Attaching disk image..." +disk_info=$(hdiutil attach -nomount -readwrite test.img) +device=$(echo $disk_info | awk '{print $1}') + +trap 'echo "Cleaning up..."; diskutil unmountDisk $device 2>/dev/null || true; diskutil eject $device 2>/dev/null || true; rm -f test.img' EXIT + +echo "Using device: $device" + +# Build with debug flags +echo "Building F3 with debug flags..." +CFLAGS="-DDEBUG" make clean all extra + +# Test different configurations +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" +) + +for test_case in "${test_cases[@]}"; do + echo -e "\n=== Testing configuration: $test_case ===" + ./build/bin/f3fix $test_case --first-sec=2048 --last-sec=102400 --boot $device + echo "Current disk layout:" + diskutil list $device + fdisk $device + echo "----------------------------------------" +done + +echo -e "\n=== All tests completed successfully! ===" From fc9cc4cb8ebbada01cad804927e9cb92eed1369f Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 22:14:51 -0300 Subject: [PATCH 17/20] Nicer macos f3fix test output --- tests/test-macos-f3fix.sh | 170 +++++++++++++++++++++++++++++++------- 1 file changed, 138 insertions(+), 32 deletions(-) diff --git a/tests/test-macos-f3fix.sh b/tests/test-macos-f3fix.sh index 1a03015..93f3722 100644 --- a/tests/test-macos-f3fix.sh +++ b/tests/test-macos-f3fix.sh @@ -1,43 +1,149 @@ #!/bin/bash -set -e # Exit on error +set -euo pipefail -echo "=== Starting F3 Test Suite on macOS ===" +# 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" +) -# Clean up from previous runs -rm -f test.img +# Nice colors for output +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color -# Create test image -echo "Creating test image..." -dd if=/dev/zero of=test.img bs=1M count=100 +log() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} -# Attach disk image -echo "Attaching disk image..." -disk_info=$(hdiutil attach -nomount -readwrite test.img) -device=$(echo $disk_info | awk '{print $1}') +log_success() { + log "${GREEN}SUCCESS: $1${NC}" +} -trap 'echo "Cleaning up..."; diskutil unmountDisk $device 2>/dev/null || true; diskutil eject $device 2>/dev/null || true; rm -f test.img' EXIT +log_error() { + log "${RED}ERROR: $1${NC}" >&2 + exit 1 +} -echo "Using device: $device" +log_info() { + log "${YELLOW}INFO: $1${NC}" +} -# Build with debug flags -echo "Building F3 with debug flags..." -CFLAGS="-DDEBUG" make clean all extra +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 +} -# Test different configurations -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" -) +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 +} -for test_case in "${test_cases[@]}"; do - echo -e "\n=== Testing configuration: $test_case ===" - ./build/bin/f3fix $test_case --first-sec=2048 --last-sec=102400 --boot $device - echo "Current disk layout:" - diskutil list $device - fdisk $device - echo "----------------------------------------" -done +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 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 ./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" +} -echo -e "\n=== All tests completed successfully! ===" +main From 0835ca9f09902961773fa631e6161520c44924a4 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 22:25:22 -0300 Subject: [PATCH 18/20] Fix cygwin and run macos f3fix on github --- .github/workflows/test.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25abfa1..6587df1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,6 +88,14 @@ jobs: - run: ./f3read --help - run: ${{ matrix.asroot.sudo }}./f3read -s 2 -e 4 -r 50000 ${{ matrix.volume }} + - name: Run F3Fix tests + if: matrix.volume == '.' && matrix.asroot.sudo == '' + run: | + brew install gawk + chmod +x tests/test-macos-f3fix.sh + cd tests + ./test-macos-f3fix.sh + Cygwin: strategy: fail-fast: false @@ -112,7 +120,7 @@ jobs: with: packages: gcc-core libargp-devel make - - run: "$Env:LDFLAGS = '-Wl,--stack,4000000'; make BIN_DIR=." + - run: "$Env:LDFLAGS = '-Wl,--stack,4000000 -largp'; make BIN_DIR=." - if: matrix.volume.windows == 'relative-path' run: New-Item -ItemType Directory -Path relative-path From 8f37c2dd525f5266fe3483d6911faf7c6797327b Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 22:39:25 -0300 Subject: [PATCH 19/20] Use latest runners on github --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6587df1..a8601c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: sudo: 'sudo ' name: Linux ${{ matrix.volume }}${{ matrix.asroot.name }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -66,7 +66,7 @@ jobs: sudo: 'sudo ' name: MacOS ${{ matrix.volume }}${{ matrix.asroot.name }} - runs-on: macos-12 + runs-on: macos-latest steps: - uses: actions/checkout@v3 @@ -111,7 +111,7 @@ jobs: windows: 'C:\cygwin' name: Cygwin ${{ matrix.volume.cygwin }} - runs-on: windows-2022 + runs-on: windows-latest steps: - uses: actions/checkout@v3 From 5cef13f13eb471e2ec7a0bb5205a1951f49e7718 Mon Sep 17 00:00:00 2001 From: Paul Eipper Date: Mon, 26 May 2025 22:57:05 -0300 Subject: [PATCH 20/20] Fix macos f3fix workflow --- .github/workflows/test.yml | 5 ++--- tests/test-macos-f3fix.sh | 13 ++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) mode change 100644 => 100755 tests/test-macos-f3fix.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8601c2..0ff7b14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -89,12 +89,11 @@ jobs: - run: ${{ matrix.asroot.sudo }}./f3read -s 2 -e 4 -r 50000 ${{ matrix.volume }} - name: Run F3Fix tests - if: matrix.volume == '.' && matrix.asroot.sudo == '' + if: matrix.volume == '.' run: | brew install gawk chmod +x tests/test-macos-f3fix.sh - cd tests - ./test-macos-f3fix.sh + ./tests/test-macos-f3fix.sh Cygwin: strategy: diff --git a/tests/test-macos-f3fix.sh b/tests/test-macos-f3fix.sh old mode 100644 new mode 100755 index 93f3722..b4381cc --- a/tests/test-macos-f3fix.sh +++ b/tests/test-macos-f3fix.sh @@ -1,6 +1,13 @@ #!/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" @@ -86,7 +93,7 @@ main() { log_system_info local test_start_time=$(date +%s) - + # Clean up from previous runs rm -f test.img @@ -106,7 +113,7 @@ main() { log_info "Using device: $device" log_info "Building F3 with debug flags..." - if ! CFLAGS="-DDEBUG" make clean all extra; then + if ! CFLAGS="-DDEBUG" make -C "$PROJECT_ROOT" clean all extra; then log_error "Build failed" fi @@ -119,7 +126,7 @@ main() { log_info "=== Test $test_count/${#TEST_CASES[@]}: $test_case ===" - if ./build/bin/f3fix $test_case --first-sec=2048 --last-sec=102400 --boot "$device"; then + 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