Skip to content

Commit 8daf34b

Browse files
committed
Let symlinks be backed by path.Parser
Right now they are backed by byte slices. This leads to some incorrect behavior on Windows. CASInitialContentsFetcher will create symlinks containing forward slashes, which we then hand over to WinFSP without any translation in between. This change modifies symlinks to be backed by a path.Parser. This means that at any point where we need to convert a symlink to a string representation, we need to perform an explicit conversion to the expected path format. What is a bit annoying is that the st_size of symlinks on UNIX needs to match the amount of data returned by readlink(). This means that symlink objects can no longer be aware of their own size. We therefore remove the SizeBytes attribute from these files. The FUSE and NFSv4 backends will now consider both AttributesMaskSizeBytes and AttributesMaskSymlinkTarget to determine the file's real size. Fixes: #213
1 parent 9b1e363 commit 8daf34b

30 files changed

Lines changed: 391 additions & 235 deletions

cmd/bb_virtual_tmp/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func main() {
5151
if err := path.Resolve(path.UNIXFormat.NewParser(configuration.BuildDirectoryPath), scopeWalker); err != nil {
5252
return util.StatusWrap(err, "Failed to resolve build directory path")
5353
}
54-
userSettableSymlink := virtual.NewUserSettableSymlink(buildDirectory)
54+
userSettableSymlink := virtual.NewUserSettableSymlink(buildDirectory, path.UNIXFormat.NewParser("/invalid"))
5555

5656
// Expose the symbolic link through a virtual file system.
5757
mount, handleAllocator, err := virtual_configuration.NewMountFromConfiguration(

cmd/bb_worker/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ func main() {
221221

222222
symlinkFactory = virtual.NewHandleAllocatingSymlinkFactory(
223223
virtual.BaseSymlinkFactory,
224-
handleAllocator.New())
224+
handleAllocator.New(),
225+
path.LocalFormat,
226+
)
225227
characterDeviceFactory = virtual.NewHandleAllocatingCharacterDeviceFactory(
226228
virtual.BaseCharacterDeviceFactory,
227229
handleAllocator.New())

pkg/builder/virtual_build_directory.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,17 @@ func (d *virtualBuildDirectory) Readlink(name path.Component) (path.Parser, erro
194194
if err != nil {
195195
return nil, err
196196
}
197-
if _, leaf := child.GetPair(); leaf != nil {
198-
p := virtual.ApplyReadlink{}
199-
if !child.GetNode().VirtualApply(&p) {
200-
panic("build directory contains leaves that don't handle ApplyReadlink")
201-
}
202-
return p.Target, p.Err
197+
198+
_, leaf := child.GetPair()
199+
if leaf == nil {
200+
return nil, syscall.EISDIR
201+
}
202+
203+
var attributes virtual.Attributes
204+
leaf.VirtualGetAttributes(context.TODO(), virtual.AttributesMaskSymlinkTarget, &attributes)
205+
symlinkTarget, ok := attributes.GetSymlinkTarget()
206+
if !ok {
207+
return nil, syscall.EINVAL
203208
}
204-
return nil, syscall.EISDIR
209+
return symlinkTarget, nil
205210
}

pkg/filesystem/virtual/attributes.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"time"
55

66
"github.com/buildbarn/bb-storage/pkg/filesystem"
7+
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
78
)
89

910
// AttributesMask is a bitmask of status attributes that need to be
@@ -51,7 +52,12 @@ const (
5152
// bits of set_mode).
5253
AttributesMaskPermissions
5354
// AttributesMaskSizeBytes requests the file size (st_size).
55+
// When requested, this field should be set for all types of
56+
// files, except for symbolic links.
5457
AttributesMaskSizeBytes
58+
// AttributesMaskSymlinkTarget requests the target of a symbolic
59+
// link.
60+
AttributesMaskSymlinkTarget
5561
)
5662

5763
// Attributes of a file, normally requested through stat() or readdir().
@@ -72,6 +78,7 @@ type Attributes struct {
7278
ownerUserID uint32
7379
permissions Permissions
7480
sizeBytes uint64
81+
symlinkTarget path.Parser
7582
}
7683

7784
// GetChangeID returns the change ID, which clients can use to determine
@@ -264,3 +271,15 @@ func (a *Attributes) SetSizeBytes(sizeBytes uint64) *Attributes {
264271
a.fieldsPresent |= AttributesMaskSizeBytes
265272
return a
266273
}
274+
275+
// GetSymlinkTarget returns the target of a symbolic link.
276+
func (a *Attributes) GetSymlinkTarget() (path.Parser, bool) {
277+
return a.symlinkTarget, a.fieldsPresent&AttributesMaskSymlinkTarget != 0
278+
}
279+
280+
// SetSymlinkTarget sets the target of a symbolic link.
281+
func (a *Attributes) SetSymlinkTarget(symlinkTarget path.Parser) *Attributes {
282+
a.symlinkTarget = symlinkTarget
283+
a.fieldsPresent |= AttributesMaskSymlinkTarget
284+
return a
285+
}

pkg/filesystem/virtual/base_symlink_factory.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,17 @@ package virtual
22

33
import (
44
"context"
5-
"unicode/utf8"
65

76
remoteexecution "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
87
"github.com/buildbarn/bb-remote-execution/pkg/proto/bazeloutputservice"
98
"github.com/buildbarn/bb-storage/pkg/filesystem"
109
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
11-
12-
"google.golang.org/grpc/codes"
13-
"google.golang.org/grpc/status"
1410
)
1511

1612
type symlinkFactory struct{}
1713

18-
func (symlinkFactory) LookupSymlink(target []byte) LinkableLeaf {
19-
return symlink{target: target}
14+
func (symlinkFactory) LookupSymlink(target path.Parser) (LinkableLeaf, error) {
15+
return symlink{target: target}, nil
2016
}
2117

2218
// BaseSymlinkFactory can be used to create simple immutable symlink nodes.
@@ -25,23 +21,12 @@ var BaseSymlinkFactory SymlinkFactory = symlinkFactory{}
2521
type symlink struct {
2622
placeholderFile
2723

28-
target []byte
29-
}
30-
31-
func (f symlink) readlinkParser() (path.Parser, error) {
32-
if !utf8.Valid(f.target) {
33-
return nil, status.Error(codes.InvalidArgument, "Symbolic link contents are not valid UTF-8")
34-
}
35-
return path.UNIXFormat.NewParser(string(f.target)), nil
24+
target path.Parser
3625
}
3726

3827
func (f symlink) readlinkString() (string, error) {
39-
targetParser, err := f.readlinkParser()
40-
if err != nil {
41-
return "", err
42-
}
4328
targetPath, scopeWalker := path.EmptyBuilder.Join(path.VoidScopeWalker)
44-
if err := path.Resolve(targetParser, scopeWalker); err != nil {
29+
if err := path.Resolve(f.target, scopeWalker); err != nil {
4530
return "", err
4631
}
4732
return targetPath.GetUNIXString(), nil
@@ -52,11 +37,7 @@ func (f symlink) VirtualGetAttributes(ctx context.Context, requested AttributesM
5237
attributes.SetFileType(filesystem.FileTypeSymlink)
5338
attributes.SetHasNamedAttributes(false)
5439
attributes.SetPermissions(PermissionsRead | PermissionsWrite | PermissionsExecute)
55-
attributes.SetSizeBytes(uint64(len(f.target)))
56-
}
57-
58-
func (f symlink) VirtualReadlink(ctx context.Context) ([]byte, Status) {
59-
return f.target, StatusOK
40+
attributes.SetSymlinkTarget(f.target)
6041
}
6142

6243
func (f symlink) VirtualSetAttributes(ctx context.Context, in *Attributes, requested AttributesMask, out *Attributes) Status {
@@ -69,8 +50,6 @@ func (f symlink) VirtualSetAttributes(ctx context.Context, in *Attributes, reque
6950

7051
func (f symlink) VirtualApply(data any) bool {
7152
switch p := data.(type) {
72-
case *ApplyReadlink:
73-
p.Target, p.Err = f.readlinkParser()
7453
case *ApplyGetBazelOutputServiceStat:
7554
if target, err := f.readlinkString(); err == nil {
7655
p.Stat = &bazeloutputservice.BatchStatResponse_Stat{

pkg/filesystem/virtual/blob_access_cas_file_factory.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package virtual
22

33
import (
44
"context"
5-
"syscall"
65

76
remoteexecution "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
87
"github.com/buildbarn/bb-remote-execution/pkg/proto/bazeloutputservice"
@@ -73,8 +72,6 @@ func (blobAccessCASFile) VirtualAllocate(off, size uint64) Status {
7372

7473
func (f *blobAccessCASFile) virtualApplyCommon(data any) bool {
7574
switch p := data.(type) {
76-
case *ApplyReadlink:
77-
p.Err = syscall.EINVAL
7875
case *ApplyUploadFile:
7976
// This file is already backed by the Content Addressable
8077
// Storage. There is thus no need to upload it once again.
@@ -147,10 +144,6 @@ func (f *blobAccessCASFile) VirtualRead(buf []byte, off uint64) (int, bool, Stat
147144
return len(buf), eof, StatusOK
148145
}
149146

150-
func (blobAccessCASFile) VirtualReadlink(ctx context.Context) ([]byte, Status) {
151-
return nil, StatusErrInval
152-
}
153-
154147
func (blobAccessCASFile) VirtualClose(shareAccess ShareMask) {}
155148

156149
func (blobAccessCASFile) virtualSetAttributesCommon(in *Attributes) Status {

pkg/filesystem/virtual/cas_initial_contents_fetcher.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ func (icf *casInitialContentsFetcher) fetchContentsUnwrapped(fileReadMonitorFact
110110
return nil, status.Errorf(codes.InvalidArgument, "Directory contains multiple children named %#v", entry.Name)
111111
}
112112

113-
leaf := icf.options.symlinkFactory.LookupSymlink([]byte(entry.Target))
113+
leaf, err := icf.options.symlinkFactory.LookupSymlink(path.UNIXFormat.NewParser(entry.Target))
114+
if err != nil {
115+
return nil, status.Errorf(codes.InvalidArgument, "Failed to look up symlink named %#v", entry.Name)
116+
}
114117
children[component] = InitialChild{}.FromLeaf(leaf)
115118
leavesToUnlink = append(leavesToUnlink, leaf)
116119
}

pkg/filesystem/virtual/cas_initial_contents_fetcher_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,13 @@ func TestCASInitialContentsFetcherFetchContents(t *testing.T) {
212212
gomock.Any(),
213213
).Return(fileLeaf)
214214
symlinkLeaf := mock.NewMockLinkableLeaf(ctrl)
215-
symlinkFactory.EXPECT().LookupSymlink([]byte("target")).Return(symlinkLeaf)
215+
symlinkFactory.EXPECT().LookupSymlink(gomock.Any()).
216+
DoAndReturn(func(targetParser path.Parser) (virtual.LinkableLeaf, error) {
217+
targetBuilder, scopeWalker := path.EmptyBuilder.Join(path.VoidScopeWalker)
218+
require.NoError(t, path.Resolve(targetParser, scopeWalker))
219+
require.Equal(t, "target", targetBuilder.GetUNIXString())
220+
return symlinkLeaf, nil
221+
})
216222

217223
children, err := initialContentsFetcher.FetchContents(fileReadMonitorFactory.Call)
218224
require.NoError(t, err)

pkg/filesystem/virtual/configuration/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ go_library(
2222
"//pkg/proto/configuration/filesystem/virtual",
2323
"@com_github_buildbarn_bb_storage//pkg/clock",
2424
"@com_github_buildbarn_bb_storage//pkg/eviction",
25+
"@com_github_buildbarn_bb_storage//pkg/filesystem/path",
2526
"@com_github_buildbarn_bb_storage//pkg/jmespath",
2627
"@com_github_buildbarn_bb_storage//pkg/program",
2728
"@com_github_buildbarn_bb_storage//pkg/random",

pkg/filesystem/virtual/configuration/configuration.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
pb "github.com/buildbarn/bb-remote-execution/pkg/proto/configuration/filesystem/virtual"
77
"github.com/buildbarn/bb-storage/pkg/clock"
88
"github.com/buildbarn/bb-storage/pkg/eviction"
9+
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
910
"github.com/buildbarn/bb-storage/pkg/jmespath"
1011
"github.com/buildbarn/bb-storage/pkg/program"
1112
"github.com/buildbarn/bb-storage/pkg/random"
@@ -111,6 +112,7 @@ func (m *nfsv4Mount) Expose(terminationGroup program.Group, rootDirectory virtua
111112
clock.SystemClock,
112113
enforcedLeaseTime.AsDuration(),
113114
announcedLeaseTime.AsDuration(),
115+
path.LocalFormat,
114116
),
115117
nfsv4.NewNFS40Program(
116118
rootDirectory,
@@ -121,6 +123,7 @@ func (m *nfsv4Mount) Expose(terminationGroup program.Group, rootDirectory virtua
121123
clock.SystemClock,
122124
enforcedLeaseTime.AsDuration(),
123125
announcedLeaseTime.AsDuration(),
126+
path.LocalFormat,
124127
),
125128
})
126129

0 commit comments

Comments
 (0)