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.

local.go 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package storage
  4. import (
  5. "context"
  6. "fmt"
  7. "io"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/util"
  14. )
  15. var _ ObjectStorage = &LocalStorage{}
  16. // LocalStorage represents a local files storage
  17. type LocalStorage struct {
  18. ctx context.Context
  19. dir string
  20. tmpdir string
  21. }
  22. // NewLocalStorage returns a local files
  23. func NewLocalStorage(ctx context.Context, config *setting.Storage) (ObjectStorage, error) {
  24. if !filepath.IsAbs(config.Path) {
  25. return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path)
  26. }
  27. log.Info("Creating new Local Storage at %s", config.Path)
  28. if err := os.MkdirAll(config.Path, os.ModePerm); err != nil {
  29. return nil, err
  30. }
  31. if config.TemporaryPath == "" {
  32. config.TemporaryPath = filepath.Join(config.Path, "tmp")
  33. }
  34. if !filepath.IsAbs(config.TemporaryPath) {
  35. return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath)
  36. }
  37. return &LocalStorage{
  38. ctx: ctx,
  39. dir: config.Path,
  40. tmpdir: config.TemporaryPath,
  41. }, nil
  42. }
  43. func (l *LocalStorage) buildLocalPath(p string) string {
  44. return util.FilePathJoinAbs(l.dir, p)
  45. }
  46. // Open a file
  47. func (l *LocalStorage) Open(path string) (Object, error) {
  48. return os.Open(l.buildLocalPath(path))
  49. }
  50. // Save a file
  51. func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
  52. p := l.buildLocalPath(path)
  53. if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
  54. return 0, err
  55. }
  56. // Create a temporary file to save to
  57. if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil {
  58. return 0, err
  59. }
  60. tmp, err := os.CreateTemp(l.tmpdir, "upload-*")
  61. if err != nil {
  62. return 0, err
  63. }
  64. tmpRemoved := false
  65. defer func() {
  66. if !tmpRemoved {
  67. _ = util.Remove(tmp.Name())
  68. }
  69. }()
  70. n, err := io.Copy(tmp, r)
  71. if err != nil {
  72. return 0, err
  73. }
  74. if err := tmp.Close(); err != nil {
  75. return 0, err
  76. }
  77. if err := util.Rename(tmp.Name(), p); err != nil {
  78. return 0, err
  79. }
  80. // Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
  81. // but we don't want to make these files executable - so ensure that we mask out the executable bits
  82. if err := util.ApplyUmask(p, os.ModePerm&0o666); err != nil {
  83. return 0, err
  84. }
  85. tmpRemoved = true
  86. return n, nil
  87. }
  88. // Stat returns the info of the file
  89. func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
  90. return os.Stat(l.buildLocalPath(path))
  91. }
  92. // Delete delete a file
  93. func (l *LocalStorage) Delete(path string) error {
  94. return util.Remove(l.buildLocalPath(path))
  95. }
  96. // URL gets the redirect URL to a file
  97. func (l *LocalStorage) URL(path, name string) (*url.URL, error) {
  98. return nil, ErrURLNotSupported
  99. }
  100. // IterateObjects iterates across the objects in the local storage
  101. func (l *LocalStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error {
  102. dir := l.buildLocalPath(dirName)
  103. return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
  104. if err != nil {
  105. return err
  106. }
  107. select {
  108. case <-l.ctx.Done():
  109. return l.ctx.Err()
  110. default:
  111. }
  112. if path == l.dir {
  113. return nil
  114. }
  115. if d.IsDir() {
  116. return nil
  117. }
  118. relPath, err := filepath.Rel(l.dir, path)
  119. if err != nil {
  120. return err
  121. }
  122. obj, err := os.Open(path)
  123. if err != nil {
  124. return err
  125. }
  126. defer obj.Close()
  127. return fn(relPath, obj)
  128. })
  129. }
  130. func init() {
  131. RegisterStorageType(setting.LocalStorageType, NewLocalStorage)
  132. }