mirror of
https://github.com/kopia/kopia.git
synced 2026-03-12 03:06:31 -04:00
The new files policy oneFileSystem ignores files that are mounted to other filesystems similarly to tar's --one-file-system switch. For example, if this is enabled, backing up / should now automatically ignore /dev, /proc, etc, so the directory entries themselves don't appear in the backup. The value of the policy is 'false' by default. This is implemented by adding a non-windows-field Device (of type DeviceInfo, reflecting the implementation of Owner) to the Entry interface. DeviceInfo holds the dev and rdev acquired with stat (same way as with Owner), but in addition to that it also holds the same values for the parent directory. It would seem that doing this in some other way, ie. in ReadDir, would require modifying the ReadDir interface which seems a too large modification for a feature this small. This change introduces a duplication of 'stat' call to the files, as the Owner feature already does a separate call. I doubt the performance implications are noticeable, though with some refactoring both Owner and Device fields could be filled in in one go. Filling in the field has been placed in fs/localfs/localfs.go where entryFromChildFileInfo has acquired a third parameter giving the the parent entry. From that information the Device of the parent is retrieved, to be passed off to platformSpecificDeviceInfo which does the rest of the paperwork. Other fs implementations just put in the default values. The Dev and Rdev fields returned by the 'stat' call have different sizes on different platforms, but for convenience they are internally handled the same. The conversion is done with local_fs_32bit.go and local_fs_64bit.go which are conditionally compiled on different platforms. Finally the actual check of the condition is in ignorefs.go function shouldIncludeByDevice which is analoguous to the other similarly named functions. Co-authored-by: Erkki Seppälä <flux@inside.org>
190 lines
4.5 KiB
Go
190 lines
4.5 KiB
Go
package fs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// Entry represents a filesystem entry, which can be Directory, File, or Symlink.
|
|
type Entry interface {
|
|
os.FileInfo
|
|
Owner() OwnerInfo
|
|
Device() DeviceInfo
|
|
}
|
|
|
|
// OwnerInfo describes owner of a filesystem entry.
|
|
type OwnerInfo struct {
|
|
UserID uint32
|
|
GroupID uint32
|
|
}
|
|
|
|
// DeviceInfo describes the device this filesystem entry is on.
|
|
type DeviceInfo struct {
|
|
Dev uint64
|
|
Rdev uint64
|
|
}
|
|
|
|
// Entries is a list of entries sorted by name.
|
|
type Entries []Entry
|
|
|
|
// Reader allows reading from a file and retrieving its up-to-date file info.
|
|
type Reader interface {
|
|
io.ReadCloser
|
|
io.Seeker
|
|
|
|
Entry() (Entry, error)
|
|
}
|
|
|
|
// File represents an entry that is a file.
|
|
type File interface {
|
|
Entry
|
|
Open(ctx context.Context) (Reader, error)
|
|
}
|
|
|
|
// Directory represents contents of a directory.
|
|
type Directory interface {
|
|
Entry
|
|
Child(ctx context.Context, name string) (Entry, error)
|
|
Readdir(ctx context.Context) (Entries, error)
|
|
}
|
|
|
|
// DirectoryWithSummary is optionally implemented by Directory that provide summary.
|
|
type DirectoryWithSummary interface {
|
|
Summary(ctx context.Context) (*DirectorySummary, error)
|
|
}
|
|
|
|
// ErrEntryNotFound is returned when an entry is not found.
|
|
var ErrEntryNotFound = errors.New("entry not found")
|
|
|
|
// ReadDirAndFindChild reads all entries from a directory and returns one by name.
|
|
// This is a convenience function that may be helpful in implementations of Directory.Child().
|
|
func ReadDirAndFindChild(ctx context.Context, d Directory, name string) (Entry, error) {
|
|
children, err := d.Readdir(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e := children.FindByName(name)
|
|
if e == nil {
|
|
return nil, ErrEntryNotFound
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
// MaxFailedEntriesPerDirectorySummary is the maximum number of failed entries per directory summary.
|
|
const MaxFailedEntriesPerDirectorySummary = 10
|
|
|
|
// EntryWithError describes error encountered when processing an entry.
|
|
type EntryWithError struct {
|
|
EntryPath string `json:"path"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// DirectorySummary represents summary information about a directory.
|
|
type DirectorySummary struct {
|
|
TotalFileSize int64 `json:"size"`
|
|
TotalFileCount int64 `json:"files"`
|
|
TotalSymlinkCount int64 `json:"symlinks"`
|
|
TotalDirCount int64 `json:"dirs"`
|
|
MaxModTime time.Time `json:"maxTime"`
|
|
IncompleteReason string `json:"incomplete,omitempty"`
|
|
|
|
// number of failed files
|
|
NumFailed int `json:"numFailed"`
|
|
|
|
// first 10 failed entries
|
|
FailedEntries []*EntryWithError `json:"errors,omitempty"`
|
|
}
|
|
|
|
// Clone clones given directory summary.
|
|
func (s *DirectorySummary) Clone() DirectorySummary {
|
|
res := *s
|
|
|
|
res.FailedEntries = append([]*EntryWithError(nil), s.FailedEntries...)
|
|
|
|
return res
|
|
}
|
|
|
|
// Symlink represents a symbolic link entry.
|
|
type Symlink interface {
|
|
Entry
|
|
Readlink(ctx context.Context) (string, error)
|
|
}
|
|
|
|
// FindByName returns an entry with a given name, or nil if not found.
|
|
func (e Entries) FindByName(n string) Entry {
|
|
i := sort.Search(
|
|
len(e),
|
|
func(i int) bool {
|
|
return e[i].Name() >= n
|
|
},
|
|
)
|
|
if i < len(e) && e[i].Name() == n {
|
|
return e[i]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update returns a copy of Entries with the provided entry included, by either replacing
|
|
// existing entry with the same name or inserted in the appropriate place to maintain sorted order.
|
|
func (e Entries) Update(newEntry Entry) Entries {
|
|
name := newEntry.Name()
|
|
pos := sort.Search(len(e), func(i int) bool {
|
|
return e[i].Name() >= name
|
|
})
|
|
|
|
// append at the end
|
|
if pos >= len(e) {
|
|
return append(append(Entries(nil), e...), newEntry)
|
|
}
|
|
|
|
if e[pos].Name() == name {
|
|
if pos > 0 {
|
|
return append(append(append(Entries(nil), e[0:pos]...), newEntry), e[pos+1:]...)
|
|
}
|
|
|
|
return append(append(Entries(nil), newEntry), e[pos+1:]...)
|
|
}
|
|
|
|
if pos > 0 {
|
|
return append(append(append(Entries(nil), e[0:pos]...), newEntry), e[pos:]...)
|
|
}
|
|
|
|
return append(append(Entries(nil), newEntry), e[pos:]...)
|
|
}
|
|
|
|
// Remove returns a copy of Entries with the provided entry removed, while maintaining sorted order.
|
|
func (e Entries) Remove(name string) Entries {
|
|
pos := sort.Search(len(e), func(i int) bool {
|
|
return e[i].Name() >= name
|
|
})
|
|
|
|
// not found
|
|
if pos >= len(e) {
|
|
return e
|
|
}
|
|
|
|
if e[pos].Name() != name {
|
|
return e
|
|
}
|
|
|
|
if pos > 0 {
|
|
return append(append(Entries(nil), e[0:pos]...), e[pos+1:]...)
|
|
}
|
|
|
|
return append(Entries(nil), e[pos+1:]...)
|
|
}
|
|
|
|
// Sort sorts the entries by name.
|
|
func (e Entries) Sort() {
|
|
sort.Slice(e, func(i, j int) bool {
|
|
return e[i].Name() < e[j].Name()
|
|
})
|
|
}
|