2020-04-05 09:20:50 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-04-05 09:20:50 +03:00
2016-12-26 04:16:37 +03:00
package lfs
import (
2024-02-25 16:32:13 +03:00
"crypto/sha256"
2016-12-26 04:16:37 +03:00
"encoding/hex"
"errors"
2021-03-06 23:21:56 +03:00
"hash"
2016-12-26 04:16:37 +03:00
"io"
"os"
2017-11-08 16:04:19 +03:00
2020-03-09 22:56:18 +03:00
"code.gitea.io/gitea/modules/log"
2020-09-08 18:45:10 +03:00
"code.gitea.io/gitea/modules/storage"
2016-12-26 04:16:37 +03:00
)
var (
2021-04-09 01:25:57 +03:00
// ErrHashMismatch occurs if the content has does not match OID
2023-05-09 10:34:36 +03:00
ErrHashMismatch = errors . New ( "content hash does not match OID" )
2021-04-09 01:25:57 +03:00
// ErrSizeMismatch occurs if the content size does not match
2023-05-09 10:34:36 +03:00
ErrSizeMismatch = errors . New ( "content size does not match" )
2016-12-26 04:16:37 +03:00
)
// ContentStore provides a simple file system based storage.
type ContentStore struct {
2020-09-08 18:45:10 +03:00
storage . ObjectStorage
2016-12-26 04:16:37 +03:00
}
2021-04-09 01:25:57 +03:00
// NewContentStore creates the default ContentStore
func NewContentStore ( ) * ContentStore {
contentStore := & ContentStore { ObjectStorage : storage . LFS }
return contentStore
}
2017-03-15 03:52:01 +03:00
// Get takes a Meta object and retrieves the content from the store, returning
2021-04-06 16:22:34 +03:00
// it as an io.ReadSeekCloser.
2021-04-09 01:25:57 +03:00
func ( s * ContentStore ) Get ( pointer Pointer ) ( storage . Object , error ) {
f , err := s . Open ( pointer . RelativePath ( ) )
2016-12-26 04:16:37 +03:00
if err != nil {
2021-04-09 01:25:57 +03:00
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to open Error: %v" , pointer . Oid , err )
2016-12-26 04:16:37 +03:00
return nil , err
}
return f , err
}
// Put takes a Meta object and an io.Reader and writes the content to the store.
2021-04-09 01:25:57 +03:00
func ( s * ContentStore ) Put ( pointer Pointer , r io . Reader ) error {
p := pointer . RelativePath ( )
2021-03-06 23:21:56 +03:00
// Wrap the provided reader with an inline hashing and size checker
2021-04-09 01:25:57 +03:00
wrappedRd := newHashingReader ( pointer . Size , pointer . Oid , r )
2021-03-06 23:21:56 +03:00
// now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
// the errors returned by the newHashingReader should percolate up to here
2021-04-09 01:25:57 +03:00
written , err := s . Save ( p , wrappedRd , pointer . Size )
2016-12-26 04:16:37 +03:00
if err != nil {
2021-04-09 01:25:57 +03:00
log . Error ( "Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v" , pointer . Oid , p , err )
2016-12-26 04:16:37 +03:00
return err
}
2023-03-28 18:10:24 +03:00
// check again whether there is any error during the Save operation
// because some errors might be ignored by the Reader's caller
if wrappedRd . lastError != nil && ! errors . Is ( wrappedRd . lastError , io . EOF ) {
err = wrappedRd . lastError
} else if written != pointer . Size {
err = ErrSizeMismatch
}
// if the upload failed, try to delete the file
if err != nil {
if errDel := s . Delete ( p ) ; errDel != nil {
log . Error ( "Cleaning the LFS OID[%s] failed: %v" , pointer . Oid , errDel )
2020-09-08 18:45:10 +03:00
}
2016-12-26 04:16:37 +03:00
}
2023-03-28 18:10:24 +03:00
return err
2016-12-26 04:16:37 +03:00
}
// Exists returns true if the object exists in the content store.
2021-04-09 01:25:57 +03:00
func ( s * ContentStore ) Exists ( pointer Pointer ) ( bool , error ) {
_ , err := s . ObjectStorage . Stat ( pointer . RelativePath ( ) )
2020-09-08 18:45:10 +03:00
if err != nil {
if os . IsNotExist ( err ) {
return false , nil
}
return false , err
2016-12-26 04:16:37 +03:00
}
2020-09-08 18:45:10 +03:00
return true , nil
2016-12-26 04:16:37 +03:00
}
2017-11-08 16:04:19 +03:00
// Verify returns true if the object exists in the content store and size is correct.
2021-04-09 01:25:57 +03:00
func ( s * ContentStore ) Verify ( pointer Pointer ) ( bool , error ) {
p := pointer . RelativePath ( )
2020-09-08 18:45:10 +03:00
fi , err := s . ObjectStorage . Stat ( p )
2021-04-09 01:25:57 +03:00
if os . IsNotExist ( err ) || ( err == nil && fi . Size ( ) != pointer . Size ) {
2017-11-08 16:04:19 +03:00
return false , nil
} else if err != nil {
2021-04-09 01:25:57 +03:00
log . Error ( "Unable stat file: %s for LFS OID[%s] Error: %v" , p , pointer . Oid , err )
2017-11-08 16:04:19 +03:00
return false , err
}
return true , nil
}
2021-03-06 23:21:56 +03:00
2022-06-12 18:51:54 +03:00
// ReadMetaObject will read a git_model.LFSMetaObject and return a reader
2023-05-09 10:34:36 +03:00
func ReadMetaObject ( pointer Pointer ) ( io . ReadSeekCloser , error ) {
2021-04-09 01:25:57 +03:00
contentStore := NewContentStore ( )
return contentStore . Get ( pointer )
}
2021-03-06 23:21:56 +03:00
type hashingReader struct {
internal io . Reader
currentSize int64
expectedSize int64
hash hash . Hash
expectedHash string
2023-03-28 18:10:24 +03:00
lastError error
}
// recordError records the last error during the Save operation
// Some callers of the Reader doesn't respect the returned "err"
// For example, MinIO's Put will ignore errors if the written size could equal to expected size
// So we must remember the error by ourselves,
// and later check again whether ErrSizeMismatch or ErrHashMismatch occurs during the Save operation
func ( r * hashingReader ) recordError ( err error ) error {
r . lastError = err
return err
2021-03-06 23:21:56 +03:00
}
func ( r * hashingReader ) Read ( b [ ] byte ) ( int , error ) {
n , err := r . internal . Read ( b )
if n > 0 {
r . currentSize += int64 ( n )
wn , werr := r . hash . Write ( b [ : n ] )
if wn != n || werr != nil {
2023-03-28 18:10:24 +03:00
return n , r . recordError ( werr )
2021-03-06 23:21:56 +03:00
}
}
2023-03-28 18:10:24 +03:00
if errors . Is ( err , io . EOF ) || r . currentSize >= r . expectedSize {
2021-03-06 23:21:56 +03:00
if r . currentSize != r . expectedSize {
2023-03-28 18:10:24 +03:00
return n , r . recordError ( ErrSizeMismatch )
2021-03-06 23:21:56 +03:00
}
shaStr := hex . EncodeToString ( r . hash . Sum ( nil ) )
if shaStr != r . expectedHash {
2023-03-28 18:10:24 +03:00
return n , r . recordError ( ErrHashMismatch )
2021-03-06 23:21:56 +03:00
}
}
2023-03-28 18:10:24 +03:00
return n , r . recordError ( err )
2021-03-06 23:21:56 +03:00
}
func newHashingReader ( expectedSize int64 , expectedHash string , reader io . Reader ) * hashingReader {
return & hashingReader {
internal : reader ,
expectedSize : expectedSize ,
expectedHash : expectedHash ,
hash : sha256 . New ( ) ,
}
}