Skip to content

Set ownership on symlinks created by build actions during their execution#211

Merged
EdSchouten merged 4 commits intobuildbarn:mainfrom
meroton:symlink-ownership
Mar 30, 2026
Merged

Set ownership on symlinks created by build actions during their execution#211
EdSchouten merged 4 commits intobuildbarn:mainfrom
meroton:symlink-ownership

Conversation

@moroten
Copy link
Copy Markdown
Contributor

@moroten moroten commented Feb 5, 2026

In commit 410ea5c, Set ownership on files created by build actions during their execution, files got correct ownership associated with them. This commit adds the same feature to symlinks.

All directories, files and symlinks created by an action are owned by the user. Files provided as input are readonly and are therefore owned by root so that actions don't try to make them writable to edit them. Because directories are always mutable and symlink targets can only be modified by replacing them, directories and symlinks provided as inputs can safely be owned by the user.

This commit changes the owner of user provided symlinks from root to the user. This solves the use case of hardlinking symlinks, which requires the user to be the owner of the symlink.

The following test was performed with Bazel:

--- rules.bzl ---
def _impl(ctx):
    file = ctx.actions.declare_file("file")
    symlink = ctx.actions.declare_symlink("symlink")
    args = ctx.actions.args()
    args.add_all([file, symlink])
    ctx.actions.run_shell(
        outputs = [file, symlink],
        command = """
exec 2>&1
set +ex
touch $1
ln -s file $2
ln $2 $2.hardlink
ls -la $(dirname $1)
""",
        arguments = [args],
    )
    return [
        DefaultInfo(files = depset([file, symlink])),
        OutputGroupInfo(
            file = depset([file]),
        ),
    ]

hardlink_a_symlink_rule = rule(
    implementation = _impl,
)

--- BUILD.bazel ---
load(":rules.bzl", "hardlink_a_symlink_rule")

hardlink_a_symlink_rule(name = "hardlink_a_symlink")

filegroup(
    name = "hardlink_a_symlink_file",
    srcs = [":hardlink_a_symlink"],
    output_group = "file",
)
genrule(
    name = "hardlink_input_symlink",
    srcs = [":hardlink_a_symlink", ":hardlink_a_symlink_file"],
    outs = ["symlink.another_hardlink"],
    cmd = """
exec 2>&1
set +ex
cd $$(dirname $(location :hardlink_a_symlink_file))
ln symlink symlink.another_hardlink
ls -la
""",
)
    
--- Output ---
$ bazel build :hardlink_input_symlink
INFO: From Action file:
total 0
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 .
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 ..
-rw-rw-rw-    1 fredrik fredrik 0 Feb  5 21:12 file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink -> file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink.hardlink -> file
INFO: From Executing genrule //:hardlink_input_symlink:
total 0
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 .
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 ..
-r-xr-xr-x 9999 root    root    0 Jan  1  2000 file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink -> file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink.another_hardlink -> file

References:
https://sourceforge.net/p/fuse/mailman/message/35004606/
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7
torvalds/linux@800179c

Copy link
Copy Markdown
Contributor Author

@moroten moroten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't files provided as inputs have the ownership set in the runner configuration? I looks like only files created by the action has the ownership of the user.

@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented Feb 5, 2026

Test

All tests were cache hits

19 tests (100.0%) were fully cached saving 3s.

Copy link
Copy Markdown
Member

@EdSchouten EdSchouten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

Comment thread cmd/bb_worker/main.go Outdated
Comment thread cmd/bb_worker/main.go Outdated
Comment thread pkg/filesystem/virtual/base_symlink_factory.go Outdated
@moroten
Copy link
Copy Markdown
Contributor Author

moroten commented Mar 26, 2026

I'm trying to get going on this again. Have I got it correct that:

  • Owned by root
    • Input file
  • Owned by user
    • Input directory
    • Input symlink
    • Action created file
    • Action created directory
    • Action created symlink

Is the rational that files are read only but directories and symlinks cannot be modified anyway, they are simply replaced?

@EdSchouten
Copy link
Copy Markdown
Member

Yes, exactly. Directories are always mutable, and symlink targets can only be modified by replacing them.

@moroten moroten force-pushed the symlink-ownership branch from e262049 to b04af61 Compare March 26, 2026 20:13
Copy link
Copy Markdown
Member

@EdSchouten EdSchouten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

Comment thread cmd/bb_worker/main.go Outdated
Comment thread pkg/builder/virtual_build_directory.go Outdated
Comment thread pkg/filesystem/virtual/error_symlink_factory.go Outdated
Comment thread pkg/filesystem/virtual/base_symlink_factory.go Outdated
Comment thread pkg/filesystem/virtual/handle_allocating_symlink_factory.go Outdated
moroten added 2 commits March 30, 2026 19:59
…tion

In commit 410ea5c
"Set ownership on files created by build actions during their execution"
files got correct ownership associated with them. This commit adds the
same feature to symlinks.

All directories, files and symlinks created by an action are owned by
the user. Files provided as input are readonly and are therefore owned
by root so that actions don't try to make them writable to edit them.
Because directories are always mutable and symlink targets
can only be modified by replacing them, directories and symlinks
provided as inputs can safely be owned by the user.

This commit changes the owner of user provided symlinks from root to the
user. This solves the use case of hardlinking symlinks, which requires
the user to be the owner of the symlink.

The following test was performed with Bazel:
```starlark
--- rules.bzl ---
def _impl(ctx):
    file = ctx.actions.declare_file("file")
    symlink = ctx.actions.declare_symlink("symlink")
    args = ctx.actions.args()
    args.add_all([file, symlink])
    ctx.actions.run_shell(
        outputs = [file, symlink],
        command = """
exec 2>&1
set +ex
touch $1
ln -s file $2
ln $2 $2.hardlink
ls -la $(dirname $1)
""",
        arguments = [args],
    )
    return [
        DefaultInfo(files = depset([file, symlink])),
        OutputGroupInfo(
            file = depset([file]),
        ),
    ]

hardlink_a_symlink_rule = rule(
    implementation = _impl,
)

--- BUILD.bazel ---
load(":rules.bzl", "hardlink_a_symlink_rule")

hardlink_a_symlink_rule(name = "hardlink_a_symlink")

filegroup(
    name = "hardlink_a_symlink_file",
    srcs = [":hardlink_a_symlink"],
    output_group = "file",
)
genrule(
    name = "hardlink_input_symlink",
    srcs = [":hardlink_a_symlink", ":hardlink_a_symlink_file"],
    outs = ["symlink.another_hardlink"],
    cmd = """
exec 2>&1
set +ex
cd $$(dirname $(location :hardlink_a_symlink_file))
ln symlink symlink.another_hardlink
ls -la
""",
)

--- Output ---
$ bazel build :hardlink_a_symlink
INFO: From Action file:
total 0
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 .
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 ..
-rw-rw-rw-    1 fredrik fredrik 0 Feb  5 21:12 file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink -> file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink.hardlink -> file
INFO: From Executing genrule //:hardlink_input_symlink:
total 0
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 .
drwxrwxrwx    1 fredrik fredrik 0 Feb  5 21:12 ..
-r-xr-xr-x 9999 root    root    0 Jan  1  2000 file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink -> file
lrwxrwxrwx 9999 fredrik fredrik 4 Jan  1  2000 symlink.another_hardlink -> file
```

References:
https://sourceforge.net/p/fuse/mailman/message/35004606/
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7
torvalds/linux@800179c
@moroten moroten force-pushed the symlink-ownership branch from b04af61 to ca080dc Compare March 30, 2026 18:34
Copy link
Copy Markdown
Member

@EdSchouten EdSchouten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to look great, Fredrik. Only one tiny request for change, and this should be good to land.

Comment thread pkg/builder/virtual_build_directory.go Outdated
Copy link
Copy Markdown
Member

@EdSchouten EdSchouten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Will merge once CI is happy.

Comment thread pkg/filesystem/virtual/winfsp/file_system_integration_test.go
@EdSchouten EdSchouten merged commit 4c74907 into buildbarn:main Mar 30, 2026
3 checks passed
@moroten moroten deleted the symlink-ownership branch March 30, 2026 20:52
Comment thread cmd/bb_worker/main.go
// Symlinks are not modified but rather replaced when changed.
// Therefore, it is safe to set the user as owner.
virtual.NewBaseSymlinkFactory(defaultAttributesSetter),
handleAllocator.New(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moroten @EdSchouten This is nil in the case of Native.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed in 31166ae.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants