github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/blobstore/local.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     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 blobstore
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/dolthub/dolt/go/store/util/tempfiles"
    26  
    27  	"github.com/dolthub/fslock"
    28  	"github.com/google/uuid"
    29  )
    30  
    31  const (
    32  	lfsVerSize = 16
    33  	bsExt      = ".bs"
    34  	lockExt    = ".lock"
    35  )
    36  
    37  type localBlobRangeReadCloser struct {
    38  	br  BlobRange
    39  	rc  io.ReadCloser
    40  	pos int64
    41  }
    42  
    43  func (lbrrc *localBlobRangeReadCloser) Read(p []byte) (int, error) {
    44  	remaining := lbrrc.br.length - lbrrc.pos
    45  
    46  	if remaining == 0 {
    47  		return 0, io.EOF
    48  	} else if int64(len(p)) > remaining {
    49  		partial := p[:remaining]
    50  		n, err := lbrrc.rc.Read(partial)
    51  		lbrrc.pos += int64(n)
    52  
    53  		return n, err
    54  	}
    55  
    56  	n, err := lbrrc.rc.Read(p)
    57  	lbrrc.pos += int64(n)
    58  
    59  	return n, err
    60  }
    61  
    62  func (lbrrc *localBlobRangeReadCloser) Close() error {
    63  	return lbrrc.rc.Close()
    64  }
    65  
    66  // LocalBlobstore is a Blobstore implementation that uses the local filesystem
    67  type LocalBlobstore struct {
    68  	RootDir string
    69  }
    70  
    71  // NewLocalBlobstore returns a new LocalBlobstore instance
    72  func NewLocalBlobstore(dir string) *LocalBlobstore {
    73  	return &LocalBlobstore{dir}
    74  }
    75  
    76  // Get retrieves an io.reader for the portion of a blob specified by br along with
    77  // its version
    78  func (bs *LocalBlobstore) Get(ctx context.Context, key string, br BlobRange) (io.ReadCloser, string, error) {
    79  	path := filepath.Join(bs.RootDir, key) + bsExt
    80  	f, err := os.Open(path)
    81  
    82  	if err != nil {
    83  		if os.IsNotExist(err) {
    84  			return nil, "", NotFound{key}
    85  		}
    86  
    87  		return nil, "", err
    88  	}
    89  
    90  	var ver uuid.UUID
    91  	n, err := f.Read(ver[:lfsVerSize])
    92  
    93  	if n != lfsVerSize {
    94  		f.Close()
    95  		return nil, "", errors.New("failed to read version")
    96  	} else if err != nil {
    97  		f.Close()
    98  		return nil, "", err
    99  	}
   100  
   101  	rc, err := readCloserForFileRange(f, lfsVerSize, br)
   102  
   103  	if err != nil {
   104  		f.Close()
   105  		return nil, "", err
   106  	}
   107  
   108  	return rc, ver.String(), nil
   109  }
   110  
   111  func readCloserForFileRange(f *os.File, pos int, br BlobRange) (io.ReadCloser, error) {
   112  	seekType := 1
   113  	if br.offset < 0 {
   114  		info, err := f.Stat()
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		seekType = 0
   119  		br = br.positiveRange(info.Size())
   120  	}
   121  
   122  	_, err := f.Seek(br.offset, seekType)
   123  
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	if br.length != 0 {
   129  		return &localBlobRangeReadCloser{br, f, 0}, nil
   130  	}
   131  
   132  	return f, nil
   133  }
   134  
   135  func writeAll(f *os.File, readers ...io.Reader) error {
   136  	for _, reader := range readers {
   137  		_, err := io.Copy(f, reader)
   138  
   139  		if err != nil {
   140  			return err
   141  		}
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  // Put sets the blob and the version for a key
   148  func (bs *LocalBlobstore) Put(ctx context.Context, key string, reader io.Reader) (string, error) {
   149  	ver := uuid.New()
   150  
   151  	// written as temp file and renamed so the file corresponding to this key
   152  	// never exists in a partially written state
   153  	tempFile, err := func() (string, error) {
   154  		temp, err := tempfiles.MovableTempFileProvider.NewFile("", ver.String())
   155  
   156  		if err != nil {
   157  			return "", err
   158  		}
   159  
   160  		defer temp.Close()
   161  
   162  		verBytes := [lfsVerSize]byte(ver)
   163  		verReader := bytes.NewReader(verBytes[:lfsVerSize])
   164  		return temp.Name(), writeAll(temp, verReader, reader)
   165  	}()
   166  
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  
   171  	path := filepath.Join(bs.RootDir, key) + bsExt
   172  	err = os.Rename(tempFile, path)
   173  
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  
   178  	return ver.String(), nil
   179  }
   180  
   181  func fLock(lockFilePath string) (*fslock.Lock, error) {
   182  	lck := fslock.New(lockFilePath)
   183  	err := lck.Lock()
   184  
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	return lck, nil
   190  }
   191  
   192  // CheckAndPut will check the current version of a blob against an expectedVersion, and if the
   193  // versions match it will update the data and version associated with the key
   194  func (bs *LocalBlobstore) CheckAndPut(ctx context.Context, expectedVersion, key string, reader io.Reader) (string, error) {
   195  	path := filepath.Join(bs.RootDir, key) + bsExt
   196  	lockFilePath := path + lockExt
   197  	lck, err := fLock(lockFilePath)
   198  
   199  	if err != nil {
   200  		return "", errors.New("Could not acquire lock of " + lockFilePath)
   201  	}
   202  
   203  	defer lck.Unlock()
   204  
   205  	rc, ver, err := bs.Get(ctx, key, BlobRange{})
   206  
   207  	if err != nil {
   208  		if !IsNotFoundError(err) {
   209  			return "", errors.New("Unable to read current version of " + path)
   210  		}
   211  	} else {
   212  		rc.Close()
   213  	}
   214  
   215  	if expectedVersion != ver {
   216  		return "", CheckAndPutError{key, expectedVersion, ver}
   217  	}
   218  
   219  	return bs.Put(ctx, key, reader)
   220  }
   221  
   222  // Exists returns true if a blob exists for the given key, and false if it does not.
   223  // error may be returned if there are errors accessing the filesystem data.
   224  func (bs *LocalBlobstore) Exists(ctx context.Context, key string) (bool, error) {
   225  	path := filepath.Join(bs.RootDir, key) + bsExt
   226  	_, err := os.Stat(path)
   227  
   228  	if os.IsNotExist(err) {
   229  		return false, nil
   230  	}
   231  
   232  	return err == nil, err
   233  }