github.com/cdoern/storage@v1.12.13/images.go (about) 1 package storage 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/containers/storage/pkg/ioutils" 12 "github.com/containers/storage/pkg/stringid" 13 "github.com/containers/storage/pkg/truncindex" 14 digest "github.com/opencontainers/go-digest" 15 "github.com/pkg/errors" 16 ) 17 18 const ( 19 // ImageDigestManifestBigDataNamePrefix is a prefix of big data item 20 // names which we consider to be manifests, used for computing a 21 // "digest" value for the image as a whole, by which we can locate the 22 // image later. 23 ImageDigestManifestBigDataNamePrefix = "manifest" 24 // ImageDigestBigDataKey is provided for compatibility with older 25 // versions of the image library. It will be removed in the future. 26 ImageDigestBigDataKey = "manifest" 27 ) 28 29 // An Image is a reference to a layer and an associated metadata string. 30 type Image struct { 31 // ID is either one which was specified at create-time, or a random 32 // value which was generated by the library. 33 ID string `json:"id"` 34 35 // Digest is a digest value that we can use to locate the image, if one 36 // was specified at creation-time. 37 Digest digest.Digest `json:"digest,omitempty"` 38 39 // Digests is a list of digest values of the image's manifests, and 40 // possibly a manually-specified value, that we can use to locate the 41 // image. If Digest is set, its value is also in this list. 42 Digests []digest.Digest `json:"-"` 43 44 // Names is an optional set of user-defined convenience values. The 45 // image can be referred to by its ID or any of its names. Names are 46 // unique among images, and are often the text representation of tagged 47 // or canonical references. 48 Names []string `json:"names,omitempty"` 49 50 // TopLayer is the ID of the topmost layer of the image itself, if the 51 // image contains one or more layers. Multiple images can refer to the 52 // same top layer. 53 TopLayer string `json:"layer,omitempty"` 54 55 // MappedTopLayers are the IDs of alternate versions of the top layer 56 // which have the same contents and parent, and which differ from 57 // TopLayer only in which ID mappings they use. When the image is 58 // to be removed, they should be removed before the TopLayer, as the 59 // graph driver may depend on that. 60 MappedTopLayers []string `json:"mapped-layers,omitempty"` 61 62 // Metadata is data we keep for the convenience of the caller. It is not 63 // expected to be large, since it is kept in memory. 64 Metadata string `json:"metadata,omitempty"` 65 66 // BigDataNames is a list of names of data items that we keep for the 67 // convenience of the caller. They can be large, and are only in 68 // memory when being read from or written to disk. 69 BigDataNames []string `json:"big-data-names,omitempty"` 70 71 // BigDataSizes maps the names in BigDataNames to the sizes of the data 72 // that has been stored, if they're known. 73 BigDataSizes map[string]int64 `json:"big-data-sizes,omitempty"` 74 75 // BigDataDigests maps the names in BigDataNames to the digests of the 76 // data that has been stored, if they're known. 77 BigDataDigests map[string]digest.Digest `json:"big-data-digests,omitempty"` 78 79 // Created is the datestamp for when this image was created. Older 80 // versions of the library did not track this information, so callers 81 // will likely want to use the IsZero() method to verify that a value 82 // is set before using it. 83 Created time.Time `json:"created,omitempty"` 84 85 // ReadOnly is true if this image resides in a read-only layer store. 86 ReadOnly bool `json:"-"` 87 88 Flags map[string]interface{} `json:"flags,omitempty"` 89 } 90 91 // ROImageStore provides bookkeeping for information about Images. 92 type ROImageStore interface { 93 ROFileBasedStore 94 ROMetadataStore 95 ROBigDataStore 96 97 // Exists checks if there is an image with the given ID or name. 98 Exists(id string) bool 99 100 // Get retrieves information about an image given an ID or name. 101 Get(id string) (*Image, error) 102 103 // Lookup attempts to translate a name to an ID. Most methods do this 104 // implicitly. 105 Lookup(name string) (string, error) 106 107 // Images returns a slice enumerating the known images. 108 Images() ([]Image, error) 109 110 // ByDigest returns a slice enumerating the images which have either an 111 // explicitly-set digest, or a big data item with a name that starts 112 // with ImageDigestManifestBigDataNamePrefix, which matches the 113 // specified digest. 114 ByDigest(d digest.Digest) ([]*Image, error) 115 } 116 117 // ImageStore provides bookkeeping for information about Images. 118 type ImageStore interface { 119 ROImageStore 120 RWFileBasedStore 121 RWMetadataStore 122 RWImageBigDataStore 123 FlaggableStore 124 125 // Create creates an image that has a specified ID (or a random one) and 126 // optional names, using the specified layer as its topmost (hopefully 127 // read-only) layer. That layer can be referenced by multiple images. 128 Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (*Image, error) 129 130 // SetNames replaces the list of names associated with an image with the 131 // supplied values. The values are expected to be valid normalized 132 // named image references. 133 SetNames(id string, names []string) error 134 135 // Delete removes the record of the image. 136 Delete(id string) error 137 138 // Wipe removes records of all images. 139 Wipe() error 140 } 141 142 type imageStore struct { 143 lockfile Locker 144 dir string 145 images []*Image 146 idindex *truncindex.TruncIndex 147 byid map[string]*Image 148 byname map[string]*Image 149 bydigest map[digest.Digest][]*Image 150 } 151 152 func copyImage(i *Image) *Image { 153 return &Image{ 154 ID: i.ID, 155 Digest: i.Digest, 156 Digests: copyDigestSlice(i.Digests), 157 Names: copyStringSlice(i.Names), 158 TopLayer: i.TopLayer, 159 MappedTopLayers: copyStringSlice(i.MappedTopLayers), 160 Metadata: i.Metadata, 161 BigDataNames: copyStringSlice(i.BigDataNames), 162 BigDataSizes: copyStringInt64Map(i.BigDataSizes), 163 BigDataDigests: copyStringDigestMap(i.BigDataDigests), 164 Created: i.Created, 165 ReadOnly: i.ReadOnly, 166 Flags: copyStringInterfaceMap(i.Flags), 167 } 168 } 169 170 func copyImageSlice(slice []*Image) []*Image { 171 if len(slice) > 0 { 172 cp := make([]*Image, len(slice)) 173 for i := range slice { 174 cp[i] = copyImage(slice[i]) 175 } 176 return cp 177 } 178 return nil 179 } 180 181 func (r *imageStore) Images() ([]Image, error) { 182 images := make([]Image, len(r.images)) 183 for i := range r.images { 184 images[i] = *copyImage(r.images[i]) 185 } 186 return images, nil 187 } 188 189 func (r *imageStore) imagespath() string { 190 return filepath.Join(r.dir, "images.json") 191 } 192 193 func (r *imageStore) datadir(id string) string { 194 return filepath.Join(r.dir, id) 195 } 196 197 func (r *imageStore) datapath(id, key string) string { 198 return filepath.Join(r.datadir(id), makeBigDataBaseName(key)) 199 } 200 201 // bigDataNameIsManifest determines if a big data item with the specified name 202 // is considered to be representative of the image, in that its digest can be 203 // said to also be the image's digest. Currently, if its name is, or begins 204 // with, "manifest", we say that it is. 205 func bigDataNameIsManifest(name string) bool { 206 return strings.HasPrefix(name, ImageDigestManifestBigDataNamePrefix) 207 } 208 209 // recomputeDigests takes a fixed digest and a name-to-digest map and builds a 210 // list of the unique values that would identify the image. 211 func (image *Image) recomputeDigests() error { 212 validDigests := make([]digest.Digest, 0, len(image.BigDataDigests)+1) 213 digests := make(map[digest.Digest]struct{}) 214 if image.Digest != "" { 215 if err := image.Digest.Validate(); err != nil { 216 return errors.Wrapf(err, "error validating image digest %q", string(image.Digest)) 217 } 218 digests[image.Digest] = struct{}{} 219 validDigests = append(validDigests, image.Digest) 220 } 221 for name, digest := range image.BigDataDigests { 222 if !bigDataNameIsManifest(name) { 223 continue 224 } 225 if digest.Validate() != nil { 226 return errors.Wrapf(digest.Validate(), "error validating digest %q for big data item %q", string(digest), name) 227 } 228 // Deduplicate the digest values. 229 if _, known := digests[digest]; !known { 230 digests[digest] = struct{}{} 231 validDigests = append(validDigests, digest) 232 } 233 } 234 if image.Digest == "" && len(validDigests) > 0 { 235 image.Digest = validDigests[0] 236 } 237 image.Digests = validDigests 238 return nil 239 } 240 241 func (r *imageStore) Load() error { 242 shouldSave := false 243 rpath := r.imagespath() 244 data, err := ioutil.ReadFile(rpath) 245 if err != nil && !os.IsNotExist(err) { 246 return err 247 } 248 images := []*Image{} 249 idlist := []string{} 250 ids := make(map[string]*Image) 251 names := make(map[string]*Image) 252 digests := make(map[digest.Digest][]*Image) 253 if err = json.Unmarshal(data, &images); len(data) == 0 || err == nil { 254 idlist = make([]string, 0, len(images)) 255 for n, image := range images { 256 ids[image.ID] = images[n] 257 idlist = append(idlist, image.ID) 258 for _, name := range image.Names { 259 if conflict, ok := names[name]; ok { 260 r.removeName(conflict, name) 261 shouldSave = true 262 } 263 } 264 // Compute the digest list. 265 err = image.recomputeDigests() 266 if err != nil { 267 return errors.Wrapf(err, "error computing digests for image with ID %q (%v)", image.ID, image.Names) 268 } 269 for _, name := range image.Names { 270 names[name] = image 271 } 272 for _, digest := range image.Digests { 273 list := digests[digest] 274 digests[digest] = append(list, image) 275 } 276 image.ReadOnly = !r.IsReadWrite() 277 } 278 } 279 if shouldSave && (!r.IsReadWrite() || !r.Locked()) { 280 return ErrDuplicateImageNames 281 } 282 r.images = images 283 r.idindex = truncindex.NewTruncIndex(idlist) 284 r.byid = ids 285 r.byname = names 286 r.bydigest = digests 287 if shouldSave { 288 return r.Save() 289 } 290 return nil 291 } 292 293 func (r *imageStore) Save() error { 294 if !r.IsReadWrite() { 295 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify the image store at %q", r.imagespath()) 296 } 297 if !r.Locked() { 298 return errors.New("image store is not locked for writing") 299 } 300 rpath := r.imagespath() 301 if err := os.MkdirAll(filepath.Dir(rpath), 0700); err != nil { 302 return err 303 } 304 jdata, err := json.Marshal(&r.images) 305 if err != nil { 306 return err 307 } 308 defer r.Touch() 309 return ioutils.AtomicWriteFile(rpath, jdata, 0600) 310 } 311 312 func newImageStore(dir string) (ImageStore, error) { 313 if err := os.MkdirAll(dir, 0700); err != nil { 314 return nil, err 315 } 316 lockfile, err := GetLockfile(filepath.Join(dir, "images.lock")) 317 if err != nil { 318 return nil, err 319 } 320 lockfile.Lock() 321 defer lockfile.Unlock() 322 istore := imageStore{ 323 lockfile: lockfile, 324 dir: dir, 325 images: []*Image{}, 326 byid: make(map[string]*Image), 327 byname: make(map[string]*Image), 328 bydigest: make(map[digest.Digest][]*Image), 329 } 330 if err := istore.Load(); err != nil { 331 return nil, err 332 } 333 return &istore, nil 334 } 335 336 func newROImageStore(dir string) (ROImageStore, error) { 337 lockfile, err := GetROLockfile(filepath.Join(dir, "images.lock")) 338 if err != nil { 339 return nil, err 340 } 341 lockfile.Lock() 342 defer lockfile.Unlock() 343 istore := imageStore{ 344 lockfile: lockfile, 345 dir: dir, 346 images: []*Image{}, 347 byid: make(map[string]*Image), 348 byname: make(map[string]*Image), 349 bydigest: make(map[digest.Digest][]*Image), 350 } 351 if err := istore.Load(); err != nil { 352 return nil, err 353 } 354 return &istore, nil 355 } 356 357 func (r *imageStore) lookup(id string) (*Image, bool) { 358 if image, ok := r.byid[id]; ok { 359 return image, ok 360 } else if image, ok := r.byname[id]; ok { 361 return image, ok 362 } else if longid, err := r.idindex.Get(id); err == nil { 363 image, ok := r.byid[longid] 364 return image, ok 365 } 366 return nil, false 367 } 368 369 func (r *imageStore) ClearFlag(id string, flag string) error { 370 if !r.IsReadWrite() { 371 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to clear flags on images at %q", r.imagespath()) 372 } 373 image, ok := r.lookup(id) 374 if !ok { 375 return ErrImageUnknown 376 } 377 delete(image.Flags, flag) 378 return r.Save() 379 } 380 381 func (r *imageStore) SetFlag(id string, flag string, value interface{}) error { 382 if !r.IsReadWrite() { 383 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to set flags on images at %q", r.imagespath()) 384 } 385 image, ok := r.lookup(id) 386 if !ok { 387 return ErrImageUnknown 388 } 389 if image.Flags == nil { 390 image.Flags = make(map[string]interface{}) 391 } 392 image.Flags[flag] = value 393 return r.Save() 394 } 395 396 func (r *imageStore) Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (image *Image, err error) { 397 if !r.IsReadWrite() { 398 return nil, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new images at %q", r.imagespath()) 399 } 400 if id == "" { 401 id = stringid.GenerateRandomID() 402 _, idInUse := r.byid[id] 403 for idInUse { 404 id = stringid.GenerateRandomID() 405 _, idInUse = r.byid[id] 406 } 407 } 408 if _, idInUse := r.byid[id]; idInUse { 409 return nil, errors.Wrapf(ErrDuplicateID, "an image with ID %q already exists", id) 410 } 411 names = dedupeNames(names) 412 for _, name := range names { 413 if image, nameInUse := r.byname[name]; nameInUse { 414 return nil, errors.Wrapf(ErrDuplicateName, "image name %q is already associated with image %q", name, image.ID) 415 } 416 } 417 if created.IsZero() { 418 created = time.Now().UTC() 419 } 420 if err == nil { 421 image = &Image{ 422 ID: id, 423 Digest: searchableDigest, 424 Digests: nil, 425 Names: names, 426 TopLayer: layer, 427 Metadata: metadata, 428 BigDataNames: []string{}, 429 BigDataSizes: make(map[string]int64), 430 BigDataDigests: make(map[string]digest.Digest), 431 Created: created, 432 Flags: make(map[string]interface{}), 433 } 434 err := image.recomputeDigests() 435 if err != nil { 436 return nil, errors.Wrapf(err, "error validating digests for new image") 437 } 438 r.images = append(r.images, image) 439 r.idindex.Add(id) 440 r.byid[id] = image 441 for _, name := range names { 442 r.byname[name] = image 443 } 444 for _, digest := range image.Digests { 445 list := r.bydigest[digest] 446 r.bydigest[digest] = append(list, image) 447 } 448 err = r.Save() 449 image = copyImage(image) 450 } 451 return image, err 452 } 453 454 func (r *imageStore) addMappedTopLayer(id, layer string) error { 455 if image, ok := r.lookup(id); ok { 456 image.MappedTopLayers = append(image.MappedTopLayers, layer) 457 return r.Save() 458 } 459 return ErrImageUnknown 460 } 461 462 func (r *imageStore) Metadata(id string) (string, error) { 463 if image, ok := r.lookup(id); ok { 464 return image.Metadata, nil 465 } 466 return "", ErrImageUnknown 467 } 468 469 func (r *imageStore) SetMetadata(id, metadata string) error { 470 if !r.IsReadWrite() { 471 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to modify image metadata at %q", r.imagespath()) 472 } 473 if image, ok := r.lookup(id); ok { 474 image.Metadata = metadata 475 return r.Save() 476 } 477 return ErrImageUnknown 478 } 479 480 func (r *imageStore) removeName(image *Image, name string) { 481 image.Names = stringSliceWithoutValue(image.Names, name) 482 } 483 484 func (r *imageStore) SetNames(id string, names []string) error { 485 if !r.IsReadWrite() { 486 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change image name assignments at %q", r.imagespath()) 487 } 488 names = dedupeNames(names) 489 if image, ok := r.lookup(id); ok { 490 for _, name := range image.Names { 491 delete(r.byname, name) 492 } 493 for _, name := range names { 494 if otherImage, ok := r.byname[name]; ok { 495 r.removeName(otherImage, name) 496 } 497 r.byname[name] = image 498 } 499 image.Names = names 500 return r.Save() 501 } 502 return ErrImageUnknown 503 } 504 505 func (r *imageStore) Delete(id string) error { 506 if !r.IsReadWrite() { 507 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete images at %q", r.imagespath()) 508 } 509 image, ok := r.lookup(id) 510 if !ok { 511 return ErrImageUnknown 512 } 513 id = image.ID 514 toDeleteIndex := -1 515 for i, candidate := range r.images { 516 if candidate.ID == id { 517 toDeleteIndex = i 518 } 519 } 520 delete(r.byid, id) 521 r.idindex.Delete(id) 522 for _, name := range image.Names { 523 delete(r.byname, name) 524 } 525 for _, digest := range image.Digests { 526 prunedList := imageSliceWithoutValue(r.bydigest[digest], image) 527 if len(prunedList) == 0 { 528 delete(r.bydigest, digest) 529 } else { 530 r.bydigest[digest] = prunedList 531 } 532 } 533 if toDeleteIndex != -1 { 534 // delete the image at toDeleteIndex 535 if toDeleteIndex == len(r.images)-1 { 536 r.images = r.images[:len(r.images)-1] 537 } else { 538 r.images = append(r.images[:toDeleteIndex], r.images[toDeleteIndex+1:]...) 539 } 540 } 541 if err := r.Save(); err != nil { 542 return err 543 } 544 if err := os.RemoveAll(r.datadir(id)); err != nil { 545 return err 546 } 547 return nil 548 } 549 550 func (r *imageStore) Get(id string) (*Image, error) { 551 if image, ok := r.lookup(id); ok { 552 return copyImage(image), nil 553 } 554 return nil, ErrImageUnknown 555 } 556 557 func (r *imageStore) Lookup(name string) (id string, err error) { 558 if image, ok := r.lookup(name); ok { 559 return image.ID, nil 560 } 561 return "", ErrImageUnknown 562 } 563 564 func (r *imageStore) Exists(id string) bool { 565 _, ok := r.lookup(id) 566 return ok 567 } 568 569 func (r *imageStore) ByDigest(d digest.Digest) ([]*Image, error) { 570 if images, ok := r.bydigest[d]; ok { 571 return copyImageSlice(images), nil 572 } 573 return nil, ErrImageUnknown 574 } 575 576 func (r *imageStore) BigData(id, key string) ([]byte, error) { 577 if key == "" { 578 return nil, errors.Wrapf(ErrInvalidBigDataName, "can't retrieve image big data value for empty name") 579 } 580 image, ok := r.lookup(id) 581 if !ok { 582 return nil, ErrImageUnknown 583 } 584 return ioutil.ReadFile(r.datapath(image.ID, key)) 585 } 586 587 func (r *imageStore) BigDataSize(id, key string) (int64, error) { 588 if key == "" { 589 return -1, errors.Wrapf(ErrInvalidBigDataName, "can't retrieve size of image big data with empty name") 590 } 591 image, ok := r.lookup(id) 592 if !ok { 593 return -1, ErrImageUnknown 594 } 595 if image.BigDataSizes == nil { 596 image.BigDataSizes = make(map[string]int64) 597 } 598 if size, ok := image.BigDataSizes[key]; ok { 599 return size, nil 600 } 601 if data, err := r.BigData(id, key); err == nil && data != nil { 602 return int64(len(data)), nil 603 } 604 return -1, ErrSizeUnknown 605 } 606 607 func (r *imageStore) BigDataDigest(id, key string) (digest.Digest, error) { 608 if key == "" { 609 return "", errors.Wrapf(ErrInvalidBigDataName, "can't retrieve digest of image big data value with empty name") 610 } 611 image, ok := r.lookup(id) 612 if !ok { 613 return "", ErrImageUnknown 614 } 615 if image.BigDataDigests == nil { 616 image.BigDataDigests = make(map[string]digest.Digest) 617 } 618 if d, ok := image.BigDataDigests[key]; ok { 619 return d, nil 620 } 621 return "", ErrDigestUnknown 622 } 623 624 func (r *imageStore) BigDataNames(id string) ([]string, error) { 625 image, ok := r.lookup(id) 626 if !ok { 627 return nil, ErrImageUnknown 628 } 629 return copyStringSlice(image.BigDataNames), nil 630 } 631 632 func imageSliceWithoutValue(slice []*Image, value *Image) []*Image { 633 modified := make([]*Image, 0, len(slice)) 634 for _, v := range slice { 635 if v == value { 636 continue 637 } 638 modified = append(modified, v) 639 } 640 return modified 641 } 642 643 func (r *imageStore) SetBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error { 644 if key == "" { 645 return errors.Wrapf(ErrInvalidBigDataName, "can't set empty name for image big data item") 646 } 647 if !r.IsReadWrite() { 648 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to save data items associated with images at %q", r.imagespath()) 649 } 650 image, ok := r.lookup(id) 651 if !ok { 652 return ErrImageUnknown 653 } 654 err := os.MkdirAll(r.datadir(image.ID), 0700) 655 if err != nil { 656 return err 657 } 658 var newDigest digest.Digest 659 if bigDataNameIsManifest(key) { 660 if digestManifest == nil { 661 return errors.Wrapf(ErrDigestUnknown, "error digesting manifest: no manifest digest callback provided") 662 } 663 if newDigest, err = digestManifest(data); err != nil { 664 return errors.Wrapf(err, "error digesting manifest") 665 } 666 } else { 667 newDigest = digest.Canonical.FromBytes(data) 668 } 669 err = ioutils.AtomicWriteFile(r.datapath(image.ID, key), data, 0600) 670 if err == nil { 671 save := false 672 if image.BigDataSizes == nil { 673 image.BigDataSizes = make(map[string]int64) 674 } 675 oldSize, sizeOk := image.BigDataSizes[key] 676 image.BigDataSizes[key] = int64(len(data)) 677 if image.BigDataDigests == nil { 678 image.BigDataDigests = make(map[string]digest.Digest) 679 } 680 oldDigest, digestOk := image.BigDataDigests[key] 681 image.BigDataDigests[key] = newDigest 682 if !sizeOk || oldSize != image.BigDataSizes[key] || !digestOk || oldDigest != newDigest { 683 save = true 684 } 685 addName := true 686 for _, name := range image.BigDataNames { 687 if name == key { 688 addName = false 689 break 690 } 691 } 692 if addName { 693 image.BigDataNames = append(image.BigDataNames, key) 694 save = true 695 } 696 for _, oldDigest := range image.Digests { 697 // remove the image from the list of images in the digest-based index 698 if list, ok := r.bydigest[oldDigest]; ok { 699 prunedList := imageSliceWithoutValue(list, image) 700 if len(prunedList) == 0 { 701 delete(r.bydigest, oldDigest) 702 } else { 703 r.bydigest[oldDigest] = prunedList 704 } 705 } 706 } 707 if err = image.recomputeDigests(); err != nil { 708 return errors.Wrapf(err, "error loading recomputing image digest information for %s", image.ID) 709 } 710 for _, newDigest := range image.Digests { 711 // add the image to the list of images in the digest-based index which 712 // corresponds to the new digest for this item, unless it's already there 713 list := r.bydigest[newDigest] 714 if len(list) == len(imageSliceWithoutValue(list, image)) { 715 // the list isn't shortened by trying to prune this image from it, 716 // so it's not in there yet 717 r.bydigest[newDigest] = append(list, image) 718 } 719 } 720 if save { 721 err = r.Save() 722 } 723 } 724 return err 725 } 726 727 func (r *imageStore) Wipe() error { 728 if !r.IsReadWrite() { 729 return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to delete images at %q", r.imagespath()) 730 } 731 ids := make([]string, 0, len(r.byid)) 732 for id := range r.byid { 733 ids = append(ids, id) 734 } 735 for _, id := range ids { 736 if err := r.Delete(id); err != nil { 737 return err 738 } 739 } 740 return nil 741 } 742 743 func (r *imageStore) Lock() { 744 r.lockfile.Lock() 745 } 746 747 func (r *imageStore) RecursiveLock() { 748 r.lockfile.RecursiveLock() 749 } 750 751 func (r *imageStore) RLock() { 752 r.lockfile.RLock() 753 } 754 755 func (r *imageStore) Unlock() { 756 r.lockfile.Unlock() 757 } 758 759 func (r *imageStore) Touch() error { 760 return r.lockfile.Touch() 761 } 762 763 func (r *imageStore) Modified() (bool, error) { 764 return r.lockfile.Modified() 765 } 766 767 func (r *imageStore) IsReadWrite() bool { 768 return r.lockfile.IsReadWrite() 769 } 770 771 func (r *imageStore) TouchedSince(when time.Time) bool { 772 return r.lockfile.TouchedSince(when) 773 } 774 775 func (r *imageStore) Locked() bool { 776 return r.lockfile.Locked() 777 }