From 779e27dcf6a721da47d8736ecec06675ffbc4017 Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Wed, 30 Jun 2021 12:39:17 -0400 Subject: [PATCH] fsync all directories after completing a backup Previously the backup tool only fsync'ed the files. Since the directories were not synced, it was possible for the references to be lost. Now we sync the entire output directory tree and its parent. --- ctl/backup.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/ctl/backup.go b/ctl/backup.go index 1ce3c8e88..d4e8149bf 100644 --- a/ctl/backup.go +++ b/ctl/backup.go @@ -114,6 +114,12 @@ func (cmd *BackupCommand) Run(ctx context.Context) (err error) { } } + // Wait for the OS to persist all directories. + err = cmd.syncDirectories(ctx) + if err != nil { + return fmt.Errorf("syncing directories: %w", err) + } + return nil } @@ -367,3 +373,70 @@ func (cmd *BackupCommand) syncFile(f *os.File) error { func (cmd *BackupCommand) TLSHost() string { return cmd.Host } func (cmd *BackupCommand) TLSConfiguration() server.TLSConfig { return cmd.TLS } + +// syncDirectories fsyncs all directories required for the backup to be persisted to the filesystem. +func (cmd *BackupCommand) syncDirectories(ctx context.Context) error { + if cmd.NoSync { + return nil + } + + syncChan := make(chan string, cmd.Concurrency) + syncChan <- filepath.Dir(cmd.OutputDir) + g, ctx := errgroup.WithContext(ctx) + for i := 0; i < cmd.Concurrency; i++ { + g.Go(func() error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case path, ok := <-syncChan: + if !ok { + return nil + } else if err := cmd.syncDir(path); err != nil { + return fmt.Errorf("cannot sync directory %q: %w", path, err) + } + } + } + }) + } + + err := filepath.Walk(cmd.OutputDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + select { + case <-ctx.Done(): + return ctx.Err() + case syncChan <- path: + } + } + + return nil + }) + close(syncChan) + if err != nil { + return fmt.Errorf("walking output directory tree: %w", err) + } + + return g.Wait() +} + +func (cmd *BackupCommand) syncDir(path string) error { + logger := cmd.Logger() + logger.Printf("syncing directory: %s", path) + + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("opening directory for sync: %w", err) + } + defer f.Close() + + err = f.Sync() + if err != nil { + return fmt.Errorf("syncing directory: %w", err) + } + + return f.Close() +}