2014-10-21 22:02:20 +00:00
|
|
|
package filesystem
|
|
|
|
|
|
|
|
import (
|
2014-12-04 00:44:20 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2014-10-21 22:02:20 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
2014-12-04 00:44:20 +00:00
|
|
|
"time"
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-24 00:01:38 +00:00
|
|
|
"github.com/docker/distribution/storagedriver"
|
|
|
|
"github.com/docker/distribution/storagedriver/factory"
|
2014-10-21 22:02:20 +00:00
|
|
|
)
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
const driverName = "filesystem"
|
|
|
|
const defaultRootDirectory = "/tmp/registry/storage"
|
2014-10-29 01:15:40 +00:00
|
|
|
|
|
|
|
func init() {
|
2014-11-17 23:44:07 +00:00
|
|
|
factory.Register(driverName, &filesystemDriverFactory{})
|
2014-10-29 01:15:40 +00:00
|
|
|
}
|
|
|
|
|
2014-10-29 19:14:19 +00:00
|
|
|
// filesystemDriverFactory implements the factory.StorageDriverFactory interface
|
2014-10-29 01:15:40 +00:00
|
|
|
type filesystemDriverFactory struct{}
|
|
|
|
|
2014-12-18 03:06:55 +00:00
|
|
|
func (factory *filesystemDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) {
|
2014-10-29 01:15:40 +00:00
|
|
|
return FromParameters(parameters), nil
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Driver is a storagedriver.StorageDriver implementation backed by a local
|
|
|
|
// filesystem. All provided paths will be subpaths of the RootDirectory
|
|
|
|
type Driver struct {
|
2014-10-21 22:02:20 +00:00
|
|
|
rootDirectory string
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// FromParameters constructs a new Driver with a given parameters map
|
2014-10-29 01:15:40 +00:00
|
|
|
// Optional Parameters:
|
|
|
|
// - rootdirectory
|
2014-12-18 03:06:55 +00:00
|
|
|
func FromParameters(parameters map[string]interface{}) *Driver {
|
2014-11-17 23:44:07 +00:00
|
|
|
var rootDirectory = defaultRootDirectory
|
2014-10-29 01:15:40 +00:00
|
|
|
if parameters != nil {
|
|
|
|
rootDir, ok := parameters["rootdirectory"]
|
|
|
|
if ok {
|
2014-12-18 03:06:55 +00:00
|
|
|
rootDirectory = fmt.Sprint(rootDir)
|
2014-10-29 01:15:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return New(rootDirectory)
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// New constructs a new Driver with a given rootDirectory
|
|
|
|
func New(rootDirectory string) *Driver {
|
|
|
|
return &Driver{rootDirectory}
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-10-29 01:15:40 +00:00
|
|
|
// Implement the storagedriver.StorageDriver interface
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// GetContent retrieves the content stored at "path" as a []byte.
|
|
|
|
func (d *Driver) GetContent(path string) ([]byte, error) {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
|
|
return nil, storagedriver.InvalidPathError{Path: path}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
rc, err := d.ReadStream(path, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
|
|
|
|
p, err := ioutil.ReadAll(rc)
|
2014-10-21 22:02:20 +00:00
|
|
|
if err != nil {
|
2014-12-04 00:44:20 +00:00
|
|
|
return nil, err
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
|
|
|
|
return p, nil
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// PutContent stores the []byte content at a location designated by "path".
|
|
|
|
func (d *Driver) PutContent(subPath string, contents []byte) error {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(subPath) {
|
|
|
|
return storagedriver.InvalidPathError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
if _, err := d.WriteStream(subPath, 0, bytes.NewReader(contents)); err != nil {
|
2014-10-21 22:02:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
return os.Truncate(d.fullPath(subPath), int64(len(contents)))
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// ReadStream retrieves an io.ReadCloser for the content stored at "path" with a
|
|
|
|
// given byte offset.
|
2014-12-03 03:01:00 +00:00
|
|
|
func (d *Driver) ReadStream(path string, offset int64) (io.ReadCloser, error) {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
|
|
return nil, storagedriver.InvalidPathError{Path: path}
|
|
|
|
}
|
|
|
|
|
2014-12-05 19:46:41 +00:00
|
|
|
if offset < 0 {
|
|
|
|
return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0644)
|
2014-10-21 22:02:20 +00:00
|
|
|
if err != nil {
|
2014-12-04 00:44:20 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, storagedriver.PathNotFoundError{Path: path}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
seekPos, err := file.Seek(int64(offset), os.SEEK_SET)
|
|
|
|
if err != nil {
|
|
|
|
file.Close()
|
|
|
|
return nil, err
|
|
|
|
} else if seekPos < int64(offset) {
|
|
|
|
file.Close()
|
2014-11-13 01:19:19 +00:00
|
|
|
return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
// WriteStream stores the contents of the provided io.Reader at a location
|
2014-11-17 23:44:07 +00:00
|
|
|
// designated by the given path.
|
2014-12-04 00:44:20 +00:00
|
|
|
func (d *Driver) WriteStream(subPath string, offset int64, reader io.Reader) (nn int64, err error) {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(subPath) {
|
|
|
|
return 0, storagedriver.InvalidPathError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
if offset < 0 {
|
|
|
|
return 0, storagedriver.InvalidOffsetError{Path: subPath, Offset: offset}
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
// TODO(stevvooe): This needs to be a requirement.
|
|
|
|
// if !path.IsAbs(subPath) {
|
|
|
|
// return fmt.Errorf("absolute path required: %q", subPath)
|
|
|
|
// }
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
fullPath := d.fullPath(subPath)
|
2014-10-21 22:02:20 +00:00
|
|
|
parentDir := path.Dir(fullPath)
|
2014-12-04 00:44:20 +00:00
|
|
|
if err := os.MkdirAll(parentDir, 0755); err != nil {
|
|
|
|
return 0, err
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
fp, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, 0644)
|
|
|
|
if err != nil {
|
|
|
|
// 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
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
defer fp.Close()
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
nn, err = fp.Seek(offset, os.SEEK_SET)
|
2014-10-21 22:02:20 +00:00
|
|
|
if err != nil {
|
2014-12-04 00:44:20 +00:00
|
|
|
return 0, err
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
|
|
|
|
if nn != offset {
|
|
|
|
return 0, fmt.Errorf("bad seek to %v, expected %v in fp=%v", offset, nn, fp)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
|
|
|
|
return io.Copy(fp, reader)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
// 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) {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(subPath) {
|
|
|
|
return nil, storagedriver.InvalidPathError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
fullPath := d.fullPath(subPath)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
fi, err := os.Stat(fullPath)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, storagedriver.PathNotFoundError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
|
|
|
|
return fileInfo{
|
|
|
|
path: subPath,
|
|
|
|
FileInfo: fi,
|
|
|
|
}, nil
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// List returns a list of the objects that are direct descendants of the given
|
|
|
|
// path.
|
|
|
|
func (d *Driver) List(subPath string) ([]string, error) {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(subPath) && subPath != "/" {
|
|
|
|
return nil, storagedriver.InvalidPathError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
2014-11-20 22:11:49 +00:00
|
|
|
if subPath[len(subPath)-1] != '/' {
|
|
|
|
subPath += "/"
|
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
fullPath := d.fullPath(subPath)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
|
|
|
dir, err := os.Open(fullPath)
|
|
|
|
if err != nil {
|
2014-12-18 00:56:36 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, storagedriver.PathNotFoundError{Path: subPath}
|
|
|
|
}
|
2014-10-21 22:02:20 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-16 20:01:27 +00:00
|
|
|
defer dir.Close()
|
|
|
|
|
2014-10-21 22:02:20 +00:00
|
|
|
fileNames, err := dir.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
keys := make([]string, 0, len(fileNames))
|
|
|
|
for _, fileName := range fileNames {
|
2014-11-04 17:52:24 +00:00
|
|
|
keys = append(keys, path.Join(subPath, fileName))
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return keys, nil
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Move moves an object stored at sourcePath to destPath, removing the original
|
|
|
|
// object.
|
|
|
|
func (d *Driver) Move(sourcePath string, destPath string) error {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(sourcePath) {
|
|
|
|
return storagedriver.InvalidPathError{Path: sourcePath}
|
|
|
|
} else if !storagedriver.PathRegexp.MatchString(destPath) {
|
|
|
|
return storagedriver.InvalidPathError{Path: destPath}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
source := d.fullPath(sourcePath)
|
|
|
|
dest := d.fullPath(destPath)
|
2014-11-19 01:41:48 +00:00
|
|
|
|
|
|
|
if _, err := os.Stat(source); os.IsNotExist(err) {
|
|
|
|
return storagedriver.PathNotFoundError{Path: sourcePath}
|
|
|
|
}
|
|
|
|
|
2014-12-09 02:22:08 +00:00
|
|
|
if err := os.MkdirAll(path.Dir(dest), 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-11-19 01:41:48 +00:00
|
|
|
err := os.Rename(source, dest)
|
2014-10-21 22:02:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Delete recursively deletes all objects stored at "path" and its subpaths.
|
|
|
|
func (d *Driver) Delete(subPath string) error {
|
2014-12-11 22:11:47 +00:00
|
|
|
if !storagedriver.PathRegexp.MatchString(subPath) {
|
|
|
|
return storagedriver.InvalidPathError{Path: subPath}
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
fullPath := d.fullPath(subPath)
|
2014-10-21 22:02:20 +00:00
|
|
|
|
|
|
|
_, err := os.Stat(fullPath)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
} else if err != nil {
|
2014-11-13 01:19:19 +00:00
|
|
|
return storagedriver.PathNotFoundError{Path: subPath}
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = os.RemoveAll(fullPath)
|
|
|
|
return err
|
|
|
|
}
|
2014-12-04 00:44:20 +00:00
|
|
|
|
2015-01-07 16:31:38 +00:00
|
|
|
// URLFor returns a URL which may be used to retrieve the content stored at the given path.
|
|
|
|
// May return an UnsupportedMethodErr in certain StorageDriver implementations.
|
2015-01-09 01:10:32 +00:00
|
|
|
func (d *Driver) URLFor(path string, options map[string]interface{}) (string, error) {
|
2015-01-07 16:31:38 +00:00
|
|
|
return "", storagedriver.ErrUnsupportedMethod
|
|
|
|
}
|
|
|
|
|
2014-12-04 00:44:20 +00:00
|
|
|
// 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()
|
|
|
|
}
|