mirror of
https://github.com/kopia/kopia.git
synced 2026-05-07 14:26:51 -04:00
[snapshot restore] use non-atomic writes (#1534)
* don't flush every file two times on snapshot restore
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user