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  }