From 68c320244636d0eb6b707b4ef61ddf9ccb64bc56 Mon Sep 17 00:00:00 2001 From: Pete Moore Date: Wed, 11 Mar 2026 00:03:22 +0100 Subject: [PATCH 1/2] Fix max_open_files integer overflow on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On macOS, RLIM_INFINITY is 0x7FFFFFFFFFFFFFFF. After tup_fuse_fs_init() doubles rlim_cur and divides by 2, casting the rlim_t result to int overflows, producing max_open_files = -1. Since open_count >= -1 is always true, every FUSE open immediately closes its fd and sets fh=0. With macFUSE (kernel FUSE) this is harmless — the kernel always delivers FUSE_RELEASE regardless of server-side fd state. With FUSE-T (NFS-backed FUSE), the NFS client may skip sending CLOSE for files the server already closed, causing finfo_wait_open_count() to time out with "FUSE did not appear to release all file descriptors after the sub-process closed." The fix caps the rlim_t value at INT_MAX before casting to int. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/tup/server/fuse_fs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tup/server/fuse_fs.c b/src/tup/server/fuse_fs.c index b4a0494c..46e45946 100644 --- a/src/tup/server/fuse_fs.c +++ b/src/tup/server/fuse_fs.c @@ -40,6 +40,7 @@ #include #include #include +#include #include static struct thread_root troot = THREAD_ROOT_INITIALIZER; @@ -63,7 +64,10 @@ void tup_fuse_fs_init(void) break; } if(getrlimit(RLIMIT_NOFILE, &rlim) == 0) { - max_open_files = rlim.rlim_cur / 2; + rlim_t half = rlim.rlim_cur / 2; + if(half > INT_MAX) + half = INT_MAX; + max_open_files = (int)half; } } } From 3ff09bae584a6ca27eebaa71364fb1276d5a6ded Mon Sep 17 00:00:00 2001 From: Pete Moore Date: Wed, 11 Mar 2026 00:03:33 +0100 Subject: [PATCH 2/2] Add macOS CI using FUSE-T (kext-free FUSE) FUSE-T is a kext-free FUSE implementation for macOS that uses an NFS v4 local server instead of a kernel extension. This makes it possible to run tup's FUSE-based test suite on GitHub-hosted macOS runners (which block kernel extensions). CI changes: - Install FUSE-T runtime and create macFUSE-compatible header symlinks - Build patched libfuse (unmount teardown fix, PR pending upstream: https://github.com/macos-fuse-t/libfuse/pull/11) - Bootstrap tup and run full test suite 9 tests are skipped (deterministic FUSE-T NFS backend limitations or macOS platform issues). ~20 additional tests are flaky under CI load due to the NFS client occasionally dropping FUSE callbacks; these are retried up to 3 times to distinguish flakes from regressions. Also update macOS install docs with FUSE-T instructions as an alternative to macFUSE. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/all.yml | 110 ++++++++++++++++++++++++++++++++++++++ docs/html/index.html | 46 ++++++++++++++-- 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 30a1347d..8c27d99a 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -9,3 +9,113 @@ jobs: - run: pip3 install sh - run: ./bootstrap.sh - run: cd test && ./test.sh --keep-going + + macos: + runs-on: macos-latest + env: + PKG_CONFIG_PATH: /usr/local/lib/pkgconfig + steps: + - uses: actions/checkout@v4 + - name: Install FUSE-T and dependencies + run: | + # FUSE-T: kext-free FUSE for macOS using NFS v4 local server. + # API-compatible with macFUSE, no kernel extension needed. + # https://github.com/macos-fuse-t/fuse-t + brew tap macos-fuse-t/homebrew-cask + brew install fuse-t + brew install pkg-config pcre2 ccache cmake + pip3 install --break-system-packages sh + - name: Create macFUSE-compatible header symlinks + run: | + # FUSE-T puts headers in a framework; tup expects /usr/local/include/fuse/ + sudo mkdir -p /usr/local/include/fuse + sudo ln -sf /Library/Frameworks/fuse_t.framework/Headers/* /usr/local/include/fuse/ + - name: Build patched libfuse for FUSE-T + run: | + # FUSE-T's libfuse has an unmount teardown bug that causes non-zero + # exit after every successful FUSE operation. We build from a patched + # fork until the fix is merged upstream. + # Fix: https://github.com/macos-fuse-t/libfuse/pull/11 + git clone https://github.com/petemoore/libfuse.git /tmp/libfuse-src \ + --branch fix/recv-eof-and-attrcache --depth 1 + cd /tmp/libfuse-src && mkdir build && cd build + cmake .. && make -j$(sysctl -n hw.ncpu) + + # Install patched library over FUSE-T's version + install_name_tool -id /usr/local/lib/libfuse-t.dylib lib/libfuse-t.dylib + sudo cp lib/libfuse-t.dylib /usr/local/lib/libfuse-t.dylib + sudo cp lib/libfuse-t.a /usr/local/lib/libfuse-t.a + + # Create fuse.pc (FUSE-T doesn't ship one) + sudo mkdir -p /usr/local/lib/pkgconfig + sudo tee /usr/local/lib/pkgconfig/fuse.pc > /dev/null << 'EOF' + prefix=/usr/local + exec_prefix=${prefix} + libdir=${exec_prefix}/lib + includedir=${prefix}/include + Name: fuse + Description: FUSE-T libfuse + Version: 2.9.9 + Libs: -L${libdir} -lfuse-t -pthread + Cflags: -I${includedir}/fuse -D_FILE_OFFSET_BITS=64 + EOF + - name: Build (bootstrap) + run: ./bootstrap.sh + - name: Run tests + run: | + cd test + + # Skip tests that consistently fail on macOS. + # + # macOS platform issues (fail with both macFUSE and FUSE-T): + # t3083 — gcc --coverage gcno naming differs on macOS + # t6082 — utimens() on non-job directory (macOS restriction) + # + # FUSE-T deterministic failures (fail every run, not flaky): + # t2094, t2128, t2135 — run-script deps not tracked via NFS + # t5074 — process management timeout under NFS latency + # t6017 — input dependency missed by NFS client + # t8079 — run-script in variant not tracked via NFS + # t9006 — input dependency missed by NFS client + skip="t2094-run4.sh t2128-run-preload.sh t2135-preload6.sh t3083-extra-outputs-bang3.sh t5074-tup-dies.sh t6017-broken-update8.sh t6082-broken-update61.sh t8079-run-variant.sh t9006-gitignore-without-glob.sh" + + tests="" + for t in t[0-9]*.sh; do + if echo " $skip " | grep -q " $t "; then + echo "SKIP (macOS platform issue): $t" + else + tests="$tests $t" + fi + done + + # FUSE-T uses an NFS v4 backend instead of a kernel module. + # Under CI load, the macOS NFS client occasionally drops or + # delays FUSE callbacks, causing tup to miss file accesses. + # This affects ~1-4 random tests per run out of ~980 (all + # pass locally and pass with macFUSE). Retry failed tests + # up to 3 times to distinguish NFS flakes from genuine + # regressions. See: https://github.com/macos-fuse-t/fuse-t/issues/91 + for attempt in 1 2 3 4; do + if [ $attempt -gt 1 ]; then + echo "" + echo "=== Attempt $attempt/4: retrying failed tests ===" + echo "" + tests="$failed_tests" + # Clean up leftover test directories from previous attempt + for t in $tests; do + rm -rf "tuptesttmp-${t%.sh}" 2>/dev/null || true + done + fi + ./test.sh --keep-going $tests 2>&1 | tee /tmp/test-attempt-${attempt}.log + rc=${PIPESTATUS[0]} + if [ $rc -eq 0 ]; then + break + fi + # Extract failed test script names (format: " *** t1234-name.sh failed") + failed_tests=$(grep -oE 't[0-9]+-[a-zA-Z0-9_-]+\.sh failed' /tmp/test-attempt-${attempt}.log | sed 's/ failed$//' | sort -u | tr '\n' ' ') + if [ -z "$failed_tests" ]; then + break + fi + echo "Failed tests: $failed_tests" + done + exit $rc diff --git a/docs/html/index.html b/docs/html/index.html index ae8ea492..82f53cb0 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -29,13 +29,49 @@

Linux Ubuntu

sudo apt-get install tup -

MacOSX

-

If you use the Homebrew package manager you can install tup as follows:

+

macOS

+

Tup requires a FUSE implementation on macOS. FUSE-T is recommended — it runs entirely in user space (no kernel extension needed) and is API-compatible with macFUSE.

+ +

Install FUSE-T and build dependencies:

+
+brew tap macos-fuse-t/homebrew-cask
+brew install fuse-t
+brew install pkg-config pcre2 cmake
+
+ +

Build the patched libfuse (fixes an unmount teardown issue in FUSE-T):

-brew cask install osxfuse
-brew install tup
+git clone https://github.com/petemoore/libfuse.git --branch fix/recv-eof-on-unmount --depth 1
+cd libfuse && mkdir build && cd build && cmake .. && make
+install_name_tool -id /usr/local/lib/libfuse-t.dylib lib/libfuse-t.dylib
+sudo cp lib/libfuse-t.dylib /usr/local/lib/libfuse-t.dylib
+sudo cp lib/libfuse-t.a /usr/local/lib/libfuse-t.a
+sudo mkdir -p /usr/local/include/fuse
+sudo ln -sf /Library/Frameworks/fuse_t.framework/Headers/* /usr/local/include/fuse/
+sudo mkdir -p /usr/local/lib/pkgconfig
+sudo tee /usr/local/lib/pkgconfig/fuse.pc > /dev/null << 'EOF'
+prefix=/usr/local
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+Name: fuse
+Description: FUSE-T libfuse
+Version: 2.9.9
+Libs: -L${libdir} -lfuse-t -pthread
+Cflags: -I${includedir}/fuse -D_FILE_OFFSET_BITS=64
+EOF
+cd ../..
 
-

If you use MacPorts install tup as:

+ +

Build tup:

+
+git clone https://github.com/gittup/tup.git
+cd tup
+./bootstrap.sh
+
+ +

Alternatively, if you already have macFUSE installed (requires enabling the kernel extension in System Settings > Privacy & Security), tup will use it automatically without the patched libfuse steps above.

+

If you use MacPorts:

sudo port install tup

Why tup?