123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
- import (
- "io"
- "io/fs"
- "os"
- "runtime"
- )
- type fileConvertFn func(dst io.WriteSeeker, src io.ReadSeeker) error
- type stfID uint64
- // sharedTempFileConverter converts files using a user-supplied function and
- // writes the results to temporary files which are automatically cleaned up on
- // close. If another request is made to convert the same file, the conversion
- // result and temporary file are reused if they have not yet been cleaned up.
- //
- // A file is considered the same as another file using the os.SameFile function,
- // which compares file identity (e.g. device and inode numbers on Linux) and is
- // robust to file renames. Input files are assumed to be immutable; no attempt
- // is made to ascertain whether the file contents have changed between requests.
- //
- // One file descriptor is used per source file, irrespective of the number of
- // concurrent readers of the converted contents.
- type sharedTempFileConverter struct {
- // The directory where temporary converted files are to be written to.
- // If set to the empty string, the default directory for temporary files
- // is used.
- TempDir string
- conv fileConvertFn
- st chan stfcState
- }
- type stfcState struct {
- fl map[stfID]sharedTempFile
- nextID stfID
- }
- type sharedTempFile struct {
- src os.FileInfo // Info about the source file for path-independent identification with os.SameFile.
- fd *os.File
- size int64
- ref int // Reference count of open readers on the temporary file.
- wait []chan<- stfConvertResult // Wait list for the conversion to complete.
- }
- type stfConvertResult struct {
- fr *sharedFileReader
- err error
- }
- func newSharedTempFileConverter(conv fileConvertFn) *sharedTempFileConverter {
- st := make(chan stfcState, 1)
- st <- stfcState{fl: make(map[stfID]sharedTempFile)}
- return &sharedTempFileConverter{conv: conv, st: st}
- }
- // Do returns a reader for the contents of f as converted by the c.C function.
- // It is the caller's responsibility to close the returned reader.
- //
- // This function is safe for concurrent use by multiple goroutines.
- func (c *sharedTempFileConverter) Do(f *os.File) (*sharedFileReader, error) {
- stat, err := f.Stat()
- if err != nil {
- return nil, err
- }
- st := <-c.st
- for id, tf := range st.fl {
- // os.SameFile can have false positives if one of the files was
- // deleted before the other file was created -- such as during
- // log rotations... https://github.com/golang/go/issues/36895
- // Weed out those false positives by also comparing the files'
- // ModTime, which conveniently also handles the case of true
- // positives where the file has also been modified since it was
- // first converted.
- if os.SameFile(tf.src, stat) && tf.src.ModTime() == stat.ModTime() {
- return c.openExisting(st, id, tf)
- }
- }
- return c.openNew(st, f, stat)
- }
- func (c *sharedTempFileConverter) openNew(st stfcState, f *os.File, stat os.FileInfo) (*sharedFileReader, error) {
- // Record that we are starting to convert this file so that any other
- // requests for the same source file while the conversion is in progress
- // can join.
- id := st.nextID
- st.nextID++
- st.fl[id] = sharedTempFile{src: stat}
- c.st <- st
- dst, size, convErr := c.convert(f)
- st = <-c.st
- flid := st.fl[id]
- if convErr != nil {
- // Conversion failed. Delete it from the state so that future
- // requests to convert the same file can try again fresh.
- delete(st.fl, id)
- c.st <- st
- for _, w := range flid.wait {
- w <- stfConvertResult{err: convErr}
- }
- return nil, convErr
- }
- flid.fd = dst
- flid.size = size
- flid.ref = len(flid.wait) + 1
- for _, w := range flid.wait {
- // Each waiter needs its own reader with an independent read pointer.
- w <- stfConvertResult{fr: flid.Reader(c, id)}
- }
- flid.wait = nil
- st.fl[id] = flid
- c.st <- st
- return flid.Reader(c, id), nil
- }
- func (c *sharedTempFileConverter) openExisting(st stfcState, id stfID, v sharedTempFile) (*sharedFileReader, error) {
- if v.fd != nil {
- // Already converted.
- v.ref++
- st.fl[id] = v
- c.st <- st
- return v.Reader(c, id), nil
- }
- // The file has not finished being converted.
- // Add ourselves to the wait list. "Don't call us; we'll call you."
- wait := make(chan stfConvertResult, 1)
- v.wait = append(v.wait, wait)
- st.fl[id] = v
- c.st <- st
- res := <-wait
- return res.fr, res.err
- }
- func (c *sharedTempFileConverter) convert(f *os.File) (converted *os.File, size int64, err error) {
- dst, err := os.CreateTemp(c.TempDir, "dockerdtemp.*")
- if err != nil {
- return nil, 0, err
- }
- defer func() {
- _ = dst.Close()
- // Delete the temporary file immediately so that final cleanup
- // of the file on disk is deferred to the OS once we close all
- // our file descriptors (or the process dies). Assuming no early
- // returns due to errors, the file will be open by this process
- // with a read-only descriptor at this point. As we don't care
- // about being able to reuse the file name -- it's randomly
- // generated and unique -- we can safely use os.Remove on
- // Windows.
- _ = os.Remove(dst.Name())
- }()
- err = c.conv(dst, f)
- if err != nil {
- return nil, 0, err
- }
- // Close the exclusive read-write file descriptor, catching any delayed
- // write errors (and on Windows, releasing the share-locks on the file)
- if err := dst.Close(); err != nil {
- _ = os.Remove(dst.Name())
- return nil, 0, err
- }
- // Open the file again read-only (without locking the file against
- // deletion on Windows).
- converted, err = open(dst.Name())
- if err != nil {
- return nil, 0, err
- }
- // The position of the file's read pointer doesn't matter as all readers
- // will be accessing the file through its io.ReaderAt interface.
- size, err = converted.Seek(0, io.SeekEnd)
- if err != nil {
- _ = converted.Close()
- return nil, 0, err
- }
- return converted, size, nil
- }
- type sharedFileReader struct {
- *io.SectionReader
- c *sharedTempFileConverter
- id stfID
- closed bool
- }
- func (stf sharedTempFile) Reader(c *sharedTempFileConverter, id stfID) *sharedFileReader {
- rdr := &sharedFileReader{SectionReader: io.NewSectionReader(stf.fd, 0, stf.size), c: c, id: id}
- runtime.SetFinalizer(rdr, (*sharedFileReader).Close)
- return rdr
- }
- func (r *sharedFileReader) Close() error {
- if r.closed {
- return fs.ErrClosed
- }
- st := <-r.c.st
- flid, ok := st.fl[r.id]
- if !ok {
- panic("invariant violation: temp file state missing from map")
- }
- flid.ref--
- lastRef := flid.ref <= 0
- if lastRef {
- delete(st.fl, r.id)
- } else {
- st.fl[r.id] = flid
- }
- r.closed = true
- r.c.st <- st
- if lastRef {
- return flid.fd.Close()
- }
- runtime.SetFinalizer(r, nil)
- return nil
- }
|