github.com/rumpl/bof@v23.0.0-rc.2+incompatible/pkg/ioutils/fswriters.go (about)

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