src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

renameio.go (2894B)


      1 // Copyright 2018 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package renameio writes files atomically by renaming temporary files.
      6 package renameio
      7 
      8 import (
      9 	"bytes"
     10 	"io"
     11 	"math/rand"
     12 	"os"
     13 	"path/filepath"
     14 	"strconv"
     15 
     16 	"honnef.co/go/tools/internal/robustio"
     17 )
     18 
     19 const patternSuffix = ".tmp"
     20 
     21 // Pattern returns a glob pattern that matches the unrenamed temporary files
     22 // created when writing to filename.
     23 func Pattern(filename string) string {
     24 	return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
     25 }
     26 
     27 // WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
     28 // file in the same directory as filename, then renames it atomically to the
     29 // final name.
     30 //
     31 // That ensures that the final location, if it exists, is always a complete file.
     32 func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
     33 	return WriteToFile(filename, bytes.NewReader(data), perm)
     34 }
     35 
     36 // WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
     37 // instead of a slice.
     38 func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) {
     39 	f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm)
     40 	if err != nil {
     41 		return err
     42 	}
     43 	defer func() {
     44 		// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
     45 		// some other process may have created a new file with the same name after
     46 		// that.
     47 		if err != nil {
     48 			f.Close()
     49 			os.Remove(f.Name())
     50 		}
     51 	}()
     52 
     53 	if _, err := io.Copy(f, data); err != nil {
     54 		return err
     55 	}
     56 	// Sync the file before renaming it: otherwise, after a crash the reader may
     57 	// observe a 0-length file instead of the actual contents.
     58 	// See https://golang.org/issue/22397#issuecomment-380831736.
     59 	if err := f.Sync(); err != nil {
     60 		return err
     61 	}
     62 	if err := f.Close(); err != nil {
     63 		return err
     64 	}
     65 
     66 	return robustio.Rename(f.Name(), filename)
     67 }
     68 
     69 // ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that
     70 // may occur if the file is concurrently replaced.
     71 //
     72 // Errors are classified heuristically and retries are bounded, so even this
     73 // function may occasionally return a spurious error on Windows.
     74 // If so, the error will likely wrap one of:
     75 //   - syscall.ERROR_ACCESS_DENIED
     76 //   - syscall.ERROR_FILE_NOT_FOUND
     77 //   - internal/syscall/windows.ERROR_SHARING_VIOLATION
     78 func ReadFile(filename string) ([]byte, error) {
     79 	return robustio.ReadFile(filename)
     80 }
     81 
     82 // tempFile creates a new temporary file with given permission bits.
     83 func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
     84 	for range 10000 {
     85 		name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
     86 		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
     87 		if os.IsExist(err) {
     88 			continue
     89 		}
     90 		break
     91 	}
     92 	return
     93 }