mirror of
https://github.com/octoleo/restic.git
synced 2024-11-22 21:05:10 +00:00
Merge pull request #259 from klauspost/windows-support-rebased
Add Windows support
This commit is contained in:
commit
a820719c07
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
)
|
)
|
||||||
@ -15,7 +16,9 @@ import (
|
|||||||
var ErrWrongData = errors.New("wrong data returned by backend, checksum does not match")
|
var ErrWrongData = errors.New("wrong data returned by backend, checksum does not match")
|
||||||
|
|
||||||
type Local struct {
|
type Local struct {
|
||||||
p string
|
p string
|
||||||
|
mu sync.Mutex
|
||||||
|
open map[string][]*os.File // Contains open files. Guarded by 'mu'.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens the local backend at dir.
|
// Open opens the local backend at dir.
|
||||||
@ -37,7 +40,7 @@ func Open(dir string) (*Local, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Local{p: dir}, nil
|
return &Local{p: dir, open: make(map[string][]*os.File)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates all the necessary files and directories for a new local
|
// Create creates all the necessary files and directories for a new local
|
||||||
@ -143,7 +146,7 @@ func (lb *localBlob) Finalize(t backend.Type, name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
|
return setNewFileMode(f, fi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new Blob. The data is available only after Finalize()
|
// Create creates a new Blob. The data is available only after Finalize()
|
||||||
@ -162,6 +165,11 @@ func (b *Local) Create() (backend.Blob, error) {
|
|||||||
basedir: b.p,
|
basedir: b.p,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open["blobs"]
|
||||||
|
b.open["blobs"] = append(open, file)
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
return &blob, nil
|
return &blob, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +206,15 @@ func dirname(base string, t backend.Type, name string) string {
|
|||||||
// Get returns a reader that yields the content stored under the given
|
// Get returns a reader that yields the content stored under the given
|
||||||
// name. The reader should be closed after draining it.
|
// name. The reader should be closed after draining it.
|
||||||
func (b *Local) Get(t backend.Type, name string) (io.ReadCloser, error) {
|
func (b *Local) Get(t backend.Type, name string) (io.ReadCloser, error) {
|
||||||
return os.Open(filename(b.p, t, name))
|
file, err := os.Open(filename(b.p, t, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[filename(b.p, t, name)]
|
||||||
|
b.open[filename(b.p, t, name)] = append(open, file)
|
||||||
|
b.mu.Unlock()
|
||||||
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReader returns an io.ReadCloser for the Blob with the given name of
|
// GetReader returns an io.ReadCloser for the Blob with the given name of
|
||||||
@ -209,6 +225,11 @@ func (b *Local) GetReader(t backend.Type, name string, offset, length uint) (io.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[filename(b.p, t, name)]
|
||||||
|
b.open[filename(b.p, t, name)] = append(open, f)
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
_, err = f.Seek(int64(offset), 0)
|
_, err = f.Seek(int64(offset), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -236,7 +257,17 @@ func (b *Local) Test(t backend.Type, name string) (bool, error) {
|
|||||||
|
|
||||||
// Remove removes the blob with the given name and type.
|
// Remove removes the blob with the given name and type.
|
||||||
func (b *Local) Remove(t backend.Type, name string) error {
|
func (b *Local) Remove(t backend.Type, name string) error {
|
||||||
return os.Remove(filename(b.p, t, name))
|
// close all open files we may have.
|
||||||
|
fn := filename(b.p, t, name)
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[fn]
|
||||||
|
for _, file := range open {
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
b.open[fn] = nil
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
return os.Remove(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns a channel that yields all names of blobs of type t. A
|
// List returns a channel that yields all names of blobs of type t. A
|
||||||
@ -283,7 +314,22 @@ func (b *Local) List(t backend.Type, done <-chan struct{}) <-chan string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the repository and all files.
|
// Delete removes the repository and all files.
|
||||||
func (b *Local) Delete() error { return os.RemoveAll(b.p) }
|
func (b *Local) Delete() error {
|
||||||
|
b.Close()
|
||||||
|
return os.RemoveAll(b.p)
|
||||||
|
}
|
||||||
|
|
||||||
// Close does nothing
|
// Close closes all open files.
|
||||||
func (b *Local) Close() error { return nil }
|
// They may have been closed already,
|
||||||
|
// so we ignore all errors.
|
||||||
|
func (b *Local) Close() error {
|
||||||
|
b.mu.Lock()
|
||||||
|
for _, open := range b.open {
|
||||||
|
for _, file := range open {
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.open = make(map[string][]*os.File)
|
||||||
|
b.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
12
backend/local/local_unix.go
Normal file
12
backend/local/local_unix.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// set file to readonly
|
||||||
|
func setNewFileMode(f string, fi os.FileInfo) error {
|
||||||
|
return os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
|
||||||
|
}
|
12
backend/local/local_windows.go
Normal file
12
backend/local/local_windows.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We don't modify read-only on windows,
|
||||||
|
// since it will make us unable to delete the file,
|
||||||
|
// and this isn't common practice on this platform.
|
||||||
|
func setNewFileMode(f string, fi os.FileInfo) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -10,7 +10,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/juju/errors"
|
"github.com/juju/errors"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
@ -37,7 +36,7 @@ func startClient(program string, args ...string) (*SFTP, error) {
|
|||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
// ignore signals sent to the parent (e.g. SIGINT)
|
// ignore signals sent to the parent (e.g. SIGINT)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
cmd.SysProcAttr = ignoreSigIntProcAttr()
|
||||||
|
|
||||||
// get stdin and stdout
|
// get stdin and stdout
|
||||||
wr, err := cmd.StdinPipe()
|
wr, err := cmd.StdinPipe()
|
||||||
|
13
backend/sftp/sftp_unix.go
Normal file
13
backend/sftp/sftp_unix.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ignoreSigIntProcAttr returns a syscall.SysProcAttr that
|
||||||
|
// disables SIGINT on parent.
|
||||||
|
func ignoreSigIntProcAttr() *syscall.SysProcAttr {
|
||||||
|
return &syscall.SysProcAttr{Setsid: true}
|
||||||
|
}
|
11
backend/sftp/sftp_windows.go
Normal file
11
backend/sftp/sftp_windows.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ignoreSigIntProcAttr returns a default syscall.SysProcAttr
|
||||||
|
// on Windows.
|
||||||
|
func ignoreSigIntProcAttr() *syscall.SysProcAttr {
|
||||||
|
return &syscall.SysProcAttr{}
|
||||||
|
}
|
9
build.go
9
build.go
@ -10,6 +10,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -258,11 +259,17 @@ func main() {
|
|||||||
|
|
||||||
version := getVersion()
|
version := getVersion()
|
||||||
compileTime := time.Now().Format(timeFormat)
|
compileTime := time.Now().Format(timeFormat)
|
||||||
|
output := "restic"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
output = "restic.exe"
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-tags", strings.Join(buildTags, " "),
|
"-tags", strings.Join(buildTags, " "),
|
||||||
"-ldflags", fmt.Sprintf(`-s -X main.version %q -X main.compiledAt %q`, version, compileTime),
|
"-ldflags", fmt.Sprintf(`-s -X main.version %q -X main.compiledAt %q`, version, compileTime),
|
||||||
"-o", "restic", "github.com/restic/restic/cmd/restic",
|
"-o", output, "github.com/restic/restic/cmd/restic",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = build(gopath, args...)
|
err = build(gopath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "build failed: %v\n", err)
|
fmt.Fprintf(os.Stderr, "build failed: %v\n", err)
|
||||||
|
31
cache.go
31
cache.go
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
@ -212,10 +213,40 @@ func getCacheDir() (string, error) {
|
|||||||
if dir := os.Getenv("RESTIC_CACHE"); dir != "" {
|
if dir := os.Getenv("RESTIC_CACHE"); dir != "" {
|
||||||
return dir, nil
|
return dir, nil
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return getWindowsCacheDir()
|
||||||
|
}
|
||||||
|
|
||||||
return getXDGCacheDir()
|
return getXDGCacheDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getWindowsCacheDir will return %APPDATA%\restic or create
|
||||||
|
// a folder in the temporary folder called "restic".
|
||||||
|
func getWindowsCacheDir() (string, error) {
|
||||||
|
cachedir := os.Getenv("APPDATA")
|
||||||
|
if cachedir == "" {
|
||||||
|
cachedir = os.TempDir()
|
||||||
|
}
|
||||||
|
cachedir = filepath.Join(cachedir, "restic")
|
||||||
|
fi, err := os.Stat(cachedir)
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(cachedir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return "", fmt.Errorf("cache dir %v is not a directory", cachedir)
|
||||||
|
}
|
||||||
|
return cachedir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getXDGCacheDir returns the cache directory according to XDG basedir spec, see
|
// getXDGCacheDir returns the cache directory according to XDG basedir spec, see
|
||||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||||
func getXDGCacheDir() (string, error) {
|
func getXDGCacheDir() (string, error) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// +build !openbsd
|
// +build !openbsd
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -132,7 +132,14 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
|
|||||||
// * s3://region/bucket -> amazon s3 bucket
|
// * s3://region/bucket -> amazon s3 bucket
|
||||||
// * sftp://user@host/foo/bar -> remote sftp repository on host for user at path foo/bar
|
// * sftp://user@host/foo/bar -> remote sftp repository on host for user at path foo/bar
|
||||||
// * sftp://host//tmp/backup -> remote sftp repository on host at path /tmp/backup
|
// * sftp://host//tmp/backup -> remote sftp repository on host at path /tmp/backup
|
||||||
|
// * c:\temp -> local repository at c:\temp - the path must exist
|
||||||
func open(u string) (backend.Backend, error) {
|
func open(u string) (backend.Backend, error) {
|
||||||
|
// check if the url is a directory that exists
|
||||||
|
fi, err := os.Stat(u)
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
return local.Open(u)
|
||||||
|
}
|
||||||
|
|
||||||
url, err := url.Parse(u)
|
url, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -140,7 +147,13 @@ func open(u string) (backend.Backend, error) {
|
|||||||
|
|
||||||
if url.Scheme == "" {
|
if url.Scheme == "" {
|
||||||
return local.Open(url.Path)
|
return local.Open(url.Path)
|
||||||
} else if url.Scheme == "s3" {
|
}
|
||||||
|
|
||||||
|
if len(url.Path) < 1 {
|
||||||
|
return nil, fmt.Errorf("unable to parse url %v", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.Scheme == "s3" {
|
||||||
return s3.Open(url.Host, url.Path[1:])
|
return s3.Open(url.Host, url.Path[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +169,12 @@ func open(u string) (backend.Backend, error) {
|
|||||||
|
|
||||||
// Create the backend specified by URI.
|
// Create the backend specified by URI.
|
||||||
func create(u string) (backend.Backend, error) {
|
func create(u string) (backend.Backend, error) {
|
||||||
|
// check if the url is a directory that exists
|
||||||
|
fi, err := os.Stat(u)
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
return local.Create(u)
|
||||||
|
}
|
||||||
|
|
||||||
url, err := url.Parse(u)
|
url, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -163,7 +182,13 @@ func create(u string) (backend.Backend, error) {
|
|||||||
|
|
||||||
if url.Scheme == "" {
|
if url.Scheme == "" {
|
||||||
return local.Create(url.Path)
|
return local.Create(url.Path)
|
||||||
} else if url.Scheme == "s3" {
|
}
|
||||||
|
|
||||||
|
if len(url.Path) < 1 {
|
||||||
|
return nil, fmt.Errorf("unable to parse url %v", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.Scheme == "s3" {
|
||||||
return s3.Open(url.Host, url.Path[1:])
|
return s3.Open(url.Host, url.Path[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// +build !openbsd
|
// +build !openbsd
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/restic/restic/test"
|
. "github.com/restic/restic/test"
|
||||||
@ -70,38 +69,6 @@ func sameModTime(fi1, fi2 os.FileInfo) bool {
|
|||||||
return fi1.ModTime() == fi2.ModTime()
|
return fi1.ModTime() == fi2.ModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *dirEntry) equals(other *dirEntry) bool {
|
|
||||||
if e.path != other.path {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v: path does not match (%v != %v)\n", e.path, e.path, other.path)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.fi.Mode() != other.fi.Mode() {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v: mode does not match (%v != %v)\n", e.path, e.fi.Mode(), other.fi.Mode())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sameModTime(e.fi, other.fi) {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v: ModTime does not match (%v != %v)\n", e.path, e.fi.ModTime(), other.fi.ModTime())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, _ := e.fi.Sys().(*syscall.Stat_t)
|
|
||||||
stat2, _ := other.fi.Sys().(*syscall.Stat_t)
|
|
||||||
|
|
||||||
if stat.Uid != stat2.Uid {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v: UID does not match (%v != %v)\n", e.path, stat.Uid, stat2.Uid)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.Gid != stat2.Gid {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v: GID does not match (%v != %v)\n", e.path, stat.Gid, stat2.Gid)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// directoriesEqualContents checks if both directories contain exactly the same
|
// directoriesEqualContents checks if both directories contain exactly the same
|
||||||
// contents.
|
// contents.
|
||||||
func directoriesEqualContents(dir1, dir2 string) bool {
|
func directoriesEqualContents(dir1, dir2 string) bool {
|
||||||
@ -237,6 +204,8 @@ func withTestEnvironment(t testing.TB, f func(*testEnvironment, GlobalOptions))
|
|||||||
}
|
}
|
||||||
|
|
||||||
OK(t, os.MkdirAll(env.testdata, 0700))
|
OK(t, os.MkdirAll(env.testdata, 0700))
|
||||||
|
OK(t, os.MkdirAll(env.cache, 0700))
|
||||||
|
OK(t, os.MkdirAll(env.repo, 0700))
|
||||||
|
|
||||||
f(&env, configureRestic(t, env.cache, env.repo))
|
f(&env, configureRestic(t, env.cache, env.repo))
|
||||||
|
|
||||||
|
41
cmd/restic/integration_helpers_unix_test.go
Normal file
41
cmd/restic/integration_helpers_unix_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//+build !windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *dirEntry) equals(other *dirEntry) bool {
|
||||||
|
if e.path != other.path {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: path does not match (%v != %v)\n", e.path, e.path, other.path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.fi.Mode() != other.fi.Mode() {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: mode does not match (%v != %v)\n", e.path, e.fi.Mode(), other.fi.Mode())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameModTime(e.fi, other.fi) {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: ModTime does not match (%v != %v)\n", e.path, e.fi.ModTime(), other.fi.ModTime())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, _ := e.fi.Sys().(*syscall.Stat_t)
|
||||||
|
stat2, _ := other.fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
|
if stat.Uid != stat2.Uid {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: UID does not match (%v != %v)\n", e.path, stat.Uid, stat2.Uid)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.Gid != stat2.Gid {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: GID does not match (%v != %v)\n", e.path, stat.Gid, stat2.Gid)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
27
cmd/restic/integration_helpers_windows_test.go
Normal file
27
cmd/restic/integration_helpers_windows_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//+build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *dirEntry) equals(other *dirEntry) bool {
|
||||||
|
if e.path != other.path {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: path does not match (%v != %v)\n", e.path, e.path, other.path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.fi.Mode() != other.fi.Mode() {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: mode does not match (%v != %v)\n", e.path, e.fi.Mode(), other.fi.Mode())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameModTime(e.fi, other.fi) {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v: ModTime does not match (%v != %v)\n", e.path, e.fi.ModTime(), other.fi.ModTime())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
@ -25,8 +25,14 @@ func Match(pattern, str string) (matched bool, err error) {
|
|||||||
return false, ErrBadString
|
return false, ErrBadString
|
||||||
}
|
}
|
||||||
|
|
||||||
patterns := strings.Split(pattern, string(filepath.Separator))
|
// convert file path separator to '/'
|
||||||
strs := strings.Split(str, string(filepath.Separator))
|
if filepath.Separator != '/' {
|
||||||
|
pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
|
||||||
|
str = strings.Replace(str, string(filepath.Separator), "/", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns := strings.Split(pattern, "/")
|
||||||
|
strs := strings.Split(str, "/")
|
||||||
|
|
||||||
return match(patterns, strs)
|
return match(patterns, strs)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"compress/bzip2"
|
"compress/bzip2"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/filter"
|
"github.com/restic/restic/filter"
|
||||||
@ -71,20 +73,40 @@ var matchTests = []struct {
|
|||||||
{"foo/**/bar", "/home/user/foo/x/y/bar/main.go", true},
|
{"foo/**/bar", "/home/user/foo/x/y/bar/main.go", true},
|
||||||
{"user/**/important*", "/home/user/work/x/y/hidden/x", false},
|
{"user/**/important*", "/home/user/work/x/y/hidden/x", false},
|
||||||
{"user/**/hidden*/**/c", "/home/user/work/x/y/hidden/z/a/b/c", true},
|
{"user/**/hidden*/**/c", "/home/user/work/x/y/hidden/z/a/b/c", true},
|
||||||
|
{"c:/foo/*test.*", "c:/foo/bar/test.go", false},
|
||||||
|
{"c:/foo/*/test.*", "c:/foo/bar/test.go", true},
|
||||||
|
{"c:/foo/*/bar/test.*", "c:/foo/bar/test.go", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testpattern(t *testing.T, pattern, path string, shouldMatch bool) {
|
||||||
|
match, err := filter.Match(pattern, path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test pattern %q failed: expected no error for path %q, but error returned: %v",
|
||||||
|
pattern, path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if match != shouldMatch {
|
||||||
|
t.Errorf("test: filter.Match(%q, %q): expected %v, got %v",
|
||||||
|
pattern, path, shouldMatch, match)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
func TestMatch(t *testing.T) {
|
||||||
for i, test := range matchTests {
|
for _, test := range matchTests {
|
||||||
match, err := filter.Match(test.pattern, test.path)
|
testpattern(t, test.pattern, test.path, test.match)
|
||||||
if err != nil {
|
|
||||||
t.Errorf("test %d failed: expected no error for pattern %q, but error returned: %v",
|
|
||||||
i, test.pattern, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if match != test.match {
|
// Test with native path separator
|
||||||
t.Errorf("test %d: filter.Match(%q, %q): expected %v, got %v",
|
if filepath.Separator != '/' {
|
||||||
i, test.pattern, test.path, test.match, match)
|
// Test with pattern as native
|
||||||
|
pattern := strings.Replace(test.pattern, "/", string(filepath.Separator), -1)
|
||||||
|
testpattern(t, pattern, test.path, test.match)
|
||||||
|
|
||||||
|
// Test with path as native
|
||||||
|
path := strings.Replace(test.path, "/", string(filepath.Separator), -1)
|
||||||
|
testpattern(t, test.pattern, path, test.match)
|
||||||
|
|
||||||
|
// Test with both pattern and path as native
|
||||||
|
testpattern(t, pattern, path, test.match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
lock.go
31
lock.go
@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -116,19 +115,8 @@ func (l *Lock) fillUserInfo() error {
|
|||||||
}
|
}
|
||||||
l.Username = usr.Username
|
l.Username = usr.Username
|
||||||
|
|
||||||
uid, err := strconv.ParseInt(usr.Uid, 10, 32)
|
l.UID, l.GID, err = uidGidInt(*usr)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.UID = uint32(uid)
|
|
||||||
|
|
||||||
gid, err := strconv.ParseInt(usr.Gid, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.GID = uint32(gid)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkForOtherLocks looks for other locks that currently exist in the repository.
|
// checkForOtherLocks looks for other locks that currently exist in the repository.
|
||||||
@ -206,17 +194,10 @@ func (l *Lock) Stale() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
proc, err := os.FindProcess(l.PID)
|
// check if we can reach the process retaining the lock
|
||||||
defer proc.Release()
|
exists := l.processExists()
|
||||||
if err != nil {
|
if !exists {
|
||||||
debug.Log("Lock.Stale", "error searching for process %d: %v\n", l.PID, err)
|
debug.Log("Lock.Stale", "could not reach process, %d, lock is probably stale\n", l.PID)
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
debug.Log("Lock.Stale", "sending SIGHUP to process %d\n", l.PID)
|
|
||||||
err = proc.Signal(syscall.SIGHUP)
|
|
||||||
if err != nil {
|
|
||||||
debug.Log("Lock.Stale", "signal error: %v, lock is probably stale\n", err)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ var staleLockTests = []struct {
|
|||||||
{
|
{
|
||||||
timestamp: time.Now(),
|
timestamp: time.Now(),
|
||||||
stale: true,
|
stale: true,
|
||||||
pid: os.Getpid() + 500,
|
pid: os.Getpid() + 500000,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ func TestLockWithStaleLock(t *testing.T) {
|
|||||||
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500)
|
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
OK(t, restic.RemoveStaleLocks(repo))
|
OK(t, restic.RemoveStaleLocks(repo))
|
||||||
|
48
lock_unix.go
Normal file
48
lock_unix.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/restic/restic/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// uidGidInt returns uid, gid of the user as a number.
|
||||||
|
func uidGidInt(u user.User) (uid, gid uint32, err error) {
|
||||||
|
var ui, gi int64
|
||||||
|
ui, err = strconv.ParseInt(u.Uid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gi, err = strconv.ParseInt(u.Gid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid = uint32(ui)
|
||||||
|
gid = uint32(gi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkProcess will check if the process retaining the lock
|
||||||
|
// exists and responds to SIGHUP signal.
|
||||||
|
// Returns true if the process exists and responds.
|
||||||
|
func (l Lock) processExists() bool {
|
||||||
|
proc, err := os.FindProcess(l.PID)
|
||||||
|
if err != nil {
|
||||||
|
debug.Log("Lock.Stale", "error searching for process %d: %v\n", l.PID, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer proc.Release()
|
||||||
|
|
||||||
|
debug.Log("Lock.Stale", "sending SIGHUP to process %d\n", l.PID)
|
||||||
|
err = proc.Signal(syscall.SIGHUP)
|
||||||
|
if err != nil {
|
||||||
|
debug.Log("Lock.Stale", "signal error: %v, lock is probably stale\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
25
lock_windows.go
Normal file
25
lock_windows.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
"github.com/restic/restic/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// uidGidInt always returns 0 on Windows, since uid isn't numbers
|
||||||
|
func uidGidInt(u user.User) (uid, gid uint32, err error) {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkProcess will check if the process retaining the lock exists.
|
||||||
|
// Returns true if the process exists.
|
||||||
|
func (l Lock) processExists() bool {
|
||||||
|
proc, err := os.FindProcess(l.PID)
|
||||||
|
if err != nil {
|
||||||
|
debug.Log("Lock.Stale", "error searching for process %d: %v\n", l.PID, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
proc.Release()
|
||||||
|
return true
|
||||||
|
}
|
78
node.go
78
node.go
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/restic/restic/debug"
|
"github.com/restic/restic/debug"
|
||||||
"github.com/restic/restic/pack"
|
"github.com/restic/restic/pack"
|
||||||
"github.com/restic/restic/repository"
|
"github.com/restic/restic/repository"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node is a file, directory or other item in a backup.
|
// Node is a file, directory or other item in a backup.
|
||||||
@ -148,7 +149,7 @@ func (node *Node) CreateAt(path string, repo *repository.Repository) error {
|
|||||||
func (node Node) restoreMetadata(path string) error {
|
func (node Node) restoreMetadata(path string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
err = os.Lchown(path, int(node.UID), int(node.GID))
|
err = lchown(path, int(node.UID), int(node.GID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Annotate(err, "Lchown")
|
return errors.Annotate(err, "Lchown")
|
||||||
}
|
}
|
||||||
@ -236,6 +237,10 @@ func (node Node) createFileAt(path string, repo *repository.Repository) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node Node) createSymlinkAt(path string) error {
|
func (node Node) createSymlinkAt(path string) error {
|
||||||
|
// Windows does not allow non-admins to create soft links.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err := os.Symlink(node.LinkTarget, path)
|
err := os.Symlink(node.LinkTarget, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Annotate(err, "Symlink")
|
return errors.Annotate(err, "Symlink")
|
||||||
@ -245,15 +250,15 @@ func (node Node) createSymlinkAt(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) createDevAt(path string) error {
|
func (node *Node) createDevAt(path string) error {
|
||||||
return syscall.Mknod(path, syscall.S_IFBLK|0600, int(node.Device))
|
return mknod(path, syscall.S_IFBLK|0600, int(node.Device))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) createCharDevAt(path string) error {
|
func (node *Node) createCharDevAt(path string) error {
|
||||||
return syscall.Mknod(path, syscall.S_IFCHR|0600, int(node.Device))
|
return mknod(path, syscall.S_IFCHR|0600, int(node.Device))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) createFifoAt(path string) error {
|
func (node *Node) createFifoAt(path string) error {
|
||||||
return syscall.Mkfifo(path, 0600)
|
return mkfifo(path, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node Node) MarshalJSON() ([]byte, error) {
|
func (node Node) MarshalJSON() ([]byte, error) {
|
||||||
@ -381,9 +386,19 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedStat := fi.Sys().(*syscall.Stat_t)
|
size := uint64(fi.Size())
|
||||||
inode := extendedStat.Ino
|
|
||||||
size := uint64(extendedStat.Size)
|
extendedStat, ok := toStatT(fi.Sys())
|
||||||
|
if !ok {
|
||||||
|
if node.ModTime != fi.ModTime() ||
|
||||||
|
node.Size != size {
|
||||||
|
debug.Log("node.isNewer", "node %v is newer: timestamp or size changed", path)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
inode := extendedStat.ino()
|
||||||
|
|
||||||
if node.ModTime != fi.ModTime() ||
|
if node.ModTime != fi.ModTime() ||
|
||||||
node.ChangeTime != changeTime(extendedStat) ||
|
node.ChangeTime != changeTime(extendedStat) ||
|
||||||
@ -397,11 +412,11 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillUser(stat *syscall.Stat_t) error {
|
func (node *Node) fillUser(stat statT) error {
|
||||||
node.UID = stat.Uid
|
node.UID = stat.uid()
|
||||||
node.GID = stat.Gid
|
node.GID = stat.gid()
|
||||||
|
|
||||||
username, err := lookupUsername(strconv.Itoa(int(stat.Uid)))
|
username, err := lookupUsername(strconv.Itoa(int(stat.uid())))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Annotate(err, "fillUser")
|
return errors.Annotate(err, "fillUser")
|
||||||
}
|
}
|
||||||
@ -439,12 +454,12 @@ func lookupUsername(uid string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
||||||
stat, ok := fi.Sys().(*syscall.Stat_t)
|
stat, ok := toStatT(fi.Sys())
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Inode = uint64(stat.Ino)
|
node.Inode = uint64(stat.ino())
|
||||||
|
|
||||||
node.fillTimes(stat)
|
node.fillTimes(stat)
|
||||||
|
|
||||||
@ -456,15 +471,15 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case "file":
|
||||||
node.Size = uint64(stat.Size)
|
node.Size = uint64(stat.size())
|
||||||
node.Links = uint64(stat.Nlink)
|
node.Links = uint64(stat.nlink())
|
||||||
case "dir":
|
case "dir":
|
||||||
case "symlink":
|
case "symlink":
|
||||||
node.LinkTarget, err = os.Readlink(path)
|
node.LinkTarget, err = os.Readlink(path)
|
||||||
case "dev":
|
case "dev":
|
||||||
node.Device = uint64(stat.Rdev)
|
node.Device = uint64(stat.rdev())
|
||||||
case "chardev":
|
case "chardev":
|
||||||
node.Device = uint64(stat.Rdev)
|
node.Device = uint64(stat.rdev())
|
||||||
case "fifo":
|
case "fifo":
|
||||||
case "socket":
|
case "socket":
|
||||||
default:
|
default:
|
||||||
@ -473,3 +488,32 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type statT interface {
|
||||||
|
dev() uint64
|
||||||
|
ino() uint64
|
||||||
|
nlink() uint64
|
||||||
|
uid() uint32
|
||||||
|
gid() uint32
|
||||||
|
rdev() uint64
|
||||||
|
size() int64
|
||||||
|
atim() syscall.Timespec
|
||||||
|
mtim() syscall.Timespec
|
||||||
|
ctim() syscall.Timespec
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfifo(path string, mode uint32) (err error) {
|
||||||
|
return mknod(path, mode|syscall.S_IFIFO, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) fillTimes(stat statT) {
|
||||||
|
ctim := stat.ctim()
|
||||||
|
atim := stat.atim()
|
||||||
|
node.ChangeTime = time.Unix(ctim.Unix())
|
||||||
|
node.AccessTime = time.Unix(atim.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeTime(stat statT) time.Time {
|
||||||
|
ctim := stat.ctim()
|
||||||
|
return time.Unix(ctim.Unix())
|
||||||
|
}
|
||||||
|
@ -3,22 +3,16 @@ package restic
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (node *Node) OpenForReading() (*os.File, error) {
|
func (node *Node) OpenForReading() (*os.File, error) {
|
||||||
return os.Open(node.path)
|
return os.Open(node.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeTime(stat *syscall.Stat_t) time.Time {
|
|
||||||
return time.Unix(stat.Ctimespec.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *Node) fillTimes(stat *syscall.Stat_t) {
|
|
||||||
node.ChangeTime = time.Unix(stat.Ctimespec.Unix())
|
|
||||||
node.AccessTime = time.Unix(stat.Atimespec.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s statUnix) atim() syscall.Timespec { return s.Atimespec }
|
||||||
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec }
|
||||||
|
func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec }
|
||||||
|
@ -3,22 +3,16 @@ package restic
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (node *Node) OpenForReading() (*os.File, error) {
|
func (node *Node) OpenForReading() (*os.File, error) {
|
||||||
return os.OpenFile(node.path, os.O_RDONLY, 0)
|
return os.OpenFile(node.path, os.O_RDONLY, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillTimes(stat *syscall.Stat_t) {
|
|
||||||
node.ChangeTime = time.Unix(stat.Ctimespec.Unix())
|
|
||||||
node.AccessTime = time.Unix(stat.Atimespec.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeTime(stat *syscall.Stat_t) time.Time {
|
|
||||||
return time.Unix(stat.Ctimespec.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s statUnix) atim() syscall.Timespec { return s.Atimespec }
|
||||||
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec }
|
||||||
|
func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec }
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/juju/errors"
|
"github.com/juju/errors"
|
||||||
@ -18,15 +17,6 @@ func (node *Node) OpenForReading() (*os.File, error) {
|
|||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillTimes(stat *syscall.Stat_t) {
|
|
||||||
node.ChangeTime = time.Unix(stat.Ctim.Unix())
|
|
||||||
node.AccessTime = time.Unix(stat.Atim.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeTime(stat *syscall.Stat_t) time.Time {
|
|
||||||
return time.Unix(stat.Ctim.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
dir, err := os.Open(filepath.Dir(path))
|
dir, err := os.Open(filepath.Dir(path))
|
||||||
defer dir.Close()
|
defer dir.Close()
|
||||||
@ -65,3 +55,7 @@ func utimensat(dirfd int, path string, times *[2]syscall.Timespec, flags int) (e
|
|||||||
func utimesNanoAt(dirfd int, path string, ts [2]syscall.Timespec, flags int) (err error) {
|
func utimesNanoAt(dirfd int, path string, ts [2]syscall.Timespec, flags int) (err error) {
|
||||||
return utimensat(dirfd, path, (*[2]syscall.Timespec)(unsafe.Pointer(&ts[0])), flags)
|
return utimensat(dirfd, path, (*[2]syscall.Timespec)(unsafe.Pointer(&ts[0])), flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s statUnix) atim() syscall.Timespec { return s.Atim }
|
||||||
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtim }
|
||||||
|
func (s statUnix) ctim() syscall.Timespec { return s.Ctim }
|
||||||
|
@ -3,7 +3,6 @@ package restic
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (node *Node) OpenForReading() (*os.File, error) {
|
func (node *Node) OpenForReading() (*os.File, error) {
|
||||||
@ -14,15 +13,10 @@ func (node *Node) OpenForReading() (*os.File, error) {
|
|||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) fillTimes(stat *syscall.Stat_t) {
|
|
||||||
node.ChangeTime = time.Unix(stat.Ctim.Unix())
|
|
||||||
node.AccessTime = time.Unix(stat.Atim.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeTime(stat *syscall.Stat_t) time.Time {
|
|
||||||
return time.Unix(stat.Ctim.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s statUnix) atim() syscall.Timespec { return s.Atim }
|
||||||
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtim }
|
||||||
|
func (s statUnix) ctim() syscall.Timespec { return s.Ctim }
|
||||||
|
19
node_test.go
19
node_test.go
@ -119,6 +119,9 @@ func TestNodeRestoreAt(t *testing.T) {
|
|||||||
nodePath := filepath.Join(tempdir, test.Name)
|
nodePath := filepath.Join(tempdir, test.Name)
|
||||||
OK(t, test.CreateAt(nodePath, nil))
|
OK(t, test.CreateAt(nodePath, nil))
|
||||||
|
|
||||||
|
if test.Type == "symlink" && runtime.GOOS == "windows" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if test.Type == "dir" {
|
if test.Type == "dir" {
|
||||||
OK(t, test.RestoreTimestamps(nodePath))
|
OK(t, test.RestoreTimestamps(nodePath))
|
||||||
}
|
}
|
||||||
@ -135,14 +138,16 @@ func TestNodeRestoreAt(t *testing.T) {
|
|||||||
"%v: type doesn't match (%v != %v)", test.Type, test.Type, n2.Type)
|
"%v: type doesn't match (%v != %v)", test.Type, test.Type, n2.Type)
|
||||||
Assert(t, test.Size == n2.Size,
|
Assert(t, test.Size == n2.Size,
|
||||||
"%v: size doesn't match (%v != %v)", test.Size, test.Size, n2.Size)
|
"%v: size doesn't match (%v != %v)", test.Size, test.Size, n2.Size)
|
||||||
Assert(t, test.UID == n2.UID,
|
|
||||||
"%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID)
|
|
||||||
Assert(t, test.GID == n2.GID,
|
|
||||||
"%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID)
|
|
||||||
|
|
||||||
if test.Type != "symlink" {
|
if runtime.GOOS != "windows" {
|
||||||
Assert(t, test.Mode == n2.Mode,
|
Assert(t, test.UID == n2.UID,
|
||||||
"%v: mode doesn't match (%v != %v)", test.Type, test.Mode, n2.Mode)
|
"%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID)
|
||||||
|
Assert(t, test.GID == n2.GID,
|
||||||
|
"%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID)
|
||||||
|
if test.Type != "symlink" {
|
||||||
|
Assert(t, test.Mode == n2.Mode,
|
||||||
|
"%v: mode doesn't match (%v != %v)", test.Type, test.Mode, n2.Mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AssertFsTimeEqual(t, "AccessTime", test.Type, test.AccessTime, n2.AccessTime)
|
AssertFsTimeEqual(t, "AccessTime", test.Type, test.AccessTime, n2.AccessTime)
|
||||||
|
32
node_unix.go
Normal file
32
node_unix.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// +build dragonfly linux netbsd openbsd freebsd solaris darwin
|
||||||
|
|
||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mknod = syscall.Mknod
|
||||||
|
var lchown = os.Lchown
|
||||||
|
|
||||||
|
type statUnix syscall.Stat_t
|
||||||
|
|
||||||
|
func toStatT(i interface{}) (statT, bool) {
|
||||||
|
if i == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
s, ok := i.(*syscall.Stat_t)
|
||||||
|
if ok && s != nil {
|
||||||
|
return statUnix(*s), true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statUnix) dev() uint64 { return uint64(s.Dev) }
|
||||||
|
func (s statUnix) ino() uint64 { return uint64(s.Ino) }
|
||||||
|
func (s statUnix) nlink() uint64 { return uint64(s.Nlink) }
|
||||||
|
func (s statUnix) uid() uint32 { return uint32(s.Uid) }
|
||||||
|
func (s statUnix) gid() uint32 { return uint32(s.Gid) }
|
||||||
|
func (s statUnix) rdev() uint64 { return uint64(s.Rdev) }
|
||||||
|
func (s statUnix) size() int64 { return int64(s.Size) }
|
63
node_windows.go
Normal file
63
node_windows.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (node *Node) OpenForReading() (*os.File, error) {
|
||||||
|
return os.OpenFile(node.path, os.O_RDONLY, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mknod() creates a filesystem node (file, device
|
||||||
|
// special file, or named pipe) named pathname, with attributes
|
||||||
|
// specified by mode and dev.
|
||||||
|
var mknod = func(path string, mode uint32, dev int) (err error) {
|
||||||
|
return errors.New("device nodes cannot be created on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows doesn't need lchown
|
||||||
|
var lchown = func(path string, uid int, gid int) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type statWin syscall.Win32FileAttributeData
|
||||||
|
|
||||||
|
func toStatT(i interface{}) (statT, bool) {
|
||||||
|
if i == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
s, ok := i.(*syscall.Win32FileAttributeData)
|
||||||
|
if ok && s != nil {
|
||||||
|
return statWin(*s), true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statWin) dev() uint64 { return 0 }
|
||||||
|
func (s statWin) ino() uint64 { return 0 }
|
||||||
|
func (s statWin) nlink() uint64 { return 0 }
|
||||||
|
func (s statWin) uid() uint32 { return 0 }
|
||||||
|
func (s statWin) gid() uint32 { return 0 }
|
||||||
|
func (s statWin) rdev() uint64 { return 0 }
|
||||||
|
|
||||||
|
func (s statWin) size() int64 {
|
||||||
|
return int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statWin) atim() syscall.Timespec {
|
||||||
|
return syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statWin) mtim() syscall.Timespec {
|
||||||
|
return syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statWin) ctim() syscall.Timespec {
|
||||||
|
return syscall.NsecToTimespec(s.CreationTime.Nanoseconds())
|
||||||
|
}
|
16
restorer.go
16
restorer.go
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
"github.com/restic/restic/debug"
|
"github.com/restic/restic/debug"
|
||||||
@ -96,16 +95,13 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Did it fail because of ENOENT?
|
// Did it fail because of ENOENT?
|
||||||
if pe, ok := errors.Cause(err).(*os.PathError); ok {
|
if err != nil && os.IsNotExist(errors.Cause(err)) {
|
||||||
errn, ok := pe.Err.(syscall.Errno)
|
debug.Log("Restorer.restoreNodeTo", "create intermediate paths")
|
||||||
if ok && errn == syscall.ENOENT {
|
|
||||||
debug.Log("Restorer.restoreNodeTo", "create intermediate paths")
|
|
||||||
|
|
||||||
// Create parent directories and retry
|
// Create parent directories and retry
|
||||||
err = os.MkdirAll(filepath.Dir(dstPath), 0700)
|
err = os.MkdirAll(filepath.Dir(dstPath), 0700)
|
||||||
if err == nil || err == os.ErrExist {
|
if err == nil || err == os.ErrExist {
|
||||||
err = node.CreateAt(dstPath, res.repo)
|
err = node.CreateAt(dstPath, res.repo)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
snapshot.go
17
snapshot.go
@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
@ -76,19 +75,9 @@ func (sn *Snapshot) fillUserInfo() error {
|
|||||||
}
|
}
|
||||||
sn.Username = usr.Username
|
sn.Username = usr.Username
|
||||||
|
|
||||||
uid, err := strconv.ParseInt(usr.Uid, 10, 32)
|
// set userid and groupid
|
||||||
if err != nil {
|
sn.UID, sn.GID, err = uidGidInt(*usr)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
sn.UID = uint32(uid)
|
|
||||||
|
|
||||||
gid, err := strconv.ParseInt(usr.Gid, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sn.GID = uint32(gid)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
||||||
|
Loading…
Reference in New Issue
Block a user