github.com/nozzle/golangci-lint@v1.49.0-nz3/internal/renameio/renameio.go (about)

     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  	"github.com/golangci/golangci-lint/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 os.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  // tempFile creates a new temporary file with given permission bits.
    70  func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
    71  	for i := 0; i < 10000; i++ {
    72  		name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
    73  		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
    74  		if os.IsExist(err) {
    75  			continue
    76  		}
    77  		break
    78  	}
    79  	return
    80  }
    81  
    82  // ReadFile is like os.ReadFile, but on Windows retries spurious errors that
    83  // may occur if the file is concurrently replaced.
    84  //
    85  // Errors are classified heuristically and retries are bounded, so even this
    86  // function may occasionally return a spurious error on Windows.
    87  // If so, the error will likely wrap one of:
    88  //     - syscall.ERROR_ACCESS_DENIED
    89  //     - syscall.ERROR_FILE_NOT_FOUND
    90  //     - internal/syscall/windows.ERROR_SHARING_VIOLATION
    91  func ReadFile(filename string) ([]byte, error) {
    92  	return robustio.ReadFile(filename)
    93  }