github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/loggerutils/sharedtemp.go (about) 1 package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils" 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "runtime" 8 ) 9 10 type fileConvertFn func(dst io.WriteSeeker, src io.ReadSeeker) error 11 12 type stfID uint64 13 14 // sharedTempFileConverter converts files using a user-supplied function and 15 // writes the results to temporary files which are automatically cleaned up on 16 // close. If another request is made to convert the same file, the conversion 17 // result and temporary file are reused if they have not yet been cleaned up. 18 // 19 // A file is considered the same as another file using the os.SameFile function, 20 // which compares file identity (e.g. device and inode numbers on Linux) and is 21 // robust to file renames. Input files are assumed to be immutable; no attempt 22 // is made to ascertain whether the file contents have changed between requests. 23 // 24 // One file descriptor is used per source file, irrespective of the number of 25 // concurrent readers of the converted contents. 26 type sharedTempFileConverter struct { 27 // The directory where temporary converted files are to be written to. 28 // If set to the empty string, the default directory for temporary files 29 // is used. 30 TempDir string 31 32 conv fileConvertFn 33 st chan stfcState 34 } 35 36 type stfcState struct { 37 fl map[stfID]sharedTempFile 38 nextID stfID 39 } 40 41 type sharedTempFile struct { 42 src os.FileInfo // Info about the source file for path-independent identification with os.SameFile. 43 fd *os.File 44 size int64 45 ref int // Reference count of open readers on the temporary file. 46 wait []chan<- stfConvertResult // Wait list for the conversion to complete. 47 } 48 49 type stfConvertResult struct { 50 fr *sharedFileReader 51 err error 52 } 53 54 func newSharedTempFileConverter(conv fileConvertFn) *sharedTempFileConverter { 55 st := make(chan stfcState, 1) 56 st <- stfcState{fl: make(map[stfID]sharedTempFile)} 57 return &sharedTempFileConverter{conv: conv, st: st} 58 } 59 60 // Do returns a reader for the contents of f as converted by the c.C function. 61 // It is the caller's responsibility to close the returned reader. 62 // 63 // This function is safe for concurrent use by multiple goroutines. 64 func (c *sharedTempFileConverter) Do(f *os.File) (*sharedFileReader, error) { 65 stat, err := f.Stat() 66 if err != nil { 67 return nil, err 68 } 69 70 st := <-c.st 71 for id, tf := range st.fl { 72 // os.SameFile can have false positives if one of the files was 73 // deleted before the other file was created -- such as during 74 // log rotations... https://github.com/golang/go/issues/36895 75 // Weed out those false positives by also comparing the files' 76 // ModTime, which conveniently also handles the case of true 77 // positives where the file has also been modified since it was 78 // first converted. 79 if os.SameFile(tf.src, stat) && tf.src.ModTime() == stat.ModTime() { 80 return c.openExisting(st, id, tf) 81 } 82 } 83 return c.openNew(st, f, stat) 84 } 85 86 func (c *sharedTempFileConverter) openNew(st stfcState, f *os.File, stat os.FileInfo) (*sharedFileReader, error) { 87 // Record that we are starting to convert this file so that any other 88 // requests for the same source file while the conversion is in progress 89 // can join. 90 id := st.nextID 91 st.nextID++ 92 st.fl[id] = sharedTempFile{src: stat} 93 c.st <- st 94 95 dst, size, convErr := c.convert(f) 96 97 st = <-c.st 98 flid := st.fl[id] 99 100 if convErr != nil { 101 // Conversion failed. Delete it from the state so that future 102 // requests to convert the same file can try again fresh. 103 delete(st.fl, id) 104 c.st <- st 105 for _, w := range flid.wait { 106 w <- stfConvertResult{err: convErr} 107 } 108 return nil, convErr 109 } 110 111 flid.fd = dst 112 flid.size = size 113 flid.ref = len(flid.wait) + 1 114 for _, w := range flid.wait { 115 // Each waiter needs its own reader with an independent read pointer. 116 w <- stfConvertResult{fr: flid.Reader(c, id)} 117 } 118 flid.wait = nil 119 st.fl[id] = flid 120 c.st <- st 121 return flid.Reader(c, id), nil 122 } 123 124 func (c *sharedTempFileConverter) openExisting(st stfcState, id stfID, v sharedTempFile) (*sharedFileReader, error) { 125 if v.fd != nil { 126 // Already converted. 127 v.ref++ 128 st.fl[id] = v 129 c.st <- st 130 return v.Reader(c, id), nil 131 } 132 // The file has not finished being converted. 133 // Add ourselves to the wait list. "Don't call us; we'll call you." 134 wait := make(chan stfConvertResult, 1) 135 v.wait = append(v.wait, wait) 136 st.fl[id] = v 137 c.st <- st 138 139 res := <-wait 140 return res.fr, res.err 141 } 142 143 func (c *sharedTempFileConverter) convert(f *os.File) (converted *os.File, size int64, err error) { 144 dst, err := os.CreateTemp(c.TempDir, "dockerdtemp.*") 145 if err != nil { 146 return nil, 0, err 147 } 148 defer func() { 149 _ = dst.Close() 150 // Delete the temporary file immediately so that final cleanup 151 // of the file on disk is deferred to the OS once we close all 152 // our file descriptors (or the process dies). Assuming no early 153 // returns due to errors, the file will be open by this process 154 // with a read-only descriptor at this point. As we don't care 155 // about being able to reuse the file name -- it's randomly 156 // generated and unique -- we can safely use os.Remove on 157 // Windows. 158 _ = os.Remove(dst.Name()) 159 }() 160 err = c.conv(dst, f) 161 if err != nil { 162 return nil, 0, err 163 } 164 // Close the exclusive read-write file descriptor, catching any delayed 165 // write errors (and on Windows, releasing the share-locks on the file) 166 if err := dst.Close(); err != nil { 167 _ = os.Remove(dst.Name()) 168 return nil, 0, err 169 } 170 // Open the file again read-only (without locking the file against 171 // deletion on Windows). 172 converted, err = open(dst.Name()) 173 if err != nil { 174 return nil, 0, err 175 } 176 177 // The position of the file's read pointer doesn't matter as all readers 178 // will be accessing the file through its io.ReaderAt interface. 179 size, err = converted.Seek(0, io.SeekEnd) 180 if err != nil { 181 _ = converted.Close() 182 return nil, 0, err 183 } 184 return converted, size, nil 185 } 186 187 type sharedFileReader struct { 188 *io.SectionReader 189 190 c *sharedTempFileConverter 191 id stfID 192 closed bool 193 } 194 195 func (stf sharedTempFile) Reader(c *sharedTempFileConverter, id stfID) *sharedFileReader { 196 rdr := &sharedFileReader{SectionReader: io.NewSectionReader(stf.fd, 0, stf.size), c: c, id: id} 197 runtime.SetFinalizer(rdr, (*sharedFileReader).Close) 198 return rdr 199 } 200 201 func (r *sharedFileReader) Close() error { 202 if r.closed { 203 return fs.ErrClosed 204 } 205 206 st := <-r.c.st 207 flid, ok := st.fl[r.id] 208 if !ok { 209 panic("invariant violation: temp file state missing from map") 210 } 211 flid.ref-- 212 lastRef := flid.ref <= 0 213 if lastRef { 214 delete(st.fl, r.id) 215 } else { 216 st.fl[r.id] = flid 217 } 218 r.closed = true 219 r.c.st <- st 220 221 if lastRef { 222 return flid.fd.Close() 223 } 224 runtime.SetFinalizer(r, nil) 225 return nil 226 }