1package inmemory 2 3import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "sync" 9 "time" 10 11 storagedriver "github.com/docker/distribution/registry/storage/driver" 12 "github.com/docker/distribution/registry/storage/driver/base" 13 "github.com/docker/distribution/registry/storage/driver/factory" 14) 15 16const driverName = "inmemory" 17 18func init() { 19 factory.Register(driverName, &inMemoryDriverFactory{}) 20} 21 22// inMemoryDriverFacotry implements the factory.StorageDriverFactory interface. 23type inMemoryDriverFactory struct{} 24 25func (factory *inMemoryDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) { 26 return New(), nil 27} 28 29type driver struct { 30 root *dir 31 mutex sync.RWMutex 32} 33 34// baseEmbed allows us to hide the Base embed. 35type baseEmbed struct { 36 base.Base 37} 38 39// Driver is a storagedriver.StorageDriver implementation backed by a local map. 40// Intended solely for example and testing purposes. 41type Driver struct { 42 baseEmbed // embedded, hidden base driver. 43} 44 45var _ storagedriver.StorageDriver = &Driver{} 46 47// New constructs a new Driver. 48func New() *Driver { 49 return &Driver{ 50 baseEmbed: baseEmbed{ 51 Base: base.Base{ 52 StorageDriver: &driver{ 53 root: &dir{ 54 common: common{ 55 p: "/", 56 mod: time.Now(), 57 }, 58 }, 59 }, 60 }, 61 }, 62 } 63} 64 65// Implement the storagedriver.StorageDriver interface. 66 67func (d *driver) Name() string { 68 return driverName 69} 70 71// GetContent retrieves the content stored at "path" as a []byte. 72func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { 73 d.mutex.RLock() 74 defer d.mutex.RUnlock() 75 76 rc, err := d.reader(ctx, path, 0) 77 if err != nil { 78 return nil, err 79 } 80 defer rc.Close() 81 82 return ioutil.ReadAll(rc) 83} 84 85// PutContent stores the []byte content at a location designated by "path". 86func (d *driver) PutContent(ctx context.Context, p string, contents []byte) error { 87 d.mutex.Lock() 88 defer d.mutex.Unlock() 89 90 normalized := normalize(p) 91 92 f, err := d.root.mkfile(normalized) 93 if err != nil { 94 // TODO(stevvooe): Again, we need to clarify when this is not a 95 // directory in StorageDriver API. 96 return fmt.Errorf("not a file") 97 } 98 99 f.truncate() 100 f.WriteAt(contents, 0) 101 102 return nil 103} 104 105// Reader retrieves an io.ReadCloser for the content stored at "path" with a 106// given byte offset. 107func (d *driver) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { 108 d.mutex.RLock() 109 defer d.mutex.RUnlock() 110 111 return d.reader(ctx, path, offset) 112} 113 114func (d *driver) reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { 115 if offset < 0 { 116 return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset} 117 } 118 119 normalized := normalize(path) 120 found := d.root.find(normalized) 121 122 if found.path() != normalized { 123 return nil, storagedriver.PathNotFoundError{Path: path} 124 } 125 126 if found.isdir() { 127 return nil, fmt.Errorf("%q is a directory", path) 128 } 129 130 return ioutil.NopCloser(found.(*file).sectionReader(offset)), nil 131} 132 133// Writer returns a FileWriter which will store the content written to it 134// at the location designated by "path" after the call to Commit. 135func (d *driver) Writer(ctx context.Context, path string, append bool) (storagedriver.FileWriter, error) { 136 d.mutex.Lock() 137 defer d.mutex.Unlock() 138 139 normalized := normalize(path) 140 141 f, err := d.root.mkfile(normalized) 142 if err != nil { 143 return nil, fmt.Errorf("not a file") 144 } 145 146 if !append { 147 f.truncate() 148 } 149 150 return d.newWriter(f), nil 151} 152 153// Stat returns info about the provided path. 154func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { 155 d.mutex.RLock() 156 defer d.mutex.RUnlock() 157 158 normalized := normalize(path) 159 found := d.root.find(normalized) 160 161 if found.path() != normalized { 162 return nil, storagedriver.PathNotFoundError{Path: path} 163 } 164 165 fi := storagedriver.FileInfoFields{ 166 Path: path, 167 IsDir: found.isdir(), 168 ModTime: found.modtime(), 169 } 170 171 if !fi.IsDir { 172 fi.Size = int64(len(found.(*file).data)) 173 } 174 175 return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil 176} 177 178// List returns a list of the objects that are direct descendants of the given 179// path. 180func (d *driver) List(ctx context.Context, path string) ([]string, error) { 181 d.mutex.RLock() 182 defer d.mutex.RUnlock() 183 184 normalized := normalize(path) 185 186 found := d.root.find(normalized) 187 188 if !found.isdir() { 189 return nil, fmt.Errorf("not a directory") // TODO(stevvooe): Need error type for this... 190 } 191 192 entries, err := found.(*dir).list(normalized) 193 194 if err != nil { 195 switch err { 196 case errNotExists: 197 return nil, storagedriver.PathNotFoundError{Path: path} 198 case errIsNotDir: 199 return nil, fmt.Errorf("not a directory") 200 default: 201 return nil, err 202 } 203 } 204 205 return entries, nil 206} 207 208// Move moves an object stored at sourcePath to destPath, removing the original 209// object. 210func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { 211 d.mutex.Lock() 212 defer d.mutex.Unlock() 213 214 normalizedSrc, normalizedDst := normalize(sourcePath), normalize(destPath) 215 216 err := d.root.move(normalizedSrc, normalizedDst) 217 switch err { 218 case errNotExists: 219 return storagedriver.PathNotFoundError{Path: destPath} 220 default: 221 return err 222 } 223} 224 225// Delete recursively deletes all objects stored at "path" and its subpaths. 226func (d *driver) Delete(ctx context.Context, path string) error { 227 d.mutex.Lock() 228 defer d.mutex.Unlock() 229 230 normalized := normalize(path) 231 232 err := d.root.delete(normalized) 233 switch err { 234 case errNotExists: 235 return storagedriver.PathNotFoundError{Path: path} 236 default: 237 return err 238 } 239} 240 241// URLFor returns a URL which may be used to retrieve the content stored at the given path. 242// May return an UnsupportedMethodErr in certain StorageDriver implementations. 243func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) { 244 return "", storagedriver.ErrUnsupportedMethod{} 245} 246 247// Walk traverses a filesystem defined within driver, starting 248// from the given path, calling f on each file 249func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error { 250 return storagedriver.WalkFallback(ctx, d, path, f) 251} 252 253type writer struct { 254 d *driver 255 f *file 256 closed bool 257 committed bool 258 cancelled bool 259} 260 261func (d *driver) newWriter(f *file) storagedriver.FileWriter { 262 return &writer{ 263 d: d, 264 f: f, 265 } 266} 267 268func (w *writer) Write(p []byte) (int, error) { 269 if w.closed { 270 return 0, fmt.Errorf("already closed") 271 } else if w.committed { 272 return 0, fmt.Errorf("already committed") 273 } else if w.cancelled { 274 return 0, fmt.Errorf("already cancelled") 275 } 276 277 w.d.mutex.Lock() 278 defer w.d.mutex.Unlock() 279 280 return w.f.WriteAt(p, int64(len(w.f.data))) 281} 282 283func (w *writer) Size() int64 { 284 w.d.mutex.RLock() 285 defer w.d.mutex.RUnlock() 286 287 return int64(len(w.f.data)) 288} 289 290func (w *writer) Close() error { 291 if w.closed { 292 return fmt.Errorf("already closed") 293 } 294 w.closed = true 295 return nil 296} 297 298func (w *writer) Cancel() error { 299 if w.closed { 300 return fmt.Errorf("already closed") 301 } else if w.committed { 302 return fmt.Errorf("already committed") 303 } 304 w.cancelled = true 305 306 w.d.mutex.Lock() 307 defer w.d.mutex.Unlock() 308 309 return w.d.root.delete(w.f.path()) 310} 311 312func (w *writer) Commit() error { 313 if w.closed { 314 return fmt.Errorf("already closed") 315 } else if w.committed { 316 return fmt.Errorf("already committed") 317 } else if w.cancelled { 318 return fmt.Errorf("already cancelled") 319 } 320 w.committed = true 321 return nil 322} 323