Compare commits

...

62 Commits
v1.13 ... main

Author SHA1 Message Date
Giuseppe Scrivano 2772fa30c7
Merge pull request #446 from giuseppe/tag-1.15
release 1.15
2025-05-08 13:34:23 +02:00
Giuseppe Scrivano 0ea1677326
configure.ac: prepare new release cycle
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-05-08 08:36:11 +02:00
Giuseppe Scrivano c5f3ef142e
NEWS: tag 1.15
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-05-08 08:36:11 +02:00
Giuseppe Scrivano e8532dce73
Merge pull request #445 from giuseppe/fix-lookup-lower-layers-of-freshly-created-dir
main: lookup upperdir only for created directories
2025-05-07 17:01:02 +02:00
Giuseppe Scrivano 6a1040f01d
main: lookup upperdir only for created directories
always stop looking up at the upper layer for newly created
directories so to not expose the lower directory content.

Closes: https://github.com/containers/fuse-overlayfs/issues/444

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-05-07 11:56:22 +02:00
Giuseppe Scrivano d5b2cec0e7
main: fix layer lookup in make_ovl_node
Stop iterating through layers in `make_ovl_node`
if the current layer being checked is the `last_layer`
of the parent node.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-05-07 11:44:28 +02:00
Giuseppe Scrivano 28ba1fd247
Merge pull request #443 from giuseppe/update-hash-map-module
main: use hash_remove instead of hash_delete
2025-04-30 08:52:47 +02:00
Giuseppe Scrivano b88a093bd4
main: use hash_remove instead of hash_delete
the function is deprecated in gnulib

Closes: https://github.com/containers/fuse-overlayfs/issues/442

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-29 23:41:30 +02:00
Giuseppe Scrivano ce8d185c16
lib: update hash module from gnulib
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-29 23:34:01 +02:00
Giuseppe Scrivano 3af99f6552
Merge pull request #441 from giuseppe/unescape-dirs
main: Allow escaped colons in directory paths
2025-04-29 22:28:08 +02:00
Giuseppe Scrivano 27cabd561b
main: Allow escaped colons in directory paths
Allow directory paths specified for lowerdir, upperdir and workdir to
contain colon characters.

Previously, colons were unconditionally treated as separators,
making it impossible to use directories with colons in their names.

Closes: https://github.com/containers/fuse-overlayfs/issues/440

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano b31d82ff8e
main: use parsed upper layer directory
Use the directory in the loaded layer structure, and do not use the
value passed on the CLI.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano 02a3582501
refactor: Remove unnecessary upperdir realpath
The explicit resolution of the upper directory path using `realpath()`
is not required before passing it to `read_dirs` since
`direct_load_data_source()` already does it.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano 5e7f7d756e
github: update checkout action
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano 4761dd0675
github: update uraimo action
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano fc6a2b916e
github: update upload-artifact action
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano e1aae5aa6b
github: enable unprivileged user namespaces
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-04-28 18:46:30 +02:00
Giuseppe Scrivano 6df9cd27c7
Merge pull request #439 from alvistack/AC_INIT-1.15-dev
Update AC_INIT to 1.15-dev
2025-04-28 12:29:34 +02:00
Wong Hoi Sing Edison efb61490c3
Update AC_INIT to 1.15-dev
The version string should update as "1.14" when we have stable release:

    -AC_INIT([fuse-overlayfs], [1.13-dev], [giuseppe@scrivano.org])
    +AC_INIT([fuse-overlayfs], [1.14], [giuseppe@scrivano.org])

But since we had already get
https://github.com/containers/fuse-overlayfs/releases/tag/v1.14 since
2024-06-28, here we bump the version string to "1.15-dev" for next
stable release.

Fixes https://github.com/containers/fuse-overlayfs/issues/438

Signed-off-by: Wong Hoi Sing Edison <hswong3i@pantarei-design.com>
2025-03-26 11:56:57 +08:00
Daniel J Walsh a54a9f2e88
Merge pull request #434 from giuseppe/use-extended-override-xattr
main: use extended override xattr
2024-12-02 10:09:47 -05:00
Giuseppe Scrivano 6bbd62bb13
main: use extended override xattr
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-12-02 09:52:35 +01:00
Giuseppe Scrivano 8d33894c48
github: update run-on-arch-action
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-12-02 09:52:35 +01:00
Giuseppe Scrivano cf8269b5f6
github: update ubuntu version
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-12-02 09:52:35 +01:00
Giuseppe Scrivano af77bb6584
tests: bump fedora version
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-12-02 09:52:35 +01:00
Giuseppe Scrivano ab0b0f6d46
Merge pull request #430 from hholst80/main
Remove unsupported option "lazytime"
2024-08-18 02:33:09 +02:00
Henrik Holst e107b0bfe4 Remove unsupported option "lazytime"
Signed-off-by: Henrik Holst <6200749+hholst80@users.noreply.github.com>
2024-08-17 11:41:49 +02:00
Daniel J Walsh 147609b1fb
Merge pull request #426 from giuseppe/tag-1.14
NEWS: tag 1.14
2024-06-27 13:48:23 -04:00
Giuseppe Scrivano 33cb788edc
NEWS: tag 1.14
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-06-27 15:45:18 +02:00
Giuseppe Scrivano 4217e1c160
Merge pull request #422 from akihikodaki/containers
Prefer user.containers.override_stat over user.fuseoverlayfs.
2024-06-17 20:33:21 +02:00
Akihiko Odaki 20161f96d7 main: Isolate security xattrs for STAT_OVERRIDE_CONTAINERS
The major use case of stat override is to enable rootless containers
on network filesystems, and they also lack security xattr support in
non-root user namespaces. Trying to set security xattrs on them result
in ENOTSUP and break things.

It makes little sense to share security xattrs with the underlying
filesystems when overriding stat in the first place. Linux's NFS server
exposes security xattrs only when the user explicitly claims the
security consistencies between the server and clients, and hide them
otherwise. Following this precedent, we should isolate security xattrs
since we know the security policy enforced by fuse-overlayfs is already
distinct from the underlying filesystem when overriding owners and file
mode.

Mark security xattrs inaccessible with STAT_OVERRIDE_CONTAINERS to
prefix all access to them with XATTR_CONTAINERS_OVERRIDE_PREFIX.

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 22:50:14 +09:00
Akihiko Odaki 9810b85aad main: Override inaccessible xattrs
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 22:50:14 +09:00
Akihiko Odaki 90bea22c73 Prefer user.containers.override_stat over user.fuseoverlayfs.
Previously, fuse-overlayfs always used user.fuseoverlayfs.override_stat
for the upper layer while honoring user.containers.override_stat for
lower layers so that it can consume a layer created by
containers/storage.

It turned out that containers/storage also needs to get the overriding
extended attribute set by fuse-overlayfs and to set one for the upper
layer to make the root directory of the upper layer inherit the mode
of a lower layer. Adding code to get and to set
user.fuseoverlayfs.override_stat to containers/storage is a bit ugly.

The underlying problem is that fuse-overlayfs changes what name to use
ad hoc. Fix it by always preferring user.containers.override_stat, which
containers/storage honors, over user.fuseoverlayfs.overlayfs, which is
specific to fuse-overlayfs.

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 22:50:14 +09:00
Akihiko Odaki e16818c042 Fix printed extended attribute name
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 22:50:14 +09:00
Akihiko Odaki 9610adf7ab main: Do not set -1 for owner overriding xattrs
ovl_setattr () used to pass -1 as uid or gid when either of them
is not changed for do_fchown () / do_chown (), but if these functions
use overriding xattrs instead of real fchown () or chown (), it causes
-1 to be written in owner overriding xattrs and break them.

Replace -1 with the current uid or gid before calling do_fchown () /
do_chown ().

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 22:50:01 +09:00
Akihiko Odaki 136aefd2f6 main: Fix file owner retrieval for chmod
do_fchmod () and do_chmod () used to call override_mode () directly to
retrieve the owner information, but the usage of override_mode () was
wrong; override_mode () expects struct stat is already populated by
the information provided by the underlying filesystem, but do_fchmod ()
and do_chmod () only zeroed st_uid and st_gid. override_mode () does not
update the owner information when st_mode is not S_IFDIR nor S_IFREG so
this caused chmod to change the file owner to root at random.

Use the logic rpl_stat () employs to file owner retrieval for chmod
functions to ensure they provide the owner information consistent with
rpl_stat ().

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 21:35:51 +09:00
Akihiko Odaki a13a9e71dc utils: Always suppress ENODATA
override_mode () used to suppress ENODATA only in a certain condition.
ENODATA errors in other situations made load_dir () fail because it
indirectly calls override_mode () when the underlying file system
reports DT_UNKNOWN for an opaque whiteout file and such an file does
not have mode xattrs. do_fchmod () and do_chmod () worked around the
problem by supressing ENODATA by themselves, but that led to code
duplication. Always suppress ENODATA to resolve these problems.

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 21:35:51 +09:00
Akihiko Odaki b644635d67 tests: Update to Ubuntu 22.04
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 21:35:39 +09:00
Akihiko Odaki 19ba462b57 tests: Update run-on-arch-action to v2.7.2
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 21:34:11 +09:00
Giuseppe Scrivano 25505d9b6c
Merge pull request #424 from akihikodaki/tests
Fix tests
2024-06-17 14:03:29 +02:00
Akihiko Odaki d34257cd3a build: Fix libfuse version
Building the current master branch fails with:
#8 4.712 ninja: job failed: cc -Ilib/libfuse3.a.p -Ilib -I../lib -Iinclude -I../include -I. -I.. -fdiagnostics-color=always -Wall -Winvalid-pch -Wextra -std=gnu11 -O2 -g -D_REENTRANT -DHAVE_LIBFUSE_PRIVATE_CONFIG_H -Wno-sign-compare -D_FILE_OFFSET_BITS=64 -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -fno-strict-aliasing -Wno-unused-result -DHAVE_SYMVER_ATTRIBUTE -fPIC -pthread -DFUSE_USE_VERSION=317 '-DFUSERMOUNT_DIR="/usr/bin"' -MD -MQ lib/libfuse3.a.p/fuse_signals.c.o -MF lib/libfuse3.a.p/fuse_signals.c.o.d -o lib/libfuse3.a.p/fuse_signals.c.o -c ../lib/fuse_signals.c
#8 4.713 ../lib/fuse_signals.c:19:10: fatal error: execinfo.h: No such file or directory
#8 4.713    19 | #include <execinfo.h>
#8 4.713       |          ^~~~~~~~~~~~
#8 4.713 compilation terminated.
#8 13.65 ninja: subcommand failed

Use the latest stable version, which is 3.16.2

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 18:01:59 +09:00
Akihiko Odaki da0f9cb03b tests: Use Fedora to test fast_ino_check
The test of fast_ino_check used CentOS Stream 8, which is no longer
supported and whose repositories are no longer available.

While we can update it to use CentOS Stream 9, use Fedora instead for
consistency with the other test cases.

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 17:51:50 +09:00
Akihiko Odaki 3a1b242526 tests: Drop the test case for https://github.com/containers/fuse-overlayfs/issues/86
The test case for https://github.com/containers/fuse-overlayfs/issues/86
does no longer work because it relies on CentOS Stream 8 repositories,
which are unavailable.

While we can update it to use CentOS Stream 9, the issue says it is
limited to CentOS 6, and it is unclear if the test case will reproduce
the intended scenario with CentOS Stream 8 or 9 so drop it.

Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
2024-06-17 17:49:20 +09:00
Giuseppe Scrivano a01f52fc6b
Merge pull request #415 from flouthoc/fix-clang-errors
.clang-format: remove duplicated mapping key which breaks newer `clang` formatters.
2024-02-21 09:35:54 +01:00
flouthoc ebc3b5c9f8
.clang-format: fix duplicated mapping key
New clang versions throw error like, following commit fixes that

```console
.clang-format:26:1: error: duplicated mapping key 'AllowShortIfStatementsOnASingleLine'
AllowShortIfStatementsOnASingleLine: false
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

Signed-off-by: flouthoc <flouthoc.git@gmail.com>
2024-02-20 22:16:16 -08:00
Giuseppe Scrivano c2dd76c6d7
Merge pull request #412 from giuseppe/fix-variable-leak
main: automatically cleanup newpath
2024-02-06 13:21:34 +01:00
Giuseppe Scrivano c08211ddd7
main: automatically cleanup newpath
this was already the assumption in the source code.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2024-02-06 10:21:52 +01:00
Giuseppe Scrivano 18f4d6768a
Merge pull request #410 from giuseppe/propagate-force-mask-on-dir-copyup
main: propagate xattrs permissions with copyup
2023-11-03 10:36:21 +01:00
Giuseppe Scrivano d9e78eadb7
main: create dir in two steps with xattrs permissions
if xattrs permissions are used, create the directory in two steps and
set the correct ownership before moving it into the target.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-11-02 11:49:16 +01:00
Giuseppe Scrivano d65ce2a8bb
main: propagate xattrs permissions with copyup
when a directory is copied up, propagate its mode if using xattrs
permissions.

Closes: https://github.com/containers/fuse-overlayfs/issues/407

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-11-01 21:35:03 +01:00
Giuseppe Scrivano 906513ccba
Merge pull request #409 from giuseppe/create-inodes-with-correct-mode
Create devices and dirs with correct mode with xattrs_permissions
2023-10-31 08:54:38 +01:00
Giuseppe Scrivano f73fd2db7c
fuse-overlayfs: honor mode for dirs with xattr_permissions
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-10-30 18:10:19 +01:00
Giuseppe Scrivano 2d8613e7f5
fuse-overlayfs: honor mode for devices with xattr_permissions
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-10-30 18:07:11 +01:00
Daniel J Walsh acfb6a27d1
Merge pull request #408 from giuseppe/honor-umask-xattr-permissions
fuse-overlayfs: honor umask with xattr_permissions
2023-10-30 10:26:32 -04:00
Giuseppe Scrivano 95e12f97f5
fuse-overlayfs: propagate force mode xattr
if the lower dir has a force mode xattr, use it for the upper layer
too.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-10-30 08:40:04 +01:00
Giuseppe Scrivano 40b431e243
fuse-overlayfs: honor umask with xattr_permissions
Closes: https://github.com/containers/fuse-overlayfs/issues/407

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-10-29 21:04:14 +01:00
Daniel J Walsh e47790da06
Merge pull request #406 from AkihiroSuda/hub-to-gh
release.yaml: migrate from `hub` to `gh`
2023-10-09 16:04:13 -04:00
Akihiro Suda 542b005c67
release.yaml: migrate from `hub` to `gh`
`hub` was deprecated and removed from GitHub Actions runners

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2023-10-09 07:51:19 +09:00
Daniel J Walsh 9aba87b78f
Merge pull request #405 from giuseppe/add-clang-format
code: add clang-format
2023-10-02 15:41:20 -04:00
Giuseppe Scrivano ba472b58bc
code: add clang-format
add a clang-format definition file and apply it to the code.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2023-10-02 15:12:42 +02:00
Giuseppe Scrivano 40e0f3c329
Merge pull request #403 from AkihiroSuda/repro
CI: release: enable reproducible builds
2023-09-14 12:11:52 +02:00
Akihiro Suda 5755b22bcb
CI: release: enable reproducible builds
The binaries are now bit-for-bit reproducible (<https://reproducible-builds.org/>):
```bash
docker buildx build \
   -o /tmp/fuse-overlayfs-builds \
   --build-arg SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \
   --platform=amd64,arm64,arm,s390x,ppc64le,riscv64 \
   -f Containerfile.cross .
```

This ensures that the binaries are not built from any unpublished source.

- Implementation is similar to <https://github.com/rootless-containers/slirp4netns/compare/v1.2.1...v1.2.2>
- `/etc/apt/sources.list` is modified to use <http://snapshot.ubuntu.com> using
  <https://github.com/reproducible-containers/repro-sources-list.sh>.
- As <http://snapshot.ubuntu.com> is slow, `/var/cache/apt` is cached on GHA using
  <https://github.com/reproducible-containers/buildkit-cache-dance>.
- For testing sake, `.github/workflows/release.yaml` is now executed for every push events.
  The release artifacts are published only on a push event with a `v*` tag.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2023-09-14 05:48:00 +09:00
Giuseppe Scrivano ff6b14d0a8
Merge pull request #402 from giuseppe/fix-perf-issue
main: fix performance issue with large dirs
2023-08-29 17:31:41 +02:00
27 changed files with 2724 additions and 929 deletions

111
.clang-format Normal file
View File

@ -0,0 +1,111 @@
---
BasedOnStyle: GNU
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: AllDefinitions
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: true
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 0
ContinuationIndentWidth: 4
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentGotoLabels: true
IndentPPDirectives: AfterHash
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: true
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: Always
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
TabWidth: 8
UseCRLF: false
UseTab: Never
...

View File

@ -1,98 +1,77 @@
name: Release
on:
push:
tags:
- 'test-cross-*'
- 'v*'
pull_request:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/setup-buildx-action@v1
- uses: actions/checkout@v4
with:
# Reproduce the exact commit hash value
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- uses: docker/setup-buildx-action@v2
- name: Cache var-cache-apt
uses: actions/cache@v3
with:
path: var-cache-apt
key: var-cache-apt-${{ hashFiles('Containerfile.cross') }}
- name: Cache var-lib-apt
uses: actions/cache@v3
with:
path: var-lib-apt
key: var-lib-apt-${{ hashFiles('Containerfile.cross') }}
- name: inject var-cache-apt into docker
uses: reproducible-containers/buildkit-cache-dance@v2.1.2
with:
cache-source: var-cache-apt
cache-target: /var/cache/apt
- name: inject var-lib-apt into docker
uses: reproducible-containers/buildkit-cache-dance@v2.1.2
with:
cache-source: var-lib-apt
cache-target: /var/lib/apt
- name: "Build binaries from Containerfile.cross"
run: docker buildx build -o out --platform=amd64,arm64,arm,s390x,ppc64le,riscv64 -f Containerfile.cross .
- name: "Create out/artifact"
run: docker buildx build -o /tmp/fuse-overlayfs-builds --build-arg SOURCE_DATE_EPOCH --platform=amd64,arm64,arm,s390x,ppc64le,riscv64 -f Containerfile.cross .
- name: "Create /tmp/artifact"
run: |
mkdir -p out/artifact
mv out/linux_amd64/fuse-overlayfs out/artifact/fuse-overlayfs-x86_64
mv out/linux_arm64/fuse-overlayfs out/artifact/fuse-overlayfs-aarch64
mv out/linux_arm_v7/fuse-overlayfs out/artifact/fuse-overlayfs-armv7l
mv out/linux_s390x/fuse-overlayfs out/artifact/fuse-overlayfs-s390x
mv out/linux_ppc64le/fuse-overlayfs out/artifact/fuse-overlayfs-ppc64le
mv out/linux_riscv64/fuse-overlayfs out/artifact/fuse-overlayfs-riscv64
mkdir -p /tmp/artifact
mv /tmp/fuse-overlayfs-builds/linux_amd64/fuse-overlayfs /tmp/artifact/fuse-overlayfs-x86_64
mv /tmp/fuse-overlayfs-builds/linux_arm64/fuse-overlayfs /tmp/artifact/fuse-overlayfs-aarch64
mv /tmp/fuse-overlayfs-builds/linux_arm_v7/fuse-overlayfs /tmp/artifact/fuse-overlayfs-armv7l
mv /tmp/fuse-overlayfs-builds/linux_s390x/fuse-overlayfs /tmp/artifact/fuse-overlayfs-s390x
mv /tmp/fuse-overlayfs-builds/linux_ppc64le/fuse-overlayfs /tmp/artifact/fuse-overlayfs-ppc64le
mv /tmp/fuse-overlayfs-builds/linux_riscv64/fuse-overlayfs /tmp/artifact/fuse-overlayfs-riscv64
echo "${SOURCE_DATE_EPOCH}" >/tmp/artifact/SOURCE_DATE_EPOCH
- name: "SHA256SUMS"
run: (cd out/artifact; sha256sum *) | tee out/SHA256SUMS
run: (cd /tmp/artifact; sha256sum *) | tee /tmp/SHA256SUMS
- name: "The sha256sum of the SHA256SUMS file"
run: sha256sum /tmp/SHA256SUMS
- name: "Prepare the release note"
run: |
cat << EOF | tee /tmp/release-note.txt
#### About the binaries
The binaries were built automatically on GitHub Actions.
The build log is available for 90 days: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
The binaries should be reproducible with the following command:
\`\`\`
docker buildx build \
-o /tmp/fuse-overlayfs-builds \
--build-arg SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} \
--build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 \
--platform=amd64,arm64,arm,s390x,ppc64le,riscv64 \
-f Containerfile.cross \
"https://github.com/${{ github.repository }}.git#${tag}"
\`\`\`
EOF
- name: "Create release"
uses: actions/create-release@v1
id: create_release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
- name: "Upload fuse-overlayfs-x86_64"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-x86_64
asset_name: fuse-overlayfs-x86_64
asset_content_type: application/octet-stream
- name: "Upload fuse-overlayfs-aarch64"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-aarch64
asset_name: fuse-overlayfs-aarch64
asset_content_type: application/octet-stream
- name: "Upload fuse-overlayfs-armv7l"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-armv7l
asset_name: fuse-overlayfs-armv7l
asset_content_type: application/octet-stream
- name: "Upload fuse-overlayfs-s390x"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-s390x
asset_name: fuse-overlayfs-s390x
asset_content_type: application/octet-stream
- name: "Upload fuse-overlayfs-ppc64le"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-ppc64le
asset_name: fuse-overlayfs-ppc64le
asset_content_type: application/octet-stream
- name: "Upload fuse-overlayfs-riscv64"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/artifact/fuse-overlayfs-riscv64
asset_name: fuse-overlayfs-riscv64
asset_content_type: application/octet-stream
- name: "Upload SHA256SUMS"
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: out/SHA256SUMS
asset_name: SHA256SUMS
asset_content_type: text/plain
run: |
tag="${GITHUB_REF##*/}"
gh release create -F /tmp/release-note.txt --draft --title "${tag}" "${tag}" /tmp/artifact/* /tmp/SHA256SUMS

View File

@ -4,23 +4,23 @@ on: [push, pull_request]
jobs:
build_job:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
name: Build on ${{ matrix.arch }}
strategy:
matrix:
include:
- arch: armv7
distro: ubuntu20.04
distro: ubuntu_latest
- arch: aarch64
distro: ubuntu20.04
distro: ubuntu_latest
- arch: s390x
distro: ubuntu20.04
distro: ubuntu_latest
- arch: ppc64le
distro: ubuntu20.04
distro: ubuntu_latest
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.5
- uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3.0.1
name: Build
id: build
with:
@ -34,7 +34,7 @@ jobs:
install: |
apt-get update -q -y
apt-get install -q -y attr automake autotools-dev git make gcc pkg-config xz-utils python3.8 g++ python3-setuptools libdevmapper-dev btrfs-progs libbtrfs-dev go-md2man parallel libfuse3-dev bats
apt-get install -q -y attr automake autotools-dev git make gcc pkg-config xz-utils python3 g++ python3-setuptools libdevmapper-dev btrfs-progs libbtrfs-dev go-md2man parallel libfuse3-dev bats
run: |
./autogen.sh
@ -42,14 +42,14 @@ jobs:
make -j $(nproc)
- name: Archive build artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4.6.2
with:
name: fuse-overlayfs-${{ matrix.arch }}-${{ matrix.distro }}
path: |
fuse-overlayfs
Test:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@ -61,12 +61,12 @@ jobs:
TAGS: exclude_graphdriver_devicemapper exclude_graphdriver_btrfs no_libsubid
steps:
- name: checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: install dependencies
run: |
sudo apt-get update -q -y
sudo apt-get install -q -y attr automake autotools-dev git make gcc pkg-config xz-utils python3.8 g++ python3-setuptools libdevmapper-dev btrfs-progs libbtrfs-dev go-md2man parallel wget libfuse3-dev bats
sudo apt-get install -q -y attr automake autotools-dev git make gcc pkg-config xz-utils python3 g++ python3-setuptools libdevmapper-dev btrfs-progs libbtrfs-dev go-md2man parallel wget libfuse3-dev bats
sudo mkdir -p /lower /upper /mnt $GOPATH/src/github.com/containers
sudo sh -c "cd $GOPATH/src/github.com/containers; git clone --depth=1 https://github.com/containers/storage"
@ -88,15 +88,18 @@ jobs:
sudo cp fuse-overlayfs /sbin
- name: Archive build artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4.6.2
with:
name: fuse-overlayfs-x86_64-ubuntu20.04
name: fuse-overlayfs-x86_64-ubuntu-latest
path: |
fuse-overlayfs
if: ${{ matrix.test == 'ovl-whiteouts' }}
- name: run test
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0
case "${{ matrix.test }}" in
ovl-whiteouts)
sudo sh -c "(cd /unionmount-testsuite; unshare -m ./run --ov --fuse=fuse-overlayfs --xdev)"

View File

@ -3,7 +3,7 @@ WORKDIR /build
RUN apk add git make gcc libc-dev musl-dev glib-static gettext eudev-dev \
linux-headers automake autoconf cmake meson ninja clang go-md2man
RUN git clone https://github.com/libfuse/libfuse && \
RUN git clone https://github.com/libfuse/libfuse -b fuse-3.16.2 && \
cd libfuse && \
mkdir build && \
cd build && \

View File

@ -1,14 +1,38 @@
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.1.0 AS xx
FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS fuse-overlayfs
RUN apt-get update && \
# Usage:
# docker buildx build \
# -o /tmp/fuse-overlayfs-builds \
# --build-arg SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \
# --platform=amd64,arm64,arm,s390x,ppc64le,riscv64 \
# -f Containerfile.cross .
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.2.1 AS xx
FROM --platform=$BUILDPLATFORM ubuntu:jammy-20230804 AS fuse-overlayfs
ADD --chmod=0755 \
https://raw.githubusercontent.com/reproducible-containers/repro-sources-list.sh/v0.1.0/repro-sources-list.sh \
/usr/local/bin/repro-sources-list.sh
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
repro-sources-list.sh && \
apt-get update && \
apt-get install --no-install-recommends -y \
git make automake autoconf pkgconf file go-md2man
# Set SOURCE_DATE_EPOCH after running repro-sources-list.sh, for cache efficiency
ARG SOURCE_DATE_EPOCH
COPY . /fuse-overlayfs
WORKDIR /fuse-overlayfs
COPY --from=xx / /
ARG TARGETPLATFORM
ENV DEBIAN_FRONTEND=noninteractive
RUN xx-apt-get install -y gcc libfuse3-dev
# xx-apt-get cannot be used, as it clobbers /etc/apt/sources.list created by repro-sources-list.sh
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
darch="$(xx-info debian-arch)" && \
dpkg --add-architecture ${darch} && \
apt-get update && \
gcc="gcc" && \
if xx-info is-cross; then gcc="gcc-$(xx-info triple)"; fi; \
apt-get install -y "${gcc}" "libfuse3-dev:${darch}"
RUN ./autogen.sh && \
LIBS="-ldl" LDFLAGS="-static" ./configure --host=$(xx-info) && \
make && mkdir /out && cp fuse-overlayfs /out && \

View File

@ -3,7 +3,7 @@ WORKDIR /build
RUN dnf update -y && \
dnf install -y git make automake autoconf gcc glibc-static meson ninja-build clang
RUN git clone https://github.com/libfuse/libfuse && \
RUN git clone https://github.com/libfuse/libfuse -b fuse-3.16.2 && \
cd libfuse && \
mkdir build && \
cd build && \

View File

@ -36,3 +36,6 @@ srpm: dist-gzip fuse-overlayfs.spec
echo $(VERSION)
$(MAKE) -C $(WD) dist-xz
rpmbuild -bs --define "_sourcedir $(WD)" --define "_specdir $(WD)" --define "_builddir $(WD)" --define "_srcrpmdir $(WD)" --define "_rpmdir $(WD)" --define "_buildrootdir $(WD)/.build" fuse-overlayfs.spec
clang-format:
git ls-files | grep -E "\\.[hc]$$" | grep -v "^lib/" | xargs clang-format -style=file -i

19
NEWS
View File

@ -1,3 +1,22 @@
* fuse-overlayfs-1.15
- main: lookup upperdir only for created directories.
- main: allow escaped colons in directory paths.
- main: use extended override xattr to support devices.
- remove unsupported option "lazytime".
* fuse-overlayfs-1.14
- isolate security xattrs for STAT_OVERRIDE_CONTAINERS. Prefix all
security xattrs with XATTR_CONTAINERS_OVERRIDE_PREFIX.
- prefer user.containers.override_stat over user.fuseoverlayfs.
- do not force -1 for owner overriding extended attributes. Otherwise
the value is written to the override extended attribute.
- fix file owner retrieval for chmod.
- honor umask with xattr_permissions.
- honor mode for devices with xattr_permissions.
- propagate extended attributes permissions with copyup.
* fuse-overlayfs-1.13
- fix a performance issue when dealing with big directories.

View File

@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([fuse-overlayfs], [1.13-dev], [giuseppe@scrivano.org])
AC_INIT([fuse-overlayfs], [1.16-dev], [giuseppe@scrivano.org])
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])

View File

@ -6,12 +6,12 @@ import stat
import errno
XATTR_OVERRIDE_STAT_PRIVILEGED = "security.fuseoverlayfs.override_stat"
XATTR_OVERRIDE_STAT = "user.fuseoverlayfs.override_stat"
XATTR_OVERRIDE_CONTAINERS_STAT = "user.fuseoverlayfs.override_stat"
if os.geteuid() == 0:
xattr_name = XATTR_OVERRIDE_STAT_PRIVILEGED
else:
xattr_name = XATTR_OVERRIDE_STAT
xattr_name = XATTR_OVERRIDE_CONTAINERS_STAT
cwd_fd = os.open(".", os.O_PATH)
@ -23,7 +23,7 @@ def fix_path(path):
os.setxattr(path, xattr_name, str.encode(content), flags=os.XATTR_CREATE, follow_symlinks=False)
except Exception as e:
if e.errno == errno.EEXIST:
print("attr %s already present for %s: %s" % (XATTR_OVERRIDE_STAT, path, e.errno))
print("attr %s already present for %s: %s" % (xattr_name, path, e.errno))
return
raise e

View File

@ -186,10 +186,10 @@ direct_load_data_source (struct ovl_layer *l, const char *opaque, const char *pa
if (fgetxattr (l->fd, XATTR_PRIVILEGED_OVERRIDE_STAT, tmp, sizeof (tmp)) >= 0)
l->stat_override_mode = STAT_OVERRIDE_PRIVILEGED;
else if (fgetxattr (l->fd, XATTR_OVERRIDE_STAT, tmp, sizeof (tmp)) >= 0)
l->stat_override_mode = STAT_OVERRIDE_USER;
else if (fgetxattr (l->fd, XATTR_OVERRIDE_CONTAINERS_STAT, tmp, sizeof (tmp)) >= 0)
l->stat_override_mode = STAT_OVERRIDE_CONTAINERS;
else if (fgetxattr (l->fd, XATTR_OVERRIDE_STAT, tmp, sizeof (tmp)) >= 0)
l->stat_override_mode = STAT_OVERRIDE_USER;
return 0;
}
@ -215,9 +215,7 @@ direct_support_acls (struct ovl_layer *l)
|| errno != ENOTSUP;
}
struct data_source direct_access_ds =
{
struct data_source direct_access_ds = {
.num_of_layers = direct_num_of_layers,
.load_data_source = direct_load_data_source,
.cleanup = direct_cleanup,

View File

@ -24,7 +24,9 @@
#ifdef HAVE_ERROR_H
# include <error.h>
#else
# define error(status, errno, fmt, ...) do { \
# define error(status, errno, fmt, ...) \
do \
{ \
if (errno == 0) \
fprintf (stderr, "fuse-overlayfs: " fmt "\n", ##__VA_ARGS__); \
else \

View File

@ -1,6 +1,6 @@
/* hash - hashing table processing.
Copyright (C) 1998-2004, 2006-2007, 2009-2023 Free Software Foundation, Inc.
Copyright (C) 1998-2004, 2006-2007, 2009-2025 Free Software Foundation, Inc.
Written by Jim Meyering, 1992.
@ -29,6 +29,7 @@
#include "bitrotate.h"
#include "xalloc-oversized.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@ -499,13 +500,17 @@ compute_bucket_size (size_t candidate, const Hash_tuning *tuning)
{
float new_candidate = candidate / tuning->growth_threshold;
if ((float) SIZE_MAX <= new_candidate)
return 0;
goto nomem;
candidate = new_candidate;
}
candidate = next_prime (candidate);
if (xalloc_oversized (candidate, sizeof (struct hash_entry *)))
return 0;
goto nomem;
return candidate;
nomem:
errno = ENOMEM;
return 0;
}
Hash_table *
@ -534,6 +539,7 @@ hash_initialize (size_t candidate, const Hash_tuning *tuning,
if the user provides invalid tuning options, we silently revert to
using the defaults, and ignore further request to change the tuning
options. */
errno = EINVAL;
goto fail;
}
@ -607,6 +613,7 @@ hash_free (Hash_table *table)
struct hash_entry *bucket;
struct hash_entry *cursor;
struct hash_entry *next;
int err = errno;
/* Call the user data_freer function. */
if (table->data_freer && table->n_entries)
@ -649,6 +656,8 @@ hash_free (Hash_table *table)
/* Free the remainder of the hash table structure. */
free (table->bucket);
free (table);
errno = err;
}
/* Insertion and deletion. */
@ -762,8 +771,8 @@ hash_find_entry (Hash_table *table, const void *entry,
/* Internal helper, to move entries from SRC to DST. Both tables must
share the same free entry list. If SAFE, only move overflow
entries, saving bucket heads for later, so that no allocations will
occur. Return false if the free entry list is exhausted and an
allocation fails. */
occur. Return false (setting errno) if the free entry list is
exhausted and an allocation fails. */
static bool
transfer_entries (Hash_table *dst, Hash_table *src, bool safe)
@ -910,12 +919,14 @@ hash_rehash (Hash_table *table, size_t candidate)
passes. Two passes give worse cache performance and takes
longer, but at this point, we're already out of memory, so slow
and safe is better than failure. */
int err = errno;
table->free_entry_list = new_table->free_entry_list;
if (! (transfer_entries (table, new_table, true)
&& transfer_entries (table, new_table, false)))
abort ();
/* table->n_entries already holds its value. */
free (new_table->bucket);
errno = err;
return false;
}
@ -962,7 +973,10 @@ hash_insert_if_absent (Hash_table *table, void const *entry,
* tuning->growth_threshold));
if ((float) SIZE_MAX <= candidate)
{
errno = ENOMEM;
return -1;
}
/* If the rehash fails, arrange to return NULL. */
if (!hash_rehash (table, candidate))

View File

@ -1,5 +1,5 @@
/* hash - hashing table processing.
Copyright (C) 1998-1999, 2001, 2003, 2009-2023 Free Software Foundation,
Copyright (C) 1998-1999, 2001, 2003, 2009-2025 Free Software Foundation,
Inc.
Written by Jim Meyering <meyering@ascend.com>, 1998.
@ -61,24 +61,24 @@ typedef struct hash_table Hash_table;
number of buckets (used plus unused), or the maximum number of slots, are
the same quantity. */
extern size_t hash_get_n_buckets (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
/* Return the number of slots in use (non-empty buckets). */
extern size_t hash_get_n_buckets_used (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
/* Return the number of active entries. */
extern size_t hash_get_n_entries (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
/* Return the length of the longest chain (bucket). */
extern size_t hash_get_max_bucket_length (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
/* Do a mild validation of a hash table, by traversing it and checking two
statistics. */
extern bool hash_table_ok (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
extern void hash_print_statistics (const Hash_table *table, FILE *stream);
@ -99,7 +99,7 @@ extern void *hash_lookup (const Hash_table *table, const void *entry);
/* Return the first data in the table, or NULL if the table is empty. */
extern void *hash_get_first (const Hash_table *table)
;
_GL_ATTRIBUTE_PURE;
/* Return the user data for the entry following ENTRY, where ENTRY has been
returned by a previous call to either 'hash_get_first' or 'hash_get_next'.
@ -124,25 +124,40 @@ typedef bool (*Hash_processor) (void *entry, void *processor_data);
extern size_t hash_do_for_each (const Hash_table *table,
Hash_processor processor, void *processor_data);
/* Return a hash index for a NUL-terminated STRING between 0 and N_BUCKETS-1.
This is a convenience routine for constructing other hashing functions. */
extern size_t hash_string (const char *string, size_t n_buckets)
_GL_ATTRIBUTE_PURE;
/* Return a hash code of ENTRY, in the range 0..TABLE_SIZE-1.
This hash code function must have the property that if the comparator of
ENTRY1 and ENTRY2 returns true, the hasher returns the same value for ENTRY1
and for ENTRY2.
The hash code function typically computes an unsigned integer and at the end
performs a % TABLE_SIZE modulo operation. This modulo operation is performed
as part of this hash code function, not by the caller, because in some cases
the unsigned integer will be a 'size_t', in other cases an 'uintmax_t' or
even larger. */
typedef size_t (*Hash_hasher) (const void *entry, size_t table_size);
/* Compare two entries, ENTRY1 (being looked up or being inserted) and
ENTRY2 (already in the table) for equality. Return true for equal,
false otherwise. */
typedef bool (*Hash_comparator) (const void *entry1, const void *entry2);
/* This function is invoked when an ENTRY is removed from the hash table. */
typedef void (*Hash_data_freer) (void *entry);
/*
* Allocation and clean-up.
*/
/* Return a hash index for a NUL-terminated STRING between 0 and N_BUCKETS-1.
This is a convenience routine for constructing other hashing functions. */
extern size_t hash_string (const char *string, size_t n_buckets)
;
extern void hash_reset_tuning (Hash_tuning *tuning);
typedef size_t (*Hash_hasher) (const void *entry, size_t table_size);
typedef bool (*Hash_comparator) (const void *entry1, const void *entry2);
typedef void (*Hash_data_freer) (void *entry);
/* Reclaim all storage associated with a hash table. If a data_freer
function has been supplied by the user when the hash table was created,
this function applies it to the data of each entry before freeing that
entry. */
entry. This function preserves errno, like 'free'. */
extern void hash_free (Hash_table *table);
/* Allocate and return a new hash table, or NULL upon failure. The initial
@ -177,23 +192,30 @@ extern void hash_free (Hash_table *table);
You should specify this function only if you want these functions to free
all of your 'data' data. This is typically the case when your data is
simply an auxiliary struct that you have malloc'd to aggregate several
values. */
values.
Set errno on failure; otherwise errno is unspecified. */
_GL_ATTRIBUTE_NODISCARD
extern Hash_table *hash_initialize (size_t candidate,
const Hash_tuning *tuning,
Hash_hasher hasher,
Hash_comparator comparator,
Hash_data_freer data_freer);
Hash_data_freer data_freer)
_GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC (hash_free, 1);
/* Same as hash_initialize, but invokes xalloc_die on memory exhaustion. */
/* Like hash_initialize, but invokes xalloc_die instead of returning NULL. */
/* This function is defined by module 'xhash'. */
_GL_ATTRIBUTE_NODISCARD
extern Hash_table *hash_xinitialize (size_t candidate,
const Hash_tuning *tuning,
Hash_hasher hasher,
Hash_comparator comparator,
Hash_data_freer data_freer);
Hash_data_freer data_freer)
_GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC (hash_free, 1)
_GL_ATTRIBUTE_RETURNS_NONNULL;
/* Make all buckets empty, placing any chained entries on the free list.
Apply the user-specified function data_freer (if any) to the datas of any
Apply the user-specified function data_freer (if any) to the data of any
affected entries. */
extern void hash_clear (Hash_table *table);
@ -207,23 +229,26 @@ extern void hash_clear (Hash_table *table);
the table may receive at least CANDIDATE different user entries, including
those already in the table, before any other growth of the hash table size
occurs. If TUNING->IS_N_BUCKETS is true, then CANDIDATE specifies the
exact number of buckets desired. Return true iff the rehash succeeded. */
exact number of buckets desired. Return true iff the rehash succeeded,
false (setting errno) otherwise. */
_GL_ATTRIBUTE_NODISCARD
extern bool hash_rehash (Hash_table *table, size_t candidate);
/* If ENTRY matches an entry already in the hash table, return the pointer
to the entry from the table. Otherwise, insert ENTRY and return ENTRY.
Return NULL if the storage required for insertion cannot be allocated.
This implementation does not support duplicate entries or insertion of
NULL. */
Return NULL (setting errno) if the storage required for insertion
cannot be allocated. This implementation does not support
duplicate entries or insertion of NULL. */
_GL_ATTRIBUTE_NODISCARD
extern void *hash_insert (Hash_table *table, const void *entry);
/* Same as hash_insert, but invokes xalloc_die on memory exhaustion. */
/* Same as hash_insert, but invokes xalloc_die instead of returning NULL. */
/* This function is defined by module 'xhash'. */
extern void *hash_xinsert (Hash_table *table, const void *entry);
/* Insert ENTRY into hash TABLE if there is not already a matching entry.
Return -1 upon memory allocation failure.
Return -1 (setting errno) upon memory allocation failure.
Return 1 if insertion succeeded.
Return 0 if there is already a matching entry in the table,
and in that case, if MATCHED_ENT is non-NULL, set *MATCHED_ENT
@ -247,6 +272,7 @@ extern void *hash_remove (Hash_table *table, const void *entry);
/* Same as hash_remove. This interface is deprecated.
FIXME: Remove in 2022. */
_GL_ATTRIBUTE_DEPRECATED
extern void *hash_delete (Hash_table *table, const void *entry);
# ifdef __cplusplus

View File

@ -1,43 +1,84 @@
# 00gnulib.m4 serial 3
dnl Copyright (C) 2009-2019 Free Software Foundation, Inc.
# 00gnulib.m4
# serial 9
dnl Copyright (C) 2009-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl This file is offered as-is, without any warranty.
dnl This file must be named something that sorts before all other
dnl gnulib-provided .m4 files. It is needed until such time as we can
dnl assume Autoconf 2.64, with its improved AC_DEFUN_ONCE and
dnl m4_divert semantics.
dnl gnulib-provided .m4 files. It is needed until the clang fix has
dnl been included in Autoconf.
# Until autoconf 2.63, handling of the diversion stack required m4_init
# to be called first; but this does not happen with aclocal. Wrapping
# the entire execution in another layer of the diversion stack fixes this.
# Worse, prior to autoconf 2.62, m4_wrap depended on the underlying m4
# for whether it was FIFO or LIFO; in order to properly balance with
# m4_init, we need to undo our push just before anything wrapped within
# the m4_init body. The way to ensure this is to wrap both sides of
# m4_init with a one-shot macro that does the pop at the right time.
m4_ifndef([_m4_divert_diversion],
[m4_divert_push([KILL])
m4_define([gl_divert_fixup], [m4_divert_pop()m4_define([$0])])
m4_define([m4_init],
[gl_divert_fixup()]m4_defn([m4_init])[gl_divert_fixup()])])
# AC_DEFUN_ONCE([NAME], VALUE)
# ----------------------------
# Define NAME to expand to VALUE on the first use (whether by direct
# expansion, or by AC_REQUIRE), and to nothing on all subsequent uses.
# Avoid bugs in AC_REQUIRE in Autoconf 2.63 and earlier. This
# definition is slower than the version in Autoconf 2.64, because it
# can only use interfaces that existed since 2.59; but it achieves the
# same effect. Quoting is necessary to avoid confusing Automake.
m4_version_prereq([2.63.263], [],
[m4_define([AC][_DEFUN_ONCE],
[AC][_DEFUN([$1],
[AC_REQUIRE([_gl_DEFUN_ONCE([$1])],
[m4_indir([_gl_DEFUN_ONCE([$1])])])])]dnl
[AC][_DEFUN([_gl_DEFUN_ONCE([$1])], [$2])])])
# The following definitions arrange to use a compiler option
# -Werror=implicit-function-declaration in AC_CHECK_DECL, when the
# compiler is clang. Without it, clang implicitly declares "known"
# library functions in C mode, but not in C++ mode, which would cause
# Gnulib to omit a declaration and thus later produce an error in C++
# mode. As of clang 9.0, these "known" functions are identified through
# LIBBUILTIN invocations in the LLVM source file
# llvm/tools/clang/include/clang/Basic/Builtins.def.
# It's not possible to AC_REQUIRE the extra tests from AC_CHECK_DECL,
# because AC_CHECK_DECL, like other Autoconf built-ins, is not supposed
# to AC_REQUIRE anything: some configure.ac files have their first
# AC_CHECK_DECL executed conditionally. Therefore append the extra tests
# to AC_PROG_CC.
AC_DEFUN([gl_COMPILER_CLANG],
[
dnl AC_REQUIRE([AC_PROG_CC])
AC_CACHE_CHECK([whether the compiler is clang],
[gl_cv_compiler_clang],
[dnl Use _AC_COMPILE_IFELSE instead of AC_EGREP_CPP, to avoid error
dnl "circular dependency of AC_LANG_COMPILER(C)" if AC_PROG_CC has
dnl not yet been invoked.
_AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[
#ifdef __clang__
barfbarf
#endif
]],[[]])
],
[gl_cv_compiler_clang=no],
[gl_cv_compiler_clang=yes])
])
])
AC_DEFUN([gl_COMPILER_PREPARE_CHECK_DECL],
[
dnl AC_REQUIRE([AC_PROG_CC])
dnl AC_REQUIRE([gl_COMPILER_CLANG])
AC_CACHE_CHECK([for compiler option needed when checking for declarations],
[gl_cv_compiler_check_decl_option],
[if test $gl_cv_compiler_clang = yes; then
dnl Test whether the compiler supports the option
dnl '-Werror=implicit-function-declaration'.
saved_ac_compile="$ac_compile"
ac_compile="$ac_compile -Werror=implicit-function-declaration"
dnl Use _AC_COMPILE_IFELSE instead of AC_COMPILE_IFELSE, to avoid a
dnl warning "AC_COMPILE_IFELSE was called before AC_USE_SYSTEM_EXTENSIONS".
_AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]],[[]])],
[gl_cv_compiler_check_decl_option='-Werror=implicit-function-declaration'],
[gl_cv_compiler_check_decl_option=none])
ac_compile="$saved_ac_compile"
else
gl_cv_compiler_check_decl_option=none
fi
])
if test "x$gl_cv_compiler_check_decl_option" != xnone; then
ac_compile_for_check_decl="$ac_compile $gl_cv_compiler_check_decl_option"
else
ac_compile_for_check_decl="$ac_compile"
fi
])
dnl Redefine _AC_CHECK_DECL_BODY so that it references ac_compile_for_check_decl
dnl instead of ac_compile. If, for whatever reason, the override of AC_PROG_CC
dnl in zzgnulib.m4 is inactive, use the original ac_compile.
m4_define([_AC_CHECK_DECL_BODY],
[ ac_saved_ac_compile="$ac_compile"
if test -n "$ac_compile_for_check_decl"; then
ac_compile="$ac_compile_for_check_decl"
fi]
m4_defn([_AC_CHECK_DECL_BODY])[ ac_compile="$ac_saved_ac_compile"
])
# gl_00GNULIB
# -----------

File diff suppressed because it is too large Load Diff

25
m4/zzgnulib.m4 Normal file
View File

@ -0,0 +1,25 @@
# zzgnulib.m4
# serial 1
dnl Copyright (C) 2020-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl This file is offered as-is, without any warranty.
dnl This file must be named something that sorts after all other
dnl package- or gnulib-provided .m4 files - at least for those packages
dnl that redefine AC_PROG_CC.
dnl Redefine AC_PROG_CC so that it ends with invocations of gl_COMPILER_CLANG
dnl and gl_COMPILER_PREPARE_CHECK_DECL.
m4_define([AC_PROG_CC],
m4_defn([AC_PROG_CC])[
gl_COMPILER_CLANG
gl_COMPILER_PREPARE_CHECK_DECL
])
# gl_ZZGNULIB
# -----------
# Witness macro that this file has been included. Needed to force
# Automake to include this file after all other gnulib .m4 files.
AC_DEFUN([gl_ZZGNULIB])

474
main.c
View File

@ -59,6 +59,7 @@
#include <sys/sysmacros.h>
#include <sys/xattr.h>
#include <linux/fs.h>
#include <linux/xattr.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <pthread.h>
@ -68,8 +69,7 @@
#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
(__extension__ ({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))
@ -142,6 +142,7 @@ open_by_handle_at (int mount_fd, struct file_handle *handle, int flags)
#define ORIGIN_XATTR "user.fuseoverlayfs.origin"
#define OPAQUE_XATTR "user.fuseoverlayfs.opaque"
#define XATTR_CONTAINERS_PREFIX "user.containers."
#define XATTR_CONTAINERS_OVERRIDE_PREFIX "user.containers.override_"
#define UNPRIVILEGED_XATTR_PREFIX "user.overlay."
#define UNPRIVILEGED_OPAQUE_XATTR "user.overlay.opaque"
#define PRIVILEGED_XATTR_PREFIX "trusted.overlay."
@ -160,8 +161,7 @@ _Static_assert (sizeof (fuse_ino_t) >= sizeof (uintptr_t),
#else
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct
{
unsigned _uintptr_to_must_hold_fuse_ino_t:
((sizeof (fuse_ino_t) >= sizeof (uintptr_t)) ? 1 : -1);
unsigned _uintptr_to_must_hold_fuse_ino_t : ((sizeof (fuse_ino_t) >= sizeof (uintptr_t)) ? 1 : -1);
};
#endif
@ -509,28 +509,46 @@ node_dirfd (struct ovl_node *n)
}
static bool
has_prefix (const char *str, const char *pref)
can_access_xattr (const struct ovl_layer *l, const char *name)
{
while (1)
{
if (*pref == '\0')
return true;
if (*str == '\0')
return false;
if (*pref != *str)
return false;
str++;
pref++;
}
return false;
return ! (has_prefix (name, XATTR_PREFIX)
|| has_prefix (name, PRIVILEGED_XATTR_PREFIX)
|| has_prefix (name, UNPRIVILEGED_XATTR_PREFIX)
|| (l->stat_override_mode == STAT_OVERRIDE_CONTAINERS && has_prefix (name, XATTR_SECURITY_PREFIX)));
}
static bool
can_access_xattr (const char *name)
encoded_xattr_name (const struct ovl_layer *l, const char *name)
{
return !has_prefix (name, XATTR_PREFIX)
&& !has_prefix (name, PRIVILEGED_XATTR_PREFIX)
&& !has_prefix (name, UNPRIVILEGED_XATTR_PREFIX);
return has_prefix (name, XATTR_CONTAINERS_OVERRIDE_PREFIX) && ! can_access_xattr (l, name + sizeof (XATTR_CONTAINERS_OVERRIDE_PREFIX) - 1);
}
static const char *
decode_xattr_name (const struct ovl_layer *l, const char *name)
{
if (encoded_xattr_name (l, name))
return name + sizeof (XATTR_CONTAINERS_OVERRIDE_PREFIX) - 1;
if (can_access_xattr (l, name))
return name;
return NULL;
}
static const char *
encode_xattr_name (const struct ovl_layer *l, char *buf,
const char *name)
{
if (can_access_xattr (l, name))
return name;
if (l->stat_override_mode != STAT_OVERRIDE_CONTAINERS || strlen (name) > XATTR_NAME_MAX + 1 - sizeof (XATTR_CONTAINERS_OVERRIDE_PREFIX))
return NULL;
strcpy (buf, XATTR_CONTAINERS_OVERRIDE_PREFIX);
strcpy (buf + sizeof (XATTR_CONTAINERS_OVERRIDE_PREFIX) - 1, name);
return buf;
}
static ssize_t
@ -541,17 +559,21 @@ write_permission_xattr (struct ovl_data *lo, int fd, const char *path, uid_t uid
int ret;
const char *name = NULL;
switch (lo->xattr_permissions)
switch (get_upper_layer (lo)->stat_override_mode)
{
case 0:
case STAT_OVERRIDE_NONE:
return 0;
case 1:
case STAT_OVERRIDE_USER:
name = XATTR_OVERRIDE_STAT;
break;
case STAT_OVERRIDE_PRIVILEGED:
name = XATTR_PRIVILEGED_OVERRIDE_STAT;
break;
case 2:
name = XATTR_OVERRIDE_STAT;
case STAT_OVERRIDE_CONTAINERS:
name = XATTR_OVERRIDE_CONTAINERS_STAT;
break;
default:
@ -624,22 +646,32 @@ do_fchownat (struct ovl_data *lo, int dfd, const char *path, uid_t uid, gid_t gi
#define fchownat ERROR
static int
do_fchmod (struct ovl_data *lo, int fd, mode_t mode)
do_stat (struct ovl_node *node, int fd, const char *path, struct stat *st)
{
struct ovl_layer *l = node->layer;
if (fd >= 0)
return l->ds->fstat (l, fd, path, STATX_BASIC_STATS, st);
if (path != NULL)
return stat (path, st);
if (node->hidden)
return fstatat (node_dirfd (node), node->path, st, AT_SYMLINK_NOFOLLOW);
return l->ds->statat (l, node->path, st, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS);
}
static int
do_fchmod (struct ovl_data *lo, struct ovl_node *node, int fd, mode_t mode)
{
if (lo->xattr_permissions)
{
struct ovl_layer *upper = get_upper_layer (lo);
struct stat st;
if (upper == NULL)
{
errno = EROFS;
return -1;
}
st.st_uid = 0;
st.st_gid = 0;
if (override_mode (upper, fd, NULL, NULL, &st) < 0 && errno != ENODATA)
if (do_stat (node, fd, NULL, &st) < 0)
return -1;
return write_permission_xattr (lo, fd, NULL, st.st_uid, st.st_gid, mode);
@ -650,22 +682,15 @@ do_fchmod (struct ovl_data *lo, int fd, mode_t mode)
#define fchmod ERROR
static int
do_chmod (struct ovl_data *lo, const char *path, mode_t mode)
do_chmod (struct ovl_data *lo, struct ovl_node *node, const char *path, mode_t mode)
{
if (lo->xattr_permissions)
{
struct ovl_layer *upper = get_upper_layer (lo);
struct stat st;
if (upper == NULL)
{
errno = EROFS;
return -1;
}
st.st_uid = 0;
st.st_gid = 0;
if (override_mode (upper, -1, path, NULL, &st) < 0 && errno != ENODATA)
if (do_stat (node, -1, path, &st) < 0)
return -1;
return write_permission_xattr (lo, -1, path, st.st_uid, st.st_gid, mode);
@ -923,14 +948,8 @@ rpl_stat (fuse_req_t req, struct ovl_node *node, int fd, const char *path, struc
if (st_in)
memcpy (st, st_in, sizeof (*st));
else if (fd >= 0)
ret = l->ds->fstat (l, fd, path, STATX_BASIC_STATS, st);
else if (path != NULL)
ret = stat (path, st);
else if (node->hidden)
ret = fstatat (node_dirfd (node), node->path, st, AT_SYMLINK_NOFOLLOW);
else
ret = l->ds->statat (l, node->path, st, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS);
ret = do_stat (node, fd, path, st);
if (ret < 0)
return ret;
@ -999,7 +1018,7 @@ node_free (void *p)
if (n->parent)
{
if (n->parent->children && hash_lookup (n->parent->children, n) == n)
hash_delete (n->parent->children, n);
hash_remove (n->parent->children, n);
n->parent->loaded = 0;
n->parent = NULL;
}
@ -1059,7 +1078,7 @@ drop_node_from_ino (Hash_table *inodes, struct ovl_node *node)
if (ino->lookups == 0)
{
hash_delete (inodes, ino);
hash_remove (inodes, ino);
inode_free (ino);
return;
}
@ -1094,7 +1113,7 @@ direct_renameat2 (int olddirfd, const char *oldpath,
static int
hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
{
char *newpath = NULL;
cleanup_free char *newpath = NULL;
int ret;
ret = asprintf (&newpath, "%lu", get_next_wd_counter ());
@ -1289,7 +1308,7 @@ do_forget (struct ovl_data *lo, fuse_ino_t ino, uint64_t nlookup)
i->lookups -= nlookup;
if (i->lookups <= 0)
{
hash_delete (lo->inodes, i);
hash_remove (lo->inodes, i);
inode_free (i);
}
return true;
@ -1507,6 +1526,7 @@ make_ovl_node (struct ovl_data *lo, const char *path, struct ovl_layer *layer, c
struct ovl_layer *it;
cleanup_free char *npath = NULL;
char whiteout_path[PATH_MAX];
bool stop_lookup = false;
npath = strdup (ret->path);
if (npath == NULL)
@ -1517,13 +1537,16 @@ make_ovl_node (struct ovl_data *lo, const char *path, struct ovl_layer *layer, c
else
strconcat3 (whiteout_path, PATH_MAX, "/.wh.", name, NULL);
for (it = layer; it; it = it->next)
for (it = layer; it && ! stop_lookup; it = it->next)
{
ssize_t s;
cleanup_free char *val = NULL;
cleanup_free char *origin = NULL;
cleanup_close int fd = -1;
if (parent && parent->last_layer == it)
stop_lookup = true;
if (dir_p)
{
int r;
@ -1640,12 +1663,12 @@ insert_node (struct ovl_node *parent, struct ovl_node *item, bool replace)
if (prev_parent)
{
if (hash_lookup (prev_parent->children, item) == item)
hash_delete (prev_parent->children, item);
hash_remove (prev_parent->children, item);
}
if (replace)
{
old = hash_delete (parent->children, item);
old = hash_remove (parent->children, item);
if (old)
node_free (old);
}
@ -1768,7 +1791,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
continue;
else
{
hash_delete (n->children, child);
hash_remove (n->children, child);
node_free (child);
child = NULL;
}
@ -1905,6 +1928,58 @@ cleanup_layerp (struct ovl_layer **p)
#define cleanup_layer __attribute__ ((cleanup (cleanup_layerp)))
static void
unescape (char *input)
{
char *dest = input;
if (input == NULL)
return;
for (; *input; input++)
{
if (*input == '\\')
continue;
*dest++ = *input;
}
*dest = '\0';
}
static char *
get_next_path (char *it, char **saveptr)
{
char *ret;
if (*saveptr == NULL)
*saveptr = it;
ret = *saveptr;
if (*ret == '\0')
return NULL;
while (1)
{
if (**saveptr == '\0')
break;
if (**saveptr == ':')
{
**saveptr = '\0';
(*saveptr)++;
break;
}
else if (**saveptr == '\\')
{
memmove (*saveptr, *saveptr + 1, strlen (*saveptr) + 1);
}
(*saveptr)++;
}
return ret;
}
static struct ovl_layer *
read_dirs (struct ovl_data *lo, char *path, bool low, struct ovl_layer *layers)
{
@ -1923,7 +1998,7 @@ read_dirs (struct ovl_data *lo, char *path, bool low, struct ovl_layer *layers)
while (last && last->next)
last = last->next;
for (it = strtok_r (buf, ":", &saveptr); it; it = strtok_r (NULL, ":", &saveptr))
for (it = get_next_path (buf, &saveptr); it; it = get_next_path (NULL, &saveptr))
{
char *name, *data;
char *it_path = it;
@ -2584,7 +2659,7 @@ inherit_acl (struct ovl_data *lo, struct ovl_node *parent, int targetfd, const c
/* in-place filter xattrs that cannot be accessed. */
static ssize_t
filter_xattrs_list (char *buf, ssize_t len)
filter_xattrs_list (struct ovl_layer *l, char *buf, ssize_t len)
{
ssize_t ret = 0;
char *it;
@ -2600,14 +2675,16 @@ filter_xattrs_list (char *buf, ssize_t len)
it_len = strlen (it) + 1;
if (can_access_xattr (it))
if (can_access_xattr (l, it))
{
it += it_len;
ret += it_len;
}
else
{
char *next = it + it_len;
char *next = it;
next += encoded_xattr_name (l, it) ? sizeof (XATTR_CONTAINERS_OVERRIDE_PREFIX) - 1 : it_len;
memmove (it, next, buf + len - next);
len -= it_len;
@ -2667,7 +2744,7 @@ ovl_listxattr (fuse_req_t req, fuse_ino_t ino, size_t size)
return;
}
len = filter_xattrs_list (buf, ret);
len = filter_xattrs_list (node->layer, buf, ret);
if (size == 0)
fuse_reply_xattr (req, len);
@ -2682,7 +2759,8 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
ssize_t len;
struct ovl_node *node;
struct ovl_data *lo = ovl_data (req);
cleanup_free char *buf = NULL;
cleanup_free char *value_buf = NULL;
char name_buf[XATTR_NAME_MAX + 1];
int ret;
if (UNLIKELY (ovl_debug (req)))
@ -2694,12 +2772,6 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
return;
}
if (! can_access_xattr (name))
{
fuse_reply_err (req, ENODATA);
return;
}
node = do_lookup_file (lo, ino, NULL);
if (node == NULL || node->whiteout)
{
@ -2707,10 +2779,17 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
return;
}
name = encode_xattr_name (node->layer, name_buf, name);
if (! name)
{
fuse_reply_err (req, ENODATA);
return;
}
if (size > 0)
{
buf = malloc (size);
if (buf == NULL)
value_buf = malloc (size);
if (value_buf == NULL)
{
fuse_reply_err (req, errno);
return;
@ -2718,12 +2797,12 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
}
if (! node->hidden)
ret = node->layer->ds->getxattr (node->layer, node->path, name, buf, size);
ret = node->layer->ds->getxattr (node->layer, node->path, name, value_buf, size);
else
{
char path[PATH_MAX];
strconcat3 (path, PATH_MAX, lo->workdir, "/", node->path);
ret = getxattr (path, name, buf, size);
ret = getxattr (path, name, value_buf, size);
}
if (ret < 0)
@ -2737,7 +2816,7 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
if (size == 0)
fuse_reply_xattr (req, len);
else
fuse_reply_buf (req, buf, len);
fuse_reply_buf (req, value_buf, len);
}
static void
@ -2758,7 +2837,8 @@ ovl_access (fuse_req_t req, fuse_ino_t ino, int mask)
}
static int
copy_xattr (int sfd, int dfd, char *buf, size_t buf_size)
copy_xattr (const struct ovl_layer *sl, int sfd,
const struct ovl_layer *dl, int dfd, char *buf, size_t buf_size)
{
ssize_t xattr_len;
@ -2769,9 +2849,16 @@ copy_xattr (int sfd, int dfd, char *buf, size_t buf_size)
for (it = buf; it - buf < xattr_len; it += strlen (it) + 1)
{
cleanup_free char *v = NULL;
const char *decoded_name = decode_xattr_name (sl, it);
const char *encoded_name;
char buf[XATTR_NAME_MAX + 1];
ssize_t s;
if (! can_access_xattr (it))
if (! decoded_name)
continue;
encoded_name = encode_xattr_name (dl, buf, decoded_name);
if (! encoded_name)
continue;
s = safe_read_xattr (&v, sfd, it, 256);
@ -2782,7 +2869,7 @@ copy_xattr (int sfd, int dfd, char *buf, size_t buf_size)
return -1;
}
if (fsetxattr (dfd, it, v, s, 0) < 0)
if (fsetxattr (dfd, encoded_name, v, s, 0) < 0)
{
if (errno == EINVAL || errno == EOPNOTSUPP)
continue;
@ -2858,7 +2945,8 @@ static int create_node_directory (struct ovl_data *lo, struct ovl_node *src);
static int
create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct timespec *times,
struct ovl_node *parent, int xattr_sfd, uid_t uid, gid_t gid, mode_t mode, bool set_opaque, struct stat *st_out)
struct ovl_node *parent, struct ovl_layer *sl, int xattr_sfd,
uid_t uid, gid_t gid, mode_t mode, bool set_opaque, struct stat *st_out)
{
int ret;
int saved_errno;
@ -2866,11 +2954,9 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct
cleanup_free char *buf = NULL;
char wd_tmp_file_name[32];
bool need_rename;
mode_t backing_file_mode = mode | (lo->xattr_permissions ? 0755 : 0);
if (lo->xattr_permissions)
mode |= 0755;
need_rename = set_opaque || times || xattr_sfd >= 0 || uid != lo->uid || gid != lo->gid;
need_rename = set_opaque || times || xattr_sfd >= 0 || uid != lo->uid || gid != lo->gid || get_upper_layer (lo)->stat_override_mode != STAT_OVERRIDE_NONE;
if (! need_rename)
{
/* mkdir can be used directly without a temporary directory in the working directory. */
@ -2892,7 +2978,7 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct
sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
ret = mkdirat (lo->workdir_fd, wd_tmp_file_name, mode);
ret = mkdirat (lo->workdir_fd, wd_tmp_file_name, backing_file_mode);
if (ret < 0)
goto out;
@ -2924,7 +3010,7 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct
goto out;
}
ret = copy_xattr (xattr_sfd, dfd, buf, buf_size);
ret = copy_xattr (sl, xattr_sfd, get_upper_layer (lo), dfd, buf, buf_size);
if (ret < 0)
goto out;
}
@ -2941,6 +3027,7 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct
ret = fstat (dfd, st_out);
if (ret < 0)
goto out;
st_out->st_mode = (st_out->st_mode & S_IFMT) | (mode & ~S_IFMT);
}
ret = inherit_acl (lo, parent, dfd, NULL);
@ -3013,7 +3100,10 @@ create_node_directory (struct ovl_data *lo, struct ovl_node *src)
times[0] = st.st_atim;
times[1] = st.st_mtim;
ret = create_directory (lo, get_upper_layer (lo)->fd, src->path, times, src->parent, sfd, st.st_uid, st.st_gid, st.st_mode, false, NULL);
if (override_mode (src->layer, sfd, NULL, NULL, &st) < 0 && errno != ENODATA && errno != EOPNOTSUPP)
return -1;
ret = create_directory (lo, get_upper_layer (lo)->fd, src->path, times, src->parent, src->layer, sfd, st.st_uid, st.st_gid, st.st_mode, false, NULL);
if (ret == 0)
{
src->layer = get_upper_layer (lo);
@ -3050,8 +3140,7 @@ copy_fd_to_fd (int sfd, int dfd, char *buf, size_t buf_size)
return ret;
nread -= ret;
written += ret;
}
while (nread);
} while (nread);
}
return 0;
}
@ -3193,7 +3282,7 @@ copyup (struct ovl_data *lo, struct ovl_node *node)
if (ret < 0)
goto exit;
ret = copy_xattr (sfd, dfd, buf, buf_size);
ret = copy_xattr (node->layer, sfd, get_upper_layer (lo), dfd, buf, buf_size);
if (ret < 0)
goto exit;
@ -3411,7 +3500,7 @@ do_rm (fuse_req_t req, fuse_ino_t parent, const char *name, bool dirp)
node_set_name (&key, (char *) name);
rm = hash_delete (pnode->children, &key);
rm = hash_remove (pnode->children, &key);
fuse_lowlevel_notify_inval_inode (lo->se, node_to_inode (node), -1, 0);
if (rm)
{
@ -3473,6 +3562,7 @@ ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name,
cleanup_lock int l = enter_big_lock ();
struct ovl_data *lo = ovl_data (req);
struct ovl_node *node;
char name_buf[XATTR_NAME_MAX + 1];
int ret;
if (UNLIKELY (ovl_debug (req)))
@ -3485,12 +3575,6 @@ ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name,
return;
}
if (has_prefix (name, PRIVILEGED_XATTR_PREFIX) || has_prefix (name, XATTR_PREFIX) || has_prefix (name, XATTR_CONTAINERS_PREFIX))
{
fuse_reply_err (req, EPERM);
return;
}
node = do_lookup_file (lo, ino, NULL);
if (node == NULL || node->whiteout)
{
@ -3505,6 +3589,13 @@ ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name,
return;
}
name = encode_xattr_name (node->layer, name_buf, name);
if (! name)
{
fuse_reply_err (req, EPERM);
return;
}
if (! node->hidden)
ret = direct_setxattr (node->layer, node->path, name, value, size, flags);
else
@ -3546,6 +3637,7 @@ ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name)
cleanup_lock int l = enter_big_lock ();
struct ovl_node *node;
struct ovl_data *lo = ovl_data (req);
char name_buf[XATTR_NAME_MAX + 1];
int ret;
if (UNLIKELY (ovl_debug (req)))
@ -3565,6 +3657,13 @@ ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name)
return;
}
name = encode_xattr_name (node->layer, name_buf, name);
if (! name)
{
fuse_reply_err (req, EPERM);
return;
}
if (! node->hidden)
ret = direct_removexattr (node->layer, node->path, name);
else
@ -3587,6 +3686,7 @@ static int
direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid, gid_t gid, int flags, mode_t mode)
{
struct ovl_data *lo = l->ovl_data;
mode_t backing_file_mode = mode | (lo->xattr_permissions ? 0755 : 0);
cleanup_close int fd = -1;
char wd_tmp_file_name[32];
int ret;
@ -3594,7 +3694,7 @@ direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid,
/* try to create directly the file if it doesn't need to be chowned. */
if (uid == lo->uid && gid == lo->gid && l->stat_override_mode == STAT_OVERRIDE_NONE)
{
ret = TEMP_FAILURE_RETRY (safe_openat (get_upper_layer (lo)->fd, path, flags, mode));
ret = TEMP_FAILURE_RETRY (safe_openat (get_upper_layer (lo)->fd, path, flags, backing_file_mode));
if (ret >= 0)
return ret;
/* if it fails (e.g. there is a whiteout) then fallback to create it in
@ -3603,7 +3703,7 @@ direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid,
sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
fd = TEMP_FAILURE_RETRY (safe_openat (lo->workdir_fd, wd_tmp_file_name, flags, mode));
fd = TEMP_FAILURE_RETRY (safe_openat (lo->workdir_fd, wd_tmp_file_name, flags, backing_file_mode));
if (fd < 0)
return -1;
if (uid != lo->uid || gid != lo->gid || l->stat_override_mode != STAT_OVERRIDE_NONE)
@ -3716,7 +3816,7 @@ ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mod
uid = get_uid (lo, ctx->uid);
gid = get_gid (lo, ctx->gid);
fd = direct_create_file (get_upper_layer (lo), get_upper_layer (lo)->fd, path, uid, gid, flags, (mode & ~ctx->umask) | (lo->xattr_permissions ? 0755 : 0));
fd = direct_create_file (get_upper_layer (lo), get_upper_layer (lo)->fd, path, uid, gid, flags, mode & ~ctx->umask);
if (fd < 0)
return fd;
@ -3787,7 +3887,8 @@ ovl_read (fuse_req_t req, fuse_ino_t ino, size_t size,
struct fuse_bufvec buf = FUSE_BUFVEC_INIT (size);
if (UNLIKELY (ovl_debug (req)))
fprintf (stderr, "ovl_read(ino=%" PRIu64 ", size=%zd, "
"off=%lu)\n", ino, size, (unsigned long) offset);
"off=%lu)\n",
ino, size, (unsigned long) offset);
buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK | FUSE_BUF_FD_RETRY;
buf.buf[0].fd = fi->fh;
buf.buf[0].pos = offset;
@ -3822,7 +3923,7 @@ ovl_write_buf (fuse_req_t req, fuse_ino_t ino,
/* if it is a writepage request, make sure to restore the setuid bit. */
if (fi->writepage && (inode->mode & (S_ISUID | S_ISGID)))
{
if (do_fchmod (lo, fi->fh, inode->mode) < 0)
if (do_fchmod (lo, inode->node, fi->fh, inode->mode) < 0)
{
fuse_reply_err (req, errno);
return;
@ -3927,9 +4028,6 @@ ovl_create (fuse_req_t req, fuse_ino_t parent, const char *name,
fi->flags = fi->flags | O_CREAT;
if (lo->xattr_permissions)
mode |= 0755;
fd = ovl_do_open (req, parent, name, fi->flags, mode, &node, &st);
if (fd < 0)
{
@ -4137,9 +4235,9 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru
if (to_set & FUSE_SET_ATTR_MODE)
{
if (fd >= 0)
ret = do_fchmod (lo, fd, attr->st_mode);
ret = do_fchmod (lo, node, fd, attr->st_mode);
else
ret = do_chmod (lo, path, attr->st_mode);
ret = do_chmod (lo, node, path, attr->st_mode);
if (ret < 0)
{
fuse_reply_err (req, errno);
@ -4163,6 +4261,24 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru
if (uid != -1 || gid != -1)
{
struct stat st;
if (do_stat (node, fd, NULL, &st) < 0)
{
fuse_reply_err (req, errno);
return;
}
if (uid == -1)
{
uid = st.st_uid;
}
if (gid == -1)
{
gid = st.st_gid;
}
if (fd >= 0)
ret = do_fchown (lo, fd, uid, gid, node->ino->mode);
else
@ -4507,13 +4623,12 @@ ovl_rename_exchange (fuse_req_t req, fuse_ino_t parent, const char *name,
if (destnode == NULL)
goto error;
ret = direct_renameat2 (srcfd, name, destfd, newname, flags);
if (ret < 0)
goto error;
rm1 = hash_delete (destpnode->children, destnode);
rm2 = hash_delete (pnode->children, node);
rm1 = hash_remove (destpnode->children, destnode);
rm2 = hash_remove (pnode->children, node);
tmp = node->path;
node->path = destnode->path;
@ -4743,7 +4858,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
hash_delete (pnode->children, node);
hash_remove (pnode->children, node);
free (node->name);
node_set_name (node, strdup (newname));
@ -4871,42 +4986,6 @@ ovl_readlink (fuse_req_t req, fuse_ino_t ino)
fuse_reply_readlink (req, buf);
}
static int
hide_all (struct ovl_data *lo, struct ovl_node *node)
{
struct ovl_node **nodes;
size_t i, nodes_size;
node = reload_dir (lo, node);
if (node == NULL)
return -1;
nodes_size = hash_get_n_entries (node->children) + 2;
nodes = malloc (sizeof (struct ovl_node *) * nodes_size);
if (nodes == NULL)
return -1;
nodes_size = hash_get_entries (node->children, (void **) nodes, nodes_size);
for (i = 0; i < nodes_size; i++)
{
struct ovl_node *it;
int ret;
it = nodes[i];
ret = create_whiteout (lo, node, it->name, false, true);
node_free (it);
if (ret < 0)
{
free(nodes);
return ret;
}
}
free (nodes);
return 0;
}
static void
ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
{
@ -4919,6 +4998,7 @@ ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev
struct fuse_entry_param e;
const struct fuse_ctx *ctx = fuse_req_ctx (req);
char wd_tmp_file_name[32];
mode_t backing_file_mode = mode | (lo->xattr_permissions ? 0755 : 0);
if (UNLIKELY (ovl_debug (req)))
fprintf (stderr, "ovl_mknod(ino=%" PRIu64 ", name=%s, mode=%d, rdev=%lu)\n",
@ -4932,9 +5012,6 @@ ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev
mode = mode & ~ctx->umask;
if (lo->xattr_permissions)
mode |= 0755;
node = do_lookup_file (lo, parent, name);
if (node != NULL && ! node->whiteout)
{
@ -4956,7 +5033,7 @@ ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev
return;
}
sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
ret = mknodat (lo->workdir_fd, wd_tmp_file_name, mode, rdev);
ret = mknodat (lo->workdir_fd, wd_tmp_file_name, backing_file_mode, rdev);
if (ret < 0)
{
fuse_reply_err (req, errno);
@ -5060,8 +5137,6 @@ ovl_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
fuse_reply_err (req, ENAMETOOLONG);
return;
}
if (lo->xattr_permissions)
mode |= 0755;
node = do_lookup_file (lo, parent, name);
if (node != NULL && ! node->whiteout)
@ -5096,7 +5171,7 @@ ovl_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
return;
}
ret = create_directory (lo, get_upper_layer (lo)->fd, path, NULL, pnode, -1,
ret = create_directory (lo, get_upper_layer (lo)->fd, path, NULL, pnode, NULL, -1,
get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), mode & ~ctx->umask,
true, &st);
if (ret < 0)
@ -5132,21 +5207,9 @@ ovl_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
return;
}
if (parent_upperdir_only)
{
node->last_layer = pnode->last_layer;
node->last_layer = get_upper_layer (lo);
if (get_timeout (lo) > 0)
node->loaded = 1;
}
else
{
ret = hide_all (lo, node);
if (ret < 0)
{
fuse_reply_err (req, errno);
return;
}
}
memset (&e, 0, sizeof (e));
@ -5438,8 +5501,7 @@ ovl_copy_file_range (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fus
}
#endif
static struct fuse_lowlevel_ops ovl_oper =
{
static struct fuse_lowlevel_ops ovl_oper = {
.statfs = ovl_statfs,
.access = ovl_access,
.getxattr = ovl_getxattr,
@ -5493,8 +5555,7 @@ fuse_opt_proc (void *data, const char *arg, int key, struct fuse_args *outargs)
return 1;
if (strcmp (arg, "-V") == 0)
return 1;
if ((strcmp (arg, "--debug") == 0) || (strcmp (arg, "-d") == 0) ||
(strcmp (arg, "debug") == 0))
if ((strcmp (arg, "--debug") == 0) || (strcmp (arg, "-d") == 0) || (strcmp (arg, "debug") == 0))
{
ovl_data->debug = 1;
return 1;
@ -5569,7 +5630,7 @@ get_new_args (int *argc, char **argv)
newargv[0] = argv[0];
if (geteuid () == 0)
newargv[1] = "-odefault_permissions,allow_other,suid,noatime,lazytime";
newargv[1] = "-odefault_permissions,allow_other,suid,noatime";
else
newargv[1] = "-odefault_permissions,noatime";
for (i = 1; i < *argc; i++)
@ -5634,7 +5695,8 @@ main (int argc, char *argv[])
struct fuse_session *se;
struct fuse_cmdline_opts opts;
char **newargv = get_new_args (&argc, argv);
struct ovl_data lo = {.debug = 0,
struct ovl_data lo = {
.debug = 0,
.uid_mappings = NULL,
.gid_mappings = NULL,
.uid_str = NULL,
@ -5701,18 +5763,7 @@ main (int argc, char *argv[])
if (lo.mountpoint == NULL)
error (EXIT_FAILURE, 0, "no mountpoint specified");
if (lo.upperdir != NULL)
{
cleanup_free char *full_path = NULL;
full_path = realpath (lo.upperdir, NULL);
if (full_path == NULL)
error (EXIT_FAILURE, errno, "cannot retrieve path for %s", lo.upperdir);
lo.upperdir = strdup (full_path);
if (lo.upperdir == NULL)
error (EXIT_FAILURE, errno, "cannot allocate memory");
}
unescape (lo.workdir);
set_limits ();
check_can_mknod (&lo);
@ -5785,27 +5836,58 @@ main (int argc, char *argv[])
}
else if (lo.xattr_permissions == 2)
{
get_upper_layer (&lo)->stat_override_mode = STAT_OVERRIDE_USER;
name = XATTR_OVERRIDE_STAT;
get_upper_layer (&lo)->stat_override_mode = STAT_OVERRIDE_CONTAINERS;
name = XATTR_OVERRIDE_CONTAINERS_STAT;
}
else
error (EXIT_FAILURE, 0, "invalid value for xattr_permissions");
s = fgetxattr (get_upper_layer (&lo)->fd, name, data, sizeof (data));
if (s < 0 && errno == ENODATA && lo.xattr_permissions == 2)
{
s = fgetxattr (get_upper_layer (&lo)->fd, XATTR_OVERRIDE_STAT, data, sizeof (data));
if (s >= 0)
{
get_upper_layer (&lo)->stat_override_mode = STAT_OVERRIDE_USER;
name = XATTR_OVERRIDE_STAT;
}
}
if (s < 0)
{
bool found = false;
struct ovl_layer *l;
if (errno != ENODATA)
error (EXIT_FAILURE, errno, "read xattr `%s` from upperdir", name);
else
{
struct stat st;
ret = fstat (get_upper_layer (&lo)->fd, &st);
if (ret < 0)
error (EXIT_FAILURE, errno, "stat upperdir");
ret = write_permission_xattr (&lo, get_upper_layer (&lo)->fd,
lo.upperdir,
st.st_uid, st.st_gid, st.st_mode);
for (l = get_lower_layers (&lo); l; l = l->next)
{
switch (lo.xattr_permissions)
{
case 1:
s = fgetxattr (l->fd, name, data, sizeof (data));
break;
case 2:
s = fgetxattr (l->fd, XATTR_OVERRIDE_CONTAINERS_STAT, data, sizeof (data));
if (s < 0 && errno == ENODATA)
s = fgetxattr (l->fd, XATTR_OVERRIDE_STAT, data, sizeof (data));
break;
}
if (s > 0)
{
ret = fsetxattr (get_upper_layer (&lo)->fd, name, data, s, 0);
if (ret < 0)
error (EXIT_FAILURE, errno, "fsetxattr mode to upper layer");
found = true;
break;
}
}
if (! found)
{
/* If the mode is missing, set a standard value. */
ret = write_permission_xattr (&lo, get_upper_layer (&lo)->fd, get_upper_layer (&lo)->path, 0, 0, 0555);
if (ret < 0)
error (EXIT_FAILURE, errno, "write xattr `%s` to upperdir", name);
}

View File

@ -37,8 +37,7 @@ struct ovl_plugin_context *load_plugins (const char *plugins);
/* taken from glibc unistd.h and fixes musl */
#ifndef TEMP_FAILURE_RETRY
#define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
(__extension__ ({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))

View File

@ -2,17 +2,17 @@
set -xeuo pipefail
mkdir lower upper workdir merged
mkdir lower:1 upper:2 workdir:3 merged
fuse-overlayfs -o sync=0,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged
fuse-overlayfs -o 'sync=0,lowerdir=lower\\:1,upperdir=upper\\:2,workdir=workdir\\:3,suid,dev' merged
docker run --rm -v $(pwd)/merged:/merged fedora dnf --installroot /merged --releasever 30 install -y glibc-common gedit
docker run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y glibc-common gedit
umount merged
# Make sure workdir is empty, and move the upper layer down
rm -rf workdir lower
mv upper lower
rm -rf lower:1 workdir:3
mv upper:2 lower
mkdir upper workdir
gcc -static -o suid-test $(dirname $0)/suid-test.c
@ -30,7 +30,7 @@ stat -c %A upper/suid | grep s
stat -c %a upper/nosuid | grep -v s
# Install some big packages
docker run --rm -v $(pwd)/merged:/merged fedora dnf --installroot /merged --releasever 30 install -y emacs texlive
docker run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y emacs texlive
docker run --rm -v $(pwd)/merged:/merged fedora sh -c 'rm /merged/usr/share/glib-2.0/schemas/gschemas.compiled; glib-compile-schemas /merged/usr/share/glib-2.0/schemas/'
@ -45,17 +45,10 @@ umount merged
rm -rf workdir lower upper
mkdir upper workdir lower
fuse-overlayfs -o sync=0,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged
# https://github.com/containers/fuse-overlayfs/issues/86
docker run --rm -v $(pwd)/merged:/merged quay.io/centos/centos:stream8 yum --installroot /merged -y --releasever 8 install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
umount merged
# fast_ino_check
fuse-overlayfs -o fast_ino_check=1,sync=0,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged
docker run --rm -v $(pwd)/merged:/merged quay.io/centos/centos:stream8 yum --installroot /merged -y --releasever 8 install nano
docker run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y glibc-common gedit
mkdir merged/a-directory
@ -265,3 +258,18 @@ fuse-overlayfs -o lowerdir=lower,upperdir=upper,workdir=workdir merged
stat merged/foo
umount merged
# https://github.com/containers/fuse-overlayfs/issues/444
rm -rf lower upper workdir merged
mkdir lower upper workdir merged
mkdir -p lower/base/test/test1
touch lower/base/test/test1/test1-file
fuse-overlayfs -o lowerdir=lower,upperdir=upper,workdir=workdir merged
mv merged/base/test/test1 merged/base/test/tmp
cp -r merged/base/test/tmp merged/base/test/test1
umount merged

View File

@ -29,3 +29,27 @@ else
fi
fusermount -u merged || [ $? -eq "${EXPECT_UMOUNT_STATUS:-0}" ]
# xattr_permissions=2
rm -rf lower upper workdir merged
mkdir lower upper workdir merged
touch upper/file
unshare -r setcap cap_net_admin+ep upper/file
fuse-overlayfs -o lowerdir=lower,upperdir=upper,workdir=workdir,xattr_permissions=2 merged
# Ensure the security xattr namespace is isolated.
test "$(unshare -r getcap merged/file)" = ''
unshare -r setcap cap_net_admin+ep merged/file
test "$(unshare -r getcap merged/file)" = 'merged/file cap_net_admin=ep'
# Ensure UID is preserved with chgrp.
podman unshare chgrp 1 merged/file
test $(podman unshare stat -c %u:%g merged/file) = 0:1
# Ensure UID and GID are preserved with chmod.
chmod 600 merged/file
test $(podman unshare stat -c %u:%g merged/file) = 0:1
fusermount -u merged || [ $? -eq "${EXPECT_UMOUNT_STATUS:-0}" ]

109
utils.c
View File

@ -33,8 +33,7 @@
#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
(__extension__ ({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))
@ -54,10 +53,7 @@
/* List of all valid flags for the open/openat flags argument: */
#define VALID_OPEN_FLAGS \
(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
O_APPEND | O_NDELAY | O_NONBLOCK | O_NDELAY | O_SYNC | O_DSYNC | \
FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
O_NOATIME | O_CLOEXEC | O_PATH | O_TMPFILE)
(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | O_APPEND | O_NDELAY | O_NONBLOCK | O_NDELAY | O_SYNC | O_DSYNC | FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | O_NOATIME | O_CLOEXEC | O_PATH | O_TMPFILE)
static int
syscall_openat2 (int dirfd, const char *path, uint64_t flags, uint64_t mode, uint64_t resolve)
@ -67,9 +63,7 @@ syscall_openat2 (int dirfd, const char *path, uint64_t flags, uint64_t mode, uin
uint64_t flags;
uint64_t mode;
uint64_t resolve;
}
how =
{
} how = {
.flags = flags & VALID_OPEN_FLAGS,
.mode = (flags & O_CREAT) ? (mode & 07777) : 0,
.resolve = resolve,
@ -105,7 +99,8 @@ int
file_exists_at (int dirfd, const char *pathname)
{
int ret = faccessat (dirfd, pathname, F_OK, AT_SYMLINK_NOFOLLOW | AT_EACCESS);
if (ret < 0 && errno == EINVAL) {
if (ret < 0 && errno == EINVAL)
{
struct stat buf;
return fstatat (dirfd, pathname, &buf, AT_SYMLINK_NOFOLLOW);
}
@ -229,16 +224,38 @@ open_fd_or_get_path (struct ovl_layer *l, const char *path, char *out, int *fd,
return *fd;
}
int
read_device (const char *s, dev_t *dev)
{
unsigned int major, minor;
int ret;
while (*s == '-')
s++;
ret = sscanf (s, "%u-%u", &major, &minor);
if (ret != 2)
{
errno = EINVAL;
return -1;
}
*dev = makedev (major, minor);
return 0;
}
int
override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *path, struct stat *st)
{
int ret;
uid_t uid;
gid_t gid;
mode_t mode;
mode_t mode = 0;
char buf[64];
cleanup_close int cleanup_fd = -1;
const char *xattr_name;
cleanup_free char *type = NULL;
switch (st->st_mode & S_IFMT)
{
@ -275,14 +292,10 @@ override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *pa
if (fd >= 0)
{
ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1);
if (ret < 0)
return ret;
}
else if (abs_path)
{
ret = lgetxattr (abs_path, xattr_name, buf, sizeof (buf) - 1);
if (ret < 0)
return ret;
}
else
{
@ -297,20 +310,53 @@ override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *pa
if (fd >= 0)
ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1);
else
{
ret = lgetxattr (full_path, xattr_name, buf, sizeof (buf) - 1);
if (ret < 0 && errno == ENODATA)
return 0;
}
if (ret < 0)
return ret;
}
return errno == ENODATA ? 0 : ret;
buf[ret] = '\0';
ret = sscanf (buf, "%d:%d:%o", &uid, &gid, &mode);
if (ret != 3)
ret = sscanf (buf, "%d:%d:%o:%ms", &uid, &gid, &mode, &type);
if (ret == 4)
{
if (has_prefix (type, "dir"))
mode |= S_IFDIR;
else if (has_prefix (type, "file"))
mode |= S_IFREG;
else if (has_prefix (type, "symlink"))
mode |= S_IFLNK;
else if (has_prefix (type, "pipe"))
mode |= S_IFIFO;
else if (has_prefix (type, "socket"))
mode |= S_IFSOCK;
else if (has_prefix (type, "block"))
{
mode |= S_IFBLK;
ret = read_device (type + strlen ("block"), &st->st_rdev);
if (ret < 0)
return ret;
}
else if (has_prefix (type, "char"))
{
mode |= S_IFCHR;
ret = read_device (type + strlen ("char"), &st->st_rdev);
if (ret < 0)
return ret;
}
else
{
errno = EINVAL;
return -1;
}
}
else if (ret == 3)
{
/* If a type is not specified, keep the original one. */
mode |= (st->st_mode & S_IFMT);
}
else
{
errno = EINVAL;
return -1;
@ -318,7 +364,24 @@ override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *pa
st->st_uid = uid;
st->st_gid = gid;
st->st_mode = (st->st_mode & S_IFMT) | mode;
st->st_mode = mode;
return 0;
}
bool
has_prefix (const char *str, const char *pref)
{
while (1)
{
if (*pref == '\0')
return true;
if (*str == '\0')
return false;
if (*pref != *str)
return false;
str++;
pref++;
}
return false;
}

View File

@ -62,4 +62,6 @@ int safe_openat (int dirfd, const char *pathname, int flags, mode_t mode);
int override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *path, struct stat *st);
bool has_prefix (const char *str, const char *pref);
#endif