tailscale: update tailfs file and package names (#11590)

This change updates the tailfs file and package names to their new
naming convention.

Updates #tailscale/corp#16827

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
This commit is contained in:
Charlotte Brandhorst-Satzkorn
2024-04-02 13:32:30 -07:00
committed by GitHub
parent 1c259100b0
commit 14683371ee
58 changed files with 180 additions and 180 deletions

View File

@@ -0,0 +1,101 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package dirfs provides a webdav.FileSystem that looks like a read-only
// directory containing only subdirectories.
package dirfs
import (
"slices"
"strings"
"time"
"tailscale.com/drive/driveimpl/shared"
"tailscale.com/tstime"
)
// Child is subdirectory of an FS.
type Child struct {
// Name is the name of the child
Name string
// Available is a function indicating whether or not the child is currently
// available. Unavailable children are excluded from the FS's directory
// listing. Available must be safe for concurrent use.
Available func() bool
}
func (c *Child) isAvailable() bool {
if c.Available == nil {
return true
}
return c.Available()
}
// FS is a read-only webdav.FileSystem that is composed of multiple child
// folders.
//
// When listing the contents of this FileSystem's root directory, children will
// be ordered in the order they're given to the FS.
//
// Children in an FS cannot be added, removed or renamed via operations on the
// webdav.FileSystem interface like filesystem.Mkdir or filesystem.OpenFile.
//
// Any attempts to perform operations on paths inside of children will result
// in a panic, as these are not expected to be performed on this FS.
//
// An FS an optionally have a StaticRoot, which will insert a folder with that
// StaticRoot into the tree, like this:
//
// -- <StaticRoot>
// ----- <Child>
// ----- <Child>
type FS struct {
// Children configures the full set of children of this FS.
Children []*Child
// Clock, if given, will cause this FS to use Clock.now() as the current
// time.
Clock tstime.Clock
// StaticRoot, if given, will insert the given name as a static root into
// every path.
StaticRoot string
}
func (dfs *FS) findChild(name string) (int, *Child) {
var child *Child
i, found := slices.BinarySearchFunc(dfs.Children, name, func(child *Child, name string) int {
return strings.Compare(child.Name, name)
})
if found {
child = dfs.Children[i]
}
return i, child
}
// childFor returns the child for the given filename. If the filename refers to
// a path inside of a child, this will panic.
func (dfs *FS) childFor(name string) *Child {
pathComponents := shared.CleanAndSplit(name)
if len(pathComponents) != 1 {
panic("dirfs does not permit reaching into child directories")
}
_, child := dfs.findChild(pathComponents[0])
return child
}
func (dfs *FS) now() time.Time {
if dfs.Clock != nil {
return dfs.Clock.Now()
}
return time.Now()
}
func (dfs *FS) trimStaticRoot(name string) (string, bool) {
before, after, found := strings.Cut(name, "/"+dfs.StaticRoot)
if !found {
return before, false
}
return after, shared.IsRoot(after)
}

View File

@@ -0,0 +1,348 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"errors"
"io/fs"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/tailscale/xnet/webdav"
"tailscale.com/drive/driveimpl/shared"
"tailscale.com/tstest"
)
func TestStat(t *testing.T) {
cfs, _, _, clock := createFileSystem(t)
tests := []struct {
label string
name string
expected fs.FileInfo
err error
}{
{
label: "root folder",
name: "",
expected: &shared.StaticFileInfo{
Named: "",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
{
label: "static root folder",
name: "/domain",
expected: &shared.StaticFileInfo{
Named: "domain",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
{
label: "remote1",
name: "/domain/remote1",
expected: &shared.StaticFileInfo{
Named: "remote1",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
{
label: "remote2",
name: "/domain/remote2",
expected: &shared.StaticFileInfo{
Named: "remote2",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
{
label: "non-existent remote",
name: "remote3",
err: os.ErrNotExist,
},
}
ctx := context.Background()
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
fi, err := cfs.Stat(ctx, test.name)
if test.err != nil {
if !errors.Is(err, test.err) {
t.Errorf("got %v, want %v", err, test.err)
}
} else {
if err != nil {
t.Errorf("unable to stat file: %v", err)
} else {
infosEqual(t, test.expected, fi)
}
}
})
}
}
func TestListDir(t *testing.T) {
cfs, _, _, clock := createFileSystem(t)
tests := []struct {
label string
name string
expected []fs.FileInfo
err error
}{
{
label: "root folder",
name: "",
expected: []fs.FileInfo{
&shared.StaticFileInfo{
Named: "domain",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
},
{
label: "static root folder",
name: "/domain",
expected: []fs.FileInfo{
&shared.StaticFileInfo{
Named: "remote1",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
&shared.StaticFileInfo{
Named: "remote2",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
&shared.StaticFileInfo{
Named: "remote4",
Sized: 0,
Moded: 0555,
ModdedTime: clock.Now(),
Dir: true,
},
},
},
}
ctx := context.Background()
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
var infos []fs.FileInfo
file, err := cfs.OpenFile(ctx, test.name, os.O_RDONLY, 0)
if err == nil {
defer file.Close()
infos, err = file.Readdir(0)
}
if test.err != nil {
if !errors.Is(err, test.err) {
t.Errorf("got %v, want %v", err, test.err)
}
} else {
if err != nil {
t.Errorf("unable to stat file: %v", err)
} else {
if len(infos) != len(test.expected) {
t.Errorf("wrong number of file infos, want %d, got %d", len(test.expected), len(infos))
} else {
for i, expected := range test.expected {
infosEqual(t, expected, infos[i])
}
}
}
}
})
}
}
func TestMkdir(t *testing.T) {
fs, _, _, _ := createFileSystem(t)
tests := []struct {
label string
name string
perm os.FileMode
err error
}{
{
label: "attempt to create root folder",
name: "/",
},
{
label: "attempt to create static root folder",
name: "/domain",
},
{
label: "attempt to create remote",
name: "/domain/remote1",
},
{
label: "attempt to create non-existent remote",
name: "/domain/remote3",
err: os.ErrPermission,
},
}
ctx := context.Background()
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
err := fs.Mkdir(ctx, test.name, test.perm)
if test.err != nil {
if !errors.Is(err, test.err) {
t.Errorf("got %v, want %v", err, test.err)
}
} else if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestRemoveAll(t *testing.T) {
fs, _, _, _ := createFileSystem(t)
tests := []struct {
label string
name string
err error
}{
{
label: "attempt to remove root folder",
name: "/",
err: os.ErrPermission,
},
}
ctx := context.Background()
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
err := fs.RemoveAll(ctx, test.name)
if !errors.Is(err, test.err) {
t.Errorf("got %v, want %v", err, test.err)
}
})
}
}
func TestRename(t *testing.T) {
fs, _, _, _ := createFileSystem(t)
tests := []struct {
label string
oldName string
newName string
err error
}{
{
label: "attempt to move root folder",
oldName: "/",
newName: "/domain/remote2/copy.txt",
err: os.ErrPermission,
},
}
ctx := context.Background()
for _, test := range tests {
t.Run(test.label, func(t *testing.T) {
err := fs.Rename(ctx, test.oldName, test.newName)
if !errors.Is(err, test.err) {
t.Errorf("got %v, want: %v", err, test.err)
}
})
}
}
func createFileSystem(t *testing.T) (webdav.FileSystem, string, string, *tstest.Clock) {
s1, dir1 := startRemote(t)
s2, dir2 := startRemote(t)
// Make some files, use perms 0666 as lowest common denominator that works
// on both UNIX and Windows.
err := os.WriteFile(filepath.Join(dir1, "file1.txt"), []byte("12345"), 0666)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(dir2, "file2.txt"), []byte("54321"), 0666)
if err != nil {
t.Fatal(err)
}
// make some directories
err = os.Mkdir(filepath.Join(dir1, "dir1"), 0666)
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(filepath.Join(dir2, "dir2"), 0666)
if err != nil {
t.Fatal(err)
}
clock := tstest.NewClock(tstest.ClockOpts{Start: time.Now()})
fs := &FS{
Clock: clock,
StaticRoot: "domain",
Children: []*Child{
{Name: "remote1"},
{Name: "remote2"},
{Name: "remote4"},
},
}
t.Cleanup(func() {
defer s1.Close()
defer os.RemoveAll(dir1)
defer s2.Close()
defer os.RemoveAll(dir2)
})
return fs, dir1, dir2, clock
}
func startRemote(t *testing.T) (*httptest.Server, string) {
dir := t.TempDir()
h := &webdav.Handler{
FileSystem: webdav.Dir(dir),
LockSystem: webdav.NewMemLS(),
}
s := httptest.NewServer(h)
t.Cleanup(s.Close)
return s, dir
}
func infosEqual(t *testing.T, expected, actual fs.FileInfo) {
t.Helper()
sfi, ok := actual.(*shared.StaticFileInfo)
if ok {
// zero out BirthedTime because we don't want to compare that
sfi.BirthedTime = time.Time{}
}
if diff := cmp.Diff(actual, expected); diff != "" {
t.Errorf("Wrong file info (-got, +want):\n%s", diff)
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"os"
"tailscale.com/drive/driveimpl/shared"
)
// Mkdir implements webdav.FileSystem. All attempts to Mkdir a directory that
// already exists will succeed. All other attempts will fail with
// os.ErrPermission.
func (dfs *FS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
nameWithoutStaticRoot, isStaticRoot := dfs.trimStaticRoot(name)
if isStaticRoot || shared.IsRoot(name) {
// root directory already exists, consider this okay
return nil
}
child := dfs.childFor(nameWithoutStaticRoot)
if child != nil {
// child already exists, consider this okay
return nil
}
return &os.PathError{Op: "mkdir", Path: name, Err: os.ErrPermission}
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"io/fs"
"os"
"github.com/tailscale/xnet/webdav"
"tailscale.com/drive/driveimpl/shared"
)
// OpenFile implements interface webdav.Filesystem.
func (dfs *FS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
_, isStaticRoot := dfs.trimStaticRoot(name)
if !isStaticRoot && !shared.IsRoot(name) {
// Show a folder with no children to represent the requested child. In
// practice, the children of this folder are never read, we just need
// to give webdav a file here which it uses to call file.Stat(). So,
// even though the Child may in fact have its own children, it doesn't
// matter here.
return &shared.DirFile{
Info: shared.ReadOnlyDirInfo(name, dfs.now()),
LoadChildren: func() ([]fs.FileInfo, error) {
return nil, nil
},
}, nil
}
di, err := dfs.Stat(ctx, name)
if err != nil {
return nil, err
}
if dfs.StaticRoot != "" && !isStaticRoot {
// Show a folder with a single subfolder that is the static root.
return &shared.DirFile{
Info: di,
LoadChildren: func() ([]fs.FileInfo, error) {
return []fs.FileInfo{
shared.ReadOnlyDirInfo(dfs.StaticRoot, dfs.now()),
}, nil
},
}, nil
}
// Show a folder with one subfolder for each Child of this FS.
return &shared.DirFile{
Info: di,
LoadChildren: func() ([]fs.FileInfo, error) {
childInfos := make([]fs.FileInfo, 0, len(dfs.Children))
for _, c := range dfs.Children {
if c.isAvailable() {
childInfo := shared.ReadOnlyDirInfo(c.Name, dfs.now())
childInfos = append(childInfos, childInfo)
}
}
return childInfos, nil
},
}, nil
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"os"
)
// RemoveAll implements webdav.File. No removal is supported and this always
// returns os.ErrPermission.
func (dfs *FS) RemoveAll(ctx context.Context, name string) error {
return &os.PathError{Op: "rm", Path: name, Err: os.ErrPermission}
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"os"
)
// Rename implements interface webdav.FileSystem. No renaming is supported and
// this always returns os.ErrPermission.
func (dfs *FS) Rename(ctx context.Context, oldName, newName string) error {
return &os.PathError{Op: "mv", Path: oldName, Err: os.ErrPermission}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dirfs
import (
"context"
"io/fs"
"os"
"tailscale.com/drive/driveimpl/shared"
)
// Stat implements webdav.FileSystem.
func (dfs *FS) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
nameWithoutStaticRoot, isStaticRoot := dfs.trimStaticRoot(name)
if isStaticRoot || shared.IsRoot(name) {
// Static root is a directory, always use now() as the modified time to
// bust caches.
fi := shared.ReadOnlyDirInfo(name, dfs.now())
return fi, nil
}
child := dfs.childFor(nameWithoutStaticRoot)
if child == nil {
return nil, &os.PathError{Op: "stat", Path: name, Err: os.ErrNotExist}
}
return shared.ReadOnlyDirInfo(name, dfs.now()), nil
}