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:
commit
2d47381b1d
8
changelog/unreleased/issue-2389
Normal file
8
changelog/unreleased/issue-2389
Normal 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
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
Loading…
Reference in New Issue
Block a user