| package ioutils |
| |
| import ( |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| ) |
| |
| // NewAtomicFileWriter returns WriteCloser so that writing to it writes to a |
| // temporary file and closing it atomically changes the temporary file to |
| // destination path. Writing and closing concurrently is not allowed. |
| func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) { |
| f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename)) |
| if err != nil { |
| return nil, err |
| } |
| |
| abspath, err := filepath.Abs(filename) |
| if err != nil { |
| return nil, err |
| } |
| return &atomicFileWriter{ |
| f: f, |
| fn: abspath, |
| perm: perm, |
| }, nil |
| } |
| |
| // AtomicWriteFile atomically writes data to a file named by filename. |
| func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { |
| f, err := NewAtomicFileWriter(filename, perm) |
| if err != nil { |
| return err |
| } |
| n, err := f.Write(data) |
| if err == nil && n < len(data) { |
| err = io.ErrShortWrite |
| f.(*atomicFileWriter).writeErr = err |
| } |
| if err1 := f.Close(); err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| type atomicFileWriter struct { |
| f *os.File |
| fn string |
| writeErr error |
| perm os.FileMode |
| } |
| |
| func (w *atomicFileWriter) Write(dt []byte) (int, error) { |
| n, err := w.f.Write(dt) |
| if err != nil { |
| w.writeErr = err |
| } |
| return n, err |
| } |
| |
| func (w *atomicFileWriter) Close() (retErr error) { |
| defer func() { |
| if retErr != nil || w.writeErr != nil { |
| os.Remove(w.f.Name()) |
| } |
| }() |
| if err := w.f.Sync(); err != nil { |
| w.f.Close() |
| return err |
| } |
| if err := w.f.Close(); err != nil { |
| return err |
| } |
| if err := os.Chmod(w.f.Name(), w.perm); err != nil { |
| return err |
| } |
| if w.writeErr == nil { |
| return os.Rename(w.f.Name(), w.fn) |
| } |
| return nil |
| } |
| |
| // AtomicWriteSet is used to atomically write a set |
| // of files and ensure they are visible at the same time. |
| // Must be committed to a new directory. |
| type AtomicWriteSet struct { |
| root string |
| } |
| |
| // NewAtomicWriteSet creates a new atomic write set to |
| // atomically create a set of files. The given directory |
| // is used as the base directory for storing files before |
| // commit. If no temporary directory is given the system |
| // default is used. |
| func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) { |
| td, err := ioutil.TempDir(tmpDir, "write-set-") |
| if err != nil { |
| return nil, err |
| } |
| |
| return &AtomicWriteSet{ |
| root: td, |
| }, nil |
| } |
| |
| // WriteFile writes a file to the set, guaranteeing the file |
| // has been synced. |
| func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error { |
| f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) |
| if err != nil { |
| return err |
| } |
| n, err := f.Write(data) |
| if err == nil && n < len(data) { |
| err = io.ErrShortWrite |
| } |
| if err1 := f.Close(); err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| type syncFileCloser struct { |
| *os.File |
| } |
| |
| func (w syncFileCloser) Close() error { |
| err := w.File.Sync() |
| if err1 := w.File.Close(); err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| // FileWriter opens a file writer inside the set. The file |
| // should be synced and closed before calling commit. |
| func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) { |
| f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm) |
| if err != nil { |
| return nil, err |
| } |
| return syncFileCloser{f}, nil |
| } |
| |
| // Cancel cancels the set and removes all temporary data |
| // created in the set. |
| func (ws *AtomicWriteSet) Cancel() error { |
| return os.RemoveAll(ws.root) |
| } |
| |
| // Commit moves all created files to the target directory. The |
| // target directory must not exist and the parent of the target |
| // directory must exist. |
| func (ws *AtomicWriteSet) Commit(target string) error { |
| return os.Rename(ws.root, target) |
| } |
| |
| // String returns the location the set is writing to. |
| func (ws *AtomicWriteSet) String() string { |
| return ws.root |
| } |