mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-03 04:04:45 -06:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e5cbc73b9 | ||
|
|
21b3d9e57f | ||
|
|
244b797a1c | ||
|
|
073292018c | ||
|
|
15f15aa2ca | ||
|
|
d8e13d8d85 | ||
|
|
bfb4b44c0a | ||
|
|
22a0b222db | ||
|
|
674d35e5ca | ||
|
|
a7d2a941be | ||
|
|
39d71a3256 | ||
|
|
9d10cc77fc |
@@ -1993,7 +1993,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 = "2.1.1" + " (" + GitCommit + ")"
|
app.Version = "2.1.2" + " (" + GitCommit + ")"
|
||||||
|
|
||||||
// If the program is interrupted, call the RunAtError function.
|
// If the program is interrupted, call the RunAtError function.
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ var B2AuthorizationURL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_acco
|
|||||||
type B2Client struct {
|
type B2Client struct {
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
AccountID string
|
AccountID string
|
||||||
|
ApplicationKeyID string
|
||||||
ApplicationKey string
|
ApplicationKey string
|
||||||
AuthorizationToken string
|
AuthorizationToken string
|
||||||
APIURL string
|
APIURL string
|
||||||
@@ -53,10 +54,10 @@ type B2Client struct {
|
|||||||
TestMode bool
|
TestMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewB2Client(accountID string, applicationKey string) *B2Client {
|
func NewB2Client(applicationKeyID string, applicationKey string) *B2Client {
|
||||||
client := &B2Client{
|
client := &B2Client{
|
||||||
HTTPClient: http.DefaultClient,
|
HTTPClient: http.DefaultClient,
|
||||||
AccountID: accountID,
|
ApplicationKeyID: applicationKeyID,
|
||||||
ApplicationKey: applicationKey,
|
ApplicationKey: applicationKey,
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
@@ -119,7 +120,7 @@ func (client *B2Client) call(url string, method string, requestHeaders map[strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if url == B2AuthorizationURL {
|
if url == B2AuthorizationURL {
|
||||||
request.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(client.AccountID+":"+client.ApplicationKey)))
|
request.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(client.ApplicationKeyID+":"+client.ApplicationKey)))
|
||||||
} else {
|
} else {
|
||||||
request.Header.Set("Authorization", client.AuthorizationToken)
|
request.Header.Set("Authorization", client.AuthorizationToken)
|
||||||
}
|
}
|
||||||
@@ -225,6 +226,10 @@ func (client *B2Client) AuthorizeAccount() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The account id may be different from the application key id so we're getting the account id from the returned
|
||||||
|
// json object here, which is needed by the b2_list_buckets call.
|
||||||
|
client.AccountID = output.AccountID
|
||||||
|
|
||||||
client.AuthorizationToken = output.AuthorizationToken
|
client.AuthorizationToken = output.AuthorizationToken
|
||||||
client.APIURL = output.APIURL
|
client.APIURL = output.APIURL
|
||||||
client.DownloadURL = output.DownloadURL
|
client.DownloadURL = output.DownloadURL
|
||||||
@@ -233,7 +238,7 @@ func (client *B2Client) AuthorizeAccount() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListBucketOutput struct {
|
type ListBucketOutput struct {
|
||||||
AccoundID string
|
AccountID string
|
||||||
BucketID string
|
BucketID string
|
||||||
BucketName string
|
BucketName string
|
||||||
BucketType string
|
BucketType string
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
|
|||||||
|
|
||||||
uploadedModifiedFileSize := atomic.AddInt64(&uploadedModifiedFileSize, int64(chunkSize))
|
uploadedModifiedFileSize := atomic.AddInt64(&uploadedModifiedFileSize, int64(chunkSize))
|
||||||
|
|
||||||
if IsTracing() || showStatistics {
|
if (IsTracing() || showStatistics) && totalModifiedFileSize > 0 {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if now <= startUploadingTime {
|
if now <= startUploadingTime {
|
||||||
now = startUploadingTime + 1
|
now = startUploadingTime + 1
|
||||||
@@ -825,6 +825,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
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 == entry.Link && !isRegular {
|
if err == nil && link == entry.Link && !isRegular {
|
||||||
|
entry.RestoreMetadata(fullPath, nil, setOwner)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -837,6 +838,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", entry.Path, err)
|
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", entry.Path, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
entry.RestoreMetadata(fullPath, nil, setOwner)
|
||||||
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", entry.Path)
|
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", entry.Path)
|
||||||
} else if entry.IsDir() {
|
} else if entry.IsDir() {
|
||||||
stat, err := os.Stat(fullPath)
|
stat, err := os.Stat(fullPath)
|
||||||
@@ -1160,6 +1162,9 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
lengthMap := make(map[string]int)
|
lengthMap := make(map[string]int)
|
||||||
var offset int64
|
var offset int64
|
||||||
|
|
||||||
|
// If the file is newly created (needed by sparse file optimization)
|
||||||
|
isNewFile := false
|
||||||
|
|
||||||
existingFile, err = os.Open(fullPath)
|
existingFile, err = os.Open(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -1194,6 +1199,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
LOG_ERROR("DOWNLOAD_OPEN", "Can't reopen the initial file just created: %v", err)
|
LOG_ERROR("DOWNLOAD_OPEN", "Can't reopen the initial file just created: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
isNewFile = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_TRACE("DOWNLOAD_OPEN", "Can't open the existing file: %v", err)
|
LOG_TRACE("DOWNLOAD_OPEN", "Can't open the existing file: %v", err)
|
||||||
@@ -1206,6 +1212,9 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The key in this map is the number of zeroes. The value is the corresponding hash.
|
||||||
|
knownHashes := make(map[int]string)
|
||||||
|
|
||||||
fileHash := ""
|
fileHash := ""
|
||||||
if existingFile != nil {
|
if existingFile != nil {
|
||||||
|
|
||||||
@@ -1215,6 +1224,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
fileHasher := manager.config.NewFileHasher()
|
fileHasher := manager.config.NewFileHasher()
|
||||||
buffer := make([]byte, 64*1024)
|
buffer := make([]byte, 64*1024)
|
||||||
err = nil
|
err = nil
|
||||||
|
isSkipped := false
|
||||||
// We set to read one more byte so the file hash will be different if the file to be restored is a
|
// We set to read one more byte so the file hash will be different if the file to be restored is a
|
||||||
// truncated portion of the existing file
|
// truncated portion of the existing file
|
||||||
for i := entry.StartChunk; i <= entry.EndChunk+1; i++ {
|
for i := entry.StartChunk; i <= entry.EndChunk+1; i++ {
|
||||||
@@ -1230,6 +1240,28 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
chunkSize = 1 // the size of extra chunk beyond EndChunk
|
chunkSize = 1 // the size of extra chunk beyond EndChunk
|
||||||
}
|
}
|
||||||
count := 0
|
count := 0
|
||||||
|
|
||||||
|
if isNewFile {
|
||||||
|
if hash, found := knownHashes[chunkSize]; found {
|
||||||
|
// We have read the same number of zeros before, so we just retrieve the hash from the map
|
||||||
|
existingChunks = append(existingChunks, hash)
|
||||||
|
existingLengths = append(existingLengths, chunkSize)
|
||||||
|
offsetMap[hash] = offset
|
||||||
|
lengthMap[hash] = chunkSize
|
||||||
|
offset += int64(chunkSize)
|
||||||
|
isSkipped = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSkipped {
|
||||||
|
_, err := existingFile.Seek(offset, 0)
|
||||||
|
if err != nil {
|
||||||
|
LOG_ERROR("DOWNLOAD_SEEK", "Failed to seek to offset %d: %v", offset, err)
|
||||||
|
}
|
||||||
|
isSkipped = false
|
||||||
|
}
|
||||||
|
|
||||||
for count < chunkSize {
|
for count < chunkSize {
|
||||||
n := chunkSize - count
|
n := chunkSize - count
|
||||||
if n > cap(buffer) {
|
if n > cap(buffer) {
|
||||||
@@ -1256,12 +1288,16 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
offsetMap[hash] = offset
|
offsetMap[hash] = offset
|
||||||
lengthMap[hash] = chunkSize
|
lengthMap[hash] = chunkSize
|
||||||
offset += int64(chunkSize)
|
offset += int64(chunkSize)
|
||||||
|
if isNewFile {
|
||||||
|
knownHashes[chunkSize] = hash
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileHash = hex.EncodeToString(fileHasher.Sum(nil))
|
fileHash = hex.EncodeToString(fileHasher.Sum(nil))
|
||||||
} else {
|
} else {
|
||||||
// If it is not inplace, we want to reuse any chunks in the existing file regardless their offets, so
|
// If it is not inplace, we want to reuse any chunks in the existing file regardless their offets, so
|
||||||
@@ -1288,6 +1324,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for i := entry.StartChunk; i <= entry.EndChunk; i++ {
|
for i := entry.StartChunk; i <= entry.EndChunk; i++ {
|
||||||
if _, found := offsetMap[chunkDownloader.taskList[i].chunkHash]; !found {
|
if _, found := offsetMap[chunkDownloader.taskList[i].chunkHash]; !found {
|
||||||
chunkDownloader.taskList[i].needed = true
|
chunkDownloader.taskList[i].needed = true
|
||||||
|
|||||||
@@ -250,10 +250,7 @@ func (chunk *Chunk) Encrypt(encryptionKey []byte, derivationKey string) (err err
|
|||||||
// PKCS7 is used. Compressed chunk sizes leaks information about the original chunks so we want the padding sizes
|
// PKCS7 is used. Compressed chunk sizes leaks information about the original chunks so we want the padding sizes
|
||||||
// to be the maximum allowed by PKCS7
|
// to be the maximum allowed by PKCS7
|
||||||
dataLength := encryptedBuffer.Len() - offset
|
dataLength := encryptedBuffer.Len() - offset
|
||||||
paddingLength := dataLength % 256
|
paddingLength := 256 - dataLength % 256
|
||||||
if paddingLength == 0 {
|
|
||||||
paddingLength = 256
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedBuffer.Write(bytes.Repeat([]byte{byte(paddingLength)}, paddingLength))
|
encryptedBuffer.Write(bytes.Repeat([]byte{byte(paddingLength)}, paddingLength))
|
||||||
encryptedBuffer.Write(bytes.Repeat([]byte{0}, gcm.Overhead()))
|
encryptedBuffer.Write(bytes.Repeat([]byte{0}, gcm.Overhead()))
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ func TestChunk(t *testing.T) {
|
|||||||
config.CompressionLevel = DEFAULT_COMPRESSION_LEVEL
|
config.CompressionLevel = DEFAULT_COMPRESSION_LEVEL
|
||||||
maxSize := 1000000
|
maxSize := 1000000
|
||||||
|
|
||||||
|
remainderLength := -1
|
||||||
|
|
||||||
for i := 0; i < 500; i++ {
|
for i := 0; i < 500; i++ {
|
||||||
|
|
||||||
size := rand.Int() % maxSize
|
size := rand.Int() % maxSize
|
||||||
@@ -44,6 +46,12 @@ func TestChunk(t *testing.T) {
|
|||||||
encryptedData := make([]byte, chunk.GetLength())
|
encryptedData := make([]byte, chunk.GetLength())
|
||||||
copy(encryptedData, chunk.GetBytes())
|
copy(encryptedData, chunk.GetBytes())
|
||||||
|
|
||||||
|
if remainderLength == -1 {
|
||||||
|
remainderLength = len(encryptedData) % 256
|
||||||
|
} else if len(encryptedData) % 256 != remainderLength {
|
||||||
|
t.Errorf("Incorrect padding size")
|
||||||
|
}
|
||||||
|
|
||||||
chunk.Reset(false)
|
chunk.Reset(false)
|
||||||
chunk.Write(encryptedData)
|
chunk.Write(encryptedData)
|
||||||
err = chunk.Decrypt(key, "")
|
err = chunk.Decrypt(key, "")
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ func (entry *Entry) String(maxSizeDigits int) string {
|
|||||||
func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setOwner bool) bool {
|
func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setOwner bool) bool {
|
||||||
|
|
||||||
if fileInfo == nil {
|
if fileInfo == nil {
|
||||||
stat, err := os.Stat(fullPath)
|
stat, err := os.Lstat(fullPath)
|
||||||
fileInfo = &stat
|
fileInfo = &stat
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("RESTORE_STAT", "Failed to retrieve the file info: %v", err)
|
LOG_ERROR("RESTORE_STAT", "Failed to retrieve the file info: %v", err)
|
||||||
@@ -307,7 +307,8 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*fileInfo).Mode()&fileModeMask != entry.GetPermissions() {
|
// Only set the permission if the file is not a symlink
|
||||||
|
if !entry.IsLink() && (*fileInfo).Mode() & fileModeMask != entry.GetPermissions() {
|
||||||
err := os.Chmod(fullPath, entry.GetPermissions())
|
err := os.Chmod(fullPath, entry.GetPermissions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("RESTORE_CHMOD", "Failed to set the file permissions: %v", err)
|
LOG_ERROR("RESTORE_CHMOD", "Failed to set the file permissions: %v", err)
|
||||||
@@ -315,7 +316,8 @@ func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*fileInfo).ModTime().Unix() != entry.Time {
|
// 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)
|
modifiedTime := time.Unix(entry.Time, 0)
|
||||||
err := os.Chtimes(fullPath, modifiedTime, modifiedTime)
|
err := os.Chtimes(fullPath, modifiedTime, modifiedTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ func (storage *GCDStorage) ListFiles(threadIndex int, dir string) ([]string, []i
|
|||||||
subDirs = append(subDirs, file.Name + "/")
|
subDirs = append(subDirs, file.Name + "/")
|
||||||
}
|
}
|
||||||
return subDirs, nil, nil
|
return subDirs, nil, nil
|
||||||
} else if strings.HasPrefix(dir, "snapshots/") {
|
} else if strings.HasPrefix(dir, "snapshots/") || strings.HasPrefix(dir, "benchmark") {
|
||||||
pathID, err := storage.getIDFromPath(threadIndex, dir, false)
|
pathID, err := storage.getIDFromPath(threadIndex, dir, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|||||||
@@ -136,6 +136,16 @@ func keyringSet(key string, value string) bool {
|
|||||||
if value == "" {
|
if value == "" {
|
||||||
keyring[key] = nil
|
keyring[key] = nil
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Check if the value to be set is the same as the existing one
|
||||||
|
existingEncryptedValue := keyring[key]
|
||||||
|
if len(existingEncryptedValue) > 0 {
|
||||||
|
existingValue, err := keyringDecrypt(existingEncryptedValue)
|
||||||
|
if err == nil && string(existingValue) == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
encryptedValue, err := keyringEncrypt([]byte(value))
|
encryptedValue, err := keyringEncrypt([]byte(value))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_DEBUG("KEYRING_ENCRYPT", "Failed to encrypt the value: %v", err)
|
LOG_DEBUG("KEYRING_ENCRYPT", "Failed to encrypt the value: %v", err)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (storage *OneDriveStorage) ListFiles(threadIndex int, dir string) ([]string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subDirs, nil, nil
|
return subDirs, nil, nil
|
||||||
} else if strings.HasPrefix(dir, "snapshots/") {
|
} else if strings.HasPrefix(dir, "snapshots/") || strings.HasPrefix(dir, "benchmark") {
|
||||||
entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
|
entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|||||||
@@ -170,6 +170,16 @@ func (collection *FossilCollection) IsEmpty() bool {
|
|||||||
return len(collection.Fossils) == 0 && len(collection.Temporaries) == 0
|
return len(collection.Fossils) == 0 && len(collection.Temporaries) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculates the number of days between two times ignoring the hours, minutes and seconds.
|
||||||
|
func getDaysBetween(start int64, end int64) int {
|
||||||
|
startTime := time.Unix(start, 0).In(time.Now().Location())
|
||||||
|
endTime := time.Unix(end, 0).In(time.Now().Location())
|
||||||
|
startDate := time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 0, 0, 0, 0, startTime.Location())
|
||||||
|
endDate := time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 0, 0, 0, 0, endTime.Location())
|
||||||
|
hours := int(endDate.Sub(startDate).Hours())
|
||||||
|
return (hours + 1) / 24
|
||||||
|
}
|
||||||
|
|
||||||
// SnapshotManager is mainly responsible for downloading, and deleting snapshots.
|
// SnapshotManager is mainly responsible for downloading, and deleting snapshots.
|
||||||
type SnapshotManager struct {
|
type SnapshotManager struct {
|
||||||
|
|
||||||
@@ -679,6 +689,9 @@ func (manager *SnapshotManager) ListSnapshots(snapshotID string, revisionsToList
|
|||||||
for _, revision := range revisions {
|
for _, revision := range revisions {
|
||||||
|
|
||||||
snapshot := manager.DownloadSnapshot(snapshotID, revision)
|
snapshot := manager.DownloadSnapshot(snapshotID, revision)
|
||||||
|
if tag != "" && snapshot.Tag != tag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
creationTime := time.Unix(snapshot.StartTime, 0).Format("2006-01-02 15:04")
|
creationTime := time.Unix(snapshot.StartTime, 0).Format("2006-01-02 15:04")
|
||||||
tagWithSpace := ""
|
tagWithSpace := ""
|
||||||
if len(snapshot.Tag) > 0 {
|
if len(snapshot.Tag) > 0 {
|
||||||
@@ -687,15 +700,16 @@ func (manager *SnapshotManager) ListSnapshots(snapshotID string, revisionsToList
|
|||||||
LOG_INFO("SNAPSHOT_INFO", "Snapshot %s revision %d created at %s %s%s",
|
LOG_INFO("SNAPSHOT_INFO", "Snapshot %s revision %d created at %s %s%s",
|
||||||
snapshotID, revision, creationTime, tagWithSpace, snapshot.Options)
|
snapshotID, revision, creationTime, tagWithSpace, snapshot.Options)
|
||||||
|
|
||||||
if tag != "" && snapshot.Tag != tag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if showFiles {
|
if showFiles {
|
||||||
manager.DownloadSnapshotFileSequence(snapshot, nil, false)
|
manager.DownloadSnapshotFileSequence(snapshot, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if showFiles {
|
if showFiles {
|
||||||
|
|
||||||
|
if snapshot.NumberOfFiles > 0 {
|
||||||
|
LOG_INFO("SNAPSHOT_STATS", "Files: %d", snapshot.NumberOfFiles)
|
||||||
|
}
|
||||||
|
|
||||||
maxSize := int64(9)
|
maxSize := int64(9)
|
||||||
maxSizeDigits := 1
|
maxSizeDigits := 1
|
||||||
totalFiles := 0
|
totalFiles := 0
|
||||||
@@ -806,11 +820,28 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
|
|||||||
|
|
||||||
for _, revision := range revisions {
|
for _, revision := range revisions {
|
||||||
snapshot := manager.DownloadSnapshot(snapshotID, revision)
|
snapshot := manager.DownloadSnapshot(snapshotID, revision)
|
||||||
snapshotMap[snapshotID] = append(snapshotMap[snapshotID], snapshot)
|
|
||||||
|
|
||||||
if tag != "" && snapshot.Tag != tag {
|
if tag != "" && snapshot.Tag != tag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
snapshotMap[snapshotID] = append(snapshotMap[snapshotID], snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRevisions := 0
|
||||||
|
for _, snapshotList := range snapshotMap {
|
||||||
|
totalRevisions += len(snapshotList)
|
||||||
|
}
|
||||||
|
LOG_INFO("SNAPSHOT_CHECK", "%d snapshots and %d revisions", len(snapshotMap), totalRevisions)
|
||||||
|
|
||||||
|
var totalChunkSize int64
|
||||||
|
for _, size := range chunkSizeMap {
|
||||||
|
totalChunkSize += size
|
||||||
|
}
|
||||||
|
LOG_INFO("SNAPSHOT_CHECK", "Total chunk size is %s in %d chunks", PrettyNumber(totalChunkSize), len(chunkSizeMap))
|
||||||
|
|
||||||
|
for snapshotID, _ = range snapshotMap {
|
||||||
|
|
||||||
|
for _, snapshot := range snapshotMap[snapshotID] {
|
||||||
|
|
||||||
if checkFiles {
|
if checkFiles {
|
||||||
manager.DownloadSnapshotContents(snapshot, nil, false)
|
manager.DownloadSnapshotContents(snapshot, nil, false)
|
||||||
@@ -833,7 +864,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
|
|||||||
missingChunks += 1
|
missingChunks += 1
|
||||||
LOG_WARN("SNAPSHOT_VALIDATE",
|
LOG_WARN("SNAPSHOT_VALIDATE",
|
||||||
"Chunk %s referenced by snapshot %s at revision %d does not exist",
|
"Chunk %s referenced by snapshot %s at revision %d does not exist",
|
||||||
chunkID, snapshotID, revision)
|
chunkID, snapshotID, snapshot.Revision)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,7 +879,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
|
|||||||
missingChunks += 1
|
missingChunks += 1
|
||||||
LOG_WARN("SNAPSHOT_VALIDATE",
|
LOG_WARN("SNAPSHOT_VALIDATE",
|
||||||
"Chunk %s referenced by snapshot %s at revision %d does not exist",
|
"Chunk %s referenced by snapshot %s at revision %d does not exist",
|
||||||
chunkID, snapshotID, revision)
|
chunkID, snapshotID, snapshot.Revision)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,7 +887,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
|
|||||||
manager.resurrectChunk(chunkPath, chunkID)
|
manager.resurrectChunk(chunkPath, chunkID)
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("SNAPSHOT_FOSSIL", "Chunk %s referenced by snapshot %s at revision %d "+
|
LOG_WARN("SNAPSHOT_FOSSIL", "Chunk %s referenced by snapshot %s at revision %d "+
|
||||||
"has been marked as a fossil", chunkID, snapshotID, revision)
|
"has been marked as a fossil", chunkID, snapshotID, snapshot.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkSizeMap[chunkID] = size
|
chunkSizeMap[chunkID] = size
|
||||||
@@ -879,11 +910,11 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
|
|||||||
|
|
||||||
if missingChunks > 0 {
|
if missingChunks > 0 {
|
||||||
LOG_WARN("SNAPSHOT_CHECK", "Some chunks referenced by snapshot %s at revision %d are missing",
|
LOG_WARN("SNAPSHOT_CHECK", "Some chunks referenced by snapshot %s at revision %d are missing",
|
||||||
snapshotID, revision)
|
snapshotID, snapshot.Revision)
|
||||||
totalMissingChunks += missingChunks
|
totalMissingChunks += missingChunks
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("SNAPSHOT_CHECK", "All chunks referenced by snapshot %s at revision %d exist",
|
LOG_INFO("SNAPSHOT_CHECK", "All chunks referenced by snapshot %s at revision %d exist",
|
||||||
snapshotID, revision)
|
snapshotID, snapshot.Revision)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1907,7 +1938,7 @@ func (manager *SnapshotManager) PruneSnapshots(selfID string, snapshotID string,
|
|||||||
|
|
||||||
// Find out which retent policy applies based on the age.
|
// Find out which retent policy applies based on the age.
|
||||||
for i < len(retentionPolicies) &&
|
for i < len(retentionPolicies) &&
|
||||||
int(now-snapshot.StartTime) < retentionPolicies[i].Age*secondsInDay {
|
getDaysBetween(snapshot.StartTime, now) < retentionPolicies[i].Age {
|
||||||
i++
|
i++
|
||||||
lastSnapshotTime = 0
|
lastSnapshotTime = 0
|
||||||
}
|
}
|
||||||
@@ -1920,9 +1951,8 @@ func (manager *SnapshotManager) PruneSnapshots(selfID string, snapshotID string,
|
|||||||
snapshot.Flag = true
|
snapshot.Flag = true
|
||||||
toBeDeleted++
|
toBeDeleted++
|
||||||
} else if lastSnapshotTime != 0 &&
|
} else if lastSnapshotTime != 0 &&
|
||||||
int(snapshot.StartTime-lastSnapshotTime) < retentionPolicies[i].Interval*secondsInDay-600 {
|
getDaysBetween(lastSnapshotTime, snapshot.StartTime) < retentionPolicies[i].Interval {
|
||||||
// Delete the snapshot if it is too close to the last kept one. Note that a tolerance of 10
|
// Delete the snapshot if it is too close to the last kept one.
|
||||||
// minutes was subtracted from the interval.
|
|
||||||
LOG_DEBUG("SNAPSHOT_DELETE", "Snapshot %s at revision %d to be deleted - older than %d days, less than %d days from previous",
|
LOG_DEBUG("SNAPSHOT_DELETE", "Snapshot %s at revision %d to be deleted - older than %d days, less than %d days from previous",
|
||||||
snapshot.ID, snapshot.Revision, retentionPolicies[i].Age, retentionPolicies[i].Interval)
|
snapshot.ID, snapshot.Revision, retentionPolicies[i].Age, retentionPolicies[i].Interval)
|
||||||
snapshot.Flag = true
|
snapshot.Flag = true
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
|
|||||||
stat, ok := (*fileInfo).Sys().(*syscall.Stat_t)
|
stat, ok := (*fileInfo).Sys().(*syscall.Stat_t)
|
||||||
if ok && stat != nil && (int(stat.Uid) != entry.UID || int(stat.Gid) != entry.GID) {
|
if ok && stat != nil && (int(stat.Uid) != entry.UID || int(stat.Gid) != entry.GID) {
|
||||||
if entry.UID != -1 && entry.GID != -1 {
|
if entry.UID != -1 && entry.GID != -1 {
|
||||||
err := os.Chown(fullPath, entry.UID, entry.GID)
|
err := os.Lchown(fullPath, entry.UID, entry.GID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("RESTORE_CHOWN", "Failed to change uid or gid: %v", err)
|
LOG_ERROR("RESTORE_CHOWN", "Failed to change uid or gid: %v", err)
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user