Skip to content

Commit 13e68cd

Browse files
committed
Set ownership on symlinks created by build actions during their execution
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. One use case is to allow hardlinking symlinks, which requires the owner of the symlink to be correct. 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_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
1 parent 79a33f1 commit 13e68cd

File tree

6 files changed

+61
-21
lines changed

6 files changed

+61
-21
lines changed

cmd/bb_worker/main.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,16 @@ func main() {
219219
normalizer = virtual.CaseInsensitiveComponentNormalizer
220220
}
221221

222+
defaultAttributesSetter := func(requested virtual.AttributesMask, attributes *virtual.Attributes) {
223+
// No need to set ownership attributes
224+
// on the top-level directory.
225+
}
222226
symlinkFactory = virtual.NewHandleAllocatingSymlinkFactory(
223-
virtual.BaseSymlinkFactory,
227+
virtual.NewBaseSymlinkFactory(defaultAttributesSetter),
224228
handleAllocator.New())
225229
characterDeviceFactory = virtual.NewHandleAllocatingCharacterDeviceFactory(
226230
virtual.BaseCharacterDeviceFactory,
227231
handleAllocator.New())
228-
defaultAttributesSetter := func(requested virtual.AttributesMask, attributes *virtual.Attributes) {
229-
// No need to set ownership attributes
230-
// on the top-level directory.
231-
}
232232
virtualBuildDirectory = virtual.NewInMemoryPrepopulatedDirectory(
233233
virtual.NewHandleAllocatingFileAllocator(
234234
virtual.NewPoolBackedFileAllocator(
@@ -343,6 +343,13 @@ func main() {
343343
}
344344
runnerClient := runner_pb.NewRunnerClient(runnerConnection)
345345

346+
defaultAttributesSetter := func(requested virtual.AttributesMask, attributes *virtual.Attributes) {
347+
attributes.SetOwnerUserID(runnerConfiguration.BuildDirectoryOwnerUserId)
348+
attributes.SetOwnerGroupID(runnerConfiguration.BuildDirectoryOwnerGroupId)
349+
}
350+
symlinkFactory := virtual.NewHandleAllocatingSymlinkFactory(
351+
virtual.NewBaseSymlinkFactory(defaultAttributesSetter),
352+
handleAllocator.New())
346353
for threadID := uint64(0); threadID < runnerConfiguration.Concurrency; threadID++ {
347354
// Per-worker separate writer of the Content
348355
// Addressable Storage that batches writes after
@@ -391,10 +398,7 @@ func main() {
391398
symlinkFactory,
392399
characterDeviceFactory,
393400
handleAllocator,
394-
/* defaultAttributesSetter = */ func(requested virtual.AttributesMask, attributes *virtual.Attributes) {
395-
attributes.SetOwnerUserID(runnerConfiguration.BuildDirectoryOwnerUserId)
396-
attributes.SetOwnerGroupID(runnerConfiguration.BuildDirectoryOwnerGroupId)
397-
},
401+
defaultAttributesSetter,
398402
clock.SystemClock,
399403
)
400404
} else {

pkg/builder/virtual_build_directory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func (d *virtualBuildDirectory) InstallHooks(filePool pool.FilePool, errorLogger
110110
),
111111
do.handleAllocator,
112112
),
113+
do.symlinkFactory,
113114
errorLogger,
114115
do.defaultAttributesSetter,
115116
namedAttributesFactory,

pkg/filesystem/virtual/base_symlink_factory.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,30 @@ import (
1313
"google.golang.org/grpc/status"
1414
)
1515

16-
type symlinkFactory struct{}
16+
type symlinkFactory struct {
17+
defaultAttributesSetter DefaultAttributesSetter
18+
}
1719

18-
func (symlinkFactory) LookupSymlink(target []byte) LinkableLeaf {
19-
return symlink{target: target}
20+
func (f *symlinkFactory) LookupSymlink(target []byte) LinkableLeaf {
21+
return symlink{
22+
defaultAttributesSetter: f.defaultAttributesSetter,
23+
target: target,
24+
}
2025
}
2126

22-
// BaseSymlinkFactory can be used to create simple immutable symlink nodes.
23-
var BaseSymlinkFactory SymlinkFactory = symlinkFactory{}
27+
// NewBaseSymlinkFactory creates a SymlinkFactory that can be used to create
28+
// simple immutable symlink nodes.
29+
func NewBaseSymlinkFactory(defaultAttributesSetter DefaultAttributesSetter) SymlinkFactory {
30+
return &symlinkFactory{
31+
defaultAttributesSetter: defaultAttributesSetter,
32+
}
33+
}
2434

2535
type symlink struct {
2636
placeholderFile
2737

28-
target []byte
38+
defaultAttributesSetter DefaultAttributesSetter
39+
target []byte
2940
}
3041

3142
func (f symlink) readlinkParser() (path.Parser, error) {
@@ -48,6 +59,7 @@ func (f symlink) readlinkString() (string, error) {
4859
}
4960

5061
func (f symlink) VirtualGetAttributes(ctx context.Context, requested AttributesMask, attributes *Attributes) {
62+
f.defaultAttributesSetter(requested, attributes)
5163
attributes.SetChangeID(0)
5264
attributes.SetFileType(filesystem.FileTypeSymlink)
5365
attributes.SetHasNamedAttributes(false)

pkg/filesystem/virtual/in_memory_prepopulated_directory.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ type StringMatcher func(s string) bool
2525
// inMemoryFilesystem contains state that is shared across all
2626
// inMemoryPrepopulatedDirectory objects that form a single hierarchy.
2727
type inMemoryFilesystem struct {
28-
symlinkFactory SymlinkFactory
2928
statefulHandleAllocator StatefulHandleAllocator
3029
initialContentsSorter Sorter
3130
hiddenFilesMatcher StringMatcher
@@ -44,6 +43,7 @@ type inMemoryFilesystem struct {
4443
type inMemorySubtree struct {
4544
filesystem *inMemoryFilesystem
4645
fileAllocator FileAllocator
46+
symlinkFactory SymlinkFactory
4747
errorLogger util.ErrorLogger
4848
defaultAttributesSetter DefaultAttributesSetter
4949
namedAttributesFactory NamedAttributesFactory
@@ -52,14 +52,14 @@ type inMemorySubtree struct {
5252
func newInMemorySubtree(fileAllocator FileAllocator, symlinkFactory SymlinkFactory, errorLogger util.ErrorLogger, handleAllocator StatefulHandleAllocator, initialContentsSorter Sorter, hiddenFilesMatcher StringMatcher, clock clock.Clock, normalizer ComponentNormalizer, defaultAttributesSetter DefaultAttributesSetter, namedAttributesFactory NamedAttributesFactory) *inMemorySubtree {
5353
return &inMemorySubtree{
5454
filesystem: &inMemoryFilesystem{
55-
symlinkFactory: symlinkFactory,
5655
statefulHandleAllocator: handleAllocator,
5756
initialContentsSorter: initialContentsSorter,
5857
hiddenFilesMatcher: hiddenFilesMatcher,
5958
normalizer: normalizer,
6059
clock: clock,
6160
},
6261
fileAllocator: fileAllocator,
62+
symlinkFactory: symlinkFactory,
6363
errorLogger: errorLogger,
6464
defaultAttributesSetter: defaultAttributesSetter,
6565
namedAttributesFactory: namedAttributesFactory,
@@ -529,13 +529,14 @@ func (i *inMemoryPrepopulatedDirectory) postRemoveChildren(entries *inMemoryDire
529529
}
530530
}
531531

532-
func (i *inMemoryPrepopulatedDirectory) InstallHooks(fileAllocator FileAllocator, errorLogger util.ErrorLogger, defaultAttributesSetter DefaultAttributesSetter, namedAttributesFactory NamedAttributesFactory) {
532+
func (i *inMemoryPrepopulatedDirectory) InstallHooks(fileAllocator FileAllocator, symlinkFactory SymlinkFactory, errorLogger util.ErrorLogger, defaultAttributesSetter DefaultAttributesSetter, namedAttributesFactory NamedAttributesFactory) {
533533
i.lock.Lock()
534534
defer i.lock.Unlock()
535535

536536
i.subtree = &inMemorySubtree{
537537
filesystem: i.subtree.filesystem,
538538
fileAllocator: fileAllocator,
539+
symlinkFactory: symlinkFactory,
539540
errorLogger: errorLogger,
540541
defaultAttributesSetter: defaultAttributesSetter,
541542
namedAttributesFactory: namedAttributesFactory,
@@ -1121,7 +1122,7 @@ func (i *inMemoryPrepopulatedDirectory) VirtualSymlink(ctx context.Context, poin
11211122
if s := contents.virtualMayAttach(normalizedLinkName); s != StatusOK {
11221123
return nil, ChangeInfo{}, s
11231124
}
1124-
child := i.subtree.filesystem.symlinkFactory.LookupSymlink(pointedTo)
1125+
child := i.subtree.symlinkFactory.LookupSymlink(pointedTo)
11251126
changeIDBefore := contents.changeID
11261127
contents.attach(i.subtree, linkName, normalizedLinkName, inMemoryDirectoryChild{}.FromLeaf(child))
11271128

pkg/filesystem/virtual/in_memory_prepopulated_directory_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,10 @@ func TestInMemoryPrepopulatedDirectoryInstallHooks(t *testing.T) {
404404
defaultAttributesSetter1 := mock.NewMockDefaultAttributesSetter(ctrl)
405405
d := virtual.NewInMemoryPrepopulatedDirectory(fileAllocator1, symlinkFactory1, errorLogger1, handleAllocator, sort.Sort, hiddenFilesPatternForTesting.MatchString, clock.SystemClock, virtual.CaseSensitiveComponentNormalizer, defaultAttributesSetter1.Call, virtual.NoNamedAttributesFactory)
406406
fileAllocator2 := mock.NewMockFileAllocator(ctrl)
407+
symlinkFactory2 := mock.NewMockSymlinkFactory(ctrl)
407408
errorLogger2 := mock.NewMockErrorLogger(ctrl)
408409
defaultAttributesSetter2 := mock.NewMockDefaultAttributesSetter(ctrl)
409-
d.InstallHooks(fileAllocator2, errorLogger2, defaultAttributesSetter2.Call, virtual.NoNamedAttributesFactory)
410+
d.InstallHooks(fileAllocator2, symlinkFactory2, errorLogger2, defaultAttributesSetter2.Call, virtual.NoNamedAttributesFactory)
410411

411412
// Validate that the top-level directory uses both the new file
412413
// allocator and error logger.
@@ -424,6 +425,27 @@ func TestInMemoryPrepopulatedDirectoryInstallHooks(t *testing.T) {
424425
&attr)
425426
require.Equal(t, virtual.StatusErrIO, s)
426427

428+
// Validate that symlinks uses the new symlink allocator
429+
// and error logger as well.
430+
symlinkLeaf := mock.NewMockLinkableLeaf(ctrl)
431+
symlinkFactory2.EXPECT().LookupSymlink([]byte("target")).Return(symlinkLeaf)
432+
symlinkLeaf.EXPECT().VirtualGetAttributes(
433+
ctx,
434+
virtual.AttributesMaskInodeNumber,
435+
gomock.Any(),
436+
).Do(func(ctx context.Context, requested virtual.AttributesMask, attributes *virtual.Attributes) {
437+
attributes.SetInodeNumber(3)
438+
})
439+
var out virtual.Attributes
440+
actualLeaf, changeInfo, s := d.VirtualSymlink(ctx, []byte("target"), path.MustNewComponent("symlink"), virtual.AttributesMaskInodeNumber, &out)
441+
require.Equal(t, virtual.StatusOK, s)
442+
require.NotNil(t, actualLeaf)
443+
require.Equal(t, virtual.ChangeInfo{
444+
Before: 0,
445+
After: 1,
446+
}, changeInfo)
447+
require.Equal(t, (&virtual.Attributes{}).SetInodeNumber(3), &out)
448+
427449
// Validate that a subdirectory uses the new file allocator
428450
// and error logger as well.
429451
inMemoryPrepopulatedDirectoryExpectMkdir(ctrl, handleAllocator)

pkg/filesystem/virtual/prepopulated_directory.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ type PrepopulatedDirectory interface {
9999
// This function is identical to BuildDirectory.InstallHooks(),
100100
// except that it uses the FUSE specific FileAllocator instead
101101
// of FilePool.
102-
InstallHooks(fileAllocator FileAllocator, errorLogger util.ErrorLogger, defaultAttributesSetter DefaultAttributesSetter, namedAttributesFactory NamedAttributesFactory)
102+
InstallHooks(fileAllocator FileAllocator, symlinkFactory SymlinkFactory, errorLogger util.ErrorLogger, defaultAttributesSetter DefaultAttributesSetter, namedAttributesFactory NamedAttributesFactory)
103103
// FilterChildren() can be used to traverse over all of the
104104
// InitialContentsFetcher and LinkableLeaf objects stored in this
105105
// directory hierarchy. For each of the objects, a callback is

0 commit comments

Comments
 (0)