mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-06 13:44:40 -06:00
Compare commits
1 Commits
jkl-dev
...
b2749b6e20
| Author | SHA1 | Date | |
|---|---|---|---|
| b2749b6e20 |
@@ -187,7 +187,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
|
|||||||
|
|
||||||
// If the listing operation is fast and this is an initial backup, list all chunks and
|
// If the listing operation is fast and this is an initial backup, list all chunks and
|
||||||
// put them in the cache.
|
// put them in the cache.
|
||||||
if manager.storage.IsFastListing() && remoteSnapshot.Revision == 0 {
|
if (manager.storage.IsFastListing() && remoteSnapshot.Revision == 0) {
|
||||||
LOG_INFO("BACKUP_LIST", "Listing all chunks")
|
LOG_INFO("BACKUP_LIST", "Listing all chunks")
|
||||||
allChunks, _ := manager.SnapshotManager.ListAllFiles(manager.storage, "chunks/")
|
allChunks, _ := manager.SnapshotManager.ListAllFiles(manager.storage, "chunks/")
|
||||||
|
|
||||||
@@ -705,34 +705,11 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
|
|
||||||
type hardLinkEntry struct {
|
type hardLinkEntry struct {
|
||||||
entry *Entry
|
entry *Entry
|
||||||
willExist bool
|
willDownload bool
|
||||||
}
|
}
|
||||||
var hardLinkTable []hardLinkEntry
|
var hardLinkTable []hardLinkEntry
|
||||||
var hardLinks []*Entry
|
var hardLinks []*Entry
|
||||||
|
|
||||||
restoreHardlink := func(entry *Entry, fullPath string) bool {
|
|
||||||
if entry.IsHardlinkRoot() {
|
|
||||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
|
||||||
} else if entry.IsHardlinkedFrom() {
|
|
||||||
i, err := entry.GetHardlinkId()
|
|
||||||
if err != nil {
|
|
||||||
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", entry.Path, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !hardLinkTable[i].willExist {
|
|
||||||
hardLinkTable[i] = hardLinkEntry{entry, true}
|
|
||||||
} else {
|
|
||||||
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
|
|
||||||
LOG_INFO("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
|
|
||||||
if err := MakeHardlink(sourcePath, fullPath); err != nil {
|
|
||||||
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s %v", fullPath, sourcePath, err)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for remoteEntry := range remoteListingChannel {
|
for remoteEntry := range remoteListingChannel {
|
||||||
|
|
||||||
if remoteEntry.IsHardlinkRoot() {
|
if remoteEntry.IsHardlinkRoot() {
|
||||||
@@ -775,16 +752,12 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullPath := joinPath(top, remoteEntry.Path)
|
fullPath := joinPath(top, remoteEntry.Path)
|
||||||
|
|
||||||
if remoteEntry.IsLink() {
|
if remoteEntry.IsLink() {
|
||||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
if stat, _ := os.Lstat(fullPath); stat != nil {
|
||||||
if stat.Mode()&os.ModeSymlink != 0 {
|
if stat.Mode()&os.ModeSymlink != 0 {
|
||||||
isRegular, link, err := Readlink(fullPath)
|
isRegular, link, err := Readlink(fullPath)
|
||||||
if err == nil && link == remoteEntry.Link && !isRegular {
|
if err == nil && link == remoteEntry.Link && !isRegular {
|
||||||
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
||||||
if remoteEntry.IsHardlinkRoot() {
|
|
||||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,16 +771,11 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
os.Remove(fullPath)
|
os.Remove(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if restoreHardlink(remoteEntry, fullPath) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
|
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
|
||||||
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
|
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
||||||
|
|
||||||
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path)
|
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path)
|
||||||
} else if remoteEntry.IsDir() {
|
} else if remoteEntry.IsDir() {
|
||||||
|
|
||||||
@@ -829,48 +797,22 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
}
|
}
|
||||||
remoteEntry.RestoreEarlyDirFlags(fullPath)
|
remoteEntry.RestoreEarlyDirFlags(fullPath)
|
||||||
directoryEntries = append(directoryEntries, remoteEntry)
|
directoryEntries = append(directoryEntries, remoteEntry)
|
||||||
} else if remoteEntry.IsSpecial() {
|
|
||||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
|
||||||
if remoteEntry.IsSameSpecial(stat) {
|
|
||||||
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
|
||||||
if remoteEntry.IsHardlinkRoot() {
|
|
||||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !overwrite {
|
|
||||||
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
|
|
||||||
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
os.Remove(fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if restoreHardlink(remoteEntry, fullPath) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := remoteEntry.RestoreSpecial(fullPath); err != nil {
|
|
||||||
LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
|
|
||||||
} else {
|
} else {
|
||||||
if remoteEntry.IsHardlinkRoot() {
|
if remoteEntry.IsHardlinkRoot() {
|
||||||
hardLinkTable[len(hardLinkTable)-1].willExist = true
|
hardLinkTable[len(hardLinkTable)-1] = hardLinkEntry{remoteEntry, true}
|
||||||
} else if remoteEntry.IsHardlinkedFrom() {
|
} else if remoteEntry.IsHardlinkedFrom() {
|
||||||
i, err := remoteEntry.GetHardlinkId()
|
i, err := strconv.ParseUint(remoteEntry.Link, 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", remoteEntry.Path, err)
|
LOG_ERROR("RESTORE_HARDLINK", "Decode error in hardlink entry, expected hex int, got %s", remoteEntry.Link)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if !hardLinkTable[i].willExist {
|
if !hardLinkTable[i].willDownload {
|
||||||
hardLinkTable[i] = hardLinkEntry{remoteEntry, true}
|
hardLinkTable[i] = hardLinkEntry{remoteEntry, true}
|
||||||
} else {
|
} else {
|
||||||
hardLinks = append(hardLinks, remoteEntry)
|
hardLinks = append(hardLinks, remoteEntry)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't download files here since fileEntries needs to be sorted
|
// We can't download files here since fileEntries needs to be sorted
|
||||||
fileEntries = append(fileEntries, remoteEntry)
|
fileEntries = append(fileEntries, remoteEntry)
|
||||||
totalFileSize += remoteEntry.Size
|
totalFileSize += remoteEntry.Size
|
||||||
@@ -991,32 +933,11 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, linkEntry := range hardLinks {
|
for _, linkEntry := range hardLinks {
|
||||||
|
i, _ := strconv.ParseUint(linkEntry.Link, 16, 64)
|
||||||
i, _ := linkEntry.GetHardlinkId()
|
|
||||||
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
|
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
|
||||||
fullPath := joinPath(top, linkEntry.Path)
|
fullPath := joinPath(top, linkEntry.Path)
|
||||||
|
LOG_INFO("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
|
||||||
if stat, _ := os.Lstat(fullPath); stat != nil {
|
if err := os.Link(sourcePath, fullPath); err != nil {
|
||||||
sourceStat, _ := os.Lstat(sourcePath)
|
|
||||||
if os.SameFile(stat, sourceStat) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceStat == nil {
|
|
||||||
LOG_WERROR(allowFailures, "RESTORE_HARDLINK",
|
|
||||||
"Target %s for hardlink %s is missing", sourcePath, linkEntry.Path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !overwrite {
|
|
||||||
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
|
|
||||||
"File %s already exists. Please specify the -overwrite option to overwrite", linkEntry.Path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
os.Remove(fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
|
|
||||||
if err := MakeHardlink(sourcePath, fullPath); err != nil {
|
|
||||||
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s", fullPath, sourcePath)
|
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s", fullPath, sourcePath)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1206,10 +1127,10 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
|
|||||||
entry.StartChunk -= lastEndChunk
|
entry.StartChunk -= lastEndChunk
|
||||||
lastEndChunk = entry.EndChunk
|
lastEndChunk = entry.EndChunk
|
||||||
entry.EndChunk = delta
|
entry.EndChunk = delta
|
||||||
} else if entry.IsHardlinkedFrom() && !entry.IsLink() {
|
} else if entry.IsHardlinkedFrom() {
|
||||||
i, err := entry.GetHardlinkId()
|
i, err := strconv.ParseUint(entry.Link, 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("SNAPSHOT_UPLOAD", "Decode error for hardlinked entry %s, %v", entry.Link, err)
|
LOG_ERROR("SNAPSHOT_UPLOAD", "Decode error in hardlink entry, expected hex int, got %s", entry.Link)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,7 +1219,6 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
|
|||||||
// file under the .duplicacy directory and then replaces the existing one. Otherwise, the existing file will be
|
// file under the .duplicacy directory and then replaces the existing one. Otherwise, the existing file will be
|
||||||
// overwritten directly.
|
// overwritten directly.
|
||||||
// Return: true, nil: Restored file;
|
// Return: true, nil: Restored file;
|
||||||
//
|
|
||||||
// false, nil: Skipped file;
|
// false, nil: Skipped file;
|
||||||
// false, error: Failure to restore file (only if allowFailures == true)
|
// false, error: Failure to restore file (only if allowFailures == true)
|
||||||
func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool,
|
func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -24,11 +23,6 @@ import (
|
|||||||
"github.com/vmihailenco/msgpack"
|
"github.com/vmihailenco/msgpack"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
entrySymHardLinkRootChunkMarker = -72
|
|
||||||
entrySymHardLinkTargetChunkMarker = -73
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is the hidden directory in the repository for storing various files.
|
// This is the hidden directory in the repository for storing various files.
|
||||||
var DUPLICACY_DIRECTORY = ".duplicacy"
|
var DUPLICACY_DIRECTORY = ".duplicacy"
|
||||||
var DUPLICACY_FILE = ".duplicacy"
|
var DUPLICACY_FILE = ".duplicacy"
|
||||||
@@ -515,36 +509,16 @@ func (entry *Entry) IsLink() bool {
|
|||||||
return entry.Mode&uint32(os.ModeSymlink) != 0
|
return entry.Mode&uint32(os.ModeSymlink) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) IsSpecial() bool {
|
|
||||||
return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) IsFileOrSpecial() bool {
|
|
||||||
return entry.Mode&uint32(os.ModeDir|os.ModeSymlink|os.ModeIrregular) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) IsComplete() bool {
|
func (entry *Entry) IsComplete() bool {
|
||||||
return entry.Size >= 0
|
return entry.Size >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) IsHardlinkedFrom() bool {
|
func (entry *Entry) IsHardlinkedFrom() bool {
|
||||||
return (entry.IsFileOrSpecial() && len(entry.Link) > 0 && entry.Link != "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkTargetChunkMarker)
|
return entry.IsFile() && len(entry.Link) > 0 && entry.Link != "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) IsHardlinkRoot() bool {
|
func (entry *Entry) IsHardlinkRoot() bool {
|
||||||
return (entry.IsFileOrSpecial() && entry.Link == "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkRootChunkMarker)
|
return entry.IsFile() && entry.Link == "/"
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) GetHardlinkId() (int, error) {
|
|
||||||
if entry.IsLink() {
|
|
||||||
if entry.StartChunk != entrySymHardLinkTargetChunkMarker {
|
|
||||||
return 0, errors.New("Symlink entry not marked as hardlinked")
|
|
||||||
}
|
|
||||||
return entry.StartOffset, nil
|
|
||||||
} else {
|
|
||||||
i, err := strconv.ParseUint(entry.Link, 16, 64)
|
|
||||||
return int(i), err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) GetPermissions() os.FileMode {
|
func (entry *Entry) GetPermissions() os.FileMode {
|
||||||
@@ -605,10 +579,6 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
|
||||||
entry.SetAttributesToFile(fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only set the time if the file is not a symlink
|
// Only set the time if the file is not a symlink
|
||||||
if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
|
if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
|
||||||
modifiedTime := time.Unix(entry.Time, 0)
|
modifiedTime := time.Unix(entry.Time, 0)
|
||||||
@@ -619,6 +589,10 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
||||||
|
entry.SetAttributesToFile(fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,44 +781,10 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
|||||||
if f.Name() == DUPLICACY_DIRECTORY {
|
if f.Name() == DUPLICACY_DIRECTORY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if f.Mode()&os.ModeSocket != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := CreateEntryFromFileInfo(f, normalizedPath)
|
entry := CreateEntryFromFileInfo(f, normalizedPath)
|
||||||
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
|
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var linkKey *listEntryLinkKey
|
|
||||||
|
|
||||||
if runtime.GOOS != "windows" && !entry.IsDir() {
|
|
||||||
if stat := f.Sys().(*syscall.Stat_t); stat != nil && stat.Nlink > 1 {
|
|
||||||
k := listEntryLinkKey{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
|
|
||||||
if linkIndex, seen := listingState.linkTable[k]; seen {
|
|
||||||
if linkIndex == -1 {
|
|
||||||
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute (hardlink)", entry.Path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entry.Size = 0
|
|
||||||
if entry.IsLink() {
|
|
||||||
entry.StartChunk = entrySymHardLinkTargetChunkMarker
|
|
||||||
entry.StartOffset = linkIndex
|
|
||||||
} else {
|
|
||||||
entry.Link = strconv.FormatInt(int64(linkIndex), 16)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if entry.IsLink() {
|
|
||||||
entry.StartChunk = entrySymHardLinkRootChunkMarker
|
|
||||||
} else {
|
|
||||||
entry.Link = "/"
|
|
||||||
}
|
|
||||||
listingState.linkTable[k] = -1
|
|
||||||
linkKey = &k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.IsLink() {
|
if entry.IsLink() {
|
||||||
isRegular := false
|
isRegular := false
|
||||||
isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
|
isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
|
||||||
@@ -875,12 +815,30 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
|
|||||||
}
|
}
|
||||||
entry = newEntry
|
entry = newEntry
|
||||||
}
|
}
|
||||||
} else if entry.IsSpecial() {
|
}
|
||||||
if !entry.ReadSpecial(f) {
|
|
||||||
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)
|
if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
|
||||||
|
LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path)
|
||||||
skippedFiles = append(skippedFiles, entry.Path)
|
skippedFiles = append(skippedFiles, entry.Path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var linkKey *listEntryLinkKey
|
||||||
|
|
||||||
|
if stat, ok := f.Sys().(*syscall.Stat_t); entry.IsFile() && ok && stat != nil && stat.Nlink > 1 {
|
||||||
|
k := listEntryLinkKey{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
|
||||||
|
if linkIndex, seen := listingState.linkTable[k]; seen {
|
||||||
|
if linkIndex == -1 {
|
||||||
|
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute (hardlink)", entry.Path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entry.Size = 0
|
||||||
|
entry.Link = strconv.FormatInt(int64(linkIndex), 16)
|
||||||
|
} else {
|
||||||
|
entry.Link = "/"
|
||||||
|
listingState.linkTable[k] = -1
|
||||||
|
linkKey = &k
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.ReadAttributes(top)
|
entry.ReadAttributes(top)
|
||||||
|
|||||||
@@ -111,12 +111,12 @@ func (entryList *EntryList)createOnDiskFile() error {
|
|||||||
// Add an entry to the entry list
|
// Add an entry to the entry list
|
||||||
func (entryList *EntryList)AddEntry(entry *Entry) error {
|
func (entryList *EntryList)AddEntry(entry *Entry) error {
|
||||||
|
|
||||||
if entry.IsFile() {
|
if !entry.IsDir() && !entry.IsLink() {
|
||||||
entryList.NumberOfEntries++
|
entryList.NumberOfEntries++
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entry.IsComplete() {
|
if !entry.IsComplete() {
|
||||||
if !entry.IsFile() {
|
if entry.IsDir() || entry.IsLink() {
|
||||||
entry.Size = 0
|
entry.Size = 0
|
||||||
} else {
|
} else {
|
||||||
modifiedEntry := ModifiedEntry {
|
modifiedEntry := ModifiedEntry {
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ package duplicacy
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/gilbertchen/gopass"
|
"github.com/gilbertchen/gopass"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
@@ -276,6 +275,7 @@ func SavePassword(preference Preference, passwordType string, password string) {
|
|||||||
// The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss,
|
// The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss,
|
||||||
// Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd'
|
// Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd'
|
||||||
// against '*ccd', and the version here fixed that issue.
|
// against '*ccd', and the version here fixed that issue.
|
||||||
|
//
|
||||||
func matchPattern(text string, pattern string) bool {
|
func matchPattern(text string, pattern string) bool {
|
||||||
|
|
||||||
textLength := len(text)
|
textLength := len(text)
|
||||||
@@ -474,34 +474,3 @@ func PrintMemoryUsage() {
|
|||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) dump() map[string]interface{} {
|
|
||||||
|
|
||||||
object := make(map[string]interface{})
|
|
||||||
|
|
||||||
object["path"] = entry.Path
|
|
||||||
object["size"] = entry.Size
|
|
||||||
object["time"] = entry.Time
|
|
||||||
object["mode"] = entry.Mode
|
|
||||||
object["hash"] = entry.Hash
|
|
||||||
object["link"] = entry.Link
|
|
||||||
|
|
||||||
object["content"] = fmt.Sprintf("%d:%d:%d:%d",
|
|
||||||
entry.StartChunk, entry.StartOffset, entry.EndChunk, entry.EndOffset)
|
|
||||||
|
|
||||||
if entry.UID != -1 && entry.GID != -1 {
|
|
||||||
object["uid"] = entry.UID
|
|
||||||
object["gid"] = entry.GID
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
|
|
||||||
object["attributes"] = entry.Attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) dumpString() string {
|
|
||||||
data, _ := json.Marshal(entry.dump())
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
package duplicacy
|
package duplicacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"bytes"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/pkg/xattr"
|
"github.com/pkg/xattr"
|
||||||
@@ -25,8 +25,6 @@ func (entry *Entry) ReadAttributes(top string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entry.IsSpecial() {
|
|
||||||
attributes, _ := xattr.LList(fullPath)
|
attributes, _ := xattr.LList(fullPath)
|
||||||
if len(attributes) > 0 {
|
if len(attributes) > 0 {
|
||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
@@ -37,14 +35,12 @@ func (entry *Entry) ReadAttributes(top string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err := entry.readFileFlags(fileInfo); err != nil {
|
if err := entry.readFileFlags(fileInfo); err != nil {
|
||||||
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
|
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
||||||
if !entry.IsSpecial() {
|
|
||||||
names, _ := xattr.LList(fullPath)
|
names, _ := xattr.LList(fullPath)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
newAttribute, found := (*entry.Attributes)[name]
|
newAttribute, found := (*entry.Attributes)[name]
|
||||||
@@ -65,7 +61,6 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
|
|||||||
}
|
}
|
||||||
xattr.LSet(fullPath, name, attribute)
|
xattr.LSet(fullPath, name, attribute)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err := entry.restoreLateFileFlags(fullPath); err != nil {
|
if err := entry.restoreLateFileFlags(fullPath); err != nil {
|
||||||
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
||||||
}
|
}
|
||||||
@@ -92,3 +87,4 @@ func (entry *Entry) RestoreEarlyDirFlags(path string) error {
|
|||||||
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
package duplicacy
|
package duplicacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"strings"
|
||||||
|
"encoding/binary"
|
||||||
)
|
)
|
||||||
|
|
||||||
func excludedByAttribute(attributes map[string][]byte) bool {
|
func excludedByAttribute(attributes map[string][]byte) bool {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/pkg/xattr"
|
"github.com/pkg/xattr"
|
||||||
)
|
)
|
||||||
@@ -84,17 +84,15 @@ func (x xattrHandle) remove(name string) error {
|
|||||||
if x.f != nil {
|
if x.f != nil {
|
||||||
return xattr.FRemove(x.f, name)
|
return xattr.FRemove(x.f, name)
|
||||||
} else {
|
} else {
|
||||||
return xattr.LRemove(x.fullPath, name)
|
return xattr.LSet(x.fullPath, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) ReadAttributes(top string) {
|
func (entry *Entry) ReadAttributes(top string) {
|
||||||
fullPath := filepath.Join(top, entry.Path)
|
x := xattrHandle{nil, filepath.Join(top, entry.Path)}
|
||||||
x := xattrHandle{nil, fullPath}
|
|
||||||
|
|
||||||
if !entry.IsLink() {
|
if !entry.IsLink() {
|
||||||
var err error
|
x.f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
|
||||||
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: We really should return errors for failure to read
|
// FIXME: We really should return errors for failure to read
|
||||||
return
|
return
|
||||||
@@ -107,7 +105,7 @@ func (entry *Entry) ReadAttributes(top string) {
|
|||||||
entry.Attributes = &map[string][]byte{}
|
entry.Attributes = &map[string][]byte{}
|
||||||
}
|
}
|
||||||
for _, name := range attributes {
|
for _, name := range attributes {
|
||||||
attribute, err := x.get(name)
|
attribute, err := x.get(f, name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
(*entry.Attributes)[name] = attribute
|
(*entry.Attributes)[name] = attribute
|
||||||
}
|
}
|
||||||
@@ -118,14 +116,13 @@ func (entry *Entry) ReadAttributes(top string) {
|
|||||||
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
|
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x.f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
||||||
x := xattrHandle{nil, fullPath}
|
x := xattrHandle{nil, fullPath}
|
||||||
if !entry.IsLink() {
|
if !entry.IsLink() {
|
||||||
var err error
|
x.f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||||
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -153,11 +150,11 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
|
|||||||
x.set(name, attribute)
|
x.set(name, attribute)
|
||||||
}
|
}
|
||||||
if entry.IsFile() || entry.IsDir() {
|
if entry.IsFile() || entry.IsDir() {
|
||||||
if err := entry.restoreLateFileFlags(x.f); err != nil {
|
if err := entry.restoreLateFileFlags(f); err != nil {
|
||||||
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x.f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) readFileFlags(f *os.File) error {
|
func (entry *Entry) readFileFlags(f *os.File) error {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Readlink(path string) (isRegular bool, s string, err error) {
|
func Readlink(path string) (isRegular bool, s string, err error) {
|
||||||
@@ -46,52 +44,6 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) ReadSpecial(fileInfo os.FileInfo) bool {
|
|
||||||
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
stat := fileInfo.Sys().(*syscall.Stat_t)
|
|
||||||
if stat == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
entry.Size = 0
|
|
||||||
rdev := uint64(stat.Rdev)
|
|
||||||
entry.StartChunk = int(rdev & 0xFFFFFFFF)
|
|
||||||
entry.StartOffset = int(rdev >> 32)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) GetRdev() uint64 {
|
|
||||||
return uint64(entry.StartChunk) | uint64(entry.StartOffset)<<32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) RestoreSpecial(fullPath string) error {
|
|
||||||
mode := entry.Mode & uint32(fileModeMask)
|
|
||||||
|
|
||||||
if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
|
|
||||||
mode |= syscall.S_IFIFO
|
|
||||||
} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
|
|
||||||
mode |= syscall.S_IFCHR
|
|
||||||
} else if entry.Mode&uint32(os.ModeDevice) != 0 {
|
|
||||||
mode |= syscall.S_IFBLK
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return syscall.Mknod(fullPath, mode, int(entry.GetRdev()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
|
|
||||||
stat := fileInfo.Sys().(*syscall.Stat_t)
|
|
||||||
if stat == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (uint32(fileInfo.Mode()) == entry.Mode) && (uint64(stat.Rdev) == entry.GetRdev())
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeHardlink(source string, target string) error {
|
|
||||||
return unix.Linkat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinPath(components ...string) string {
|
func joinPath(components ...string) string {
|
||||||
return path.Join(components...)
|
return path.Join(components...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,18 +117,6 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) ReadDeviceNode(fileInfo os.FileInfo) bool {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) RestoreSpecial(fullPath string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeHardlink(source string, target string) error {
|
|
||||||
return os.Link(source, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinPath(components ...string) string {
|
func joinPath(components ...string) string {
|
||||||
|
|
||||||
combinedPath := `\\?\` + filepath.Join(components...)
|
combinedPath := `\\?\` + filepath.Join(components...)
|
||||||
|
|||||||
Reference in New Issue
Block a user