Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/renameio/renameio.go

Documentation: cmd/go/internal/renameio

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

View as plain text