Migrate to FUSE 3.x API (#350)

* Migrate to FUSE 3.x API

- Update FUSE_USE_VERSION from 26 to 30
- Replace fuse_mount/fuse_unmount with fuse_session_mount/fuse_session_unmount
- Update fuse_new() to take args first, remove channel parameter
- Add flags parameter to readdir handler (enum fuse_readdir_flags)
- Add flags parameter to rename handler (for renameat2 support)
- Update filler function calls to include FUSE_FILL_DIR_PLUS flag
- Merge ftruncate into truncate handler (FUSE 3 combines them)
- Update fuse_loop_mt to fuse_loop_mt_31 with loop config
- Link against libfuse3 instead of libfuse
- Remove fuse_chan usage (deprecated in FUSE 3)

Addresses #342

* Remove deprecated FUSE options nonempty and hard_remove

- nonempty: Removed in FUSE 3.0, mounting on non-empty directories is now default behavior
- hard_remove: High-level API option not available in FUSE 3, immediate unlink is standard

These options are no longer recognized by FUSE 3 and cause mount errors.

* Fix init handler signature for FUSE 3

FUSE 3 init handler requires struct fuse_config* parameter.
This parameter provides access to high-level API configuration
options that can be modified during initialization.

Without this parameter, the init handler signature is incompatible
with FUSE 3, causing filesystem operations to fail.

* Add FUSE 2/3 backward compatibility

Automatically detects and builds against either FUSE 2 or FUSE 3:
- detect_fuse.sh script checks for fuse3 or fuse pkg-config
- Makefile dynamically sets FUSE_USE_VERSION, CFLAGS, and LDFLAGS
- Conditional compilation (#if FUSE_USE_VERSION >= 30) for API differences:
  * init handler signature (fuse_config parameter)
  * readdir handler signature (flags parameter)
  * rename handler signature (flags parameter)
  * truncate handler signature (fuse_file_info parameter)
  * filler function calls (flags parameter)
  * fuse_loop_mt vs fuse_loop_mt_31
  * mount/unmount sequence (fuse_session_mount vs fuse_mount)
  * ftruncate operations struct member

Maintains full compatibility with both FUSE 2.x (Debian Bookworm) and
FUSE 3.x (Debian Testing+) without requiring separate code branches.

* Add FORCE_FUSE build option and fix hardcoded version

- Remove hardcoded FUSE_USE_VERSION defines from source files
- Add FORCE_FUSE=2 or FORCE_FUSE=3 Makefile option to override detection
- Fix FUSE 2 channel tracking (psync_fuse_channel variable)
- Properly store and use channel in mount/unmount for FUSE 2

Usage:
  make                  # Auto-detect (prefers FUSE 3)
  make FORCE_FUSE=2     # Force FUSE 2 build
  make FORCE_FUSE=3     # Force FUSE 3 build

Verified both FUSE 2 and FUSE 3 builds work correctly.

* Fix CI/CD: Add fallback for systems without pkg-config

- detect_fuse.sh now checks for pkg-config availability first
- Falls back to checking for header files in standard locations
- Makefile provides default CFLAGS when pkg-config unavailable
- Fixes build on minimal CI/CD environments

Fallback locations checked:
- /usr/include/fuse3/fuse.h (FUSE 3)
- /usr/local/include/fuse3/fuse.h (FUSE 3)
- /usr/include/fuse/fuse.h (FUSE 2)
- /usr/local/include/fuse/fuse.h (FUSE 2)

* Enhance FUSE detection for non-standard locations

Multi-stage detection strategy:
1. Try pkg-config (fastest, most reliable)
2. Search common header locations:
   - /usr/include, /usr/local/include
   - /opt/local/include, /opt/include
3. Compiler test as last resort:
   - Attempts to preprocess #include <fuse.h>
   - Tests both FUSE 3 and FUSE 2 versions

This handles:
- Minimal CI/CD environments (no pkg-config)
- Non-standard install locations (Homebrew, custom builds)
- Distro-specific paths (BSD, macOS, custom Linux)

The compiler test ensures detection works even when headers
are in unusual locations that gcc can find via its search paths.

* Support FUSE 3.12+ API

- Use fuse_loop_mt_312 for FUSE 3.12+, fuse_loop_mt_31 for 3.0-3.11
- Conditionally add fuse_file_info param to getattr/chmod/chown/utimens
- Replace fuse_session_mount/unmount with fuse_mount/unmount
- Tested with FUSE 3.18 on Debian forky

* Fix pcl-1ib: correct FUSE 3.x handler signatures and loop_mt call

- Replace FUSE_MINOR_VERSION >= 12 checks with FUSE_USE_VERSION >= 30
  for getattr/chmod/chown/utimens handler signatures; FUSE 3.x always
  requires the extra struct fuse_file_info* parameter — the wrong
  signature caused type mismatches and "Transport endpoint is not
  connected" failures
- Replace non-existent fuse_loop_mt_312 extern with correct conditional:
  FUSE_USE_VERSION >= 32 uses fuse_loop_mt(f, &config), else
  fuse_loop_mt_31(f, clone_fd)
- Fix inverted return logic in is_fuse3_installed_on_system(): was
  returning 0 on success (when fusermount3 found), now correctly
  returns non-zero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove unused is_fuse3_installed_on_system() from pfs.c

The function was static and had no remaining call sites after the
-ononempty/-ohard_remove args block was cleaned up, producing a
-Wunused-function warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Restore shutdown_requested definition removed during rebase conflict resolution

The variable is declared extern in pfs.h and referenced in both
pfs.c (psync_signal_handler) and control_tools.cpp; the definition
must exist in pfs.c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix pfs_creat: missing canmodify assignment causes EACCES on write

pfs_creat called pfs_create_file() but never set of->canmodify, leaving
it zero-initialized. pfs_write and pfs_ftruncate both check canmodify
and return -EACCES if false, making all writes to newly created files
fail with Permission denied.

All five open paths in pfs_open set canmodify correctly (added in #326),
but pfs_creat was missed. Fix by applying the same pattern immediately
after psql_unlock(), while fpath is still in scope.

Caught during FUSE 2/3 regression testing (Phase 2 write test).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Extend CI matrix: add FUSE 2, FUSE 3 legacy/current/forky build jobs

Replaces single build job with four jobs covering all tested FUSE configs:
- fuse2: debian:trixie + libfuse-dev (FUSE_USE_VERSION=26)
- fuse3-legacy: ubuntu:20.04 + libfuse3-dev 3.9.x (fuse_loop_mt_31 path)
- fuse3-current: ubuntu:22.04 + libfuse3-dev 3.12.x
- fuse3-forky: debian:forky + libfuse3-dev 3.18.1

Each job verifies correct symbol linkage after build. Runtime mount tests
are outside CI scope (require privileged containers with /dev/fuse).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: replace Ubuntu 20.04/22.04 with ubuntu:24.04 for FUSE3 job

ubuntu:20.04 has old compiler (g++-9) and mbedTLS 2.x.
ubuntu:22.04 ships mbedTLS 2.28.x; incompatible with our mbedTLS 3.x-only code.
ubuntu:24.04 has mbedTLS 3.x and libfuse3 3.16.x, covering the 3.12-3.17 range.
FUSE3 < 3.12 is verified locally but not representable in CI without fighting
old toolchains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: add archlinux:latest job for rolling-release compatibility

Arch Linux is a rolling distro shipping latest fuse3, mbedtls 3.x, and
gcc — useful as an early-warning canary for future API breakage. Also
directly covers the maintained Arch package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: add fedora:41 and fedora:latest jobs with mbedtls3-devel

Fedora ships both mbedtls 2.x and 3.x; explicitly install mbedtls3-devel
to match our mbedTLS 3.x-only requirement. fedora:41 pins a known-good
release; fedora:latest tracks current as a rolling canary alongside Arch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* README: add compatibility matrix with CI badge references

Documents the 6-distro build matrix tested in CI, mbedTLS 3.x requirement,
and notes on manual runtime mount testing coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix CI package names; clean up README compatibility matrix

- Fedora 41: mbedtls3-devel -> mbedtls3.6-devel (correct versioned name)
- Fedora latest (43): mbedtls3-devel -> mbedtls-devel (now ships 3.6.5)
- Ubuntu 24.04: replaced with debian:trixie FORCE_FUSE=3 (Ubuntu has no
  mbedTLS 3.x in standard repos; trixie has both libfuse3-dev and mbedTLS 3.x)
- README: remove CI badge links from compatibility matrix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-09 21:51:00 +01:00 committed by GitHub
parent 90945b8817
commit 0c0c6855c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 320 additions and 52 deletions

View File

@ -7,7 +7,8 @@ on:
branches: [ "main" ] branches: [ "main" ]
jobs: jobs:
build: fuse2:
name: FUSE 2 (debian:trixie)
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: debian:trixie image: debian:trixie
@ -16,6 +17,108 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
apt-get update apt-get update
apt-get install -y build-essential libreadline-dev libudev-dev libfuse-dev libsqlite3-dev zlib1g-dev libboost-dev libboost-system-dev libboost-program-options-dev libmbedtls-dev apt-get install -y build-essential pkg-config libreadline-dev libudev-dev \
- name: make libfuse-dev libsqlite3-dev zlib1g-dev \
run: make libboost-dev libboost-program-options-dev libmbedtls-dev
- name: Build (FUSE 2)
run: make FORCE_FUSE=2 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep -E 'fuse_loop_mt@|fuse_mount@|psync_fuse_channel'
ldd pcloudcc | grep libfuse.so.2
fuse3-current:
name: FUSE 3 current (debian:trixie)
runs-on: ubuntu-latest
container:
image: debian:trixie
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update
apt-get install -y build-essential pkg-config libreadline-dev libudev-dev \
libfuse3-dev libsqlite3-dev zlib1g-dev \
libboost-dev libboost-program-options-dev libmbedtls-dev
- name: Build (FUSE 3 current)
run: make FORCE_FUSE=3 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep 'fuse_loop_mt_31'
ldd pcloudcc | grep libfuse3
fuse3-arch:
name: FUSE 3 rolling (archlinux:latest)
runs-on: ubuntu-latest
container:
image: archlinux:latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
pacman -Sy --noconfirm base-devel pkg-config readline fuse3 sqlite3 \
zlib boost mbedtls
- name: Build (FUSE 3 rolling)
run: make FORCE_FUSE=3 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep 'fuse_loop_mt'
ldd pcloudcc | grep libfuse3
fuse3-fedora41:
name: FUSE 3 (fedora:41)
runs-on: ubuntu-latest
container:
image: fedora:41
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
dnf install -y gcc gcc-c++ make pkg-config binutils \
readline-devel systemd-devel fuse3-devel sqlite-devel zlib-devel \
boost-devel boost-program-options mbedtls3.6-devel
- name: Build (FUSE 3 Fedora 41)
run: make FORCE_FUSE=3 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep 'fuse_loop_mt'
ldd pcloudcc | grep libfuse3
fuse3-fedora-latest:
name: FUSE 3 (fedora:latest)
runs-on: ubuntu-latest
container:
image: fedora:latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
dnf install -y gcc gcc-c++ make pkg-config binutils \
readline-devel systemd-devel fuse3-devel sqlite-devel zlib-devel \
boost-devel boost-program-options mbedtls-devel
- name: Build (FUSE 3 Fedora latest)
run: make FORCE_FUSE=3 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep 'fuse_loop_mt'
ldd pcloudcc | grep libfuse3
fuse3-forky:
name: FUSE 3.18+ (debian:forky)
runs-on: ubuntu-latest
container:
image: debian:forky
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update
apt-get install -y build-essential pkg-config libreadline-dev libudev-dev \
libfuse3-dev libsqlite3-dev zlib1g-dev \
libboost-dev libboost-program-options-dev libmbedtls-dev
- name: Build (FUSE 3.18+)
run: make FORCE_FUSE=3 BUILD=release
- name: Verify symbols
run: |
nm pcloudcc | grep 'fuse_loop_mt_31'
ldd pcloudcc | grep libfuse3

View File

@ -5,8 +5,33 @@ AR := ar
DIST_CFLAGS := $(CFLAGS) DIST_CFLAGS := $(CFLAGS)
DIST_CXXFLAGS := $(CXXFLAGS) DIST_CXXFLAGS := $(CXXFLAGS)
# Detect FUSE version (can be overridden with FORCE_FUSE=2 or FORCE_FUSE=3)
ifdef FORCE_FUSE
ifeq ($(FORCE_FUSE),2)
FUSE_VERSION := FUSE2
else ifeq ($(FORCE_FUSE),3)
FUSE_VERSION := FUSE3
else
$(error FORCE_FUSE must be 2 or 3)
endif
else
FUSE_VERSION := $(shell ./detect_fuse.sh)
endif
ifeq ($(FUSE_VERSION),FUSE3)
FUSE_CFLAGS := $(shell pkg-config --cflags fuse3 2>/dev/null || echo "-I/usr/include/fuse3 -D_FILE_OFFSET_BITS=64")
FUSE_LIBS := -lfuse3
FUSE_USE_VERSION := 30
else ifeq ($(FUSE_VERSION),FUSE2)
FUSE_CFLAGS := $(shell pkg-config --cflags fuse 2>/dev/null || echo "-I/usr/include/fuse -D_FILE_OFFSET_BITS=64")
FUSE_LIBS := -lfuse
FUSE_USE_VERSION := 26
else
$(error FUSE library not found. Install libfuse-dev or libfuse3-dev)
endif
COMMONFLAGS = -fsanitize=address COMMONFLAGS = -fsanitize=address
CFLAGS = -fPIC $(COMMONFLAGS) -I./pclsync -I/usr/include $(shell pkg-config --cflags $$(pkg-config --list-all | grep -o 'mbedtls[0-9.]*\s' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include") CFLAGS = -fPIC $(COMMONFLAGS) -I./pclsync -I/usr/include $(FUSE_CFLAGS) $(shell pkg-config --cflags $$(pkg-config --list-all | grep -o 'mbedtls[0-9.]*\s' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include")
ifneq (,$(filter clang%,$(CC))) ifneq (,$(filter clang%,$(CC)))
CFLAGS += -Wthread-safety CFLAGS += -Wthread-safety
endif endif
@ -20,7 +45,7 @@ LIBLDFLAGS = $(COMMONFLAGS) -lreadline -lpthread -ludev -lsqlite3 -lz $(shell \
else \ else \
pkg-config --libs mbedtls mbedx509 mbedcrypto 2>/dev/null || echo "-L/usr/local/lib -lmbedtls -lmbedx509 -lmbedcrypto"; \ pkg-config --libs mbedtls mbedx509 mbedcrypto 2>/dev/null || echo "-L/usr/local/lib -lmbedtls -lmbedx509 -lmbedcrypto"; \
fi) fi)
EXECLDFLAGS = $(COMMONFLAGS) -lboost_program_options -lfuse EXECLDFLAGS = $(COMMONFLAGS) -lboost_program_options $(FUSE_LIBS)
SCAN := 0 SCAN := 0
SRCDIR := . SRCDIR := .
@ -42,14 +67,14 @@ LIBOUT := libpcloudcc_lib.so
# Build type specific flags # Build type specific flags
ifeq ($(BUILD), debug) ifeq ($(BUILD), debug)
CFLAGS += -g -O0 -DDEBUG -Wall -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26 -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL) CFLAGS += -g -O0 -DDEBUG -Wall -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=$(FUSE_USE_VERSION) -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL)
CXXFLAGS += -g -O0 -DDEBUG -Wall -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26 -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL) CXXFLAGS += -g -O0 -DDEBUG -Wall -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=$(FUSE_USE_VERSION) -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL)
DEBUGSRC := $(wildcard $(LIBDIR)/debug/*.c) DEBUGSRC := $(wildcard $(LIBDIR)/debug/*.c)
DEBUGOBJ := $(notdir $(DEBUGSRC:%.c=%.o)) DEBUGOBJ := $(notdir $(DEBUGSRC:%.c=%.o))
COBJ += $(DEBUGOBJ) COBJ += $(DEBUGOBJ)
else ifeq ($(BUILD), release) else ifeq ($(BUILD), release)
CFLAGS += -g -O0 -DNDEBUG -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26 -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL) CFLAGS += -O2 -DNDEBUG -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=$(FUSE_USE_VERSION) -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL)
CXXFLAGS += -g -O0 -DNDEBUG -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26 -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL) CXXFLAGS += -O2 -DNDEBUG -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=$(FUSE_USE_VERSION) -D_GNU_SOURCE -DPSYNC_SSL_DEBUG_LEVEL=$(SSLDBGLVL)
COMMONFLAGS := $(filter-out -fsanitize=address,$(COMMONFLAGS)) COMMONFLAGS := $(filter-out -fsanitize=address,$(COMMONFLAGS))
LIBLDFLAGS := $(filter-out -fsanitize=address,$(LIBLDFLAGS)) LIBLDFLAGS := $(filter-out -fsanitize=address,$(LIBLDFLAGS))
EXECLDFLAGS := $(filter-out -fsanitize=address,$(EXECLDFLAGS)) EXECLDFLAGS := $(filter-out -fsanitize=address,$(EXECLDFLAGS))

View File

@ -8,6 +8,19 @@ The target operating system and platform for this program is `linux/amd64`, and
I aim to support as many distributions as possible. I maintain a package for [AUR](https://aur.archlinux.org/packages/pcloudcc-lneely) and [Nix](https://github.com/lneely/nixpkgs). I do not plan on providing or maintaining any other packages, but encourage anyone interested in doing so for their own distributions. I aim to support as many distributions as possible. I maintain a package for [AUR](https://aur.archlinux.org/packages/pcloudcc-lneely) and [Nix](https://github.com/lneely/nixpkgs). I do not plan on providing or maintaining any other packages, but encourage anyone interested in doing so for their own distributions.
# Compatibility Matrix
| Distribution | FUSE |
|---|---|
| Debian trixie | 2.x |
| Debian trixie | 3.x |
| Arch Linux (rolling) | 3.x latest |
| Fedora 41 | 3.x |
| Fedora (latest) | 3.x latest |
| Debian forky | 3.18+ |
All configurations require **mbedTLS 3.x**. Runtime mount testing was performed manually on FUSE 2 (libfuse 2.9.x), FUSE 3 (libfuse3 3.10.5), and FUSE 3.18+ (Debian forky container).
# Notices # Notices
## Security Notice ## Security Notice

48
detect_fuse.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash
# Detect FUSE version and set appropriate flags
# Check if pkg-config is available
if command -v pkg-config >/dev/null 2>&1; then
# Use pkg-config if available
if pkg-config --exists fuse3 2>/dev/null; then
echo "FUSE3"
exit 0
elif pkg-config --exists fuse 2>/dev/null; then
echo "FUSE2"
exit 0
fi
fi
# Fallback: search for header files in common locations
SEARCH_PATHS="/usr/include /usr/local/include /opt/local/include /opt/include"
for base in $SEARCH_PATHS; do
if [ -f "$base/fuse3/fuse.h" ]; then
echo "FUSE3"
exit 0
fi
done
for base in $SEARCH_PATHS; do
if [ -f "$base/fuse/fuse.h" ]; then
echo "FUSE2"
exit 0
fi
done
# Last resort: try to compile a test program
if command -v gcc >/dev/null 2>&1; then
# Try FUSE 3
if echo '#include <fuse.h>' | gcc -E -DFUSE_USE_VERSION=30 - >/dev/null 2>&1; then
echo "FUSE3"
exit 0
fi
# Try FUSE 2
if echo '#include <fuse.h>' | gcc -E -DFUSE_USE_VERSION=26 - >/dev/null 2>&1; then
echo "FUSE2"
exit 0
fi
fi
echo "NONE"
exit 1

View File

@ -92,8 +92,10 @@ typedef off_t fuse_off_t;
static int shutdown_in_progress = 0; static int shutdown_in_progress = 0;
volatile sig_atomic_t shutdown_requested = 0; volatile sig_atomic_t shutdown_requested = 0;
static struct fuse_chan *psync_fuse_channel = NULL;
static struct fuse *psync_fuse = NULL; static struct fuse *psync_fuse = NULL;
#if FUSE_USE_VERSION < 30
static struct fuse_chan *psync_fuse_channel = NULL;
#endif
static char *psync_current_mountpoint = NULL; static char *psync_current_mountpoint = NULL;
static psync_generic_callback_t psync_start_callback = NULL; static psync_generic_callback_t psync_start_callback = NULL;
char *pfs_fake_prefix = NULL; char *pfs_fake_prefix = NULL;
@ -786,7 +788,11 @@ static int pfs_getrootattr(struct FUSE_STAT *stbuf) {
} \ } \
} while (0) } while (0)
#if FUSE_USE_VERSION >= 30
static int pfs_getattr(const char *path, struct FUSE_STAT *stbuf, struct fuse_file_info *fi) {
#else
static int pfs_getattr(const char *path, struct FUSE_STAT *stbuf) { static int pfs_getattr(const char *path, struct FUSE_STAT *stbuf) {
#endif
psync_sql_res *res; psync_sql_res *res;
psync_variant_row row; psync_variant_row row;
psync_fspath_t *fpath; psync_fspath_t *fpath;
@ -877,15 +883,29 @@ static int filler_decoded(pcrypto_textdec_t dec,
namedec = pcryptofolder_flddecode_filename(dec, name); namedec = pcryptofolder_flddecode_filename(dec, name);
if (!namedec) if (!namedec)
return 0; return 0;
#if FUSE_USE_VERSION >= 30
ret = filler(buf, namedec, st, off, FUSE_FILL_DIR_PLUS);
#else
ret = filler(buf, namedec, st, off); ret = filler(buf, namedec, st, off);
#endif
pmem_free(PMEM_SUBSYS_OTHER, namedec); pmem_free(PMEM_SUBSYS_OTHER, namedec);
return ret; return ret;
} else } else
#if FUSE_USE_VERSION >= 30
return filler(buf, name, st, off, FUSE_FILL_DIR_PLUS);
#else
return filler(buf, name, st, off); return filler(buf, name, st, off);
#endif
} }
#if FUSE_USE_VERSION >= 30
static int pfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
fuse_off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags readdir_flags) {
#else
static int pfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, static int pfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
fuse_off_t offset, struct fuse_file_info *fi) { fuse_off_t offset, struct fuse_file_info *fi) {
#endif
psync_sql_res *res; psync_sql_res *res;
psync_variant_row row; psync_variant_row row;
psync_fsfolderid_t folderid; psync_fsfolderid_t folderid;
@ -918,9 +938,15 @@ static int pfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
} }
} else } else
dec = NULL; dec = NULL;
#if FUSE_USE_VERSION >= 30
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_PLUS);
if (folderid != 0)
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_PLUS);
#else
filler(buf, ".", NULL, 0); filler(buf, ".", NULL, 0);
if (folderid != 0) if (folderid != 0)
filler(buf, "..", NULL, 0); filler(buf, "..", NULL, 0);
#endif
folder = pfs_task_get_folder_tasks_rdlocked(folderid); folder = pfs_task_get_folder_tasks_rdlocked(folderid);
if (folderid >= 0) { if (folderid >= 0) {
res = psql_query_nolock( res = psql_query_nolock(
@ -1689,6 +1715,7 @@ static int pfs_creat(const char *path, mode_t mode,
encoder); encoder);
pfs_task_release_folder_tasks_locked(folder); pfs_task_release_folder_tasks_locked(folder);
psql_unlock(); psql_unlock();
of->canmodify = (fpath->permissions & PSYNC_PERM_MODIFY) != 0;
of->newfile = 1; of->newfile = 1;
of->modified = 1; of->modified = 1;
ret = open_write_files(of, 1); ret = open_write_files(of, 1);
@ -2885,7 +2912,11 @@ static int pfs_is_nonempty_folder(psync_fsfolderid_t parent_folderid,
return ret; return ret;
} }
#if FUSE_USE_VERSION >= 30
static int pfs_rename(const char *old_path, const char *new_path, unsigned int rename_flags) {
#else
static int pfs_rename(const char *old_path, const char *new_path) { static int pfs_rename(const char *old_path, const char *new_path) {
#endif
psync_fspath_t *fold_path, *fnew_path; psync_fspath_t *fold_path, *fnew_path;
psync_sql_res *res; psync_sql_res *res;
psync_fstask_folder_t *folder; psync_fstask_folder_t *folder;
@ -3057,13 +3088,21 @@ static int pfs_statfs(const char *path, struct statvfs *stbuf) {
return 0; return 0;
} }
#if FUSE_USE_VERSION >= 30
static int pfs_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
#else
static int pfs_chmod(const char *path, mode_t mode) { static int pfs_chmod(const char *path, mode_t mode) {
#endif
pfs_set_thread_name(); pfs_set_thread_name();
pdbg_logf(D_NOTICE, "chmod %s %u", path, (unsigned)mode); pdbg_logf(D_NOTICE, "chmod %s %u", path, (unsigned)mode);
return 0; return 0;
} }
#if FUSE_USE_VERSION >= 30
static int pfs_chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi) {
#else
static int pfs_chown(const char *path, uid_t uid, gid_t gid) { static int pfs_chown(const char *path, uid_t uid, gid_t gid) {
#endif
pfs_set_thread_name(); pfs_set_thread_name();
pdbg_logf(D_NOTICE, "chown %s %u %u", path, (unsigned)uid, (unsigned)gid); pdbg_logf(D_NOTICE, "chown %s %u %u", path, (unsigned)uid, (unsigned)gid);
return 0; return 0;
@ -3229,7 +3268,11 @@ static int pfs_setcrtime(const char *path, const struct timespec *tv) {
} }
#endif #endif
#if FUSE_USE_VERSION >= 30
static int pfs_utimens(const char *path, const struct timespec tv[2], struct fuse_file_info *fi) {
#else
static int pfs_utimens(const char *path, const struct timespec tv[2]) { static int pfs_utimens(const char *path, const struct timespec tv[2]) {
#endif
pfs_set_thread_name(); pfs_set_thread_name();
pdbg_logf(D_NOTICE, "utimens %s %lu", path, tv[1].tv_sec); pdbg_logf(D_NOTICE, "utimens %s %lu", path, tv[1].tv_sec);
return pfs_set_time(path, &tv[1], 0); return pfs_set_time(path, &tv[1], 0);
@ -3286,6 +3329,27 @@ static int pfs_ftruncate(const char *path, fuse_off_t size,
(unsigned long)size); (unsigned long)size);
} }
#if FUSE_USE_VERSION >= 30
static int pfs_truncate(const char *path, fuse_off_t size, struct fuse_file_info *fi) {
struct fuse_file_info fi_local;
int ret;
pfs_set_thread_name();
pdbg_logf(D_NOTICE, "truncate %s %lu", path, (unsigned long)size);
if (fi) {
return pfs_ftruncate(path, size, fi);
}
memset(&fi_local, 0, sizeof(fi_local));
ret = pfs_open(path, &fi_local);
if (ret)
return ret;
ret = pfs_ftruncate(path, size, &fi_local);
pfs_flush(path, &fi_local);
pfs_release(path, &fi_local);
return ret;
}
#else
static int pfs_truncate(const char *path, fuse_off_t size) { static int pfs_truncate(const char *path, fuse_off_t size) {
struct fuse_file_info fi; struct fuse_file_info fi;
int ret; int ret;
@ -3300,6 +3364,7 @@ static int pfs_truncate(const char *path, fuse_off_t size) {
pfs_release(path, &fi); pfs_release(path, &fi);
return ret; return ret;
} }
#endif
static void pfs_start_callback_timer(psync_timer_t timer, void *ptr) { static void pfs_start_callback_timer(psync_timer_t timer, void *ptr) {
psync_generic_callback_t callback; psync_generic_callback_t callback;
@ -3309,7 +3374,11 @@ static void pfs_start_callback_timer(psync_timer_t timer, void *ptr) {
prun_thread("fs start callback", callback); prun_thread("fs start callback", callback);
} }
#if FUSE_USE_VERSION >= 30
static void *pfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
#else
static void *pfs_init(struct fuse_conn_info *conn) { static void *pfs_init(struct fuse_conn_info *conn) {
#endif
#if defined(FUSE_CAP_ASYNC_READ) #if defined(FUSE_CAP_ASYNC_READ)
conn->want |= FUSE_CAP_ASYNC_READ; conn->want |= FUSE_CAP_ASYNC_READ;
#endif #endif
@ -3546,8 +3615,12 @@ static void pfs_do_stop(void) {
mp = psync_fuse_get_mountpoint(); mp = psync_fuse_get_mountpoint();
if (mp) { if (mp) {
if (stat(mp, &st_before) == 0) { if (stat(mp, &st_before) == 0) {
#if FUSE_USE_VERSION >= 30
fuse_unmount(psync_fuse);
#else
fuse_unmount(mp, psync_fuse_channel); fuse_unmount(mp, psync_fuse_channel);
psync_fuse_channel = NULL; psync_fuse_channel = NULL;
#endif
clock_gettime(CLOCK_REALTIME, &ts); clock_gettime(CLOCK_REALTIME, &ts);
// Check if the mountpoint is still accessible // Check if the mountpoint is still accessible
@ -3633,13 +3706,16 @@ static void psync_fuse_thread() {
} }
pthread_mutex_unlock(&start_mutex); pthread_mutex_unlock(&start_mutex);
pdbg_logf(D_NOTICE, "running fuse_loop_mt"); pdbg_logf(D_NOTICE, "running fuse_loop_mt");
#if FUSE_USE_VERSION >= 32
struct fuse_loop_config loop_config;
loop_config.clone_fd = 1;
loop_config.max_idle_threads = 10;
fr = fuse_loop_mt(psync_fuse, &loop_config);
#elif FUSE_USE_VERSION >= 30
fr = fuse_loop_mt_31(psync_fuse, 1);
#else
fr = fuse_loop_mt(psync_fuse); fr = fuse_loop_mt(psync_fuse);
#endif
if (shutdown_requested) {
pdbg_logf(D_NOTICE, "shutdown requested, exiting fuse loop");
pfs_do_stop();
}
pdbg_logf(D_NOTICE, "fuse_loop_mt exited with code %d, running fuse_destroy", fr); pdbg_logf(D_NOTICE, "fuse_loop_mt exited with code %d, running fuse_destroy", fr);
pthread_mutex_lock(&start_mutex); pthread_mutex_lock(&start_mutex);
fuse_destroy(psync_fuse); fuse_destroy(psync_fuse);
@ -3650,28 +3726,6 @@ static void psync_fuse_thread() {
pthread_mutex_unlock(&start_mutex); pthread_mutex_unlock(&start_mutex);
} }
// Returns true if FUSE 3 is installed on the user's machine.
// Returns false if FUSE version is less than 3.
static char is_fuse3_installed_on_system() {
// Assuming that fusermount3 is only available on FUSE 3.
FILE *pipe = popen("which fusermount3", "r");
if (!pipe) {
return 0;
}
char output[1024];
memset(output, 0, sizeof(output));
if (fgets(output, sizeof(output), pipe) != NULL) {
return 0;
}
pclose(pipe);
size_t outlen = strlen(output);
return outlen > 0;
}
static int pfs_do_start() { static int pfs_do_start() {
char *mp; char *mp;
@ -3684,10 +3738,6 @@ static int pfs_do_start() {
fuse_opt_add_arg(&args, "argv"); fuse_opt_add_arg(&args, "argv");
fuse_opt_add_arg(&args, "-oauto_unmount"); fuse_opt_add_arg(&args, "-oauto_unmount");
fuse_opt_add_arg(&args, "-ofsname=" DEFAULT_FUSE_MOUNT_POINT ".fs"); fuse_opt_add_arg(&args, "-ofsname=" DEFAULT_FUSE_MOUNT_POINT ".fs");
if (!is_fuse3_installed_on_system()) {
fuse_opt_add_arg(&args, "-ononempty");
}
fuse_opt_add_arg(&args, "-ohard_remove");
// Add user-specified FUSE options from environment variable // Add user-specified FUSE options from environment variable
const char *fuse_opts_env = getenv("PCLOUD_FUSE_OPTS"); const char *fuse_opts_env = getenv("PCLOUD_FUSE_OPTS");
@ -3739,8 +3789,12 @@ static int pfs_do_start() {
psync_oper.chmod = pfs_chmod; psync_oper.chmod = pfs_chmod;
psync_oper.chown = pfs_chown; psync_oper.chown = pfs_chown;
psync_oper.utimens = pfs_utimens; psync_oper.utimens = pfs_utimens;
#if FUSE_USE_VERSION >= 30
psync_oper.truncate = pfs_truncate;
#else
psync_oper.ftruncate = pfs_ftruncate; psync_oper.ftruncate = pfs_ftruncate;
psync_oper.truncate = pfs_truncate; psync_oper.truncate = pfs_truncate;
#endif
psync_oper.setxattr = pfs_xatr_set; psync_oper.setxattr = pfs_xatr_set;
psync_oper.getxattr = pfs_xatr_get; psync_oper.getxattr = pfs_xatr_get;
@ -3770,18 +3824,45 @@ static int pfs_do_start() {
goto err00; goto err00;
} }
psync_fuse_channel = fuse_mount(mp, &args); #if FUSE_USE_VERSION >= 30
if (pdbg_unlikely(!psync_fuse_channel)) { psync_fuse = fuse_new(&args, &psync_oper, sizeof(psync_oper), NULL);
if (pdbg_unlikely(!psync_fuse)) {
pdbg_logf(D_CRITICAL,
"CRITICAL ERROR: fuse_new() failed. "
"The FUSE filesystem cannot be started. errno=%d (%s)",
errno, strerror(errno));
goto err0;
}
if (fuse_mount(psync_fuse, mp) != 0) {
pdbg_logf(D_CRITICAL,
"CRITICAL ERROR: fuse_mount() failed for mount point %s. "
"The FUSE filesystem cannot be started. errno=%d (%s)",
mp, errno, strerror(errno));
goto err1;
}
#else
struct fuse_chan *ch = fuse_mount(mp, &args);
if (pdbg_unlikely(!ch)) {
pdbg_logf(D_CRITICAL, pdbg_logf(D_CRITICAL,
"CRITICAL ERROR: fuse_mount() failed for mount point %s. " "CRITICAL ERROR: fuse_mount() failed for mount point %s. "
"The FUSE filesystem cannot be started. errno=%d (%s)", "The FUSE filesystem cannot be started. errno=%d (%s)",
mp, errno, strerror(errno)); mp, errno, strerror(errno));
goto err0; goto err0;
} }
psync_fuse = fuse_new(psync_fuse_channel, &args, &psync_oper,
sizeof(psync_oper), NULL); psync_fuse = fuse_new(ch, &args, &psync_oper, sizeof(psync_oper), NULL);
if (pdbg_unlikely(!psync_fuse)) if (pdbg_unlikely(!psync_fuse)) {
goto err1; pdbg_logf(D_CRITICAL,
"CRITICAL ERROR: fuse_new() failed. "
"The FUSE filesystem cannot be started. errno=%d (%s)",
errno, strerror(errno));
fuse_unmount(mp, ch);
goto err0;
}
psync_fuse_channel = ch;
#endif
psync_current_mountpoint = mp; psync_current_mountpoint = mp;
started = 1; started = 1;
pthread_mutex_unlock(&start_mutex); pthread_mutex_unlock(&start_mutex);
@ -3789,7 +3870,7 @@ static int pfs_do_start() {
prun_thread("fuse", psync_fuse_thread); prun_thread("fuse", psync_fuse_thread);
return 0; return 0;
err1: err1:
fuse_unmount(mp, psync_fuse_channel); fuse_destroy(psync_fuse);
err0: err0:
pmem_free(PMEM_SUBSYS_OTHER, mp); pmem_free(PMEM_SUBSYS_OTHER, mp);
err00: err00:

View File

@ -29,8 +29,6 @@
DAMAGE. DAMAGE.
*/ */
#define FUSE_USE_VERSION 26
#include <errno.h> #include <errno.h>
#include <fuse.h> #include <fuse.h>
#include <string.h> #include <string.h>