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  }