0270bec916
Several API tests were added to ensure correct acceptance of zero-size and empty tar files. This led to several changes in the storage backend around the guarantees of remote file reading, which backs the layer and layer upload type. In support of these changes, zero-length and empty checks have been added to the digest package. These provide a sanity check against upstream tarsum changes. The fileReader has been modified to be more robust when reading and seeking on zero-length or non-existent files. The file no longer needs to exist for the reader to be created. Seeks can now move beyond the end of the file, causing reads to issue an io.EOF. This eliminates errors during certain race conditions for reading files which should be detected by stat calls. As a part of this, a few error types were factored out and the read buffer size was increased to something more reasonable. Signed-off-by: Stephen J Day <stephen.day@docker.com>
151 lines
3.5 KiB
Go
151 lines
3.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/docker/distribution/storagedriver"
|
|
)
|
|
|
|
// fileWriter implements a remote file writer backed by a storage driver.
|
|
type fileWriter struct {
|
|
driver storagedriver.StorageDriver
|
|
|
|
// identifying fields
|
|
path string
|
|
|
|
// mutable fields
|
|
size int64 // size of the file, aka the current end
|
|
offset int64 // offset is the current write offset
|
|
err error // terminal error, if set, reader is closed
|
|
}
|
|
|
|
// fileWriterInterface makes the desired io compliant interface that the
|
|
// filewriter should implement.
|
|
type fileWriterInterface interface {
|
|
io.WriteSeeker
|
|
io.WriterAt
|
|
io.ReaderFrom
|
|
io.Closer
|
|
}
|
|
|
|
var _ fileWriterInterface = &fileWriter{}
|
|
|
|
// newFileWriter returns a prepared fileWriter for the driver and path. This
|
|
// could be considered similar to an "open" call on a regular filesystem.
|
|
func newFileWriter(driver storagedriver.StorageDriver, path string) (*fileWriter, error) {
|
|
fw := fileWriter{
|
|
driver: driver,
|
|
path: path,
|
|
}
|
|
|
|
if fi, err := driver.Stat(path); err != nil {
|
|
switch err := err.(type) {
|
|
case storagedriver.PathNotFoundError:
|
|
// ignore, offset is zero
|
|
default:
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if fi.IsDir() {
|
|
return nil, fmt.Errorf("cannot write to a directory")
|
|
}
|
|
|
|
fw.size = fi.Size()
|
|
}
|
|
|
|
return &fw, nil
|
|
}
|
|
|
|
// Write writes the buffer p at the current write offset.
|
|
func (fw *fileWriter) Write(p []byte) (n int, err error) {
|
|
nn, err := fw.readFromAt(bytes.NewReader(p), -1)
|
|
return int(nn), err
|
|
}
|
|
|
|
// WriteAt writes p at the specified offset. The underlying offset does not
|
|
// change.
|
|
func (fw *fileWriter) WriteAt(p []byte, offset int64) (n int, err error) {
|
|
nn, err := fw.readFromAt(bytes.NewReader(p), offset)
|
|
return int(nn), err
|
|
}
|
|
|
|
// ReadFrom reads reader r until io.EOF writing the contents at the current
|
|
// offset.
|
|
func (fw *fileWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
|
return fw.readFromAt(r, -1)
|
|
}
|
|
|
|
// Seek moves the write position do the requested offest based on the whence
|
|
// argument, which can be os.SEEK_CUR, os.SEEK_END, or os.SEEK_SET.
|
|
func (fw *fileWriter) Seek(offset int64, whence int) (int64, error) {
|
|
if fw.err != nil {
|
|
return 0, fw.err
|
|
}
|
|
|
|
var err error
|
|
newOffset := fw.offset
|
|
|
|
switch whence {
|
|
case os.SEEK_CUR:
|
|
newOffset += int64(offset)
|
|
case os.SEEK_END:
|
|
newOffset = fw.size + int64(offset)
|
|
case os.SEEK_SET:
|
|
newOffset = int64(offset)
|
|
}
|
|
|
|
if newOffset < 0 {
|
|
err = fmt.Errorf("cannot seek to negative position")
|
|
} else {
|
|
// No problems, set the offset.
|
|
fw.offset = newOffset
|
|
}
|
|
|
|
return fw.offset, err
|
|
}
|
|
|
|
// Close closes the fileWriter for writing.
|
|
func (fw *fileWriter) Close() error {
|
|
if fw.err != nil {
|
|
return fw.err
|
|
}
|
|
|
|
fw.err = fmt.Errorf("filewriter@%v: closed", fw.path)
|
|
|
|
return fw.err
|
|
}
|
|
|
|
// readFromAt writes to fw from r at the specified offset. If offset is less
|
|
// than zero, the value of fw.offset is used and updated after the operation.
|
|
func (fw *fileWriter) readFromAt(r io.Reader, offset int64) (n int64, err error) {
|
|
if fw.err != nil {
|
|
return 0, fw.err
|
|
}
|
|
|
|
var updateOffset bool
|
|
if offset < 0 {
|
|
offset = fw.offset
|
|
updateOffset = true
|
|
}
|
|
|
|
nn, err := fw.driver.WriteStream(fw.path, offset, r)
|
|
|
|
if updateOffset {
|
|
// We should forward the offset, whether or not there was an error.
|
|
// Basically, we keep the filewriter in sync with the reader's head. If an
|
|
// error is encountered, the whole thing should be retried but we proceed
|
|
// from an expected offset, even if the data didn't make it to the
|
|
// backend.
|
|
fw.offset += nn
|
|
|
|
if fw.offset > fw.size {
|
|
fw.size = fw.offset
|
|
}
|
|
}
|
|
|
|
return nn, err
|
|
}
|