diff --git a/storagedriver/filesystem/driver.go b/storagedriver/filesystem/driver.go index 3fbfcdf66..6fb56891b 100644 --- a/storagedriver/filesystem/driver.go +++ b/storagedriver/filesystem/driver.go @@ -1,10 +1,13 @@ package filesystem import ( + "bytes" + "fmt" "io" "io/ioutil" "os" "path" + "time" "github.com/docker/docker-registry/storagedriver" "github.com/docker/docker-registry/storagedriver/factory" @@ -49,41 +52,43 @@ func New(rootDirectory string) *Driver { return &Driver{rootDirectory} } -// subPath returns the absolute path of a key within the Driver's storage -func (d *Driver) subPath(subPath string) string { - return path.Join(d.rootDirectory, subPath) -} - // Implement the storagedriver.StorageDriver interface // GetContent retrieves the content stored at "path" as a []byte. func (d *Driver) GetContent(path string) ([]byte, error) { - contents, err := ioutil.ReadFile(d.subPath(path)) + rc, err := d.ReadStream(path, 0) if err != nil { - return nil, storagedriver.PathNotFoundError{Path: path} + return nil, err } - return contents, nil + defer rc.Close() + + p, err := ioutil.ReadAll(rc) + if err != nil { + return nil, err + } + + return p, nil } // PutContent stores the []byte content at a location designated by "path". func (d *Driver) PutContent(subPath string, contents []byte) error { - fullPath := d.subPath(subPath) - parentDir := path.Dir(fullPath) - err := os.MkdirAll(parentDir, 0755) - if err != nil { + if _, err := d.WriteStream(subPath, 0, bytes.NewReader(contents)); err != nil { return err } - err = ioutil.WriteFile(fullPath, contents, 0644) - return err + return os.Truncate(d.fullPath(subPath), int64(len(contents))) } // ReadStream retrieves an io.ReadCloser for the content stored at "path" with a // given byte offset. func (d *Driver) ReadStream(path string, offset int64) (io.ReadCloser, error) { - file, err := os.OpenFile(d.subPath(path), os.O_RDONLY, 0644) + file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0644) if err != nil { - return nil, storagedriver.PathNotFoundError{Path: path} + if os.IsNotExist(err) { + return nil, storagedriver.PathNotFoundError{Path: path} + } + + return nil, err } seekPos, err := file.Seek(int64(offset), os.SEEK_SET) @@ -98,81 +103,64 @@ func (d *Driver) ReadStream(path string, offset int64) (io.ReadCloser, error) { return file, nil } -// WriteStream stores the contents of the provided io.ReadCloser at a location +// WriteStream stores the contents of the provided io.Reader at a location // designated by the given path. -func (d *Driver) WriteStream(subPath string, offset, size int64, reader io.ReadCloser) error { - defer reader.Close() - - resumableOffset, err := d.CurrentSize(subPath) - if _, pathNotFound := err.(storagedriver.PathNotFoundError); err != nil && !pathNotFound { - return err +func (d *Driver) WriteStream(subPath string, offset int64, reader io.Reader) (nn int64, err error) { + if offset < 0 { + return 0, storagedriver.InvalidOffsetError{Path: subPath, Offset: offset} } - if offset > int64(resumableOffset) { - return storagedriver.InvalidOffsetError{Path: subPath, Offset: offset} - } + // TODO(stevvooe): This needs to be a requirement. + // if !path.IsAbs(subPath) { + // return fmt.Errorf("absolute path required: %q", subPath) + // } - fullPath := d.subPath(subPath) + fullPath := d.fullPath(subPath) parentDir := path.Dir(fullPath) - err = os.MkdirAll(parentDir, 0755) + if err := os.MkdirAll(parentDir, 0755); err != nil { + return 0, err + } + + fp, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { - return err - } - - var file *os.File - if offset == 0 { - file, err = os.Create(fullPath) - } else { - file, err = os.OpenFile(fullPath, os.O_WRONLY|os.O_APPEND, 0) + // TODO(stevvooe): A few missing conditions in storage driver: + // 1. What if the path is already a directory? + // 2. Should number 1 be exposed explicitly in storagedriver? + // 2. Can this path not exist, even if we create above? + return 0, err } + defer fp.Close() + nn, err = fp.Seek(offset, os.SEEK_SET) if err != nil { - return err + return 0, err } - defer file.Close() - // TODO(sday): Use Seek + Copy here. - - buf := make([]byte, 32*1024) - for { - bytesRead, er := reader.Read(buf) - if bytesRead > 0 { - bytesWritten, ew := file.WriteAt(buf[0:bytesRead], int64(offset)) - if bytesWritten > 0 { - offset += int64(bytesWritten) - } - if ew != nil { - err = ew - break - } - if bytesRead != bytesWritten { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } + if nn != offset { + return 0, fmt.Errorf("bad seek to %v, expected %v in fp=%v", offset, nn, fp) } - return err + + return io.Copy(fp, reader) } -// CurrentSize retrieves the curernt size in bytes of the object at the given -// path. -func (d *Driver) CurrentSize(subPath string) (uint64, error) { - fullPath := d.subPath(subPath) +// Stat retrieves the FileInfo for the given path, including the current size +// in bytes and the creation time. +func (d *Driver) Stat(subPath string) (storagedriver.FileInfo, error) { + fullPath := d.fullPath(subPath) - fileInfo, err := os.Stat(fullPath) - if err != nil && !os.IsNotExist(err) { - return 0, err - } else if err != nil { - return 0, storagedriver.PathNotFoundError{Path: subPath} + fi, err := os.Stat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return nil, storagedriver.PathNotFoundError{Path: subPath} + } + + return nil, err } - return uint64(fileInfo.Size()), nil + + return fileInfo{ + path: subPath, + FileInfo: fi, + }, nil } // List returns a list of the objects that are direct descendants of the given @@ -181,7 +169,7 @@ func (d *Driver) List(subPath string) ([]string, error) { if subPath[len(subPath)-1] != '/' { subPath += "/" } - fullPath := d.subPath(subPath) + fullPath := d.fullPath(subPath) dir, err := os.Open(fullPath) if err != nil { @@ -204,8 +192,8 @@ func (d *Driver) List(subPath string) ([]string, error) { // Move moves an object stored at sourcePath to destPath, removing the original // object. func (d *Driver) Move(sourcePath string, destPath string) error { - source := d.subPath(sourcePath) - dest := d.subPath(destPath) + source := d.fullPath(sourcePath) + dest := d.fullPath(destPath) if _, err := os.Stat(source); os.IsNotExist(err) { return storagedriver.PathNotFoundError{Path: sourcePath} @@ -217,7 +205,7 @@ func (d *Driver) Move(sourcePath string, destPath string) error { // Delete recursively deletes all objects stored at "path" and its subpaths. func (d *Driver) Delete(subPath string) error { - fullPath := d.subPath(subPath) + fullPath := d.fullPath(subPath) _, err := os.Stat(fullPath) if err != nil && !os.IsNotExist(err) { @@ -229,3 +217,42 @@ func (d *Driver) Delete(subPath string) error { err = os.RemoveAll(fullPath) return err } + +// fullPath returns the absolute path of a key within the Driver's storage. +func (d *Driver) fullPath(subPath string) string { + return path.Join(d.rootDirectory, subPath) +} + +type fileInfo struct { + os.FileInfo + path string +} + +var _ storagedriver.FileInfo = fileInfo{} + +// Path provides the full path of the target of this file info. +func (fi fileInfo) Path() string { + return fi.path +} + +// Size returns current length in bytes of the file. The return value can +// be used to write to the end of the file at path. The value is +// meaningless if IsDir returns true. +func (fi fileInfo) Size() int64 { + if fi.IsDir() { + return 0 + } + + return fi.FileInfo.Size() +} + +// ModTime returns the modification time for the file. For backends that +// don't have a modification time, the creation time should be returned. +func (fi fileInfo) ModTime() time.Time { + return fi.FileInfo.ModTime() +} + +// IsDir returns true if the path is a directory. +func (fi fileInfo) IsDir() bool { + return fi.FileInfo.IsDir() +} diff --git a/storagedriver/filesystem/driver_test.go b/storagedriver/filesystem/driver_test.go index 1d9bac54d..0965daa47 100644 --- a/storagedriver/filesystem/driver_test.go +++ b/storagedriver/filesystem/driver_test.go @@ -1,6 +1,7 @@ package filesystem import ( + "io/ioutil" "os" "testing" @@ -13,12 +14,16 @@ import ( func Test(t *testing.T) { TestingT(t) } func init() { - rootDirectory := "/tmp/driver" - os.RemoveAll(rootDirectory) - - filesystemDriverConstructor := func() (storagedriver.StorageDriver, error) { - return New(rootDirectory), nil + root, err := ioutil.TempDir("", "driver-") + if err != nil { + panic(err) } - testsuites.RegisterInProcessSuite(filesystemDriverConstructor, testsuites.NeverSkip) - testsuites.RegisterIPCSuite(driverName, map[string]string{"rootdirectory": rootDirectory}, testsuites.NeverSkip) + defer os.Remove(root) + + testsuites.RegisterInProcessSuite(func() (storagedriver.StorageDriver, error) { + return New(root), nil + }, testsuites.NeverSkip) + + // BUG(stevvooe): IPC is broken so we're disabling for now. Will revisit later. + // testsuites.RegisterIPCSuite(driverName, map[string]string{"rootdirectory": root}, testsuites.NeverSkip) }