| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- // 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"
- "runtime"
- "strings"
- "time"
- )
- 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
- }
- var start time.Time
- for {
- err := os.Rename(f.Name(), filename)
- if err == nil || runtime.GOOS != "windows" || !strings.HasSuffix(err.Error(), "Access is denied.") {
- return err
- }
- // Windows seems to occasionally trigger spurious "Access is denied" errors
- // here (see golang.org/issue/31247). We're not sure why. It's probably
- // worth a little extra latency to avoid propagating the spurious errors.
- if start.IsZero() {
- start = time.Now()
- } else if time.Since(start) >= 500*time.Millisecond {
- return err
- }
- time.Sleep(5 * time.Millisecond)
- }
- }
|