mirror of
https://github.com/restic/restic.git
synced 2025-10-09 07:33:53 +00:00
backend: move backend implementation helpers to util package
This removes code that is only used within a backend implementation from the backend package. The latter now only contains code that also has external users.
This commit is contained in:
50
internal/backend/util/defaults.go
Normal file
50
internal/backend/util/defaults.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// DefaultLoad implements Backend.Load using lower-level openReader func
|
||||
func DefaultLoad(ctx context.Context, h restic.Handle, length int, offset int64,
|
||||
openReader func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error),
|
||||
fn func(rd io.Reader) error) error {
|
||||
|
||||
rd, err := openReader(ctx, h, length, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fn(rd)
|
||||
if err != nil {
|
||||
_ = rd.Close() // ignore secondary errors closing the reader
|
||||
return err
|
||||
}
|
||||
return rd.Close()
|
||||
}
|
||||
|
||||
// DefaultDelete removes all restic keys in the bucket. It will not remove the bucket itself.
|
||||
func DefaultDelete(ctx context.Context, be restic.Backend) error {
|
||||
alltypes := []restic.FileType{
|
||||
restic.PackFile,
|
||||
restic.KeyFile,
|
||||
restic.LockFile,
|
||||
restic.SnapshotFile,
|
||||
restic.IndexFile}
|
||||
|
||||
for _, t := range alltypes {
|
||||
err := be.List(ctx, t, func(fi restic.FileInfo) error {
|
||||
return be.Remove(ctx, restic.Handle{Type: t, Name: fi.Name})
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err := be.Remove(ctx, restic.Handle{Type: restic.ConfigFile})
|
||||
if err != nil && be.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
64
internal/backend/util/defaults_test.go
Normal file
64
internal/backend/util/defaults_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend/util"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
type mockReader struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (rd *mockReader) Read(_ []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (rd *mockReader) Close() error {
|
||||
rd.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDefaultLoad(t *testing.T) {
|
||||
|
||||
h := restic.Handle{Name: "id", Type: restic.PackFile}
|
||||
rd := &mockReader{}
|
||||
|
||||
// happy case, assert correct parameters are passed around and content stream is closed
|
||||
err := util.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
rtest.Equals(t, h, ih)
|
||||
rtest.Equals(t, int(10), length)
|
||||
rtest.Equals(t, int64(11), offset)
|
||||
|
||||
return rd, nil
|
||||
}, func(ird io.Reader) error {
|
||||
rtest.Equals(t, rd, ird)
|
||||
return nil
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, true, rd.closed)
|
||||
|
||||
// unhappy case, assert producer errors are handled correctly
|
||||
err = util.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
return nil, errors.Errorf("producer error")
|
||||
}, func(ird io.Reader) error {
|
||||
t.Fatalf("unexpected consumer invocation")
|
||||
return nil
|
||||
})
|
||||
rtest.Equals(t, "producer error", err.Error())
|
||||
|
||||
// unhappy case, assert consumer errors are handled correctly
|
||||
rd = &mockReader{}
|
||||
err = util.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
return rd, nil
|
||||
}, func(ird io.Reader) error {
|
||||
return errors.Errorf("consumer error")
|
||||
})
|
||||
rtest.Equals(t, true, rd.closed)
|
||||
rtest.Equals(t, "consumer error", err.Error())
|
||||
}
|
20
internal/backend/util/errdot_119.go
Normal file
20
internal/backend/util/errdot_119.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
// This file provides a function to check whether an error from cmd.Start() is
|
||||
// exec.ErrDot which was introduced in Go 1.19.
|
||||
// This function is needed so that we can perform this check only for Go 1.19 and
|
||||
// up, whereas for older versions we use a dummy/stub in the file errdot_old.go.
|
||||
// Once the minimum Go version restic supports is 1.19, remove this file and
|
||||
// replace any calls to it with the corresponding code as per below.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func IsErrDot(err error) bool {
|
||||
return errors.Is(err, exec.ErrDot)
|
||||
}
|
13
internal/backend/util/errdot_old.go
Normal file
13
internal/backend/util/errdot_old.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !go1.19
|
||||
// +build !go1.19
|
||||
|
||||
// This file provides a stub for IsErrDot() for Go versions below 1.19.
|
||||
// See the corresponding file errdot_119.go for more information.
|
||||
// Once the minimum Go version restic supports is 1.19, remove this file
|
||||
// and perform the actions listed in errdot_119.go.
|
||||
|
||||
package util
|
||||
|
||||
func IsErrDot(err error) bool {
|
||||
return false
|
||||
}
|
26
internal/backend/util/foreground.go
Normal file
26
internal/backend/util/foreground.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StartForeground runs cmd in the foreground, by temporarily switching to the
|
||||
// new process group created for cmd. The returned function `bg` switches back
|
||||
// to the previous process group.
|
||||
//
|
||||
// The command's environment has all RESTIC_* variables removed.
|
||||
func StartForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
env := os.Environ() // Returns a copy that we can modify.
|
||||
|
||||
cmd.Env = env[:0]
|
||||
for _, kv := range env {
|
||||
if strings.HasPrefix(kv, "RESTIC_") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, kv)
|
||||
}
|
||||
|
||||
return startForeground(cmd)
|
||||
}
|
28
internal/backend/util/foreground_sysv.go
Normal file
28
internal/backend/util/foreground_sysv.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build aix || solaris
|
||||
// +build aix solaris
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
)
|
||||
|
||||
func startForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
// run the command in it's own process group so that SIGINT
|
||||
// is not sent to it.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
// start the process
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cmd.Start")
|
||||
}
|
||||
|
||||
bg = func() error { return nil }
|
||||
return bg, nil
|
||||
}
|
41
internal/backend/util/foreground_test.go
Normal file
41
internal/backend/util/foreground_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend/util"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestForeground(t *testing.T) {
|
||||
err := os.Setenv("RESTIC_PASSWORD", "supersecret")
|
||||
rtest.OK(t, err)
|
||||
|
||||
cmd := exec.Command("env")
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
rtest.OK(t, err)
|
||||
|
||||
bg, err := util.StartForeground(cmd)
|
||||
rtest.OK(t, err)
|
||||
defer func() {
|
||||
rtest.OK(t, cmd.Wait())
|
||||
}()
|
||||
|
||||
err = bg()
|
||||
rtest.OK(t, err)
|
||||
|
||||
sc := bufio.NewScanner(stdout)
|
||||
for sc.Scan() {
|
||||
if strings.HasPrefix(sc.Text(), "RESTIC_PASSWORD=") {
|
||||
t.Error("subprocess got to see the password")
|
||||
}
|
||||
}
|
||||
rtest.OK(t, err)
|
||||
}
|
72
internal/backend/util/foreground_unix.go
Normal file
72
internal/backend/util/foreground_unix.go
Normal file
@@ -0,0 +1,72 @@
|
||||
//go:build !aix && !solaris && !windows
|
||||
// +build !aix,!solaris,!windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func tcsetpgrp(fd int, pid int) error {
|
||||
// IoctlSetPointerInt silently casts to int32 internally,
|
||||
// so this assumes pid fits in 31 bits.
|
||||
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pid)
|
||||
}
|
||||
|
||||
func startForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
// open the TTY, we need the file descriptor
|
||||
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
debug.Log("unable to open tty: %v", err)
|
||||
bg = func() error {
|
||||
return nil
|
||||
}
|
||||
return bg, cmd.Start()
|
||||
}
|
||||
|
||||
signal.Ignore(unix.SIGTTIN)
|
||||
signal.Ignore(unix.SIGTTOU)
|
||||
|
||||
// run the command in its own process group
|
||||
cmd.SysProcAttr = &unix.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
// start the process
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return nil, errors.Wrap(err, "cmd.Start")
|
||||
}
|
||||
|
||||
// move the command's process group into the foreground
|
||||
prev := unix.Getpgrp()
|
||||
err = tcsetpgrp(int(tty.Fd()), cmd.Process.Pid)
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bg = func() error {
|
||||
signal.Reset(unix.SIGTTIN)
|
||||
signal.Reset(unix.SIGTTOU)
|
||||
|
||||
// reset the foreground process group
|
||||
err = tcsetpgrp(int(tty.Fd()), prev)
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return tty.Close()
|
||||
}
|
||||
|
||||
return bg, nil
|
||||
}
|
22
internal/backend/util/foreground_windows.go
Normal file
22
internal/backend/util/foreground_windows.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func startForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
// just start the process and hope for the best
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.CreationFlags = windows.CREATE_NEW_PROCESS_GROUP
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cmd.Start")
|
||||
}
|
||||
|
||||
bg = func() error { return nil }
|
||||
return bg, nil
|
||||
}
|
29
internal/backend/util/paths.go
Normal file
29
internal/backend/util/paths.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package util
|
||||
|
||||
import "os"
|
||||
|
||||
type Modes struct {
|
||||
Dir os.FileMode
|
||||
File os.FileMode
|
||||
}
|
||||
|
||||
// DefaultModes defines the default permissions to apply to new repository
|
||||
// files and directories stored on file-based backends.
|
||||
var DefaultModes = Modes{Dir: 0700, File: 0600}
|
||||
|
||||
// DeriveModesFromFileInfo will, given the mode of a regular file, compute
|
||||
// the mode we should use for new files and directories. If the passed
|
||||
// error is non-nil DefaultModes are returned.
|
||||
func DeriveModesFromFileInfo(fi os.FileInfo, err error) Modes {
|
||||
m := DefaultModes
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
|
||||
if fi.Mode()&0040 != 0 { // Group has read access
|
||||
m.Dir |= 0070
|
||||
m.File |= 0060
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
Reference in New Issue
Block a user