2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 06:46:34 +00:00

Merge pull request #2545 from MichaelEischer/fix-racy-backup-json

Fix racy json output for backup command
This commit is contained in:
rawtaz 2020-02-13 22:33:17 +01:00 committed by GitHub
commit 2d47381b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 22 deletions

View File

@ -0,0 +1,8 @@
Bugfix: Fix mangled json output of backup command
We've fixed a race condition in the json output of the backup command
that could cause multiple lines to get mixed up. We've also ensured that
the backup summary is printed last.
https://github.com/restic/restic/issues/2389
https://github.com/restic/restic/pull/2545

View File

@ -25,7 +25,7 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/textfile"
"github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/jsonstatus" "github.com/restic/restic/internal/ui/json"
"github.com/restic/restic/internal/ui/termstatus" "github.com/restic/restic/internal/ui/termstatus"
) )
@ -447,7 +447,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
var p ArchiveProgressReporter var p ArchiveProgressReporter
if gopts.JSON { if gopts.JSON {
p = jsonstatus.NewBackup(term, gopts.verbosity) p = json.NewBackup(term, gopts.verbosity)
} else { } else {
p = ui.NewBackup(term, gopts.verbosity) p = ui.NewBackup(term, gopts.verbosity)
} }
@ -601,19 +601,18 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
return errors.Fatalf("unable to save snapshot: %v", err) return errors.Fatalf("unable to save snapshot: %v", err)
} }
p.Finish(id)
if !gopts.JSON {
p.P("snapshot %s saved\n", id.Str())
}
// cleanly shutdown all running goroutines // cleanly shutdown all running goroutines
t.Kill(nil) t.Kill(nil)
// let's see if one returned an error // let's see if one returned an error
err = t.Wait() err = t.Wait()
if err != nil {
return err // Report finished execution
p.Finish(id)
if !gopts.JSON {
p.P("snapshot %s saved\n", id.Str())
} }
return nil // Return error if any
return err
} }

View File

@ -1,6 +1,7 @@
package jsonstatus package json
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"os" "os"
@ -74,6 +75,20 @@ func NewBackup(term *termstatus.Terminal, verbosity uint) *Backup {
} }
} }
func toJSONString(status interface{}) string {
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(status)
return buf.String()
}
func (b *Backup) print(status interface{}) {
b.term.Print(toJSONString(status))
}
func (b *Backup) error(status interface{}) {
b.term.Error(toJSONString(status))
}
// Run regularly updates the status lines. It should be called in a separate // Run regularly updates the status lines. It should be called in a separate
// goroutine. // goroutine.
func (b *Backup) Run(ctx context.Context) error { func (b *Backup) Run(ctx context.Context) error {
@ -162,13 +177,13 @@ func (b *Backup) update(total, processed counter, errors uint, currentFiles map[
} }
sort.Strings(status.CurrentFiles) sort.Strings(status.CurrentFiles)
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(status) b.print(status)
} }
// ScannerError is the error callback function for the scanner, it prints the // ScannerError is the error callback function for the scanner, it prints the
// error in verbose mode and returns nil. // error in verbose mode and returns nil.
func (b *Backup) ScannerError(item string, fi os.FileInfo, err error) error { func (b *Backup) ScannerError(item string, fi os.FileInfo, err error) error {
json.NewEncoder(b.StdioWrapper.Stderr()).Encode(errorUpdate{ b.error(errorUpdate{
MessageType: "error", MessageType: "error",
Error: err, Error: err,
During: "scan", During: "scan",
@ -179,7 +194,7 @@ func (b *Backup) ScannerError(item string, fi os.FileInfo, err error) error {
// Error is the error callback function for the archiver, it prints the error and returns nil. // Error is the error callback function for the archiver, it prints the error and returns nil.
func (b *Backup) Error(item string, fi os.FileInfo, err error) error { func (b *Backup) Error(item string, fi os.FileInfo, err error) error {
json.NewEncoder(b.StdioWrapper.Stderr()).Encode(errorUpdate{ b.error(errorUpdate{
MessageType: "error", MessageType: "error",
Error: err, Error: err,
During: "archival", During: "archival",
@ -233,7 +248,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
if current.Type == "dir" { if current.Type == "dir" {
if previous == nil { if previous == nil {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "new", Action: "new",
Item: item, Item: item,
@ -250,7 +265,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
if previous.Equals(*current) { if previous.Equals(*current) {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "unchanged", Action: "unchanged",
Item: item, Item: item,
@ -261,7 +276,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
b.summary.Unlock() b.summary.Unlock()
} else { } else {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "modified", Action: "modified",
Item: item, Item: item,
@ -284,7 +299,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
if previous == nil { if previous == nil {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "new", Action: "new",
Item: item, Item: item,
@ -300,7 +315,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
if previous.Equals(*current) { if previous.Equals(*current) {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "unchanged", Action: "unchanged",
Item: item, Item: item,
@ -311,7 +326,7 @@ func (b *Backup) CompleteItem(item string, previous, current *restic.Node, s arc
b.summary.Unlock() b.summary.Unlock()
} else { } else {
if b.v >= 3 { if b.v >= 3 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "verbose_status", MessageType: "verbose_status",
Action: "modified", Action: "modified",
Item: item, Item: item,
@ -335,7 +350,7 @@ func (b *Backup) ReportTotal(item string, s archiver.ScanStats) {
if item == "" { if item == "" {
if b.v >= 2 { if b.v >= 2 {
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(verboseUpdate{ b.print(verboseUpdate{
MessageType: "status", MessageType: "status",
Action: "scan_finished", Action: "scan_finished",
Duration: time.Since(b.start).Seconds(), Duration: time.Since(b.start).Seconds(),
@ -351,7 +366,7 @@ func (b *Backup) ReportTotal(item string, s archiver.ScanStats) {
// Finish prints the finishing messages. // Finish prints the finishing messages.
func (b *Backup) Finish(snapshotID restic.ID) { func (b *Backup) Finish(snapshotID restic.ID) {
close(b.finished) close(b.finished)
json.NewEncoder(b.StdioWrapper.Stdout()).Encode(summaryOutput{ b.print(summaryOutput{
MessageType: "summary", MessageType: "summary",
FilesNew: b.summary.Files.New, FilesNew: b.summary.Files.New,
FilesChanged: b.summary.Files.Changed, FilesChanged: b.summary.Files.Changed,