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 }