Compare commits

..

5 Commits

Author SHA1 Message Date
498a948b34 gofmt some files 2023-10-04 01:23:03 -05:00
2679751896 Fix handling of hardlinks and special files
Also, don't attempt xattr ops on special files on the BSD likes.

TODO: Should have a way to allow restore without special files,
otherwise very cumbersome for a regular user.
2023-10-04 01:12:13 -05:00
66a938af67 Support for hardlinks to symlinks
This is a specialized use-case, but it is indeed possible to do this
and can be used if xattrs are attached to the symlink.
2023-10-04 01:12:11 -05:00
015d2200da Fix handling of xattrs with symlinks
Fix Linux, Darwin, and other BSD (untested) to allow proper
handling of xattrs with symlinks. On Linux we cannot use the f*
syscalls for symlinks because symlinks cannot be opened.
File flags must be handled differently on darwin and other BSD due
to the lack of the LCHFLAGS syscall on darwin, and the fact that it
is emulated in libc. However, we do have O_SYMLINK on darwin.
2023-10-04 01:11:20 -05:00
96e7c93a2c Support backup and restore of special files on POSIX style systems
Special files are device nodes and named pipes. The necessity of the
former is clear, the latter is debatable.
In order to preserve backward compatibility, the device number is
encoded in the StartChunk/StartOffset fields of the entry.
2023-10-03 16:26:19 -05:00
9 changed files with 345 additions and 125 deletions

View File

@@ -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
// 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")
allChunks, _ := manager.SnapshotManager.ListAllFiles(manager.storage, "chunks/")
@@ -239,7 +239,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
// List local files
defer CatchLogException()
localSnapshot.ListLocalFiles(shadowTop, manager.nobackupFile, manager.filtersFile, manager.excludeByAttribute, localListingChannel, &skippedDirectories, &skippedFiles)
} ()
}()
go func() {
// List remote files
@@ -261,7 +261,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
})
}
close(remoteListingChannel)
} ()
}()
// Create the local file list
localEntryList, err := CreateEntryList(manager.snapshotID, manager.cachePath, maximumInMemoryEntries)
@@ -275,7 +275,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
var remoteEntry *Entry
remoteListingOK := true
for {
localEntry := <- localListingChannel
localEntry := <-localListingChannel
if localEntry == nil {
break
}
@@ -289,7 +289,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
compareResult = localEntry.Compare(remoteEntry)
} else {
if remoteListingOK {
remoteEntry, remoteListingOK = <- remoteListingChannel
remoteEntry, remoteListingOK = <-remoteListingChannel
}
if !remoteListingOK {
compareResult = -1
@@ -448,7 +448,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
_, found := chunkCache[chunkID]
if found {
if time.Now().Unix() - lastUploadingTime > keepUploadAlive {
if time.Now().Unix()-lastUploadingTime > keepUploadAlive {
LOG_INFO("UPLOAD_KEEPALIVE", "Skip chunk cache to keep connection alive")
found = false
}
@@ -558,7 +558,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
if showStatistics {
LOG_INFO("BACKUP_STATS", "Files: %d total, %s bytes; %d new, %s bytes",
localEntryList.NumberOfEntries - int64(len(skippedFiles)),
localEntryList.NumberOfEntries-int64(len(skippedFiles)),
PrettyNumber(preservedFileSize+uploadedFileSize),
len(localEntryList.ModifiedEntries), PrettyNumber(uploadedFileSize))
@@ -686,7 +686,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
// List local files
defer CatchLogException()
localSnapshot.ListLocalFiles(top, manager.nobackupFile, manager.filtersFile, manager.excludeByAttribute, localListingChannel, nil, nil)
} ()
}()
remoteSnapshot := manager.SnapshotManager.DownloadSnapshot(manager.snapshotID, revision)
manager.SnapshotManager.DownloadSnapshotSequences(remoteSnapshot)
@@ -698,18 +698,41 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
return true
})
close(remoteListingChannel)
} ()
}()
var localEntry *Entry
localListingOK := true
type hardLinkEntry struct {
entry *Entry
willDownload bool
willExist bool
}
var hardLinkTable []hardLinkEntry
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 {
if remoteEntry.IsHardlinkRoot() {
@@ -725,7 +748,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
for {
if localEntry == nil && localListingOK {
localEntry, localListingOK = <- localListingChannel
localEntry, localListingOK = <-localListingChannel
}
if localEntry == nil {
compareResult = 1
@@ -752,12 +775,16 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
}
fullPath := joinPath(top, remoteEntry.Path)
if remoteEntry.IsLink() {
if stat, _ := os.Lstat(fullPath); stat != nil {
if stat.Mode()&os.ModeSymlink != 0 {
isRegular, link, err := Readlink(fullPath)
if err == nil && link == remoteEntry.Link && !isRegular {
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
if remoteEntry.IsHardlinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
}
continue
}
}
@@ -771,11 +798,16 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
os.Remove(fullPath)
}
if restoreHardlink(remoteEntry, fullPath) {
continue
}
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
return 0
}
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path)
} else if remoteEntry.IsDir() {
@@ -797,22 +829,48 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
}
remoteEntry.RestoreEarlyDirFlags(fullPath)
directoryEntries = append(directoryEntries, remoteEntry)
} else {
} 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] = hardLinkEntry{remoteEntry, true}
} else if remoteEntry.IsHardlinkedFrom() {
i, err := strconv.ParseUint(remoteEntry.Link, 16, 64)
if err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Decode error in hardlink entry, expected hex int, got %s", remoteEntry.Link)
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
}
if !hardLinkTable[i].willDownload {
remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
} else {
if remoteEntry.IsHardlinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
} else if remoteEntry.IsHardlinkedFrom() {
i, err := remoteEntry.GetHardlinkId()
if err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", remoteEntry.Path, err)
return 0
}
if !hardLinkTable[i].willExist {
hardLinkTable[i] = hardLinkEntry{remoteEntry, true}
} else {
hardLinks = append(hardLinks, remoteEntry)
continue
}
}
// We can't download files here since fileEntries needs to be sorted
fileEntries = append(fileEntries, remoteEntry)
totalFileSize += remoteEntry.Size
@@ -824,7 +882,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
}
for localListingOK {
localEntry, localListingOK = <- localListingChannel
localEntry, localListingOK = <-localListingChannel
if localEntry != nil {
extraFiles = append(extraFiles, localEntry.Path)
}
@@ -933,11 +991,32 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
}
for _, linkEntry := range hardLinks {
i, _ := strconv.ParseUint(linkEntry.Link, 16, 64)
i, _ := linkEntry.GetHardlinkId()
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
fullPath := joinPath(top, linkEntry.Path)
LOG_INFO("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
if err := os.Link(sourcePath, fullPath); err != nil {
if stat, _ := os.Lstat(fullPath); stat != 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)
return 0
}
@@ -1127,10 +1206,10 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
entry.StartChunk -= lastEndChunk
lastEndChunk = entry.EndChunk
entry.EndChunk = delta
} else if entry.IsHardlinkedFrom() {
i, err := strconv.ParseUint(entry.Link, 16, 64)
} else if entry.IsHardlinkedFrom() && !entry.IsLink() {
i, err := entry.GetHardlinkId()
if err != nil {
LOG_ERROR("SNAPSHOT_UPLOAD", "Decode error in hardlink entry, expected hex int, got %s", entry.Link)
LOG_ERROR("SNAPSHOT_UPLOAD", "Decode error for hardlinked entry %s, %v", entry.Link, err)
return err
}
@@ -1219,6 +1298,7 @@ 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
// overwritten directly.
// Return: true, nil: Restored file;
//
// false, nil: Skipped file;
// 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,
@@ -1797,7 +1877,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
}
}
LOG_INFO("SNAPSHOT_COPY", "Chunks to copy: %d, to skip: %d, total: %d", len(chunksToCopy), len(chunks) - len(chunksToCopy), len(chunks))
LOG_INFO("SNAPSHOT_COPY", "Chunks to copy: %d, to skip: %d, total: %d", len(chunksToCopy), len(chunks)-len(chunksToCopy), len(chunks))
chunkDownloader := CreateChunkOperator(manager.config, manager.storage, nil, false, false, downloadingThreads, false)
@@ -1817,10 +1897,10 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
elapsedTime := time.Now().Sub(startTime).Seconds()
speed := int64(float64(atomic.LoadInt64(&uploadedBytes)) / elapsedTime)
remainingTime := int64(float64(len(chunksToCopy) - chunkIndex - 1) / float64(chunkIndex + 1) * elapsedTime)
percentage := float64(chunkIndex + 1) / float64(len(chunksToCopy)) * 100.0
remainingTime := int64(float64(len(chunksToCopy)-chunkIndex-1) / float64(chunkIndex+1) * elapsedTime)
percentage := float64(chunkIndex+1) / float64(len(chunksToCopy)) * 100.0
LOG_INFO("COPY_PROGRESS", "%s chunk %s (%d/%d) %sB/s %s %.1f%%",
action, chunk.GetID(), chunkIndex + 1, len(chunksToCopy),
action, chunk.GetID(), chunkIndex+1, len(chunksToCopy),
PrettySize(speed), PrettyTime(remainingTime), percentage)
otherManager.config.PutChunk(chunk)
}
@@ -1844,7 +1924,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
chunkDownloader.Stop()
chunkUploader.Stop()
LOG_INFO("SNAPSHOT_COPY", "Copied %d new chunks and skipped %d existing chunks", copiedChunks, len(chunks) - copiedChunks)
LOG_INFO("SNAPSHOT_COPY", "Copied %d new chunks and skipped %d existing chunks", copiedChunks, len(chunks)-copiedChunks)
for _, snapshot := range snapshots {
if revisionMap[snapshot.ID][snapshot.Revision] == false {

View File

@@ -8,6 +8,7 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
@@ -23,6 +24,11 @@ import (
"github.com/vmihailenco/msgpack"
)
const (
entrySymHardLinkRootChunkMarker = -72
entrySymHardLinkTargetChunkMarker = -73
)
// This is the hidden directory in the repository for storing various files.
var DUPLICACY_DIRECTORY = ".duplicacy"
var DUPLICACY_FILE = ".duplicacy"
@@ -509,16 +515,36 @@ func (entry *Entry) IsLink() bool {
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 {
return entry.Size >= 0
}
func (entry *Entry) IsHardlinkedFrom() bool {
return entry.IsFile() && len(entry.Link) > 0 && entry.Link != "/"
return (entry.IsFileOrSpecial() && len(entry.Link) > 0 && entry.Link != "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkTargetChunkMarker)
}
func (entry *Entry) IsHardlinkRoot() bool {
return entry.IsFile() && entry.Link == "/"
return (entry.IsFileOrSpecial() && entry.Link == "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkRootChunkMarker)
}
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 {
@@ -579,6 +605,10 @@ 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
if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
modifiedTime := time.Unix(entry.Time, 0)
@@ -589,10 +619,6 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
}
}
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
entry.SetAttributesToFile(fullPath)
}
return true
}
@@ -781,10 +807,44 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
if f.Name() == DUPLICACY_DIRECTORY {
continue
}
if f.Mode()&os.ModeSocket != 0 {
continue
}
entry := CreateEntryFromFileInfo(f, normalizedPath)
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
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() {
isRegular := false
isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
@@ -815,30 +875,12 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
}
entry = newEntry
}
}
if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path)
} else if entry.IsSpecial() {
if !entry.ReadSpecial(f) {
LOG_WARN("LIST_DEV", "Failed to save device node %s", entry.Path)
skippedFiles = append(skippedFiles, entry.Path)
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)

View File

@@ -111,12 +111,12 @@ func (entryList *EntryList)createOnDiskFile() error {
// Add an entry to the entry list
func (entryList *EntryList)AddEntry(entry *Entry) error {
if !entry.IsDir() && !entry.IsLink() {
if entry.IsFile() {
entryList.NumberOfEntries++
}
if !entry.IsComplete() {
if entry.IsDir() || entry.IsLink() {
if !entry.IsFile() {
entry.Size = 0
} else {
modifiedEntry := ModifiedEntry {

View File

@@ -7,14 +7,15 @@ package duplicacy
import (
"bufio"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"runtime"
"github.com/gilbertchen/gopass"
"golang.org/x/crypto/pbkdf2"
@@ -275,7 +276,6 @@ 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,
// 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.
//
func matchPattern(text string, pattern string) bool {
textLength := len(text)
@@ -474,3 +474,34 @@ func PrintMemoryUsage() {
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)
}

View File

@@ -8,10 +8,10 @@
package duplicacy
import (
"bytes"
"encoding/binary"
"os"
"path/filepath"
"bytes"
"syscall"
"github.com/pkg/xattr"
@@ -25,6 +25,8 @@ func (entry *Entry) ReadAttributes(top string) {
if err != nil {
return
}
if !entry.IsSpecial() {
attributes, _ := xattr.LList(fullPath)
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
@@ -35,12 +37,14 @@ func (entry *Entry) ReadAttributes(top string) {
}
}
}
}
if err := entry.readFileFlags(fileInfo); err != nil {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
}
func (entry *Entry) SetAttributesToFile(fullPath string) {
if !entry.IsSpecial() {
names, _ := xattr.LList(fullPath)
for _, name := range names {
newAttribute, found := (*entry.Attributes)[name]
@@ -61,6 +65,7 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
}
xattr.LSet(fullPath, name, attribute)
}
}
if err := entry.restoreLateFileFlags(fullPath); err != nil {
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
}
@@ -87,4 +92,3 @@ func (entry *Entry) RestoreEarlyDirFlags(path string) error {
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
return nil
}

View File

@@ -5,10 +5,10 @@
package duplicacy
import (
"os"
"syscall"
"strings"
"encoding/binary"
"os"
"strings"
"syscall"
)
func excludedByAttribute(attributes map[string][]byte) bool {

View File

@@ -8,9 +8,9 @@ import (
"bytes"
"encoding/binary"
"os"
"path/filepath"
"syscall"
"unsafe"
"path/filepath"
"github.com/pkg/xattr"
)
@@ -84,15 +84,17 @@ func (x xattrHandle) remove(name string) error {
if x.f != nil {
return xattr.FRemove(x.f, name)
} else {
return xattr.LSet(x.fullPath, name)
return xattr.LRemove(x.fullPath, name)
}
}
func (entry *Entry) ReadAttributes(top string) {
x := xattrHandle{nil, filepath.Join(top, entry.Path)}
fullPath := filepath.Join(top, entry.Path)
x := xattrHandle{nil, fullPath}
if !entry.IsLink() {
x.f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
var err error
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
if err != nil {
// FIXME: We really should return errors for failure to read
return
@@ -105,7 +107,7 @@ func (entry *Entry) ReadAttributes(top string) {
entry.Attributes = &map[string][]byte{}
}
for _, name := range attributes {
attribute, err := x.get(f, name)
attribute, err := x.get(name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
@@ -116,13 +118,14 @@ func (entry *Entry) ReadAttributes(top string) {
LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
}
}
f.Close()
x.f.Close()
}
func (entry *Entry) SetAttributesToFile(fullPath string) {
x := xattrHandle{nil, fullPath}
if !entry.IsLink() {
x.f, err := os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
var err error
x.f, err = os.OpenFile(fullPath, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
return
}
@@ -150,11 +153,11 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
x.set(name, attribute)
}
if entry.IsFile() || entry.IsDir() {
if err := entry.restoreLateFileFlags(f); err != nil {
if err := entry.restoreLateFileFlags(x.f); err != nil {
LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
}
}
f.Close()
x.f.Close()
}
func (entry *Entry) readFileFlags(f *os.File) error {

View File

@@ -11,6 +11,8 @@ import (
"os"
"path"
"syscall"
"golang.org/x/sys/unix"
)
func Readlink(path string) (isRegular bool, s string, err error) {
@@ -44,6 +46,52 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
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 {
return path.Join(components...)
}

View File

@@ -117,6 +117,18 @@ 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 {
combinedPath := `\\?\` + filepath.Join(components...)