]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go/internal/renameio: add package
authorBryan C. Mills <bcmills@google.com>
Wed, 31 Oct 2018 19:42:44 +0000 (15:42 -0400)
committerBryan C. Mills <bcmills@google.com>
Thu, 29 Nov 2018 15:45:19 +0000 (15:45 +0000)
renameio.WriteFile writes files atomically by renaming temporary files.

See the subsequent changes for usage examples.

Updates #26794
Updates #22397

Change-Id: I4bfe3125a53f58060587f98afbb4260bb1cc3d32
Reviewed-on: https://go-review.googlesource.com/c/146377
Run-TryBot: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Giovanni Bajo <rasky@develer.com>
Reviewed-by: Russ Cox <rsc@golang.org>
src/cmd/go/internal/renameio/renameio.go [new file with mode: 0644]

diff --git a/src/cmd/go/internal/renameio/renameio.go b/src/cmd/go/internal/renameio/renameio.go
new file mode 100644 (file)
index 0000000..8f59e1a
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package renameio writes files atomically by renaming temporary files.
+package renameio
+
+import (
+       "bytes"
+       "io"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+)
+
+const patternSuffix = "*.tmp"
+
+// Pattern returns a glob pattern that matches the unrenamed temporary files
+// created when writing to filename.
+func Pattern(filename string) string {
+       return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
+}
+
+// WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
+// file in the same directory as filename, then renames it atomically to the
+// final name.
+//
+// That ensures that the final location, if it exists, is always a complete file.
+func WriteFile(filename string, data []byte) (err error) {
+       return WriteToFile(filename, bytes.NewReader(data))
+}
+
+// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
+// instead of a slice.
+func WriteToFile(filename string, data io.Reader) (err error) {
+       f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
+       if err != nil {
+               return err
+       }
+       defer func() {
+               // Only call os.Remove on f.Name() if we failed to rename it: otherwise,
+               // some other process may have created a new file with the same name after
+               // that.
+               if err != nil {
+                       f.Close()
+                       os.Remove(f.Name())
+               }
+       }()
+
+       if _, err := io.Copy(f, data); err != nil {
+               return err
+       }
+       // Sync the file before renaming it: otherwise, after a crash the reader may
+       // observe a 0-length file instead of the actual contents.
+       // See https://golang.org/issue/22397#issuecomment-380831736.
+       if err := f.Sync(); err != nil {
+               return err
+       }
+       if err := f.Close(); err != nil {
+               return err
+       }
+       return os.Rename(f.Name(), filename)
+}