2015-02-15 11:57:09 +00:00
|
|
|
package pipe
|
|
|
|
|
|
|
|
import (
|
2017-06-04 09:16:55 +00:00
|
|
|
"context"
|
2015-02-15 11:57:09 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
2015-03-02 13:48:47 +00:00
|
|
|
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/errors"
|
2016-08-21 15:46:23 +00:00
|
|
|
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/debug"
|
|
|
|
"github.com/restic/restic/internal/fs"
|
2015-02-15 11:57:09 +00:00
|
|
|
)
|
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
type Result interface{}
|
|
|
|
|
|
|
|
type Job interface {
|
|
|
|
Path() string
|
|
|
|
Fullpath() string
|
|
|
|
Error() error
|
|
|
|
Info() os.FileInfo
|
|
|
|
|
|
|
|
Result() chan<- Result
|
|
|
|
}
|
|
|
|
|
2015-02-15 11:57:09 +00:00
|
|
|
type Entry struct {
|
2015-03-07 10:53:32 +00:00
|
|
|
basedir string
|
|
|
|
path string
|
|
|
|
info os.FileInfo
|
|
|
|
error error
|
|
|
|
result chan<- Result
|
|
|
|
|
|
|
|
// points to the old node if available, interface{} is used to prevent
|
|
|
|
// circular import
|
|
|
|
Node interface{}
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
func (e Entry) Path() string { return e.path }
|
|
|
|
func (e Entry) Fullpath() string { return filepath.Join(e.basedir, e.path) }
|
|
|
|
func (e Entry) Error() error { return e.error }
|
|
|
|
func (e Entry) Info() os.FileInfo { return e.info }
|
|
|
|
func (e Entry) Result() chan<- Result { return e.result }
|
|
|
|
|
2015-02-15 11:57:09 +00:00
|
|
|
type Dir struct {
|
2015-03-07 10:53:32 +00:00
|
|
|
basedir string
|
|
|
|
path string
|
|
|
|
error error
|
|
|
|
info os.FileInfo
|
2015-02-15 11:57:09 +00:00
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
Entries [](<-chan Result)
|
|
|
|
result chan<- Result
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
func (e Dir) Path() string { return e.path }
|
|
|
|
func (e Dir) Fullpath() string { return filepath.Join(e.basedir, e.path) }
|
|
|
|
func (e Dir) Error() error { return e.error }
|
|
|
|
func (e Dir) Info() os.FileInfo { return e.info }
|
|
|
|
func (e Dir) Result() chan<- Result { return e.result }
|
|
|
|
|
2015-02-15 11:57:09 +00:00
|
|
|
// readDirNames reads the directory named by dirname and returns
|
|
|
|
// a sorted list of directory entries.
|
|
|
|
// taken from filepath/path.go
|
|
|
|
func readDirNames(dirname string) ([]string, error) {
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
f, err := fs.Open(dirname)
|
2015-02-15 11:57:09 +00:00
|
|
|
if err != nil {
|
2016-08-29 20:16:58 +00:00
|
|
|
return nil, errors.Wrap(err, "Open")
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
names, err := f.Readdirnames(-1)
|
2017-10-25 16:03:55 +00:00
|
|
|
_ = f.Close()
|
2015-02-15 11:57:09 +00:00
|
|
|
if err != nil {
|
2016-08-29 20:16:58 +00:00
|
|
|
return nil, errors.Wrap(err, "Readdirnames")
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
sort.Strings(names)
|
|
|
|
return names, nil
|
|
|
|
}
|
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
// SelectFunc returns true for all items that should be included (files and
|
|
|
|
// dirs). If false is returned, files are ignored and dirs are not even walked.
|
|
|
|
type SelectFunc func(item string, fi os.FileInfo) bool
|
|
|
|
|
2017-06-04 09:16:55 +00:00
|
|
|
func walk(ctx context.Context, basedir, dir string, selectFunc SelectFunc, jobs chan<- Job, res chan<- Result) (excluded bool) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("start on %q, basedir %q", dir, basedir)
|
2015-11-06 18:41:57 +00:00
|
|
|
|
|
|
|
relpath, err := filepath.Rel(basedir, dir)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
info, err := fs.Lstat(dir)
|
2015-02-15 11:57:09 +00:00
|
|
|
if err != nil {
|
2016-08-29 20:16:58 +00:00
|
|
|
err = errors.Wrap(err, "Lstat")
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("error for %v: %v, res %p", dir, err, res)
|
2015-11-06 18:41:57 +00:00
|
|
|
select {
|
|
|
|
case jobs <- Dir{basedir: basedir, path: relpath, info: info, error: err, result: res}:
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-11-06 18:41:57 +00:00
|
|
|
}
|
|
|
|
return
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
if !selectFunc(dir, info) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("file %v excluded by filter, res %p", dir, res)
|
2016-02-07 22:22:52 +00:00
|
|
|
excluded = true
|
2015-11-06 18:41:57 +00:00
|
|
|
return
|
2015-07-19 22:13:39 +00:00
|
|
|
}
|
|
|
|
|
2015-02-15 11:57:09 +00:00
|
|
|
if !info.IsDir() {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("sending file job for %v, res %p", dir, res)
|
2015-03-02 18:44:16 +00:00
|
|
|
select {
|
2015-03-07 10:53:32 +00:00
|
|
|
case jobs <- Entry{info: info, basedir: basedir, path: relpath, result: res}:
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-03-02 18:44:16 +00:00
|
|
|
}
|
2015-11-06 18:41:57 +00:00
|
|
|
return
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-11-06 21:38:34 +00:00
|
|
|
debug.RunHook("pipe.readdirnames", dir)
|
2015-03-15 13:24:58 +00:00
|
|
|
names, err := readDirNames(dir)
|
2015-02-15 11:57:09 +00:00
|
|
|
if err != nil {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("Readdirnames(%v) returned error: %v, res %p", dir, err, res)
|
2015-11-06 18:41:57 +00:00
|
|
|
select {
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-11-06 18:41:57 +00:00
|
|
|
case jobs <- Dir{basedir: basedir, path: relpath, info: info, error: err, result: res}:
|
|
|
|
}
|
|
|
|
return
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 13:24:58 +00:00
|
|
|
// Insert breakpoint to allow testing behaviour with vanishing files
|
|
|
|
// between Readdir() and lstat()
|
2015-06-21 15:12:38 +00:00
|
|
|
debug.RunHook("pipe.walk1", relpath)
|
2015-03-15 13:24:58 +00:00
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
entries := make([]<-chan Result, 0, len(names))
|
2015-02-15 11:57:09 +00:00
|
|
|
|
|
|
|
for _, name := range names {
|
2015-03-15 13:24:58 +00:00
|
|
|
subpath := filepath.Join(dir, name)
|
2015-02-15 13:44:54 +00:00
|
|
|
|
Fix 567 (#570)
* Patch for https://github.com/restic/restic/issues/567
Backup also files on windows with longer pathnames than 255 chars (e.g. from node).
as fd0 says "So, as far as I can see, we need to have custom methods for all functions that accept a path, so that on Windows we can substitute the normal (possibly relative) path used within restic by an (absolute) UNC path, and only then call the underlying functions like os.Stat(), os.Lstat(), os.Open() and so on.
I've already thought about adding a generic abstraction for the file system (so we can mock this easier in tests), and this looks like a good opportunity to build it."
* fixed building tests
* Restructured patches
Add Wrapper for filepath.Walk
* using \\?\ requires absolute pathes to be used.
Now all tests run
* used gofmt on the code
* Restructured Code. No patches dir, integrate the file functions into restic/fs/
There is still an issue, because restic.fs.Open has a different api the os.Open, which returns the result of OpenFile, but takes only a string
* Changed the last os.Open() calls to fs.Open() after extending the File interface
* fixed name-clash of restic.fs and fuse.fs detected by travis
* fixed fmt with gofmt
* c&p failure: removed fixpath() call.
* missing include
* fixed includes in linux variant
* Fix for Linux. Fd() is required on File interface
* done gofmt
2016-08-15 19:59:13 +00:00
|
|
|
fi, statErr := fs.Lstat(subpath)
|
2015-07-19 22:13:39 +00:00
|
|
|
if !selectFunc(subpath, fi) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("file %v excluded by filter", subpath)
|
2015-07-19 22:13:39 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
ch := make(chan Result, 1)
|
2015-02-15 13:44:54 +00:00
|
|
|
entries = append(entries, ch)
|
2015-02-15 11:57:09 +00:00
|
|
|
|
2015-07-19 22:13:39 +00:00
|
|
|
if statErr != nil {
|
2016-08-29 20:16:58 +00:00
|
|
|
statErr = errors.Wrap(statErr, "Lstat")
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("sending file job for %v, err %v, res %p", subpath, err, res)
|
2015-03-02 18:44:16 +00:00
|
|
|
select {
|
2015-07-19 22:13:39 +00:00
|
|
|
case jobs <- Entry{info: fi, error: statErr, basedir: basedir, path: filepath.Join(relpath, name), result: ch}:
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-11-06 18:41:57 +00:00
|
|
|
return
|
2015-03-02 18:44:16 +00:00
|
|
|
}
|
|
|
|
continue
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 13:24:58 +00:00
|
|
|
// Insert breakpoint to allow testing behaviour with vanishing files
|
|
|
|
// between walk and open
|
2015-06-21 15:12:38 +00:00
|
|
|
debug.RunHook("pipe.walk2", filepath.Join(relpath, name))
|
2015-03-15 13:24:58 +00:00
|
|
|
|
2017-06-04 09:16:55 +00:00
|
|
|
walk(ctx, basedir, subpath, selectFunc, jobs, ch)
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("sending dirjob for %q, basedir %q, res %p", dir, basedir, res)
|
2015-03-02 18:44:16 +00:00
|
|
|
select {
|
2015-03-07 10:53:32 +00:00
|
|
|
case jobs <- Dir{basedir: basedir, path: relpath, info: info, Entries: entries, result: res}:
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-03-02 18:44:16 +00:00
|
|
|
}
|
2016-02-07 22:22:52 +00:00
|
|
|
|
|
|
|
return
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|
|
|
|
|
2016-02-07 18:30:00 +00:00
|
|
|
// cleanupPath is used to clean a path. For a normal path, a slice with just
|
|
|
|
// the path is returned. For special cases such as "." and "/" the list of
|
|
|
|
// names within those paths is returned.
|
|
|
|
func cleanupPath(path string) ([]string, error) {
|
|
|
|
path = filepath.Clean(path)
|
|
|
|
if filepath.Dir(path) != path {
|
|
|
|
return []string{path}, nil
|
|
|
|
}
|
|
|
|
|
2016-02-07 21:18:37 +00:00
|
|
|
paths, err := readDirNames(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, p := range paths {
|
|
|
|
paths[i] = filepath.Join(path, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return paths, nil
|
2016-02-07 18:30:00 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 13:48:47 +00:00
|
|
|
// Walk sends a Job for each file and directory it finds below the paths. When
|
|
|
|
// the channel done is closed, processing stops.
|
2017-06-04 09:16:55 +00:00
|
|
|
func Walk(ctx context.Context, walkPaths []string, selectFunc SelectFunc, jobs chan<- Job, res chan<- Result) {
|
2016-02-07 18:30:00 +00:00
|
|
|
var paths []string
|
|
|
|
|
|
|
|
for _, p := range walkPaths {
|
|
|
|
ps, err := cleanupPath(p)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Readdirnames(%v): %v, skipping\n", p, err)
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("Readdirnames(%v) returned error: %v, skipping", p, err)
|
2016-02-07 18:30:00 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
paths = append(paths, ps...)
|
|
|
|
}
|
|
|
|
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("start on %v", paths)
|
2015-03-02 13:48:47 +00:00
|
|
|
defer func() {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("output channel closed")
|
2015-03-07 10:53:32 +00:00
|
|
|
close(jobs)
|
2015-03-02 13:48:47 +00:00
|
|
|
}()
|
|
|
|
|
2015-03-07 10:53:32 +00:00
|
|
|
entries := make([]<-chan Result, 0, len(paths))
|
2015-03-02 13:48:47 +00:00
|
|
|
for _, path := range paths {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("start walker for %v", path)
|
2015-03-07 10:53:32 +00:00
|
|
|
ch := make(chan Result, 1)
|
2017-06-04 09:16:55 +00:00
|
|
|
excluded := walk(ctx, filepath.Dir(path), path, selectFunc, jobs, ch)
|
2016-02-07 22:22:52 +00:00
|
|
|
|
|
|
|
if excluded {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("walker for %v done, it was excluded by the filter", path)
|
2016-02-07 22:22:52 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-03-28 15:35:46 +00:00
|
|
|
entries = append(entries, ch)
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("walker for %v done", path)
|
2015-03-02 13:48:47 +00:00
|
|
|
}
|
2015-03-08 19:57:21 +00:00
|
|
|
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("sending root node, res %p", res)
|
2015-03-08 19:57:21 +00:00
|
|
|
select {
|
2017-06-04 09:16:55 +00:00
|
|
|
case <-ctx.Done():
|
2015-11-06 18:41:57 +00:00
|
|
|
return
|
2015-03-08 19:57:21 +00:00
|
|
|
case jobs <- Dir{Entries: entries, result: res}:
|
|
|
|
}
|
|
|
|
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("walker done")
|
2015-03-02 13:48:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Split feeds all elements read from inChan to dirChan and entChan.
|
2015-03-07 10:53:32 +00:00
|
|
|
func Split(inChan <-chan Job, dirChan chan<- Dir, entChan chan<- Entry) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("start")
|
|
|
|
defer debug.Log("done")
|
2015-03-02 13:48:47 +00:00
|
|
|
|
|
|
|
inCh := inChan
|
|
|
|
dirCh := dirChan
|
|
|
|
entCh := entChan
|
|
|
|
|
|
|
|
var (
|
|
|
|
dir Dir
|
|
|
|
ent Entry
|
|
|
|
)
|
|
|
|
|
|
|
|
// deactivate sending until we received at least one job
|
|
|
|
dirCh = nil
|
|
|
|
entCh = nil
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case job, ok := <-inCh:
|
|
|
|
if !ok {
|
|
|
|
// channel is closed
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if job == nil {
|
|
|
|
panic("nil job received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// disable receiving until the current job has been sent
|
|
|
|
inCh = nil
|
|
|
|
|
|
|
|
switch j := job.(type) {
|
|
|
|
case Dir:
|
|
|
|
dir = j
|
|
|
|
dirCh = dirChan
|
|
|
|
case Entry:
|
|
|
|
ent = j
|
|
|
|
entCh = entChan
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unknown job type %v", j))
|
|
|
|
}
|
|
|
|
case dirCh <- dir:
|
|
|
|
// disable sending, re-enable receiving
|
|
|
|
dirCh = nil
|
|
|
|
inCh = inChan
|
|
|
|
case entCh <- ent:
|
|
|
|
// disable sending, re-enable receiving
|
|
|
|
entCh = nil
|
|
|
|
inCh = inChan
|
|
|
|
}
|
|
|
|
}
|
2015-02-15 11:57:09 +00:00
|
|
|
}
|