tailscale/tailfs/tailfsimpl/dirfs/dirfs.go
Percy Wegmann 50fb8b9123 tailfs: replace webdavfs with reverse proxies
Instead of modeling remote WebDAV servers as actual
webdav.FS instances, we now just proxy traffic to them.
This not only simplifies the code, but it also allows
WebDAV locking to work correctly by making sure locks are
handled by the servers that need to (i.e. the ones actually
serving the files).

Updates tailscale/corp#16827

Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-02-26 09:30:22 -06:00

102 lines
2.7 KiB
Go

// 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/tailfs/tailfsimpl/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)
}