You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

content_store.go 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package lfs
  4. import (
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "errors"
  8. "hash"
  9. "io"
  10. "os"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/storage"
  13. )
  14. var (
  15. // ErrHashMismatch occurs if the content has does not match OID
  16. ErrHashMismatch = errors.New("Content hash does not match OID")
  17. // ErrSizeMismatch occurs if the content size does not match
  18. ErrSizeMismatch = errors.New("Content size does not match")
  19. )
  20. // ContentStore provides a simple file system based storage.
  21. type ContentStore struct {
  22. storage.ObjectStorage
  23. }
  24. // NewContentStore creates the default ContentStore
  25. func NewContentStore() *ContentStore {
  26. contentStore := &ContentStore{ObjectStorage: storage.LFS}
  27. return contentStore
  28. }
  29. // Get takes a Meta object and retrieves the content from the store, returning
  30. // it as an io.ReadSeekCloser.
  31. func (s *ContentStore) Get(pointer Pointer) (storage.Object, error) {
  32. f, err := s.Open(pointer.RelativePath())
  33. if err != nil {
  34. log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", pointer.Oid, err)
  35. return nil, err
  36. }
  37. return f, err
  38. }
  39. // Put takes a Meta object and an io.Reader and writes the content to the store.
  40. func (s *ContentStore) Put(pointer Pointer, r io.Reader) error {
  41. p := pointer.RelativePath()
  42. // Wrap the provided reader with an inline hashing and size checker
  43. wrappedRd := newHashingReader(pointer.Size, pointer.Oid, r)
  44. // now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
  45. // the errors returned by the newHashingReader should percolate up to here
  46. written, err := s.Save(p, wrappedRd, pointer.Size)
  47. if err != nil {
  48. log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", pointer.Oid, p, err)
  49. return err
  50. }
  51. // check again whether there is any error during the Save operation
  52. // because some errors might be ignored by the Reader's caller
  53. if wrappedRd.lastError != nil && !errors.Is(wrappedRd.lastError, io.EOF) {
  54. err = wrappedRd.lastError
  55. } else if written != pointer.Size {
  56. err = ErrSizeMismatch
  57. }
  58. // if the upload failed, try to delete the file
  59. if err != nil {
  60. if errDel := s.Delete(p); errDel != nil {
  61. log.Error("Cleaning the LFS OID[%s] failed: %v", pointer.Oid, errDel)
  62. }
  63. }
  64. return err
  65. }
  66. // Exists returns true if the object exists in the content store.
  67. func (s *ContentStore) Exists(pointer Pointer) (bool, error) {
  68. _, err := s.ObjectStorage.Stat(pointer.RelativePath())
  69. if err != nil {
  70. if os.IsNotExist(err) {
  71. return false, nil
  72. }
  73. return false, err
  74. }
  75. return true, nil
  76. }
  77. // Verify returns true if the object exists in the content store and size is correct.
  78. func (s *ContentStore) Verify(pointer Pointer) (bool, error) {
  79. p := pointer.RelativePath()
  80. fi, err := s.ObjectStorage.Stat(p)
  81. if os.IsNotExist(err) || (err == nil && fi.Size() != pointer.Size) {
  82. return false, nil
  83. } else if err != nil {
  84. log.Error("Unable stat file: %s for LFS OID[%s] Error: %v", p, pointer.Oid, err)
  85. return false, err
  86. }
  87. return true, nil
  88. }
  89. // ReadMetaObject will read a git_model.LFSMetaObject and return a reader
  90. func ReadMetaObject(pointer Pointer) (io.ReadCloser, error) {
  91. contentStore := NewContentStore()
  92. return contentStore.Get(pointer)
  93. }
  94. type hashingReader struct {
  95. internal io.Reader
  96. currentSize int64
  97. expectedSize int64
  98. hash hash.Hash
  99. expectedHash string
  100. lastError error
  101. }
  102. // recordError records the last error during the Save operation
  103. // Some callers of the Reader doesn't respect the returned "err"
  104. // For example, MinIO's Put will ignore errors if the written size could equal to expected size
  105. // So we must remember the error by ourselves,
  106. // and later check again whether ErrSizeMismatch or ErrHashMismatch occurs during the Save operation
  107. func (r *hashingReader) recordError(err error) error {
  108. r.lastError = err
  109. return err
  110. }
  111. func (r *hashingReader) Read(b []byte) (int, error) {
  112. n, err := r.internal.Read(b)
  113. if n > 0 {
  114. r.currentSize += int64(n)
  115. wn, werr := r.hash.Write(b[:n])
  116. if wn != n || werr != nil {
  117. return n, r.recordError(werr)
  118. }
  119. }
  120. if errors.Is(err, io.EOF) || r.currentSize >= r.expectedSize {
  121. if r.currentSize != r.expectedSize {
  122. return n, r.recordError(ErrSizeMismatch)
  123. }
  124. shaStr := hex.EncodeToString(r.hash.Sum(nil))
  125. if shaStr != r.expectedHash {
  126. return n, r.recordError(ErrHashMismatch)
  127. }
  128. }
  129. return n, r.recordError(err)
  130. }
  131. func newHashingReader(expectedSize int64, expectedHash string, reader io.Reader) *hashingReader {
  132. return &hashingReader{
  133. internal: reader,
  134. expectedSize: expectedSize,
  135. expectedHash: expectedHash,
  136. hash: sha256.New(),
  137. }
  138. }