github.com/cdoern/storage@v1.12.13/pkg/ioutils/fswriters.go (about)

     1  package ioutils
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  )
     9  
    10  // NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
    11  // temporary file and closing it atomically changes the temporary file to
    12  // destination path. Writing and closing concurrently is not allowed.
    13  func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
    14  	f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
    15  	if err != nil {
    16  		return nil, err
    17  	}
    18  
    19  	abspath, err := filepath.Abs(filename)
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  	return &atomicFileWriter{
    24  		f:    f,
    25  		fn:   abspath,
    26  		perm: perm,
    27  	}, nil
    28  }
    29  
    30  // AtomicWriteFile atomically writes data to a file named by filename.
    31  func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
    32  	f, err := NewAtomicFileWriter(filename, perm)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	n, err := f.Write(data)
    37  	if err == nil && n < len(data) {
    38  		err = io.ErrShortWrite
    39  		f.(*atomicFileWriter).writeErr = err
    40  	}
    41  	if err1 := f.Close(); err == nil {
    42  		err = err1
    43  	}
    44  	return err
    45  }
    46  
    47  type atomicFileWriter struct {
    48  	f        *os.File
    49  	fn       string
    50  	writeErr error
    51  	perm     os.FileMode
    52  }
    53  
    54  func (w *atomicFileWriter) Write(dt []byte) (int, error) {
    55  	n, err := w.f.Write(dt)
    56  	if err != nil {
    57  		w.writeErr = err
    58  	}
    59  	return n, err
    60  }
    61  
    62  func (w *atomicFileWriter) Close() (retErr error) {
    63  	defer func() {
    64  		if retErr != nil || w.writeErr != nil {
    65  			os.Remove(w.f.Name())
    66  		}
    67  	}()
    68  	if err := w.f.Sync(); err != nil {
    69  		w.f.Close()
    70  		return err
    71  	}
    72  	if err := w.f.Close(); err != nil {
    73  		return err
    74  	}
    75  	if err := os.Chmod(w.f.Name(), w.perm); err != nil {
    76  		return err
    77  	}
    78  	if w.writeErr == nil {
    79  		return os.Rename(w.f.Name(), w.fn)
    80  	}
    81  	return nil
    82  }
    83  
    84  // AtomicWriteSet is used to atomically write a set
    85  // of files and ensure they are visible at the same time.
    86  // Must be committed to a new directory.
    87  type AtomicWriteSet struct {
    88  	root string
    89  }
    90  
    91  // NewAtomicWriteSet creates a new atomic write set to
    92  // atomically create a set of files. The given directory
    93  // is used as the base directory for storing files before
    94  // commit. If no temporary directory is given the system
    95  // default is used.
    96  func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
    97  	td, err := ioutil.TempDir(tmpDir, "write-set-")
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return &AtomicWriteSet{
   103  		root: td,
   104  	}, nil
   105  }
   106  
   107  // WriteFile writes a file to the set, guaranteeing the file
   108  // has been synced.
   109  func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
   110  	f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	n, err := f.Write(data)
   115  	if err == nil && n < len(data) {
   116  		err = io.ErrShortWrite
   117  	}
   118  	if err1 := f.Close(); err == nil {
   119  		err = err1
   120  	}
   121  	return err
   122  }
   123  
   124  type syncFileCloser struct {
   125  	*os.File
   126  }
   127  
   128  func (w syncFileCloser) Close() error {
   129  	err := w.File.Sync()
   130  	if err1 := w.File.Close(); err == nil {
   131  		err = err1
   132  	}
   133  	return err
   134  }
   135  
   136  // FileWriter opens a file writer inside the set. The file
   137  // should be synced and closed before calling commit.
   138  func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
   139  	f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	return syncFileCloser{f}, nil
   144  }
   145  
   146  // Cancel cancels the set and removes all temporary data
   147  // created in the set.
   148  func (ws *AtomicWriteSet) Cancel() error {
   149  	return os.RemoveAll(ws.root)
   150  }
   151  
   152  // Commit moves all created files to the target directory. The
   153  // target directory must not exist and the parent of the target
   154  // directory must exist.
   155  func (ws *AtomicWriteSet) Commit(target string) error {
   156  	return os.Rename(ws.root, target)
   157  }
   158  
   159  // String returns the location the set is writing to.
   160  func (ws *AtomicWriteSet) String() string {
   161  	return ws.root
   162  }