github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/store/imagestore/store.go (about) 1 // Copyright 2014 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package imagestore 16 17 import ( 18 "crypto/sha512" 19 "database/sql" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "hash" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 "syscall" 30 "time" 31 32 "github.com/rkt/rkt/pkg/backup" 33 "github.com/rkt/rkt/pkg/lock" 34 "github.com/rkt/rkt/store/db" 35 36 "github.com/appc/spec/aci" 37 "github.com/appc/spec/schema" 38 "github.com/appc/spec/schema/types" 39 40 "github.com/hashicorp/errwrap" 41 "github.com/peterbourgon/diskv" 42 ) 43 44 const ( 45 blobType int64 = iota 46 imageManifestType 47 48 defaultPathPerm = os.FileMode(0770 | os.ModeSetgid) 49 defaultFilePerm = os.FileMode(0660) 50 51 // To ameliorate excessively long paths, keys for the (blob)store use 52 // only the first half of a sha512 rather than the entire sum 53 hashPrefix = "sha512-" 54 lenHash = sha512.Size // raw byte size 55 lenHashKey = (lenHash / 2) * 2 // half length, in hex characters 56 lenKey = len(hashPrefix) + lenHashKey 57 minlenKey = len(hashPrefix) + 2 // at least sha512-aa 58 59 // how many backups to keep when migrating to new db version 60 backupsNumber = 5 61 ) 62 63 var diskvStores = [...]string{ 64 "blob", 65 "imageManifest", 66 } 67 68 var ( 69 ErrKeyNotFound = errors.New("no image IDs found") 70 ErrDBUpdateNeedsRoot = errors.New("database schema needs to be updated, re-run as root to perform the update") 71 ) 72 73 // ACINotFoundError is returned when an ACI cannot be found by GetACI 74 // Useful to distinguish a generic error from an aci not found. 75 type ACINotFoundError struct { 76 name types.ACIdentifier 77 labels types.Labels 78 } 79 80 func (e ACINotFoundError) Error() string { 81 return fmt.Sprintf( 82 "cannot find aci satisfying name: %q and labels: %q in the local store", 83 e.name, e.labels) 84 } 85 86 // StoreRemovalError defines an error removing a non transactional store (like 87 // a diskv store). 88 // When this happen there's the possibility that the store is left in an 89 // unclean state (for example with some stale files). 90 type StoreRemovalError struct { 91 errors []error 92 } 93 94 func (e *StoreRemovalError) Error() string { 95 s := fmt.Sprintf("some aci disk entries cannot be removed: ") 96 for _, err := range e.errors { 97 s = s + fmt.Sprintf("[%v]", err) 98 } 99 return s 100 } 101 102 // Store encapsulates a content-addressable-storage for storing ACIs on disk. 103 type Store struct { 104 dir string 105 stores []*diskv.Diskv 106 db *db.DB 107 // storeLock is a lock on the whole store. It's used for store migration. If 108 // a previous version of rkt is using the store and in the meantime a 109 // new version is installed and executed it will try migrate the store 110 // during NewStore. This means that the previous running rkt will fail 111 // or behave badly after the migration as it's expecting another db format. 112 // For this reason, before executing migration, an exclusive lock must 113 // be taken on the whole store. 114 storeLock *lock.FileLock 115 imageLockDir string 116 } 117 118 func (s *Store) updateSize(key string, newSize int64) error { 119 return s.db.Do(func(tx *sql.Tx) error { 120 _, err := tx.Exec("UPDATE aciinfo SET size = $1 WHERE blobkey == $2", newSize, key) 121 return err 122 }) 123 } 124 125 // TODO(sgotti) remove this when the treestore will save its images' sizes by itself 126 func (s *Store) UpdateTreeStoreSize(key string, newSize int64) error { 127 return s.db.Do(func(tx *sql.Tx) error { 128 _, err := tx.Exec("UPDATE aciinfo SET treestoresize = $1 WHERE blobkey == $2", newSize, key) 129 return err 130 }) 131 } 132 133 func (s *Store) populateSize() error { 134 var ais []*ACIInfo 135 err := s.db.Do(func(tx *sql.Tx) error { 136 var err error 137 ais, err = GetACIInfosWithKeyPrefix(tx, "") 138 return err 139 }) 140 if err != nil { 141 return errwrap.Wrap(errors.New("error retrieving ACI Infos"), err) 142 } 143 144 aciSizes := make(map[string]int64) 145 for _, ai := range ais { 146 key := ai.BlobKey 147 148 im, err := s.ReadStream(key) 149 if err != nil { 150 return err 151 } 152 153 rd, err := io.Copy(ioutil.Discard, im) 154 if err != nil { 155 return err 156 } 157 aciSizes[key] = rd 158 159 } 160 161 for k := range aciSizes { 162 s.updateSize(k, aciSizes[k]) 163 } 164 165 return nil 166 } 167 168 func NewStore(dir string) (*Store, error) { 169 // We need to allow the store's setgid bits (if any) to propagate, so 170 // disable umask 171 um := syscall.Umask(0) 172 defer syscall.Umask(um) 173 174 s := &Store{ 175 dir: dir, 176 stores: make([]*diskv.Diskv, len(diskvStores)), 177 } 178 179 s.imageLockDir = filepath.Join(dir, "imagelocks") 180 err := os.MkdirAll(s.imageLockDir, defaultPathPerm) 181 if err != nil { 182 return nil, err 183 } 184 185 // Take a shared cas lock 186 s.storeLock, err = lock.NewLock(dir, lock.Dir) 187 if err != nil { 188 return nil, err 189 } 190 if err := s.storeLock.SharedLock(); err != nil { 191 return nil, err 192 } 193 194 for i, p := range diskvStores { 195 s.stores[i] = diskv.New(diskv.Options{ 196 PathPerm: defaultPathPerm, 197 FilePerm: defaultFilePerm, 198 BasePath: filepath.Join(dir, p), 199 Transform: blockTransform, 200 }) 201 } 202 db, err := db.NewDB(s.dbDir()) 203 if err != nil { 204 return nil, err 205 } 206 s.db = db 207 208 needsMigrate := false 209 needsSizePopulation := false 210 fn := func(tx *sql.Tx) error { 211 var err error 212 ok, err := dbIsPopulated(tx) 213 if err != nil { 214 return err 215 } 216 // populate the db 217 if !ok { 218 for _, stmt := range dbCreateStmts { 219 _, err = tx.Exec(stmt) 220 if err != nil { 221 return err 222 } 223 } 224 return nil 225 } 226 // if db is populated check its version 227 version, err := getDBVersion(tx) 228 if err != nil { 229 return err 230 } 231 if version < dbVersion { 232 needsMigrate = true 233 } 234 if version > dbVersion { 235 return fmt.Errorf("current store db version: %d (greater than the current rkt expected version: %d)", version, dbVersion) 236 } 237 if version < 5 { 238 needsSizePopulation = true 239 } 240 return nil 241 } 242 if err = db.Do(fn); err != nil { 243 return nil, err 244 } 245 246 // migration is done in another transaction as it must take an exclusive 247 // store lock. If, in the meantime, another process has already done the 248 // migration, between the previous db version check and the below 249 // migration code, the migration will do nothing as it'll start 250 // migration from the current version. 251 if needsMigrate { 252 // Take an exclusive store lock 253 err := s.storeLock.ExclusiveLock() 254 if err != nil { 255 return nil, err 256 } 257 if err := s.backupDB(); err != nil { 258 return nil, err 259 } 260 fn := func(tx *sql.Tx) error { 261 return migrate(tx, dbVersion) 262 } 263 if err = db.Do(fn); err != nil { 264 return nil, err 265 } 266 267 if needsSizePopulation { 268 if err := s.populateSize(); err != nil { 269 return nil, err 270 } 271 } 272 } 273 274 return s, nil 275 } 276 277 // Close closes a Store opened with NewStore(). 278 func (s *Store) Close() error { 279 return s.storeLock.Close() 280 } 281 282 // backupDB backs up current database. 283 func (s *Store) backupDB() error { 284 if os.Geteuid() != 0 { 285 return ErrDBUpdateNeedsRoot 286 } 287 backupsDir := filepath.Join(s.dir, "db-backups") 288 return backup.CreateBackup(s.dbDir(), backupsDir, backupsNumber) 289 } 290 291 func (s *Store) dbDir() string { 292 return filepath.Join(s.dir, "db") 293 } 294 295 // TODO(sgotti), unexport this and provide other functions for external users 296 // TmpFile returns an *os.File local to the same filesystem as the Store, or 297 // any error encountered 298 func (s *Store) TmpFile() (*os.File, error) { 299 dir, err := s.TmpDir() 300 if err != nil { 301 return nil, err 302 } 303 return ioutil.TempFile(dir, "") 304 } 305 306 // TODO(sgotti), unexport this and provide other functions for external users 307 // TmpNamedFile returns an *os.File with the specified name local to the same 308 // filesystem as the Store, or any error encountered. If the file already 309 // exists it will return the existing file in read/write mode with the cursor 310 // at the end of the file. 311 func (s Store) TmpNamedFile(name string) (*os.File, error) { 312 dir, err := s.TmpDir() 313 if err != nil { 314 return nil, err 315 } 316 fname := filepath.Join(dir, name) 317 _, err = os.Stat(fname) 318 if os.IsNotExist(err) { 319 return os.Create(fname) 320 } 321 if err != nil { 322 return nil, err 323 } 324 return os.OpenFile(fname, os.O_RDWR|os.O_APPEND, 0644) 325 } 326 327 // TODO(sgotti), unexport this and provide other functions for external users 328 // TmpDir creates and returns dir local to the same filesystem as the Store, 329 // or any error encountered 330 func (s *Store) TmpDir() (string, error) { 331 dir := filepath.Join(s.dir, "tmp") 332 if err := os.MkdirAll(dir, defaultPathPerm); err != nil { 333 return "", err 334 } 335 return dir, nil 336 } 337 338 // ResolveKey resolves a partial key (of format `sha512-0c45e8c0ab2`) to a full 339 // key by considering the key a prefix and using the store for resolution. 340 // If the key is longer than the full key length, it is first truncated. 341 func (s *Store) ResolveKey(key string) (string, error) { 342 if !strings.HasPrefix(key, hashPrefix) { 343 return "", fmt.Errorf("wrong key prefix") 344 } 345 if len(key) < minlenKey { 346 return "", fmt.Errorf("image ID too short") 347 } 348 if len(key) > lenKey { 349 key = key[:lenKey] 350 } 351 352 var aciInfos []*ACIInfo 353 err := s.db.Do(func(tx *sql.Tx) error { 354 var err error 355 aciInfos, err = GetACIInfosWithKeyPrefix(tx, key) 356 return err 357 }) 358 if err != nil { 359 return "", errwrap.Wrap(errors.New("error retrieving ACI Infos"), err) 360 } 361 362 keyCount := len(aciInfos) 363 if keyCount == 0 { 364 return "", ErrKeyNotFound 365 } 366 if keyCount != 1 { 367 return "", fmt.Errorf("ambiguous image ID: %q", key) 368 } 369 return aciInfos[0].BlobKey, nil 370 } 371 372 // ResolveName resolves an image name to a list of full keys and using the 373 // store for resolution. 374 func (s *Store) ResolveName(name string) ([]string, bool, error) { 375 var ( 376 aciInfos []*ACIInfo 377 found bool 378 ) 379 err := s.db.Do(func(tx *sql.Tx) error { 380 var err error 381 aciInfos, found, err = GetACIInfosWithName(tx, name) 382 return err 383 }) 384 if err != nil { 385 return nil, found, errwrap.Wrap(errors.New("error retrieving ACI Infos"), err) 386 } 387 388 keys := make([]string, len(aciInfos)) 389 for i, aciInfo := range aciInfos { 390 keys[i] = aciInfo.BlobKey 391 } 392 393 return keys, found, nil 394 } 395 396 func (s *Store) ReadStream(key string) (io.ReadCloser, error) { 397 key, err := s.ResolveKey(key) 398 if err != nil { 399 return nil, errwrap.Wrap(errors.New("error resolving image ID"), err) 400 } 401 keyLock, err := lock.SharedKeyLock(s.imageLockDir, key) 402 if err != nil { 403 return nil, errwrap.Wrap(errors.New("error locking image"), err) 404 } 405 defer keyLock.Close() 406 407 err = s.db.Do(func(tx *sql.Tx) error { 408 aciinfo, found, err := GetACIInfoWithBlobKey(tx, key) 409 if err != nil { 410 return errwrap.Wrap(errors.New("error getting aciinfo"), err) 411 } else if !found { 412 return fmt.Errorf("cannot find image with key: %s", key) 413 } 414 415 aciinfo.LastUsed = time.Now() 416 417 return WriteACIInfo(tx, aciinfo) 418 }) 419 if err != nil { 420 return nil, errwrap.Wrap(fmt.Errorf("cannot get image info for %q from db", key), err) 421 } 422 423 return s.stores[blobType].ReadStream(key, false) 424 } 425 426 // WriteACI takes an ACI encapsulated in an io.Reader, decompresses it if 427 // necessary, and then stores it in the store under a key based on the image ID 428 // (i.e. the hash of the uncompressed ACI) 429 // latest defines if the aci has to be marked as the latest. For example an ACI 430 // discovered without asking for a specific version (latest pattern). 431 func (s *Store) WriteACI(r io.ReadSeeker, fetchInfo ACIFetchInfo) (string, error) { 432 // We need to allow the store's setgid bits (if any) to propagate, so 433 // disable umask 434 um := syscall.Umask(0) 435 defer syscall.Umask(um) 436 437 dr, err := aci.NewCompressedReader(r) 438 if err != nil { 439 return "", errwrap.Wrap(errors.New("error decompressing image"), err) 440 } 441 defer dr.Close() 442 443 // Write the decompressed image (tar) to a temporary file on disk, and 444 // tee so we can generate the hash 445 h := sha512.New() 446 tr := io.TeeReader(dr, h) 447 fh, err := s.TmpFile() 448 if err != nil { 449 return "", errwrap.Wrap(errors.New("error creating image"), err) 450 } 451 sz, err := io.Copy(fh, tr) 452 if err != nil { 453 return "", errwrap.Wrap(errors.New("error copying image"), err) 454 } 455 im, err := aci.ManifestFromImage(fh) 456 if err != nil { 457 return "", errwrap.Wrap(errors.New("error extracting image manifest"), err) 458 } 459 if err := fh.Close(); err != nil { 460 return "", errwrap.Wrap(errors.New("error closing image"), err) 461 } 462 463 // Import the uncompressed image into the store at the real key 464 key := s.HashToKey(h) 465 keyLock, err := lock.ExclusiveKeyLock(s.imageLockDir, key) 466 if err != nil { 467 return "", errwrap.Wrap(errors.New("error locking image"), err) 468 } 469 defer keyLock.Close() 470 471 if err = s.stores[blobType].Import(fh.Name(), key, true); err != nil { 472 return "", errwrap.Wrap(errors.New("error importing image"), err) 473 } 474 475 // Save the imagemanifest using the same key used for the image 476 imj, err := json.Marshal(im) 477 if err != nil { 478 return "", errwrap.Wrap(errors.New("error marshalling image manifest"), err) 479 } 480 if err := s.stores[imageManifestType].Write(key, imj); err != nil { 481 return "", errwrap.Wrap(errors.New("error importing image manifest"), err) 482 } 483 484 // Save aciinfo 485 if err = s.db.Do(func(tx *sql.Tx) error { 486 aciinfo := &ACIInfo{ 487 BlobKey: key, 488 Name: im.Name.String(), 489 ImportTime: time.Now(), 490 LastUsed: time.Now(), 491 Latest: fetchInfo.Latest, 492 Size: sz, 493 } 494 return WriteACIInfo(tx, aciinfo) 495 }); err != nil { 496 return "", errwrap.Wrap(errors.New("error writing ACI Info"), err) 497 } 498 499 return key, nil 500 } 501 502 // RemoveACI removes the ACI with the given key. It firstly removes the aci 503 // infos inside the db, then it tries to remove the non transactional data. 504 // If some error occurs removing some non transactional data a 505 // StoreRemovalError is returned. 506 func (s *Store) RemoveACI(key string) error { 507 imageKeyLock, err := lock.ExclusiveKeyLock(s.imageLockDir, key) 508 if err != nil { 509 return errwrap.Wrap(errors.New("error locking image"), err) 510 } 511 defer imageKeyLock.Close() 512 513 // Firstly remove aciinfo and remote from the db in an unique transaction. 514 // remote needs to be removed or a GetRemote will return a blobKey not 515 // referenced by any ACIInfo. 516 err = s.db.Do(func(tx *sql.Tx) error { 517 if _, found, err := GetACIInfoWithBlobKey(tx, key); err != nil { 518 return errwrap.Wrap(errors.New("error getting aciinfo"), err) 519 } else if !found { 520 return fmt.Errorf("cannot find image with key: %s", key) 521 } 522 523 if err := RemoveACIInfo(tx, key); err != nil { 524 return err 525 } 526 if err := RemoveRemote(tx, key); err != nil { 527 return err 528 } 529 return nil 530 }) 531 if err != nil { 532 return errwrap.Wrap(fmt.Errorf("cannot remove image with ID: %s from db", key), err) 533 } 534 535 // Then remove non transactional entries from the blob, imageManifest 536 // and tree store. 537 // TODO(sgotti). Now that the ACIInfo is removed the image doesn't 538 // exists anymore, but errors removing non transactional entries can 539 // leave stale data that will require a cas GC to be implemented. 540 var storeErrors []error 541 for _, ds := range s.stores { 542 if err := ds.Erase(key); err != nil { 543 // If there's an error save it and continue with the other stores 544 storeErrors = append(storeErrors, err) 545 } 546 } 547 if len(storeErrors) > 0 { 548 return &StoreRemovalError{errors: storeErrors} 549 } 550 return nil 551 } 552 553 // GetRemote tries to retrieve a remote with the given ACIURL. 554 // If remote doesn't exist, it returns ErrRemoteNotFound error. 555 func (s *Store) GetRemote(aciURL string) (*Remote, error) { 556 var remote *Remote 557 558 err := s.db.Do(func(tx *sql.Tx) error { 559 var err error 560 561 remote, err = GetRemote(tx, aciURL) 562 563 return err 564 }) 565 if err != nil { 566 return nil, err 567 } 568 569 return remote, nil 570 } 571 572 // WriteRemote adds or updates the provided Remote. 573 func (s *Store) WriteRemote(remote *Remote) error { 574 err := s.db.Do(func(tx *sql.Tx) error { 575 return WriteRemote(tx, remote) 576 }) 577 return err 578 } 579 580 // GetImageManifestJSON gets the ImageManifest JSON bytes with the 581 // specified key. 582 func (s *Store) GetImageManifestJSON(key string) ([]byte, error) { 583 key, err := s.ResolveKey(key) 584 if err != nil { 585 return nil, errwrap.Wrap(errors.New("error resolving image ID"), err) 586 } 587 keyLock, err := lock.SharedKeyLock(s.imageLockDir, key) 588 if err != nil { 589 return nil, errwrap.Wrap(errors.New("error locking image"), err) 590 } 591 defer keyLock.Close() 592 593 imj, err := s.stores[imageManifestType].Read(key) 594 if err != nil { 595 return nil, errwrap.Wrap(errors.New("error retrieving image manifest"), err) 596 } 597 return imj, nil 598 } 599 600 // GetImageManifest gets the ImageManifest with the specified key. 601 func (s *Store) GetImageManifest(key string) (*schema.ImageManifest, error) { 602 imj, err := s.GetImageManifestJSON(key) 603 if err != nil { 604 return nil, err 605 } 606 var im *schema.ImageManifest 607 if err = json.Unmarshal(imj, &im); err != nil { 608 return nil, errwrap.Wrap(errors.New("error unmarshalling image manifest"), err) 609 } 610 return im, nil 611 } 612 613 // GetACI retrieves the ACI that best matches the provided app name and labels. 614 // The returned value is the blob store key of the retrieved ACI. 615 // If there are multiple matching ACIs choose the latest one (defined as the 616 // last one imported in the store). 617 // If no version label is requested, ACIs marked as latest in the ACIInfo are 618 // preferred. 619 func (s *Store) GetACI(name types.ACIdentifier, labels types.Labels) (string, error) { 620 var curaciinfo *ACIInfo 621 versionRequested := false 622 if _, ok := labels.Get("version"); ok { 623 versionRequested = true 624 } 625 626 var aciinfos []*ACIInfo 627 err := s.db.Do(func(tx *sql.Tx) error { 628 var err error 629 aciinfos, _, err = GetACIInfosWithName(tx, name.String()) 630 return err 631 }) 632 if err != nil { 633 return "", err 634 } 635 636 nextKey: 637 for _, aciinfo := range aciinfos { 638 im, err := s.GetImageManifest(aciinfo.BlobKey) 639 if err != nil { 640 return "", errwrap.Wrap(errors.New("error getting image manifest"), err) 641 } 642 643 // The image manifest must have all the requested labels 644 for _, l := range labels { 645 ok := false 646 for _, rl := range im.Labels { 647 if l.Name == rl.Name && l.Value == rl.Value { 648 ok = true 649 break 650 } 651 } 652 if !ok { 653 continue nextKey 654 } 655 } 656 657 if curaciinfo != nil { 658 // If no version is requested prefer the acis marked as latest 659 if !versionRequested { 660 if !curaciinfo.Latest && aciinfo.Latest { 661 curaciinfo = aciinfo 662 continue nextKey 663 } 664 if curaciinfo.Latest && !aciinfo.Latest { 665 continue nextKey 666 } 667 } 668 // If multiple matching image manifests are found, choose the latest imported in the cas. 669 if aciinfo.ImportTime.After(curaciinfo.ImportTime) { 670 curaciinfo = aciinfo 671 } 672 } else { 673 curaciinfo = aciinfo 674 } 675 } 676 677 if curaciinfo != nil { 678 return curaciinfo.BlobKey, nil 679 } 680 return "", ACINotFoundError{name: name, labels: labels} 681 } 682 683 func (s *Store) GetAllRemotes() ([]*Remote, error) { 684 var remotes []*Remote 685 err := s.db.Do(func(tx *sql.Tx) error { 686 var err error 687 remotes, err = GetAllRemotes(tx) 688 return err 689 }) 690 if err != nil { 691 return nil, err 692 } 693 694 return remotes, nil 695 } 696 697 func (s *Store) GetAllACIInfos(sortfields []string, ascending bool) ([]*ACIInfo, error) { 698 var aciInfos []*ACIInfo 699 err := s.db.Do(func(tx *sql.Tx) error { 700 var err error 701 aciInfos, err = GetAllACIInfos(tx, sortfields, ascending) 702 return err 703 }) 704 return aciInfos, err 705 } 706 707 func (s *Store) GetACIInfoWithBlobKey(blobKey string) (*ACIInfo, error) { 708 var aciInfo *ACIInfo 709 var found bool 710 err := s.db.Do(func(tx *sql.Tx) error { 711 var err error 712 aciInfo, found, err = GetACIInfoWithBlobKey(tx, blobKey) 713 return err 714 }) 715 if !found { 716 if err != nil { 717 err = errwrap.Wrap(fmt.Errorf("ACI info not found with blob key %q", blobKey), err) 718 } else { 719 err = fmt.Errorf("ACI info not found with blob key %q", blobKey) 720 } 721 } 722 return aciInfo, err 723 } 724 725 func (s *Store) Dump(hex bool) { 726 for _, ds := range s.stores { 727 var keyCount int 728 for key := range ds.Keys(nil) { 729 val, err := ds.Read(key) 730 if err != nil { 731 panic(fmt.Sprintf("key %s had no value", key)) 732 } 733 if len(val) > 128 { 734 val = val[:128] 735 } 736 out := string(val) 737 if hex { 738 out = fmt.Sprintf("%x", val) 739 } 740 fmt.Printf("%s/%s: %s\n", ds.BasePath, key, out) 741 keyCount++ 742 } 743 fmt.Printf("%d total image ID(s)\n", keyCount) 744 } 745 } 746 747 // HashToKey takes a hash.Hash (which currently _MUST_ represent a full SHA512), 748 // calculates its sum, and returns a string which should be used as the key to 749 // store the data matching the hash. 750 func (s *Store) HashToKey(h hash.Hash) string { 751 return hashToKey(h) 752 } 753 754 // HasFullKey returns whether the image with the given key exists on the disk by 755 // checking if the image manifest kv store contains the key. 756 func (s *Store) HasFullKey(key string) bool { 757 return s.stores[imageManifestType].Has(key) 758 } 759 760 func hashToKey(h hash.Hash) string { 761 s := h.Sum(nil) 762 return keyToString(s) 763 } 764 765 // keyToString takes a key and returns a shortened and prefixed hexadecimal string version 766 func keyToString(k []byte) string { 767 if len(k) != lenHash { 768 panic(fmt.Sprintf("bad hash passed to hashToKey: %x", k)) 769 } 770 return fmt.Sprintf("%s%x", hashPrefix, k)[0:lenKey] 771 }