...

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

     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		"cmd/go/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	

View as plain text