Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: brew install argp-standalone
- run: make
- run: make all extra

- if: matrix.volume == 'relative-path'
run: mkdir relative-path
Expand All @@ -114,6 +114,25 @@ jobs:
- run: build/f3read --help
- run: ${{ matrix.asroot.sudo }}build/f3read -s 2 -e 4 -r 50000 ${{ matrix.volume }}

- run: build/f3probe -V
- run: build/f3probe --help
- run: build/f3brew -V
- run: build/f3brew --help

- name: Create RAM disk for f3probe and f3brew tests
id: ramdisk
run: echo "dev=$(hdiutil attach -nomount ram://20480 | tr -d '[:space:]' | sed 's|^/dev/||')" >> $GITHUB_OUTPUT

- run: sudo build/f3probe /dev/r${{ steps.ramdisk.outputs.dev }}
- run: sudo build/f3brew --reset-type=2 -h 0 -e 4 /dev/r${{ steps.ramdisk.outputs.dev }}

- name: Cleanup RAM disk
if: always()
run: |
if [ -n "${{ steps.ramdisk.outputs.dev }}" ]; then
hdiutil detach ${{ steps.ramdisk.outputs.dev }}
fi

OpenBSD:
strategy:
fail-fast: false
Expand Down
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ UNLINK = unlink
ifndef OS
OS = $(shell uname -s)
endif
ifneq ($(OS), Linux)
ifeq ($(OS), Linux)
UDEV_LIB = -ludev
else
ARGP = /usr/local
ifeq ($(OS), Darwin)
# f3fix needs libparted, which is not available on macOS.
EXTRA_TARGETS = $(BUILD_DIR)/f3probe $(BUILD_DIR)/f3brew
ifneq ($(shell command -v brew),)
ARGP = $(shell brew --prefix)
# argp-standalone is keg-only, so it isn't linked into the prefix.
ARGP = $(shell brew --prefix argp-standalone)
endif
endif
CFLAGS += -I$(ARGP)/include
Expand Down Expand Up @@ -64,10 +69,10 @@ $(BUILD_DIR)/f3read: $(BUILD_DIR)/libutils.o $(BUILD_DIR)/libfile.o $(BUILD_DIR)
$(CC) -o $@ $^ $(LDFLAGS) -lm

$(BUILD_DIR)/f3probe: $(BUILD_DIR)/libutils.o $(BUILD_DIR)/libflow.o $(BUILD_DIR)/libdevs.o $(BUILD_DIR)/libprobe.o $(BUILD_DIR)/f3probe.o
$(CC) -o $@ $^ $(LDFLAGS) -lm -ludev
$(CC) -o $@ $^ $(LDFLAGS) -lm $(UDEV_LIB)

$(BUILD_DIR)/f3brew: $(BUILD_DIR)/libutils.o $(BUILD_DIR)/libflow.o $(BUILD_DIR)/libdevs.o $(BUILD_DIR)/f3brew.o
$(CC) -o $@ $^ $(LDFLAGS) -lm -ludev
$(CC) -o $@ $^ $(LDFLAGS) -lm $(UDEV_LIB)

$(BUILD_DIR)/f3fix: $(BUILD_DIR)/libutils.o $(BUILD_DIR)/f3fix.o
$(CC) -o $@ $^ $(LDFLAGS) -lparted
Expand Down
25 changes: 20 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ user::

.. warning:: This will destroy any previously stored data on your disk!

On Mac, use the raw whole-disk device (e.g. /dev/rdiskN, not /dev/diskNs1),
and unmount the disk right before probing since macOS remounts it
automatically::

$ diskutil unmountDisk /dev/diskN && sudo build/f3probe --destructive --time-ops /dev/rdiskN

Correcting capacity to actual size with f3fix
---------------------------------------------

Expand Down Expand Up @@ -128,10 +134,10 @@ If you want to install f3write and f3read, run the following command::
Compile stable software on Apple Mac
------------------------------------

f3write and f3read can be installed on Mac, but currently f3probe, f3fix, and
f3brew `require Linux <#the-extra-applications-for-linux>`__. To use them on
Mac, use the `Docker Installation <#docker>`__. For f3write and f3read, read
on.
f3write, f3read, f3probe, and f3brew can be built on Mac, but f3fix
`requires Linux <#the-extra-applications-for-linux>`__ because it depends on
libparted. Note that the `Docker Installation <#docker>`__ does not help on
Mac since Docker Desktop cannot expose the host's block devices to containers.

Using HomeBrew
~~~~~~~~~~~~~~
Expand Down Expand Up @@ -200,6 +206,14 @@ for details.

make ARGP=/opt/local

5) Optionally, build f3probe and f3brew::

make extra

On Mac, f3probe and f3brew do not support resetting USB devices, so
f3brew must be run with :code:`--reset-type=2`. f3fix is not built
because it depends on libparted.

Docker
------

Expand Down Expand Up @@ -329,7 +343,8 @@ Compile the extra applications

.. note::
- The extra applications are only compiled and tested on Linux
platform.
platform, except that f3probe and f3brew also build on
`Mac <#compile-stable-software-on-apple-mac>`__.
- Please do not e-mail me saying that you want the extra
applications to run on your platform; I already know that.
- If you want the extra applications to run on your platform, help
Expand Down
103 changes: 103 additions & 0 deletions src/libdevs.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#ifdef __APPLE__
#define _DARWIN_C_SOURCE /* For F_NOCACHE and F_FULLFSYNC. */
#endif

#include <stdio.h>
#include <stdlib.h>
Expand All @@ -16,9 +19,13 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef __APPLE__
#include <sys/disk.h>
#else
#include <linux/fs.h>
#include <linux/usbdevice_fs.h>
#include <libudev.h>
#endif

#include "libutils.h"
#include "libdevs.h"
Expand Down Expand Up @@ -463,16 +470,53 @@ static int bdev_write_blocks(struct device *dev, const char *buf,
rc = write_all(bdev->fd, buf, length);
if (rc)
return rc;
#ifdef __APPLE__
/* F_FULLFSYNC asks the drive to flush its own cache too;
* plain fsync() does not on macOS. Neither is guaranteed to be
* supported on a device node (e.g. /dev/rdiskN), so treat
* "not supported" as success; F_NOCACHE already bypasses the
* page cache and the writes were synchronous.
*/
if (fcntl(bdev->fd, F_FULLFSYNC) == 0)
return 0;
if (errno == ENOTSUP || errno == ENOTTY || errno == EINVAL ||
errno == ENODEV) {
if (fsync(bdev->fd) == 0 || errno == ENOTSUP ||
errno == ENOTTY || errno == EINVAL)
return 0;
}
return - errno;
#else
rc = fsync(bdev->fd);
if (rc)
return rc;
return posix_fadvise(bdev->fd, 0, 0, POSIX_FADV_DONTNEED);
#endif
}

#ifdef __APPLE__
static inline int bdev_open(const char *filename)
{
int fd = open(filename, O_RDWR);
if (fd < 0)
return fd;
/* macOS has no O_DIRECT; F_NOCACHE is the closest equivalent. */
if (fcntl(fd, F_NOCACHE, 1) < 0) {
int saved = errno;
close(fd);
errno = saved;
return -1;
}
return fd;
}
#else
static inline int bdev_open(const char *filename)
{
return open(filename, O_RDWR | O_DIRECT);
}
#endif

#ifndef __APPLE__

static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev)
{
Expand Down Expand Up @@ -815,6 +859,8 @@ static int bdev_usb_reset(struct device *dev)
return 0;
}

#endif /* !__APPLE__ */

static int bdev_none_reset(struct device *dev)
{
UNUSED(dev);
Expand All @@ -834,6 +880,7 @@ static const char *bdev_get_filename(struct device *dev)
return dev_bdev(dev)->filename;
}

#ifndef __APPLE__
static struct udev_device *map_partition_to_disk(struct udev_device *dev)
{
struct udev_device *disk_dev;
Expand All @@ -849,6 +896,25 @@ static struct udev_device *map_partition_to_disk(struct udev_device *dev)
return udev_device_ref(disk_dev);
}

#else
/* Return true if @filename is of the form /dev/[r]disk<N>s<M>[...]. */
static bool darwin_is_partition_name(const char *filename)
{
const char *p = strrchr(filename, '/');
p = p ? p + 1 : filename;
if (*p == 'r')
p++;
if (strncmp(p, "disk", 4))
return false;
p += 4;
if (*p < '0' || *p > '9')
return false;
while (*p >= '0' && *p <= '9')
p++;
return *p == 's' && p[1] >= '0' && p[1] <= '9';
}
#endif /* !__APPLE__ */

/* XXX This is borrowing from glibc.
* A better solution would be to return proper errors,
* so callers write their own messages.
Expand All @@ -858,9 +924,11 @@ extern const char *__progname;
struct device *create_block_device(const char *filename, enum reset_type rt)
{
struct block_device *bdev;
#ifndef __APPLE__
struct udev *udev;
struct udev_device *fd_dev;
const char *s;
#endif
int block_size, block_order;

bdev = malloc(sizeof(*bdev));
Expand All @@ -871,6 +939,19 @@ struct device *create_block_device(const char *filename, enum reset_type rt)
if (!bdev->filename)
goto bdev;

#ifdef __APPLE__
/* macOS has no udev, so rely on the device naming convention to
* make sure that @filename is a whole disk, not a partition:
* /dev/diskN and /dev/rdiskN are disks, /dev/diskNsM are partitions.
*/
if (darwin_is_partition_name(filename)) {
fprintf(stderr, "Device `%s' looks like a partition.\n"
"You must run %s on the whole disk device, "
"for example /dev/rdiskN\n", filename, __progname);
goto filename;
}
#endif

bdev->fd = bdev_open(filename);
if (bdev->fd < 0) {
if (errno == EACCES && getuid()) {
Expand All @@ -885,6 +966,25 @@ struct device *create_block_device(const char *filename, enum reset_type rt)
goto filename;
}

#ifdef __APPLE__
/* Resets need Linux's USBDEVFS_RESET, so only RT_NONE is supported. */
if (rt != RT_NONE) {
fprintf(stderr, "Device resets are not supported on macOS.\n"
"You must disable reset, run %s as follows:\n"
"%s --reset-type=%i %s\n",
__progname, __progname, RT_NONE, filename);
goto fd;
}
bdev->dev.reset = bdev_none_reset;
{
uint64_t block_count;
uint32_t bsize;
assert(!ioctl(bdev->fd, DKIOCGETBLOCKCOUNT, &block_count));
assert(!ioctl(bdev->fd, DKIOCGETBLOCKSIZE, &bsize));
block_size = bsize;
bdev->dev.size_byte = block_count * bsize;
}
#else
/* Make sure that @bdev->fd is a disk, not a partition. */
udev = udev_new();
if (!udev) {
Expand Down Expand Up @@ -949,6 +1049,7 @@ struct device *create_block_device(const char *filename, enum reset_type rt)
assert(!ioctl(bdev->fd, BLKGETSIZE64, &bdev->dev.size_byte));

assert(!ioctl(bdev->fd, BLKSSZGET, &block_size));
#endif
block_order = ilog2(block_size);
assert(block_size == (1 << block_order));
bdev->dev.block_order = block_order;
Expand All @@ -960,10 +1061,12 @@ struct device *create_block_device(const char *filename, enum reset_type rt)

return &bdev->dev;

#ifndef __APPLE__
fd_dev:
udev_device_unref(fd_dev);
udev:
assert(!udev_unref(udev));
#endif
fd:
assert(!close(bdev->fd));
filename:
Expand Down