fs.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Package keyvalue contains a key-value based FS for easy, custom FS implementations.
  2. package keyvalue
  3. import (
  4. "context"
  5. "errors"
  6. "path"
  7. "time"
  8. "github.com/hack-pad/hackpadfs"
  9. )
  10. const chmodBits = hackpadfs.ModePerm | hackpadfs.ModeSetuid | hackpadfs.ModeSetgid | hackpadfs.ModeSticky // Only a subset of bits are allowed to be changed. Documented under os.Chmod()
  11. // FS wraps a Store as a file system.
  12. type FS struct {
  13. store *transactionOnly
  14. }
  15. // NewFS returns a new FS wrapping the given 'store'.
  16. func NewFS(store Store) (*FS, error) {
  17. fs := &FS{
  18. store: newFSTransactioner(store),
  19. }
  20. err := fs.Mkdir(".", 0o666)
  21. return fs, ignoreErrExist(err)
  22. }
  23. func ignoreErrExist(err error) error {
  24. if errors.Is(err, hackpadfs.ErrExist) {
  25. return nil
  26. }
  27. return err
  28. }
  29. func (fs *FS) wrapperErr(op string, path string, err error) error {
  30. if err == nil {
  31. return nil
  32. }
  33. return &hackpadfs.PathError{Op: op, Path: path, Err: err}
  34. }
  35. // Mkdir implements hackpadfs.MkdirFS
  36. func (fs *FS) Mkdir(name string, perm hackpadfs.FileMode) error {
  37. file := fs.newDir(name, perm)
  38. _, err := fs.Stat(name)
  39. switch {
  40. case err == nil:
  41. return fs.wrapperErr("mkdir", name, hackpadfs.ErrExist)
  42. case !errors.Is(err, hackpadfs.ErrNotExist):
  43. return err
  44. }
  45. if name != "." {
  46. _, err := fs.Stat(path.Dir(name))
  47. if err != nil {
  48. return fs.wrapperErr("mkdir", name, err)
  49. }
  50. }
  51. return fs.wrapperErr("mkdir", name, file.save())
  52. }
  53. func (fs *FS) newDir(name string, perm hackpadfs.FileMode) *file {
  54. return fs.newFile(name, 0, hackpadfs.ModeDir|(perm&hackpadfs.ModePerm))
  55. }
  56. // MkdirAll implements hackpadfs.MkdirAllFS
  57. func (fs *FS) MkdirAll(path string, perm hackpadfs.FileMode) error {
  58. missingDirs, err := fs.findMissingDirs(path)
  59. if err != nil {
  60. return err
  61. }
  62. for i := len(missingDirs) - 1; i >= 0; i-- { // missingDirs are in reverse order
  63. name := missingDirs[i]
  64. file := fs.newDir(name, perm)
  65. err := file.save()
  66. err = fs.wrapperErr("mkdirall", name, err)
  67. err = ignoreErrExist(err)
  68. if err != nil {
  69. return err
  70. }
  71. }
  72. return nil
  73. }
  74. func statAll(store *transactionOnly, paths []string) ([]hackpadfs.FileInfo, []error) {
  75. infos := make([]hackpadfs.FileInfo, len(paths))
  76. errs := make([]error, len(paths))
  77. results, err := getFileRecords(store, paths)
  78. if err != nil {
  79. return nil, []error{err}
  80. }
  81. for i := range paths {
  82. path := paths[i]
  83. result := results[i]
  84. infos[i], errs[i] = fileInfo{Record: result.Record, Path: path}, result.Err
  85. }
  86. return infos, errs
  87. }
  88. func (fs *FS) getFiles(paths ...string) ([]*file, []error) {
  89. files := make([]*file, len(paths))
  90. errs := make([]error, len(paths))
  91. for _, path := range paths {
  92. if !hackpadfs.ValidPath(path) {
  93. errs[0] = hackpadfs.ErrInvalid
  94. return files, errs
  95. }
  96. }
  97. results, err := getFileRecords(fs.store, paths)
  98. if err != nil {
  99. errs[0] = err
  100. return files, errs
  101. }
  102. for i := range paths {
  103. result, err := results[i].Record, results[i].Err
  104. files[i], errs[i] = &file{
  105. fileData: &fileData{
  106. runOnceFileRecord: runOnceFileRecord{record: result},
  107. path: paths[i],
  108. fs: fs,
  109. },
  110. }, err
  111. }
  112. return files, errs
  113. }
  114. func getFileRecords(store *transactionOnly, paths []string) ([]OpResult, error) {
  115. txn, err := store.Transaction(TransactionOptions{
  116. Mode: TransactionReadOnly,
  117. })
  118. if err != nil {
  119. return nil, err
  120. }
  121. for _, path := range paths {
  122. txn.Get(path)
  123. }
  124. return txn.Commit(context.Background())
  125. }
  126. // findMissingDirs returns all paths that must be created, in reverse order
  127. func (fs *FS) findMissingDirs(name string) ([]string, error) {
  128. if !hackpadfs.ValidPath(name) {
  129. return nil, hackpadfs.ErrInvalid
  130. }
  131. const fsRootPath = "."
  132. var paths []string
  133. for currentPath := name; currentPath != fsRootPath; currentPath = path.Dir(currentPath) {
  134. paths = append(paths, currentPath)
  135. }
  136. paths = append(paths, fsRootPath)
  137. infos, errs := statAll(fs.store, paths)
  138. var missingDirs []string
  139. for i := range paths {
  140. missing, err := isMissingDir(paths[i], infos[i], errs[i])
  141. if err != nil {
  142. return nil, err
  143. }
  144. if missing {
  145. missingDirs = append(missingDirs, paths[i])
  146. } else {
  147. return missingDirs, nil
  148. }
  149. }
  150. return missingDirs, nil
  151. }
  152. func isMissingDir(path string, info hackpadfs.FileInfo, err error) (missing bool, returnedErr error) {
  153. switch {
  154. case errors.Is(err, hackpadfs.ErrNotExist):
  155. return true, nil
  156. case err != nil:
  157. return false, err
  158. case info.IsDir():
  159. // found a directory in the chain, return early
  160. return false, nil
  161. case !info.IsDir():
  162. // a file is found where we want a directory, fail with ENOTDIR
  163. return true, &hackpadfs.PathError{Op: "mkdir", Path: path, Err: hackpadfs.ErrNotDir}
  164. default:
  165. return false, nil
  166. }
  167. }
  168. // Open implements hackpadfs.FS
  169. func (fs *FS) Open(name string) (hackpadfs.File, error) {
  170. return fs.OpenFile(name, hackpadfs.FlagReadOnly, 0)
  171. }
  172. // OpenFile implements hackpadfs.OpenFileFS
  173. func (fs *FS) OpenFile(name string, flag int, perm hackpadfs.FileMode) (afFile hackpadfs.File, retErr error) {
  174. paths := []string{name}
  175. if flag&hackpadfs.FlagCreate != 0 {
  176. paths = append(paths, path.Dir(name))
  177. }
  178. files, errs := fs.getFiles(paths...)
  179. storeFile, err := files[0], errs[0]
  180. switch {
  181. case err == nil:
  182. if storeFile.info().IsDir() && flag&(hackpadfs.FlagCreate|hackpadfs.FlagWriteOnly) != 0 {
  183. // write-only or create on a directory isn't allowed on hackpadfs.OpenFile
  184. return nil, &hackpadfs.PathError{Op: "open", Path: name, Err: hackpadfs.ErrIsDir}
  185. }
  186. storeFile.flag = flag
  187. case errors.Is(err, hackpadfs.ErrNotExist) && flag&hackpadfs.FlagCreate != 0:
  188. // require parent directory
  189. err := errs[1]
  190. if err != nil {
  191. return nil, fs.wrapperErr("open", name, err)
  192. }
  193. storeFile = fs.newFile(name, flag, perm&hackpadfs.ModePerm)
  194. if err := storeFile.save(); err != nil {
  195. return nil, fs.wrapperErr("open", name, err)
  196. }
  197. default:
  198. return nil, fs.wrapperErr("open", name, err)
  199. }
  200. var file hackpadfs.File = storeFile
  201. switch {
  202. case flag&hackpadfs.FlagWriteOnly != 0:
  203. file = &writeOnlyFile{storeFile}
  204. case flag&hackpadfs.FlagReadWrite != 0:
  205. default:
  206. // hackpadfs.FlagReadOnly = 0
  207. file = &readOnlyFile{storeFile}
  208. }
  209. if flag&hackpadfs.FlagTruncate != 0 {
  210. return file, fs.wrapperErr("open", name, hackpadfs.TruncateFile(file, 0))
  211. }
  212. return file, nil
  213. }
  214. // Remove implements hackpadfs.RemoveFS
  215. func (fs *FS) Remove(name string) error {
  216. file, err := fs.getFile(name)
  217. if err != nil {
  218. return fs.wrapperErr("remove", name, err)
  219. }
  220. if file.Mode().IsDir() {
  221. dirNames, err := file.ReadDirNames()
  222. if err != nil {
  223. return err
  224. }
  225. if len(dirNames) > 0 {
  226. return &hackpadfs.PathError{Op: "remove", Path: name, Err: hackpadfs.ErrNotEmpty}
  227. }
  228. }
  229. return fs.setFile(name, nil)
  230. }
  231. // Rename implements hackpadfs.RenameFS
  232. func (fs *FS) Rename(oldname, newname string) error {
  233. oldFile, err := fs.getFile(oldname)
  234. if err != nil {
  235. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: hackpadfs.ErrNotExist}
  236. }
  237. oldInfo, err := oldFile.Stat()
  238. if err != nil {
  239. return err
  240. }
  241. if !oldInfo.IsDir() {
  242. if oldname == newname {
  243. return nil
  244. }
  245. contents, err := oldFile.fileData.Data()
  246. if err != nil {
  247. return err
  248. }
  249. txn, err := fs.store.Transaction(TransactionOptions{Mode: TransactionReadWrite})
  250. if err == nil {
  251. err = fs.setFileTxn(txn, newname, oldFile.fileData, contents)
  252. }
  253. if err == nil {
  254. err = fs.setFileTxn(txn, oldname, nil, nil)
  255. }
  256. if err != nil {
  257. _ = txn.Abort()
  258. } else {
  259. _, err = txn.Commit(context.Background())
  260. }
  261. return err
  262. }
  263. _, err = fs.getFile(newname)
  264. if !errors.Is(err, hackpadfs.ErrNotExist) {
  265. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: hackpadfs.ErrExist}
  266. }
  267. files, err := oldFile.ReadDirNames()
  268. if err != nil {
  269. return err
  270. }
  271. err = fs.setFile(newname, oldFile.fileData)
  272. if err != nil {
  273. return err
  274. }
  275. for _, name := range files {
  276. err := fs.Rename(path.Join(oldname, name), path.Join(newname, name))
  277. if err != nil {
  278. // TODO don't leave destination in corrupted state (missing file records for dir names)
  279. return err
  280. }
  281. }
  282. return fs.setFile(oldname, nil)
  283. }
  284. // Stat implements hackpadfs.StatFS
  285. func (fs *FS) Stat(name string) (hackpadfs.FileInfo, error) {
  286. file, err := fs.getFile(name)
  287. if err != nil {
  288. return nil, fs.wrapperErr("stat", name, err)
  289. }
  290. return file.info(), nil
  291. }
  292. // Chmod implements hackpadfs.ChmodFS
  293. func (fs *FS) Chmod(name string, mode hackpadfs.FileMode) error {
  294. file, err := fs.getFile(name)
  295. if err != nil {
  296. return fs.wrapperErr("chmod", name, err)
  297. }
  298. newMode := (file.Mode() & ^chmodBits) | (mode & chmodBits)
  299. file.modeOverride = &newMode
  300. return file.save()
  301. }
  302. // Chtimes implements hackpadfs.ChtimesFS
  303. func (fs *FS) Chtimes(name string, _ time.Time, mtime time.Time) error {
  304. file, err := fs.getFile(name)
  305. if err != nil {
  306. return fs.wrapperErr("chtimes", name, err)
  307. }
  308. file.modTimeOverride = mtime
  309. return file.save()
  310. }