renameio.go raw

   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 i := 0; i < 10000; i++ {
  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  }
  94