Compare commits

..

20 Commits

Author SHA1 Message Date
3b50b7ec05 Rename hardlink to hard link (two words) 2023-10-04 13:37:26 -05:00
e5a72ddf4f minor: missing variable assignment in CheckSnapshot, struct-tag 2023-10-04 13:37:26 -05:00
de2a5c5fd9 fix broken tests 2023-10-04 13:37:26 -05:00
bdbd75f88c Fix formatting of diagnostic some diagnostic messages 2023-10-04 13:37:26 -05:00
ab74d19d2b Cleanup hardlinking code
This moves special file link data to the chunk fields like symlink
hardlinks.
2023-10-04 13:37:22 -05:00
9755b666b3 tweak the symlink hardlink chunk ids 2023-10-04 04:09:38 -05:00
28efe91c3f Support backup of sockets
This only works on Linux. Darwin does not allow mknod of a socket.
Have not tested BSD.
2023-10-04 02:53:48 -05:00
73ca9794ab gofmt some files 2023-10-04 02:53:48 -05:00
70ea4d3acf 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 02:53:48 -05:00
a1a3f3d4cb 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 02:53:48 -05:00
5b40bf3d93 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 02:53:48 -05:00
5087ac738d 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-04 02:53:48 -05:00
99e4dcae00 Don't overwrite symlinks if file already exists 2023-10-04 02:53:48 -05:00
997ff5bdf6 Support backup and restore of hardlinks
This tracks inode/device from the stat info and creates backward
compatible snapshots that allow preserving hardlinks. Backwards
compatibility is preserved by saving a virtual inode number index in the
Link field of the file entry. Since this field was previously only used
for symlinks, this won't break old versions. Additionally, the entry
data is cloned so restoration with an old version works.

Current limitations are primarility with restore. They include:
- no command line option to prevent hard link restore
- if a file has the immutable or append only flag it will be set before
hardlinks are restored, so hardlinking will fail.
- if a partial restore includes a hardlink but not the parent
directories the hardlink will fail.

These will be solved by grouping restore of hardlinks together
with file, prior to applying final metadata.

- if a file is changed and is being rewritten by a restore hardlinks are
not preserved.
2023-10-04 02:53:48 -05:00
34b1e19278 Initial implementation of file/inode flags (Linux, BSD, darwin)
Basic support for BSD and Darwin style chflags (stat flags). Applies
these flags at the end of file restore.
Supports linux style ioctl_iflags(2) in a 2 step process. Flags that
need to be applied prior to writes such as compress and especially no-COW
are applied immediately upon file open.

The flags format is backwards compatible. An attribute starting with a
null byte is used to store flags in the entry attributes table. With
an old version of duplicacy the restore of this attribute should silently
fail (effectively be ignored).

Fixes xattr restore to use O_NOFOLLOW so attributes are applied to symlink.

TODO: Tests, possible option to switch off mutable/append prior to
restore of existing file similar to rsync. Does not apply attributes
or flags to the top most directory.
2023-10-04 02:53:48 -05:00
c1c8af1de9 Increase b2 client max file listing count to 10000
Considerable speed improvement with listing large storage.
2023-10-04 02:53:48 -05:00
8b788572c8 Fix exclude_by_attribute feature on POSIX
The exclude by attribute function is broken on non-Darwin POSIX: linux and freebsd.
This is because those xattrs must be prefixed by a legal namespace. The old xattr
library implicitly appended the user namespace to the xattr, but the current
official go pkg does not (which is just as well).

Also fix the test to remove the discordant old xattr dependency and provide
test cases for both darwin and non-darwin POSIX.
2023-10-04 02:53:48 -05:00
b592484b54 Use S3 ListObjectsV2 for listing files
ListObjects has been deprecated since 2016 and ListObjectsV2 with use of
explicit pagination tokens is more performant for large listings as well.

This also mitigates an issue with iDrive E2 where the StartAfter/Marker
is included in the output, leading to duplicate entries. Right now this
causes an exhaustive prune to delete chunks erroneously flagged as
duplicate, destroying the storage.
2023-10-04 02:53:48 -05:00
Gilbert Chen
50120146df Bump version to 3.2.2 2023-10-03 22:35:18 -04:00
Gilbert Chen
7bfc0e7d51 Save passwords after a storj storage has been created 2023-10-03 22:33:41 -04:00
12 changed files with 134 additions and 87 deletions

View File

@@ -2262,7 +2262,7 @@ func main() {
app.Name = "duplicacy" app.Name = "duplicacy"
app.HelpName = "duplicacy" app.HelpName = "duplicacy"
app.Usage = "A new generation cloud backup tool based on lock-free deduplication" app.Usage = "A new generation cloud backup tool based on lock-free deduplication"
app.Version = "3.2.1" + " (" + GitCommit + ")" app.Version = "3.2.2" + " (" + GitCommit + ")"
// Exit with code 2 if an invalid command is provided // Exit with code 2 if an invalid command is provided
app.CommandNotFound = func(context *cli.Context, command string) { app.CommandNotFound = func(context *cli.Context, command string) {

2
go.mod
View File

@@ -27,6 +27,7 @@ require (
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.12.0
golang.org/x/net v0.10.0 golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.11.0
google.golang.org/api v0.21.0 google.golang.org/api v0.21.0
storj.io/uplink v1.12.0 storj.io/uplink v1.12.0
) )
@@ -63,7 +64,6 @@ require (
go.opencensus.io v0.22.3 // indirect go.opencensus.io v0.22.3 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.10.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.9.1 // indirect golang.org/x/tools v0.9.1 // indirect

View File

@@ -710,23 +710,23 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
var hardLinkTable []hardLinkEntry var hardLinkTable []hardLinkEntry
var hardLinks []*Entry var hardLinks []*Entry
restoreHardlink := func(entry *Entry, fullPath string) bool { restoreHardLink := func(entry *Entry, fullPath string) bool {
if entry.IsHardlinkRoot() { if entry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} else if entry.IsHardlinkedFrom() { } else if entry.IsHardLinkChild() {
i, err := entry.GetHardlinkId() i, err := entry.GetHardLinkId()
if err != nil { if err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hardlinked entry %s, %v", entry.Path, err) LOG_ERROR("RESTORE_HARDLINK", "Decode error for hard link entry %s: %v", entry.Path, err)
return false return false
} }
if !hardLinkTable[i].willExist { if !hardLinkTable[i].willExist {
hardLinkTable[i] = hardLinkEntry{entry, true} hardLinkTable[i] = hardLinkEntry{entry, true}
} else { } else {
sourcePath := joinPath(top, hardLinkTable[i].entry.Path) 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 { if err := MakeHardlink(sourcePath, fullPath); err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s %v", fullPath, sourcePath, err) LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s: %v", fullPath, sourcePath, err)
} }
LOG_TRACE("DOWNLOAD_DONE", "Hard linked %s to %s", entry.Path, hardLinkTable[i].entry.Path)
return true return true
} }
} }
@@ -735,7 +735,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
for remoteEntry := range remoteListingChannel { for remoteEntry := range remoteListingChannel {
if remoteEntry.IsHardlinkRoot() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable = append(hardLinkTable, hardLinkEntry{remoteEntry, false}) hardLinkTable = append(hardLinkTable, hardLinkEntry{remoteEntry, false})
} }
@@ -782,7 +782,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
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() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} }
continue continue
@@ -798,7 +798,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
os.Remove(fullPath) os.Remove(fullPath)
} }
if restoreHardlink(remoteEntry, fullPath) { if restoreHardLink(remoteEntry, fullPath) {
continue continue
} }
@@ -807,8 +807,8 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
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() {
stat, err := os.Stat(fullPath) stat, err := os.Stat(fullPath)
@@ -833,7 +833,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
if stat, _ := os.Lstat(fullPath); stat != nil { if stat, _ := os.Lstat(fullPath); stat != nil {
if remoteEntry.IsSameSpecial(stat) { if remoteEntry.IsSameSpecial(stat) {
remoteEntry.RestoreMetadata(fullPath, nil, setOwner) remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
if remoteEntry.IsHardlinkRoot() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} }
} }
@@ -845,22 +845,24 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
os.Remove(fullPath) os.Remove(fullPath)
} }
if restoreHardlink(remoteEntry, fullPath) { if restoreHardLink(remoteEntry, fullPath) {
continue continue
} }
if err := remoteEntry.RestoreSpecial(fullPath); err != nil { if err := remoteEntry.RestoreSpecial(fullPath); err != nil {
LOG_ERROR("RESTORE_SPECIAL", "Unable to restore special file %s: %v", remoteEntry.Path, err) LOG_ERROR("RESTORE_SPECIAL", "Failed to restore special file %s: %v", fullPath, err)
return 0 return 0
} }
remoteEntry.RestoreMetadata(fullPath, nil, setOwner) remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
LOG_TRACE("DOWNLOAD_DONE", "Special %s %s restored", remoteEntry.Path, remoteEntry.FmtSpecial())
} else { } else {
if remoteEntry.IsHardlinkRoot() { if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true hardLinkTable[len(hardLinkTable)-1].willExist = true
} else if remoteEntry.IsHardlinkedFrom() { } else if remoteEntry.IsHardLinkChild() {
i, err := remoteEntry.GetHardlinkId() i, err := remoteEntry.GetHardLinkId()
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 for hard link entry %s: %v", remoteEntry.Path, err)
return 0 return 0
} }
if !hardLinkTable[i].willExist { if !hardLinkTable[i].willExist {
@@ -992,7 +994,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
for _, linkEntry := range hardLinks { for _, linkEntry := range hardLinks {
i, _ := linkEntry.GetHardlinkId() 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)
@@ -1004,7 +1006,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
if sourceStat == nil { if sourceStat == nil {
LOG_WERROR(allowFailures, "RESTORE_HARDLINK", LOG_WERROR(allowFailures, "RESTORE_HARDLINK",
"Target %s for hardlink %s is missing", sourcePath, linkEntry.Path) "Target %s for hard link %s is missing", sourcePath, linkEntry.Path)
continue continue
} }
if !overwrite { if !overwrite {
@@ -1015,11 +1017,11 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
os.Remove(fullPath) os.Remove(fullPath)
} }
LOG_DEBUG("RESTORE_HARDLINK", "Hard linking %s to %s", fullPath, sourcePath)
if err := MakeHardlink(sourcePath, fullPath); err != nil { 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: %v", fullPath, sourcePath, err)
return 0 return 0
} }
LOG_TRACE("RESTORE_HARDLINK", "Hard linked %s to %s", linkEntry.Path, hardLinkTable[i].entry.Path)
} }
if deleteMode && len(patterns) == 0 { if deleteMode && len(patterns) == 0 {
@@ -1197,8 +1199,7 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
entry.StartChunk -= delta entry.StartChunk -= delta
entry.EndChunk -= delta entry.EndChunk -= delta
if entry.IsHardlinkRoot() { if entry.IsHardLinkRoot() {
LOG_DEBUG("SNAPSHOT_UPLOAD", "Hard link root %s %v %v", entry.Path, entry.StartChunk, entry.EndChunk)
hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, entry.StartChunk}) hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, entry.StartChunk})
} }
@@ -1206,28 +1207,24 @@ 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.IsHardLinkChild() {
i, err := entry.GetHardlinkId() i, err := entry.GetHardLinkId()
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 for hard link entry %s: %v", entry.Link, err)
return err return err
} }
targetEntry := hardLinkTable[i].entry targetEntry := hardLinkTable[i].entry
var startChunk, endChunk int var startChunk, endChunk int
if targetEntry.Size > 0 { if targetEntry.IsFile() && targetEntry.Size > 0 {
startChunk = hardLinkTable[i].startChunk - lastEndChunk startChunk = hardLinkTable[i].startChunk - lastEndChunk
endChunk = targetEntry.EndChunk endChunk = targetEntry.EndChunk
lastEndChunk = hardLinkTable[i].startChunk + endChunk
} }
entry = entry.HardLinkTo(targetEntry, startChunk, endChunk) entry = entry.HardLinkTo(targetEntry, startChunk, endChunk)
if targetEntry.Size > 0 { } else if entry.IsHardLinkRoot() {
lastEndChunk = hardLinkTable[i].startChunk + endChunk
}
LOG_DEBUG("SNAPSHOT_UPLOAD", "Uploading cloned hardlink for %s to %s (%v %v)", entry.Path, targetEntry.Path, startChunk, endChunk)
} else if entry.IsHardlinkRoot() {
hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, 0}) hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, 0})
} }

View File

@@ -85,8 +85,8 @@ type Config struct {
FileKey []byte `json:"-"` FileKey []byte `json:"-"`
// for erasure coding // for erasure coding
DataShards int `json:'data-shards'` DataShards int `json:"data-shards"`
ParityShards int `json:'parity-shards'` ParityShards int `json:"parity-shards"`
// for RSA encryption // for RSA encryption
rsaPrivateKey *rsa.PrivateKey rsaPrivateKey *rsa.PrivateKey

View File

@@ -25,8 +25,8 @@ import (
) )
const ( const (
entrySymHardLinkRootChunkMarker = -72 entryHardLinkRootChunkMarker = -9
entrySymHardLinkTargetChunkMarker = -73 entryHardLinkTargetChunkMarker = -10
) )
// This is the hidden directory in the repository for storing various files. // This is the hidden directory in the repository for storing various files.
@@ -126,6 +126,13 @@ func (entry *Entry) Copy() *Entry {
} }
func (entry *Entry) HardLinkTo(target *Entry, startChunk int, endChunk int) *Entry { func (entry *Entry) HardLinkTo(target *Entry, startChunk int, endChunk int) *Entry {
endOffset := target.EndOffset
if !target.IsFile() {
startChunk = target.StartChunk
endChunk = entry.EndChunk
endOffset = entry.EndOffset
}
return &Entry{ return &Entry{
Path: entry.Path, Path: entry.Path,
Size: target.Size, Size: target.Size,
@@ -140,7 +147,7 @@ func (entry *Entry) HardLinkTo(target *Entry, startChunk int, endChunk int) *Ent
StartChunk: startChunk, StartChunk: startChunk,
StartOffset: target.StartOffset, StartOffset: target.StartOffset,
EndChunk: endChunk, EndChunk: endChunk,
EndOffset: target.EndOffset, EndOffset: endOffset,
Attributes: target.Attributes, Attributes: target.Attributes,
} }
@@ -516,34 +523,30 @@ func (entry *Entry) IsLink() bool {
} }
func (entry *Entry) IsSpecial() bool { func (entry *Entry) IsSpecial() bool {
return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice) != 0 return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice|os.ModeSocket) != 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) IsHardLinkChild() bool {
return (entry.IsFileOrSpecial() && len(entry.Link) > 0 && entry.Link != "/") || (entry.IsLink() && entry.StartChunk == entrySymHardLinkTargetChunkMarker) return (entry.IsFile() && len(entry.Link) > 0 && entry.Link != "/") || (!entry.IsDir() && entry.EndChunk == entryHardLinkTargetChunkMarker)
} }
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 == "/") || (!entry.IsDir() && entry.EndChunk == entryHardLinkRootChunkMarker)
} }
func (entry *Entry) GetHardlinkId() (int, error) { func (entry *Entry) GetHardLinkId() (int, error) {
if entry.IsLink() { if entry.IsFile() {
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) i, err := strconv.ParseUint(entry.Link, 16, 64)
return int(i), err return int(i), err
} else {
if entry.EndChunk != entryHardLinkTargetChunkMarker {
return 0, errors.New("Entry not marked as hard link child")
}
return entry.EndOffset, nil
} }
} }
@@ -807,9 +810,6 @@ 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) {
@@ -823,21 +823,23 @@ func ListEntries(top string, path string, patterns []string, nobackupFile string
k := listEntryLinkKey{dev: uint64(stat.Dev), ino: uint64(stat.Ino)} k := listEntryLinkKey{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
if linkIndex, seen := listingState.linkTable[k]; seen { if linkIndex, seen := listingState.linkTable[k]; seen {
if linkIndex == -1 { if linkIndex == -1 {
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute (hardlink)", entry.Path) LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute (hard link)", entry.Path)
continue continue
} }
entry.Size = 0 entry.Size = 0
if entry.IsLink() { if entry.IsFile() {
entry.StartChunk = entrySymHardLinkTargetChunkMarker
entry.StartOffset = linkIndex
} else {
entry.Link = strconv.FormatInt(int64(linkIndex), 16) entry.Link = strconv.FormatInt(int64(linkIndex), 16)
}
} else {
if entry.IsLink() {
entry.StartChunk = entrySymHardLinkRootChunkMarker
} else { } else {
entry.EndChunk = entryHardLinkTargetChunkMarker
entry.EndOffset = linkIndex
}
listingChannel <- entry
continue
} else {
if entry.IsFile() {
entry.Link = "/" entry.Link = "/"
} else {
entry.EndChunk = entryHardLinkRootChunkMarker
} }
listingState.linkTable[k] = -1 listingState.linkTable[k] = -1
linkKey = &k linkKey = &k

View File

@@ -172,6 +172,8 @@ func TestEntryOrder(t *testing.T) {
} }
} }
listingState := NewListingState()
directories := make([]*Entry, 0, 4) directories := make([]*Entry, 0, 4)
directories = append(directories, CreateEntry("", 0, 0, 0)) directories = append(directories, CreateEntry("", 0, 0, 0))
@@ -182,7 +184,7 @@ func TestEntryOrder(t *testing.T) {
for len(directories) > 0 { for len(directories) > 0 {
directory := directories[len(directories)-1] directory := directories[len(directories)-1]
directories = directories[:len(directories)-1] directories = directories[:len(directories)-1]
subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", false, entryChannel) subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", false, listingState, entryChannel)
if err != nil { if err != nil {
t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err) t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err)
} }
@@ -288,6 +290,8 @@ func TestEntryExcludeByAttribute(t *testing.T) {
for _, excludeByAttribute := range [2]bool{true, false} { for _, excludeByAttribute := range [2]bool{true, false} {
t.Logf("testing excludeByAttribute: %t", excludeByAttribute) t.Logf("testing excludeByAttribute: %t", excludeByAttribute)
listingState := NewListingState()
directories := make([]*Entry, 0, 4) directories := make([]*Entry, 0, 4)
directories = append(directories, CreateEntry("", 0, 0, 0)) directories = append(directories, CreateEntry("", 0, 0, 0))
@@ -298,7 +302,7 @@ func TestEntryExcludeByAttribute(t *testing.T) {
for len(directories) > 0 { for len(directories) > 0 {
directory := directories[len(directories)-1] directory := directories[len(directories)-1]
directories = directories[:len(directories)-1] directories = directories[:len(directories)-1]
subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", excludeByAttribute, entryChannel) subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", excludeByAttribute, listingState, entryChannel)
if err != nil { if err != nil {
t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err) t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err)
} }

View File

@@ -2568,7 +2568,7 @@ func (manager *SnapshotManager) CheckSnapshot(snapshot *Snapshot) (err error) {
} }
if entry.EndChunk < entry.StartChunk { if entry.EndChunk < entry.StartChunk {
fmt.Errorf("The file %s starts at chunk %d and ends at chunk %d", err = fmt.Errorf("The file %s starts at chunk %d and ends at chunk %d",
entry.Path, entry.StartChunk, entry.EndChunk) entry.Path, entry.StartChunk, entry.EndChunk)
return false return false
} }

View File

@@ -756,6 +756,8 @@ func CreateStorage(preference Preference, resetPassword bool, threads int) (stor
LOG_ERROR("STORAGE_CREATE", "Failed to load the Storj storage at %s: %v", storageURL, err) LOG_ERROR("STORAGE_CREATE", "Failed to load the Storj storage at %s: %v", storageURL, err)
return nil return nil
} }
SavePassword(preference, "storj_key", apiKey)
SavePassword(preference, "storj_passphrase", passphrase)
return storjStorage return storjStorage
} else if matched[1] == "smb" { } else if matched[1] == "smb" {
server := matched[3] server := matched[3]

View File

@@ -92,3 +92,18 @@ 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
} }
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()))
}

View File

@@ -219,6 +219,23 @@ func (entry *Entry) restoreLateFileFlags(f *os.File) error {
return nil return nil
} }
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 if entry.Mode&uint32(os.ModeSocket) != 0 {
mode |= syscall.S_IFSOCK
} else {
return nil
}
return syscall.Mknod(fullPath, mode, int(entry.GetRdev()))
}
func excludedByAttribute(attributes map[string][]byte) bool { func excludedByAttribute(attributes map[string][]byte) bool {
_, ok := attributes["user.duplicacy_exclude"] _, ok := attributes["user.duplicacy_exclude"]
return ok return ok

View File

@@ -8,6 +8,7 @@
package duplicacy package duplicacy
import ( import (
"fmt"
"os" "os"
"path" "path"
"syscall" "syscall"
@@ -65,21 +66,6 @@ func (entry *Entry) GetRdev() uint64 {
return uint64(entry.StartChunk) | uint64(entry.StartOffset)<<32 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 { func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
stat := fileInfo.Sys().(*syscall.Stat_t) stat := fileInfo.Sys().(*syscall.Stat_t)
if stat == nil { if stat == nil {
@@ -88,6 +74,26 @@ func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
return (uint32(fileInfo.Mode()) == entry.Mode) && (uint64(stat.Rdev) == entry.GetRdev()) return (uint32(fileInfo.Mode()) == entry.Mode) && (uint64(stat.Rdev) == entry.GetRdev())
} }
func (entry *Entry) FmtSpecial() string {
var c string
mode := entry.Mode & uint32(os.ModeType)
if mode&uint32(os.ModeNamedPipe) != 0 {
c = "p"
} else if mode&uint32(os.ModeCharDevice) != 0 {
c = "c"
} else if mode&uint32(os.ModeDevice) != 0 {
c = "b"
} else if mode&uint32(os.ModeSocket) != 0 {
c = "s"
} else {
return ""
}
rdev := entry.GetRdev()
return fmt.Sprintf("%s (%d, %d)", c, unix.Major(rdev), unix.Minor(rdev))
}
func MakeHardlink(source string, target string) error { func MakeHardlink(source string, target string) error {
return unix.Linkat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, 0) return unix.Linkat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, 0)
} }

View File

@@ -125,6 +125,10 @@ func (entry *Entry) RestoreSpecial(fullPath string) error {
return nil return nil
} }
func (entry *Entry) FmtSpecial() string {
return ""
}
func MakeHardlink(source string, target string) error { func MakeHardlink(source string, target string) error {
return os.Link(source, target) return os.Link(source, target)
} }