code.gitea.io/gitea@v1.21.7/models/dbfs/dbfile.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package dbfs 5 6 import ( 7 "context" 8 "errors" 9 "io" 10 "io/fs" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "time" 16 17 "code.gitea.io/gitea/models/db" 18 ) 19 20 var defaultFileBlockSize int64 = 32 * 1024 21 22 type File interface { 23 io.ReadWriteCloser 24 io.Seeker 25 fs.File 26 } 27 28 type file struct { 29 ctx context.Context 30 metaID int64 31 fullPath string 32 blockSize int64 33 34 allowRead bool 35 allowWrite bool 36 offset int64 37 } 38 39 var _ File = (*file)(nil) 40 41 func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err error) { 42 if offset >= fileMeta.FileSize { 43 return 0, io.EOF 44 } 45 46 blobPos := int(offset % f.blockSize) 47 blobOffset := offset - int64(blobPos) 48 blobRemaining := int(f.blockSize) - blobPos 49 needRead := len(p) 50 if needRead > blobRemaining { 51 needRead = blobRemaining 52 } 53 if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize { 54 needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos)) 55 } 56 if needRead <= 0 { 57 return 0, io.EOF 58 } 59 var fileData dbfsData 60 ok, err := db.GetEngine(f.ctx).Where("meta_id = ? AND blob_offset = ?", f.metaID, blobOffset).Get(&fileData) 61 if err != nil { 62 return 0, err 63 } 64 blobData := fileData.BlobData 65 if !ok { 66 blobData = nil 67 } 68 69 canCopy := len(blobData) - blobPos 70 if canCopy <= 0 { 71 canCopy = 0 72 } 73 realRead := needRead 74 if realRead > canCopy { 75 realRead = canCopy 76 } 77 if realRead > 0 { 78 copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead]) 79 } 80 for i := realRead; i < needRead; i++ { 81 p[i] = 0 82 } 83 return needRead, nil 84 } 85 86 func (f *file) Read(p []byte) (n int, err error) { 87 if f.metaID == 0 || !f.allowRead { 88 return 0, os.ErrInvalid 89 } 90 91 fileMeta, err := findFileMetaByID(f.ctx, f.metaID) 92 if err != nil { 93 return 0, err 94 } 95 n, err = f.readAt(fileMeta, f.offset, p) 96 f.offset += int64(n) 97 return n, err 98 } 99 100 func (f *file) Write(p []byte) (n int, err error) { 101 if f.metaID == 0 || !f.allowWrite { 102 return 0, os.ErrInvalid 103 } 104 105 fileMeta, err := findFileMetaByID(f.ctx, f.metaID) 106 if err != nil { 107 return 0, err 108 } 109 110 needUpdateSize := false 111 written := 0 112 for len(p) > 0 { 113 blobPos := int(f.offset % f.blockSize) 114 blobOffset := f.offset - int64(blobPos) 115 blobRemaining := int(f.blockSize) - blobPos 116 needWrite := len(p) 117 if needWrite > blobRemaining { 118 needWrite = blobRemaining 119 } 120 buf := make([]byte, f.blockSize) 121 readBytes, err := f.readAt(fileMeta, blobOffset, buf) 122 if err != nil && !errors.Is(err, io.EOF) { 123 return written, err 124 } 125 copy(buf[blobPos:blobPos+needWrite], p[:needWrite]) 126 if blobPos+needWrite > readBytes { 127 buf = buf[:blobPos+needWrite] 128 } else { 129 buf = buf[:readBytes] 130 } 131 132 fileData := dbfsData{ 133 MetaID: fileMeta.ID, 134 BlobOffset: blobOffset, 135 BlobData: buf, 136 } 137 if res, err := db.GetEngine(f.ctx).Exec("UPDATE dbfs_data SET revision=revision+1, blob_data=? WHERE meta_id=? AND blob_offset=?", buf, fileMeta.ID, blobOffset); err != nil { 138 return written, err 139 } else if updated, err := res.RowsAffected(); err != nil { 140 return written, err 141 } else if updated == 0 { 142 if _, err = db.GetEngine(f.ctx).Insert(&fileData); err != nil { 143 return written, err 144 } 145 } 146 written += needWrite 147 f.offset += int64(needWrite) 148 if f.offset > fileMeta.FileSize { 149 fileMeta.FileSize = f.offset 150 needUpdateSize = true 151 } 152 p = p[needWrite:] 153 } 154 155 fileMetaUpdate := dbfsMeta{ 156 ModifyTimestamp: timeToFileTimestamp(time.Now()), 157 } 158 if needUpdateSize { 159 fileMetaUpdate.FileSize = f.offset 160 } 161 if _, err := db.GetEngine(f.ctx).ID(fileMeta.ID).Update(fileMetaUpdate); err != nil { 162 return written, err 163 } 164 return written, nil 165 } 166 167 func (f *file) Seek(n int64, whence int) (int64, error) { 168 if f.metaID == 0 { 169 return 0, os.ErrInvalid 170 } 171 172 newOffset := f.offset 173 switch whence { 174 case io.SeekStart: 175 newOffset = n 176 case io.SeekCurrent: 177 newOffset += n 178 case io.SeekEnd: 179 size, err := f.size() 180 if err != nil { 181 return f.offset, err 182 } 183 newOffset = size + n 184 default: 185 return f.offset, os.ErrInvalid 186 } 187 if newOffset < 0 { 188 return f.offset, os.ErrInvalid 189 } 190 f.offset = newOffset 191 return newOffset, nil 192 } 193 194 func (f *file) Close() error { 195 return nil 196 } 197 198 func (f *file) Stat() (os.FileInfo, error) { 199 if f.metaID == 0 { 200 return nil, os.ErrInvalid 201 } 202 203 fileMeta, err := findFileMetaByID(f.ctx, f.metaID) 204 if err != nil { 205 return nil, err 206 } 207 return fileMeta, nil 208 } 209 210 func timeToFileTimestamp(t time.Time) int64 { 211 return t.UnixMicro() 212 } 213 214 func fileTimestampToTime(timestamp int64) time.Time { 215 return time.UnixMicro(timestamp) 216 } 217 218 func (f *file) loadMetaByPath() (*dbfsMeta, error) { 219 var fileMeta dbfsMeta 220 if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil { 221 return nil, err 222 } else if ok { 223 f.metaID = fileMeta.ID 224 f.blockSize = fileMeta.BlockSize 225 return &fileMeta, nil 226 } 227 return nil, nil 228 } 229 230 func (f *file) open(flag int) (err error) { 231 // see os.OpenFile for flag values 232 if flag&os.O_WRONLY != 0 { 233 f.allowWrite = true 234 } else if flag&os.O_RDWR != 0 { 235 f.allowRead = true 236 f.allowWrite = true 237 } else /* O_RDONLY */ { 238 f.allowRead = true 239 } 240 241 if f.allowWrite { 242 if flag&os.O_CREATE != 0 { 243 if flag&os.O_EXCL != 0 { 244 // file must not exist. 245 if f.metaID != 0 { 246 return os.ErrExist 247 } 248 } else { 249 // create a new file if none exists. 250 if f.metaID == 0 { 251 if err = f.createEmpty(); err != nil { 252 return err 253 } 254 } 255 } 256 } 257 if flag&os.O_TRUNC != 0 { 258 if err = f.truncate(); err != nil { 259 return err 260 } 261 } 262 if flag&os.O_APPEND != 0 { 263 if _, err = f.Seek(0, io.SeekEnd); err != nil { 264 return err 265 } 266 } 267 return nil 268 } 269 270 // read only mode 271 if f.metaID == 0 { 272 return os.ErrNotExist 273 } 274 return nil 275 } 276 277 func (f *file) createEmpty() error { 278 if f.metaID != 0 { 279 return os.ErrExist 280 } 281 now := time.Now() 282 _, err := db.GetEngine(f.ctx).Insert(&dbfsMeta{ 283 FullPath: f.fullPath, 284 BlockSize: f.blockSize, 285 CreateTimestamp: timeToFileTimestamp(now), 286 ModifyTimestamp: timeToFileTimestamp(now), 287 }) 288 if err != nil { 289 return err 290 } 291 if _, err = f.loadMetaByPath(); err != nil { 292 return err 293 } 294 return nil 295 } 296 297 func (f *file) truncate() error { 298 if f.metaID == 0 { 299 return os.ErrNotExist 300 } 301 return db.WithTx(f.ctx, func(ctx context.Context) error { 302 if _, err := db.GetEngine(ctx).Exec("UPDATE dbfs_meta SET file_size = 0 WHERE id = ?", f.metaID); err != nil { 303 return err 304 } 305 if _, err := db.GetEngine(ctx).Delete(&dbfsData{MetaID: f.metaID}); err != nil { 306 return err 307 } 308 return nil 309 }) 310 } 311 312 func (f *file) renameTo(newPath string) error { 313 if f.metaID == 0 { 314 return os.ErrNotExist 315 } 316 newPath = buildPath(newPath) 317 return db.WithTx(f.ctx, func(ctx context.Context) error { 318 if _, err := db.GetEngine(ctx).Exec("UPDATE dbfs_meta SET full_path = ? WHERE id = ?", newPath, f.metaID); err != nil { 319 return err 320 } 321 return nil 322 }) 323 } 324 325 func (f *file) delete() error { 326 if f.metaID == 0 { 327 return os.ErrNotExist 328 } 329 return db.WithTx(f.ctx, func(ctx context.Context) error { 330 if _, err := db.GetEngine(ctx).Delete(&dbfsMeta{ID: f.metaID}); err != nil { 331 return err 332 } 333 if _, err := db.GetEngine(ctx).Delete(&dbfsData{MetaID: f.metaID}); err != nil { 334 return err 335 } 336 return nil 337 }) 338 } 339 340 func (f *file) size() (int64, error) { 341 if f.metaID == 0 { 342 return 0, os.ErrNotExist 343 } 344 fileMeta, err := findFileMetaByID(f.ctx, f.metaID) 345 if err != nil { 346 return 0, err 347 } 348 return fileMeta.FileSize, nil 349 } 350 351 func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) { 352 var fileMeta dbfsMeta 353 if ok, err := db.GetEngine(ctx).Where("id = ?", metaID).Get(&fileMeta); err != nil { 354 return nil, err 355 } else if ok { 356 return &fileMeta, nil 357 } 358 return nil, nil 359 } 360 361 func buildPath(path string) string { 362 path = filepath.Clean(path) 363 path = strings.ReplaceAll(path, "\\", "/") 364 path = strings.TrimPrefix(path, "/") 365 return strconv.Itoa(strings.Count(path, "/")) + ":" + path 366 } 367 368 func newDbFile(ctx context.Context, path string) (*file, error) { 369 path = buildPath(path) 370 f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize} 371 if _, err := f.loadMetaByPath(); err != nil { 372 return nil, err 373 } 374 return f, nil 375 }