fix(backup): retain staging cleanup capability
This commit is contained in:
@@ -149,6 +149,68 @@ func TestCreateCanonicalNewPrivateFileProvidesPrivateStreamingWriter(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateDirectoryCreatesAndRemovesStreamingRegularFile(t *testing.T) {
|
||||
temporaryRoot, err := filepath.EvalSymlinks(os.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-safeio-stream-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = os.RemoveAll(root) })
|
||||
if err := ProtectPrivateDirectory(root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
directory, found, err := OpenPrivateDirectory(root, true)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("OpenPrivateDirectory() = found %v, err %v", found, err)
|
||||
}
|
||||
defer directory.Close()
|
||||
|
||||
file, created, err := directory.CreateRegularFile("archive-stream.zip")
|
||||
if err != nil || !created || file == nil {
|
||||
t.Fatalf("CreateRegularFile() = file %v, created %v, err %v", file, created, err)
|
||||
}
|
||||
if _, err := file.Write([]byte("private archive")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := file.Sync(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := file.Seek(0, io.SeekStart); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
streamed := make([]byte, len("private archive"))
|
||||
if _, err := io.ReadFull(file, streamed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(streamed) != "private archive" {
|
||||
t.Fatalf("streamed contents = %q, want %q", streamed, "private archive")
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
removed, err := directory.RemoveRegular("archive-stream.zip")
|
||||
if err != nil || !removed {
|
||||
t.Fatalf("RemoveRegular() = removed %v, err %v", removed, err)
|
||||
}
|
||||
|
||||
if created, err := directory.CreateRegular("existing.zip", []byte("existing")); err != nil || !created {
|
||||
t.Fatalf("CreateRegular(existing.zip) = created %v, err %v", created, err)
|
||||
}
|
||||
if file, created, err := directory.CreateRegularFile("existing.zip"); err != nil || created || file != nil {
|
||||
t.Fatalf("CreateRegularFile(existing.zip) = file %v, created %v, err %v", file, created, err)
|
||||
}
|
||||
if created, err := directory.CreateRegular("target.zip", []byte("target")); err != nil || !created {
|
||||
t.Fatalf("CreateRegular(target.zip) = created %v, err %v", created, err)
|
||||
}
|
||||
testsupport.SymlinkOrSkip(t, filepath.Join(root, "target.zip"), filepath.Join(root, "linked.zip"))
|
||||
if file, created, err := directory.CreateRegularFile("linked.zip"); !errors.Is(err, ErrUnsafeFile) || created || file != nil {
|
||||
t.Fatalf("CreateRegularFile(linked.zip) = file %v, created %v, err %v", file, created, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreflightPrivateDirectoryAllowsOnlyAMissingFinalComponentWithoutMutation(t *testing.T) {
|
||||
temporaryRoot, err := filepath.EvalSymlinks(os.TempDir())
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package safeio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -13,6 +14,7 @@ type PrivateDirectoryHandle interface {
|
||||
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)
|
||||
|
||||
@@ -201,6 +201,48 @@ func (directory *unixPrivateDirectory) CreateRegular(name string, contents []byt
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (directory *unixPrivateDirectory) CreateRegularFile(name string) (*os.File, bool, error) {
|
||||
if directory.Validate() != nil || !validPrivateLeafName(name) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
descriptor, err := unix.Openat(directory.descriptor, name,
|
||||
unix.O_RDWR|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0o600)
|
||||
if errors.Is(err, unix.EEXIST) {
|
||||
if _, existingErr := requirePrivateUnixRootRegularAt(directory.descriptor, name, 1); existingErr != nil || directory.Validate() != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
file := os.NewFile(uintptr(descriptor), "tht-safeio-private-root-stream")
|
||||
if file == nil {
|
||||
_ = unix.Close(descriptor)
|
||||
_ = unix.Unlinkat(directory.descriptor, name, 0)
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
failed := true
|
||||
defer func() {
|
||||
if failed {
|
||||
_ = file.Close()
|
||||
_ = unix.Unlinkat(directory.descriptor, name, 0)
|
||||
}
|
||||
}()
|
||||
if unix.Fchmod(descriptor, 0o600) != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
var stat unix.Stat_t
|
||||
if unix.Fstat(descriptor, &stat) != nil || !privateUnixRootRegular(&stat, 1) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
if directory.Validate() != nil || unix.Fsync(directory.descriptor) != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
failed = false
|
||||
return file, true, nil
|
||||
}
|
||||
|
||||
func (directory *unixPrivateDirectory) ReadRegular(name string, maximum int64) ([]byte, bool, error) {
|
||||
if directory.Validate() != nil || !validPrivateLeafName(name) || maximum < 0 || maximum == int64(^uint64(0)>>1) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
|
||||
@@ -538,6 +538,41 @@ func (directory *windowsPrivateDirectory) CreateRegular(name string, contents []
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (directory *windowsPrivateDirectory) CreateRegularFile(name string) (*os.File, bool, error) {
|
||||
if directory.Validate() != nil || !validPrivateLeafName(name) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
value, err := createWindowsPrivateRegularAt(directory.handle, name)
|
||||
if err != nil {
|
||||
existing, existingErr := openWindowsPrivateRegularAt(directory.handle, name, windows.FILE_GENERIC_READ, 1)
|
||||
if existingErr == nil {
|
||||
if existing.Close() != nil || directory.Validate() != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
failed := true
|
||||
defer func() {
|
||||
if failed {
|
||||
_ = closeAndDeleteWindowsPrivateRegular(value)
|
||||
}
|
||||
}()
|
||||
info, err := privateWindowsRegularInfo(value.handle, 1)
|
||||
if err != nil || directory.Validate() != nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
value.info = info
|
||||
file := os.NewFile(uintptr(value.handle), "tht-safeio-private-root-stream")
|
||||
if file == nil {
|
||||
return nil, false, ErrUnsafeFile
|
||||
}
|
||||
value.handle = 0
|
||||
failed = false
|
||||
return file, true, nil
|
||||
}
|
||||
|
||||
func (directory *windowsPrivateDirectory) ReadRegular(name string, maximum int64) ([]byte, bool, error) {
|
||||
if directory.Validate() != nil || !validPrivateLeafName(name) || maximum < 0 || maximum == int64(^uint64(0)>>1) {
|
||||
return nil, false, ErrUnsafeFile
|
||||
|
||||
Reference in New Issue
Block a user