Skip to content

Commit c9b96ae

Browse files
authored
Fix storage OS bucket not returning ErrNotExist correctly (#3188)
This PR fixes a bug of the storage bucket where it does not return an `fs.ErrNotExist` error when it should. It happens when both conditions met: * The bucket is created with an absolute path, i.e. `provider.NewReadWriteBucket("/absPath")` * `bucket.Get(ctx, "existing.file/foo")` is called, where `existing.file` exists as a regular file. Fixes #3185
1 parent 3ef45f8 commit c9b96ae

File tree

3 files changed

+73
-17
lines changed

3 files changed

+73
-17
lines changed

private/buf/bufworkspace/workspace_targeting.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -625,14 +625,6 @@ func checkForControllingWorkspaceOrV1Module(
625625
ignoreWorkspaceCheck bool,
626626
) (buftarget.ControllingWorkspace, error) {
627627
path = normalpath.Normalize(path)
628-
// We attempt to check that the provided target path is not a file by checking the extension.
629-
// Any valid proto file provided as a target would have the .proto extension, so we treat
630-
// any path given without as a directory.
631-
// This could be a file without an extension, in which case an error would be returned
632-
// to the user when we attempt to check for a controlling workspace.
633-
if normalpath.Ext(path) != "" {
634-
path = normalpath.Dir(path)
635-
}
636628
// Keep track of any v1 module found along the way. If we find a v1 or v2 workspace, we
637629
// return that over the v1 module, but we return this as the fallback.
638630
var fallbackV1Module buftarget.ControllingWorkspace

private/pkg/storage/storageos/bucket.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"io/fs"
2121
"os"
2222
"path/filepath"
23-
"strings"
2423

2524
"github.com/bufbuild/buf/private/pkg/filepathext"
2625
"github.com/bufbuild/buf/private/pkg/normalpath"
@@ -284,14 +283,10 @@ func (b *bucket) validateExternalPath(path string, externalPath string) error {
284283
// It's important that we detect these cases so that
285284
// multi buckets don't unnecessarily fail when one of
286285
// its delegates actually defines the path.
287-
elements := strings.Split(normalpath.Normalize(externalPath), "/")
288-
if len(elements) == 1 {
289-
// The path is a single element, so there aren't
290-
// any other files to check.
291-
return err
292-
}
293-
for i := len(elements) - 1; i >= 0; i-- {
294-
parentFileInfo, err := os.Stat(filepath.Join(elements[:i]...))
286+
lastParentPath := externalPath
287+
parentPath := filepath.Dir(externalPath)
288+
for ; parentPath != lastParentPath; lastParentPath, parentPath = parentPath, filepath.Dir(parentPath) {
289+
parentFileInfo, err := os.Stat(parentPath)
295290
if err != nil {
296291
continue
297292
}

private/pkg/storage/storageos/storageos_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
package storageos_test
1616

1717
import (
18+
"context"
19+
"io/fs"
20+
"os"
1821
"path/filepath"
1922
"testing"
2023

@@ -37,6 +40,72 @@ func TestOS(t *testing.T) {
3740
testWriteBucketToReadBucket,
3841
true,
3942
)
43+
44+
t.Run("get_non_existent_file", func(t *testing.T) {
45+
t.Parallel()
46+
ctx := context.Background()
47+
// Create a bucket at an absolute path.
48+
tempDir := t.TempDir()
49+
tempDir, err := filepath.Abs(tempDir)
50+
require.NoError(t, err)
51+
bucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir)
52+
require.NoError(t, err)
53+
54+
// Write a file to it.
55+
writeObjectCloser, err := bucket.Put(ctx, "foo.txt")
56+
require.NoError(t, err)
57+
written, err := writeObjectCloser.Write([]byte(nil))
58+
require.NoError(t, err)
59+
require.Zero(t, written)
60+
require.NoError(t, writeObjectCloser.Close())
61+
62+
// Try reading a file as if foo.txt is a directory.
63+
_, err = bucket.Get(ctx, "foo.txt/bar.txt")
64+
require.ErrorIs(t, err, fs.ErrNotExist)
65+
_, err = bucket.Get(ctx, "foo.txt/bar.txt/baz.txt")
66+
require.ErrorIs(t, err, fs.ErrNotExist)
67+
68+
// Read a file that does not exist at all.
69+
_, err = bucket.Get(ctx, "baz.txt")
70+
require.ErrorIs(t, err, fs.ErrNotExist)
71+
})
72+
73+
t.Run("get_non_existent_file_symlink", func(t *testing.T) {
74+
t.Parallel()
75+
ctx := context.Background()
76+
// Create a bucket at an absolute path.
77+
actualTempDir := t.TempDir()
78+
actualTempDir, err := filepath.Abs(actualTempDir)
79+
require.NoError(t, err)
80+
_, err = os.Create(filepath.Join(actualTempDir, "foo.txt"))
81+
require.NoError(t, err)
82+
tempDir := t.TempDir()
83+
tempDir, err = filepath.Abs(tempDir)
84+
require.NoError(t, err)
85+
tempDir = filepath.Join(tempDir, "sym")
86+
require.NoError(t, os.Symlink(actualTempDir, tempDir))
87+
t.Cleanup(func() {
88+
if err := os.Remove(tempDir); err != nil {
89+
t.Error(err)
90+
}
91+
})
92+
provider := storageos.NewProvider(storageos.ProviderWithSymlinks())
93+
bucket, err := provider.NewReadWriteBucket(tempDir, storageos.ReadWriteBucketWithSymlinksIfSupported())
94+
require.NoError(t, err)
95+
96+
_, err = bucket.Get(ctx, "foo.txt")
97+
require.NoError(t, err)
98+
99+
// Try reading a file as if foo.txt is a directory.
100+
_, err = bucket.Get(ctx, "foo.txt/bar.txt")
101+
require.ErrorIs(t, err, fs.ErrNotExist)
102+
_, err = bucket.Get(ctx, "foo.txt/bar.txt/baz.txt")
103+
require.ErrorIs(t, err, fs.ErrNotExist)
104+
105+
// Read a file that does not exist at all.
106+
_, err = bucket.Get(ctx, "baz.txt")
107+
require.ErrorIs(t, err, fs.ErrNotExist)
108+
})
40109
}
41110

42111
func testNewReadBucket(t *testing.T, dirPath string, storageosProvider storageos.Provider) (storage.ReadBucket, storagetesting.GetExternalPathFunc) {

0 commit comments

Comments
 (0)