From 58f0d2be5a4356307dfecba56f1fd0ea982f6a92 Mon Sep 17 00:00:00 2001 From: Gilbert Chen Date: Tue, 22 Nov 2022 21:31:24 -0500 Subject: [PATCH] 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 `[`. --- src/duplicacy_snapshot.go | 31 +++++++++++++++---------------- src/duplicacy_snapshotmanager.go | 26 +++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/duplicacy_snapshot.go b/src/duplicacy_snapshot.go index 4fe8df7..e7511b6 100644 --- a/src/duplicacy_snapshot.go +++ b/src/duplicacy_snapshot.go @@ -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 diff --git a/src/duplicacy_snapshotmanager.go b/src/duplicacy_snapshotmanager.go index c44c194..1a94f09 100644 --- a/src/duplicacy_snapshotmanager.go +++ b/src/duplicacy_snapshotmanager.go @@ -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)