You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dbfile.go 7.7KB

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