1
0
mirror of https://github.com/gilbertchen/duplicacy synced 2025-12-06 00:03:38 +00:00

Fixed a bug that didn't preserve the version bit when copying old snapshots

The version bit should not be set to 1 when encoding a snapshot.  Instead,
it must be set to 1 on snapshot creation.

To correctly process old snapshots encoded incorrectly with version bit set
to 1, the first byte of the encoded file list is also checked.  If the first
byte is `[`, then it must be an old snapshot, since the file list in the new
snapshot format always starts with a string encoded in msgpack, the first
byte of which can't be `[`.
This commit is contained in:
Gilbert Chen
2022-11-22 21:31:24 -05:00
parent 0a794e6fea
commit 58f0d2be5a
2 changed files with 38 additions and 19 deletions

View File

@@ -15,7 +15,6 @@ import (
"strings"
"time"
"sort"
"bytes"
"github.com/vmihailenco/msgpack"
@@ -52,6 +51,7 @@ type Snapshot struct {
// CreateEmptySnapshot creates an empty snapshot.
func CreateEmptySnapshot(id string) (snapshto *Snapshot) {
return &Snapshot{
Version: 1,
ID: id,
Revision: 0,
StartTime: time.Now().Unix(),
@@ -112,22 +112,21 @@ func (snapshot *Snapshot)ListRemoteFiles(config *Config, chunkOperator *ChunkOpe
}
var chunk *Chunk
reader := sequenceReader{
sequence: snapshot.FileSequence,
buffer: new(bytes.Buffer),
refillFunc: func(chunkHash string) []byte {
if chunk != nil {
config.PutChunk(chunk)
}
chunk = chunkOperator.Download(chunkHash, 0, true)
return chunk.GetBytes()
},
}
reader := NewSequenceReader(snapshot.FileSequence, func(chunkHash string) []byte {
if chunk != nil {
config.PutChunk(chunk)
}
chunk = chunkOperator.Download(chunkHash, 0, true)
return chunk.GetBytes()
})
if snapshot.Version == 0 {
// Normally if Version is 0 then the snapshot is created by CLI v2 but unfortunately CLI 3.0.1 does not set the
// version bit correctly when copying old backups. So we need to check the first byte -- if it is '[' then it is
// the old format. The new format starts with a string encoded in msgpack and the first byte can't be '['.
if snapshot.Version == 0 || reader.GetFirstByte() == '['{
LOG_INFO("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in an old version format", snapshot.ID, snapshot.Revision)
files := make([]*Entry, 0)
decoder := json.NewDecoder(&reader)
decoder := json.NewDecoder(reader)
// read open bracket
_, err := decoder.Token()
@@ -156,7 +155,7 @@ func (snapshot *Snapshot)ListRemoteFiles(config *Config, chunkOperator *ChunkOpe
}
}
} else if snapshot.Version == 1 {
decoder := msgpack.NewDecoder(&reader)
decoder := msgpack.NewDecoder(reader)
lastEndChunk := 0
@@ -434,7 +433,7 @@ func (snapshot *Snapshot) MarshalJSON() ([]byte, error) {
object := make(map[string]interface{})
object["version"] = 1
object["version"] = snapshot.Version
object["id"] = snapshot.ID
object["revision"] = snapshot.Revision
object["options"] = snapshot.Options

View File

@@ -249,17 +249,27 @@ func (manager *SnapshotManager) DownloadSnapshot(snapshotID string, revision int
// the memory before passing them to the json unmarshaller.
type sequenceReader struct {
sequence []string
buffer *bytes.Buffer
buffer *bytes.Reader
index int
refillFunc func(hash string) []byte
}
func NewSequenceReader(sequence []string, refillFunc func(hash string) []byte) *sequenceReader {
newData := refillFunc(sequence[0])
return &sequenceReader{
sequence: sequence,
buffer: bytes.NewReader(newData),
index: 1,
refillFunc: refillFunc,
}
}
// Read reads a new chunk using the refill function when there is no more data in the buffer
func (reader *sequenceReader) Read(data []byte) (n int, err error) {
if len(reader.buffer.Bytes()) == 0 {
if reader.buffer.Len() == 0 {
if reader.index < len(reader.sequence) {
newData := reader.refillFunc(reader.sequence[reader.index])
reader.buffer.Write(newData)
reader.buffer = bytes.NewReader(newData)
reader.index++
} else {
return 0, io.EOF
@@ -269,6 +279,16 @@ func (reader *sequenceReader) Read(data []byte) (n int, err error) {
return reader.buffer.Read(data)
}
func (reader *sequenceReader) GetFirstByte() byte {
b, err := reader.buffer.ReadByte()
reader.buffer.UnreadByte()
if err != nil {
return 0
} else {
return b
}
}
func (manager *SnapshotManager) CreateChunkOperator(resurrect bool, rewriteChunks bool, threads int, allowFailures bool) {
if manager.chunkOperator == nil {
manager.chunkOperator = CreateChunkOperator(manager.config, manager.storage, manager.snapshotCache, resurrect, rewriteChunks, threads, allowFailures)