aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/dependabot.yml11
-rw-r--r--.github/workflows/ci.yml28
-rw-r--r--.gitignore3
-rw-r--r--Documentation/MANPAGE.md12
-rw-r--r--Documentation/file-format.md53
-rw-r--r--Documentation/performance.txt4
-rw-r--r--README.md65
-rwxr-xr-xbuild-without-openssl.bash7
-rwxr-xr-xbuild.bash3
-rw-r--r--cli_args.go10
-rw-r--r--contrib/atomicrename/main.go7
-rw-r--r--contrib/findholes/holes/holes.go4
-rwxr-xr-xcrossbuild.bash10
-rw-r--r--fsck.go3
-rw-r--r--go.mod17
-rw-r--r--go.sum43
-rw-r--r--gocryptfs-xray/xray_tests/xray_test.go6
-rwxr-xr-xgolint.bash17
-rw-r--r--help.go8
-rw-r--r--init_dir.go7
-rw-r--r--internal/configfile/config_file.go5
-rw-r--r--internal/configfile/scrypt.go10
-rw-r--r--internal/configfile/validate.go6
-rw-r--r--internal/contentenc/bpool.go1
-rw-r--r--internal/contentenc/content.go2
-rw-r--r--internal/cryptocore/hkdf.go15
-rw-r--r--internal/cryptocore/nonce.go2
-rw-r--r--internal/ctlsocksrv/ctlsock_serve.go4
-rw-r--r--internal/exitcodes/exitcodes.go4
-rw-r--r--internal/fusefrontend/args.go2
-rw-r--r--internal/fusefrontend/file.go28
-rw-r--r--internal/fusefrontend/file_allocate_truncate.go24
-rw-r--r--internal/fusefrontend/file_dir_ops.go177
-rw-r--r--internal/fusefrontend/file_holes.go10
-rw-r--r--internal/fusefrontend/node.go32
-rw-r--r--internal/fusefrontend/node_api_check.go1
-rw-r--r--internal/fusefrontend/node_dir_ops.go97
-rw-r--r--internal/fusefrontend/node_helpers.go5
-rw-r--r--internal/fusefrontend/node_open_create.go29
-rw-r--r--internal/fusefrontend/node_prepare_syscall.go3
-rw-r--r--internal/fusefrontend/node_xattr.go39
-rw-r--r--internal/fusefrontend/node_xattr_darwin.go17
-rw-r--r--internal/fusefrontend/node_xattr_linux.go3
-rw-r--r--internal/fusefrontend/prepare_syscall_test.go13
-rw-r--r--internal/fusefrontend/root_node.go36
-rw-r--r--internal/fusefrontend/xattr_unit_test.go6
-rw-r--r--internal/fusefrontend_reverse/excluder.go3
-rw-r--r--internal/fusefrontend_reverse/excluder_test.go5
-rw-r--r--internal/fusefrontend_reverse/node_api_check.go6
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go5
-rw-r--r--internal/fusefrontend_reverse/node_xattr.go89
-rw-r--r--internal/fusefrontend_reverse/node_xattr_darwin.go55
-rw-r--r--internal/fusefrontend_reverse/node_xattr_linux.go43
-rw-r--r--internal/fusefrontend_reverse/root_node.go45
-rw-r--r--internal/fusefrontend_reverse/virtualnode.go9
-rw-r--r--internal/inomap/inomap.go11
-rw-r--r--internal/nametransform/diriv.go2
-rw-r--r--internal/nametransform/names.go43
-rw-r--r--internal/nametransform/nfc_test.go29
-rw-r--r--internal/nametransform/pad16.go12
-rw-r--r--internal/openfiletable/open_file_table.go6
-rw-r--r--internal/pathiv/pathiv.go2
-rw-r--r--internal/readpassword/read.go10
-rw-r--r--internal/siv_aead/correctness_test.go4
-rw-r--r--internal/speed/cpuinfo.go4
-rw-r--r--internal/stupidgcm/chacha.go3
-rw-r--r--internal/stupidgcm/chacha_test.go3
-rw-r--r--internal/stupidgcm/common.go3
-rw-r--r--internal/stupidgcm/common_test.go1
-rw-r--r--internal/stupidgcm/gcm.go3
-rw-r--r--internal/stupidgcm/gcm_test.go3
-rw-r--r--internal/stupidgcm/locking.go3
-rw-r--r--internal/stupidgcm/openssl.go3
-rw-r--r--internal/stupidgcm/openssl_aead.c2
-rw-r--r--internal/stupidgcm/without_openssl.go3
-rw-r--r--internal/stupidgcm/xchacha.go5
-rw-r--r--internal/stupidgcm/xchacha_test.go3
-rw-r--r--internal/syscallcompat/asuser.go59
-rw-r--r--internal/syscallcompat/asuser_darwin.go46
-rw-r--r--internal/syscallcompat/asuser_linux.go80
-rw-r--r--internal/syscallcompat/getdents_test.go5
-rw-r--r--internal/syscallcompat/main_test.go3
-rw-r--r--internal/syscallcompat/quirks.go11
-rw-r--r--internal/syscallcompat/quirks_darwin.go2
-rw-r--r--internal/syscallcompat/quirks_linux.go44
-rw-r--r--internal/syscallcompat/rename_exchange_test.go58
-rw-r--r--internal/syscallcompat/sys_common.go74
-rw-r--r--internal/syscallcompat/sys_darwin.go98
-rw-r--r--internal/syscallcompat/sys_linux.go139
-rw-r--r--internal/syscallcompat/thread_credentials_linux.go61
-rw-r--r--internal/syscallcompat/thread_credentials_linux_32.go40
-rw-r--r--internal/syscallcompat/thread_credentials_linux_other.go37
-rw-r--r--internal/tlog/log.go2
-rw-r--r--main.go2
-rw-r--r--masterkey.go8
-rw-r--r--mount.go19
-rwxr-xr-xpackage-release-tarballs.bash2
-rwxr-xr-xprofiling/tinyfiles.bash33
-rwxr-xr-xtest-without-openssl.bash2
-rwxr-xr-xtest.bash31
-rw-r--r--tests/cli/cli_test.go65
-rw-r--r--tests/cli/longnamemax_test.go5
-rw-r--r--tests/cli/xchacha_test.go5
-rw-r--r--tests/cli/zerokey_test.go5
-rw-r--r--tests/defaults/acl_test.go14
-rw-r--r--tests/defaults/diriv_test.go5
-rw-r--r--tests/defaults/getdents_linux.go9
-rw-r--r--tests/defaults/getdents_other.go11
-rw-r--r--tests/defaults/main_test.go56
-rw-r--r--tests/defaults/overlayfs_test.go9
-rw-r--r--tests/defaults/performance_test.go3
-rw-r--r--tests/deterministic_names/deterministic_names_test.go3
-rw-r--r--tests/example_filesystems/example_test_helpers.go5
-rw-r--r--tests/fsck/.gitignore7
-rw-r--r--tests/fsck/fsck_test.go11
-rw-r--r--tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g0
-rw-r--r--tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g0
-rw-r--r--tests/hkdf_sanity/sanity_test.go3
-rwxr-xr-xtests/issue893.sh27
-rw-r--r--tests/matrix/dir_test.go24
-rw-r--r--tests/matrix/main_test.go121
-rw-r--r--tests/matrix/matrix_test.go235
-rw-r--r--tests/matrix/symlink_darwin_test.go39
-rw-r--r--tests/matrix/symlink_linux_test.go47
-rw-r--r--tests/plaintextnames/file_holes_test.go4
-rw-r--r--tests/plaintextnames/plaintextnames_test.go7
-rw-r--r--tests/reverse/correctness_test.go104
-rw-r--r--tests/reverse/ctlsock_test.go6
-rw-r--r--tests/reverse/exclude_test.go105
-rw-r--r--tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf21
-rw-r--r--tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames18
-rw-r--r--tests/reverse/exclude_test_fs/bkp1~0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/exclude0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/file10
-rw-r--r--tests/reverse/exclude_test_fs/dir1/file20
-rw-r--r--tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/subdir1/exclude0
-rw-r--r--tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude0
-rw-r--r--tests/reverse/exclude_test_fs/dir2/file0
-rw-r--r--tests/reverse/exclude_test_fs/dir2/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file0
-rw-r--r--tests/reverse/exclude_test_fs/dir2/longfile.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/dir2/subdir/file0
-rw-r--r--tests/reverse/exclude_test_fs/file10
-rw-r--r--tests/reverse/exclude_test_fs/file20
-rw-r--r--tests/reverse/exclude_test_fs/file30
-rw-r--r--tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file10
-rw-r--r--tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~0
-rw-r--r--tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file0
-rw-r--r--tests/reverse/exclude_test_fs/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
-rw-r--r--tests/reverse/force_owner_test.go3
-rw-r--r--tests/reverse/inomap_test.go4
-rw-r--r--tests/reverse/main_test.go41
-rw-r--r--tests/reverse/one_file_system_test.go11
-rw-r--r--tests/reverse/xattr_test.go39
-rw-r--r--tests/root_test/btrfs_test.go110
-rw-r--r--tests/root_test/issue893_test.go98
-rw-r--r--tests/root_test/main_test.go20
-rw-r--r--tests/root_test/root_test.go145
-rwxr-xr-xtests/stress_tests/fsstress-gocryptfs.bash11
-rwxr-xr-xtests/stress_tests/parallel_cp.sh8
-rwxr-xr-xtests/stress_tests/pingpong.bash6
-rw-r--r--tests/test_helpers/helpers.go13
-rw-r--r--tests/test_helpers/mount_unmount.go2
-rw-r--r--tests/xattr/xattr_fd_test.go4
-rw-r--r--tests/xattr/xattr_integration_test.go24
170 files changed, 2551 insertions, 1125 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..bf7c985
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ # Check for updates to GitHub Actions every week
+ interval: "weekly"
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index abda518..792b879 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,19 +14,22 @@ jobs:
# Each major Go release is supported until there are two newer major releases.
# https://go.dev/doc/devel/release#policy
go:
- - "1.18.x" # Ubuntu 22.04 LTS "jammy"
- - "1.19.x" # Debian bookworm, bullseye-backports
- - "1.20.x"
- - "1.21.x"
- "oldstable" # 2nd-latest Golang upstream stable
- "stable" # Latest Go upstream stable
+ os:
+ - "ubuntu-24.04"
+ - "ubuntu-24.04-arm"
# Don't cancel everything when one Go version fails
fail-fast: false
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}
steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0 # Make "git describe" work
+
- name: Install Go ${{ matrix.go }}
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
@@ -34,21 +37,20 @@ jobs:
# https://github.com/actions/runner/issues/1188
- run: ls -l /proc/self/fd
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0 # Make "git describe" work
-
# CI platform specific setup steps happen here
- run: sudo apt-get install -qq fuse3 libssl-dev
# Fix "/usr/bin/fusermount: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf"
- - run: echo user_allow_other | sudo tee -a /etc/fuse.conf
+ # and "/usr/bin/fusermount3: too many FUSE filesystems mounted"
+ # The latter is likely a bug in fusermount, fixed in Oct 2025 by
+ # https://github.com/libfuse/libfuse/commit/bf3dd153fbfcd610d799562490f6555b9d708905
+ - run: echo -e 'user_allow_other\nmount_max = 10000' | sudo tee -a /etc/fuse.conf && cat /etc/fuse.conf
# Build & upload static binary
- run: ./build-without-openssl.bash
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v6
with:
- name: gocryptfs static binary (Go ${{ matrix.go }})
+ name: gocryptfs ${{ github.sha }} static ${{ runner.arch }} binary, Go ${{ matrix.go }}
path: gocryptfs
# Actual test steps are in the Makefile
diff --git a/.gitignore b/.gitignore
index 2e2743f..73b7357 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@ gocryptfs.1
# Source tarball version. Should never be committed to git.
/VERSION
+
+# Ignore macos specific files
+.DS_Store \ No newline at end of file
diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md
index 1d3ab6c..64bbaa8 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -208,6 +208,16 @@ Show all invalid filenames:
-badname '*'
+#### -context string
+Set the SELinux context. See mount(8) for details.
+
+This option was added for compatibility with xfstests which sets
+this option via `-o context="system_u:object_r:root_t:s0"`.
+
+Only works when mounting as root, otherwise you get this error from fusermount3:
+
+ fusermount3: unknown option 'context="system_u:object_r:root_t:s0"'
+
#### -ctlsock string
Create a control socket at the specified location. The socket can be
used to decrypt and encrypt paths inside the filesystem. When using
@@ -580,7 +590,7 @@ files. They are concatenated for the effective password.
Example:
echo hello > hello.txt
- echo word > world.txt
+ echo world > world.txt
gocryptfs -passfile hello.txt -passfile world.txt
The effective password will be "helloworld".
diff --git a/Documentation/file-format.md b/Documentation/file-format.md
index 7cce72c..7c2e2c8 100644
--- a/Documentation/file-format.md
+++ b/Documentation/file-format.md
@@ -1,43 +1,67 @@
File Format
===========
+Empty files are stored as empty files.
+
+Non-empty files contain a *Header* and one or more *Data blocks*.
+
Header
+------
2 bytes header version (big endian uint16, currently 2)
16 bytes file id
Data block, default AES-GCM mode
+--------------------------------
16 bytes GCM IV (nonce)
1-4096 bytes encrypted data
16 bytes GHASH
-Data block, AES-SIV mode (used in reverse mode, or when explicitly enabled with `-init -aessiv`)
+Overhead = (16+16)/4096 = 1/128 = 0.78125 %
+
+Data block, AES-SIV mode
+------------------------
+
+AES-SIV is used in reverse mode, or when explicitly enabled with `-init -aessiv`.
16 bytes nonce
16 bytes SIV
1-4096 bytes encrypted data
-Data block, XChaCha20-Poly1305 (enabled via `-init -xchacha`)
+Overhead = (16+16)/4096 = 1/128 = 0.78125 %
+
+Data block, XChaCha20-Poly1305
+------------------------------
+
+Enabled via `-init -xchacha`
24 bytes nonce
1-4096 bytes encrypted data
16 bytes Poly1305 tag
-Full block overhead (AES-GCM and AES-SIV mode) = 32/4096 = 1/128 = 0.78125 %
+Overhead = (24+16)/4096 = 0.98 %
-Full block overhead (XChaCha20-Poly1305 mode) = 40/4096 = \~1 %
+Examples
+========
-Example: 1-byte file, AES-GCM and AES-SIV mode
-----------------------------------------------
+0-byte file (all modes)
+-----------------------
+
+ (empty)
+
+Total: 0 bytes
+
+1-byte file, AES-GCM and AES-SIV mode
+-------------------------------------
Header 18 bytes
Data block 33 bytes
Total: 51 bytes
-Example: 5000-byte file, , AES-GCM and AES-SIV mode
----------------------------------------------------
+5000-byte file, , AES-GCM and AES-SIV mode
+------------------------------------------
Header 18 bytes
Data block 4128 bytes
@@ -45,19 +69,24 @@ Example: 5000-byte file, , AES-GCM and AES-SIV mode
Total: 5082 bytes
-Example: 1-byte file, XChaCha20-Poly1305 mode
-----------------------------------------------
+1-byte file, XChaCha20-Poly1305 mode
+------------------------------------
Header 18 bytes
Data block 41 bytes
Total: 59 bytes
-Example: 5000-byte file, XChaCha20-Poly1305 mode
-----------------------------------------------
+5000-byte file, XChaCha20-Poly1305 mode
+---------------------------------------
Header 18 bytes
Data block 4136 bytes
Data block 944 bytes
Total: 5098 bytes
+
+See Also
+========
+
+https://nuetzlich.net/gocryptfs/forward_mode_crypto/ / https://github.com/rfjakob/gocryptfs-website/blob/master/docs/forward_mode_crypto.md
diff --git a/Documentation/performance.txt b/Documentation/performance.txt
index 24265f5..7e964e0 100644
--- a/Documentation/performance.txt
+++ b/Documentation/performance.txt
@@ -73,6 +73,10 @@ v2.0-beta2-37-g24d5d39 558 1000 12.3 6.4 4.4 2.8
v2.0-beta2-42-g4a07d65 549 1000 8.2 4.7 1.8 2.4 fusefrontend: make dirCache work for "node itself"
v2.0 420 1000 8.5 4.5 1.8 2.3 go1.16.5, Linux 5.11.21
v2.0.1-28-g49507ea 471 991 8.6 4.5 1.7 2.2
+v2.0.1-28-g49507ea 335 951 10.2 5.4 4.1 2.0 go1.25.4, Linux 6.18.6
+v2.6.1-22-gbc94538 432 950 10.0 5.4 3.8 2.0
+v2.6.1-24-gb239d51 426 941 9.9 5.5 3.7 2.0 go-fuse v2.9.0
+v2.6.1-26-g700432e 461 962 9.8 5.4 2.0 2.0
Results for EncFS for comparison (benchmark.bash -encfs):
diff --git a/README.md b/README.md
index f5783a4..c5228dc 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[![gocryptfs](Documentation/gocryptfs-logo.png)](https://nuetzlich.net/gocryptfs/)
[![CI](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml/badge.svg)](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
-[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs)
+[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs/v2)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs/v2)
[![Latest release](https://img.shields.io/github/release/rfjakob/gocryptfs.svg)](https://github.com/rfjakob/gocryptfs/releases)
[![Homebrew version](https://img.shields.io/homebrew/v/gocryptfs.svg)](https://formulae.brew.sh/formula/gocryptfs#default)
@@ -30,7 +30,7 @@ hours and hours of stress (fsstress, extractloop.bash) and correctness
testing (xfstests). It is now considered ready for general consumption.
The old principle still applies: Important data should have a backup.
-Also, keep a copy of your master key (printed on mount) in a safe place.
+Also, keep a copy of your master key (printed at init) in a safe place.
This allows you to access the data even if the gocryptfs.conf config
file is damaged or you lose the password.
@@ -50,8 +50,14 @@ of macOS support but please create a new ticket if you hit a problem.
For Windows, an independent C++ reimplementation can be found here:
[cppcryptfs](https://github.com/bailey27/cppcryptfs)
-A standalone Python tool that can decrypt files & file names is here:
+Standalone tools:
+
[gocryptfs-inspect](https://github.com/slackner/gocryptfs-inspect)
+is Python tool that can decrypt files & file names without
+using FUSE.
+
+[gocryptfs-create-folder](https://github.com/lquenti/gocryptfs-create-folder)
+is a Python tool can encrypt a directory without using FUSE.
Installation
------------
@@ -195,6 +201,59 @@ RM: 2,367
Changelog
---------
+#### v2.6.1, 2025-08-10
+* Fix warnings `cipherSize X: incomplete last block (Y bytes), padding to Z bytes`
+ (harmless but annoying, [#951](https://github.com/rfjakob/gocryptfs/issues/951))
+* MacOS: Fix GUI apps reporting failure to save files [#914](https://github.com/rfjakob/gocryptfs/issues/914)
+* MacOS: Fix `test-without-openssl.bash` trying to build tests with openssl enabled
+ ([2ebd0d754b8ee4](https://github.com/rfjakob/gocryptfs/commit/2ebd0d754b8ee46e6c65e90e1d1e13491b03b7b5))
+
+#### v2.6.0, 2025-07-14
+* Upgrade to go-fuse v2.8.0
+* Switch to the new go-fuse directory API( https://github.com/rfjakob/gocryptfs/commit/ae3c859c1179498a4882b4bd69c2243aa6912332 )
+* Fix `-force_owner` not allowing file/dir create ( https://github.com/rfjakob/gocryptfs/issues/783 )
+* Skip `TestBtrfsQuirks` if mkfs.btrfs is not installed ( https://github.com/rfjakob/gocryptfs/issues/930 )
+
+#### v2.5.4, 2025-04-13
+* Drop `GOAMD64=v2` from `build.bash`, there's user(s) still running `GOAMD64=v1` CPUs
+ ([#908](https://github.com/rfjakob/gocryptfs/issues/908),
+ [commit](https://github.com/rfjakob/gocryptfs/commit/4851c322d5ce06c559eed9e9f3cb0a5c2c72fd5e))
+
+#### v2.5.3, 2025-04-05
+* Fix `go install` failing with `g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng": invalid char '\n'`
+ ([c80558](https://github.com/rfjakob/gocryptfs/commit/c8055829c311ecaf532fd171f3a5d104f873272d))
+* Fix panic when go-fuse is newer than specified in go.mod ([#897](https://github.com/rfjakob/gocryptfs/issues/897))
+
+#### v2.5.2, 2025-03-19
+* Use our own `syscallcompat.Setreuid` (and friends) wrappers
+ ([6b1ba584](https://github.com/rfjakob/gocryptfs/commit/6b1ba5846b17eec710a70cb6c6bf23e3f2024289))
+* Upgrade `golang.org/x/sys` again
+* Provide arm64 binaries in releases in addition to amd64
+
+#### v2.5.1, 2025-01-23
+* **Downgrade `golang.org/x/sys` to unbreak `unix.Setreuid` and `-allow_other`
+ ([6d342f3](https://github.com/rfjakob/gocryptfs/commit/6d342f3f4f1e9468da00b141b2abaf1e55f28665),
+ [#893](https://github.com/rfjakob/gocryptfs/issues/893), [#892](https://github.com/rfjakob/gocryptfs/issues/892))**
+
+#### v2.5.0, 2025-01-18
+* **Important fixes for `-reverse` mode affecting the virtual `gocryptfs.diriv` and
+ `gocryptfs.longname.*.name` files.** The bug can cause file *names* to become
+ undecryptable. To make sure that sync tools like rsync copy new, good copies,
+ gocryptfs v2.5.0 and later advance ctime and mtime for these files by 10 seconds.
+ * Fix `-reverse` mode sometimes (triggered by inode number reuse) returning stale
+ data for `gocryptfs.diriv` (#802)
+ * Fix `-reverse` mode hardlinking `gocryptfs.longname.*.name` files of hardlinked
+ files together (#802)
+* Fix `-reverse` mode ignoring `-force-owner` (#809)
+* Add workaround for excessive file fragementation on btrfs (#811)
+* `-ctlsock`: automatically delete orphaned colliding socket file (#776)
+* MacOS: Fix XTIMES panic on startup (#823)
+ * Fixed by updating the go-fuse library to v2.5.0
+* MacOS: merge kernel options before passing them on (#854, #557)
+* Add `-fido2-assert-option` (#807)
+* `-init` now accepts `-masterkey`
+* `-passwd` now ignores `-extpass` and `-passfile` for the *new* password (#287, #882)
+
#### v2.4.0, 2023-06-10
* Try the `mount(2)` syscall before falling back to `fusermount(1)`. This means we
don't need `fusermount(1)` at all if running as root or in a root-like namespace
diff --git a/build-without-openssl.bash b/build-without-openssl.bash
index d5dc218..c09e7f3 100755
--- a/build-without-openssl.bash
+++ b/build-without-openssl.bash
@@ -2,9 +2,4 @@
cd "$(dirname "$0")"
-CGO_ENABLED=0 source ./build.bash -tags without_openssl
-
-if ldd gocryptfs 2> /dev/null ; then
- echo "build-without-openssl.bash: error: compiled binary is not static"
- exit 1
-fi
+CGO_ENABLED=0 source ./build.bash
diff --git a/build.bash b/build.bash
index ffdd307..5e95c3e 100755
--- a/build.bash
+++ b/build.bash
@@ -85,9 +85,6 @@ if [[ -n ${LDFLAGS:-} ]] ; then
GO_LDFLAGS="$GO_LDFLAGS \"-extldflags=$LDFLAGS\""
fi
-# Set GOAMD64 version to v2
-export GOAMD64=v2
-
# Actual "go build" call for gocryptfs
go build "-ldflags=$GO_LDFLAGS" "$@"
# Additional binaries
diff --git a/cli_args.go b/cli_args.go
index 2e9e796..707b453 100644
--- a/cli_args.go
+++ b/cli_args.go
@@ -31,13 +31,13 @@ type argContainer struct {
longnames, allow_other, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, hh, info,
sharedstorage, fsck, one_file_system, deterministic_names,
- xchacha bool
+ xchacha, noxattr bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
masterkey, mountpoint, cipherdir, cpuprofile,
- memprofile, ko, ctlsock, fsname, force_owner, trace string
+ memprofile, ko, ctlsock, fsname, force_owner, trace, context string
// FIDO2
- fido2 string
+ fido2 string
fido2_assert_options []string
// -extpass, -badname, -passfile can be passed multiple times
extpass, badname, passfile []string
@@ -84,7 +84,7 @@ func prefixOArgs(osArgs []string) ([]string, error) {
if osArgs[i] == "-o" {
// Last argument?
if i+1 >= len(osArgs) {
- return nil, fmt.Errorf("The \"-o\" option requires an argument")
+ return nil, fmt.Errorf("the \"-o\" option requires an argument")
}
oOpts = strings.Split(osArgs[i+1], ",")
// Skip over the arguments to "-o"
@@ -188,6 +188,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.BoolVar(&args.one_file_system, "one-file-system", false, "Don't cross filesystem boundaries")
flagSet.BoolVar(&args.deterministic_names, "deterministic-names", false, "Disable diriv file name randomisation")
flagSet.BoolVar(&args.xchacha, "xchacha", false, "Use XChaCha20-Poly1305 file content encryption")
+ flagSet.BoolVar(&args.noxattr, "noxattr", false, "Disable extended attribute operations")
// Mount options with opposites
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
@@ -211,6 +212,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
flagSet.StringVar(&args.fido2, "fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
+ flagSet.StringVar(&args.context, "context", "", "Set SELinux context (see mount(8) for details)")
flagSet.StringArrayVar(&args.fido2_assert_options, "fido2-assert-option", nil, "Options to be passed with `fido2-assert -t`")
// Exclusion options
diff --git a/contrib/atomicrename/main.go b/contrib/atomicrename/main.go
index 394753b..337371c 100644
--- a/contrib/atomicrename/main.go
+++ b/contrib/atomicrename/main.go
@@ -4,7 +4,6 @@ import (
"bytes"
"flag"
"fmt"
- "io/ioutil"
"os"
"strings"
"sync/atomic"
@@ -49,7 +48,7 @@ func main() {
srcFiles[srcName] = struct{}{}
buf := bytes.Repeat([]byte("_"), i)
buf = append(buf, hello...)
- if err := ioutil.WriteFile(srcName, buf, 0600); err != nil {
+ if err := os.WriteFile(srcName, buf, 0600); err != nil {
panic(err)
}
fmt.Print(".")
@@ -58,7 +57,7 @@ func main() {
// prepare destination file
const dstName = "dst.atomicrename"
- if err := ioutil.WriteFile(dstName, hello, 0600); err != nil {
+ if err := os.WriteFile(dstName, hello, 0600); err != nil {
panic(err)
}
@@ -69,7 +68,7 @@ func main() {
// read thread
go func() {
for atomic.LoadInt32(&running) == 1 {
- have, err := ioutil.ReadFile(dstName)
+ have, err := os.ReadFile(dstName)
if err != nil {
fmt.Println(err)
stats.readError++
diff --git a/contrib/findholes/holes/holes.go b/contrib/findholes/holes/holes.go
index 95c9d2b..72f5114 100644
--- a/contrib/findholes/holes/holes.go
+++ b/contrib/findholes/holes/holes.go
@@ -8,7 +8,6 @@ import (
"math/rand"
"os"
"syscall"
- "time"
)
const (
@@ -130,7 +129,7 @@ func Find(fd int) (segments []Segment, err error) {
cursor = off
if oldCursor == cursor {
- return nil, fmt.Errorf("%s\nerror: seek loop!", PrettyPrint(segments))
+ return nil, fmt.Errorf("%s\nerror: seek loop", PrettyPrint(segments))
}
}
return segments, nil
@@ -176,7 +175,6 @@ func Create(path string) {
}
defer f.Close()
- rand.Seed(time.Now().UnixNano())
offsets := make([]int64, 10)
for i := range offsets {
offsets[i] = int64(rand.Int31n(60000))
diff --git a/crossbuild.bash b/crossbuild.bash
index a9fc6ae..ff773ec 100755
--- a/crossbuild.bash
+++ b/crossbuild.bash
@@ -15,12 +15,12 @@ function compile_tests {
set -eux
-export GO111MODULE=on
export CGO_ENABLED=0
GOOS=linux GOARCH=amd64 build
+GOOS=linux GOARCH=386 build
-# See https://github.com/golang/go/wiki/GoArm
+# See https://go.dev/wiki/GoArm
GOOS=linux GOARCH=arm GOARM=7 build
GOOS=linux GOARCH=arm64 build
@@ -30,8 +30,4 @@ GOOS=darwin GOARCH=amd64 build
time GOOS=darwin GOARCH=amd64 compile_tests
# MacOS on Apple Silicon M1.
-# Go 1.16 added support for the M1 and added ios/arm64,
-# so we use this to check if we should attempt a build.
-if go tool dist list | grep ios/arm64 ; then
- GOOS=darwin GOARCH=arm64 build
-fi
+GOOS=darwin GOARCH=arm64 build
diff --git a/fsck.go b/fsck.go
index 6c2cc92..2a76020 100644
--- a/fsck.go
+++ b/fsck.go
@@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"io"
- "io/ioutil"
"os"
"os/signal"
"path/filepath"
@@ -264,7 +263,7 @@ func fsck(args *argContainer) (exitcode int) {
args.allow_other = false
args.ro = true
var err error
- args.mountpoint, err = ioutil.TempDir("", "gocryptfs.fsck.")
+ args.mountpoint, err = os.MkdirTemp("", "gocryptfs.fsck.")
if err != nil {
tlog.Fatal.Printf("fsck: TmpDir: %v", err)
os.Exit(exitcodes.MountPoint)
diff --git a/go.mod b/go.mod
index 63ac969..66584d4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,16 +1,17 @@
module github.com/rfjakob/gocryptfs/v2
-go 1.19
+go 1.24.0
require (
- github.com/aperturerobotics/jacobsa-crypto v1.0.2
- github.com/hanwen/go-fuse/v2 v2.5.0
- github.com/moby/sys/mountinfo v0.6.2
+ github.com/aperturerobotics/jacobsa-crypto v1.1.0
+ github.com/hanwen/go-fuse/v2 v2.9.0
+ github.com/moby/sys/mountinfo v0.7.2
github.com/pkg/xattr v0.4.9
- github.com/rfjakob/eme v1.1.2
+ github.com/rfjakob/eme v1.2.0
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/spf13/pflag v1.0.5
- golang.org/x/crypto v0.18.0
- golang.org/x/sys v0.16.0
- golang.org/x/term v0.16.0
+ golang.org/x/crypto v0.45.0
+ golang.org/x/sys v0.38.0
+ golang.org/x/term v0.37.0
+ golang.org/x/text v0.31.0
)
diff --git a/go.sum b/go.sum
index 37b5355..6d0cd6c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,27 @@
-github.com/aperturerobotics/jacobsa-crypto v1.0.2 h1:tNvVy1rev9FagnOyBmTcI6d23FfNceG9IujZROTRtlc=
-github.com/aperturerobotics/jacobsa-crypto v1.0.2/go.mod h1:buWU1iY+FjIcfpb1aYfFJZfl07WlS7O30lTyC2iwjv8=
+github.com/aperturerobotics/jacobsa-crypto v1.1.0 h1:0hig54FMzU80OHrqSfqmj/W8HydRymVdz2K6D9Guffs=
+github.com/aperturerobotics/jacobsa-crypto v1.1.0/go.mod h1:buWU1iY+FjIcfpb1aYfFJZfl07WlS7O30lTyC2iwjv8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/hanwen/go-fuse/v2 v2.5.0 h1:JSJcwHQ1V9EGRy6QsosoLDMX6HaLdzyLOJpKdPqDt9k=
-github.com/hanwen/go-fuse/v2 v2.5.0/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
+github.com/hanwen/go-fuse/v2 v2.9.0 h1:0AOGUkHtbOVeyGLr0tXupiid1Vg7QB7M6YUcdmVdC58=
+github.com/hanwen/go-fuse/v2 v2.9.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA=
+github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M=
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw=
+github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff/go.mod h1:gJWba/XXGl0UoOmBQKRWCJdHrr3nE0T65t6ioaj3mLI=
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ulwVIHVDWFHp8eIXmbfSExkvdn9qMXI=
+github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI=
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
-github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
-github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
+github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
+github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4=
-github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
+github.com/rfjakob/eme v1.2.0 h1:8dAHL+WVAw06+7DkRKnRiFp1JL3QjcJEZFqDnndUaSI=
+github.com/rfjakob/eme v1.2.0/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -25,16 +29,17 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/gocryptfs-xray/xray_tests/xray_test.go b/gocryptfs-xray/xray_tests/xray_test.go
index 6dc7c13..e9b9eab 100644
--- a/gocryptfs-xray/xray_tests/xray_test.go
+++ b/gocryptfs-xray/xray_tests/xray_test.go
@@ -3,7 +3,7 @@ package xray_tests
import (
"bytes"
"fmt"
- "io/ioutil"
+ "os"
"os/exec"
"testing"
@@ -11,7 +11,7 @@ import (
)
func TestAesgcmXray(t *testing.T) {
- expected, err := ioutil.ReadFile("aesgcm_fs.xray.txt")
+ expected, err := os.ReadFile("aesgcm_fs.xray.txt")
if err != nil {
t.Fatal(err)
}
@@ -28,7 +28,7 @@ func TestAesgcmXray(t *testing.T) {
}
func TestAessivXray(t *testing.T) {
- expected, err := ioutil.ReadFile("aessiv_fs.xray.txt")
+ expected, err := os.ReadFile("aessiv_fs.xray.txt")
if err != nil {
t.Fatal(err)
}
diff --git a/golint.bash b/golint.bash
deleted file mode 100755
index d6fe729..0000000
--- a/golint.bash
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash -u
-
-OUTPUT=$(
- golint ./... | \
- grep -v "don't use an underscore in package name" | \
- grep -v "don't use ALL_CAPS in Go names; use CamelCase" |
- grep -v "don't use underscores in Go names"
-)
-
-# No output --> all good
-if [[ -z $OUTPUT ]] ; then
- exit 0
-fi
-
-echo "golint.bash:"
-echo "$OUTPUT"
-exit 1
diff --git a/help.go b/help.go
index 2a79e22..bb7e024 100644
--- a/help.go
+++ b/help.go
@@ -13,8 +13,8 @@ const tUsage = "" +
// helpShort is what gets displayed when passed "-h" or on syntax error.
func helpShort() {
printVersion()
- fmt.Printf("\n")
- fmt.Printf(tUsage)
+ fmt.Print("\n")
+ fmt.Print(tUsage)
fmt.Printf(`
Common Options (use -hh to show all):
-aessiv Use AES-SIV encryption (with -init)
@@ -48,8 +48,8 @@ Common Options (use -hh to show all):
// helpLong gets only displayed on "-hh"
func helpLong() {
printVersion()
- fmt.Printf("\n")
- fmt.Printf(tUsage)
+ fmt.Print("\n")
+ fmt.Print(tUsage)
fmt.Printf(`
Notes: All options can equivalently use "-" (single dash) or "--" (double dash).
A standalone "--" stops option parsing.
diff --git a/init_dir.go b/init_dir.go
index d79a4b7..870604a 100644
--- a/init_dir.go
+++ b/init_dir.go
@@ -2,7 +2,6 @@ package main
import (
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -26,7 +25,7 @@ func isEmptyDir(dir string) error {
if err != nil {
return err
}
- entries, err := ioutil.ReadDir(dir)
+ entries, err := os.ReadDir(dir)
if err != nil {
return err
}
@@ -69,14 +68,14 @@ func initDir(args *argContainer) {
os.Exit(exitcodes.CipherDir)
}
if !args.xchacha && !stupidgcm.HasAESGCMHardwareSupport() {
- tlog.Info.Printf(tlog.ColorYellow +
+ tlog.Info.Println(tlog.ColorYellow +
"Notice: Your CPU does not have AES-GCM acceleration. Consider using -xchacha for better performance." +
tlog.ColorReset)
}
}
// Choose password for config file
if len(args.extpass) == 0 && args.fido2 == "" {
- tlog.Info.Printf("Choose a password for protecting your files.")
+ tlog.Info.Println("Choose a password for protecting your files.")
}
{
var password []byte
diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go
index 995a0c8..28a1ca5 100644
--- a/internal/configfile/config_file.go
+++ b/internal/configfile/config_file.go
@@ -5,7 +5,6 @@ package configfile
import (
"encoding/json"
"fmt"
- "io/ioutil"
"syscall"
"os"
@@ -183,12 +182,12 @@ func Load(filename string) (*ConfFile, error) {
cf.filename = filename
// Read from disk
- js, err := ioutil.ReadFile(filename)
+ js, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
if len(js) == 0 {
- return nil, fmt.Errorf("Config file is empty")
+ return nil, fmt.Errorf("config file is empty")
}
// Unmarshal
diff --git a/internal/configfile/scrypt.go b/internal/configfile/scrypt.go
index 0ce8777..f6201ba 100644
--- a/internal/configfile/scrypt.go
+++ b/internal/configfile/scrypt.go
@@ -87,19 +87,19 @@ func (s *ScryptKDF) LogN() int {
func (s *ScryptKDF) validateParams() error {
minN := 1 << scryptMinLogN
if s.N < minN {
- return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense")
+ return fmt.Errorf("fatal: scryptn below 10 is too low to make sense")
}
if s.R < scryptMinR {
- return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
+ return fmt.Errorf("fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
}
if s.P < scryptMinP {
- return fmt.Errorf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
+ return fmt.Errorf("fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
}
if len(s.Salt) < scryptMinSaltLen {
- return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
+ return fmt.Errorf("fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
}
if s.KeyLen < cryptocore.KeyLen {
- return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
+ return fmt.Errorf("fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
}
return nil
}
diff --git a/internal/configfile/validate.go b/internal/configfile/validate.go
index ab8917d..5428a7b 100644
--- a/internal/configfile/validate.go
+++ b/internal/configfile/validate.go
@@ -9,7 +9,7 @@ import (
// Validate that the combination of settings makes sense and is supported
func (cf *ConfFile) Validate() error {
if cf.Version != contentenc.CurrentVersion {
- return fmt.Errorf("Unsupported on-disk format %d", cf.Version)
+ return fmt.Errorf("unsupported on-disk format %d", cf.Version)
}
// scrypt params ok?
if err := cf.ScryptObject.validateParams(); err != nil {
@@ -18,13 +18,13 @@ func (cf *ConfFile) Validate() error {
// All feature flags that are in the config file are known?
for _, flag := range cf.FeatureFlags {
if !isFeatureFlagKnown(flag) {
- return fmt.Errorf("Unknown feature flag %q", flag)
+ return fmt.Errorf("unknown feature flag %q", flag)
}
}
// File content encryption
{
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV) {
- return fmt.Errorf("Can't have both XChaCha20Poly1305 and AESSIV feature flags")
+ return fmt.Errorf("can't have both XChaCha20Poly1305 and AESSIV feature flags")
}
if cf.IsFeatureFlagSet(FlagAESSIV) && !cf.IsFeatureFlagSet(FlagGCMIV128) {
diff --git a/internal/contentenc/bpool.go b/internal/contentenc/bpool.go
index c4517d3..c62170d 100644
--- a/internal/contentenc/bpool.go
+++ b/internal/contentenc/bpool.go
@@ -26,6 +26,7 @@ func (b *bPool) Put(s []byte) {
if len(s) != b.sliceLen {
log.Panicf("wrong len=%d, want=%d", len(s), b.sliceLen)
}
+ //lint:ignore SA6002 We intentionally pass slice by value to avoid allocation overhead in this specific use case
b.Pool.Put(s)
}
diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go
index 3005bf5..5bf0b3c 100644
--- a/internal/contentenc/content.go
+++ b/internal/contentenc/content.go
@@ -151,7 +151,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
if len(ciphertext) < be.cryptoCore.IVLen {
tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
- return nil, errors.New("Block is too short")
+ return nil, errors.New("block is too short")
}
// Extract nonce
diff --git a/internal/cryptocore/hkdf.go b/internal/cryptocore/hkdf.go
index b56f507..369616a 100644
--- a/internal/cryptocore/hkdf.go
+++ b/internal/cryptocore/hkdf.go
@@ -1,10 +1,9 @@
package cryptocore
import (
+ "crypto/hkdf"
"crypto/sha256"
"log"
-
- "golang.org/x/crypto/hkdf"
)
const (
@@ -19,12 +18,10 @@ const (
// hkdfDerive derives "outLen" bytes from "masterkey" and "info" using
// HKDF-SHA256 (RFC 5869).
// It returns the derived bytes or panics.
-func hkdfDerive(masterkey []byte, info string, outLen int) (out []byte) {
- h := hkdf.New(sha256.New, masterkey, nil, []byte(info))
- out = make([]byte, outLen)
- n, err := h.Read(out)
- if n != outLen || err != nil {
- log.Panicf("hkdfDerive: hkdf read failed, got %d bytes, error: %v", n, err)
+func hkdfDerive(masterkey []byte, info string, outLen int) []byte {
+ key, err := hkdf.Key(sha256.New, masterkey, nil, info, outLen)
+ if err != nil {
+ log.Panicf("hkdfDerive: hkdf failed with error: %v", err)
}
- return out
+ return key
}
diff --git a/internal/cryptocore/nonce.go b/internal/cryptocore/nonce.go
index 9df094c..c800807 100644
--- a/internal/cryptocore/nonce.go
+++ b/internal/cryptocore/nonce.go
@@ -11,6 +11,8 @@ func RandBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
+ // crypto/rand.Read() is documented to never return an
+ // error, so this should never happen. Still, better safe than sorry.
log.Panic("Failed to read random bytes: " + err.Error())
}
return b
diff --git a/internal/ctlsocksrv/ctlsock_serve.go b/internal/ctlsocksrv/ctlsock_serve.go
index 85f5b65..25d1e44 100644
--- a/internal/ctlsocksrv/ctlsock_serve.go
+++ b/internal/ctlsocksrv/ctlsock_serve.go
@@ -101,7 +101,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni
}
// Neither encryption nor encryption has been requested, makes no sense
if in.DecryptPath == "" && in.EncryptPath == "" {
- err = errors.New("Empty input")
+ err = errors.New("empty input")
sendResponse(conn, err, "", "")
return
}
@@ -118,7 +118,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni
}
// Error out if the canonical path is now empty
if clean == "" {
- err = errors.New("Empty input after canonicalization")
+ err = errors.New("empty input after canonicalization")
sendResponse(conn, err, "", warnText)
return
}
diff --git a/internal/exitcodes/exitcodes.go b/internal/exitcodes/exitcodes.go
index 508ba38..881afda 100644
--- a/internal/exitcodes/exitcodes.go
+++ b/internal/exitcodes/exitcodes.go
@@ -3,7 +3,7 @@
package exitcodes
import (
- "fmt"
+ "errors"
"os"
)
@@ -83,7 +83,7 @@ type Err struct {
// NewErr returns an error containing "msg" and the exit code "code".
func NewErr(msg string, code int) Err {
return Err{
- error: fmt.Errorf(msg),
+ error: errors.New(msg),
code: code,
}
}
diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go
index 64a5923..ec3d1c2 100644
--- a/internal/fusefrontend/args.go
+++ b/internal/fusefrontend/args.go
@@ -51,4 +51,6 @@ type Args struct {
OneFileSystem bool
// DeterministicNames disables gocryptfs.diriv files
DeterministicNames bool
+ // NoXattr disables extended attribute operations
+ NoXattr bool
}
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 0e25de3..64c6ca0 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -37,8 +37,6 @@ type File struct {
// Every FUSE entrypoint should RLock(). The only user of Lock() is
// Release(), which closes the fd and sets "released" to true.
fdLock sync.RWMutex
- // Content encryption helper
- contentEnc *contentenc.ContentEnc
// Device and inode number uniquely identify the backing file
qIno inomap.QIno
// Entry in the open file table
@@ -50,6 +48,8 @@ type File struct {
lastOpCount uint64
// Parent filesystem
rootNode *RootNode
+ // If this open file is a directory, dirHandle will be set, otherwise it's nil.
+ dirHandle *DirHandle
}
// NewFile returns a new go-fuse File instance based on an already-open file
@@ -71,7 +71,6 @@ func NewFile(fd int, cName string, rn *RootNode) (f *File, st *syscall.Stat_t, e
f = &File{
fd: osFile,
- contentEnc: rn.contentEnc,
qIno: qi,
fileTableEntry: e,
rootNode: rn,
@@ -116,7 +115,7 @@ func (f *File) createHeader() (fileID []byte, err error) {
h := contentenc.RandomHeader()
buf := h.Pack()
// Prevent partially written (=corrupt) header by preallocating the space beforehand
- if !f.rootNode.args.NoPrealloc && f.rootNode.quirks&syscallcompat.QuirkBrokenFalloc == 0 {
+ if !f.rootNode.args.NoPrealloc && f.rootNode.quirks&syscallcompat.QuirkBtrfsBrokenFalloc == 0 {
err = syscallcompat.EnospcPrealloc(f.intFd(), 0, contentenc.HeaderLen)
if err != nil {
if !syscallcompat.IsENOSPC(err) {
@@ -175,7 +174,7 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er
log.Panicf("fileID=%v", fileID)
}
// Read the backing ciphertext in one go
- blocks := f.contentEnc.ExplodePlainRange(off, length)
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(off, length)
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
// f.fd.ReadAt takes an int64!
if alignedOffset > math.MaxInt64 {
@@ -204,10 +203,10 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er
tlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n)
// Decrypt it
- plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
+ plaintext, err := f.rootNode.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
f.rootNode.contentEnc.CReqPool.Put(ciphertext)
if err != nil {
- corruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
+ corruptBlockNo := firstBlockNo + f.rootNode.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, corruptBlockNo, err)
return nil, syscall.EIO
}
@@ -285,20 +284,20 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
}
// Handle payload data
dataBuf := bytes.NewBuffer(data)
- blocks := f.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
toEncrypt := make([][]byte, len(blocks))
for i, b := range blocks {
blockData := dataBuf.Next(int(b.Length))
// Incomplete block -> Read-Modify-Write
if b.IsPartial() {
// Read
- oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS())
+ oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.rootNode.contentEnc.PlainBS())
if errno != 0 {
tlog.Warn.Printf("ino%d fh%d: RMW read failed: errno=%d", f.qIno.Ino, f.intFd(), errno)
return 0, errno
}
// Modify
- blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
+ blockData = f.rootNode.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
tlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData))
}
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
@@ -307,7 +306,7 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
toEncrypt[i] = blockData
}
// Encrypt all blocks
- ciphertext := f.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID)
+ ciphertext := f.rootNode.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID)
// Preallocate so we cannot run out of space in the middle of the write.
// This prevents partially written (=corrupt) blocks.
var err error
@@ -316,7 +315,7 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
if cOff > math.MaxInt64 {
return 0, syscall.EFBIG
}
- if !f.rootNode.args.NoPrealloc && f.rootNode.quirks&syscallcompat.QuirkBrokenFalloc == 0 {
+ if !f.rootNode.args.NoPrealloc && f.rootNode.quirks&syscallcompat.QuirkBtrfsBrokenFalloc == 0 {
err = syscallcompat.EnospcPrealloc(f.intFd(), int64(cOff), int64(len(ciphertext)))
if err != nil {
if !syscallcompat.IsENOSPC(err) {
@@ -437,7 +436,10 @@ func (f *File) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
}
f.rootNode.inoMap.TranslateStat(&st)
a.FromStat(&st)
- a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size)
+ if a.IsRegular() {
+ a.Size = f.rootNode.contentEnc.CipherSizeToPlainSize(a.Size)
+ }
+ // TODO: Handle symlink size similar to node.translateSize()
if f.rootNode.args.ForceOwner != nil {
a.Owner = *f.rootNode.args.ForceOwner
}
diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go
index cae796e..a3decf9 100644
--- a/internal/fusefrontend/file_allocate_truncate.go
+++ b/internal/fusefrontend/file_allocate_truncate.go
@@ -54,7 +54,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32)
f.fileTableEntry.ContentLock.Lock()
defer f.fileTableEntry.ContentLock.Unlock()
- blocks := f.contentEnc.ExplodePlainRange(off, sz)
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(off, sz)
firstBlock := blocks[0]
lastBlock := blocks[len(blocks)-1]
@@ -63,7 +63,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32)
// the file.
cipherOff := firstBlock.BlockCipherOff()
cipherSz := lastBlock.BlockCipherOff() - cipherOff +
- f.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length
+ f.rootNode.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length
err := syscallcompat.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz))
tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n",
off, sz, mode, cipherOff, cipherSz)
@@ -113,8 +113,8 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) {
return fs.ToErrno(err)
}
- oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
- newB := float32(newSize) / float32(f.contentEnc.PlainBS())
+ oldB := float32(oldSize) / float32(f.rootNode.contentEnc.PlainBS())
+ newB := float32(newSize) / float32(f.rootNode.contentEnc.PlainBS())
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.qIno.Ino, oldB, newB, oldSize, newSize)
// File size stays the same - nothing to do
@@ -127,9 +127,9 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) {
}
// File shrinks
- blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
- cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo)
- plainOff := f.contentEnc.BlockNoToPlainOff(blockNo)
+ blockNo := f.rootNode.contentEnc.PlainOffToBlockNo(newSize)
+ cipherOff := f.rootNode.contentEnc.BlockNoToCipherOff(blockNo)
+ plainOff := f.rootNode.contentEnc.BlockNoToPlainOff(blockNo)
lastBlockLen := newSize - plainOff
var data []byte
if lastBlockLen > 0 {
@@ -161,7 +161,7 @@ func (f *File) statPlainSize() (uint64, error) {
return 0, err
}
cipherSz := uint64(fi.Size())
- plainSz := uint64(f.contentEnc.CipherSizeToPlainSize(cipherSz))
+ plainSz := uint64(f.rootNode.contentEnc.CipherSizeToPlainSize(cipherSz))
return plainSz, nil
}
@@ -174,8 +174,8 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
newEOFOffset := newPlainSz - 1
if oldPlainSz > 0 {
- n1 := f.contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
- n2 := f.contentEnc.PlainOffToBlockNo(newEOFOffset)
+ n1 := f.rootNode.contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
+ n2 := f.rootNode.contentEnc.PlainOffToBlockNo(newEOFOffset)
// The file is grown within one block, no need to pad anything.
// Write a single zero to the last byte and let doWrite figure out the RMW.
if n1 == n2 {
@@ -194,7 +194,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
// The new size is block-aligned. In this case we can do everything ourselves
// and avoid the call to doWrite.
- if newPlainSz%f.contentEnc.PlainBS() == 0 {
+ if newPlainSz%f.rootNode.contentEnc.PlainBS() == 0 {
// The file was empty, so it did not have a header. Create one.
if oldPlainSz == 0 {
id, err := f.createHeader()
@@ -203,7 +203,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
f.fileTableEntry.ID = id
}
- cSz := int64(f.contentEnc.PlainSizeToCipherSize(newPlainSz))
+ cSz := int64(f.rootNode.contentEnc.PlainSizeToCipherSize(newPlainSz))
err := syscall.Ftruncate(f.intFd(), cSz)
if err != nil {
tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
diff --git a/internal/fusefrontend/file_dir_ops.go b/internal/fusefrontend/file_dir_ops.go
new file mode 100644
index 0000000..b69e7bc
--- /dev/null
+++ b/internal/fusefrontend/file_dir_ops.go
@@ -0,0 +1,177 @@
+package fusefrontend
+
+import (
+ "context"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+ "github.com/rfjakob/gocryptfs/v2/internal/configfile"
+ "github.com/rfjakob/gocryptfs/v2/internal/nametransform"
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+ "github.com/rfjakob/gocryptfs/v2/internal/tlog"
+)
+
+func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ var fd int = -1
+ var fdDup int = -1
+ var file *File
+ var dirIV []byte
+ var ds fs.DirStream
+ rn := n.rootNode()
+
+ dirfd, cName, errno := n.prepareAtSyscallMyself()
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(dirfd)
+
+ // Open backing directory
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+
+ // NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir
+ fdDup, err = syscall.Dup(fd)
+
+ if err != nil {
+ errno = fs.ToErrno(err)
+ goto err_out
+ }
+
+ ds, errno = fs.NewLoopbackDirStreamFd(fdDup)
+ if errno != 0 {
+ goto err_out
+ }
+
+ if !rn.args.PlaintextNames {
+ // Read the DirIV from disk
+ dirIV, err = rn.nameTransform.ReadDirIVAt(fd)
+ if err != nil {
+ tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err)
+ errno = syscall.EIO
+ goto err_out
+ }
+ }
+
+ file, _, errno = NewFile(fd, cName, rn)
+ if errno != 0 {
+ goto err_out
+ }
+
+ file.dirHandle = &DirHandle{
+ ds: ds,
+ dirIV: dirIV,
+ isRootDir: n.IsRoot(),
+ }
+
+ return file, fuseFlags, errno
+
+err_out:
+ if fd >= 0 {
+ syscall.Close(fd)
+ }
+ if fdDup >= 0 {
+ syscall.Close(fdDup)
+ }
+ if errno == 0 {
+ tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0")
+ errno = syscall.EIO
+ }
+ return nil, 0, errno
+}
+
+type DirHandle struct {
+ // Content of gocryptfs.diriv. nil if plaintextnames is used.
+ dirIV []byte
+
+ isRootDir bool
+
+ // fs.loopbackDirStream with a private dup of the file descriptor
+ ds fs.FileHandle
+}
+
+var _ = (fs.FileReleasedirer)((*File)(nil))
+
+func (f *File) Releasedir(ctx context.Context, flags uint32) {
+ // Does its own locking
+ f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags)
+ // Does its own locking
+ f.Release(ctx)
+}
+
+var _ = (fs.FileSeekdirer)((*File)(nil))
+
+func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off)
+}
+
+var _ = (fs.FileFsyncdirer)((*File)(nil))
+
+func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags)
+}
+
+var _ = (fs.FileReaddirenter)((*File)(nil))
+
+// This function is symlink-safe through use of openBackingDir() and
+// ReadDirIVAt().
+func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) {
+ f.fdLock.RLock()
+ defer f.fdLock.RUnlock()
+
+ for {
+ entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx)
+ if errno != 0 || entry == nil {
+ return
+ }
+
+ cName := entry.Name
+ if cName == "." || cName == ".." {
+ // We want these as-is
+ return
+ }
+ if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName {
+ // silently ignore "gocryptfs.conf" in the top level dir
+ continue
+ }
+ if f.rootNode.args.PlaintextNames {
+ return
+ }
+ if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename {
+ // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
+ continue
+ }
+ // Handle long file name
+ isLong := nametransform.LongNameNone
+ if f.rootNode.args.LongNames {
+ isLong = nametransform.NameType(cName)
+ }
+ if isLong == nametransform.LongNameContent {
+ cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ cName = cNameLong
+ } else if isLong == nametransform.LongNameFilename {
+ // ignore "gocryptfs.longname.*.name"
+ continue
+ }
+ name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ // Override the ciphertext name with the plaintext name but reuse the rest
+ // of the structure
+ entry.Name = name
+ return
+ }
+}
diff --git a/internal/fusefrontend/file_holes.go b/internal/fusefrontend/file_holes.go
index f35fa70..fc58898 100644
--- a/internal/fusefrontend/file_holes.go
+++ b/internal/fusefrontend/file_holes.go
@@ -21,12 +21,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno {
tlog.Warn.Printf("checkAndPadHole: Fstat failed: %v", err)
return fs.ToErrno(err)
}
- plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
+ plainSize := f.rootNode.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
// Appending a single byte to the file (equivalent to writing to
// offset=plainSize) would write to "nextBlock".
- nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize)
+ nextBlock := f.rootNode.contentEnc.PlainOffToBlockNo(plainSize)
// targetBlock is the block the user wants to write to.
- targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(targetOff))
+ targetBlock := f.rootNode.contentEnc.PlainOffToBlockNo(uint64(targetOff))
// The write goes into an existing block or (if the last block was full)
// starts a new one directly after the last block. Nothing to do.
if targetBlock <= nextBlock {
@@ -45,12 +45,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno {
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
// if the file is already block-aligned.
func (f *File) zeroPad(plainSize uint64) syscall.Errno {
- lastBlockLen := plainSize % f.contentEnc.PlainBS()
+ lastBlockLen := plainSize % f.rootNode.contentEnc.PlainBS()
if lastBlockLen == 0 {
// Already block-aligned
return 0
}
- missing := f.contentEnc.PlainBS() - lastBlockLen
+ missing := f.rootNode.contentEnc.PlainBS() - lastBlockLen
pad := make([]byte, missing)
tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing)
_, errno := f.doWrite(pad, int64(plainSize))
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 687a386..95be48d 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -71,28 +71,54 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) {
// If the kernel gives us a file handle, use it.
if f != nil {
- return f.(fs.FileGetattrer).Getattr(ctx, out)
+ if fga, ok := f.(fs.FileGetattrer); ok {
+ return fga.Getattr(ctx, out)
+ }
}
+ rn := n.rootNode()
+ var st *syscall.Stat_t
+ var err error
dirfd, cName, errno := n.prepareAtSyscallMyself()
+ // Hack for deleted fifos. As OPEN on a fifo does not reach
+ // the filesystem, we have no fd to access it. To make "cat" and git's
+ // t9300-fast-import.sh happy, we fake it as best as we can.
+ // https://github.com/rfjakob/gocryptfs/issues/929
+ if errno == syscall.ENOENT && n.StableAttr().Mode == syscall.S_IFIFO {
+ out.Mode = syscall.S_IFIFO
+ out.Ino = n.StableAttr().Ino
+ // cat looks at this to determine the optimal io size. Seems to be always 4096 for fifos.
+ out.Blksize = 4096
+ // We don't know what the owner was. Set it to nobody which seems safer
+ // than leaving it at 0 (root).
+ out.Owner.Gid = 65534
+ out.Owner.Uid = 65534
+ // All the other fields stay 0. This is what cat sees (strace -v log):
+ //
+ // fstat(1, {st_dev=makedev(0, 0x4d), st_ino=3838227, st_mode=S_IFIFO|000, st_nlink=0,
+ // st_uid=65534, st_gid=65534, st_blksize=4096, st_blocks=0, st_size=0, st_atime=0,
+ // st_atime_nsec=0, st_mtime=0, st_mtime_nsec=0, st_ctime=0, st_ctime_nsec=0}) = 0
+ goto out
+ }
if errno != 0 {
return
}
+
defer syscall.Close(dirfd)
- st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
+ st, err = syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fs.ToErrno(err)
}
// Fix inode number
- rn := n.rootNode()
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
// Translate ciphertext size in `out.Attr.Size` to plaintext size
n.translateSize(dirfd, cName, &out.Attr)
+out:
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go
index 0f60c74..37d4293 100644
--- a/internal/fusefrontend/node_api_check.go
+++ b/internal/fusefrontend/node_api_check.go
@@ -7,7 +7,6 @@ import (
// Check that we have implemented the fs.Node* interfaces
var _ = (fs.NodeGetattrer)((*Node)(nil))
var _ = (fs.NodeLookuper)((*Node)(nil))
-var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))
var _ = (fs.NodeRmdirer)((*Node)(nil))
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 97e4caa..97327ce 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -12,7 +12,6 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
- "github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
@@ -137,12 +136,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
}
defer syscall.Close(fd)
- err = syscall.Fstat(fd, &st)
- if err != nil {
- tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
- return nil, fs.ToErrno(err)
- }
-
// Fix permissions
if origMode != mode {
// Preserve SGID bit if it was set due to inheritance.
@@ -151,97 +144,17 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
if err != nil {
tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err)
}
-
}
- // Create child node & return
- ch := n.newChild(ctx, &st, out)
- return ch, 0
-}
-
-// Readdir - FUSE call.
-//
-// This function is symlink-safe through use of openBackingDir() and
-// ReadDirIVAt().
-func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
- parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
- if errno != 0 {
- return nil, errno
- }
- defer syscall.Close(parentDirFd)
-
- // Read ciphertext directory
- fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
- if err != nil {
- return nil, fs.ToErrno(err)
- }
- defer syscall.Close(fd)
- cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd)
+ err = syscall.Fstat(fd, &st)
if err != nil {
+ tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
return nil, fs.ToErrno(err)
}
- // Get DirIV (stays nil if PlaintextNames is used)
- var cachedIV []byte
- rn := n.rootNode()
- if !rn.args.PlaintextNames {
- // Read the DirIV from disk
- cachedIV, err = rn.nameTransform.ReadDirIVAt(fd)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)
- return nil, syscall.EIO
- }
- }
- // Decrypted directory entries
- var plain []fuse.DirEntry
- // Add "." and ".."
- plain = append(plain, specialEntries...)
- // Filter and decrypt filenames
- for i := range cipherEntries {
- cName := cipherEntries[i].Name
- if n.IsRoot() && cName == configfile.ConfDefaultName {
- // silently ignore "gocryptfs.conf" in the top level dir
- continue
- }
- if rn.args.PlaintextNames {
- plain = append(plain, cipherEntries[i])
- continue
- }
- if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename {
- // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
- continue
- }
- // Handle long file name
- isLong := nametransform.LongNameNone
- if rn.args.LongNames {
- isLong = nametransform.NameType(cName)
- }
- if isLong == nametransform.LongNameContent {
- cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- cName = cNameLong
- } else if isLong == nametransform.LongNameFilename {
- // ignore "gocryptfs.longname.*.name"
- continue
- }
- name, err := rn.nameTransform.DecryptName(cName, cachedIV)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- // Override the ciphertext name with the plaintext name but reuse the rest
- // of the structure
- cipherEntries[i].Name = name
- plain = append(plain, cipherEntries[i])
- }
- return fs.NewListDirStream(plain), 0
+ // Create child node & return
+ ch := n.newChild(ctx, &st, out)
+ return ch, 0
}
// Rmdir - FUSE call.
diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go
index f5dfeb6..e8fca80 100644
--- a/internal/fusefrontend/node_helpers.go
+++ b/internal/fusefrontend/node_helpers.go
@@ -2,7 +2,6 @@ package fusefrontend
import (
"context"
- "sync/atomic"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
@@ -91,12 +90,12 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry
if rn.args.SharedStorage || rn.quirks&syscallcompat.QuirkDuplicateIno1 != 0 {
// Make each directory entry a unique node by using a unique generation
// value - see the comment at RootNode.gen for details.
- gen = atomic.AddUint64(&rn.gen, 1)
+ gen = rn.gen.Add(1)
}
// Create child node
id := fs.StableAttr{
- Mode: uint32(st.Mode),
+ Mode: uint32(st.Mode), // go-fuse masks this with syscall.S_IFMT
Gen: gen,
Ino: st.Ino,
}
diff --git a/internal/fusefrontend/node_open_create.go b/internal/fusefrontend/node_open_create.go
index 9598559..622d5dc 100644
--- a/internal/fusefrontend/node_open_create.go
+++ b/internal/fusefrontend/node_open_create.go
@@ -2,6 +2,7 @@ package fusefrontend
import (
"context"
+ "os"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
@@ -12,6 +13,30 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
+// mangleOpenCreateFlags is used by Create() and Open() to convert the open flags the user
+// wants to the flags we internally use to open the backing file using Openat().
+// The returned flags always contain O_NOFOLLOW/O_SYMLINK.
+func mangleOpenCreateFlags(flags uint32) (newFlags int) {
+ newFlags = int(flags)
+ // Convert WRONLY to RDWR. We always need read access to do read-modify-write cycles.
+ if (newFlags & syscall.O_ACCMODE) == syscall.O_WRONLY {
+ newFlags = newFlags ^ os.O_WRONLY | os.O_RDWR
+ }
+ // We also cannot open the file in append mode, we need to seek back for RMW
+ newFlags = newFlags &^ os.O_APPEND
+ // O_DIRECT accesses must be aligned in both offset and length. Due to our
+ // crypto header, alignment will be off, even if userspace makes aligned
+ // accesses. Running xfstests generic/013 on ext4 used to trigger lots of
+ // EINVAL errors due to missing alignment. Just fall back to buffered IO.
+ newFlags = newFlags &^ syscallcompat.O_DIRECT
+ // Create and Open are two separate FUSE operations, so O_CREAT should usually not
+ // be part of the Open() flags. Create() will add O_CREAT back itself.
+ newFlags = newFlags &^ syscall.O_CREAT
+ // We always want O_NOFOLLOW/O_SYMLINK to be safe against symlink races
+ newFlags |= syscallcompat.OpenatFlagNofollowSymlink
+ return newFlags
+}
+
// Open - FUSE call. Open already-existing file.
//
// Symlink-safe through Openat().
@@ -23,7 +48,7 @@ func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFl
defer syscall.Close(dirfd)
rn := n.rootNode()
- newFlags := rn.mangleOpenFlags(flags)
+ newFlags := mangleOpenCreateFlags(flags)
// Taking this lock makes sure we don't race openWriteOnlyFile()
rn.openWriteOnlyLock.RLock()
defer rn.openWriteOnlyLock.RUnlock()
@@ -71,7 +96,7 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3
if !rn.args.PreserveOwner {
ctx = nil
}
- newFlags := rn.mangleOpenFlags(flags)
+ newFlags := mangleOpenCreateFlags(flags)
// Handle long file name
ctx2 := toFuseCtx(ctx)
if !rn.args.PlaintextNames && nametransform.IsLongContent(cName) {
diff --git a/internal/fusefrontend/node_prepare_syscall.go b/internal/fusefrontend/node_prepare_syscall.go
index 2a4d6ab..9021350 100644
--- a/internal/fusefrontend/node_prepare_syscall.go
+++ b/internal/fusefrontend/node_prepare_syscall.go
@@ -1,7 +1,6 @@
package fusefrontend
import (
- "sync/atomic"
"syscall"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
@@ -24,7 +23,7 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy
// All filesystem operations go through here, so this is a good place
// to reset the idle marker.
- atomic.StoreUint32(&rn.IsIdle, 0)
+ rn.IsIdle.Store(false)
if n.IsRoot() && rn.isFiltered(child) {
return -1, "", syscall.EPERM
diff --git a/internal/fusefrontend/node_xattr.go b/internal/fusefrontend/node_xattr.go
index 44bc502..1470a2a 100644
--- a/internal/fusefrontend/node_xattr.go
+++ b/internal/fusefrontend/node_xattr.go
@@ -12,9 +12,6 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
-// -1 as uint32
-const minus1 = ^uint32(0)
-
// We store encrypted xattrs under this prefix plus the base64-encoded
// encrypted original name.
var xattrStorePrefix = "user.gocryptfs."
@@ -35,6 +32,10 @@ func isAcl(attr string) bool {
// This function is symlink-safe through Fgetxattr.
func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
rn := n.rootNode()
+ // If -noxattr is enabled, return ENOATTR for all getxattr calls
+ if rn.args.NoXattr {
+ return 0, noSuchAttributeError
+ }
// If we are not mounted with -suid, reading the capability xattr does not
// make a lot of sense, so reject the request and gain a massive speedup.
// See https://github.com/rfjakob/gocryptfs/issues/515 .
@@ -50,13 +51,13 @@ func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32,
var errno syscall.Errno
data, errno = n.getXAttr(attr)
if errno != 0 {
- return minus1, errno
+ return 0, errno
}
} else {
// encrypted user xattr
cAttr, err := rn.encryptXattrName(attr)
if err != nil {
- return minus1, syscall.EIO
+ return 0, syscall.EIO
}
cData, errno := n.getXAttr(cAttr)
if errno != 0 {
@@ -65,15 +66,11 @@ func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32,
data, err = rn.decryptXattrValue(cData)
if err != nil {
tlog.Warn.Printf("GetXAttr: %v", err)
- return minus1, syscall.EIO
+ return 0, syscall.EIO
}
}
- // Caller passes size zero to find out how large their buffer should be
- if len(dest) == 0 {
- return uint32(len(data)), 0
- }
if len(dest) < len(data) {
- return minus1, syscall.ERANGE
+ return uint32(len(data)), syscall.ERANGE
}
l := copy(dest, data)
return uint32(l), 0
@@ -84,6 +81,10 @@ func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32,
// This function is symlink-safe through Fsetxattr.
func (n *Node) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
rn := n.rootNode()
+ // If -noxattr is enabled, fail all setxattr calls
+ if rn.args.NoXattr {
+ return syscall.EOPNOTSUPP
+ }
flags = uint32(filterXattrSetFlags(int(flags)))
// ACLs are passed through without encryption
@@ -109,6 +110,10 @@ func (n *Node) Setxattr(ctx context.Context, attr string, data []byte, flags uin
// This function is symlink-safe through Fremovexattr.
func (n *Node) Removexattr(ctx context.Context, attr string) syscall.Errno {
rn := n.rootNode()
+ // If -noxattr is enabled, fail all removexattr calls
+ if rn.args.NoXattr {
+ return syscall.EOPNOTSUPP
+ }
// ACLs are passed through without encryption
if isAcl(attr) {
@@ -126,11 +131,15 @@ func (n *Node) Removexattr(ctx context.Context, attr string) syscall.Errno {
//
// This function is symlink-safe through Flistxattr.
func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
+ rn := n.rootNode()
+ // If -noxattr is enabled, return zero results for listxattr
+ if rn.args.NoXattr {
+ return 0, 0
+ }
cNames, errno := n.listXAttr()
if errno != 0 {
return 0, errno
}
- rn := n.rootNode()
var buf bytes.Buffer
for _, curName := range cNames {
// ACLs are passed through without encryption
@@ -155,12 +164,8 @@ func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errn
}
buf.WriteString(name + "\000")
}
- // Caller passes size zero to find out how large their buffer should be
- if len(dest) == 0 {
- return uint32(buf.Len()), 0
- }
if buf.Len() > len(dest) {
- return minus1, syscall.ERANGE
+ return uint32(buf.Len()), syscall.ERANGE
}
return uint32(copy(dest, buf.Bytes())), 0
}
diff --git a/internal/fusefrontend/node_xattr_darwin.go b/internal/fusefrontend/node_xattr_darwin.go
index a539847..1d25f3d 100644
--- a/internal/fusefrontend/node_xattr_darwin.go
+++ b/internal/fusefrontend/node_xattr_darwin.go
@@ -11,6 +11,9 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
)
+// On Darwin, ENOATTR is returned when an attribute is not found.
+const noSuchAttributeError = syscall.ENOATTR
+
// On Darwin we have to unset XATTR_NOSECURITY 0x0008
func filterXattrSetFlags(flags int) int {
// See https://opensource.apple.com/source/xnu/xnu-1504.15.3/bsd/sys/xattr.h.auto.html
@@ -26,8 +29,8 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
}
defer syscall.Close(dirfd)
- // O_NONBLOCK to not block on FIFOs.
- fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ // O_NONBLOCK to not block on FIFOs, O_SYMLINK to open the symlink itself (if it is one).
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
if err != nil {
return nil, fs.ToErrno(err)
}
@@ -49,10 +52,10 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
defer syscall.Close(dirfd)
// O_NONBLOCK to not block on FIFOs.
- fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
// Directories cannot be opened read-write. Retry.
if err == syscall.EISDIR {
- fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
}
if err != nil {
fs.ToErrno(err)
@@ -71,10 +74,10 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
defer syscall.Close(dirfd)
// O_NONBLOCK to not block on FIFOs.
- fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
// Directories cannot be opened read-write. Retry.
if err == syscall.EISDIR {
- fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
}
if err != nil {
return fs.ToErrno(err)
@@ -93,7 +96,7 @@ func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
defer syscall.Close(dirfd)
// O_NONBLOCK to not block on FIFOs.
- fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_SYMLINK, 0)
if err != nil {
return nil, fs.ToErrno(err)
}
diff --git a/internal/fusefrontend/node_xattr_linux.go b/internal/fusefrontend/node_xattr_linux.go
index 4a356a5..9964212 100644
--- a/internal/fusefrontend/node_xattr_linux.go
+++ b/internal/fusefrontend/node_xattr_linux.go
@@ -12,6 +12,9 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
)
+// On Linux, ENODATA is returned when an attribute is not found.
+const noSuchAttributeError = syscall.ENODATA
+
func filterXattrSetFlags(flags int) int {
return flags
}
diff --git a/internal/fusefrontend/prepare_syscall_test.go b/internal/fusefrontend/prepare_syscall_test.go
index acddaf3..e2c7d08 100644
--- a/internal/fusefrontend/prepare_syscall_test.go
+++ b/internal/fusefrontend/prepare_syscall_test.go
@@ -1,6 +1,7 @@
package fusefrontend
import (
+ "context"
"strings"
"syscall"
"testing"
@@ -22,13 +23,13 @@ func TestPrepareAtSyscall(t *testing.T) {
rn := newTestFS(args)
out := &fuse.EntryOut{}
- child, errno := rn.Mkdir(nil, "dir1", 0700, out)
+ child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
rn.AddChild("dir1", child, false)
dir1 := toNode(child.Operations())
- _, errno = dir1.Mkdir(nil, "dir2", 0700, out)
+ _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
@@ -43,7 +44,7 @@ func TestPrepareAtSyscall(t *testing.T) {
syscall.Close(dirfd)
// Again, but populate the cache for "" by looking up a non-existing file
- rn.Lookup(nil, "xyz1234", &fuse.EntryOut{})
+ rn.Lookup(context.TODO(), "xyz1234", &fuse.EntryOut{})
dirfd, cName, errno = rn.prepareAtSyscallMyself()
if errno != 0 {
t.Fatal(errno)
@@ -89,7 +90,7 @@ func TestPrepareAtSyscall(t *testing.T) {
syscall.Close(dirfd)
n255 := strings.Repeat("n", 255)
- dir1.Mkdir(nil, n255, 0700, out)
+ dir1.Mkdir(context.TODO(), n255, 0700, out)
dirfd, cName, errno = dir1.prepareAtSyscall(n255)
if errno != 0 {
t.Fatal(errno)
@@ -116,13 +117,13 @@ func TestPrepareAtSyscallPlaintextnames(t *testing.T) {
rn := newTestFS(args)
out := &fuse.EntryOut{}
- child, errno := rn.Mkdir(nil, "dir1", 0700, out)
+ child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
rn.AddChild("dir1", child, false)
dir1 := toNode(child.Operations())
- _, errno = dir1.Mkdir(nil, "dir2", 0700, out)
+ _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index ac814ad..38d070d 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -1,9 +1,9 @@
package fusefrontend
import (
- "os"
"strings"
"sync"
+ "sync/atomic"
"syscall"
"time"
@@ -44,7 +44,7 @@ type RootNode struct {
// (uint32 so that it can be reset with CompareAndSwapUint32).
// When -idle was used when mounting, idleMonitor() sets it to 1
// periodically.
- IsIdle uint32
+ IsIdle atomic.Bool
// dirCache caches directory fds
dirCache dirCache
// inoMap translates inode numbers from different devices to unique inode
@@ -55,7 +55,7 @@ type RootNode struct {
// This makes each directory entry unique (even hard links),
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back.
- gen uint64
+ gen atomic.Uint64
// quirks is a bitmap that enables workaround for quirks in the filesystem
// backing the cipherdir
quirks uint64
@@ -90,6 +90,12 @@ func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTrans
dirCache: dirCache{ivLen: ivLen},
quirks: syscallcompat.DetectQuirks(args.Cipherdir),
}
+ // Suppress the message if the user has already specified -noprealloc
+ if rn.quirks&syscallcompat.QuirkBtrfsBrokenFalloc != 0 && !args.NoPrealloc {
+ syscallcompat.LogQuirk("Btrfs detected, forcing -noprealloc. " +
+ "Use \"chattr +C\" on the backing directory to enable NOCOW and allow preallocation. " +
+ "See https://github.com/rfjakob/gocryptfs/issues/395 for details.")
+ }
if statErr == nil {
rn.inoMap.TranslateStat(&st)
rn.rootIno = st.Ino
@@ -103,30 +109,6 @@ func (rn *RootNode) AfterUnmount() {
rn.dirCache.stats()
}
-// mangleOpenFlags is used by Create() and Open() to convert the open flags the user
-// wants to the flags we internally use to open the backing file.
-// The returned flags always contain O_NOFOLLOW.
-func (rn *RootNode) mangleOpenFlags(flags uint32) (newFlags int) {
- newFlags = int(flags)
- // Convert WRONLY to RDWR. We always need read access to do read-modify-write cycles.
- if (newFlags & syscall.O_ACCMODE) == syscall.O_WRONLY {
- newFlags = newFlags ^ os.O_WRONLY | os.O_RDWR
- }
- // We also cannot open the file in append mode, we need to seek back for RMW
- newFlags = newFlags &^ os.O_APPEND
- // O_DIRECT accesses must be aligned in both offset and length. Due to our
- // crypto header, alignment will be off, even if userspace makes aligned
- // accesses. Running xfstests generic/013 on ext4 used to trigger lots of
- // EINVAL errors due to missing alignment. Just fall back to buffered IO.
- newFlags = newFlags &^ syscallcompat.O_DIRECT
- // Create and Open are two separate FUSE operations, so O_CREAT should not
- // be part of the open flags.
- newFlags = newFlags &^ syscall.O_CREAT
- // We always want O_NOFOLLOW to be safe against symlink races
- newFlags |= syscall.O_NOFOLLOW
- return newFlags
-}
-
// reportMitigatedCorruption is used to report a corruption that was transparently
// mitigated and did not return an error to the user. Pass the name of the corrupt
// item (filename for OpenDir(), xattr name for ListXAttr() etc).
diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go
index 86c87a7..7d8e32e 100644
--- a/internal/fusefrontend/xattr_unit_test.go
+++ b/internal/fusefrontend/xattr_unit_test.go
@@ -21,10 +21,10 @@ func newTestFS(args Args) *RootNode {
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
n := nametransform.New(cCore.EMECipher, true, 0, true, nil, false)
rn := NewRootNode(args, cEnc, n)
- oneSec := time.Second
+ oneSecond := time.Second
options := &fs.Options{
- EntryTimeout: &oneSec,
- AttrTimeout: &oneSec,
+ EntryTimeout: &oneSecond,
+ AttrTimeout: &oneSecond,
}
fs.NewNodeFS(rn, options)
return rn
diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go
index 0faadfa..1cb4b80 100644
--- a/internal/fusefrontend_reverse/excluder.go
+++ b/internal/fusefrontend_reverse/excluder.go
@@ -1,7 +1,6 @@
package fusefrontend_reverse
import (
- "io/ioutil"
"log"
"os"
"strings"
@@ -50,7 +49,7 @@ func getExclusionPatterns(args fusefrontend.Args) []string {
// getLines reads a file and splits it into lines
func getLines(file string) ([]string, error) {
- buffer, err := ioutil.ReadFile(file)
+ buffer, err := os.ReadFile(file)
if err != nil {
return nil, err
}
diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go
index bb041ce..b44ddce 100644
--- a/internal/fusefrontend_reverse/excluder_test.go
+++ b/internal/fusefrontend_reverse/excluder_test.go
@@ -1,7 +1,6 @@
package fusefrontend_reverse
import (
- "io/ioutil"
"os"
"reflect"
"testing"
@@ -23,7 +22,7 @@ func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) {
}
func TestShouldReadExcludePatternsFromFiles(t *testing.T) {
- tmpfile1, err := ioutil.TempFile("", "excludetest")
+ tmpfile1, err := os.CreateTemp("", "excludetest")
if err != nil {
t.Fatal(err)
}
@@ -31,7 +30,7 @@ func TestShouldReadExcludePatternsFromFiles(t *testing.T) {
defer os.Remove(exclude1)
defer tmpfile1.Close()
- tmpfile2, err := ioutil.TempFile("", "excludetest")
+ tmpfile2, err := os.CreateTemp("", "excludetest")
if err != nil {
t.Fatal(err)
}
diff --git a/internal/fusefrontend_reverse/node_api_check.go b/internal/fusefrontend_reverse/node_api_check.go
index f8ec9ce..eb608f9 100644
--- a/internal/fusefrontend_reverse/node_api_check.go
+++ b/internal/fusefrontend_reverse/node_api_check.go
@@ -11,14 +11,8 @@ var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeReadlinker)((*Node)(nil))
var _ = (fs.NodeOpener)((*Node)(nil))
var _ = (fs.NodeStatfser)((*Node)(nil))
-
-/*
-TODO but low prio. reverse mode in gocryptfs v1 did not have xattr support
-either.
-
var _ = (fs.NodeGetxattrer)((*Node)(nil))
var _ = (fs.NodeListxattrer)((*Node)(nil))
-*/
/* Not needed
var _ = (fs.NodeOpendirer)((*Node)(nil))
diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go
index 30361bc..f733689 100644
--- a/internal/fusefrontend_reverse/node_helpers.go
+++ b/internal/fusefrontend_reverse/node_helpers.go
@@ -24,7 +24,6 @@ const (
// * base64(192 bytes) = 256 bytes (over 255!)
// But the PKCS#7 padding is at least one byte. This means we can only use
// 175 bytes for the file name.
- shortNameMax = 175
)
// translateSize translates the ciphertext size in `out` into plaintext size.
@@ -135,7 +134,7 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
if errno != 0 {
return
}
- if rn.isExcludedPlain(filepath.Join(d.cPath, pName)) {
+ if rn.isExcludedPlain(filepath.Join(d.pPath, pName)) {
errno = syscall.EPERM
return
}
@@ -175,7 +174,7 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
errno = fs.ToErrno(err)
return
}
- content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
+ content := rn.deriveDirIV(d.cPath)
var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 {
diff --git a/internal/fusefrontend_reverse/node_xattr.go b/internal/fusefrontend_reverse/node_xattr.go
new file mode 100644
index 0000000..b940339
--- /dev/null
+++ b/internal/fusefrontend_reverse/node_xattr.go
@@ -0,0 +1,89 @@
+// Package fusefrontend_reverse interfaces directly with the go-fuse library.
+package fusefrontend_reverse
+
+import (
+ "bytes"
+ "context"
+ "syscall"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/pathiv"
+)
+
+// We store encrypted xattrs under this prefix plus the base64-encoded
+// encrypted original name.
+var xattrStorePrefix = "user.gocryptfs."
+
+// isAcl returns true if the attribute name is for storing ACLs
+//
+// ACLs are passed through without encryption
+func isAcl(attr string) bool {
+ return attr == "system.posix_acl_access" || attr == "system.posix_acl_default"
+}
+
+// GetXAttr - FUSE call. Reads the value of extended attribute "attr".
+//
+// This function is symlink-safe through Fgetxattr.
+func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
+ rn := n.rootNode()
+ // If -noxattr is enabled, return ENOATTR for all getxattr calls
+ if rn.args.NoXattr {
+ return 0, noSuchAttributeError
+ }
+ var data []byte
+ // ACLs are passed through without encryption
+ if isAcl(attr) {
+ var errno syscall.Errno
+ data, errno = n.getXAttr(attr)
+ if errno != 0 {
+ return 0, errno
+ }
+ } else {
+ pAttr, err := rn.decryptXattrName(attr)
+ if err != nil {
+ return 0, noSuchAttributeError
+ }
+ pData, errno := n.getXAttr(pAttr)
+ if errno != 0 {
+ return 0, errno
+ }
+ nonce := pathiv.Derive(n.Path()+"\000"+attr, pathiv.PurposeXattrIV)
+ data = rn.encryptXattrValue(pData, nonce)
+ }
+ if len(dest) < len(data) {
+ return uint32(len(data)), syscall.ERANGE
+ }
+ l := copy(dest, data)
+ return uint32(l), 0
+}
+
+// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
+//
+// This function is symlink-safe through Flistxattr.
+func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
+ rn := n.rootNode()
+ // If -noxattr is enabled, return zero results for listxattr
+ if rn.args.NoXattr {
+ return 0, 0
+ }
+ pNames, errno := n.listXAttr()
+ if errno != 0 {
+ return 0, errno
+ }
+ var buf bytes.Buffer
+ for _, pName := range pNames {
+ // ACLs are passed through without encryption
+ if isAcl(pName) {
+ buf.WriteString(pName + "\000")
+ continue
+ }
+ cName, err := rn.encryptXattrName(pName)
+ if err != nil {
+ continue
+ }
+ buf.WriteString(cName + "\000")
+ }
+ if buf.Len() > len(dest) {
+ return uint32(buf.Len()), syscall.ERANGE
+ }
+ return uint32(copy(dest, buf.Bytes())), 0
+}
diff --git a/internal/fusefrontend_reverse/node_xattr_darwin.go b/internal/fusefrontend_reverse/node_xattr_darwin.go
new file mode 100644
index 0000000..6816a18
--- /dev/null
+++ b/internal/fusefrontend_reverse/node_xattr_darwin.go
@@ -0,0 +1,55 @@
+package fusefrontend_reverse
+
+import (
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+)
+
+// On Darwin, ENOATTR is returned when an attribute is not found.
+const noSuchAttributeError = syscall.ENOATTR
+
+func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
+ d, errno := n.prepareAtSyscall("")
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(d.dirfd)
+
+ // O_NONBLOCK to not block on FIFOs.
+ fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ defer syscall.Close(fd)
+
+ cData, err := syscallcompat.Fgetxattr(fd, cAttr)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+
+ return cData, 0
+}
+
+func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
+ d, errno := n.prepareAtSyscall("")
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(d.dirfd)
+
+ // O_NONBLOCK to not block on FIFOs.
+ fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ defer syscall.Close(fd)
+
+ pNames, err := syscallcompat.Flistxattr(fd)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ return pNames, 0
+}
diff --git a/internal/fusefrontend_reverse/node_xattr_linux.go b/internal/fusefrontend_reverse/node_xattr_linux.go
new file mode 100644
index 0000000..3c574f5
--- /dev/null
+++ b/internal/fusefrontend_reverse/node_xattr_linux.go
@@ -0,0 +1,43 @@
+package fusefrontend_reverse
+
+import (
+ "fmt"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+)
+
+// On Linux, ENODATA is returned when an attribute is not found.
+const noSuchAttributeError = syscall.ENODATA
+
+func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
+ d, errno := n.prepareAtSyscall("")
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(d.dirfd)
+
+ procPath := fmt.Sprintf("/proc/self/fd/%d/%s", d.dirfd, d.pName)
+ pData, err := syscallcompat.Lgetxattr(procPath, cAttr)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ return pData, 0
+}
+
+func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
+ d, errno := n.prepareAtSyscall("")
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(d.dirfd)
+
+ procPath := fmt.Sprintf("/proc/self/fd/%d/%s", d.dirfd, d.pName)
+ pNames, err := syscallcompat.Llistxattr(procPath)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ return pNames, 0
+}
diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go
index cb04151..1a668af 100644
--- a/internal/fusefrontend_reverse/root_node.go
+++ b/internal/fusefrontend_reverse/root_node.go
@@ -13,6 +13,7 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
+ "github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
"github.com/rfjakob/gocryptfs/v2/internal/fusefrontend"
@@ -21,7 +22,7 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
- "github.com/sabhiram/go-gitignore"
+ ignore "github.com/sabhiram/go-gitignore"
)
// RootNode is the root directory in a `gocryptfs -reverse` mount
@@ -50,7 +51,7 @@ type RootNode struct {
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back,
// like this one: https://github.com/rfjakob/gocryptfs/issues/802
- gen uint64
+ gen atomic.Uint64
// rootIno is the inode number that we report for the root node on mount
rootIno uint64
}
@@ -136,7 +137,8 @@ func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (p
// excluded (used when -exclude is passed by the user).
func (rn *RootNode) isExcludedPlain(pPath string) bool {
// root dir can't be excluded
- if pPath == "" {
+ // Don't exclude gocryptfs.conf too
+ if pPath == "" || pPath == configfile.ConfReverseName {
return false
}
return rn.excluder != nil && rn.excluder.MatchesPath(pPath)
@@ -175,10 +177,45 @@ func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr {
Ino: ino,
// Make each directory entry a unique node by using a unique generation
// value. Also see the comment at RootNode.gen for details.
- Gen: atomic.AddUint64(&rn.gen, 1),
+ Gen: rn.gen.Add(1),
}
}
func (rn *RootNode) RootIno() uint64 {
return rn.rootIno
}
+
+// encryptXattrValue encrypts the xattr value "data".
+// The data is encrypted like a file content block, but without binding it to
+// a file location (block number and file id are set to zero).
+// Special case: an empty value is encrypted to an empty value.
+func (rn *RootNode) encryptXattrValue(data []byte, nonce []byte) (cData []byte) {
+ if len(data) == 0 {
+ return []byte{}
+ }
+ return rn.contentEnc.EncryptBlockNonce(data, 0, nil, nonce)
+}
+
+// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf"
+func (rn *RootNode) encryptXattrName(attr string) (string, error) {
+ // xattr names are encrypted like file names, but with a fixed IV.
+ cAttr, err := rn.nameTransform.EncryptXattrName(attr)
+ if err != nil {
+ return "", err
+ }
+ return xattrStorePrefix + cAttr, nil
+}
+
+func (rn *RootNode) decryptXattrName(cAttr string) (attr string, err error) {
+ // Reject anything that does not start with "user.gocryptfs."
+ if !strings.HasPrefix(cAttr, xattrStorePrefix) {
+ return "", syscall.EINVAL
+ }
+ // Strip "user.gocryptfs." prefix
+ cAttr = cAttr[len(xattrStorePrefix):]
+ attr, err = rn.nameTransform.DecryptXattrName(cAttr)
+ if err != nil {
+ return "", err
+ }
+ return attr, nil
+}
diff --git a/internal/fusefrontend_reverse/virtualnode.go b/internal/fusefrontend_reverse/virtualnode.go
index 732564a..95e71ab 100644
--- a/internal/fusefrontend_reverse/virtualnode.go
+++ b/internal/fusefrontend_reverse/virtualnode.go
@@ -100,6 +100,15 @@ func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, ino
st.Nlink = 1
var a fuse.Attr
a.FromStat(st)
+ // With inode number reuse and hard links, we could have returned
+ // wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+ // (https://github.com/rfjakob/gocryptfs/issues/802).
+ //
+ // Now that this is fixed, ensure that rsync and similar tools pick up the new
+ // correct files by advancing mtime and ctime by 10 seconds, which should be more
+ // than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+ a.Mtime += 10
+ a.Ctime += 10
if rn.args.ForceOwner != nil {
a.Owner = *rn.args.ForceOwner
}
diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go
index b4dbf27..5749202 100644
--- a/internal/inomap/inomap.go
+++ b/internal/inomap/inomap.go
@@ -45,7 +45,7 @@ type InoMap struct {
// spill is used once the namespaces map is full
spillMap map[QIno]uint64
// spillNext is the next free inode number in the spill map
- spillNext uint64
+ spillNext atomic.Uint64
}
// New returns a new InoMap.
@@ -57,8 +57,9 @@ func New(rootDev uint64) *InoMap {
namespaceMap: make(map[namespaceData]uint16),
namespaceNext: 0,
spillMap: make(map[QIno]uint64),
- spillNext: spillSpaceStart,
}
+ m.spillNext.Store(spillSpaceStart)
+
if rootDev > 0 {
// Reserve namespace 0 for rootDev
m.namespaceMap[namespaceData{rootDev, 0}] = 0
@@ -74,10 +75,10 @@ var spillWarn sync.Once
// Reverse mode NextSpillIno() for gocryptfs.longname.*.name files where a stable
// mapping is not needed.
func (m *InoMap) NextSpillIno() (out uint64) {
- if m.spillNext == math.MaxUint64 {
- log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
+ if m.spillNext.Load() == math.MaxUint64 {
+ log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext.Load())
}
- return atomic.AddUint64(&m.spillNext, 1) - 1
+ return m.spillNext.Add(1) - 1
}
func (m *InoMap) spill(in QIno) (out uint64) {
diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go
index 7929c40..5dd4940 100644
--- a/internal/nametransform/diriv.go
+++ b/internal/nametransform/diriv.go
@@ -67,7 +67,7 @@ func fdReadDirIV(fd *os.File) (iv []byte, err error) {
func WriteDirIVAt(dirfd int) error {
iv := cryptocore.RandBytes(DirIVLen)
// 0400 permissions: gocryptfs.diriv should never be modified after creation.
- // Don't use "ioutil.WriteFile", it causes trouble on NFS:
+ // Don't use "os.WriteFile", it causes trouble on NFS:
// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed
fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms)
if err != nil {
diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go
index 3313a7c..0389c95 100644
--- a/internal/nametransform/names.go
+++ b/internal/nametransform/names.go
@@ -7,9 +7,12 @@ import (
"errors"
"math"
"path/filepath"
+ "runtime"
"strings"
"syscall"
+ "golang.org/x/text/unicode/norm"
+
"github.com/rfjakob/eme"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
@@ -32,6 +35,12 @@ type NameTransform struct {
// Patterns to bypass decryption
badnamePatterns []string
deterministicNames bool
+ // Convert filenames to NFC before encrypting,
+ // and to NFD when decrypting.
+ // For MacOS compatibility.
+ // Automatically enabled on MacOS, off otherwise,
+ // except in tests (see nfc_test.go).
+ nfd2nfc bool
}
// New returns a new NameTransform instance.
@@ -55,34 +64,44 @@ func New(e *eme.EMECipher, longNames bool, longNameMax uint8, raw64 bool, badnam
effectiveLongNameMax = int(longNameMax)
}
}
+ nfd2nfc := runtime.GOOS == "darwin"
+ if nfd2nfc {
+ tlog.Info.Printf("Running on MacOS, enabling Unicode normalization")
+ }
return &NameTransform{
emeCipher: e,
longNameMax: effectiveLongNameMax,
B64: b64,
badnamePatterns: badname,
deterministicNames: deterministicNames,
+ nfd2nfc: nfd2nfc,
}
}
// DecryptName calls decryptName to try and decrypt a base64-encoded encrypted
// filename "cipherName", and failing that checks if it can be bypassed
-func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
- res, err := n.decryptName(cipherName, iv)
+func (n *NameTransform) DecryptName(cipherName string, iv []byte) (plainName string, err error) {
+ plainName, err = n.decryptName(cipherName, iv)
if err != nil && n.HaveBadnamePatterns() {
- res, err = n.decryptBadname(cipherName, iv)
+ plainName, err = n.decryptBadname(cipherName, iv)
}
if err != nil {
return "", err
}
- if err := IsValidName(res); err != nil {
+ if err := IsValidName(plainName); err != nil {
tlog.Warn.Printf("DecryptName %q: invalid name after decryption: %v", cipherName, err)
return "", syscall.EBADMSG
}
- return res, err
+ if n.nfd2nfc {
+ // MacOS expects file names in NFD form. Present them as NFD.
+ // They are converted back to NFC in EncryptName.
+ plainName = norm.NFD.String(plainName)
+ }
+ return plainName, err
}
-// decryptName decrypts a base64-encoded encrypted filename "cipherName" using the
-// initialization vector "iv".
+// decryptName decrypts a base64-encoded encrypted file- or xattr-name "cipherName"
+// using the initialization vector "iv".
func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error) {
// From https://pkg.go.dev/encoding/base64#Encoding.Strict :
// > Note that the input is still malleable, as new line characters
@@ -126,6 +145,16 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
tlog.Warn.Printf("EncryptName %q: invalid plainName: %v", plainName, err)
return "", syscall.EBADMSG
}
+ if n.nfd2nfc {
+ // MacOS GUI apps expect Unicode in NFD form.
+ // But MacOS CLI apps, Linux and Windows use NFC form.
+ // We normalize to NFC for two reasons:
+ // 1) Make sharing gocryptfs filesystems from MacOS to other systems
+ // less painful
+ // 2) Enable DecryptName to normalize to NFD, which works for both
+ // GUI and CLI on MacOS.
+ plainName = norm.NFC.String(plainName)
+ }
return n.encryptName(plainName, iv), nil
}
diff --git a/internal/nametransform/nfc_test.go b/internal/nametransform/nfc_test.go
new file mode 100644
index 0000000..aad1d7d
--- /dev/null
+++ b/internal/nametransform/nfc_test.go
@@ -0,0 +1,29 @@
+package nametransform
+
+import (
+ "strconv"
+ "testing"
+
+ "golang.org/x/text/unicode/norm"
+)
+
+func TestNFD2NFC(t *testing.T) {
+ n := newLognamesTestInstance(NameMax)
+ n.nfd2nfc = true
+ iv := make([]byte, DirIVLen)
+ srcNFC := "Österreich Café"
+ srcNFD := norm.NFD.String(srcNFC)
+
+ // cipherName should get normalized to NFC
+ cipherName, _ := n.EncryptName(srcNFD, iv)
+ // Decrypt without changing normalization
+ decryptedRaw, _ := n.decryptName(cipherName, iv)
+ if srcNFC != decryptedRaw {
+ t.Errorf("want %s have %s", strconv.QuoteToASCII(srcNFC), strconv.QuoteToASCII(decryptedRaw))
+ }
+ // Decrypt with normalizing to NFD
+ decrypted, _ := n.DecryptName(cipherName, iv)
+ if srcNFD != decrypted {
+ t.Errorf("want %s have %s", strconv.QuoteToASCII(srcNFD), strconv.QuoteToASCII(decrypted))
+ }
+}
diff --git a/internal/nametransform/pad16.go b/internal/nametransform/pad16.go
index 833be0e..2c2466a 100644
--- a/internal/nametransform/pad16.go
+++ b/internal/nametransform/pad16.go
@@ -32,10 +32,10 @@ func pad16(orig []byte) (padded []byte) {
func unPad16(padded []byte) ([]byte, error) {
oldLen := len(padded)
if oldLen == 0 {
- return nil, errors.New("Empty input")
+ return nil, errors.New("empty input")
}
if oldLen%aes.BlockSize != 0 {
- return nil, errors.New("Unaligned size")
+ return nil, errors.New("unaligned size")
}
// The last byte is always a padding byte
padByte := padded[oldLen-1]
@@ -43,20 +43,20 @@ func unPad16(padded []byte) ([]byte, error) {
padLen := int(padByte)
// Padding must be at least 1 byte
if padLen == 0 {
- return nil, errors.New("Padding cannot be zero-length")
+ return nil, errors.New("padding cannot be zero-length")
}
// Padding more than 16 bytes make no sense
if padLen > aes.BlockSize {
- return nil, fmt.Errorf("Padding too long, padLen=%d > 16", padLen)
+ return nil, fmt.Errorf("padding too long, padLen=%d > 16", padLen)
}
// Padding cannot be as long as (or longer than) the whole string,
if padLen >= oldLen {
- return nil, fmt.Errorf("Padding too long, oldLen=%d >= padLen=%d", oldLen, padLen)
+ return nil, fmt.Errorf("padding too long, oldLen=%d >= padLen=%d", oldLen, padLen)
}
// All padding bytes must be identical
for i := oldLen - padLen; i < oldLen; i++ {
if padded[i] != padByte {
- return nil, fmt.Errorf("Padding byte at i=%d is invalid", i)
+ return nil, fmt.Errorf("padding byte at i=%d is invalid", i)
}
}
newLen := oldLen - padLen
diff --git a/internal/openfiletable/open_file_table.go b/internal/openfiletable/open_file_table.go
index ce8df76..420d070 100644
--- a/internal/openfiletable/open_file_table.go
+++ b/internal/openfiletable/open_file_table.go
@@ -29,7 +29,7 @@ type table struct {
// The variable is accessed without holding any locks so atomic operations
// must be used. It must be the first element of the struct to guarantee
// 64-bit alignment.
- writeOpCount uint64
+ writeOpCount atomic.Uint64
// Protects map access
sync.Mutex
// Table entries
@@ -85,13 +85,13 @@ type countingMutex struct {
func (c *countingMutex) Lock() {
c.RWMutex.Lock()
- atomic.AddUint64(&t.writeOpCount, 1)
+ t.writeOpCount.Add(1)
}
// WriteOpCount returns the write lock counter value. This value is incremented
// each time writeLock.Lock() on a file table entry is called.
func WriteOpCount() uint64 {
- return atomic.LoadUint64(&t.writeOpCount)
+ return t.writeOpCount.Load()
}
// CountOpenFiles returns how many entries are currently in the table
diff --git a/internal/pathiv/pathiv.go b/internal/pathiv/pathiv.go
index 48f8426..11a7189 100644
--- a/internal/pathiv/pathiv.go
+++ b/internal/pathiv/pathiv.go
@@ -20,6 +20,8 @@ const (
PurposeSymlinkIV Purpose = "SYMLINKIV"
// PurposeBlock0IV means the value will be used as the IV of ciphertext block #0.
PurposeBlock0IV Purpose = "BLOCK0IV"
+ // PurposeXattrIV means the value will be used as a xattr IV
+ PurposeXattrIV Purpose = "XATTRIV"
)
// Derive derives an IV from an encrypted path by hashing it with sha256
diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go
index 582a104..9193ae8 100644
--- a/internal/readpassword/read.go
+++ b/internal/readpassword/read.go
@@ -58,7 +58,7 @@ func Twice(extpass []string, passfile []string) ([]byte, error) {
return nil, err
}
if !bytes.Equal(p1, p2) {
- return nil, fmt.Errorf("Passwords do not match")
+ return nil, fmt.Errorf("passwords do not match")
}
// Wipe the password duplicate from memory
for i := range p2 {
@@ -71,15 +71,15 @@ func Twice(extpass []string, passfile []string) ([]byte, error) {
// Exits on read error or empty result.
func readPasswordTerminal(prompt string) ([]byte, error) {
fd := int(os.Stdin.Fd())
- fmt.Fprintf(os.Stderr, prompt)
+ fmt.Fprint(os.Stderr, prompt)
// term.ReadPassword removes the trailing newline
p, err := term.ReadPassword(fd)
if err != nil {
- return nil, fmt.Errorf("Could not read password from terminal: %v\n", err)
+ return nil, fmt.Errorf("could not read password from terminal: %v", err)
}
fmt.Fprintf(os.Stderr, "\n")
if len(p) == 0 {
- return nil, fmt.Errorf("Password is empty")
+ return nil, fmt.Errorf("password is empty")
}
return p, nil
}
@@ -100,7 +100,7 @@ func readPasswordStdin(prompt string) ([]byte, error) {
return nil, err
}
if len(p) == 0 {
- return nil, fmt.Errorf("Got empty %s from stdin", prompt)
+ return nil, fmt.Errorf("got empty %s from stdin", prompt)
}
return p, nil
}
diff --git a/internal/siv_aead/correctness_test.go b/internal/siv_aead/correctness_test.go
index 0653e26..7be97bc 100644
--- a/internal/siv_aead/correctness_test.go
+++ b/internal/siv_aead/correctness_test.go
@@ -50,7 +50,7 @@ func TestK32(t *testing.T) {
expectedResult, _ := hex.DecodeString(
"02020202020202020202020202020202ad7a4010649a84d8c1dd5f752e935eed57d45b8b10008f3834")
if !bytes.Equal(aResult, expectedResult) {
- t.Errorf(hex.EncodeToString(aResult))
+ t.Error(hex.EncodeToString(aResult))
}
// Verify overhead
overhead := len(aResult) - len(plaintext) - len(nonce)
@@ -108,7 +108,7 @@ func TestK64(t *testing.T) {
expectedResult, _ := hex.DecodeString(
"02020202020202020202020202020202317b316f67c3ad336c01c9a01b4c5e552ba89e966bc4c1ade1")
if !bytes.Equal(aResult, expectedResult) {
- t.Errorf(hex.EncodeToString(aResult))
+ t.Error(hex.EncodeToString(aResult))
}
// Verify overhead
overhead := len(aResult) - len(plaintext) - len(nonce)
diff --git a/internal/speed/cpuinfo.go b/internal/speed/cpuinfo.go
index df3177d..636a4f0 100644
--- a/internal/speed/cpuinfo.go
+++ b/internal/speed/cpuinfo.go
@@ -1,7 +1,7 @@
package speed
import (
- "io/ioutil"
+ "io"
"os"
"runtime"
"strings"
@@ -33,7 +33,7 @@ func cpuModelName() string {
if err != nil {
return ""
}
- content, err := ioutil.ReadAll(f)
+ content, err := io.ReadAll(f)
if err != nil {
return ""
}
diff --git a/internal/stupidgcm/chacha.go b/internal/stupidgcm/chacha.go
index de0c2e8..c500ea5 100644
--- a/internal/stupidgcm/chacha.go
+++ b/internal/stupidgcm/chacha.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/chacha_test.go b/internal/stupidgcm/chacha_test.go
index 542ff15..356c813 100644
--- a/internal/stupidgcm/chacha_test.go
+++ b/internal/stupidgcm/chacha_test.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/common.go b/internal/stupidgcm/common.go
index d88dc62..03698b9 100644
--- a/internal/stupidgcm/common.go
+++ b/internal/stupidgcm/common.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/common_test.go b/internal/stupidgcm/common_test.go
index 7f38e90..633f279 100644
--- a/internal/stupidgcm/common_test.go
+++ b/internal/stupidgcm/common_test.go
@@ -1,5 +1,4 @@
//go:build cgo && !without_openssl
-// +build cgo,!without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/gcm.go b/internal/stupidgcm/gcm.go
index 2e5aac4..274d3df 100644
--- a/internal/stupidgcm/gcm.go
+++ b/internal/stupidgcm/gcm.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/gcm_test.go b/internal/stupidgcm/gcm_test.go
index c730a87..041dcab 100644
--- a/internal/stupidgcm/gcm_test.go
+++ b/internal/stupidgcm/gcm_test.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
// We compare against Go's built-in GCM implementation. Since stupidgcm only
// supports 128-bit IVs and Go only supports that from 1.5 onward, we cannot
diff --git a/internal/stupidgcm/locking.go b/internal/stupidgcm/locking.go
index 04cf232..00cc361 100644
--- a/internal/stupidgcm/locking.go
+++ b/internal/stupidgcm/locking.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/openssl.go b/internal/stupidgcm/openssl.go
index 8c950f8..3a72f9c 100644
--- a/internal/stupidgcm/openssl.go
+++ b/internal/stupidgcm/openssl.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/openssl_aead.c b/internal/stupidgcm/openssl_aead.c
index e02466f..a7f4f59 100644
--- a/internal/stupidgcm/openssl_aead.c
+++ b/internal/stupidgcm/openssl_aead.c
@@ -1,4 +1,4 @@
-// +build !without_openssl
+//go:build cgo && !without_openssl
#include "openssl_aead.h"
#include <openssl/evp.h>
diff --git a/internal/stupidgcm/without_openssl.go b/internal/stupidgcm/without_openssl.go
index fcef793..c59ebe6 100644
--- a/internal/stupidgcm/without_openssl.go
+++ b/internal/stupidgcm/without_openssl.go
@@ -1,5 +1,4 @@
-//go:build without_openssl
-// +build without_openssl
+//go:build !cgo || without_openssl
package stupidgcm
diff --git a/internal/stupidgcm/xchacha.go b/internal/stupidgcm/xchacha.go
index 3c121ba..cfd69d7 100644
--- a/internal/stupidgcm/xchacha.go
+++ b/internal/stupidgcm/xchacha.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@@ -7,7 +6,7 @@
//
// Copied from
// https://github.com/golang/crypto/blob/32db794688a5a24a23a43f2a984cecd5b3d8da58/chacha20poly1305/xchacha20poly1305.go
-// and adapted for stupidgcm by @rfjakob.
+// and adapted to use OpenSSL ChaCha20 (see chacha.go) instaed of stdlib by @rfjakob.
package stupidgcm
diff --git a/internal/stupidgcm/xchacha_test.go b/internal/stupidgcm/xchacha_test.go
index 676a023..a448f20 100644
--- a/internal/stupidgcm/xchacha_test.go
+++ b/internal/stupidgcm/xchacha_test.go
@@ -1,5 +1,4 @@
-//go:build !without_openssl
-// +build !without_openssl
+//go:build cgo && !without_openssl
package stupidgcm
diff --git a/internal/syscallcompat/asuser.go b/internal/syscallcompat/asuser.go
new file mode 100644
index 0000000..0c083ec
--- /dev/null
+++ b/internal/syscallcompat/asuser.go
@@ -0,0 +1,59 @@
+package syscallcompat
+
+import (
+ "golang.org/x/sys/unix"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// OpenatUser runs the Openat syscall in the context of a different user.
+// It switches the current thread to the new user, performs the syscall,
+// and switches back.
+//
+// If `context` is nil, this function behaves like ordinary Openat (no
+// user switching).
+func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
+ f := func() (int, error) {
+ return Openat(dirfd, path, flags, mode)
+ }
+ return asUser(f, context)
+}
+
+// MknodatUser runs the Mknodat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mknodat.
+//
+// See OpenatUser() for how this works.
+func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := Mknodat(dirfd, path, mode, dev)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Symlinkat.
+//
+// See OpenatUser() for how this works.
+func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Symlinkat(oldpath, newdirfd, newpath)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// MkdiratUser runs the Mkdirat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mkdirat.
+//
+// See OpenatUser() for how this works.
+func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Mkdirat(dirfd, path, mode)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
diff --git a/internal/syscallcompat/asuser_darwin.go b/internal/syscallcompat/asuser_darwin.go
new file mode 100644
index 0000000..5da2782
--- /dev/null
+++ b/internal/syscallcompat/asuser_darwin.go
@@ -0,0 +1,46 @@
+package syscallcompat
+
+import (
+ "runtime"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
+ if err != nil {
+ return -1, err
+ }
+
+ const (
+ // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
+ // revert permissions to the process credentials.
+ KAUTH_UID_NONE = ^uint32(0) - 100
+ KAUTH_GID_NONE = ^uint32(0) - 100
+ )
+
+ defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
+
+ return f()
+}
+
+// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
+func pthread_setugid_np(uid uint32, gid uint32) (err error) {
+ _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/asuser_linux.go b/internal/syscallcompat/asuser_linux.go
new file mode 100644
index 0000000..39e3ff2
--- /dev/null
+++ b/internal/syscallcompat/asuser_linux.go
@@ -0,0 +1,80 @@
+package syscallcompat
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
+ // https://go-review.googlesource.com/c/go/+/210639 )
+ // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
+ // is exactly what we not want.
+ //
+ // And unix.{Setgroups,Setregid,Setreuid} also changed to this behavoir in
+ // v0.1.0 (commit d0df966e6959f00dc1c74363e537872647352d51 ,
+ // https://go-review.googlesource.com/c/sys/+/428174 ), so we use
+ // our own syscall wrappers.
+
+ err := Setgroups(getSupplementaryGroups(context.Pid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetgroupsPanic(nil)
+
+ err = Setregid(-1, int(context.Owner.Gid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetregidPanic(-1, 0)
+
+ err = Setreuid(-1, int(context.Owner.Uid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetreuidPanic(-1, 0)
+
+ return f()
+}
+
+func getSupplementaryGroups(pid uint32) (gids []int) {
+ procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
+ blob, err := os.ReadFile(procPath)
+ if err != nil {
+ return nil
+ }
+
+ lines := strings.Split(string(blob), "\n")
+ for _, line := range lines {
+ if strings.HasPrefix(line, "Groups:") {
+ f := strings.Fields(line[7:])
+ gids = make([]int, len(f))
+ for i := range gids {
+ val, err := strconv.ParseInt(f[i], 10, 32)
+ if err != nil {
+ return nil
+ }
+ gids[i] = int(val)
+ }
+ return gids
+ }
+ }
+
+ return nil
+}
diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go
index eb670d6..c9e6a99 100644
--- a/internal/syscallcompat/getdents_test.go
+++ b/internal/syscallcompat/getdents_test.go
@@ -4,7 +4,6 @@
package syscallcompat
import (
- "io/ioutil"
"os"
"runtime"
"strings"
@@ -49,13 +48,13 @@ func testGetdents(t *testing.T) {
getdentsUnderTest = emulateGetdents
}
// Fill a directory with filenames of length 1 ... 255
- testDir, err := ioutil.TempDir(tmpDir, "TestGetdents")
+ testDir, err := os.MkdirTemp(tmpDir, "TestGetdents")
if err != nil {
t.Fatal(err)
}
for i := 1; i <= unix.NAME_MAX; i++ {
n := strings.Repeat("x", i)
- err = ioutil.WriteFile(testDir+"/"+n, nil, 0600)
+ err = os.WriteFile(testDir+"/"+n, nil, 0600)
if err != nil {
t.Fatal(err)
}
diff --git a/internal/syscallcompat/main_test.go b/internal/syscallcompat/main_test.go
index ddf6bc4..7183f5a 100644
--- a/internal/syscallcompat/main_test.go
+++ b/internal/syscallcompat/main_test.go
@@ -2,7 +2,6 @@ package syscallcompat
import (
"fmt"
- "io/ioutil"
"os"
"testing"
)
@@ -23,7 +22,7 @@ func TestMain(m *testing.M) {
fmt.Println(err)
os.Exit(1)
}
- tmpDir, err = ioutil.TempDir(parent, "syscallcompat")
+ tmpDir, err = os.MkdirTemp(parent, "syscallcompat")
if err != nil {
fmt.Println(err)
os.Exit(1)
diff --git a/internal/syscallcompat/quirks.go b/internal/syscallcompat/quirks.go
index 858f16d..36bcb9f 100644
--- a/internal/syscallcompat/quirks.go
+++ b/internal/syscallcompat/quirks.go
@@ -5,18 +5,17 @@ import (
)
const (
- // QuirkBrokenFalloc means the falloc is broken.
+ // QuirkBtrfsBrokenFalloc means the falloc is broken.
// Preallocation on Btrfs is broken ( https://github.com/rfjakob/gocryptfs/issues/395 )
// and slow ( https://github.com/rfjakob/gocryptfs/issues/63 ).
- QuirkBrokenFalloc = uint64(1 << iota)
+ QuirkBtrfsBrokenFalloc = uint64(1 << iota)
// QuirkDuplicateIno1 means that we have duplicate inode numbers.
// On MacOS ExFAT, all empty files share inode number 1:
// https://github.com/rfjakob/gocryptfs/issues/585
QuirkDuplicateIno1
- // QuirkNoUserXattr means that user.* xattrs are not supported
- QuirkNoUserXattr
)
-func logQuirk(s string) {
- tlog.Info.Printf(tlog.ColorYellow + "DetectQuirks: " + s + tlog.ColorReset)
+// LogQuirk prints a yellow message about a detected quirk.
+func LogQuirk(s string) {
+ tlog.Info.Println(tlog.ColorYellow + "DetectQuirks: " + s + tlog.ColorReset)
}
diff --git a/internal/syscallcompat/quirks_darwin.go b/internal/syscallcompat/quirks_darwin.go
index 4adeea1..c4d5006 100644
--- a/internal/syscallcompat/quirks_darwin.go
+++ b/internal/syscallcompat/quirks_darwin.go
@@ -33,7 +33,7 @@ func DetectQuirks(cipherdir string) (q uint64) {
// On MacOS ExFAT, all empty files share inode number 1:
// https://github.com/rfjakob/gocryptfs/issues/585
if fstypename == FstypenameExfat {
- logQuirk("ExFAT detected, disabling hard links. See https://github.com/rfjakob/gocryptfs/issues/585 for why.")
+ LogQuirk("ExFAT detected, disabling hard links. See https://github.com/rfjakob/gocryptfs/issues/585 for why.")
q |= QuirkDuplicateIno1
}
diff --git a/internal/syscallcompat/quirks_linux.go b/internal/syscallcompat/quirks_linux.go
index 5ef2d8a..35f754d 100644
--- a/internal/syscallcompat/quirks_linux.go
+++ b/internal/syscallcompat/quirks_linux.go
@@ -1,11 +1,38 @@
package syscallcompat
import (
+ "syscall"
+
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
+// FS_NOCOW_FL is the flag set by "chattr +C" to disable copy-on-write on
+// btrfs. Not exported by golang.org/x/sys/unix, value from linux/fs.h.
+const FS_NOCOW_FL = 0x00800000
+
+// dirHasNoCow checks whether the directory at the given path has the
+// NOCOW (No Copy-on-Write) attribute set (i.e. "chattr +C").
+// When a directory has this attribute, files created within it inherit
+// NOCOW, which makes fallocate work correctly on btrfs because writes
+// go in-place rather than through COW.
+func dirHasNoCow(path string) bool {
+ fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
+ if err != nil {
+ tlog.Debug.Printf("dirHasNoCow: Open %q failed: %v", path, err)
+ return false
+ }
+ defer syscall.Close(fd)
+
+ flags, err := unix.IoctlGetInt(fd, unix.FS_IOC_GETFLAGS)
+ if err != nil {
+ tlog.Debug.Printf("dirHasNoCow: FS_IOC_GETFLAGS on %q failed: %v", path, err)
+ return false
+ }
+ return flags&FS_NOCOW_FL != 0
+}
+
// DetectQuirks decides if there are known quirks on the backing filesystem
// that need to be workarounded.
//
@@ -21,14 +48,19 @@ func DetectQuirks(cipherdir string) (q uint64) {
// Preallocation on Btrfs is broken ( https://github.com/rfjakob/gocryptfs/issues/395 )
// and slow ( https://github.com/rfjakob/gocryptfs/issues/63 ).
//
+ // The root cause is that btrfs COW allocates new blocks on write even for
+ // preallocated extents, defeating the purpose of fallocate. However, if the
+ // backing directory has the NOCOW attribute (chattr +C), writes go in-place
+ // and fallocate works correctly.
+ //
// Cast to uint32 avoids compile error on arm: "constant 2435016766 overflows int32"
if uint32(st.Type) == unix.BTRFS_SUPER_MAGIC {
- logQuirk("Btrfs detected, forcing -noprealloc. See https://github.com/rfjakob/gocryptfs/issues/395 for why.")
- q |= QuirkBrokenFalloc
- }
-
- if uint32(st.Type) == unix.TMPFS_MAGIC {
- logQuirk("tmpfs detected, no extended attributes except acls will work.")
+ if dirHasNoCow(cipherdir) {
+ tlog.Debug.Printf("DetectQuirks: Btrfs detected but cipherdir has NOCOW attribute (chattr +C), fallocate should work correctly")
+ } else {
+ // LogQuirk is called in fusefrontend/root_node.go
+ q |= QuirkBtrfsBrokenFalloc
+ }
}
return q
diff --git a/internal/syscallcompat/rename_exchange_test.go b/internal/syscallcompat/rename_exchange_test.go
new file mode 100644
index 0000000..97a95f8
--- /dev/null
+++ b/internal/syscallcompat/rename_exchange_test.go
@@ -0,0 +1,58 @@
+package syscallcompat
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/sys/unix"
+)
+
+func TestRenameExchange(t *testing.T) {
+ // Create a temporary directory for testing
+ tmpDir, err := os.MkdirTemp("", "renameat2_test")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ // Test basic exchange functionality
+ file1 := filepath.Join(tmpDir, "file1.txt")
+ file2 := filepath.Join(tmpDir, "file2.txt")
+
+ content1 := []byte("content of file 1")
+ content2 := []byte("content of file 2")
+
+ if err := os.WriteFile(file1, content1, 0644); err != nil {
+ t.Fatalf("Failed to create file1: %v", err)
+ }
+
+ if err := os.WriteFile(file2, content2, 0644); err != nil {
+ t.Fatalf("Failed to create file2: %v", err)
+ }
+
+ // Test RENAME_EXCHANGE - this is the core functionality for issue #914
+ err = Renameat2(unix.AT_FDCWD, file1, unix.AT_FDCWD, file2, RENAME_EXCHANGE)
+ if err != nil {
+ t.Fatalf("RENAME_EXCHANGE failed: %v", err)
+ }
+
+ // Verify that the files have been swapped
+ newContent1, err := os.ReadFile(file1)
+ if err != nil {
+ t.Fatalf("Failed to read file1 after exchange: %v", err)
+ }
+
+ newContent2, err := os.ReadFile(file2)
+ if err != nil {
+ t.Fatalf("Failed to read file2 after exchange: %v", err)
+ }
+
+ if string(newContent1) != string(content2) {
+ t.Errorf("file1 content after exchange. Expected: %s, Got: %s", content2, newContent1)
+ }
+
+ if string(newContent2) != string(content1) {
+ t.Errorf("file2 content after exchange. Expected: %s, Got: %s", content1, newContent2)
+ }
+}
diff --git a/internal/syscallcompat/sys_common.go b/internal/syscallcompat/sys_common.go
index 1aa6a6e..3cb9ffa 100644
--- a/internal/syscallcompat/sys_common.go
+++ b/internal/syscallcompat/sys_common.go
@@ -54,10 +54,10 @@ func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
flags |= syscall.O_EXCL
}
} else {
- // If O_CREAT is not used, we should use O_NOFOLLOW
- if flags&syscall.O_NOFOLLOW == 0 {
- tlog.Warn.Printf("Openat: O_NOFOLLOW missing: flags = %#x", flags)
- flags |= syscall.O_NOFOLLOW
+ // If O_CREAT is not used, we should use O_NOFOLLOW or O_SYMLINK
+ if flags&(unix.O_NOFOLLOW|OpenatFlagNofollowSymlink) == 0 {
+ tlog.Warn.Printf("Openat: O_NOFOLLOW/O_SYMLINK missing: flags = %#x", flags)
+ flags |= unix.O_NOFOLLOW
}
}
@@ -112,10 +112,10 @@ const XATTR_SIZE_MAX = 65536
// Make the buffer 1kB bigger so we can detect overflows. Unfortunately,
// slices larger than 64kB are always allocated on the heap.
-const XATTR_BUFSZ = XATTR_SIZE_MAX + 1024
+const GETXATTR_BUFSZ_BIG = XATTR_SIZE_MAX + 1024
// We try with a small buffer first - this one can be allocated on the stack.
-const XATTR_BUFSZ_SMALL = 500
+const GETXATTR_BUFSZ_SMALL = 500
// Fgetxattr is a wrapper around unix.Fgetxattr that handles the buffer sizing.
func Fgetxattr(fd int, attr string) (val []byte, err error) {
@@ -135,7 +135,7 @@ func Lgetxattr(path string, attr string) (val []byte, err error) {
func getxattrSmartBuf(fn func(buf []byte) (int, error)) ([]byte, error) {
// Fastpaths. Important for security.capabilities, which gets queried a lot.
- buf := make([]byte, XATTR_BUFSZ_SMALL)
+ buf := make([]byte, GETXATTR_BUFSZ_SMALL)
sz, err := fn(buf)
// Non-existing xattr
if err == unix.ENODATA {
@@ -159,7 +159,7 @@ func getxattrSmartBuf(fn func(buf []byte) (int, error)) ([]byte, error) {
// We choose the simple approach of buffer that is bigger than the limit on
// Linux, and return an error for everything that is bigger (which can
// only happen on MacOS).
- buf = make([]byte, XATTR_BUFSZ)
+ buf = make([]byte, GETXATTR_BUFSZ_BIG)
sz, err = fn(buf)
if err == syscall.ERANGE {
// Do NOT return ERANGE - the user might retry ad inifinitum!
@@ -182,42 +182,44 @@ out:
// Flistxattr is a wrapper for unix.Flistxattr that handles buffer sizing and
// parsing the returned blob to a string slice.
func Flistxattr(fd int) (attrs []string, err error) {
- // See the buffer sizing comments in getxattrSmartBuf.
- // TODO: smarter buffer sizing?
- buf := make([]byte, XATTR_BUFSZ)
- sz, err := unix.Flistxattr(fd, buf)
- if err == syscall.ERANGE {
- // Do NOT return ERANGE - the user might retry ad inifinitum!
- return nil, syscall.EOVERFLOW
+ listxattrSyscall := func(buf []byte) (int, error) {
+ return unix.Flistxattr(fd, buf)
}
- if err != nil {
- return nil, err
- }
- if sz >= XATTR_SIZE_MAX {
- return nil, syscall.EOVERFLOW
- }
- attrs = parseListxattrBlob(buf[:sz])
- return attrs, nil
+ return listxattrSmartBuf(listxattrSyscall)
}
// Llistxattr is a wrapper for unix.Llistxattr that handles buffer sizing and
// parsing the returned blob to a string slice.
func Llistxattr(path string) (attrs []string, err error) {
- // TODO: smarter buffer sizing?
- buf := make([]byte, XATTR_BUFSZ)
- sz, err := unix.Llistxattr(path, buf)
- if err == syscall.ERANGE {
- // Do NOT return ERANGE - the user might retry ad inifinitum!
- return nil, syscall.EOVERFLOW
+ listxattrSyscall := func(buf []byte) (int, error) {
+ return unix.Llistxattr(path, buf)
}
- if err != nil {
- return nil, err
- }
- if sz >= XATTR_SIZE_MAX {
- return nil, syscall.EOVERFLOW
+ return listxattrSmartBuf(listxattrSyscall)
+}
+
+// listxattrSmartBuf handles smart buffer sizing for Flistxattr and Llistxattr
+func listxattrSmartBuf(listxattrSyscall func([]byte) (int, error)) ([]string, error) {
+ const LISTXATTR_BUFSZ_SMALL = 100
+
+ // Blindly try with the small buffer first
+ buf := make([]byte, LISTXATTR_BUFSZ_SMALL)
+ sz, err := listxattrSyscall(buf)
+ if err == syscall.ERANGE {
+ // Did not fit. Find the actual size
+ sz, err = listxattrSyscall(nil)
+ if err != nil {
+ return nil, err
+ }
+ // ...and allocate the buffer to fit
+ buf = make([]byte, sz)
+ sz, err = listxattrSyscall(buf)
+ if err != nil {
+ // When an xattr got added between the size probe and here,
+ // we could fail with ERANGE. This is ok as the caller will retry.
+ return nil, err
+ }
}
- attrs = parseListxattrBlob(buf[:sz])
- return attrs, nil
+ return parseListxattrBlob(buf[:sz]), nil
}
func parseListxattrBlob(buf []byte) (attrs []string) {
diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go
index 06f09f0..ef19f24 100644
--- a/internal/syscallcompat/sys_darwin.go
+++ b/internal/syscallcompat/sys_darwin.go
@@ -3,7 +3,6 @@ package syscallcompat
import (
"log"
"path/filepath"
- "runtime"
"syscall"
"time"
"unsafe"
@@ -21,26 +20,18 @@ const (
// O_PATH is only defined on Linux
O_PATH = 0
+ // Same meaning, different name
+ RENAME_NOREPLACE = unix.RENAME_EXCL
+ RENAME_EXCHANGE = unix.RENAME_SWAP
+
// Only exists on Linux. Define here to fix build failure, even though
- // we will never see the flags.
- RENAME_NOREPLACE = 1
- RENAME_EXCHANGE = 2
- RENAME_WHITEOUT = 4
-
- // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
- // revert permissions to the process credentials.
- KAUTH_UID_NONE = ^uint32(0) - 100
- KAUTH_GID_NONE = ^uint32(0) - 100
-)
+ // we will never see this flag.
+ RENAME_WHITEOUT = 1 << 30
-// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
-func pthread_setugid_np(uid uint32, gid uint32) (err error) {
- _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
- if e1 != 0 {
- err = e1
- }
- return
-}
+ // On Darwin we use O_SYMLINK which allows opening a symlink itself.
+ // On Linux, we only have O_NOFOLLOW.
+ OpenatFlagNofollowSymlink = unix.O_SYMLINK
+)
// Unfortunately fsetattrlist does not have a syscall wrapper yet.
func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
@@ -84,74 +75,14 @@ func Dup3(oldfd int, newfd int, flags int) (err error) {
//// Emulated Syscalls (see emulate.go) ////////////////
////////////////////////////////////////////////////////
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return -1, err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Openat(dirfd, path, flags, mode)
-}
-
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return emulateMknodat(dirfd, path, mode, dev)
}
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Mknodat(dirfd, path, mode, dev)
-}
-
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW)
}
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Symlinkat(oldpath, newdirfd, newpath)
-}
-
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Mkdirat(dirfd, path, mode)
-}
-
type attrList struct {
bitmapCount uint16
_ uint16
@@ -227,7 +158,12 @@ func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.Dir
return emulateGetdents(fd)
}
-// Renameat2 does not exist on Darwin, so we call Renameat and ignore the flags.
+// Renameat2 does not exist on Darwin, but RenameatxNp does.
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
- return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
+ // If no flags are set, use tried and true renameat
+ if flags == 0 {
+ return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
+ }
+ // Let RenameatxNp handle everything else
+ return unix.RenameatxNp(olddirfd, oldpath, newdirfd, newpath, uint32(flags))
}
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index a64b27e..71478af 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -3,10 +3,6 @@ package syscallcompat
import (
"fmt"
- "io/ioutil"
- "runtime"
- "strconv"
- "strings"
"sync"
"syscall"
"time"
@@ -32,6 +28,10 @@ const (
RENAME_NOREPLACE = unix.RENAME_NOREPLACE
RENAME_WHITEOUT = unix.RENAME_WHITEOUT
RENAME_EXCHANGE = unix.RENAME_EXCHANGE
+
+ // On Darwin we use O_SYMLINK which allows opening a symlink itself.
+ // On Linux, we only have O_NOFOLLOW.
+ OpenatFlagNofollowSymlink = unix.O_NOFOLLOW
)
var preallocWarn sync.Once
@@ -67,104 +67,11 @@ func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return syscall.Fallocate(fd, mode, off, len)
}
-func getSupplementaryGroups(pid uint32) (gids []int) {
- procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
- blob, err := ioutil.ReadFile(procPath)
- if err != nil {
- return nil
- }
-
- lines := strings.Split(string(blob), "\n")
- for _, line := range lines {
- if strings.HasPrefix(line, "Groups:") {
- f := strings.Fields(line[7:])
- gids = make([]int, len(f))
- for i := range gids {
- val, err := strconv.ParseInt(f[i], 10, 32)
- if err != nil {
- return nil
- }
- gids[i] = int(val)
- }
- return gids
- }
- }
-
- return nil
-}
-
-// asUser runs `f()` under the effective uid, gid, groups specified
-// in `context`.
-//
-// If `context` is nil, `f()` is executed directly without switching user id.
-func asUser(f func() (int, error), context *fuse.Context) (int, error) {
- if context == nil {
- return f()
- }
-
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
- // https://go-review.googlesource.com/c/go/+/210639 )
- // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
- // is exactly what we not want.
- //
- // We now use unix.{Setgroups,Setregid,Setreuid} instead.
-
- err := unix.Setgroups(getSupplementaryGroups(context.Pid))
- if err != nil {
- return -1, err
- }
- defer unix.Setgroups(nil)
-
- err = unix.Setregid(-1, int(context.Owner.Gid))
- if err != nil {
- return -1, err
- }
- defer unix.Setregid(-1, 0)
-
- err = unix.Setreuid(-1, int(context.Owner.Uid))
- if err != nil {
- return -1, err
- }
- defer unix.Setreuid(-1, 0)
-
- return f()
-}
-
-// OpenatUser runs the Openat syscall in the context of a different user.
-//
-// It switches the current thread to the new user, performs the syscall,
-// and switches back.
-//
-// If `context` is nil, this function behaves like ordinary Openat (no
-// user switching).
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- f := func() (int, error) {
- return Openat(dirfd, path, flags, mode)
- }
- return asUser(f, context)
-}
-
// Mknodat wraps the Mknodat syscall.
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return syscall.Mknodat(dirfd, path, mode, dev)
}
-// MknodatUser runs the Mknodat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mknodat.
-//
-// See OpenatUser() for how this works.
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := Mknodat(dirfd, path, mode, dev)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2
// is not implemented on arm64.
func Dup3(oldfd int, newfd int, flags int) (err error) {
@@ -205,32 +112,6 @@ func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return syscall.Chmod(procPath, mode)
}
-// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Symlinkat.
-//
-// See OpenatUser() for how this works.
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Symlinkat(oldpath, newdirfd, newpath)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
-// MkdiratUser runs the Mkdirat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mkdirat.
-//
-// See OpenatUser() for how this works.
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Mkdirat(dirfd, path, mode)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// LsetxattrUser runs the Lsetxattr syscall in the context of a different user.
// This is useful when setting ACLs, as the result depends on the user running
// the operation (see fuse-xfstests generic/375).
@@ -247,8 +128,16 @@ func LsetxattrUser(path string, attr string, data []byte, flags int, context *fu
func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec {
ts := make([]unix.Timespec, 2)
- ts[0] = unix.Timespec(fuse.UtimeToTimespec(a))
- ts[1] = unix.Timespec(fuse.UtimeToTimespec(m))
+ if a == nil {
+ ts[0] = unix.Timespec{Nsec: unix.UTIME_OMIT}
+ } else {
+ ts[0], _ = unix.TimeToTimespec(*a)
+ }
+ if m == nil {
+ ts[1] = unix.Timespec{Nsec: unix.UTIME_OMIT}
+ } else {
+ ts[1], _ = unix.TimeToTimespec(*m)
+ }
return ts
}
diff --git a/internal/syscallcompat/thread_credentials_linux.go b/internal/syscallcompat/thread_credentials_linux.go
new file mode 100644
index 0000000..b5ec6cd
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux.go
@@ -0,0 +1,61 @@
+//go:build linux
+
+// golang.org/x/sys/unix commit
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// changed unix.Setreuid/unix.Setregid functions to affect the whole thread, which is
+// what gocryptfs does NOT want (https://github.com/rfjakob/gocryptfs/issues/893).
+// The functions Setreuid/Setegid are copy-pasted from one commit before
+// (9e1f76180b77a12eb07c82eb8e1ea8a7f8d202e7).
+//
+// Looking at the diff at https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// we see that only two architectures, 386 and arm, use SYS_SETREUID32/SYS_SETREGID32
+// (see "man 2 setreuid" for why).
+// All the others architectures use SYS_SETREUID/SYS_SETREGID.
+//
+// As of golang.org/x/sys/unix v0.30.0, Setgroups/setgroups is still per-thread, but
+// it is likely that this will change, too. Setgroups/setgroups are copy-pasted from
+// v0.30.0. The SYS_SETGROUPS32/SYS_SETGROUPS split is the same as for Setreuid.
+//
+// Note: _Gid_t is always uint32 on linux, so we can directly use uint32 for setgroups.
+package syscallcompat
+
+import (
+ "log"
+)
+
+// Setgroups is like setgroups(2) but affects only the current thread
+func Setgroups(gids []int) (err error) {
+ if len(gids) == 0 {
+ return setgroups(0, nil)
+ }
+
+ a := make([]uint32, len(gids))
+ for i, v := range gids {
+ a[i] = uint32(v)
+ }
+ return setgroups(len(a), &a[0])
+}
+
+// SetgroupsPanic calls Setgroups and panics on error
+func SetgroupsPanic(gids []int) {
+ err := Setgroups(gids)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetregidPanic calls Setregid and panics on error
+func SetregidPanic(rgid int, egid int) {
+ err := Setregid(rgid, egid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetreuidPanic calls Setreuid and panics on error
+func SetreuidPanic(ruid int, euid int) {
+ err := Setreuid(ruid, euid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_32.go b/internal/syscallcompat/thread_credentials_linux_32.go
new file mode 100644
index 0000000..69fffca
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_32.go
@@ -0,0 +1,40 @@
+//go:build (linux && 386) || (linux && arm)
+
+// Linux on i386 and 32-bit ARM has SYS_SETREUID and friends returning 16-bit values.
+// We need to use SYS_SETREUID32 instead.
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID32, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID32, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS32, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_other.go b/internal/syscallcompat/thread_credentials_linux_other.go
new file mode 100644
index 0000000..ab11c71
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_other.go
@@ -0,0 +1,37 @@
+//go:build !((linux && 386) || (linux && arm))
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/tlog/log.go b/internal/tlog/log.go
index 62d791d..02c760c 100644
--- a/internal/tlog/log.go
+++ b/internal/tlog/log.go
@@ -73,7 +73,7 @@ func (l *toggledLogger) Printf(format string, v ...interface{}) {
return
}
msg := trimNewline(fmt.Sprintf(format, v...))
- l.Logger.Printf(l.prefix + msg + l.postfix)
+ l.Logger.Print(l.prefix + msg + l.postfix)
if l.Wpanic {
l.Logger.Panic(wpanicMsg + msg)
}
diff --git a/main.go b/main.go
index cd643b5..62a7985 100644
--- a/main.go
+++ b/main.go
@@ -120,7 +120,7 @@ func changePassword(args *argContainer) {
tlog.Fatal.Println(err)
os.Exit(exitcodes.WriteConf)
}
- tlog.Info.Printf(tlog.ColorGreen + "Password changed." + tlog.ColorReset)
+ tlog.Info.Println(tlog.ColorGreen + "Password changed." + tlog.ColorReset)
}
func main() {
diff --git a/masterkey.go b/masterkey.go
index d488441..c67c5d5 100644
--- a/masterkey.go
+++ b/masterkey.go
@@ -24,9 +24,9 @@ func unhexMasterKey(masterkey string, fromStdin bool) []byte {
tlog.Fatal.Printf("Master key has length %d but we require length %d", len(key), cryptocore.KeyLen)
os.Exit(exitcodes.MasterKey)
}
- tlog.Info.Printf("Using explicit master key.")
+ tlog.Info.Println("Using explicit master key.")
if !fromStdin {
- tlog.Info.Printf(tlog.ColorYellow +
+ tlog.Info.Println(tlog.ColorYellow +
"THE MASTER KEY IS VISIBLE VIA \"ps ax\" AND MAY BE STORED IN YOUR SHELL HISTORY!\n" +
"ONLY USE THIS MODE FOR EMERGENCIES" + tlog.ColorReset)
}
@@ -52,8 +52,8 @@ func handleArgsMasterkey(args *argContainer) (masterkey []byte) {
}
// "-zerokey"
if args.zerokey {
- tlog.Info.Printf("Using all-zero dummy master key.")
- tlog.Info.Printf(tlog.ColorYellow +
+ tlog.Info.Println("Using all-zero dummy master key.")
+ tlog.Info.Println(tlog.ColorYellow +
"ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING." +
tlog.ColorReset)
return make([]byte, cryptocore.KeyLen)
diff --git a/mount.go b/mount.go
index 0eaa3dd..f7378aa 100644
--- a/mount.go
+++ b/mount.go
@@ -13,7 +13,6 @@ import (
"runtime"
"runtime/debug"
"strings"
- "sync/atomic"
"syscall"
"time"
@@ -181,7 +180,7 @@ func idleMonitor(idleTimeout time.Duration, fs *fusefrontend.RootNode, srv *fuse
}
for {
// Atomically check whether the flag is 0 and reset it to 1 if so.
- isIdle := !atomic.CompareAndSwapUint32(&fs.IsIdle, 0, 1)
+ isIdle := !fs.IsIdle.CompareAndSwap(false, true)
// Any form of current or recent access resets the idle counter.
openFileCount := openfiletable.CountOpenFiles()
if !isIdle || openFileCount > 0 {
@@ -285,6 +284,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
SharedStorage: args.sharedstorage,
OneFileSystem: args.one_file_system,
DeterministicNames: args.deterministic_names,
+ NoXattr: args.noxattr,
}
// confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil {
@@ -316,9 +316,13 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
}
}
}
- // If allow_other is set and we run as root, try to give newly created files to
- // the right user.
- if args.allow_other && os.Getuid() == 0 {
+ // If allow_other is set and we run as root, create files as the accessing
+ // user.
+ // Except when -force_owner is set, because in this case the user may
+ // not have write permissions. And the point of -force_owner is to map uids,
+ // so we want the files on the backing dir to get the uid the gocryptfs process
+ // is running as.
+ if args.allow_other && os.Getuid() == 0 && args._forceOwner == nil {
frontendArgs.PreserveOwner = true
}
@@ -409,7 +413,7 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {
mOpts := &fuseOpts.MountOptions
opts := make(map[string]string)
if args.allow_other {
- tlog.Info.Printf(tlog.ColorYellow + "The option \"-allow_other\" is set. Make sure the file " +
+ tlog.Info.Println(tlog.ColorYellow + "The option \"-allow_other\" is set. Make sure the file " +
"permissions protect your data from unwanted access." + tlog.ColorReset)
mOpts.AllowOther = true
// Make the kernel check the file permissions for us
@@ -469,6 +473,9 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {
} else if args.exec {
opts["exec"] = ""
}
+ if args.context != "" {
+ opts["context"] = args.context
+ }
// Add additional mount options (if any) after the stock ones, so the user has
// a chance to override them.
if args.ko != "" {
diff --git a/package-release-tarballs.bash b/package-release-tarballs.bash
index fde214e..881bce0 100755
--- a/package-release-tarballs.bash
+++ b/package-release-tarballs.bash
@@ -94,4 +94,6 @@ fi
package_source
package_static_binary
+export GOARCH=arm64
+package_static_binary
signing_hint
diff --git a/profiling/tinyfiles.bash b/profiling/tinyfiles.bash
new file mode 100755
index 0000000..90820de
--- /dev/null
+++ b/profiling/tinyfiles.bash
@@ -0,0 +1,33 @@
+#!/bin/bash -eu
+#
+# Create a tarball of 100k 1-byte files using reverse mode
+# https://github.com/rfjakob/gocryptfs/issues/965
+
+cd "$(dirname "$0")"
+
+T=$(mktemp -d)
+mkdir "$T/a" "$T/b"
+
+../gocryptfs -init -reverse -quiet -scryptn 10 -extpass "echo test" "$@" "$T/a"
+
+# Cleanup trap
+# shellcheck disable=SC2064
+trap "cd /; fusermount -u -z '$T/b'; rm -Rf '$T/a'" EXIT
+
+echo "Creating 100k 1-byte files"
+SECONDS=0
+dd if=/dev/urandom bs=100k count=1 status=none | split --suffix-length=10 -b 1 - "$T/a/tinyfile."
+echo "done, $SECONDS seconds"
+
+../gocryptfs -reverse -quiet -nosyslog -extpass "echo test" \
+ -cpuprofile "$T/cprof" -memprofile "$T/mprof" \
+ "$@" "$T/a" "$T/b"
+
+echo "Running tar under profiler..."
+SECONDS=0
+tar -cf /dev/null "$T/b"
+echo "done, $SECONDS seconds"
+
+echo
+echo "Hint: go tool pprof ../gocryptfs $T/cprof"
+echo " go tool pprof -alloc_space ../gocryptfs $T/mprof"
diff --git a/test-without-openssl.bash b/test-without-openssl.bash
index 3f9de9e..e596753 100755
--- a/test-without-openssl.bash
+++ b/test-without-openssl.bash
@@ -2,4 +2,4 @@
cd "$(dirname "$0")"
-./test.bash -tags without_openssl "$@"
+CGO_ENABLED=0 ./test.bash "$@"
diff --git a/test.bash b/test.bash
index 174236e..d5da874 100755
--- a/test.bash
+++ b/test.bash
@@ -22,7 +22,6 @@ else
fi
cd "$(dirname "$0")"
-export GO111MODULE=on
MYNAME=$(basename "$0")
TESTDIR=$TMPDIR/gocryptfs-test-parent-$UID
mkdir -p "$TESTDIR"
@@ -38,9 +37,9 @@ unmount_leftovers() {
return $RET
}
-(
# Prevent multiple parallel test.bash instances as this causes
# all kinds of mayhem
+exec 200> "$LOCKFILE"
if ! command -v flock > /dev/null ; then
echo "flock is not available, skipping"
elif ! flock -n 200 ; then
@@ -55,8 +54,8 @@ unmount_leftovers || true
echo "$MYNAME: build-without-openssl.bash failed"
exit 1
}
-# Don't build with openssl if we were passed "-tags without_openssl"
-if [[ "$*" != *without_openssl* ]] ; then
+# Only build with openssl if cgo is enabled
+if [[ $(go env CGO_ENABLED) -eq 1 ]] ; then
./build.bash
fi
@@ -68,15 +67,20 @@ else
go vet ./...
fi
+if command -v staticcheck > /dev/null ; then
+ staticcheck ./...
+else
+ echo "staticcheck not installed - skipping"
+fi
+
if command -v shellcheck > /dev/null ; then
- # SC2002 = useless cat. Does no harm, disable the check.
- shellcheck -x -e SC2002 ./*.bash
+ shellcheck -x ./*.bash
else
echo "shellcheck not installed - skipping"
fi
echo -n "Testing on TMPDIR=$TMPDIR, filesystem: "
-findmnt --noheadings --target "$TESTDIR" --output FSTYPE,OPTIONS || true
+findmnt --noheadings --target "$TESTDIR" --output FSTYPE,OPTIONS || echo "?"
EXTRA_ARGS=""
if [[ $VERBOSE -eq 1 ]]; then
@@ -110,18 +114,19 @@ if find internal -type f -name \*.go -print0 | xargs -0 grep "panic("; then
exit 1
fi
-# All functions from the commit msg in https://go-review.googlesource.com/c/go/+/210639
-if find . -type f -name \*.go -print0 | xargs -0 grep -E 'syscall.(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' ; then
- echo "$MYNAME: You probably want to use unix.Setgroups and friends. See the comments in OpenatUser() for why."
+# Both syscall.Setreuid etc (since 2020, https://github.com/golang/go/commit/d1b1145cace8b968307f9311ff611e4bb810710c)
+# and unix.Setreuid etc (since 2022, https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51)
+# affect the whole process, not only the current thread, which is what we do NOT want.
+if find . ! -path "./vendor/*" -type f -name \*.go -print0 | xargs -0 grep -v -E '^//' |
+ grep -E '(syscall|unix).(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' ; then
+ echo "$MYNAME: This affects the whole process. Please use the syscallcompat wrappers instead."
exit 1
fi
-if find . -type f -name \*.go -print0 | xargs -0 grep '\.Creat('; then
+if find . ! -path "./vendor/*" -type f -name \*.go -print0 | xargs -0 grep '\.Creat('; then
# MacOS does not have syscall.Creat(). Creat() is equivalent to Open(..., O_CREAT|O_WRONLY|O_TRUNC, ...),
# but usually you want O_EXCL instead of O_TRUNC because it is safer, so that's what we suggest
# instead.
echo "$MYNAME: Please use Open(..., O_CREAT|O_WRONLY|O_EXCL, ...) instead of Creat()! https://github.com/rfjakob/gocryptfs/issues/623"
exit 1
fi
-
-) 200> "$LOCKFILE"
diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go
index fb09338..01cc3b7 100644
--- a/tests/cli/cli_test.go
+++ b/tests/cli/cli_test.go
@@ -6,7 +6,6 @@ import (
"encoding/hex"
"errors"
"fmt"
- "io/ioutil"
"os"
"os/exec"
"strconv"
@@ -174,7 +173,7 @@ func TestPasswd(t *testing.T) {
// Add content
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
file1 := mnt + "/file1"
- err := ioutil.WriteFile(file1, []byte("somecontent"), 0600)
+ err := os.WriteFile(file1, []byte("somecontent"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -186,7 +185,7 @@ func TestPasswd(t *testing.T) {
testPasswd(t, dir)
// Mount and verify
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo newpasswd")
- content, err := ioutil.ReadFile(file1)
+ content, err := os.ReadFile(file1)
if err != nil {
t.Error(err)
} else if string(content) != "somecontent" {
@@ -201,12 +200,12 @@ func TestPasswd(t *testing.T) {
// cp copies file at `src` to `dst`, overwriting
// `dst` if it already exists. Calls t.Fatal on failure.
func cp(t *testing.T, src string, dst string) {
- conf, err := ioutil.ReadFile(src)
+ conf, err := os.ReadFile(src)
if err != nil {
t.Fatal(err)
}
syscall.Unlink(dst)
- err = ioutil.WriteFile(dst, conf, 0600)
+ err = os.WriteFile(dst, conf, 0600)
if err != nil {
t.Fatal(err)
}
@@ -222,7 +221,7 @@ func TestPasswdMasterkey(t *testing.T) {
mnt := dir + ".mnt"
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
file1 := mnt + "/file1"
- err := ioutil.WriteFile(file1, []byte("somecontent"), 0600)
+ err := os.WriteFile(file1, []byte("somecontent"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -251,7 +250,7 @@ func TestPasswdMasterkey(t *testing.T) {
}
// Mount and verify
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo newpasswd")
- content, err := ioutil.ReadFile(file1)
+ content, err := os.ReadFile(file1)
if err != nil {
t.Error(err)
} else if string(content) != "somecontent" {
@@ -270,7 +269,7 @@ func TestPasswdMasterkeyStdin(t *testing.T) {
mnt := dir + ".mnt"
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
file1 := mnt + "/file1"
- err := ioutil.WriteFile(file1, []byte("somecontent"), 0600)
+ err := os.WriteFile(file1, []byte("somecontent"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -300,7 +299,7 @@ func TestPasswdMasterkeyStdin(t *testing.T) {
}
// Mount and verify
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo newpasswd")
- content, err := ioutil.ReadFile(file1)
+ content, err := os.ReadFile(file1)
if err != nil {
t.Fatal(err)
} else if string(content) != "somecontent" {
@@ -392,7 +391,7 @@ func TestNonempty(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile(mnt+"/somefile", []byte("xyz"), 0600)
+ err = os.WriteFile(mnt+"/somefile", []byte("xyz"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -586,7 +585,7 @@ func TestNoexec(t *testing.T) {
content := `#!/bin/bash
echo hello
`
- err := ioutil.WriteFile(sh, []byte(content), 0755)
+ err := os.WriteFile(sh, []byte(content), 0755)
if err != nil {
t.Fatal(err)
}
@@ -697,13 +696,13 @@ func TestNotIdle(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile(mnt+"/foo", []byte("foo"), 0600)
+ err = os.WriteFile(mnt+"/foo", []byte("foo"), 0600)
if err != nil {
t.Fatal(err)
}
// Read every 10 milliseconds for a total of 1 second
for i := 1; i < 100; i++ {
- _, err = ioutil.ReadFile(mnt + "/foo")
+ _, err = os.ReadFile(mnt + "/foo")
if err != nil {
t.Fatalf("iteration %d failed: %v", i, err)
}
@@ -784,7 +783,7 @@ func TestBadname(t *testing.T) {
file := mnt + "/" + validFileName
// Case 1: write one valid filename (empty content)
- err := ioutil.WriteFile(file, nil, 0600)
+ err := os.WriteFile(file, nil, 0600)
if err != nil {
t.Fatal(err)
}
@@ -809,26 +808,26 @@ func TestBadname(t *testing.T) {
}
//Generate valid cipherdata for all cases
for i := 0; i < len(contentCipher); i++ {
- err := ioutil.WriteFile(file, []byte(fmt.Sprintf("Content Case %d.", i+1)), 0600)
+ err := os.WriteFile(file, []byte(fmt.Sprintf("Content Case %d.", i+1)), 0600)
if err != nil {
t.Fatal(err)
}
//save the cipher data for file operations in cipher dir
- contentCipher[i], err = ioutil.ReadFile(dir + "/" + encryptedfilename)
+ contentCipher[i], err = os.ReadFile(dir + "/" + encryptedfilename)
if err != nil {
t.Fatal(err)
}
}
//re-write content for case 1
- err = ioutil.WriteFile(file, []byte("Content Case 1."), 0600)
+ err = os.WriteFile(file, []byte("Content Case 1."), 0600)
if err != nil {
t.Fatal(err)
}
// Case 2: File with invalid suffix in plain name but valid cipher file
file = mnt + "/" + validFileName + nametransform.BadnameSuffix
- err = ioutil.WriteFile(file, []byte("Content Case 2."), 0600)
+ err = os.WriteFile(file, []byte("Content Case 2."), 0600)
if err != nil {
t.Fatal(err)
}
@@ -842,29 +841,29 @@ func TestBadname(t *testing.T) {
// Case 3 is impossible: only BadnameSuffix would mean the cipher name is valid
// Case 4: write invalid file which should be decodable
- err = ioutil.WriteFile(dir+"/"+encryptedfilename+invalidSuffix, contentCipher[3], 0600)
+ err = os.WriteFile(dir+"/"+encryptedfilename+invalidSuffix, contentCipher[3], 0600)
if err != nil {
t.Fatal(err)
}
//Case 5: write invalid file which is not decodable (replace last 2 bytes with percent sign)
- err = ioutil.WriteFile(dir+"/"+encryptedfilename[:len(encryptedfilename)-2]+"%%"+invalidSuffix, contentCipher[4], 0600)
+ err = os.WriteFile(dir+"/"+encryptedfilename[:len(encryptedfilename)-2]+"%%"+invalidSuffix, contentCipher[4], 0600)
if err != nil {
t.Fatal(err)
}
// Case 6: Multiple possible matches
// generate two files with invalid cipher names which can both match the badname pattern
- err = ioutil.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wPC"+invalidSuffix, contentCipher[5], 0600)
+ err = os.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wPC"+invalidSuffix, contentCipher[5], 0600)
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wP"+invalidSuffix, contentCipher[5], 0600)
+ err = os.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wP"+invalidSuffix, contentCipher[5], 0600)
if err != nil {
t.Fatal(err)
}
// Case 7: Non-Matching badname pattern
- err = ioutil.WriteFile(dir+"/"+encryptedfilename+"wrongPattern", contentCipher[6], 0600)
+ err = os.WriteFile(dir+"/"+encryptedfilename+"wrongPattern", contentCipher[6], 0600)
if err != nil {
t.Fatal(err)
}
@@ -894,7 +893,7 @@ func TestBadname(t *testing.T) {
for _, name := range names {
if name == searchstrings[0] {
//Case 1: Test access
- filebytes, err = ioutil.ReadFile(mnt + "/" + name)
+ filebytes, err = os.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
@@ -905,7 +904,7 @@ func TestBadname(t *testing.T) {
} else if name == searchstrings[1] {
//Case 2: Test Access
- filebytes, err = ioutil.ReadFile(mnt + "/" + name)
+ filebytes, err = os.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
@@ -915,7 +914,7 @@ func TestBadname(t *testing.T) {
}
} else if name == searchstrings[3] {
//Case 4: Test Access
- filebytes, err = ioutil.ReadFile(mnt + "/" + name)
+ filebytes, err = os.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
@@ -925,7 +924,7 @@ func TestBadname(t *testing.T) {
}
} else if name == searchstrings[4] {
//Case 5: Test Access
- filebytes, err = ioutil.ReadFile(mnt + "/" + name)
+ filebytes, err = os.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
@@ -955,7 +954,7 @@ func TestPassfile(t *testing.T) {
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
passfile1 := mnt + ".1.txt"
- ioutil.WriteFile(passfile1, []byte("test"), 0600)
+ os.WriteFile(passfile1, []byte("test"), 0600)
test_helpers.MountOrFatal(t, dir, mnt, "-passfile="+passfile1)
defer test_helpers.UnmountPanic(mnt)
}
@@ -966,8 +965,8 @@ func TestPassfileX2(t *testing.T) {
mnt := dir + ".mnt"
passfile1 := mnt + ".1.txt"
passfile2 := mnt + ".2.txt"
- ioutil.WriteFile(passfile1, []byte("te"), 0600)
- ioutil.WriteFile(passfile2, []byte("st"), 0600)
+ os.WriteFile(passfile1, []byte("te"), 0600)
+ os.WriteFile(passfile2, []byte("st"), 0600)
test_helpers.MountOrFatal(t, dir, mnt, "-passfile="+passfile1, "-passfile="+passfile2)
defer test_helpers.UnmountPanic(mnt)
}
@@ -979,7 +978,7 @@ func TestInitNotEmpty(t *testing.T) {
if err := os.Mkdir(dir, 0700); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(dir+"/foo", nil, 0700); err != nil {
+ if err := os.WriteFile(dir+"/foo", nil, 0700); err != nil {
t.Fatal(err)
}
cmd := exec.Command(test_helpers.GocryptfsBinary, "-init", "-extpass", "echo test", dir)
@@ -1001,7 +1000,7 @@ func TestSharedstorage(t *testing.T) {
defer test_helpers.UnmountPanic(mnt)
foo1 := mnt + "/foo1"
foo2 := mnt + "/foo2"
- if err := ioutil.WriteFile(foo1, nil, 0755); err != nil {
+ if err := os.WriteFile(foo1, nil, 0755); err != nil {
t.Fatal(err)
}
if err := os.Link(foo1, foo2); err != nil {
@@ -1020,7 +1019,7 @@ func TestSharedstorage(t *testing.T) {
}
// Check that we we don't have stat caching. New length should show up
// on the hard link immediately.
- if err := ioutil.WriteFile(foo1, []byte("xxxxxx"), 0755); err != nil {
+ if err := os.WriteFile(foo1, []byte("xxxxxx"), 0755); err != nil {
t.Fatal(err)
}
if err := syscall.Stat(foo2, &st2); err != nil {
diff --git a/tests/cli/longnamemax_test.go b/tests/cli/longnamemax_test.go
index e44a84e..357ede6 100644
--- a/tests/cli/longnamemax_test.go
+++ b/tests/cli/longnamemax_test.go
@@ -2,7 +2,6 @@ package cli
import (
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -38,7 +37,7 @@ func TestLongnamemax100(t *testing.T) {
for l := 1; l <= 255; l++ {
path := pDir + "/" + strings.Repeat("x", l)
- if err := ioutil.WriteFile(path, nil, 0600); err != nil {
+ if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
matches, err := filepath.Glob(cDir + "/gocryptfs.longname.*")
@@ -84,7 +83,7 @@ func TestLongnamemax100Reverse(t *testing.T) {
for l := 1; l <= 255; l++ {
path := backingDir + "/" + strings.Repeat("x", l)
- if err := ioutil.WriteFile(path, nil, 0600); err != nil {
+ if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
matches, err := filepath.Glob(mntDir + "/gocryptfs.longname.*")
diff --git a/tests/cli/xchacha_test.go b/tests/cli/xchacha_test.go
index 7f24c8b..f0fe163 100644
--- a/tests/cli/xchacha_test.go
+++ b/tests/cli/xchacha_test.go
@@ -2,7 +2,6 @@ package cli
import (
"fmt"
- "io/ioutil"
"os"
"syscall"
"testing"
@@ -40,7 +39,7 @@ func TestXchacha(t *testing.T) {
test_helpers.MountOrExit(cDir, pDir, "-extpass", "echo test")
defer test_helpers.UnmountPanic(pDir)
- if err := ioutil.WriteFile(pDir+"/1byte", []byte("x"), 0700); err != nil {
+ if err := os.WriteFile(pDir+"/1byte", []byte("x"), 0700); err != nil {
t.Fatal(err)
}
var st syscall.Stat_t
@@ -53,7 +52,7 @@ func TestXchacha(t *testing.T) {
}
// 1 MiB = 256 4kiB blocks
- if err := ioutil.WriteFile(pDir+"/1MiB", make([]byte, 1024*1024), 0700); err != nil {
+ if err := os.WriteFile(pDir+"/1MiB", make([]byte, 1024*1024), 0700); err != nil {
t.Fatal(err)
}
if err := syscall.Stat(cDir+"/1MiB", &st); err != nil {
diff --git a/tests/cli/zerokey_test.go b/tests/cli/zerokey_test.go
index e9b9c50..359651c 100644
--- a/tests/cli/zerokey_test.go
+++ b/tests/cli/zerokey_test.go
@@ -1,7 +1,6 @@
package cli
import (
- "io/ioutil"
"os"
"os/exec"
"testing"
@@ -42,7 +41,7 @@ func TestZerokey(t *testing.T) {
mnt := dir + ".mnt"
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
file1 := mnt + "/file1"
- err = ioutil.WriteFile(file1, []byte("somecontent"), 0600)
+ err = os.WriteFile(file1, []byte("somecontent"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -50,7 +49,7 @@ func TestZerokey(t *testing.T) {
// Mount using -zerokey and verify we get the same result
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
- content, err := ioutil.ReadFile(file1)
+ content, err := os.ReadFile(file1)
if err != nil {
t.Error(err)
} else if string(content) != "somecontent" {
diff --git a/tests/defaults/acl_test.go b/tests/defaults/acl_test.go
index 2ab5dc1..bddb5e5 100644
--- a/tests/defaults/acl_test.go
+++ b/tests/defaults/acl_test.go
@@ -1,14 +1,12 @@
package defaults
import (
- "io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
- "time"
"golang.org/x/sys/unix"
@@ -22,8 +20,6 @@ func TestCpA(t *testing.T) {
fn1 := filepath.Join(test_helpers.TmpDir, t.Name())
fn2 := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
- rand.Seed(int64(time.Now().Nanosecond()))
-
{
// Need unrestricted umask
old := syscall.Umask(000)
@@ -35,7 +31,7 @@ func TestCpA(t *testing.T) {
var modeWant os.FileMode = os.FileMode(rand.Int31n(0777+1) | 0400)
// Create file outside mount
- err := ioutil.WriteFile(fn1, nil, modeWant)
+ err := os.WriteFile(fn1, nil, modeWant)
if err != nil {
t.Fatal(err)
}
@@ -45,7 +41,7 @@ func TestCpA(t *testing.T) {
t.Fatal(err)
}
if fi.Mode() != modeWant {
- t.Errorf("ioutil.WriteFile created wrong permissions: want %o have %o", modeWant, fi.Mode())
+ t.Errorf("os.WriteFile created wrong permissions: want %o have %o", modeWant, fi.Mode())
}
// "cp -a" from outside to inside mount
@@ -93,7 +89,7 @@ func TestAcl543(t *testing.T) {
}
// Set acl on file outside gocryptfs mount
- err := ioutil.WriteFile(fn1, nil, modeWant)
+ err := os.WriteFile(fn1, nil, modeWant)
if err != nil {
t.Fatal(err)
}
@@ -117,7 +113,7 @@ func TestAcl543(t *testing.T) {
}
// Set acl on file inside gocryptfs mount
- err = ioutil.WriteFile(fn2, nil, modeWant)
+ err = os.WriteFile(fn2, nil, modeWant)
if err != nil {
t.Fatal(err)
}
@@ -164,7 +160,7 @@ func TestAcl543(t *testing.T) {
// Check that we handle zero-sized and undersized buffers correctly
func TestXattrOverflow(t *testing.T) {
fn := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
- ioutil.WriteFile(fn, nil, 0600)
+ os.WriteFile(fn, nil, 0600)
attr := "user.foo123"
val := []byte("12341234")
diff --git a/tests/defaults/diriv_test.go b/tests/defaults/diriv_test.go
index 639b33d..48ba39a 100644
--- a/tests/defaults/diriv_test.go
+++ b/tests/defaults/diriv_test.go
@@ -1,7 +1,6 @@
package defaults
import (
- "io/ioutil"
"os"
"sync"
"sync/atomic"
@@ -19,7 +18,7 @@ func TestDirIVRace(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- err = ioutil.WriteFile(dir1+"/file", nil, 0600)
+ err = os.WriteFile(dir1+"/file", nil, 0600)
if err != nil {
t.Fatal(err)
}
@@ -31,7 +30,7 @@ func TestDirIVRace(t *testing.T) {
t.Fatal(err)
}
file2 := dir2 + "/file"
- err = ioutil.WriteFile(file2, nil, 0600)
+ err = os.WriteFile(file2, nil, 0600)
if err != nil {
t.Fatal(err)
}
diff --git a/tests/defaults/getdents_linux.go b/tests/defaults/getdents_linux.go
new file mode 100644
index 0000000..57956ce
--- /dev/null
+++ b/tests/defaults/getdents_linux.go
@@ -0,0 +1,9 @@
+package defaults
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+func getdents(fd int, buf []byte) (int, error) {
+ return unix.Getdents(fd, buf)
+}
diff --git a/tests/defaults/getdents_other.go b/tests/defaults/getdents_other.go
new file mode 100644
index 0000000..c0552a8
--- /dev/null
+++ b/tests/defaults/getdents_other.go
@@ -0,0 +1,11 @@
+//go:build !linux
+
+package defaults
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+func getdents(fd int, buf []byte) (int, error) {
+ return unix.Getdirentries(fd, buf, nil)
+}
diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go
index d0210e2..a19f079 100644
--- a/tests/defaults/main_test.go
+++ b/tests/defaults/main_test.go
@@ -3,8 +3,8 @@ package defaults
import (
"bytes"
+ "fmt"
"io"
- "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -175,7 +175,7 @@ func TestXfs124(t *testing.T) {
func TestWrite0200File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestWrite0200File"
- err := ioutil.WriteFile(fn, nil, 0200)
+ err := os.WriteFile(fn, nil, 0200)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -211,7 +211,7 @@ func TestWrite0200File(t *testing.T) {
// Now we return EOPNOTSUPP and mv is happy.
func TestMvWarnings(t *testing.T) {
fn := test_helpers.TmpDir + "/TestMvWarnings"
- err := ioutil.WriteFile(fn, nil, 0600)
+ err := os.WriteFile(fn, nil, 0600)
if err != nil {
t.Fatalf("creating file failed: %v", err)
}
@@ -256,7 +256,7 @@ func TestMvWarningSymlink(t *testing.T) {
// See TestMvWarnings.
func TestCpWarnings(t *testing.T) {
fn := test_helpers.TmpDir + "/TestCpWarnings"
- err := ioutil.WriteFile(fn, []byte("foo"), 0600)
+ err := os.WriteFile(fn, []byte("foo"), 0600)
if err != nil {
t.Fatalf("creating file failed: %v", err)
}
@@ -506,3 +506,51 @@ func TestForceOwner(t *testing.T) {
t.Errorf("LOOKUP returned uid or gid != 1234: %#v", st)
}
}
+
+func TestSeekDir(t *testing.T) {
+ wd := test_helpers.DefaultPlainDir + "/" + t.Name()
+ err := os.Mkdir(wd, 0700)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < 10; i++ {
+ path := fmt.Sprintf(wd+"/%3d", i)
+ err = os.WriteFile(path, nil, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ fd, err := syscall.Open(wd, syscall.O_DIRECTORY|syscall.O_RDONLY, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer syscall.Close(fd)
+
+ buf := make([]byte, 1000)
+ n, err := getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("1st getdents returned %d bytes", n)
+
+ n, err = getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("2nd getdents returned %d bytes", n)
+
+ _, err = unix.Seek(fd, 0, 0)
+ if err != nil {
+ t.Error(err)
+ }
+
+ n, err = getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("3rd getdents (after seek) returned %d bytes", n)
+ if n == 0 {
+ t.Error("Seek did not have any effect")
+ }
+}
diff --git a/tests/defaults/overlayfs_test.go b/tests/defaults/overlayfs_test.go
index 8cb773d..32c0a90 100644
--- a/tests/defaults/overlayfs_test.go
+++ b/tests/defaults/overlayfs_test.go
@@ -4,7 +4,6 @@
package defaults
import (
- "io/ioutil"
"os"
"strings"
"testing"
@@ -43,7 +42,7 @@ func TestRenameWhiteout(t *testing.T) {
for _, n := range names {
pSrc := test_helpers.DefaultPlainDir + "/" + n[0]
pDst := test_helpers.DefaultPlainDir + "/" + n[1]
- if err := ioutil.WriteFile(pSrc, nil, 0200); err != nil {
+ if err := os.WriteFile(pSrc, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err := unix.Renameat2(-1, pSrc, -1, pDst, flags)
@@ -96,10 +95,10 @@ func TestRenameExchange(t *testing.T) {
for _, n := range names {
pSrc := test_helpers.DefaultPlainDir + "/" + n[0]
pDst := test_helpers.DefaultPlainDir + "/" + n[1]
- if err := ioutil.WriteFile(pSrc, nil, 0200); err != nil {
+ if err := os.WriteFile(pSrc, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
- if err := ioutil.WriteFile(pDst, nil, 0200); err != nil {
+ if err := os.WriteFile(pDst, nil, 0200); err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
err := unix.Renameat2(-1, pSrc, -1, pDst, unix.RENAME_EXCHANGE)
@@ -119,7 +118,7 @@ func TestRenameExchange(t *testing.T) {
}
}
-// Looks like the FUSE protocol does support O_TMPFILE yet
+// Looks like the FUSE protocol does not support O_TMPFILE yet
func TestOTmpfile(t *testing.T) {
p := test_helpers.DefaultPlainDir + "/" + t.Name()
fd, err := unix.Openat(-1, p, unix.O_TMPFILE, 0600)
diff --git a/tests/defaults/performance_test.go b/tests/defaults/performance_test.go
index 11d827f..8bae5c1 100644
--- a/tests/defaults/performance_test.go
+++ b/tests/defaults/performance_test.go
@@ -4,7 +4,6 @@ package defaults
import (
"fmt"
"io"
- "io/ioutil"
"os"
"testing"
@@ -96,7 +95,7 @@ func createFiles(t *testing.B, count int, size int) {
for i = 0; i < count; i++ {
file := fmt.Sprintf("%s/%d", dir, i)
if size > 0 {
- err = ioutil.WriteFile(file, buf, 0666)
+ err = os.WriteFile(file, buf, 0666)
} else {
var fh *os.File
fh, err = os.Create(file)
diff --git a/tests/deterministic_names/deterministic_names_test.go b/tests/deterministic_names/deterministic_names_test.go
index 00d80fc..330cfc3 100644
--- a/tests/deterministic_names/deterministic_names_test.go
+++ b/tests/deterministic_names/deterministic_names_test.go
@@ -4,7 +4,6 @@ package deterministic_names
import (
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -70,7 +69,7 @@ func TestDeterministicNames(t *testing.T) {
if err := os.RemoveAll(pDir + "/foo"); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(pDir+"/foo", nil, 0700); err != nil {
+ if err := os.WriteFile(pDir+"/foo", nil, 0700); err != nil {
t.Fatal(err)
}
_, err = os.Stat(cDir + "/" + fooEncrypted)
diff --git a/tests/example_filesystems/example_test_helpers.go b/tests/example_filesystems/example_test_helpers.go
index c56d75a..34e5786 100644
--- a/tests/example_filesystems/example_test_helpers.go
+++ b/tests/example_filesystems/example_test_helpers.go
@@ -1,7 +1,6 @@
package example_filesystems
import (
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -15,7 +14,7 @@ const statusTxtContent = "It works!\n"
func checkExampleFS(t *testing.T, dir string, rw bool) {
// Read regular file
statusFile := filepath.Join(dir, "status.txt")
- contentBytes, err := ioutil.ReadFile(statusFile)
+ contentBytes, err := os.ReadFile(statusFile)
if err != nil {
t.Error(err)
return
@@ -68,7 +67,7 @@ func checkExampleFSrw(t *testing.T, dir string, rw bool) {
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxx"
- contentBytes, err := ioutil.ReadFile(filepath.Join(dir, longname))
+ contentBytes, err := os.ReadFile(filepath.Join(dir, longname))
if err != nil {
t.Error(err)
return
diff --git a/tests/fsck/.gitignore b/tests/fsck/.gitignore
new file mode 100644
index 0000000..9c9febc
--- /dev/null
+++ b/tests/fsck/.gitignore
@@ -0,0 +1,7 @@
+# Cannot have these "evil" files in git because they break "go install"
+# https://github.com/golang/go/issues/28001
+#
+# "malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng"
+# "malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg"
+#
+malleable_base64/27AG8t-XZH7G9ou2OSD_z?g
diff --git a/tests/fsck/fsck_test.go b/tests/fsck/fsck_test.go
index b70fd49..de49819 100644
--- a/tests/fsck/fsck_test.go
+++ b/tests/fsck/fsck_test.go
@@ -50,6 +50,17 @@ func TestBrokenFsV14(t *testing.T) {
}
func TestMalleableBase64(t *testing.T) {
+ // Evil filenames. Cannot have them in git, because if we do,
+ // go install github.com/rfjakob/gocryptfs/v2@latest
+ // fails with
+ // g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg": invalid char '\r'
+ // g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg": invalid char '\r'
+ if err := os.WriteFile("malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng", nil, 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile("malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg", nil, 0644); err != nil {
+ t.Fatal(err)
+ }
cmd := exec.Command(test_helpers.GocryptfsBinary, "-fsck", "-extpass", "echo test", "malleable_base64")
outBin, err := cmd.CombinedOutput()
out := string(outBin)
diff --git a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g b/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
deleted file mode 100644
index e69de29..0000000
--- a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
+++ /dev/null
diff --git a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g b/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
deleted file mode 100644
index e69de29..0000000
--- a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
+++ /dev/null
diff --git a/tests/hkdf_sanity/sanity_test.go b/tests/hkdf_sanity/sanity_test.go
index f221439..27a1085 100644
--- a/tests/hkdf_sanity/sanity_test.go
+++ b/tests/hkdf_sanity/sanity_test.go
@@ -4,7 +4,6 @@
package hkdf_sanity
import (
- "io/ioutil"
"os"
"testing"
@@ -15,7 +14,7 @@ func TestBrokenContent(t *testing.T) {
cDir := "broken_content"
pDir := test_helpers.TmpDir + "/" + cDir
test_helpers.MountOrFatal(t, cDir, pDir, "-extpass", "echo test", "-wpanic=false")
- _, err := ioutil.ReadFile(pDir + "/status.txt")
+ _, err := os.ReadFile(pDir + "/status.txt")
if err == nil {
t.Error("this should fail")
}
diff --git a/tests/issue893.sh b/tests/issue893.sh
new file mode 100755
index 0000000..a1e7cdb
--- /dev/null
+++ b/tests/issue893.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Reproducer for https://github.com/rfjakob/gocryptfs/issues/893 .
+# Run this script as non-root against a root-mounted gocryptfs -allow_other.
+
+set -eu
+
+mountpoint $1
+cd $1
+
+work() {
+ for i in $(seq 100) ; do
+ D=mtest.$BASHPID.$i/foo/bar/baz
+ mkdir -p $D
+ touch $D/foo $D/bar
+ echo AAAAAAAAAAAAAAAAAAAAA > $D/foo
+ rm $D/foo
+ mkdir $D/baz
+ done
+}
+
+rm -Rf mtest.*
+echo .
+
+work &
+work &
+
+wait
diff --git a/tests/matrix/dir_test.go b/tests/matrix/dir_test.go
index 13d6712..e2ecf46 100644
--- a/tests/matrix/dir_test.go
+++ b/tests/matrix/dir_test.go
@@ -70,3 +70,27 @@ func TestHaveDotdot(t *testing.T) {
t.Errorf("have=%q want=%q", have, want)
}
}
+
+// mkdir used to report the wrong file mode when creating a read-only
+// director.
+// https://github.com/rfjakob/gocryptfs/issues/964
+func Test555Dir(t *testing.T) {
+ dir1 := test_helpers.DefaultPlainDir + "/" + t.Name()
+ err := os.Mkdir(dir1, 0555)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Avoid permission errors when cleaning up the directory later
+ defer syscall.Chmod(dir1, 0700)
+
+ var st syscall.Stat_t
+ err = syscall.Stat(dir1, &st)
+ if err != nil {
+ t.Fatal(err)
+ }
+ have := st.Mode & 0777
+ if have != 0555 {
+ t.Errorf("wrong mode. want 0555 have %04o", have)
+ }
+}
diff --git a/tests/matrix/main_test.go b/tests/matrix/main_test.go
new file mode 100644
index 0000000..cf0b6c4
--- /dev/null
+++ b/tests/matrix/main_test.go
@@ -0,0 +1,121 @@
+// Tests run for (almost all) combinations of openssl, aessiv, plaintextnames.
+//
+// File reading, writing, modification, truncate, ...
+//
+// Runs all tests N times, for the combinations of different flags specified
+// in the `matrix` variable.
+
+package matrix
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// Several tests need to be aware if plaintextnames is active or not, so make this
+// a global variable
+var testcase testcaseMatrix
+
+var ctlsockPath string
+
+type testcaseMatrix struct {
+ plaintextnames bool
+ openssl string
+ aessiv bool
+ raw64 bool
+ extraArgs []string
+}
+
+// isSet finds out if `extraArg` is set in `tc.extraArgs`
+func (tc *testcaseMatrix) isSet(extraArg string) bool {
+ for _, v := range tc.extraArgs {
+ if v == extraArg {
+ return true
+ }
+ }
+ return false
+}
+
+// This is the entry point for the tests
+func TestMain(m *testing.M) {
+ var matrix = []testcaseMatrix{
+ // Normal
+ {false, "auto", false, false, nil},
+ {false, "true", false, false, nil},
+ {false, "false", false, false, nil},
+ // Plaintextnames
+ {true, "true", false, false, nil},
+ {true, "false", false, false, nil},
+ // AES-SIV (does not use openssl, no need to test permutations)
+ {false, "auto", true, false, nil},
+ {true, "auto", true, false, nil},
+ // Raw64
+ {false, "auto", false, true, nil},
+ // -serialize_reads
+ {false, "auto", false, false, []string{"-serialize_reads"}},
+ {false, "auto", false, false, []string{"-sharedstorage"}},
+ {false, "auto", false, false, []string{"-deterministic-names"}},
+ // Test xchacha with and without openssl
+ {false, "true", false, true, []string{"-xchacha"}},
+ {false, "false", false, true, []string{"-xchacha"}},
+ }
+
+ // Make "testing.Verbose()" return the correct value
+ flag.Parse()
+ var i int
+ for i, testcase = range matrix {
+ if testcase.openssl == "true" && stupidgcm.BuiltWithoutOpenssl {
+ continue
+ }
+ if testing.Verbose() {
+ fmt.Printf("matrix: testcase = %#v\n", testcase)
+ }
+ createDirIV := true
+ if testcase.plaintextnames {
+ createDirIV = false
+ } else if testcase.isSet("-deterministic-names") {
+ createDirIV = false
+ }
+ ctlsockPath = fmt.Sprintf("%s/ctlsock.%d", test_helpers.TmpDir, i)
+ test_helpers.ResetTmpDir(createDirIV)
+ opts := []string{"-zerokey", "-ctlsock", ctlsockPath}
+ //opts = append(opts, "-fusedebug")
+ opts = append(opts, fmt.Sprintf("-openssl=%v", testcase.openssl))
+ opts = append(opts, fmt.Sprintf("-plaintextnames=%v", testcase.plaintextnames))
+ opts = append(opts, fmt.Sprintf("-aessiv=%v", testcase.aessiv))
+ opts = append(opts, fmt.Sprintf("-raw64=%v", testcase.raw64))
+ opts = append(opts, testcase.extraArgs...)
+ test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, opts...)
+ before := test_helpers.ListFds(0, test_helpers.TmpDir)
+ t0 := time.Now()
+ r := m.Run()
+ if testing.Verbose() {
+ fmt.Printf("matrix[%d]: run took %v\n", i, time.Since(t0))
+ }
+ // Catch fd leaks in the tests. NOTE: this does NOT catch leaks in
+ // the gocryptfs FUSE process, but only in the tests that access it!
+ // All fds that point outside TmpDir are not interesting (the Go test
+ // infrastucture creates temporary log files we don't care about).
+ after := test_helpers.ListFds(0, test_helpers.TmpDir)
+ if len(before) != len(after) {
+ fmt.Printf("fd leak in test process? before, after:\n%v\n%v\n", before, after)
+ os.Exit(1)
+ }
+ test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
+ if r != 0 {
+ fmt.Printf("TestMain: matrix[%d] = %#v failed\n", i, testcase)
+ os.Exit(r)
+ }
+ // The ctlsock file is deleted asynchronously after unmount.
+ // Ensure it is delete here so it does not race (and trip up) ResetTmpDir() of the next
+ // loop iteration.
+ os.Remove(ctlsockPath)
+ }
+ os.Exit(0)
+}
diff --git a/tests/matrix/matrix_test.go b/tests/matrix/matrix_test.go
index 417e126..a2ec549 100644
--- a/tests/matrix/matrix_test.go
+++ b/tests/matrix/matrix_test.go
@@ -1,16 +1,8 @@
-// Tests run for (almost all) combinations of openssl, aessiv, plaintextnames.
package matrix
-// File reading, writing, modification, truncate, ...
-//
-// Runs all tests N times, for the combinations of different flags specified
-// in the `matrix` variable.
-
import (
"bytes"
- "flag"
"fmt"
- "io/ioutil"
"math/rand"
"os"
"os/exec"
@@ -20,109 +12,14 @@ import (
"sync"
"syscall"
"testing"
- "time"
"golang.org/x/sys/unix"
- "github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
+ "github.com/rfjakob/gocryptfs/v2/ctlsock"
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
-// Several tests need to be aware if plaintextnames is active or not, so make this
-// a global variable
-var testcase testcaseMatrix
-
-type testcaseMatrix struct {
- plaintextnames bool
- openssl string
- aessiv bool
- raw64 bool
- extraArgs []string
-}
-
-// isSet finds out if `extraArg` is set in `tc.extraArgs`
-func (tc *testcaseMatrix) isSet(extraArg string) bool {
- for _, v := range tc.extraArgs {
- if v == extraArg {
- return true
- }
- }
- return false
-}
-
-var matrix = []testcaseMatrix{
- // Normal
- {false, "auto", false, false, nil},
- {false, "true", false, false, nil},
- {false, "false", false, false, nil},
- // Plaintextnames
- {true, "true", false, false, nil},
- {true, "false", false, false, nil},
- // AES-SIV (does not use openssl, no need to test permutations)
- {false, "auto", true, false, nil},
- {true, "auto", true, false, nil},
- // Raw64
- {false, "auto", false, true, nil},
- // -serialize_reads
- {false, "auto", false, false, []string{"-serialize_reads"}},
- {false, "auto", false, false, []string{"-sharedstorage"}},
- {false, "auto", false, false, []string{"-deterministic-names"}},
- // Test xchacha with and without openssl
- {false, "true", false, true, []string{"-xchacha"}},
- {false, "false", false, true, []string{"-xchacha"}},
-}
-
-// This is the entry point for the tests
-func TestMain(m *testing.M) {
- // Make "testing.Verbose()" return the correct value
- flag.Parse()
- var i int
- for i, testcase = range matrix {
- if testcase.openssl == "true" && stupidgcm.BuiltWithoutOpenssl {
- continue
- }
- if testing.Verbose() {
- fmt.Printf("matrix: testcase = %#v\n", testcase)
- }
- createDirIV := true
- if testcase.plaintextnames {
- createDirIV = false
- } else if testcase.isSet("-deterministic-names") {
- createDirIV = false
- }
- test_helpers.ResetTmpDir(createDirIV)
- opts := []string{"-zerokey"}
- //opts = append(opts, "-fusedebug")
- opts = append(opts, fmt.Sprintf("-openssl=%v", testcase.openssl))
- opts = append(opts, fmt.Sprintf("-plaintextnames=%v", testcase.plaintextnames))
- opts = append(opts, fmt.Sprintf("-aessiv=%v", testcase.aessiv))
- opts = append(opts, fmt.Sprintf("-raw64=%v", testcase.raw64))
- opts = append(opts, testcase.extraArgs...)
- test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, opts...)
- before := test_helpers.ListFds(0, test_helpers.TmpDir)
- t0 := time.Now()
- r := m.Run()
- if testing.Verbose() {
- fmt.Printf("matrix: run took %v\n", time.Since(t0))
- }
- // Catch fd leaks in the tests. NOTE: this does NOT catch leaks in
- // the gocryptfs FUSE process, but only in the tests that access it!
- // All fds that point outside TmpDir are not interesting (the Go test
- // infrastucture creates temporary log files we don't care about).
- after := test_helpers.ListFds(0, test_helpers.TmpDir)
- if len(before) != len(after) {
- fmt.Printf("fd leak in test process? before, after:\n%v\n%v\n", before, after)
- os.Exit(1)
- }
- test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
- if r != 0 {
- fmt.Printf("TestMain: matrix[%d] = %#v failed\n", i, testcase)
- os.Exit(r)
- }
- }
- os.Exit(0)
-}
-
// Write `n` random bytes to filename `fn`, read again, compare hash
func testWriteN(t *testing.T, fn string, n int) string {
file, err := os.Create(test_helpers.DefaultPlainDir + "/" + fn)
@@ -327,7 +224,7 @@ func TestFileHoles(t *testing.T) {
foo := []byte("foo")
file.Write(foo)
file.WriteAt(foo, 4096)
- _, err = ioutil.ReadFile(fn)
+ _, err = os.ReadFile(fn)
if err != nil {
t.Error(err)
}
@@ -391,7 +288,7 @@ func TestRmwRace(t *testing.T) {
// but it must not be
// [oooooossss]
- buf, _ := ioutil.ReadFile(fn)
+ buf, _ := os.ReadFile(fn)
m := test_helpers.Md5hex(buf)
goodMd5[m] = goodMd5[m] + 1
@@ -495,7 +392,7 @@ func TestNameLengths(t *testing.T) {
}
func TestLongNames(t *testing.T) {
- fi, err := ioutil.ReadDir(test_helpers.DefaultCipherDir)
+ fi, err := os.ReadDir(test_helpers.DefaultCipherDir)
if err != nil {
t.Fatal(err)
}
@@ -607,7 +504,7 @@ func TestLongNames(t *testing.T) {
t.Error(err)
}
// Check for orphaned files
- fi, err = ioutil.ReadDir(test_helpers.DefaultCipherDir)
+ fi, err = os.ReadDir(test_helpers.DefaultCipherDir)
if err != nil {
t.Fatal(err)
}
@@ -740,7 +637,7 @@ func doTestUtimesNano(t *testing.T, path string) {
// Set nanoseconds by path, normal file
func TestUtimesNano(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/utimesnano"
- err := ioutil.WriteFile(path, []byte("foobar"), 0600)
+ err := os.WriteFile(path, []byte("foobar"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -788,7 +685,7 @@ func TestMagicNames(t *testing.T) {
t.Logf("Testing n=%q", n)
p := test_helpers.DefaultPlainDir + "/" + n
// Create file
- err := ioutil.WriteFile(p, []byte("xxxxxxx"), 0200)
+ err := os.WriteFile(p, []byte("xxxxxxx"), 0200)
if err != nil {
t.Fatalf("creating file %q failed: %v", n, err)
}
@@ -825,7 +722,7 @@ func TestMagicNames(t *testing.T) {
syscall.Unlink(p)
// Link
target := test_helpers.DefaultPlainDir + "/linktarget"
- err = ioutil.WriteFile(target, []byte("yyyyy"), 0600)
+ err = os.WriteFile(target, []byte("yyyyy"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -982,3 +879,117 @@ func TestRootIno(t *testing.T) {
t.Errorf("inode number of root dir is zero")
}
}
+
+// TestDirSize checks that we report the correct size for directories.
+//
+// https://github.com/rfjakob/gocryptfs/issues/951:
+// "cipherSize 30: incomplete last block" after upgrade to 2.6.0
+func TestDirSize(t *testing.T) {
+ pDir := test_helpers.DefaultPlainDir + "/" + t.Name()
+ err := os.Mkdir(pDir, 0700)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req := ctlsock.RequestStruct{
+ EncryptPath: t.Name(),
+ }
+ resp := test_helpers.QueryCtlSock(t, ctlsockPath, req)
+ if resp.Result == "" {
+ t.Fatal(resp)
+ }
+ cDir := test_helpers.DefaultCipherDir + "/" + resp.Result
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fstat := func(path string) (st syscall.Stat_t) {
+ fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer syscall.Close(fd)
+
+ err = syscall.Fstat(fd, &st)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return st
+ }
+
+ // Check that plaintext size is equal to ciphertext size and
+ // that stat and fstat gives the same result
+ compareSize := func() {
+ var pStat, cStat syscall.Stat_t
+ err = syscall.Stat(pDir, &pStat)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pFstat := fstat(pDir)
+
+ err = syscall.Stat(cDir, &cStat)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cFstat := fstat(cDir)
+
+ if pStat.Size != cStat.Size {
+ t.Errorf("stat size is different: pSt=%d cSt=%d", pStat.Size, cStat.Size)
+ }
+ if pStat.Size != pFstat.Size {
+ t.Errorf("stat size is different from fstat size: pStat=%d pFstat=%d", pStat.Size, pFstat.Size)
+ }
+ if pFstat.Size != cFstat.Size {
+ t.Errorf("fstat size is different: pSt=%d cSt=%d", pFstat.Size, cFstat.Size)
+ }
+ }
+
+ for i := 0; i < 100; i++ {
+ // Grow the directory by creating new files with long names inside it
+ path := fmt.Sprintf("%s/%0100d", pDir, i)
+ err = os.WriteFile(path, nil, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ compareSize()
+ }
+}
+
+// TestRenameExchangeOnGocryptfs tests the core RENAME_EXCHANGE functionality
+// on a mounted gocryptfs filesystem
+func TestRenameExchangeOnGocryptfs(t *testing.T) {
+ // Create two files with different content
+ file1 := filepath.Join(test_helpers.DefaultPlainDir, "file1.txt")
+ file2 := filepath.Join(test_helpers.DefaultPlainDir, "file2.txt")
+ content1 := []byte("Content of file 1")
+ content2 := []byte("Content of file 2")
+
+ if err := os.WriteFile(file1, content1, 0644); err != nil {
+ t.Fatalf("Failed to create file1: %v", err)
+ }
+ if err := os.WriteFile(file2, content2, 0644); err != nil {
+ t.Fatalf("Failed to create file2: %v", err)
+ }
+
+ // Use RENAME_EXCHANGE to atomically swap the files
+ err := syscallcompat.Renameat2(unix.AT_FDCWD, file1, unix.AT_FDCWD, file2, syscallcompat.RENAME_EXCHANGE)
+ if err != nil {
+ t.Fatalf("RENAME_EXCHANGE failed on gocryptfs: %v", err)
+ }
+
+ // Verify the files were swapped
+ newContent1, err := os.ReadFile(file1)
+ if err != nil {
+ t.Fatalf("Failed to read file1 after exchange: %v", err)
+ }
+ newContent2, err := os.ReadFile(file2)
+ if err != nil {
+ t.Fatalf("Failed to read file2 after exchange: %v", err)
+ }
+
+ if string(newContent1) != string(content2) {
+ t.Errorf("file1 content wrong after exchange. Expected: %s, Got: %s", content2, newContent1)
+ }
+ if string(newContent2) != string(content1) {
+ t.Errorf("file2 content wrong after exchange. Expected: %s, Got: %s", content1, newContent2)
+ }
+}
diff --git a/tests/matrix/symlink_darwin_test.go b/tests/matrix/symlink_darwin_test.go
new file mode 100644
index 0000000..be28d9d
--- /dev/null
+++ b/tests/matrix/symlink_darwin_test.go
@@ -0,0 +1,39 @@
+package matrix
+
+import (
+ "os"
+ "testing"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// TestOpenSymlinkDarwin checks that a symlink can be opened
+// using O_SYMLINK.
+func TestOpenSymlinkDarwin(t *testing.T) {
+ path := test_helpers.DefaultPlainDir + "/TestOpenSymlink"
+ target := "/target/does/not/exist"
+ err := os.Symlink(target, path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fd, err := unix.Open(path, unix.O_RDONLY|unix.O_SYMLINK, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer unix.Close(fd)
+ var st unix.Stat_t
+ if err := unix.Fstat(fd, &st); err != nil {
+ t.Fatal(err)
+ }
+ if st.Size != int64(len(target)) {
+ t.Errorf("wrong size: have=%d want=%d", st.Size, len(target))
+ }
+ if err := unix.Unlink(path); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Fstat(fd, &st); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/tests/matrix/symlink_linux_test.go b/tests/matrix/symlink_linux_test.go
new file mode 100644
index 0000000..fdb7051
--- /dev/null
+++ b/tests/matrix/symlink_linux_test.go
@@ -0,0 +1,47 @@
+package matrix
+
+import (
+ "os"
+ "testing"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// TestOpenSymlinkLinux checks that a symlink can be opened
+// using O_PATH.
+// Only works on Linux because is uses O_PATH and AT_EMPTY_PATH.
+// MacOS has O_SYMLINK instead (see TestOpenSymlinkDarwin).
+func TestOpenSymlinkLinux(t *testing.T) {
+ path := test_helpers.DefaultPlainDir + "/TestOpenSymlink"
+ target := "/target/does/not/exist"
+ err := os.Symlink(target, path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ how := unix.OpenHow{
+ Flags: unix.O_PATH | unix.O_NOFOLLOW,
+ }
+ fd, err := unix.Openat2(unix.AT_FDCWD, path, &how)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer unix.Close(fd)
+ var st unix.Stat_t
+ if err := unix.Fstatat(fd, "", &st, unix.AT_EMPTY_PATH); err != nil {
+ t.Fatal(err)
+ }
+ if st.Size != int64(len(target)) {
+ t.Errorf("wrong size: have=%d want=%d", st.Size, len(target))
+ }
+ if err := unix.Unlink(path); err != nil {
+ t.Fatal(err)
+ }
+ if err = unix.Fstatat(fd, "", &st, unix.AT_EMPTY_PATH); err != nil {
+ // That's a bug, but I have never heard of a use case that would break because of this.
+ // Also I don't see how to fix it, as gocryptfs does not get informed about the earlier
+ // Openat2().
+ t.Logf("posix compliance issue: deleted symlink cannot be accessed: Fstatat: %v", err)
+ }
+}
diff --git a/tests/plaintextnames/file_holes_test.go b/tests/plaintextnames/file_holes_test.go
index a17597a..7a356c0 100644
--- a/tests/plaintextnames/file_holes_test.go
+++ b/tests/plaintextnames/file_holes_test.go
@@ -7,7 +7,6 @@ import (
"os/exec"
"syscall"
"testing"
- "time"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
@@ -129,6 +128,8 @@ func doTestFileHoleCopy(t *testing.T, name string, writeOffsets []int64) {
// The test runs with -plaintextnames because that makes it easier to manipulate
// cipherdir directly.
func TestFileHoleCopy(t *testing.T) {
+ t.Skip("TODO: find out why this fails on recent kernels")
+
// | hole | x | hole | x | hole |
// truncate -s 50000 foo && dd if=/dev/zero of=foo bs=1 seek=10000 count=1 conv=notrunc && dd if=/dev/zero of=foo bs=1 seek=30000 count=1 conv=notrunc
name := "c0"
@@ -138,7 +139,6 @@ func TestFileHoleCopy(t *testing.T) {
return
}
- rand.Seed(time.Now().UnixNano())
for k := 0; k < 100; k++ {
c1 := make([]int64, 10)
for i := range c1 {
diff --git a/tests/plaintextnames/plaintextnames_test.go b/tests/plaintextnames/plaintextnames_test.go
index 4de0730..a755deb 100644
--- a/tests/plaintextnames/plaintextnames_test.go
+++ b/tests/plaintextnames/plaintextnames_test.go
@@ -4,7 +4,6 @@ package plaintextnames
import (
"fmt"
- "io/ioutil"
"os"
"syscall"
"testing"
@@ -63,7 +62,7 @@ func TestDirIV(t *testing.T) {
// else should work.
func TestFiltered(t *testing.T) {
filteredFile := pDir + "/gocryptfs.conf"
- err := ioutil.WriteFile(filteredFile, []byte("foo"), 0777)
+ err := os.WriteFile(filteredFile, []byte("foo"), 0777)
if err == nil {
t.Errorf("should have failed but didn't")
}
@@ -71,11 +70,11 @@ func TestFiltered(t *testing.T) {
if err == nil {
t.Errorf("should have failed but didn't")
}
- err = ioutil.WriteFile(pDir+"/gocryptfs.diriv", []byte("foo"), 0777)
+ err = os.WriteFile(pDir+"/gocryptfs.diriv", []byte("foo"), 0777)
if err != nil {
t.Error(err)
}
- subDir, err := ioutil.TempDir(pDir, "")
+ subDir, err := os.MkdirTemp(pDir, "")
if err != nil {
t.Fatal(err)
}
diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go
index b335456..4028ac3 100644
--- a/tests/reverse/correctness_test.go
+++ b/tests/reverse/correctness_test.go
@@ -3,7 +3,6 @@ package reverse_test
import (
"bytes"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -72,7 +71,7 @@ func TestSymlinkDentrySize(t *testing.T) {
}
symlink := "a_symlink"
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, "reverse_mnt_")
+ mnt, err := os.MkdirTemp(test_helpers.TmpDir, "reverse_mnt_")
if err != nil {
t.Fatal(err)
}
@@ -111,7 +110,7 @@ func TestConfigMapping(t *testing.T) {
if !test_helpers.VerifyExistence(t, c) {
t.Errorf("%s missing", c)
}
- data, err := ioutil.ReadFile(c)
+ data, err := os.ReadFile(c)
if err != nil {
t.Fatal(err)
}
@@ -233,7 +232,7 @@ func Test0100Dir(t *testing.T) {
t.Fatal(err)
}
file := dir + "/hello"
- err = ioutil.WriteFile(file, []byte("hello"), 0600)
+ err = os.WriteFile(file, []byte("hello"), 0600)
if err != nil {
t.Fatal(err)
}
@@ -295,6 +294,23 @@ func TestSeekData(t *testing.T) {
f.Close()
}
+// newWorkdir creates a new empty dir in dirA and returns the full path to it along
+// with the corresponding encrypted path in dirB
+func newWorkdir(t *testing.T) (workdirA, workdirB string) {
+ workdirA = dirA + "/" + t.Name()
+ if err := os.Mkdir(workdirA, 0700); err != nil {
+ t.Fatal(err)
+ }
+ // Find workdir in dirB (=encrypted view)
+ var st syscall.Stat_t
+ if err := syscall.Stat(workdirA, &st); err != nil {
+ t.Fatal(err)
+ }
+ workdirB = dirB + "/" + findIno(dirB, st.Ino)
+ t.Logf("newWorkdir: workdirA=%q workdirB=%q", workdirA, workdirB)
+ return
+}
+
// gocryptfs.longname.*.name of hardlinked files should not appear hardlinked (as the
// contents are different).
//
@@ -308,28 +324,22 @@ func TestHardlinkedLongname(t *testing.T) {
t.Skip()
}
- workdir := dirA + "/" + t.Name()
- if err := os.Mkdir(workdir, 0700); err != nil {
+ workdirA, workdirB := newWorkdir(t)
+
+ long1 := workdirA + "/" + strings.Repeat("x", 200)
+ if err := os.WriteFile(long1, []byte("hello"), 0600); err != nil {
t.Fatal(err)
}
- long1 := workdir + "/" + strings.Repeat("x", 200)
- if err := ioutil.WriteFile(long1, []byte("hello"), 0600); err != nil {
+ var long1_stat syscall.Stat_t
+ if err := syscall.Stat(long1, &long1_stat); err != nil {
t.Fatal(err)
}
- long2 := workdir + "/" + strings.Repeat("y", 220)
+ long2 := workdirA + "/" + strings.Repeat("y", 220)
if err := syscall.Link(long1, long2); err != nil {
t.Fatal(err)
}
- // Find workdir in encrypted view
- var st syscall.Stat_t
- if err := syscall.Stat(workdir, &st); err != nil {
- t.Fatal(err)
- }
- cWorkdir := dirB + "/" + findIno(dirB, st.Ino)
- t.Logf("workdir=%q cWorkdir=%q", workdir, cWorkdir)
-
- matches, err := filepath.Glob(cWorkdir + "/gocryptfs.longname.*.name")
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
if err != nil {
t.Fatal(err)
}
@@ -352,3 +362,61 @@ func TestHardlinkedLongname(t *testing.T) {
t.Errorf("Files %q have the same inode number - that's wrong!", matches)
}
}
+
+// With inode number reuse and hard links, we could have returned
+// wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+// (https://github.com/rfjakob/gocryptfs/issues/802).
+//
+// Now that this is fixed, ensure that rsync and similar tools pick up the new
+// correct files by advancing mtime and ctime by 10 seconds, which should be more
+// than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+func TestMtimePlus10(t *testing.T) {
+ if plaintextnames {
+ t.Skip("plaintextnames mode does not have virtual files")
+ }
+
+ workdirA, workdirB := newWorkdir(t)
+
+ long := workdirA + "/" + strings.Repeat("x", 200)
+ if err := os.WriteFile(long, nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+ long_stat, err := os.Stat(long)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ workdirA_stat, err := os.Stat(workdirA)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Find and check gocryptfs.longname.*.name
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(matches) != 1 {
+ t.Fatal(matches)
+ }
+ name_stat, err := os.Stat(matches[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if name_stat.ModTime().Unix() != long_stat.ModTime().Unix()+10 {
+ t.Errorf(".name file should show mtime+10")
+ }
+
+ // Check gocryptfs.diriv
+ if deterministic_names {
+ // No gocryptfs.diriv
+ return
+ }
+ diriv_stat, err := os.Stat(workdirB + "/gocryptfs.diriv")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diriv_stat.ModTime().Unix() != workdirA_stat.ModTime().Unix()+10 {
+ t.Errorf("diriv file should show mtime+10")
+ }
+}
diff --git a/tests/reverse/ctlsock_test.go b/tests/reverse/ctlsock_test.go
index f59fa45..8f9d843 100644
--- a/tests/reverse/ctlsock_test.go
+++ b/tests/reverse/ctlsock_test.go
@@ -1,7 +1,7 @@
package reverse_test
import (
- "io/ioutil"
+ "os"
"syscall"
"testing"
@@ -28,7 +28,7 @@ func TestCtlSockPathOps(t *testing.T) {
if plaintextnames {
t.Skip("this only tests encrypted names")
}
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, "reverse_mnt_")
+ mnt, err := os.MkdirTemp(test_helpers.TmpDir, "reverse_mnt_")
if err != nil {
t.Fatal(err)
}
@@ -74,7 +74,7 @@ func TestCtlSockCrash(t *testing.T) {
if plaintextnames {
t.Skip("this only tests encrypted names")
}
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, "reverse_mnt_")
+ mnt, err := os.MkdirTemp(test_helpers.TmpDir, "reverse_mnt_")
if err != nil {
t.Fatal(err)
}
diff --git a/tests/reverse/exclude_test.go b/tests/reverse/exclude_test.go
index f24ff2c..645d267 100644
--- a/tests/reverse/exclude_test.go
+++ b/tests/reverse/exclude_test.go
@@ -1,7 +1,8 @@
package reverse_test
import (
- "io/ioutil"
+ "log"
+ "os"
"path/filepath"
"testing"
@@ -19,36 +20,29 @@ func ctlsockEncryptPath(t *testing.T, sock string, path string) string {
return response.Result
}
-// doTestExcludeTestFs runs exclude tests against the exclude_test_fs folder
-func doTestExcludeTestFs(t *testing.T, flag string, patterns, visible, hidden []string) {
- // Mount reverse fs
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, t.Name())
- if err != nil {
- t.Fatal(err)
- }
- sock := mnt + ".sock"
- cliArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", sock}
+// doTestExcludeTestFs runs exclude tests against the exclude_test_fs folder.
+// flag is either "--exclude-wildcard" or "--exclude"
+func doTestExcludeTestFs(t *testing.T, flag string, patterns []string, tree directoryTree) {
+ var extraArgs []string
for _, v := range patterns {
- cliArgs = append(cliArgs, flag, v)
- }
- if plaintextnames {
- cliArgs = append(cliArgs, "-config", "exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames")
+ extraArgs = append(extraArgs, flag, v)
}
- test_helpers.MountOrFatal(t, "exclude_test_fs", mnt, cliArgs...)
+ // Mount reverse fs
+ backingDir, mnt, sock := newReverseFS(extraArgs)
defer test_helpers.UnmountPanic(mnt)
+ tree.createOnDisk(backingDir)
+
// Get encrypted version of visible and hidden paths
- cVisible := encryptExcludeTestPaths(t, sock, visible)
- cHidden := encryptExcludeTestPaths(t, sock, hidden)
+ cVisible := encryptExcludeTestPaths(t, sock, tree.visible())
+ cHidden := encryptExcludeTestPaths(t, sock, tree.hidden())
// Check that hidden paths are not there and visible paths are there
for _, v := range cHidden {
if test_helpers.VerifyExistence(t, mnt+"/"+v) {
t.Errorf("File %q is visible, but should be hidden", v)
}
- if nametransform.IsLongContent(filepath.Base(v)) {
- // TODO ???
- }
+
}
for _, v := range cVisible {
if !test_helpers.VerifyExistence(t, mnt+"/"+v) {
@@ -74,6 +68,44 @@ func encryptExcludeTestPaths(t *testing.T, socket string, pRelPaths []string) (o
return out
}
+type directoryTree struct {
+ visibleFiles []string
+ visibleDirs []string
+ hiddenFiles []string
+ hiddenDirs []string
+}
+
+func (tr *directoryTree) visible() []string {
+ return append(tr.visibleDirs, tr.visibleFiles...)
+}
+
+func (tr *directoryTree) hidden() []string {
+ return append(tr.hiddenDirs, tr.hiddenFiles...)
+}
+
+func (tr *directoryTree) createOnDisk(baseDir string) {
+ dirs := append(tr.hiddenDirs, tr.visibleDirs...)
+ for _, d := range dirs {
+ err := os.MkdirAll(baseDir+"/"+d, 0700)
+ if err != nil {
+ log.Panic(err)
+ }
+ }
+
+ files := append(tr.hiddenFiles, tr.visibleFiles...)
+ for _, f := range files {
+ d := filepath.Dir(f)
+ err := os.MkdirAll(baseDir+"/"+d, 0700)
+ if err != nil {
+ log.Panic(err)
+ }
+ err = os.WriteFile(baseDir+"/"+f, nil, 0600)
+ if err != nil {
+ log.Panic(err)
+ }
+ }
+}
+
// TestExcludeTestFs runs exclude tests against the exclude_test_fs folder.
func TestExcludeTestFs(t *testing.T) {
// --exclude-wildcard patterns, gitignore syntax
@@ -90,19 +122,22 @@ func TestExcludeTestFs(t *testing.T) {
"dir1/**/exclude", // ** matches any number of directories
"file3/", // pattern with trailing slash should not match a file
}
+ var tree directoryTree
// visible are plaintext paths that should be visible in the encrypted view
- visible := []string{
+ tree.visibleFiles = []string{
"file2",
"dir1/longfile1" + x240,
"dir1/longfile3" + x240,
- "longdir1" + x240,
"longdir1" + x240 + "/file1",
"longdir2" + x240 + "/file",
"longfile1" + x240,
"file3",
}
+ tree.visibleDirs = []string{
+ "longdir1" + x240,
+ }
// hidden are plaintext paths that should be hidden in the encrypted view
- hidden := []string{
+ tree.hiddenFiles = []string{
"bkp1~",
"dir1/file1",
"dir1/file2",
@@ -111,20 +146,21 @@ func TestExcludeTestFs(t *testing.T) {
"dir1/longfile2" + x240,
"dir1/subdir1/exclude",
"dir1/subdir1/subdir2/exclude",
- "dir2",
"dir2/file",
"dir2/longdir1" + x240 + "/file",
"dir2/longfile." + x240,
- "dir2/subdir",
"dir2/subdir/file",
"file1",
"longdir2" + x240 + "/bkp~",
"longfile2" + x240,
"longfile3" + x240,
}
-
- doTestExcludeTestFs(t, "-exclude-wildcard", patterns, visible, hidden)
- doTestExcludeTestFs(t, "-ew", patterns, visible, hidden)
+ tree.hiddenDirs = []string{
+ "dir2",
+ "dir2/subdir",
+ }
+ doTestExcludeTestFs(t, "-exclude-wildcard", patterns, tree)
+ doTestExcludeTestFs(t, "-ew", patterns, tree)
}
// Exclude everything using "/*", then selectively include only dir1 using "!/dir1"
@@ -135,23 +171,28 @@ func TestExcludeAllOnlyDir1(t *testing.T) {
"*",
"!/dir1",
}
+ var tree directoryTree
// visible are plaintext paths that should be visible in the encrypted view
- visible := []string{
+ tree.visibleDirs = []string{
"dir1",
+ }
+ tree.visibleFiles = []string{
"dir1/file1",
}
// hidden are plaintext paths that should be hidden in the encrypted view
- hidden := []string{
+ tree.hiddenDirs = []string{
"dir2",
+ "dir2/subdir",
+ }
+ tree.hiddenFiles = []string{
"dir2/file",
"dir2/longdir1" + x240 + "/file",
"dir2/longfile." + x240,
- "dir2/subdir",
"dir2/subdir/file",
"file1",
"longdir2" + x240 + "/bkp~",
"longfile2" + x240,
"longfile3" + x240,
}
- doTestExcludeTestFs(t, "-exclude-wildcard", patterns, visible, hidden)
+ doTestExcludeTestFs(t, "-exclude-wildcard", patterns, tree)
}
diff --git a/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf b/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf
deleted file mode 100644
index 835d11c..0000000
--- a/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "Creator": "gocryptfs v1.5-41-gf48b731-dirty",
- "EncryptedKey": "FkACqloUeFZesem0UzRD3ezLXtPl8wIAxEHoIEfZxFdLMQeWOxqtw5xopJagDWE/GI1VFSUIrJIIIwwgMipmYA==",
- "ScryptObject": {
- "Salt": "UVfIgV31uj/voHWI4GqGwsTcbVKyYDOWvbleqJKhZbk=",
- "N": 1024,
- "R": 8,
- "P": 1,
- "KeyLen": 32
- },
- "Version": 2,
- "FeatureFlags": [
- "GCMIV128",
- "HKDF",
- "DirIV",
- "EMENames",
- "LongNames",
- "Raw64",
- "AESSIV"
- ]
-}
diff --git a/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames b/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames
deleted file mode 100644
index 9cb762c..0000000
--- a/tests/reverse/exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "Creator": "gocryptfs v1.5-41-gf48b731-dirty",
- "EncryptedKey": "wAmckZb7QsIv/GCdkhb5ep8TwJa44qhnswn5tbER6Tifk8TbUmkwBTceaTtYfHAnTQ48q9mnIlcN9cfbNe5oPw==",
- "ScryptObject": {
- "Salt": "o5XJ78TgG85zZXRnU55ZqHhKLbPge6jsyDiqrLvSqe0=",
- "N": 1024,
- "R": 8,
- "P": 1,
- "KeyLen": 32
- },
- "Version": 2,
- "FeatureFlags": [
- "GCMIV128",
- "HKDF",
- "PlaintextNames",
- "AESSIV"
- ]
-}
diff --git a/tests/reverse/exclude_test_fs/bkp1~ b/tests/reverse/exclude_test_fs/bkp1~
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/bkp1~
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/exclude b/tests/reverse/exclude_test_fs/dir1/exclude
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/exclude
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/file1 b/tests/reverse/exclude_test_fs/dir1/file1
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/file1
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/file2 b/tests/reverse/exclude_test_fs/dir1/file2
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/file2
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~ b/tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/dir1/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/dir1/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/subdir1/exclude b/tests/reverse/exclude_test_fs/dir1/subdir1/exclude
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/subdir1/exclude
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude b/tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir1/subdir1/subdir2/exclude
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir2/file b/tests/reverse/exclude_test_fs/dir2/file
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir2/file
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir2/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file b/tests/reverse/exclude_test_fs/dir2/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir2/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir2/longfile.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/dir2/longfile.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir2/longfile.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/dir2/subdir/file b/tests/reverse/exclude_test_fs/dir2/subdir/file
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/dir2/subdir/file
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/file1 b/tests/reverse/exclude_test_fs/file1
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/file1
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/file2 b/tests/reverse/exclude_test_fs/file2
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/file2
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/file3 b/tests/reverse/exclude_test_fs/file3
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/file3
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1 b/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file1
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~ b/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/bkp~
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file b/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/file
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx b/tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
deleted file mode 100644
index e69de29..0000000
--- a/tests/reverse/exclude_test_fs/longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+++ /dev/null
diff --git a/tests/reverse/force_owner_test.go b/tests/reverse/force_owner_test.go
index 6e47b50..aeb0a4e 100644
--- a/tests/reverse/force_owner_test.go
+++ b/tests/reverse/force_owner_test.go
@@ -1,7 +1,6 @@
package reverse_test
import (
- "io/ioutil"
"net/url"
"os"
"syscall"
@@ -13,7 +12,7 @@ import (
func TestForceOwner(t *testing.T) {
// Let's not explode with "TempDir: pattern contains path separator"
myEscapedName := url.PathEscape(t.Name())
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, myEscapedName)
+ mnt, err := os.MkdirTemp(test_helpers.TmpDir, myEscapedName)
if err != nil {
t.Fatal(err)
}
diff --git a/tests/reverse/inomap_test.go b/tests/reverse/inomap_test.go
index ff78f4c..fadf6a5 100644
--- a/tests/reverse/inomap_test.go
+++ b/tests/reverse/inomap_test.go
@@ -85,7 +85,7 @@ func TestVirtualFileIno(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- dirents, err := fd.Readdirnames(0)
+ _, err = fd.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
@@ -104,7 +104,7 @@ func TestVirtualFileIno(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- dirents, err = fd.Readdirnames(0)
+ dirents, err := fd.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
diff --git a/tests/reverse/main_test.go b/tests/reverse/main_test.go
index 2fc9e5e..6a3ed9b 100644
--- a/tests/reverse/main_test.go
+++ b/tests/reverse/main_test.go
@@ -2,7 +2,9 @@ package reverse_test
import (
"bytes"
+ "flag"
"fmt"
+ "log"
"os"
"testing"
@@ -31,6 +33,8 @@ var dirC string
func TestMain(m *testing.M) {
var r int
+ flag.Parse()
+
testcases := []struct {
plaintextnames bool
deterministic_names bool
@@ -40,23 +44,17 @@ func TestMain(m *testing.M) {
{false, true},
}
for i, tc := range testcases {
- argsA := []string{"-reverse"}
+ // Fill the global vars
plaintextnames, deterministic_names = tc.plaintextnames, tc.deterministic_names
- if tc.plaintextnames {
- argsA = append(argsA, "-plaintextnames")
- } else if tc.deterministic_names {
- argsA = append(argsA, "-deterministic-names")
+ if testing.Verbose() {
+ log.Printf("TestMain: plaintextnames=%v deterministic_names=%v", plaintextnames, deterministic_names)
}
- dirA = test_helpers.InitFS(nil, argsA...)
- dirB = test_helpers.TmpDir + "/b"
+
+ dirA, dirB, _ = newReverseFS(nil)
dirC = test_helpers.TmpDir + "/c"
- if err := os.Mkdir(dirB, 0700); err != nil {
- panic(err)
- }
if err := os.Mkdir(dirC, 0700); err != nil {
panic(err)
}
- test_helpers.MountOrExit(dirA, dirB, "-reverse", "-extpass", "echo test")
test_helpers.MountOrExit(dirB, dirC, "-extpass", "echo test")
r = m.Run()
test_helpers.UnmountPanic(dirC)
@@ -73,3 +71,24 @@ func TestMain(m *testing.M) {
}
os.Exit(r)
}
+
+// newReverseFS creates and mounts a new, empty reverse filesystem.
+func newReverseFS(extraMountArgs []string) (backingDir, mntDir, ctlsockPath string) {
+ args := []string{"-reverse"}
+ if plaintextnames {
+ args = append(args, "-plaintextnames")
+ } else if deterministic_names {
+ args = append(args, "-deterministic-names")
+ }
+ backingDir = test_helpers.InitFS(nil, args...)
+ mntDir = backingDir + ".mnt"
+ ctlsockPath = mntDir + ".sock"
+ mountArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", ctlsockPath}
+ mountArgs = append(mountArgs, extraMountArgs...)
+ test_helpers.MountOrExit(backingDir, mntDir, mountArgs...)
+
+ if testing.Verbose() {
+ log.Printf("newReverseFS: mounted %q on %q", backingDir, mntDir)
+ }
+ return
+}
diff --git a/tests/reverse/one_file_system_test.go b/tests/reverse/one_file_system_test.go
index 61190ea..823cf62 100644
--- a/tests/reverse/one_file_system_test.go
+++ b/tests/reverse/one_file_system_test.go
@@ -1,7 +1,6 @@
package reverse_test
import (
- "io/ioutil"
"net/url"
"os"
"runtime"
@@ -17,7 +16,7 @@ func TestOneFileSystem(t *testing.T) {
}
// Let's not explode with "TempDir: pattern contains path separator"
myEscapedName := url.PathEscape(t.Name())
- mnt, err := ioutil.TempDir(test_helpers.TmpDir, myEscapedName)
+ mnt, err := os.MkdirTemp(test_helpers.TmpDir, myEscapedName)
if err != nil {
t.Fatal(err)
}
@@ -33,7 +32,7 @@ func TestOneFileSystem(t *testing.T) {
// Copied from inomap
const maxPassthruIno = 1<<48 - 1
- entries, err := ioutil.ReadDir(mnt)
+ entries, err := os.ReadDir(mnt)
if err != nil {
t.Fatal(err)
}
@@ -43,7 +42,11 @@ func TestOneFileSystem(t *testing.T) {
// We are only interested in directories
continue
}
- st := e.Sys().(*syscall.Stat_t)
+ info, err := e.Info()
+ if err != nil {
+ continue
+ }
+ st := info.Sys().(*syscall.Stat_t)
// The inode numbers of files with a different device number are remapped
// to something above maxPassthruIno
if st.Ino > maxPassthruIno {
diff --git a/tests/reverse/xattr_test.go b/tests/reverse/xattr_test.go
index 8002604..b6a7b34 100644
--- a/tests/reverse/xattr_test.go
+++ b/tests/reverse/xattr_test.go
@@ -2,12 +2,14 @@ package reverse_test
import (
"fmt"
- "io/ioutil"
+ "os"
"path/filepath"
+ "strings"
"syscall"
"testing"
"github.com/pkg/xattr"
+ "golang.org/x/sys/unix"
)
func xattrSupported(path string) bool {
@@ -16,26 +18,21 @@ func xattrSupported(path string) bool {
return true
}
err2 := err.(*xattr.Error)
- if err2.Err == syscall.EOPNOTSUPP {
- return false
- }
- return true
+ return err2.Err != syscall.EOPNOTSUPP
}
func TestXattrList(t *testing.T) {
- t.Skip("TODO: not implemented yet in reverse mode")
-
if !xattrSupported(dirA) {
t.Skip()
}
fnA := filepath.Join(dirA, t.Name())
- err := ioutil.WriteFile(fnA, nil, 0700)
+ err := os.WriteFile(fnA, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
val := []byte("xxxxxxxxyyyyyyyyyyyyyyyzzzzzzzzzzzzz")
num := 20
- var namesA map[string]string
+ namesA := map[string]string{}
for i := 1; i <= num; i++ {
attr := fmt.Sprintf("user.TestXattrList.%02d", i)
err = xattr.LSet(fnA, attr, val)
@@ -49,9 +46,14 @@ func TestXattrList(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- var namesC map[string]string
+ namesC := map[string]string{}
for _, n := range tmp {
- namesC[n] = string(val)
+ if strings.HasPrefix(n, "security.") {
+ t.Logf("Ignoring xattr %q", n)
+ continue
+ }
+ v, _ := xattr.LGet(fnC, n)
+ namesC[n] = string(v)
}
if len(namesA) != len(namesC) {
t.Errorf("wrong number of names, want=%d have=%d", len(namesA), len(namesC))
@@ -64,3 +66,18 @@ func TestXattrList(t *testing.T) {
}
}
}
+
+// Shouldn't get EINVAL when querying the mountpoint.
+func TestXattrGetMountpoint(t *testing.T) {
+ _, err := xattr.LGet(dirB, "user.foo453465324")
+ if err == nil {
+ return
+ }
+ e2 := err.(*xattr.Error)
+ if e2.Unwrap() == unix.EINVAL {
+ t.Errorf("LGet: %v", err)
+ }
+ // Let's see what LList says
+ _, err = xattr.LList(dirB)
+ t.Logf("LList: err=%v", err)
+}
diff --git a/tests/root_test/btrfs_test.go b/tests/root_test/btrfs_test.go
new file mode 100644
index 0000000..898cd39
--- /dev/null
+++ b/tests/root_test/btrfs_test.go
@@ -0,0 +1,110 @@
+package root_test
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// createBtrfsImage creates a btrfs image file, formats it, and mounts it.
+// Returns the mount path and a cleanup function.
+func createBtrfsImage(t *testing.T) (mnt string, cleanup func()) {
+ t.Helper()
+
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ _, err := exec.LookPath("mkfs.btrfs")
+ if err != nil {
+ t.Skip("mkfs.btrfs not found, skipping test")
+ }
+
+ img := filepath.Join(test_helpers.TmpDir, t.Name()+".img")
+ f, err := os.Create(img)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ // minimum size for each btrfs device is 114294784
+ err = f.Truncate(200 * 1024 * 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Format as Btrfs
+ cmd := exec.Command("mkfs.btrfs", img)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Logf("%q", cmd.Args)
+ t.Log(string(out))
+ t.Fatal(err)
+ }
+
+ // Mount
+ mnt = img + ".mnt"
+ err = os.Mkdir(mnt, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cmd = exec.Command("mount", img, mnt)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Log(string(out))
+ t.Fatal(err)
+ }
+
+ cleanup = func() {
+ syscall.Unmount(mnt, 0)
+ syscall.Unlink(img)
+ }
+ return mnt, cleanup
+}
+
+// TestBtrfsQuirks needs root permissions because it creates a loop disk
+func TestBtrfsQuirks(t *testing.T) {
+ mnt, cleanup := createBtrfsImage(t)
+ defer cleanup()
+
+ quirk := syscallcompat.DetectQuirks(mnt)
+ if quirk != syscallcompat.QuirkBtrfsBrokenFalloc {
+ t.Errorf("wrong quirk: %v", quirk)
+ }
+}
+
+// TestBtrfsQuirksNoCow verifies that when the backing directory has
+// the NOCOW attribute (chattr +C), the QuirkBtrfsBrokenFalloc quirk
+// is NOT set, because fallocate works correctly with NOCOW.
+func TestBtrfsQuirksNoCow(t *testing.T) {
+ mnt, cleanup := createBtrfsImage(t)
+ defer cleanup()
+
+ _, err := exec.LookPath("chattr")
+ if err != nil {
+ t.Skip("chattr not found, skipping test")
+ }
+
+ // Create a subdirectory with NOCOW attribute
+ nocowDir := filepath.Join(mnt, "nocow")
+ err = os.Mkdir(nocowDir, 0700)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cmd := exec.Command("chattr", "+C", nocowDir)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Log(string(out))
+ t.Fatal(err)
+ }
+
+ quirk := syscallcompat.DetectQuirks(nocowDir)
+ if quirk&syscallcompat.QuirkBtrfsBrokenFalloc != 0 {
+ t.Errorf("QuirkBtrfsBrokenFalloc should not be set on NOCOW directory, got quirks: %v", quirk)
+ }
+}
diff --git a/tests/root_test/issue893_test.go b/tests/root_test/issue893_test.go
new file mode 100644
index 0000000..2db0b19
--- /dev/null
+++ b/tests/root_test/issue893_test.go
@@ -0,0 +1,98 @@
+//go:build linux
+
+package root_test
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// gocryptfs v2.5.0 upgraded x/sys/unix and we found out that, since
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51 ,
+// unix.Setreuid() and friends now affect the whole process instead of only the
+// current thread, breaking allow_other: https://github.com/rfjakob/gocryptfs/issues/893
+//
+// Let's not have this happen again by testing it here.
+func TestConcurrentUserOps(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+
+ oneStressor := func(tid int) {
+ defer wg.Done()
+ err := asUser(10000+tid, 20000+tid, nil, func() (err error) {
+ for i := 0; i < 100; i++ {
+ d := fmt.Sprintf("%s/tid%d.i%d/foo/bar/baz", test_helpers.DefaultPlainDir, tid, i)
+ if err = os.MkdirAll(d, 0700); err != nil {
+ return
+ }
+ if err = os.WriteFile(d+"/foo", nil, 0400); err != nil {
+ return
+ }
+ if err = os.WriteFile(d+"/bar", []byte("aaaaaaaaaaaaaaaaaaaaa"), 0400); err != nil {
+ return
+ }
+ if err = syscall.Unlink(d + "/foo"); err != nil {
+ return
+ }
+ if err = os.Mkdir(d+"/foo", 0700); err != nil {
+ return
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ }
+
+ threads := 4
+ wg.Add(threads)
+ for tid := 0; tid < threads; tid++ {
+ go oneStressor(tid)
+ }
+ wg.Wait()
+}
+
+// Test that our root_test.asUser function works as expected under concurrency by
+// similating a long-runnig operation with sleep(10ms).
+// https://github.com/rfjakob/gocryptfs/issues/893
+func TestAsUserSleep(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+ f := func(euid_want int) error {
+ euid_have := syscall.Geteuid()
+ if euid_want != euid_have {
+ return fmt.Errorf("wrong euid: want=%d have=%d", euid_want, euid_have)
+ }
+ time.Sleep(10 * time.Millisecond)
+ euid_have2 := syscall.Geteuid()
+ if euid_want != euid_have2 {
+ return fmt.Errorf("wrong euid: want=%d have2=%d", euid_want, euid_have2)
+ }
+ return nil
+ }
+ threads := 100
+ wg.Add(threads)
+ for i := 0; i < threads; i++ {
+ go func(i int) {
+ defer wg.Done()
+ err := asUser(10000+i, 20000+i, nil, func() error { return f(10000 + i) })
+ if err != nil {
+ t.Error(err)
+ }
+ }(i)
+ }
+ wg.Wait()
+}
diff --git a/tests/root_test/main_test.go b/tests/root_test/main_test.go
new file mode 100644
index 0000000..d6d5cc4
--- /dev/null
+++ b/tests/root_test/main_test.go
@@ -0,0 +1,20 @@
+//go:build linux
+
+package root_test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+func TestMain(m *testing.M) {
+ test_helpers.ResetTmpDir(true)
+ os.Chmod(test_helpers.DefaultCipherDir, 0777)
+ test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey", "-allow_other")
+ r := m.Run()
+ test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
+ os.RemoveAll(test_helpers.TmpDir)
+ os.Exit(r)
+}
diff --git a/tests/root_test/root_test.go b/tests/root_test/root_test.go
index 23b44d0..650d802 100644
--- a/tests/root_test/root_test.go
+++ b/tests/root_test/root_test.go
@@ -1,12 +1,10 @@
//go:build linux
-// +build linux
// Package root_test contains tests that need root
// permissions to run
package root_test
import (
- "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -17,8 +15,6 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
- "golang.org/x/sys/unix"
-
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
@@ -26,36 +22,23 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- err := unix.Setgroups(supplementaryGroups)
+ err := syscallcompat.Setgroups(supplementaryGroups)
if err != nil {
return err
}
- defer func() {
- err = unix.Setgroups(nil)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setregid(-1, gid)
+ defer syscallcompat.SetgroupsPanic(nil)
+
+ err = syscallcompat.Setregid(-1, gid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setregid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setreuid(-1, uid)
+ defer syscallcompat.SetregidPanic(-1, 0)
+
+ err = syscallcompat.Setreuid(-1, uid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setreuid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
+ defer syscallcompat.SetreuidPanic(-1, 0)
ret := f()
@@ -67,13 +50,13 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
//
// How to check:
// ps -o tid,pid,euid,ruid,suid,egid,rgid,sgid,cmd -eL
- err = unix.Setresuid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno := syscall.RawSyscall(syscall.SYS_SETRESUID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
- err = unix.Setresgid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno = syscall.RawSyscall(syscall.SYS_SETRESGID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
return ret
@@ -83,16 +66,11 @@ func TestSupplementaryGroups(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("must run as root")
}
- cDir := test_helpers.InitFS(t)
- os.Chmod(cDir, 0755)
- pDir := cDir + ".mnt"
- test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test")
- defer test_helpers.UnmountPanic(pDir)
// We need an unrestricted umask
syscall.Umask(0000)
- dir1 := pDir + "/dir1"
+ dir1 := test_helpers.DefaultPlainDir + "/dir1"
err := os.Mkdir(dir1, 0770)
if err != nil {
t.Fatal(err)
@@ -224,7 +202,7 @@ func TestDiskFull(t *testing.T) {
}
t.Logf("sz1=%d, sz2=%d", sz1, sz2)
- foo1, err := ioutil.ReadFile(mnt + "/foo1")
+ foo1, err := os.ReadFile(mnt + "/foo1")
if err != nil {
t.Fatal(err)
}
@@ -232,7 +210,7 @@ func TestDiskFull(t *testing.T) {
t.Fail()
}
- foo2, err := ioutil.ReadFile(mnt + "/foo2")
+ foo2, err := os.ReadFile(mnt + "/foo2")
if err != nil {
t.Fatal(err)
}
@@ -252,7 +230,7 @@ func TestAcl(t *testing.T) {
defer test_helpers.UnmountPanic(pDir)
f1 := pDir + "/f1"
- if err := ioutil.WriteFile(f1, []byte("hello world\n"), 000); err != nil {
+ if err := os.WriteFile(f1, []byte("hello world\n"), 000); err != nil {
t.Fatal(err)
}
@@ -322,62 +300,11 @@ func TestAcl(t *testing.T) {
}
}
-// TestBtrfsQuirks needs root permissions because it creates a loop disk
-func TestBtrfsQuirks(t *testing.T) {
- if os.Getuid() != 0 {
- t.Skip("must run as root")
- }
-
- img := filepath.Join(test_helpers.TmpDir, t.Name()+".img")
- f, err := os.Create(img)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- // minimum size for each btrfs device is 114294784
- err = f.Truncate(200 * 1024 * 1024)
- if err != nil {
- t.Fatal(err)
- }
-
- // Format as Btrfs
- cmd := exec.Command("mkfs.btrfs", img)
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Logf("%q", cmd.Args)
- t.Log(string(out))
- t.Fatal(err)
- }
-
- // Mount
- mnt := img + ".mnt"
- err = os.Mkdir(mnt, 0600)
- if err != nil {
- t.Fatal(err)
- }
- cmd = exec.Command("mount", img, mnt)
- out, err = cmd.CombinedOutput()
- if err != nil {
- t.Log(string(out))
- t.Fatal(err)
- }
- defer syscall.Unlink(img)
- defer syscall.Unmount(mnt, 0)
-
- quirk := syscallcompat.DetectQuirks(mnt)
- if quirk != syscallcompat.QuirkBrokenFalloc {
- t.Errorf("wrong quirk: %v", quirk)
- }
-}
-
func TestOverlay(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("must run as root")
}
cDir := test_helpers.InitFS(t)
- if syscallcompat.DetectQuirks(cDir)&syscallcompat.QuirkNoUserXattr != 0 {
- t.Logf("No user xattrs! overlay mount will likely fail.")
- }
os.Chmod(cDir, 0755)
pDir := cDir + ".mnt"
test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test")
@@ -400,3 +327,39 @@ func TestOverlay(t *testing.T) {
}
defer syscall.Unmount(ovlMnt, 0)
}
+
+// Check that mkdir and file create works with force_owner and runnung as root
+// https://github.com/rfjakob/gocryptfs/issues/783
+func TestRootForceOwner(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+ cDir := test_helpers.InitFS(t)
+ pDir := cDir + ".mnt"
+ test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test", "-force_owner=1234:1234")
+ defer test_helpers.UnmountPanic(pDir)
+
+ err := asUser(1234, 1234, nil, func() error {
+ return os.Mkdir(pDir+"/dir1", 0700)
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ err = asUser(1234, 1234, nil, func() error {
+ f, err := os.Create(pDir + "/file1")
+ if err == nil {
+ f.Close()
+ }
+ return err
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ err = asUser(1234, 1234, nil, func() error {
+ sock := pDir + "/sock"
+ return syscall.Mknod(sock, syscall.S_IFSOCK|0600, 0)
+ })
+ if err != nil {
+ t.Errorf("mknod: %v", err)
+ }
+}
diff --git a/tests/stress_tests/fsstress-gocryptfs.bash b/tests/stress_tests/fsstress-gocryptfs.bash
index 30ea456..e6c3281 100755
--- a/tests/stress_tests/fsstress-gocryptfs.bash
+++ b/tests/stress_tests/fsstress-gocryptfs.bash
@@ -27,11 +27,11 @@ MYNAME=$(basename "$0")
source ../fuse-unmount.bash
# fsstress binary
-FSSTRESS=/opt/fuse-xfstests/ltp/fsstress
+FSSTRESS=/var/lib/xfstests/ltp/fsstress
if [[ ! -x $FSSTRESS ]]
then
echo "$MYNAME: fsstress binary not found at $FSSTRESS"
- echo "Please clone and compile https://github.com/rfjakob/fuse-xfstests"
+ echo "Please build and \"sudo make install\" git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git"
exit 1
fi
@@ -62,9 +62,8 @@ if [[ $MYNAME = fsstress-loopback.bash ]]; then
$GOPATH/bin/loopback $OPTS "$MNT" "$DIR" &
disown
elif [[ $MYNAME = fsstress-gocryptfs.bash ]]; then
- echo "Recompile gocryptfs"
- cd "$GOPATH/src/github.com/rfjakob/gocryptfs"
- ./build.bash # also prints the version
+ echo "$MYNAME: using gocryptfs at $GOPATH/bin/gocryptfs"
+ $GOPATH/bin/gocryptfs --version
$GOPATH/bin/gocryptfs -q -init -extpass "echo test" -scryptn=10 "$DIR"
$GOPATH/bin/gocryptfs -q -extpass "echo test" -nosyslog -fusedebug="$DEBUG" "$DIR" "$MNT"
elif [[ $MYNAME = fsstress-encfs.bash ]]; then
@@ -84,7 +83,7 @@ done
echo " ok: $MNT"
# Cleanup trap
-trap "kill %1 ; cd / ; fuse-unmount -z $MNT ; rm -rf $DIR $MNT" EXIT
+trap "echo ' cleaning up...' ; kill %1 ; cd / ; fuse-unmount -z $MNT ; rm -rf $DIR $MNT" EXIT
echo "Starting fsstress loop"
N=1
diff --git a/tests/stress_tests/parallel_cp.sh b/tests/stress_tests/parallel_cp.sh
index cd08d31..f113248 100755
--- a/tests/stress_tests/parallel_cp.sh
+++ b/tests/stress_tests/parallel_cp.sh
@@ -2,15 +2,16 @@
#
# Simplified xfstests generic/273
#
-# Fails with
+# Used to fail with
#
# cp: cannot create regular file 'sub_49/file_773': No such file or directory
#
# If you cannot reproduce, try running this in the background:
#
-# while sleep 0.1 ; do echo 3 > /proc/sys/vm/drop_caches ; done"
+# while sleep 0.1 ; do echo 3 > /proc/sys/vm/drop_caches ; done
#
# See https://github.com/rfjakob/gocryptfs/issues/322 for details.
+# Fixed by https://github.com/hanwen/go-fuse/commit/d0fca860a5759d17592becfa1b8e5b1bd354b24a .
if [[ -z $TMPDIR ]]; then
TMPDIR=/var/tmp
@@ -24,6 +25,9 @@ source ../fuse-unmount.bash
# Set the GOPATH variable to the default if it is empty
GOPATH=$(go env GOPATH)
+echo "$MYNAME: using gocryptfs at $GOPATH/bin/gocryptfs"
+$GOPATH/bin/gocryptfs --version
+
# Backing directory
DIR=$(mktemp -d "$TMPDIR/$MYNAME.XXX")
$GOPATH/bin/gocryptfs -q -init -extpass "echo test" -scryptn=10 "$DIR"
diff --git a/tests/stress_tests/pingpong.bash b/tests/stress_tests/pingpong.bash
index 4b8346e..d0d21b3 100755
--- a/tests/stress_tests/pingpong.bash
+++ b/tests/stress_tests/pingpong.bash
@@ -1,7 +1,8 @@
#!/bin/bash
#
# Mounts two gocryptfs filesystems, "ping" and "pong" and moves the
-# linux-3.0 kernel tree back and forth between them.
+# linux-3.0 kernel tree back and forth between them, checking integrity
+# using md5sum.
#
# When called as "pingpong-rsync.bash" it uses "rsync --remove-source-files"
# for moving the files, otherwise plain "mv".
@@ -29,6 +30,9 @@ mkdir "$PING.mnt" "$PONG.mnt"
# Just ignore unmount errors.
trap "set +e ; cd /tmp; fuse-unmount -z $PING.mnt ; fuse-unmount -z $PONG.mnt ; rm -rf $PING $PONG $PING.mnt $PONG.mnt" EXIT
+echo "$MYNAME: using gocryptfs at $(command -v gocryptfs)"
+gocryptfs --version
+
gocryptfs -q -init -extpass="echo test" -scryptn=10 "$PING"
gocryptfs -q -init -extpass="echo test" -scryptn=10 "$PONG"
diff --git a/tests/test_helpers/helpers.go b/tests/test_helpers/helpers.go
index 0d21548..c8cd151 100644
--- a/tests/test_helpers/helpers.go
+++ b/tests/test_helpers/helpers.go
@@ -5,7 +5,6 @@ import (
"crypto/md5"
"encoding/hex"
"fmt"
- "io/ioutil"
"log"
"os"
"os/exec"
@@ -55,7 +54,7 @@ func doInit() {
fmt.Printf("test_helpers: warning: testParentDir %q does not reside on ext4, we will miss failures caused by ino reuse\n", testParentDir)
}
var err error
- TmpDir, err = ioutil.TempDir(testParentDir, "")
+ TmpDir, err = os.MkdirTemp(testParentDir, "")
if err != nil {
panic(err)
}
@@ -73,7 +72,7 @@ func doInit() {
// *-- gocryptfs.diriv
func ResetTmpDir(createDirIV bool) {
// Try to unmount and delete everything
- entries, err := ioutil.ReadDir(TmpDir)
+ entries, err := os.ReadDir(TmpDir)
if err == nil {
for _, e := range entries {
d := filepath.Join(TmpDir, e.Name())
@@ -148,7 +147,7 @@ func InitFS(t *testing.T, extraArgs ...string) string {
if t != nil {
prefix = t.Name() + "."
}
- dir, err := ioutil.TempDir(TmpDir, prefix)
+ dir, err := os.MkdirTemp(TmpDir, prefix)
if err != nil {
if t != nil {
t.Fatal(err)
@@ -178,7 +177,7 @@ func InitFS(t *testing.T, extraArgs ...string) string {
// Md5fn returns an md5 string for file "filename"
func Md5fn(filename string) string {
- buf, err := ioutil.ReadFile(filename)
+ buf, err := os.ReadFile(filename)
if err != nil {
fmt.Printf("ReadFile: %v\n", err)
return ""
@@ -199,7 +198,7 @@ func Md5hex(buf []byte) string {
// 3) Size reported by Fstat()
func VerifySize(t *testing.T, path string, want int) {
// Read whole file
- buf, err := ioutil.ReadFile(path)
+ buf, err := os.ReadFile(path)
if err != nil {
t.Errorf("ReadFile failed: %v", err)
} else if len(buf) != want {
@@ -299,7 +298,7 @@ func TestMkdirRmdir(t *testing.T, plainDir string) {
func TestRename(t *testing.T, plainDir string) {
file1 := plainDir + "/rename1"
file2 := plainDir + "/rename2"
- err := ioutil.WriteFile(file1, []byte("content"), 0777)
+ err := os.WriteFile(file1, []byte("content"), 0777)
if err != nil {
t.Error(err)
return
diff --git a/tests/test_helpers/mount_unmount.go b/tests/test_helpers/mount_unmount.go
index 46c64c9..4abc432 100644
--- a/tests/test_helpers/mount_unmount.go
+++ b/tests/test_helpers/mount_unmount.go
@@ -187,7 +187,7 @@ func UnmountErr(dir string) (err error) {
err = cmd.Run()
if err == nil {
if len(fdsNow) > len(fds)+maxCacheFds {
- return fmt.Errorf("fd leak in gocryptfs process? pid=%d dir=%q, fds:\nold=%v \nnew=%v\n", pid, dir, fds, fdsNow)
+ return fmt.Errorf("fd leak in gocryptfs process? pid=%d dir=%q, fds:\nold=%v \nnew=%v", pid, dir, fds, fdsNow)
}
return nil
}
diff --git a/tests/xattr/xattr_fd_test.go b/tests/xattr/xattr_fd_test.go
index f3586cf..ae53afc 100644
--- a/tests/xattr/xattr_fd_test.go
+++ b/tests/xattr/xattr_fd_test.go
@@ -6,7 +6,7 @@
package xattr_tests
import (
- "io/ioutil"
+ "os"
"syscall"
"testing"
@@ -18,7 +18,7 @@ import (
func TestFdXattr(t *testing.T) {
attr := "user.foo"
fn := test_helpers.DefaultPlainDir + "/TestFdXattr"
- err := ioutil.WriteFile(fn, nil, 0700)
+ err := os.WriteFile(fn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
diff --git a/tests/xattr/xattr_integration_test.go b/tests/xattr/xattr_integration_test.go
index c968a47..e662828 100644
--- a/tests/xattr/xattr_integration_test.go
+++ b/tests/xattr/xattr_integration_test.go
@@ -10,7 +10,6 @@ import (
"bytes"
"encoding/base64"
"fmt"
- "io/ioutil"
"os"
"strings"
"syscall"
@@ -31,7 +30,7 @@ func TestMain(m *testing.M) {
// Write deterministic diriv so encrypted filenames are deterministic.
os.Remove(test_helpers.DefaultCipherDir + "/gocryptfs.diriv")
diriv := []byte("1234567890123456")
- err := ioutil.WriteFile(test_helpers.DefaultCipherDir+"/gocryptfs.diriv", diriv, 0400)
+ err := os.WriteFile(test_helpers.DefaultCipherDir+"/gocryptfs.diriv", diriv, 0400)
if err != nil {
fmt.Println(err)
os.Exit(1)
@@ -92,7 +91,7 @@ func setGetRmList3(fn string, attr string, val []byte) error {
// Test xattr set, get, rm on a regular file.
func TestSetGetRmRegularFile(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSetGetRmRegularFile"
- err := ioutil.WriteFile(fn, []byte("12345"), 0700)
+ err := os.WriteFile(fn, []byte("12345"), 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -132,7 +131,7 @@ func TestSetGetRmDir(t *testing.T) {
func TestXattrSetEmpty(t *testing.T) {
attr := "user.foo"
fn := test_helpers.DefaultPlainDir + "/TestXattrSetEmpty1"
- err := ioutil.WriteFile(fn, nil, 0700)
+ err := os.WriteFile(fn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -185,7 +184,7 @@ func TestXattrSetEmpty(t *testing.T) {
func TestXattrList(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestXattrList"
- err := ioutil.WriteFile(fn, nil, 0700)
+ err := os.WriteFile(fn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -218,10 +217,7 @@ func xattrSupported(path string) bool {
return true
}
err2 := err.(*xattr.Error)
- if err2.Err == syscall.EOPNOTSUPP {
- return false
- }
- return true
+ return err2.Err != syscall.EOPNOTSUPP
}
func TestBase64XattrRead(t *testing.T) {
@@ -236,7 +232,7 @@ func TestBase64XattrRead(t *testing.T) {
plainFn := test_helpers.DefaultPlainDir + "/" + fileName
encryptedFn := test_helpers.DefaultCipherDir + "/" + encryptedFileName
- err := ioutil.WriteFile(plainFn, nil, 0700)
+ err := os.WriteFile(plainFn, nil, 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -289,7 +285,7 @@ func TestBase64XattrRead(t *testing.T) {
// Listing xattrs should work even when we don't have read access
func TestList0000File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestList0000File"
- err := ioutil.WriteFile(fn, nil, 0000)
+ err := os.WriteFile(fn, nil, 0000)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -302,7 +298,7 @@ func TestList0000File(t *testing.T) {
// Setting xattrs should work even when we don't have read access
func TestSet0200File(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestSet0200File"
- err := ioutil.WriteFile(fn, nil, 0200)
+ err := os.WriteFile(fn, nil, 0200)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -342,7 +338,7 @@ func TestSet0200Dir(t *testing.T) {
func TestAcl(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/TestAcl"
- err := ioutil.WriteFile(fn, nil, 0600)
+ err := os.WriteFile(fn, nil, 0600)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}
@@ -374,7 +370,7 @@ func TestAcl(t *testing.T) {
// https://github.com/rfjakob/gocryptfs/issues/627
func TestSlashInName(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/" + t.Name()
- err := ioutil.WriteFile(fn, []byte("12345"), 0700)
+ err := os.WriteFile(fn, []byte("12345"), 0700)
if err != nil {
t.Fatalf("creating empty file failed: %v", err)
}