github.com/gernest/nezuko@v0.1.2/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  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  )
    15  
    16  const patternSuffix = "*.tmp"
    17  
    18  // Pattern returns a glob pattern that matches the unrenamed temporary files
    19  // created when writing to filename.
    20  func Pattern(filename string) string {
    21  	return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
    22  }
    23  
    24  // WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
    25  // file in the same directory as filename, then renames it atomically to the
    26  // final name.
    27  //
    28  // That ensures that the final location, if it exists, is always a complete file.
    29  func WriteFile(filename string, data []byte) (err error) {
    30  	return WriteToFile(filename, bytes.NewReader(data))
    31  }
    32  
    33  // WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
    34  // instead of a slice.
    35  func WriteToFile(filename string, data io.Reader) (err error) {
    36  	f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	defer func() {
    41  		// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
    42  		// some other process may have created a new file with the same name after
    43  		// that.
    44  		if err != nil {
    45  			f.Close()
    46  			os.Remove(f.Name())
    47  		}
    48  	}()
    49  
    50  	if _, err := io.Copy(f, data); err != nil {
    51  		return err
    52  	}
    53  	// Sync the file before renaming it: otherwise, after a crash the reader may
    54  	// observe a 0-length file instead of the actual contents.
    55  	// See https://golang.org/issue/22397#issuecomment-380831736.
    56  	if err := f.Sync(); err != nil {
    57  		return err
    58  	}
    59  	if err := f.Close(); err != nil {
    60  		return err
    61  	}
    62  	return os.Rename(f.Name(), filename)
    63  }