github.com/bhojpur/cache@v0.0.4/pkg/ioutils/fswriters.go (about)

     1  package ioutils
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  )
    28  
    29  // NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
    30  // temporary file and closing it atomically changes the temporary file to
    31  // destination path. Writing and closing concurrently is not allowed.
    32  func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
    33  	f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	abspath, err := filepath.Abs(filename)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return &atomicFileWriter{
    43  		f:    f,
    44  		fn:   abspath,
    45  		perm: perm,
    46  	}, nil
    47  }
    48  
    49  // AtomicWriteFile atomically writes data to a file named by filename.
    50  func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
    51  	f, err := NewAtomicFileWriter(filename, perm)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	n, err := f.Write(data)
    56  	if err == nil && n < len(data) {
    57  		err = io.ErrShortWrite
    58  		f.(*atomicFileWriter).writeErr = err
    59  	}
    60  	if err1 := f.Close(); err == nil {
    61  		err = err1
    62  	}
    63  	return err
    64  }
    65  
    66  type atomicFileWriter struct {
    67  	f        *os.File
    68  	fn       string
    69  	writeErr error
    70  	perm     os.FileMode
    71  }
    72  
    73  func (w *atomicFileWriter) Write(dt []byte) (int, error) {
    74  	n, err := w.f.Write(dt)
    75  	if err != nil {
    76  		w.writeErr = err
    77  	}
    78  	return n, err
    79  }
    80  
    81  func (w *atomicFileWriter) Close() (retErr error) {
    82  	defer func() {
    83  		if retErr != nil || w.writeErr != nil {
    84  			os.Remove(w.f.Name())
    85  		}
    86  	}()
    87  	if err := w.f.Sync(); err != nil {
    88  		w.f.Close()
    89  		return err
    90  	}
    91  	if err := w.f.Close(); err != nil {
    92  		return err
    93  	}
    94  	if err := os.Chmod(w.f.Name(), w.perm); err != nil {
    95  		return err
    96  	}
    97  	if w.writeErr == nil {
    98  		return os.Rename(w.f.Name(), w.fn)
    99  	}
   100  	return nil
   101  }
   102  
   103  // AtomicWriteSet is used to atomically write a set
   104  // of files and ensure they are visible at the same time.
   105  // Must be committed to a new directory.
   106  type AtomicWriteSet struct {
   107  	root string
   108  }
   109  
   110  // NewAtomicWriteSet creates a new atomic write set to
   111  // atomically create a set of files. The given directory
   112  // is used as the base directory for storing files before
   113  // commit. If no temporary directory is given the system
   114  // default is used.
   115  func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
   116  	td, err := os.MkdirTemp(tmpDir, "write-set-")
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	return &AtomicWriteSet{
   122  		root: td,
   123  	}, nil
   124  }
   125  
   126  // WriteFile writes a file to the set, guaranteeing the file
   127  // has been synced.
   128  func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
   129  	f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	n, err := f.Write(data)
   134  	if err == nil && n < len(data) {
   135  		err = io.ErrShortWrite
   136  	}
   137  	if err1 := f.Close(); err == nil {
   138  		err = err1
   139  	}
   140  	return err
   141  }
   142  
   143  type syncFileCloser struct {
   144  	*os.File
   145  }
   146  
   147  func (w syncFileCloser) Close() error {
   148  	err := w.File.Sync()
   149  	if err1 := w.File.Close(); err == nil {
   150  		err = err1
   151  	}
   152  	return err
   153  }
   154  
   155  // FileWriter opens a file writer inside the set. The file
   156  // should be synced and closed before calling commit.
   157  func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
   158  	f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	return syncFileCloser{f}, nil
   163  }
   164  
   165  // Cancel cancels the set and removes all temporary data
   166  // created in the set.
   167  func (ws *AtomicWriteSet) Cancel() error {
   168  	return os.RemoveAll(ws.root)
   169  }
   170  
   171  // Commit moves all created files to the target directory. The
   172  // target directory must not exist and the parent of the target
   173  // directory must exist.
   174  func (ws *AtomicWriteSet) Commit(target string) error {
   175  	return os.Rename(ws.root, target)
   176  }
   177  
   178  // String returns the location the set is writing to.
   179  func (ws *AtomicWriteSet) String() string {
   180  	return ws.root
   181  }