diff options
author | Tonis Tiigi <tonistiigi@gmail.com> | 2016-04-20 17:08:47 -0700 |
---|---|---|
committer | Tonis Tiigi <tonistiigi@gmail.com> | 2016-04-21 11:31:15 -0700 |
commit | ea3cbd3274664f5b16fce78d7df036f6b5c94e30 (patch) | |
tree | 7b2fbcb619e4e04108333e73f31060becd75550e /pkg/ioutils | |
parent | 6f67c13d20b207e5ab9a038b04ccc44d337131ed (diff) | |
download | docker-ea3cbd3274664f5b16fce78d7df036f6b5c94e30.tar.gz |
Safer file io for configuration files
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Diffstat (limited to 'pkg/ioutils')
-rw-r--r-- | pkg/ioutils/fswriters.go | 75 | ||||
-rw-r--r-- | pkg/ioutils/fswriters_test.go | 31 |
2 files changed, 106 insertions, 0 deletions
diff --git a/pkg/ioutils/fswriters.go b/pkg/ioutils/fswriters.go new file mode 100644 index 0000000000..ca97670724 --- /dev/null +++ b/pkg/ioutils/fswriters.go @@ -0,0 +1,75 @@ +package ioutils + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// NewAtomicFileWriter returns WriteCloser so that writing to it writes to a +// temporary file and closing it atomically changes the temporary file to +// destination path. Writing and closing concurrently is not allowed. +func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) { + f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename)) + if err != nil { + return nil, err + } + abspath, err := filepath.Abs(filename) + if err != nil { + return nil, err + } + return &atomicFileWriter{ + f: f, + fn: abspath, + }, nil +} + +// AtomicWriteFile atomically writes data to a file named by filename. +func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { + f, err := NewAtomicFileWriter(filename, perm) + if err != nil { + return err + } + n, err := f.Write(data) + if err == nil && n < len(data) { + err = io.ErrShortWrite + } + if err1 := f.Close(); err == nil { + err = err1 + } + return err +} + +type atomicFileWriter struct { + f *os.File + fn string + writeErr error +} + +func (w *atomicFileWriter) Write(dt []byte) (int, error) { + n, err := w.f.Write(dt) + if err != nil { + w.writeErr = err + } + return n, err +} + +func (w *atomicFileWriter) Close() (retErr error) { + defer func() { + if retErr != nil { + os.Remove(w.f.Name()) + } + }() + if err := w.f.Sync(); err != nil { + w.f.Close() + return err + } + if err := w.f.Close(); err != nil { + return err + } + if w.writeErr == nil { + return os.Rename(w.f.Name(), w.fn) + } + return nil +} diff --git a/pkg/ioutils/fswriters_test.go b/pkg/ioutils/fswriters_test.go new file mode 100644 index 0000000000..40717a5108 --- /dev/null +++ b/pkg/ioutils/fswriters_test.go @@ -0,0 +1,31 @@ +package ioutils + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestAtomicWriteToFile(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "atomic-writers-test") + if err != nil { + t.Fatalf("Error when creating temporary directory: %s", err) + } + defer os.RemoveAll(tmpDir) + + expected := []byte("barbaz") + if err := AtomicWriteFile(filepath.Join(tmpDir, "foo"), expected, 0600); err != nil { + t.Fatalf("Error writing to file: %v", err) + } + + actual, err := ioutil.ReadFile(filepath.Join(tmpDir, "foo")) + if err != nil { + t.Fatalf("Error reading from file: %v", err) + } + + if bytes.Compare(actual, expected) != 0 { + t.Fatalf("Data mismatch, expected %q, got %q", expected, actual) + } +} |