github.com/lusis/distribution@v2.0.1+incompatible/registry/storage/filewriter.go (about)

     1  package storage
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  
    10  	storagedriver "github.com/docker/distribution/registry/storage/driver"
    11  )
    12  
    13  const (
    14  	fileWriterBufferSize = 5 << 20
    15  )
    16  
    17  // fileWriter implements a remote file writer backed by a storage driver.
    18  type fileWriter struct {
    19  	driver storagedriver.StorageDriver
    20  
    21  	// identifying fields
    22  	path string
    23  
    24  	// mutable fields
    25  	size   int64 // size of the file, aka the current end
    26  	offset int64 // offset is the current write offset
    27  	err    error // terminal error, if set, reader is closed
    28  }
    29  
    30  type bufferedFileWriter struct {
    31  	fileWriter
    32  	bw *bufio.Writer
    33  }
    34  
    35  // fileWriterInterface makes the desired io compliant interface that the
    36  // filewriter should implement.
    37  type fileWriterInterface interface {
    38  	io.WriteSeeker
    39  	io.WriterAt
    40  	io.ReaderFrom
    41  	io.Closer
    42  }
    43  
    44  var _ fileWriterInterface = &fileWriter{}
    45  
    46  // newFileWriter returns a prepared fileWriter for the driver and path. This
    47  // could be considered similar to an "open" call on a regular filesystem.
    48  func newFileWriter(driver storagedriver.StorageDriver, path string) (*bufferedFileWriter, error) {
    49  	fw := fileWriter{
    50  		driver: driver,
    51  		path:   path,
    52  	}
    53  
    54  	if fi, err := driver.Stat(path); err != nil {
    55  		switch err := err.(type) {
    56  		case storagedriver.PathNotFoundError:
    57  			// ignore, offset is zero
    58  		default:
    59  			return nil, err
    60  		}
    61  	} else {
    62  		if fi.IsDir() {
    63  			return nil, fmt.Errorf("cannot write to a directory")
    64  		}
    65  
    66  		fw.size = fi.Size()
    67  	}
    68  
    69  	buffered := bufferedFileWriter{
    70  		fileWriter: fw,
    71  	}
    72  	buffered.bw = bufio.NewWriterSize(&buffered.fileWriter, fileWriterBufferSize)
    73  
    74  	return &buffered, nil
    75  }
    76  
    77  // wraps the fileWriter.Write method to buffer small writes
    78  func (bfw *bufferedFileWriter) Write(p []byte) (int, error) {
    79  	return bfw.bw.Write(p)
    80  }
    81  
    82  // wraps fileWriter.Close to ensure the buffer is flushed
    83  // before we close the writer.
    84  func (bfw *bufferedFileWriter) Close() (err error) {
    85  	if err = bfw.Flush(); err != nil {
    86  		return err
    87  	}
    88  	err = bfw.fileWriter.Close()
    89  	return err
    90  }
    91  
    92  // wraps fileWriter.Seek to ensure offset is handled
    93  // correctly in respect to pending data in the buffer
    94  func (bfw *bufferedFileWriter) Seek(offset int64, whence int) (int64, error) {
    95  	if err := bfw.Flush(); err != nil {
    96  		return 0, err
    97  	}
    98  	return bfw.fileWriter.Seek(offset, whence)
    99  }
   100  
   101  // wraps bufio.Writer.Flush to allow intermediate flushes
   102  // of the bufferedFileWriter
   103  func (bfw *bufferedFileWriter) Flush() error {
   104  	return bfw.bw.Flush()
   105  }
   106  
   107  // Write writes the buffer p at the current write offset.
   108  func (fw *fileWriter) Write(p []byte) (n int, err error) {
   109  	nn, err := fw.readFromAt(bytes.NewReader(p), -1)
   110  	return int(nn), err
   111  }
   112  
   113  // WriteAt writes p at the specified offset. The underlying offset does not
   114  // change.
   115  func (fw *fileWriter) WriteAt(p []byte, offset int64) (n int, err error) {
   116  	nn, err := fw.readFromAt(bytes.NewReader(p), offset)
   117  	return int(nn), err
   118  }
   119  
   120  // ReadFrom reads reader r until io.EOF writing the contents at the current
   121  // offset.
   122  func (fw *fileWriter) ReadFrom(r io.Reader) (n int64, err error) {
   123  	return fw.readFromAt(r, -1)
   124  }
   125  
   126  // Seek moves the write position do the requested offest based on the whence
   127  // argument, which can be os.SEEK_CUR, os.SEEK_END, or os.SEEK_SET.
   128  func (fw *fileWriter) Seek(offset int64, whence int) (int64, error) {
   129  	if fw.err != nil {
   130  		return 0, fw.err
   131  	}
   132  
   133  	var err error
   134  	newOffset := fw.offset
   135  
   136  	switch whence {
   137  	case os.SEEK_CUR:
   138  		newOffset += int64(offset)
   139  	case os.SEEK_END:
   140  		newOffset = fw.size + int64(offset)
   141  	case os.SEEK_SET:
   142  		newOffset = int64(offset)
   143  	}
   144  
   145  	if newOffset < 0 {
   146  		err = fmt.Errorf("cannot seek to negative position")
   147  	} else {
   148  		// No problems, set the offset.
   149  		fw.offset = newOffset
   150  	}
   151  
   152  	return fw.offset, err
   153  }
   154  
   155  // Close closes the fileWriter for writing.
   156  // Calling it once is valid and correct and it will
   157  // return a nil error. Calling it subsequent times will
   158  // detect that fw.err has been set and will return the error.
   159  func (fw *fileWriter) Close() error {
   160  	if fw.err != nil {
   161  		return fw.err
   162  	}
   163  
   164  	fw.err = fmt.Errorf("filewriter@%v: closed", fw.path)
   165  
   166  	return nil
   167  }
   168  
   169  // readFromAt writes to fw from r at the specified offset. If offset is less
   170  // than zero, the value of fw.offset is used and updated after the operation.
   171  func (fw *fileWriter) readFromAt(r io.Reader, offset int64) (n int64, err error) {
   172  	if fw.err != nil {
   173  		return 0, fw.err
   174  	}
   175  
   176  	var updateOffset bool
   177  	if offset < 0 {
   178  		offset = fw.offset
   179  		updateOffset = true
   180  	}
   181  
   182  	nn, err := fw.driver.WriteStream(fw.path, offset, r)
   183  
   184  	if updateOffset {
   185  		// We should forward the offset, whether or not there was an error.
   186  		// Basically, we keep the filewriter in sync with the reader's head. If an
   187  		// error is encountered, the whole thing should be retried but we proceed
   188  		// from an expected offset, even if the data didn't make it to the
   189  		// backend.
   190  		fw.offset += nn
   191  
   192  		if fw.offset > fw.size {
   193  			fw.size = fw.offset
   194  		}
   195  	}
   196  
   197  	return nn, err
   198  }