[snapshot restore] use non-atomic writes (#1534)

* don't flush every file two times on snapshot restore
This commit is contained in:
Lukas Rieger
2021-11-26 22:10:44 +01:00
committed by GitHub
parent b91d8ac992
commit 5224f79d7d
3 changed files with 41 additions and 2 deletions

View File

@@ -105,6 +105,7 @@ type commandRestore struct {
restoreMode string
restoreParallel int
restoreIgnorePermissionErrors bool
restoreWriteFilesAtomically bool
restoreSkipTimes bool
restoreSkipOwners bool
restoreSkipPermissions bool
@@ -131,6 +132,7 @@ func (c *commandRestore) setup(svc appServices, parent commandParent) {
cmd.Flag("skip-permissions", "Skip permissions during restore").BoolVar(&c.restoreSkipPermissions)
cmd.Flag("skip-times", "Skip times during restore").BoolVar(&c.restoreSkipTimes)
cmd.Flag("ignore-permission-errors", "Ignore permission errors").Default("true").BoolVar(&c.restoreIgnorePermissionErrors)
cmd.Flag("write-files-atomically", "Write files atomically to disk, ensuring they are either fully committed, or not written at all, preventing partially written files").Default("false").BoolVar(&c.restoreWriteFilesAtomically)
cmd.Flag("ignore-errors", "Ignore all errors").BoolVar(&c.restoreIgnoreErrors)
cmd.Flag("skip-existing", "Skip files and symlinks that exist in the output").BoolVar(&c.restoreIncremental)
cmd.Flag("shallow", "Shallow restore the directory hierarchy starting at this level (default is to deep restore the entire hierarchy.)").Int32Var(&c.restoreShallowAtDepth)
@@ -214,6 +216,7 @@ func (c *commandRestore) restoreOutput(ctx context.Context) (restore.Output, err
OverwriteFiles: c.restoreOverwriteFiles,
OverwriteSymlinks: c.restoreOverwriteSymlinks,
IgnorePermissionErrors: c.restoreIgnorePermissionErrors,
WriteFilesAtomically: c.restoreWriteFilesAtomically,
SkipOwners: c.restoreSkipOwners,
SkipPermissions: c.restoreSkipPermissions,
SkipTimes: c.restoreSkipTimes,

View File

@@ -23,6 +23,7 @@ export class BeginRestore extends Component {
overwriteDirectories: false,
overwriteSymlinks: false,
ignorePermissionErrors: true,
writeFilesAtomically: false,
restoreDirEntryAtDepth: 1000,
minSizeForPlaceholder: 0,
restoreTask: "",
@@ -67,6 +68,7 @@ export class BeginRestore extends Component {
overwriteFiles: this.state.overwriteFiles,
overwriteDirectories: this.state.overwriteDirectories,
overwriteSymlinks: this.state.overwriteSymlinks,
writeFilesAtomically: this.state.writeFilesAtomically
}
}
@@ -123,6 +125,9 @@ export class BeginRestore extends Component {
<Row>
{RequiredBoolean(this, "Overwrite Symbolic Links", "overwriteSymlinks")}
</Row>
<Row>
{RequiredBoolean(this, "Write files atomically", "writeFilesAtomically")}
</Row>
<Row>
<Col><hr/></Col>
</Row>

View File

@@ -14,6 +14,7 @@
"github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/localfs"
"github.com/kopia/kopia/internal/atomicfile"
"github.com/kopia/kopia/internal/iocopy"
"github.com/kopia/kopia/snapshot"
)
@@ -44,6 +45,9 @@ type FilesystemOutput struct {
// IgnorePermissionErrors causes restore to ignore errors due to invalid permissions.
IgnorePermissionErrors bool `json:"ignorePermissionErrors"`
// When set to true, first write to a temp file and rename it, to ensure there are no partially written files in case of a crash.
WriteFilesAtomically bool `json:"writeFilesAtomically"`
// SkipOwners when set to true causes restore to skip restoring owner information.
SkipOwners bool `json:"skipOwners"`
@@ -299,6 +303,29 @@ func (o *FilesystemOutput) createDirectory(ctx context.Context, path string) err
}
}
func write(targetPath string, r fs.Reader) error {
f, err := os.OpenFile(targetPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) //nolint:gosec,gomnd
if err != nil {
return err //nolint:wrapcheck
}
// ensure we always close f. Note that this does not conflict with the
// close below, as close is idempotent.
defer f.Close() //nolint:errcheck,gosec
name := f.Name()
if err := iocopy.JustCopy(f, r); err != nil {
return errors.Wrap(err, "cannot write data to file %q "+name)
}
if err := f.Close(); err != nil {
return err //nolint:wrapcheck
}
return nil
}
func (o *FilesystemOutput) copyFileContent(ctx context.Context, targetPath string, f fs.File) error {
switch _, err := os.Stat(targetPath); {
case os.IsNotExist(err): // copy file below
@@ -320,8 +347,12 @@ func (o *FilesystemOutput) copyFileContent(ctx context.Context, targetPath strin
log(ctx).Debugf("copying file contents to: %v", targetPath)
// nolint:wrapcheck
return atomicfile.Write(targetPath, r)
if o.WriteFilesAtomically {
// nolint:wrapcheck
return atomicfile.Write(targetPath, r)
}
return write(atomicfile.MaybePrefixLongFilenameOnWindows(targetPath), r)
}
func isEmptyDirectory(name string) (bool, error) {