70 lines
2.6 KiB
Go
70 lines
2.6 KiB
Go
package safeio
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// PrivateDirectoryHandle pins one private directory for the duration of a storage operation.
|
|
// Implementations use descriptor-relative operations on POSIX and retained no-delete handles on
|
|
// Windows. Callers must close every returned child before releasing its parent.
|
|
type PrivateDirectoryHandle interface {
|
|
Close() error
|
|
Validate() error
|
|
OpenChild(name string, ensure bool) (PrivateDirectoryHandle, bool, error)
|
|
CreateRegular(name string, contents []byte) (bool, error)
|
|
CreateRegularFile(name string) (*os.File, bool, error)
|
|
ReadRegular(name string, maximum int64) ([]byte, bool, error)
|
|
ReplaceRegular(name string, contents []byte) error
|
|
RemoveRegular(name string) (bool, error)
|
|
ListPage(maximumEntries int, afterName string, validName func(string) bool, validLinks func(string, uint64) bool) (PrivateDirectoryPage, error)
|
|
ClaimRegular(source, claim string) (bool, error)
|
|
ReadClaim(source, claim string, maximum int64) ([]byte, bool, error)
|
|
RemoveClaim(source, claim string) (bool, error)
|
|
}
|
|
|
|
// OpenPrivateDirectory validates or creates the final private directory while retaining the
|
|
// opened canonical directory handle. With ensure=false, found=false means the final component is
|
|
// absent but its already-opened parent proves the same operation could safely create it.
|
|
func OpenPrivateDirectory(path string, ensure bool) (PrivateDirectoryHandle, bool, error) {
|
|
if err := ValidateCanonicalPath(path); err != nil {
|
|
return nil, false, ErrUnsafeFile
|
|
}
|
|
return openPrivateDirectory(path, ensure)
|
|
}
|
|
|
|
func validPrivateLeafName(name string) bool {
|
|
return name != "" && name != "." && name != ".." && !strings.ContainsAny(name, "\\/:\x00")
|
|
}
|
|
|
|
var privateDirectoryTestHook struct {
|
|
sync.RWMutex
|
|
hook func(string)
|
|
}
|
|
|
|
// SetPrivateDirectoryTestHookForTest installs a deterministic race hook for internal package
|
|
// tests. It has no production effect unless a test explicitly installs one.
|
|
func SetPrivateDirectoryTestHookForTest(hook func(string)) func() {
|
|
privateDirectoryTestHook.Lock()
|
|
previous := privateDirectoryTestHook.hook
|
|
privateDirectoryTestHook.hook = hook
|
|
privateDirectoryTestHook.Unlock()
|
|
return func() {
|
|
privateDirectoryTestHook.Lock()
|
|
privateDirectoryTestHook.hook = previous
|
|
privateDirectoryTestHook.Unlock()
|
|
}
|
|
}
|
|
|
|
// NotifyPrivateDirectoryTestHookForTest marks an internal retained-handle boundary. It is called
|
|
// only by storage code and lets tests install deterministic directory replacement races.
|
|
func NotifyPrivateDirectoryTestHookForTest(stage string) {
|
|
privateDirectoryTestHook.RLock()
|
|
hook := privateDirectoryTestHook.hook
|
|
privateDirectoryTestHook.RUnlock()
|
|
if hook != nil {
|
|
hook(stage)
|
|
}
|
|
}
|