// 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" "math/rand" "os" "path/filepath" "strconv" "honnef.co/go/tools/internal/robustio" ) 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, perm os.FileMode) (err error) { return WriteToFile(filename, bytes.NewReader(data), perm) } // 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, perm os.FileMode) (err error) { f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm) 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 robustio.Rename(f.Name(), filename) } // tempFile creates a new temporary file with given permission bits. func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) { for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix) f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) if os.IsExist(err) { continue } break } return } // ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that // may occur if the file is concurrently replaced. // // Errors are classified heuristically and retries are bounded, so even this // function may occasionally return a spurious error on Windows. // If so, the error will likely wrap one of: // - syscall.ERROR_ACCESS_DENIED // - syscall.ERROR_FILE_NOT_FOUND // - internal/syscall/windows.ERROR_SHARING_VIOLATION func ReadFile(filename string) ([]byte, error) { return robustio.ReadFile(filename) }