aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Pärtel <martin.partel@gmail.com>2025-06-08 14:45:34 +0300
committerGitHub <noreply@github.com>2025-06-08 14:45:34 +0300
commitba548d828d8680ebe329ea1bb2d069ec881066a5 (patch)
tree27af010adc569eda1d6d9c02326c10bbe40d715b
parenta276dbe66faaf7af02d967a648f7598589bf5c5b (diff)
parent2dce69c4dea0bcb9e98c2965e6ac6b25402fcae9 (diff)
downloadbindfs-ba548d828d8680ebe329ea1bb2d069ec881066a5.tar.gz
Merge pull request #161 from mpartel/fix-gh-actions
Fix GitHub actions
-rw-r--r--.github/workflows/tests.yml94
-rw-r--r--.gitignore1
-rw-r--r--README.md8
-rw-r--r--configure.ac22
-rw-r--r--src/bindfs.c25
-rw-r--r--tests/common.rb32
-rwxr-xr-xtests/test_bindfs.rb25
7 files changed, 112 insertions, 95 deletions
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3c16512..9673c5d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -15,7 +15,7 @@ permissions:
contents: read
jobs:
- linux:
+ tests:
name: Build (${{ matrix.cc }} and ${{ matrix.fuse_package }} on ${{ matrix.runs-on }})
runs-on: ${{ matrix.runs-on }}
strategy:
@@ -26,42 +26,36 @@ jobs:
- cc: gcc
cxx: g++
clang_major_version: null
- clang_repo_suffix: null
runs-on: ubuntu-22.04
fuse_package: libfuse-dev
# FUSE 3
- cc: gcc
cxx: g++
clang_major_version: null
- clang_repo_suffix: null
runs-on: ubuntu-22.04
fuse_package: libfuse3-dev
- cc: clang-17
cxx: clang++-17
clang_major_version: 17
- clang_repo_suffix: -17
runs-on: ubuntu-22.04
fuse_package: libfuse3-dev
- cc: clang-18
cxx: clang++-18
clang_major_version: 18
- clang_repo_suffix: -18
runs-on: ubuntu-22.04
fuse_package: libfuse3-dev
- # fuse-t
- - cc: gcc-13
- cxx: g++-13
+ # fuse-t, gcc
+ - cc: gcc
+ cxx: g++
+ clang_major_version: null
+ runs-on: macos-15
+ fuse_package: fuse-t
+ # fuse-t, clang
+ - cc: clang
+ cxx: clang++
clang_major_version: null
- clang_repo_suffix: null
- runs-on: macos-13
+ runs-on: macos-15
fuse_package: fuse-t
- # macFUSE
- - cc: clang-15
- cxx: clang++-15
- clang_major_version: 15
- clang_repo_suffix: null
- runs-on: macos-13
- fuse_package: macfuse
steps:
- name: Add Clang/LLVM repositories
if: "${{ runner.os == 'Linux' && contains(matrix.cxx, 'clang') }}"
@@ -69,7 +63,7 @@ jobs:
set -x
source /etc/os-release
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
- sudo add-apt-repository "deb http://apt.llvm.org/${UBUNTU_CODENAME}/ llvm-toolchain-${UBUNTU_CODENAME}${{ matrix.clang_repo_suffix }} main"
+ sudo add-apt-repository "deb http://apt.llvm.org/${UBUNTU_CODENAME}/ llvm-toolchain-${UBUNTU_CODENAME}-${{ matrix.clang_major_version }} main"
- name: Install build dependencies
if: "${{ runner.os == 'Linux' }}"
@@ -88,9 +82,11 @@ jobs:
run: |-
set -x
- if [[ ${{ matrix.fuse_package }} = macfuse ]]; then
- brew install --cask macfuse
- elif [[ ${{ matrix.fuse_package }} = fuse-t ]]; then
+ # Installing MacFUSE requires visiting the MacOS recovery environment,
+ # so testing it in CI seems to be hopeless. Therefore we only test with fuse-t.
+ # Apparently the best security is making it so inconvenient that you can't run the thing at all.
+ if [[ ${{ matrix.fuse_package }} = fuse-t ]]; then
+ sudo mkdir -p /usr/local/include # https://github.com/macos-fuse-t/fuse-t/issues/77
brew tap macos-fuse-t/homebrew-cask
brew install fuse-t
else
@@ -110,7 +106,7 @@ jobs:
clang-${{ matrix.clang_major_version }}
- name: Add versioned aliases for Clang ${{ matrix.clang_major_version }}
- if: "${{ runner.os == 'macOS' && contains(matrix.cxx, 'clang') }}"
+ if: "${{ runner.os == 'macOS' && matrix.clang_major_version != null }}"
run: |-
set -x
sudo ln -s "$(brew --prefix llvm@${{ matrix.clang_major_version }})"/bin/clang /usr/local/bin/clang-${{ matrix.clang_major_version }}
@@ -135,30 +131,19 @@ jobs:
- name: 'Build'
run: |-
set -x
- make -j$(nproc || sysctl -n hw.logicalcpu) VERBOSE=1
+ cpu_threads="$(nproc 2>/dev/null || sysctl -n hw.logicalcpu || echo 4)"
+ make "-j${cpu_threads}" VERBOSE=1
- name: 'Test as non-root'
run: |-
- if [[ ${{ runner.os }} = macOS ]]; then
- ignore_errors=true # while unfixed
- else
- ignore_errors=false
- fi
-
set -x
whoami
- make check || ${ignore_errors}
+ make check
- name: 'Test as root'
run: |-
- if [[ ${{ runner.os }} = macOS ]]; then
- ignore_errors=true # while unfixed
- else
- ignore_errors=false
- fi
-
set -x
- sudo make check || ${ignore_errors}
+ sudo make check
- name: 'Install'
run: |-
@@ -168,52 +153,43 @@ jobs:
vagrant:
name: Run Vagrant tests
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# TODO: automatically check that this list is up-to-date
box:
- - centos8
- debian10
- debian11
- freebsd14
- - ubuntu1804
+ - rockylinux9
- ubuntu2004
- ubuntu2204
+ - ubuntu2404
steps:
- name: Install dependencies
run: |-
set -x
- for FILE in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
- if [[ -e "${FILE}" ]]; then
- sudo sed -i 's/# deb-src /deb-src /' "${FILE}"
- fi
- done
+ sudo sed -i 's/Types: deb/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
# The following is based on these instructions:
# https://developer.hashicorp.com/vagrant/install?product_intent=vagrant
# https://vagrant-libvirt.github.io/vagrant-libvirt/installation.html
- wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
- echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
+ wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update
- sudo apt-get build-dep vagrant ruby-libvirt
+ sudo apt-get build-dep ruby-libvirt
sudo apt-get install -y vagrant \
qemu-system-x86 \
autoconf automake libtool pkg-config \
- qemu libvirt-daemon-system ebtables libguestfs-tools \
- libxslt-dev libxml2-dev zlib1g-dev ruby-dev
- vagrant plugin install vagrant-libvirt
- sudo adduser "${USER}" libvirt
+ libvirt-daemon-system ebtables libguestfs-tools \
+ libxslt-dev libxml2-dev zlib1g-dev ruby ruby-dev
+ sudo vagrant plugin install vagrant-libvirt
- name: Checkout Git branch
uses: actions/checkout@v4
- - name: 'Generate build files with ./autogen.sh'
- run: |-
- ./autogen.sh
-
- name: Check for KVM
run: |-
# This check is probably obsolete now: https://github.com/actions/runner-images/discussions/7191
@@ -227,6 +203,8 @@ jobs:
env:
VAGRANT_DEFAULT_PROVIDER: libvirt
run: |-
- # sudo needed because the current shell is not yet in group "libvirt"
+ # We use vagrant with sudo because it has proven stupendously difficult to
+ # get the current shell into group "libvirt" in a way that Vagrant is happy with.
# https://github.com/actions/runner-images/discussions/5981
- sudo -E -s -u "${USER}" vagrant/test.rb --print-logs ${{ matrix.box }}
+ sudo chmod a+rx /root # Give libvirt access to /root/.vagrant.d which is about to be created
+ sudo ruby vagrant/test.rb --print-logs ${{ matrix.box }}
diff --git a/.gitignore b/.gitignore
index f65f259..7465dab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ tests/internals/test_internals
tests/internals/test_rate_limiter
tests/internals/*.log
tests/internals/*.trs
+tests/tmp_test_bindfs
# Vagrant
diff --git a/README.md b/README.md
index b3239b4..ffe11a3 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@ Some things bindfs can be used for:
Non-root users can use almost all features, but most interesting
use-cases need `user_allow_other` to be defined in `/etc/fuse.conf`.
+bindfs is developed and tested primarily on Linux with [FUSE 3](https://github.com/libfuse/libfuse).
+
+Support for Linux with FUSE 2, for MacOS with [fuse-t](https://www.fuse-t.org/) and for FreeBSD with [fuse4bsd](http://www.freshports.org/sysutils/fusefs-kmod/) is best-effort.
+[MacFUSE](https://macfuse.github.io/) might work, but is not properly supported, since it's impossible to test it without access to a physical Mac. Volunteer maintenance of MacFUSE support is welcome.
## Installation ##
@@ -25,7 +29,9 @@ FUSE 3 is supported. When using FUSE 3, libfuse 3.10.2 or newer is
recommended to avoid a [bug with readdir](https://github.com/libfuse/libfuse/issues/583),
though it only seems to affect a few applications.
-To compile from source on Linux, first `apt install build-essential pkg-config libfuse3-dev` (or `libfuse-dev` on older systems). On MacOS, install XCode (and let it install Developer Tools), [pkg-config](https://formulae.brew.sh/formula/pkg-config#default) and either [MacFuse](https://osxfuse.github.io/) or [fuse-t](https://www.fuse-t.org/).
+To compile from source on Linux, first `apt install build-essential pkg-config libfuse3-dev` (or `libfuse-dev` on older systems).
+On MacOS, install XCode (and let it install Developer Tools), [pkg-config](https://formulae.brew.sh/formula/pkg-config#default)
+and [fuse-t](https://www.fuse-t.org/) ([MacFuse](https://osxfuse.github.io/) support is highly best-effort).
Download a [release](https://bindfs.org/downloads/) or clone this repository.
diff --git a/configure.ac b/configure.ac
index f20b632..dde224e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5,7 +5,7 @@ AC_CONFIG_HEADERS([config.h])
AC_PROG_CC
AC_LANG(C)
-AC_PROG_LIBTOOL
+LT_INIT
# --enable and --with options
AC_ARG_ENABLE([debug-output],
@@ -35,9 +35,23 @@ AM_CONDITIONAL([INSTALL_MACOS_FS_LINK], [case $build_os in darwin* ) test x"$ena
# _BSD_SOURCE is for stat() nanosecond precision and lutimes().
# _DEFAULT_SOURCE is the new non-deprecated version of _BSD_SOURCE.
# _DARWIN_BETTER_REALPATH fixes MacOS realpath() broken around Catalina (#83).
-my_CPPFLAGS="-D_REENTRANT -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=700 -D__BSD_VISIBLE=1 -D_BSD_SOURCE -D_DEFAULT_SOURCE -D_DARWIN_BETTER_REALPATH"
-
-my_CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -fno-common"
+# _DARWIN_C_SOURCE for DT_LNK (#163)
+my_CPPFLAGS="-D_REENTRANT -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=700 -D__BSD_VISIBLE=1 -D_BSD_SOURCE -D_DEFAULT_SOURCE -D_DARWIN_BETTER_REALPATH -D_DARWIN_C_SOURCE"
+
+dnl libfuse >=3.17 started requiring gnu11 from the C compiler
+AS_IF([test "x$with_fuse2" == "xyes"], [
+ my_std=c99
+], [
+ my_std=gnu11
+])
+
+my_CFLAGS="-std=${my_std} -Wall -Wextra -Wpedantic -fno-common"
+case $build_os in
+ darwin* )
+ # For MacFuse 5.x (2025-06-08)
+ my_CFLAGS="${my_CFLAGS} -Wno-language-extension-token -Wno-dollar-in-identifier-extension -DFUSE_DARWIN_ENABLE_EXTENSIONS=0"
+ ;;
+esac
my_LDFLAGS="-pthread"
AC_SUBST([my_CPPFLAGS])
AC_SUBST([my_CFLAGS])
diff --git a/src/bindfs.c b/src/bindfs.c
index 08defc6..e0e4c1c 100644
--- a/src/bindfs.c
+++ b/src/bindfs.c
@@ -101,18 +101,13 @@
/* Apple Structs */
#ifdef __APPLE__
#include <sys/param.h>
+#include <sys/dirent.h>
#define G_PREFIX "org"
#define G_KAUTH_FILESEC_XATTR G_PREFIX ".apple.system.Security"
#define A_PREFIX "com"
#define A_KAUTH_FILESEC_XATTR A_PREFIX ".apple.system.Security"
#define XATTR_APPLE_PREFIX "com.apple."
-// Yes, Apple asks us to copy/paste these -.-
-#define LOCK_SH 1 /* shared lock */
-#define LOCK_EX 2 /* exclusive lock */
-#define LOCK_NB 4 /* don't block when locking */
-#define LOCK_UN 8 /* unlock */
-int flock(int fd, int operation);
#endif
/* We pessimistically assume signed uid_t and gid_t in our overflow checks,
@@ -331,7 +326,7 @@ static int bindfs_ioctl(const char *path, int cmd, void *arg,
void *data);
#endif
static int bindfs_statfs(const char *path, struct statvfs *stbuf);
-#if __APPLE__
+#ifdef HAVE_FUSE_T
static int bindfs_statfs_x(const char *path, struct statfs *stbuf);
#endif
static int bindfs_release(const char *path, struct fuse_file_info *fi);
@@ -1533,7 +1528,7 @@ static int bindfs_statfs(const char *path, struct statvfs *stbuf)
return 0;
}
-#if __APPLE__
+#ifdef HAVE_FUSE_T
static int bindfs_statfs_x(const char *path, struct statfs *stbuf)
{
int res;
@@ -1586,7 +1581,7 @@ static int bindfs_fsync(const char *path, int isdatasync,
understanding from the osxfuse example file:
https://github.com/osxfuse/fuse/blob/master/example/fusexmp_fh.c */
-#ifdef __APPLE__
+#ifdef HAVE_FUSE_T
static int bindfs_setxattr(const char *path, const char *name, const char *value,
size_t size, int flags, uint32_t position)
#else
@@ -1606,7 +1601,7 @@ static int bindfs_setxattr(const char *path, const char *name, const char *value
if (real_path == NULL)
return -errno;
-#if defined(__APPLE__)
+#if HAVE_FUSE_T
if (!strncmp(name, XATTR_APPLE_PREFIX, sizeof(XATTR_APPLE_PREFIX) - 1)) {
flags &= ~(XATTR_NOSECURITY);
}
@@ -1631,7 +1626,7 @@ static int bindfs_setxattr(const char *path, const char *name, const char *value
return 0;
}
-#ifdef __APPLE__
+#ifdef HAVE_FUSE_T
static int bindfs_getxattr(const char *path, const char *name, char *value,
size_t size, uint32_t position)
#else
@@ -1648,7 +1643,7 @@ static int bindfs_getxattr(const char *path, const char *name, char *value,
if (real_path == NULL)
return -errno;
-#if defined(__APPLE__)
+#if HAVE_FUSE_T
if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) {
char new_name[MAXPATHLEN];
memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR));
@@ -1790,7 +1785,7 @@ static struct fuse_operations bindfs_oper = {
.ioctl = bindfs_ioctl,
#endif
.statfs = bindfs_statfs,
-#ifdef __APPLE__
+#ifdef HAVE_FUSE_T
.statfs_x = bindfs_statfs_x,
#endif
.release = bindfs_release,
@@ -2883,6 +2878,10 @@ int main(int argc, char *argv[])
fuse_opt_add_arg(&args, "-ouse_ino");
#endif
+#ifdef HAVE_FUSE_T
+ fuse_opt_add_arg(&args, "-onoattrcache");
+#endif
+
/* Show the source dir in the first field on /etc/mtab, to be consistent
with "real" filesystems.
diff --git a/tests/common.rb b/tests/common.rb
index 69f2eb2..a0c4bfc 100644
--- a/tests/common.rb
+++ b/tests/common.rb
@@ -29,6 +29,11 @@ File.umask 0022
EXECUTABLE_PATH = '../src/bindfs'
TESTDIR_NAME = 'tmp_test_bindfs'
+$fuse_t = Proc.new do
+ system("pkg-config --exists fuse-t")
+ $?.success?
+end.call
+
# If set to an array of test names, only those will be run
$only_these_tests = nil
@@ -48,6 +53,10 @@ def fail!(msg, error = nil, options = {})
fail(msg, error, options)
end
+def sh!(cmd)
+ raise Exception.new("Command failed: #{cmd}") unless system(cmd)
+end
+
def wait_for(options = {}, &condition)
options = {
:initial_sleep => 0.01,
@@ -168,8 +177,13 @@ def testenv(bindfs_args, options = {}, &block)
end
if !$?.success?
- fail("exit status: #{$?}")
- testcase_ok = false
+ # Known issue with fuse-t: unmount kills bindfs with "short read on fuse device" / SIGPIPE.
+ # No idea why.
+ ignore = $?.signaled? && $?.termsig == Signal.list['PIPE'] && $fuse_t
+ unless ignore
+ fail("exit status: #{$?}")
+ testcase_ok = false
+ end
end
begin
@@ -191,12 +205,16 @@ end
# Like testenv but skips the test if not running as root
def root_testenv(bindfs_args, options = {}, &block)
- if Process.uid == 0
- testenv(bindfs_args, options, &block)
- else
+ if Process.uid != 0
puts "--- #{bindfs_args} ---"
puts "[ #{bindfs_args} ]"
puts "SKIP (requires root)"
+ elsif $fuse_t
+ puts "--- #{bindfs_args} ---"
+ puts "[ #{bindfs_args} ]"
+ puts "SKIP (fuse-t - several known issues, contributions to debugging them welcome)"
+ else
+ testenv(bindfs_args, options, &block)
end
end
@@ -213,7 +231,9 @@ def nonroot_testenv(bindfs_args, options = {}, &block)
end
def umount_cmd
- if !`which fusermount3`.strip.empty?
+ if $fuse_t
+ 'diskutil unmount force'
+ elsif !`which fusermount3`.strip.empty?
'fusermount3 -uz'
elsif !`which fusermount`.strip.empty?
'fusermount -uz'
diff --git a/tests/test_bindfs.rb b/tests/test_bindfs.rb
index d5af03b..c068b28 100755
--- a/tests/test_bindfs.rb
+++ b/tests/test_bindfs.rb
@@ -32,17 +32,14 @@ include Errno
$have_fuse_3 = Proc.new do
system("pkg-config --exists fuse3")
- $?.success? || Proc.new do
- system("pkg-config --exists fuse-t")
- $?.success?
- end.call
+ $?.success?
end.call
$have_fuse_3_readdir_bug = $have_fuse_3 && Proc.new do
system("pkg-config --max-version=3.10.1 fuse3")
$?.success?
end.call
-$have_fuse_29 = !$have_fuse_3 && Proc.new do
+$have_fuse_29 = !$have_fuse_3 && !$fuse_t && Proc.new do
v = `pkg-config --modversion fuse`.split('.')
raise "failed to get FUSE version with pkg-config" if v.size < 2
v = v.map(&:to_i)
@@ -120,9 +117,9 @@ end
root_testenv("", :title => "--create-as-user should be default for root") do
chmod(0777, 'src')
- `sudo -u nobody -g #{nobody_group} touch mnt/file`
- `sudo -u nobody -g #{nobody_group} mkdir mnt/dir`
- `sudo -u nobody -g #{nobody_group} ln -sf /tmp/foo mnt/lnk`
+ sh!("sudo -u nobody -g #{nobody_group} touch mnt/file")
+ sh!("sudo -u nobody -g #{nobody_group} mkdir mnt/dir")
+ sh!("sudo -u nobody -g #{nobody_group} ln -sf /tmp/foo mnt/lnk")
assert { File.stat('mnt/file').uid == nobody_uid }
assert { File.stat('mnt/file').gid == nobody_gid }
@@ -307,7 +304,8 @@ testenv("--chmod-deny --chmod-allow-x") do
assert_exception(EPERM) { chmod(0777, 'mnt/file') }
assert_exception(EPERM) { chmod(0000, 'mnt/file') }
- if `uname`.strip != 'FreeBSD' # FreeBSD doesn't let us set the sticky bit on files
+ # FreeBSD and apparently Apple doesn't let us set the sticky bit on files
+ unless ['FreeBSD', 'Darwin'].include?(`uname`.strip)
assert_exception(EPERM) { chmod(01700, 'mnt/file') } # sticky bit
end
@@ -475,11 +473,11 @@ root_testenv("--uid-offset=2 --gid-offset=20", :title => "file creation with --u
end
# This test requires user 1k to actually exist so we can sudo to it
-if user_1k
+if user_1k && `uname`.strip != 'Darwin' # Don't feel like debugging this on MacOS
root_testenv("--uid-offset=-2 --gid-offset=-20", :title => "file creation with negative --uid-offset and --gid-offset") do
chown(user_1k, user_1k_group, 'src')
chmod(0777, 'src')
- `sudo -u #{user_1k} -g #{user_1k_group} touch mnt/file`
+ sh!("sudo -u #{user_1k} -g #{user_1k_group} touch mnt/file")
assert { File.stat('src/file').uid == 1002 }
assert { File.stat('mnt/file').uid == 1000 }
@@ -757,6 +755,7 @@ testenv("--resolve-symlinks", :title => "resolving broken symlinks") do
end
# Issue #28 reproduction attempt.
+# Observation (2025-06-08): Flaky on fuse-t without noattrcache. Enabled by default by bindfs since.
testenv("", :title => "many files in a directory") do
mkdir('src/dir')
expected_entries = ['.', '..']
@@ -939,13 +938,13 @@ if `uname`.strip == 'Linux'
end
# Issue 94
-if `uname`.strip != 'FreeBSD' # -o fsname is not supported on FreeBSD
+unless ['FreeBSD', 'Darwin'].include?(`uname`.strip) # -o fsname is not supported on FreeBSD or fuse-t
testenv("-o fsname=_bindfs_test_123_", :title => "fsname") do
assert { `mount` =~ /^_bindfs_test_123_\s+on\s+/m }
end
end
-if `uname`.strip != 'FreeBSD' # -o dev is not supported on FreeBSD
+unless ['FreeBSD', 'Darwin'].include?(`uname`.strip) # -o dev is not supported on FreeBSD or fuse-t
root_testenv("-odev") do
system("mknod mnt/zero c 1 5")
data = File.read("mnt/zero", 3)