From 93d8c2beba43af5c9d96db8d00252368a129e137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zlatko=20=C4=8Calu=C5=A1i=C4=87?= Date: Sat, 5 Nov 2016 17:33:34 +0100 Subject: [PATCH] Copy errors & fs packages from the restic repo and fix import paths Tedious, but I see no better way... --- errors/doc.go | 2 + errors/fatal.go | 38 +++++++++++ errors/wrap.go | 20 ++++++ fs/deviceid_unix.go | 30 +++++++++ fs/deviceid_windows.go | 15 +++++ fs/doc.go | 3 + fs/file.go | 145 +++++++++++++++++++++++++++++++++++++++++ fs/file_linux.go | 58 +++++++++++++++++ fs/file_nonlinux.go | 15 +++++ handlers.go | 2 +- htpasswd.go | 2 +- 11 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 errors/doc.go create mode 100644 errors/fatal.go create mode 100644 errors/wrap.go create mode 100644 fs/deviceid_unix.go create mode 100644 fs/deviceid_windows.go create mode 100644 fs/doc.go create mode 100644 fs/file.go create mode 100644 fs/file_linux.go create mode 100644 fs/file_nonlinux.go diff --git a/errors/doc.go b/errors/doc.go new file mode 100644 index 0000000..9f63cf9 --- /dev/null +++ b/errors/doc.go @@ -0,0 +1,2 @@ +// Package errors provides custom error types used within restic. +package errors diff --git a/errors/fatal.go b/errors/fatal.go new file mode 100644 index 0000000..b61822c --- /dev/null +++ b/errors/fatal.go @@ -0,0 +1,38 @@ +package errors + +import "fmt" + +// fatalError is an error that should be printed to the user, then the program +// should exit with an error code. +type fatalError string + +func (e fatalError) Error() string { + return string(e) +} + +func (e fatalError) Fatal() bool { + return true +} + +// Fataler is an error which should be printed to the user directly. +// Afterwards, the program should exit with an error. +type Fataler interface { + Fatal() bool +} + +// IsFatal returns true if err is a fatal message that should be printed to the +// user. Then, the program should exit. +func IsFatal(err error) bool { + e, ok := err.(Fataler) + return ok && e.Fatal() +} + +// Fatal returns a wrapped error which implements the Fataler interface. +func Fatal(s string) error { + return Wrap(fatalError(s), "Fatal") +} + +// Fatalf returns an error which implements the Fataler interface. +func Fatalf(s string, data ...interface{}) error { + return fatalError(fmt.Sprintf(s, data...)) +} diff --git a/errors/wrap.go b/errors/wrap.go new file mode 100644 index 0000000..5906bd6 --- /dev/null +++ b/errors/wrap.go @@ -0,0 +1,20 @@ +package errors + +import "github.com/pkg/errors" + +// Cause returns the cause of an error. +func Cause(err error) error { + return errors.Cause(err) +} + +// New creates a new error based on message. Wrapped so that this package does +// not appear in the stack trace. +var New = errors.New + +// Errorf creates an error based on a format string and values. Wrapped so that +// this package does not appear in the stack trace. +var Errorf = errors.Errorf + +// Wrap wraps an error retrieved from outside of restic. Wrapped so that this +// package does not appear in the stack trace. +var Wrap = errors.Wrap diff --git a/fs/deviceid_unix.go b/fs/deviceid_unix.go new file mode 100644 index 0000000..a872a4f --- /dev/null +++ b/fs/deviceid_unix.go @@ -0,0 +1,30 @@ +// +build !windows + +package fs + +import ( + "os" + "syscall" + + "github.com/zcalusic/restic-server/errors" +) + +// DeviceID extracts the device ID from an os.FileInfo object by casting it +// to syscall.Stat_t +func DeviceID(fi os.FileInfo) (deviceID uint64, err error) { + if fi == nil { + return 0, errors.New("unable to determine device: fi is nil") + } + + if fi.Sys() == nil { + return 0, errors.New("unable to determine device: fi.Sys() is nil") + } + + if st, ok := fi.Sys().(*syscall.Stat_t); ok { + // st.Dev is uint32 on Darwin and uint64 on Linux. Just cast + // everything to uint64. + return uint64(st.Dev), nil + } + + return 0, errors.New("Could not cast to syscall.Stat_t") +} diff --git a/fs/deviceid_windows.go b/fs/deviceid_windows.go new file mode 100644 index 0000000..e4590aa --- /dev/null +++ b/fs/deviceid_windows.go @@ -0,0 +1,15 @@ +// +build windows + +package fs + +import ( + "os" + + "github.com/zcalusic/restic-server/errors" +) + +// DeviceID extracts the device ID from an os.FileInfo object by casting it +// to syscall.Stat_t +func DeviceID(fi os.FileInfo) (deviceID uint64, err error) { + return 0, errors.New("Device IDs are not supported on Windows") +} diff --git a/fs/doc.go b/fs/doc.go new file mode 100644 index 0000000..29bfa66 --- /dev/null +++ b/fs/doc.go @@ -0,0 +1,3 @@ +// Package fs implements an OS independend abstraction of a file system +// suitable for backup purposes. +package fs diff --git a/fs/file.go b/fs/file.go new file mode 100644 index 0000000..2f6c500 --- /dev/null +++ b/fs/file.go @@ -0,0 +1,145 @@ +package fs + +import ( + "io" + "os" + "path/filepath" + "runtime" + "strings" +) + +// File is an open file on a file system. +type File interface { + io.Reader + io.Writer + io.Closer + + Fd() uintptr + Readdirnames(n int) ([]string, error) + Readdir(int) ([]os.FileInfo, error) + Seek(int64, int) (int64, error) + Stat() (os.FileInfo, error) +} + +// fixpath returns an absolute path on windows, so restic can open long file +// names. +func fixpath(name string) string { + if runtime.GOOS == "windows" { + abspath, err := filepath.Abs(name) + if err == nil { + // Check if \\?\UNC\ already exist + if strings.HasPrefix(abspath, `\\?\UNC\`) { + return abspath + } + // Check if \\?\ already exist + if strings.HasPrefix(abspath, `\\?\`) { + return abspath + } + // Check if path starts with \\ + if strings.HasPrefix(abspath, `\\`) { + return strings.Replace(abspath, `\\`, `\\?\UNC\`, 1) + } + // Normal path + return `\\?\` + abspath + } + } + return name +} + +// Chmod changes the mode of the named file to mode. +func Chmod(name string, mode os.FileMode) error { + return os.Chmod(fixpath(name), mode) +} + +// Mkdir creates a new directory with the specified name and permission bits. +// If there is an error, it will be of type *PathError. +func Mkdir(name string, perm os.FileMode) error { + return os.Mkdir(fixpath(name), perm) +} + +// MkdirAll creates a directory named path, +// along with any necessary parents, and returns nil, +// or else returns an error. +// The permission bits perm are used for all +// directories that MkdirAll creates. +// If path is already a directory, MkdirAll does nothing +// and returns nil. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(fixpath(path), perm) +} + +// Readlink returns the destination of the named symbolic link. +// If there is an error, it will be of type *PathError. +func Readlink(name string) (string, error) { + return os.Readlink(fixpath(name)) +} + +// Remove removes the named file or directory. +// If there is an error, it will be of type *PathError. +func Remove(name string) error { + return os.Remove(fixpath(name)) +} + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) error { + return os.RemoveAll(fixpath(path)) +} + +// Rename renames (moves) oldpath to newpath. +// If newpath already exists, Rename replaces it. +// OS-specific restrictions may apply when oldpath and newpath are in different directories. +// If there is an error, it will be of type *LinkError. +func Rename(oldpath, newpath string) error { + return os.Rename(fixpath(oldpath), fixpath(newpath)) +} + +// Symlink creates newname as a symbolic link to oldname. +// If there is an error, it will be of type *LinkError. +func Symlink(oldname, newname string) error { + return os.Symlink(fixpath(oldname), fixpath(newname)) +} + +// Stat returns a FileInfo structure describing the named file. +// If there is an error, it will be of type *PathError. +func Stat(name string) (os.FileInfo, error) { + return os.Stat(fixpath(name)) +} + +// Lstat returns the FileInfo structure describing the named file. +// If the file is a symbolic link, the returned FileInfo +// describes the symbolic link. Lstat makes no attempt to follow the link. +// If there is an error, it will be of type *PathError. +func Lstat(name string) (os.FileInfo, error) { + return os.Lstat(fixpath(name)) +} + +// Create creates the named file with mode 0666 (before umask), truncating +// it if it already exists. If successful, methods on the returned +// File can be used for I/O; the associated file descriptor has mode +// O_RDWR. +// If there is an error, it will be of type *PathError. +func Create(name string) (*os.File, error) { + return os.Create(fixpath(name)) +} + +// OpenFile is the generalized open call; most users will use Open +// or Create instead. It opens the named file with specified flag +// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, +// methods on the returned File can be used for I/O. +// If there is an error, it will be of type *PathError. +func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) { + return os.OpenFile(fixpath(name), flag, perm) +} + +// Walk walks the file tree rooted at root, calling walkFn for each file or +// directory in the tree, including root. All errors that arise visiting files +// and directories are filtered by walkFn. The files are walked in lexical +// order, which makes the output deterministic but means that for very +// large directories Walk can be inefficient. +// Walk does not follow symbolic links. +func Walk(root string, walkFn filepath.WalkFunc) error { + return filepath.Walk(fixpath(root), walkFn) +} diff --git a/fs/file_linux.go b/fs/file_linux.go new file mode 100644 index 0000000..95b2e64 --- /dev/null +++ b/fs/file_linux.go @@ -0,0 +1,58 @@ +// +build linux,go1.4 + +package fs + +import ( + "os" + "syscall" + + "github.com/zcalusic/restic-server/errors" + "golang.org/x/sys/unix" +) + +// Open opens a file for reading, without updating the atime and without caching data on read. +func Open(name string) (File, error) { + file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_NOATIME, 0) + if os.IsPermission(errors.Cause(err)) { + file, err = os.OpenFile(name, os.O_RDONLY, 0) + } + return &nonCachingFile{File: file}, err +} + +// nonCachingFile wraps an *os.File and calls fadvise() to instantly forget +// data that has been read or written. +type nonCachingFile struct { + *os.File + readOffset int64 +} + +func (f *nonCachingFile) Read(p []byte) (int, error) { + n, err := f.File.Read(p) + + if n > 0 { + ferr := unix.Fadvise(int(f.File.Fd()), f.readOffset, int64(n), unix.FADV_DONTNEED) + + f.readOffset += int64(n) + + if err == nil { + err = ferr + } + } + + return n, err +} + +// ClearCache syncs and then removes the file's content from the OS cache. +func ClearCache(file File) error { + f, ok := file.(*os.File) + if !ok { + panic("ClearCache called for file not *os.File") + } + + err := f.Sync() + if err != nil { + return err + } + + return unix.Fadvise(int(f.Fd()), 0, 0, unix.FADV_DONTNEED) +} diff --git a/fs/file_nonlinux.go b/fs/file_nonlinux.go new file mode 100644 index 0000000..6723480 --- /dev/null +++ b/fs/file_nonlinux.go @@ -0,0 +1,15 @@ +// +build !linux !go1.4 + +package fs + +import "os" + +// Open opens a file for reading. +func Open(name string) (File, error) { + return os.OpenFile(fixpath(name), os.O_RDONLY, 0) +} + +// ClearCache syncs and then removes the file's content from the OS cache. +func ClearCache(f File) error { + return nil +} diff --git a/handlers.go b/handlers.go index 3351fdc..f53d5c4 100644 --- a/handlers.go +++ b/handlers.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "restic/fs" + "github.com/zcalusic/restic-server/fs" ) // Context contains repository meta-data. diff --git a/htpasswd.go b/htpasswd.go index 6ec6704..b0a60c0 100644 --- a/htpasswd.go +++ b/htpasswd.go @@ -33,7 +33,7 @@ import ( "io" "log" - "restic/fs" + "github.com/zcalusic/restic-server/fs" ) // lookup passwords in a htpasswd file