From a9b1fe6241c8fb453d1e37bacae46848f681f42e Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Thu, 8 Jun 2017 14:10:50 -0400 Subject: [PATCH] Add read-only layer/image/container stores Implement read-only versions of layer and image store interfaces which allocate read-only locks and which return errors whenever a write function is called (which should only be possible after a type assertion, since they're not part of the read-only interfaces). Signed-off-by: Nalin Dahyabhai --- images.go | 58 +++++++++++++++++++++++++++++++++--- layers.go | 89 +++++++++++++++++++++++++++++++++++++++++++++---------- store.go | 2 ++ 3 files changed, 129 insertions(+), 20 deletions(-) diff --git a/images.go b/images.go index efc7ff6dc..4843b2135 100644 --- a/images.go +++ b/images.go @@ -2,7 +2,6 @@ package storage import ( "encoding/json" - "errors" "io/ioutil" "os" "path/filepath" @@ -11,6 +10,7 @@ import ( "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/truncindex" + "github.com/pkg/errors" ) var ( @@ -123,7 +123,7 @@ func (r *imageStore) datapath(id, key string) string { } func (r *imageStore) Load() error { - needSave := false + shouldSave := false rpath := r.imagespath() data, err := ioutil.ReadFile(rpath) if err != nil && !os.IsNotExist(err) { @@ -140,23 +140,29 @@ func (r *imageStore) Load() error { for _, name := range image.Names { if conflict, ok := names[name]; ok { r.removeName(conflict, name) - needSave = true + shouldSave = true } names[name] = images[n] } } } + if shouldSave && !r.IsReadWrite() { + return errors.New("image store assigns the same name to multiple images") + } r.images = images r.idindex = truncindex.NewTruncIndex(idlist) r.byid = ids r.byname = names - if needSave { + if shouldSave { return r.Save() } return nil } func (r *imageStore) Save() error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify the image store at %q", r.imagespath()) + } rpath := r.imagespath() if err := os.MkdirAll(filepath.Dir(rpath), 0700); err != nil { return err @@ -192,6 +198,26 @@ func newImageStore(dir string) (ImageStore, error) { return &istore, nil } +func newROImageStore(dir string) (ROImageStore, error) { + lockfile, err := GetROLockfile(filepath.Join(dir, "images.lock")) + if err != nil { + return nil, err + } + lockfile.Lock() + defer lockfile.Unlock() + istore := imageStore{ + lockfile: lockfile, + dir: dir, + images: []*Image{}, + byid: make(map[string]*Image), + byname: make(map[string]*Image), + } + if err := istore.Load(); err != nil { + return nil, err + } + return &istore, nil +} + func (r *imageStore) lookup(id string) (*Image, bool) { if image, ok := r.byid[id]; ok { return image, ok @@ -205,6 +231,9 @@ func (r *imageStore) lookup(id string) (*Image, bool) { } func (r *imageStore) ClearFlag(id string, flag string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to clear flags on images at %q", r.imagespath()) + } image, ok := r.lookup(id) if !ok { return ErrImageUnknown @@ -214,6 +243,9 @@ func (r *imageStore) ClearFlag(id string, flag string) error { } func (r *imageStore) SetFlag(id string, flag string, value interface{}) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to set flags on images at %q", r.imagespath()) + } image, ok := r.lookup(id) if !ok { return ErrImageUnknown @@ -223,6 +255,9 @@ func (r *imageStore) SetFlag(id string, flag string, value interface{}) error { } func (r *imageStore) Create(id string, names []string, layer, metadata string) (image *Image, err error) { + if !r.IsReadWrite() { + return nil, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new images at %q", r.imagespath()) + } if id == "" { id = stringid.GenerateRandomID() _, idInUse := r.byid[id] @@ -268,6 +303,9 @@ func (r *imageStore) Metadata(id string) (string, error) { } func (r *imageStore) SetMetadata(id, metadata string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify image metadata at %q", r.imagespath()) + } if image, ok := r.lookup(id); ok { image.Metadata = metadata return r.Save() @@ -280,6 +318,9 @@ func (r *imageStore) removeName(image *Image, name string) { } func (r *imageStore) SetNames(id string, names []string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change image name assignments at %q", r.imagespath()) + } if image, ok := r.lookup(id); ok { for _, name := range image.Names { delete(r.byname, name) @@ -297,6 +338,9 @@ func (r *imageStore) SetNames(id string, names []string) error { } func (r *imageStore) Delete(id string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete images at %q", r.imagespath()) + } image, ok := r.lookup(id) if !ok { return ErrImageUnknown @@ -370,6 +414,9 @@ func (r *imageStore) BigDataNames(id string) ([]string, error) { } func (r *imageStore) SetBigData(id, key string, data []byte) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to save data items associated with images at %q", r.imagespath()) + } image, ok := r.lookup(id) if !ok { return ErrImageUnknown @@ -404,6 +451,9 @@ func (r *imageStore) SetBigData(id, key string, data []byte) error { } func (r *imageStore) Wipe() error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete images at %q", r.imagespath()) + } ids := []string{} for id := range r.byid { ids = append(ids, id) diff --git a/layers.go b/layers.go index b3d1834af..691d84f6d 100644 --- a/layers.go +++ b/layers.go @@ -4,7 +4,6 @@ import ( "bytes" "compress/gzip" "encoding/json" - "errors" "io" "io/ioutil" "os" @@ -16,6 +15,7 @@ import ( "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/truncindex" + "github.com/pkg/errors" "github.com/vbatts/tar-split/tar/asm" "github.com/vbatts/tar-split/tar/storage" ) @@ -192,7 +192,7 @@ func (r *layerStore) layerspath() string { } func (r *layerStore) Load() error { - needSave := false + shouldSave := false rpath := r.layerspath() data, err := ioutil.ReadFile(rpath) if err != nil && !os.IsNotExist(err) { @@ -211,7 +211,7 @@ func (r *layerStore) Load() error { for _, name := range layer.Names { if conflict, ok := names[name]; ok { r.removeName(conflict, name) - needSave = true + shouldSave = true } names[name] = layers[n] } @@ -222,6 +222,9 @@ func (r *layerStore) Load() error { } } } + if shouldSave && !r.IsReadWrite() { + return errors.New("layer store assigns the same name to multiple layers") + } mpath := r.mountspath() data, err = ioutil.ReadFile(mpath) if err != nil && !os.IsNotExist(err) { @@ -245,27 +248,32 @@ func (r *layerStore) Load() error { r.byname = names r.bymount = mounts err = nil - // Last step: try to remove anything that a previous user of this - // storage area marked for deletion but didn't manage to actually - // delete. - for _, layer := range r.layers { - if cleanup, ok := layer.Flags[incompleteFlag]; ok { - if b, ok := cleanup.(bool); ok && b { - err = r.Delete(layer.ID) - if err != nil { - break + // Last step: if we're writable, try to remove anything that a previous + // user of this storage area marked for deletion but didn't manage to + // actually delete. + if r.IsReadWrite() { + for _, layer := range r.layers { + if cleanup, ok := layer.Flags[incompleteFlag]; ok { + if b, ok := cleanup.(bool); ok && b { + err = r.Delete(layer.ID) + if err != nil { + break + } + shouldSave = true } - needSave = true } } - } - if needSave { - return r.Save() + if shouldSave { + return r.Save() + } } return err } func (r *layerStore) Save() error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify the layer store at %q", r.layerspath()) + } rpath := r.layerspath() if err := os.MkdirAll(filepath.Dir(rpath), 0700); err != nil { return err @@ -327,6 +335,28 @@ func newLayerStore(rundir string, layerdir string, driver drivers.Driver) (Layer return &rlstore, nil } +func newROLayerStore(rundir string, layerdir string, driver drivers.Driver) (ROLayerStore, error) { + lockfile, err := GetROLockfile(filepath.Join(layerdir, "layers.lock")) + if err != nil { + return nil, err + } + lockfile.Lock() + defer lockfile.Unlock() + rlstore := layerStore{ + lockfile: lockfile, + driver: driver, + rundir: rundir, + layerdir: layerdir, + byid: make(map[string]*Layer), + bymount: make(map[string]*Layer), + byname: make(map[string]*Layer), + } + if err := rlstore.Load(); err != nil { + return nil, err + } + return &rlstore, nil +} + func (r *layerStore) lookup(id string) (*Layer, bool) { if layer, ok := r.byid[id]; ok { return layer, ok @@ -340,6 +370,9 @@ func (r *layerStore) lookup(id string) (*Layer, bool) { } func (r *layerStore) ClearFlag(id string, flag string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to clear flags on layers at %q", r.layerspath()) + } layer, ok := r.lookup(id) if !ok { return ErrLayerUnknown @@ -349,6 +382,9 @@ func (r *layerStore) ClearFlag(id string, flag string) error { } func (r *layerStore) SetFlag(id string, flag string, value interface{}) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to set flags on layers at %q", r.layerspath()) + } layer, ok := r.lookup(id) if !ok { return ErrLayerUnknown @@ -362,6 +398,9 @@ func (r *layerStore) Status() ([][2]string, error) { } func (r *layerStore) Put(id, parent string, names []string, mountLabel string, options map[string]string, writeable bool, flags map[string]interface{}, diff archive.Reader) (layer *Layer, size int64, err error) { + if !r.IsReadWrite() { + return nil, -1, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new layers at %q", r.layerspath()) + } size = -1 if err := os.MkdirAll(r.rundir, 0700); err != nil { return nil, -1, err @@ -453,6 +492,9 @@ func (r *layerStore) Create(id, parent string, names []string, mountLabel string } func (r *layerStore) Mount(id, mountLabel string) (string, error) { + if !r.IsReadWrite() { + return "", errors.Wrapf(ErrStoreIsReadOnly, "not allowed to update mount locations for layers at %q", r.mountspath()) + } layer, ok := r.lookup(id) if !ok { return "", ErrLayerUnknown @@ -478,6 +520,9 @@ func (r *layerStore) Mount(id, mountLabel string) (string, error) { } func (r *layerStore) Unmount(id string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to update mount locations for layers at %q", r.mountspath()) + } layer, ok := r.lookup(id) if !ok { layerByMount, ok := r.bymount[filepath.Clean(id)] @@ -507,6 +552,9 @@ func (r *layerStore) removeName(layer *Layer, name string) { } func (r *layerStore) SetNames(id string, names []string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change layer name assignments at %q", r.layerspath()) + } if layer, ok := r.lookup(id); ok { for _, name := range layer.Names { delete(r.byname, name) @@ -531,6 +579,9 @@ func (r *layerStore) Metadata(id string) (string, error) { } func (r *layerStore) SetMetadata(id, metadata string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify layer metadata at %q", r.layerspath()) + } if layer, ok := r.lookup(id); ok { layer.Metadata = metadata return r.Save() @@ -543,6 +594,9 @@ func (r *layerStore) tspath(id string) string { } func (r *layerStore) Delete(id string) error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete layers at %q", r.layerspath()) + } layer, ok := r.lookup(id) if !ok { return ErrLayerUnknown @@ -595,6 +649,9 @@ func (r *layerStore) Get(id string) (*Layer, error) { } func (r *layerStore) Wipe() error { + if !r.IsReadWrite() { + return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete layers at %q", r.layerspath()) + } ids := []string{} for id := range r.byid { ids = append(ids, id) diff --git a/store.go b/store.go index 9257fe48a..14fbd2c37 100644 --- a/store.go +++ b/store.go @@ -52,6 +52,8 @@ var ( ErrIncompleteOptions = errors.New("missing necessary StoreOptions") // ErrSizeUnknown is returned when the caller asks for the size of a big data item, but the Store couldn't determine the answer. ErrSizeUnknown = errors.New("size is not known") + // ErrStoreIsReadOnly is returned when the caller makes a call to a read-only store that would require modifying its contents. + ErrStoreIsReadOnly = errors.New("called a write method on a read-only store") // DefaultStoreOptions is a reasonable default set of options. DefaultStoreOptions StoreOptions stores []*store