435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
// 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)
|
|
}
|