filestorage.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package storage
  2. import (
  3. "fmt"
  4. gofs "io/fs"
  5. "os"
  6. gopath "path"
  7. "path/filepath"
  8. "github.com/opencost/opencost/core/pkg/log"
  9. "github.com/opencost/opencost/core/pkg/util/fileutil"
  10. "github.com/pkg/errors"
  11. )
  12. // FileStorage leverages the file system to write data to disk.
  13. type FileStorage struct {
  14. baseDir string
  15. }
  16. // NewFileStorage returns a new storage API which leverages the file system.
  17. func NewFileStorage(baseDir string) Storage {
  18. return &FileStorage{baseDir}
  19. }
  20. // StorageType returns a string identifier for the type of storage used by the implementation.
  21. func (fs *FileStorage) StorageType() StorageType {
  22. return StorageTypeFile
  23. }
  24. // FullPath returns the storage working path combined with the path provided
  25. func (fs *FileStorage) FullPath(path string) string {
  26. return gopath.Join(fs.baseDir, path)
  27. }
  28. // Stat returns the StorageStats for the specific path.
  29. func (fs *FileStorage) Stat(path string) (*StorageInfo, error) {
  30. f := gopath.Join(fs.baseDir, path)
  31. st, err := os.Stat(f)
  32. if err != nil {
  33. if os.IsNotExist(err) {
  34. return nil, DoesNotExistError
  35. }
  36. return nil, errors.Wrap(err, "Failed to stat file")
  37. }
  38. return FileToStorageInfo(st), nil
  39. }
  40. // List uses the relative path of the storage combined with the provided path to return
  41. // storage information for the files.
  42. func (fs *FileStorage) List(path string) ([]*StorageInfo, error) {
  43. p := gopath.Join(fs.baseDir, path)
  44. // Read files in the backup path
  45. entries, err := os.ReadDir(p)
  46. if err != nil {
  47. return nil, err
  48. }
  49. files := make([]gofs.FileInfo, 0, len(entries))
  50. for _, entry := range entries {
  51. info, err := entry.Info()
  52. if err != nil {
  53. return nil, err
  54. }
  55. files = append(files, info)
  56. }
  57. return FilesToStorageInfo(files), nil
  58. }
  59. func (fs *FileStorage) ListDirectories(path string) ([]*StorageInfo, error) {
  60. p := gopath.Join(fs.baseDir, path)
  61. // Read files in the backup path
  62. entries, err := os.ReadDir(p)
  63. if err != nil {
  64. return nil, err
  65. }
  66. files := make([]gofs.FileInfo, 0, len(entries))
  67. for _, entry := range entries {
  68. info, err := entry.Info()
  69. if err != nil {
  70. return nil, err
  71. }
  72. files = append(files, info)
  73. }
  74. return DirFilesToStorageInfo(files, path, fs.baseDir), nil
  75. }
  76. // Read uses the relative path of the storage combined with the provided path to
  77. // read the contents.
  78. //
  79. // It takes advantage of flock() based locking to improve safety.
  80. func (fs *FileStorage) Read(path string) ([]byte, error) {
  81. f := gopath.Join(fs.baseDir, path)
  82. b, err := fileutil.ReadLocked(f)
  83. if err != nil {
  84. if errors.Is(err, os.ErrNotExist) {
  85. return nil, DoesNotExistError
  86. }
  87. return nil, fmt.Errorf("reading %s: %w", f, err)
  88. }
  89. return b, nil
  90. }
  91. // Write uses the relative path of the storage combined with the provided path
  92. // to write a new file or overwrite an existing file.
  93. //
  94. // It takes advantage of flock() based locking to improve safety.
  95. func (fs *FileStorage) Write(path string, data []byte) error {
  96. f, err := fs.prepare(path)
  97. if err != nil {
  98. return errors.Wrap(err, "Failed to prepare path")
  99. }
  100. if _, err := fileutil.WriteLocked(f, data); err != nil {
  101. return fmt.Errorf("writing %s: %w", f, err)
  102. }
  103. return nil
  104. }
  105. // Remove uses the relative path of the storage combined with the provided path to
  106. // remove a file from storage permanently.
  107. func (fs *FileStorage) Remove(path string) error {
  108. f := gopath.Join(fs.baseDir, path)
  109. err := os.Remove(f)
  110. if err != nil {
  111. if os.IsNotExist(err) {
  112. return DoesNotExistError
  113. }
  114. return errors.Wrap(err, "Failed to remove file")
  115. }
  116. return nil
  117. }
  118. // Exists uses the relative path of the storage combined with the provided path to
  119. // determine if the file exists.
  120. func (fs *FileStorage) Exists(path string) (bool, error) {
  121. f := gopath.Join(fs.baseDir, path)
  122. return fileutil.FileExists(f)
  123. }
  124. // prepare checks to see if the directory being written to should be created before writing
  125. // the file, and then returns the correct full path.
  126. func (fs *FileStorage) prepare(path string) (string, error) {
  127. f := gopath.Join(fs.baseDir, path)
  128. dir := filepath.Dir(f)
  129. if _, e := os.Stat(dir); e != nil && os.IsNotExist(e) {
  130. err := os.MkdirAll(dir, os.ModePerm)
  131. if err != nil {
  132. return "", err
  133. }
  134. }
  135. return f, nil
  136. }
  137. // FilesToStorageInfo maps a []fs.FileInfo to []*storage.StorageInfo
  138. func FilesToStorageInfo(fileInfo []gofs.FileInfo) []*StorageInfo {
  139. var stats []*StorageInfo
  140. for _, info := range fileInfo {
  141. stats = append(stats, FileToStorageInfo(info))
  142. }
  143. return stats
  144. }
  145. // FileToStorageInfo maps a fs.FileInfo to *storage.StorageInfo
  146. func FileToStorageInfo(fileInfo gofs.FileInfo) *StorageInfo {
  147. return &StorageInfo{
  148. Name: fileInfo.Name(),
  149. Size: fileInfo.Size(),
  150. ModTime: fileInfo.ModTime(),
  151. }
  152. }
  153. // DirFilesToStorageInfo maps a []fs.FileInfo to []*storage.StorageInfo
  154. // but only returning StorageInfo for directories
  155. func DirFilesToStorageInfo(fileInfo []gofs.FileInfo, relativePath string, basePath string) []*StorageInfo {
  156. var stats []*StorageInfo
  157. for _, info := range fileInfo {
  158. if info.IsDir() {
  159. stats = append(stats, &StorageInfo{
  160. Name: filepath.Join(relativePath, info.Name()),
  161. Size: info.Size(),
  162. ModTime: info.ModTime(),
  163. })
  164. } else if info.Mode()&os.ModeSymlink == os.ModeSymlink {
  165. targetPath, err := os.Readlink(filepath.Join(basePath, relativePath, info.Name()))
  166. if err != nil {
  167. log.Warnf("Converting files to storage info failed on supposed symlink that failed to be Readlink-ed (%s): %s", filepath.Join(basePath, relativePath, info.Name()), err)
  168. continue
  169. }
  170. if targetInfo, err := os.Stat(targetPath); err == nil {
  171. if targetInfo.IsDir() {
  172. stats = append(stats, &StorageInfo{
  173. // The Name added here is the path not readlink-ed
  174. Name: filepath.Join(relativePath, info.Name()),
  175. Size: info.Size(),
  176. ModTime: info.ModTime(),
  177. })
  178. }
  179. } else if errors.Is(err, os.ErrNotExist) {
  180. continue
  181. } else {
  182. log.Warnf("Converting files to storage info failed to stat target (%s) of symlink (%s): %s", targetPath, info.Name(), err)
  183. continue
  184. }
  185. }
  186. }
  187. return stats
  188. }