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 }