package storage import ( "encoding/json" "errors" "io/ioutil" "os" "path/filepath" "time" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/truncindex" ) var ( // ErrImageUnknown indicates that there was no image with the specified name or ID ErrImageUnknown = errors.New("image not known") ) // An Image is a reference to a layer and an associated metadata string. type Image struct { // ID is either one which was specified at create-time, or a random // value which was generated by the library. ID string `json:"id"` // Names is an optional set of user-defined convenience values. The // image can be referred to by its ID or any of its names. Names are // unique among images. Names []string `json:"names,omitempty"` // TopLayer is the ID of the topmost layer of the image itself. // Multiple images can refer to the same top layer. TopLayer string `json:"layer"` // Metadata is data we keep for the convenience of the caller. It is not // expected to be large, since it is kept in memory. Metadata string `json:"metadata,omitempty"` // BigDataNames is a list of names of data items that we keep for the // convenience of the caller. They can be large, and are only in // memory when being read from or written to disk. BigDataNames []string `json:"big-data-names,omitempty"` // BigDataSizes maps the names in BigDataNames to the sizes of the data // that has been stored, if they're known. BigDataSizes map[string]int64 `json:"big-data-sizes,omitempty"` Flags map[string]interface{} `json:"flags,omitempty"` } // ImageStore provides bookkeeping for information about Images. type ImageStore interface { FileBasedStore MetadataStore BigDataStore FlaggableStore // Create creates an image that has a specified ID (or a random one) and // optional names, using the specified layer as its topmost (hopefully // read-only) layer. That layer can be referenced by multiple images. Create(id string, names []string, layer, metadata string) (*Image, error) // SetNames replaces the list of names associated with an image with the // supplied values. SetNames(id string, names []string) error // Exists checks if there is an image with the given ID or name. Exists(id string) bool // Get retrieves information about an image given an ID or name. Get(id string) (*Image, error) // Delete removes the record of the image. Delete(id string) error // Wipe removes records of all images. Wipe() error // Lookup attempts to translate a name to an ID. Most methods do this // implicitly. Lookup(name string) (string, error) // Images returns a slice enumerating the known images. Images() ([]Image, error) } type imageStore struct { lockfile Locker dir string images []Image idindex *truncindex.TruncIndex byid map[string]*Image byname map[string]*Image } func (r *imageStore) Images() ([]Image, error) { return r.images, nil } func (r *imageStore) imagespath() string { return filepath.Join(r.dir, "images.json") } func (r *imageStore) datadir(id string) string { return filepath.Join(r.dir, id) } func (r *imageStore) datapath(id, key string) string { return filepath.Join(r.datadir(id), makeBigDataBaseName(key)) } func (r *imageStore) Load() error { needSave := false rpath := r.imagespath() data, err := ioutil.ReadFile(rpath) if err != nil && !os.IsNotExist(err) { return err } images := []Image{} idlist := []string{} ids := make(map[string]*Image) names := make(map[string]*Image) if err = json.Unmarshal(data, &images); len(data) == 0 || err == nil { for n, image := range images { ids[image.ID] = &images[n] idlist = append(idlist, image.ID) for _, name := range image.Names { if conflict, ok := names[name]; ok { r.removeName(conflict, name) needSave = true } names[name] = &images[n] } } } r.images = images r.idindex = truncindex.NewTruncIndex(idlist) r.byid = ids r.byname = names if needSave { r.Touch() return r.Save() } return nil } func (r *imageStore) Save() error { rpath := r.imagespath() if err := os.MkdirAll(filepath.Dir(rpath), 0700); err != nil { return err } jdata, err := json.Marshal(&r.images) if err != nil { return err } return ioutils.AtomicWriteFile(rpath, jdata, 0600) } func newImageStore(dir string) (ImageStore, error) { if err := os.MkdirAll(dir, 0700); err != nil { return nil, err } lockfile, err := GetLockfile(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 } else if image, ok := r.byname[id]; ok { return image, ok } else if longid, err := r.idindex.Get(id); err == nil { image, ok := r.byid[longid] return image, ok } return nil, false } func (r *imageStore) ClearFlag(id string, flag string) error { image, ok := r.lookup(id) if !ok { return ErrImageUnknown } delete(image.Flags, flag) return r.Save() } func (r *imageStore) SetFlag(id string, flag string, value interface{}) error { image, ok := r.lookup(id) if !ok { return ErrImageUnknown } image.Flags[flag] = value return r.Save() } func (r *imageStore) Create(id string, names []string, layer, metadata string) (image *Image, err error) { if id == "" { id = stringid.GenerateRandomID() _, idInUse := r.byid[id] for idInUse { id = stringid.GenerateRandomID() _, idInUse = r.byid[id] } } if _, idInUse := r.byid[id]; idInUse { return nil, ErrDuplicateID } for _, name := range names { if _, nameInUse := r.byname[name]; nameInUse { return nil, ErrDuplicateName } } if err == nil { newImage := Image{ ID: id, Names: names, TopLayer: layer, Metadata: metadata, BigDataNames: []string{}, BigDataSizes: make(map[string]int64), Flags: make(map[string]interface{}), } r.images = append(r.images, newImage) image = &r.images[len(r.images)-1] r.idindex.Add(id) r.byid[id] = image for _, name := range names { r.byname[name] = image } err = r.Save() } return image, err } func (r *imageStore) Metadata(id string) (string, error) { if image, ok := r.lookup(id); ok { return image.Metadata, nil } return "", ErrImageUnknown } func (r *imageStore) SetMetadata(id, metadata string) error { if image, ok := r.lookup(id); ok { image.Metadata = metadata return r.Save() } return ErrImageUnknown } func (r *imageStore) removeName(image *Image, name string) { image.Names = stringSliceWithoutValue(image.Names, name) } func (r *imageStore) SetNames(id string, names []string) error { if image, ok := r.lookup(id); ok { for _, name := range image.Names { delete(r.byname, name) } for _, name := range names { if otherImage, ok := r.byname[name]; ok { r.removeName(otherImage, name) } r.byname[name] = image } image.Names = names return r.Save() } return ErrImageUnknown } func (r *imageStore) Delete(id string) error { image, ok := r.lookup(id) if !ok { return ErrImageUnknown } id = image.ID newImages := []Image{} for _, candidate := range r.images { if candidate.ID != id { newImages = append(newImages, candidate) } } delete(r.byid, id) r.idindex.Delete(id) for _, name := range image.Names { delete(r.byname, name) } r.images = newImages if err := r.Save(); err != nil { return err } if err := os.RemoveAll(r.datadir(id)); err != nil { return err } return nil } func (r *imageStore) Get(id string) (*Image, error) { if image, ok := r.lookup(id); ok { return image, nil } return nil, ErrImageUnknown } func (r *imageStore) Lookup(name string) (id string, err error) { if image, ok := r.lookup(name); ok { return image.ID, nil } return "", ErrImageUnknown } func (r *imageStore) Exists(id string) bool { _, ok := r.lookup(id) return ok } func (r *imageStore) BigData(id, key string) ([]byte, error) { image, ok := r.lookup(id) if !ok { return nil, ErrImageUnknown } return ioutil.ReadFile(r.datapath(image.ID, key)) } func (r *imageStore) BigDataSize(id, key string) (int64, error) { image, ok := r.lookup(id) if !ok { return -1, ErrImageUnknown } if size, ok := image.BigDataSizes[key]; ok { return size, nil } return -1, ErrSizeUnknown } func (r *imageStore) BigDataNames(id string) ([]string, error) { image, ok := r.lookup(id) if !ok { return nil, ErrImageUnknown } return image.BigDataNames, nil } func (r *imageStore) SetBigData(id, key string, data []byte) error { image, ok := r.lookup(id) if !ok { return ErrImageUnknown } if err := os.MkdirAll(r.datadir(image.ID), 0700); err != nil { return err } err := ioutils.AtomicWriteFile(r.datapath(image.ID, key), data, 0600) if err == nil { add := true save := false oldSize, ok := image.BigDataSizes[key] image.BigDataSizes[key] = int64(len(data)) if !ok || oldSize != image.BigDataSizes[key] { save = true } for _, name := range image.BigDataNames { if name == key { add = false break } } if add { image.BigDataNames = append(image.BigDataNames, key) save = true } if save { err = r.Save() } } return err } func (r *imageStore) Wipe() error { ids := []string{} for id := range r.byid { ids = append(ids, id) } for _, id := range ids { if err := r.Delete(id); err != nil { return err } } return nil } func (r *imageStore) Lock() { r.lockfile.Lock() } func (r *imageStore) Unlock() { r.lockfile.Unlock() } func (r *imageStore) Touch() error { return r.lockfile.Touch() } func (r *imageStore) Modified() (bool, error) { return r.lockfile.Modified() } func (r *imageStore) TouchedSince(when time.Time) bool { return r.lockfile.TouchedSince(when) }