pkg/private/fpath: subsume AtomicWriteFile

AtomicWriteFile is useful primitive to use throughout the codebase

Change-Id: I338fc4505ba20d5aece09ddc257286f46298e083
This commit is contained in:
Andrew Harding 2019-11-15 16:33:19 -07:00 committed by paul cannon
parent 4d2881b711
commit 2461ccd469
2 changed files with 44 additions and 31 deletions

View File

@ -6,9 +6,6 @@ package process
import ( import (
"bytes" "bytes"
"flag" "flag"
"io/ioutil"
"os"
"path/filepath"
"sort" "sort"
"github.com/spf13/cast" "github.com/spf13/cast"
@ -18,6 +15,7 @@ import (
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"storj.io/storj/pkg/cfgstruct" "storj.io/storj/pkg/cfgstruct"
"storj.io/storj/private/fpath"
) )
// SaveConfigOption is a function that updates the options for SaveConfig. // SaveConfigOption is a function that updates the options for SaveConfig.
@ -201,7 +199,7 @@ func SaveConfig(cmd *cobra.Command, outfile string, opts ...SaveConfigOption) er
lines = append(lines, nil) lines = append(lines, nil)
} }
return errs.Wrap(atomicWrite(outfile, 0600, bytes.Join(lines, nl))) return errs.Wrap(fpath.AtomicWriteFile(outfile, bytes.Join(lines, nl), 0600))
} }
// readSourceAnnotation is a helper to return the source annotation or cfgstruct.AnySource if unset. // readSourceAnnotation is a helper to return the source annotation or cfgstruct.AnySource if unset.
@ -218,30 +216,3 @@ func readBoolAnnotation(flag *pflag.Flag, key string) bool {
annotation := flag.Annotations[key] annotation := flag.Annotations[key]
return len(annotation) > 0 && annotation[0] == "true" return len(annotation) > 0 && annotation[0] == "true"
} }
// atomicWrite is a helper to atomically write the data to the outfile.
func atomicWrite(outfile string, mode os.FileMode, data []byte) (err error) {
fh, err := ioutil.TempFile(filepath.Dir(outfile), filepath.Base(outfile))
if err != nil {
return errs.Wrap(err)
}
defer func() {
if err != nil {
err = errs.Combine(err, fh.Close())
err = errs.Combine(err, os.Remove(fh.Name()))
}
}()
if _, err := fh.Write(data); err != nil {
return errs.Wrap(err)
}
if err := fh.Sync(); err != nil {
return errs.Wrap(err)
}
if err := fh.Close(); err != nil {
return errs.Wrap(err)
}
if err := os.Rename(fh.Name(), outfile); err != nil {
return errs.Wrap(err)
}
return nil
}

42
private/fpath/atomic.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package fpath
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/zeebo/errs"
)
// AtomicWriteFile is a helper to atomically write the data to the outfile.
func AtomicWriteFile(outfile string, data []byte, mode os.FileMode) (err error) {
// TODO: provide better atomicity guarantees, like fsyncing the parent
// directory and, on windows, using MoveFileEx with MOVEFILE_WRITE_THROUGH.
fh, err := ioutil.TempFile(filepath.Dir(outfile), filepath.Base(outfile))
if err != nil {
return errs.Wrap(err)
}
defer func() {
if err != nil {
err = errs.Combine(err, fh.Close())
err = errs.Combine(err, os.Remove(fh.Name()))
}
}()
if _, err := fh.Write(data); err != nil {
return errs.Wrap(err)
}
if err := fh.Sync(); err != nil {
return errs.Wrap(err)
}
if err := fh.Close(); err != nil {
return errs.Wrap(err)
}
if err := os.Rename(fh.Name(), outfile); err != nil {
return errs.Wrap(err)
}
return nil
}