stewartsmith / libeatmydata

libeatmydata - because fsync() should be a no-op
https://www.flamingspork.com/projects/libeatmydata/
GNU General Public License v3.0
409 stars 21 forks source link

eatmydata doesn't seem to work on macos #34

Open ojwb opened 1 year ago

ojwb commented 1 year ago

Thanks to a tip-off from @carlocab I did a simple test to see if eatmydata actually eats my data on macos:

eatmydata relies on DYLD_FORCE_FLAT_NAMESPACE and DYLD_INSERT_LIBRARIES, which, in my experience, no longer works on newer versions of macOS.

The trick I used relies on eatmydata (quite reasonably IMHO) not attempting to emulate the error case where the fd isn't actually syncable. So this test program behaves differently when under eatmydata vs not:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
    int fd;
    for (fd = 0; fd <= 2; ++fd) {
    int r = fsync(fd);
    if (r != 0) {
        printf("fsync of fd %d failed: %s\n", fd, strerror(errno));
    }
#ifdef __APPLE__
    r = fcntl(fd, F_FULLFSYNC, 0);
    if (r != 0) {
        printf("F_FULLFSYNC of fd %d failed: %s\n", fd, strerror(errno));
    }
#endif
    }
    return 0;
}

Testing on Debian unstable, using the debian eatmydata package:

$ gcc -Wall -W -O2 testeatmydata.c -o testeatmydata
$ ./testeatmydata < /dev/null 2> /dev/null
fsync of fd 0 failed: Invalid argument
fsync of fd 1 failed: Invalid argument
fsync of fd 2 failed: Invalid argument
$ eatmydata ./testeatmydata < /dev/null 2> /dev/null
$

This demonstrates that this error case is indeed handled differently when eatmydata is working. So far, so good.

I don't own a mac, so I tested macos using github actions - this is using the homebrew libeatmydata package which includes the patch from #33 that you kindly recently merged:

https://github.com/ojwb/homebrew-eatmydata-test/actions/runs/5766706912/job/15635169095

The output from ./testeatmydata < /dev/null 2> /dev/null is exactly the same with and without eatmydata:

F_FULLFSYNC of fd 0 failed: Operation not supported by device
fsync of fd 1 failed: Operation not supported
F_FULLFSYNC of fd 1 failed: Bad file descriptor
F_FULLFSYNC of fd 2 failed: Operation not supported by device

With a working eatmydata I would expect the output to differ, probably with no errors reported under eatmydata though I find F_FULLFSYNC of fd 1 failed: Bad file descriptor surprising as stdout must be open for this output to get logged - I'd guess it's a socket (fds 0 and 2 should be block devices).

I'm afraid I don't know how to fix this.

ojwb commented 1 year ago

It occurred to me to look at other preload libraries, and the first one I looked at was libfaketime which seems to provide the solution:

https://github.com/wolfcw/libfaketime/commit/ee4f57d8a581fe2fb826d820954eecc0c33e8724