fs.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. // Package hackpadfs defines many essential file and file system interfaces as well as helpers for use with the standard library's 'io/fs' package.
  2. package hackpadfs
  3. import (
  4. "errors"
  5. gofs "io/fs"
  6. gopath "path"
  7. "time"
  8. )
  9. // FS provides access to a file system and its files.
  10. // It is the minimum functionality required for a file system, and mirrors Go's io/fs.FS interface.
  11. type FS = gofs.FS
  12. // SubFS is an FS that can return a subset of the current FS.
  13. // The same effect as `chroot` in a program.
  14. type SubFS interface {
  15. FS
  16. Sub(dir string) (FS, error)
  17. }
  18. // OpenFileFS is an FS that can open files with the given flags and can create with the given permission.
  19. // Should matche the behavior of os.OpenFile().
  20. type OpenFileFS interface {
  21. FS
  22. OpenFile(name string, flag int, perm FileMode) (File, error)
  23. }
  24. // CreateFS is an FS that can create files. Should match the behavior of os.Create().
  25. type CreateFS interface {
  26. FS
  27. Create(name string) (File, error)
  28. }
  29. // MkdirFS is an FS that can make directories. Should match the behavior of os.Mkdir().
  30. type MkdirFS interface {
  31. FS
  32. Mkdir(name string, perm FileMode) error
  33. }
  34. // MkdirAllFS is an FS that can make all missing directories in a given path. Should match the behavior of os.MkdirAll().
  35. type MkdirAllFS interface {
  36. FS
  37. MkdirAll(path string, perm FileMode) error
  38. }
  39. // RemoveFS is an FS that can remove files or empty directories. Should match the behavior of os.Remove().
  40. type RemoveFS interface {
  41. FS
  42. Remove(name string) error
  43. }
  44. // RemoveAllFS is an FS that can remove files or directories recursively. Should match the behavior of os.RemoveAll().
  45. type RemoveAllFS interface {
  46. FS
  47. RemoveAll(name string) error
  48. }
  49. // RenameFS is an FS that can move files or directories. Should match the behavior of os.Rename().
  50. type RenameFS interface {
  51. FS
  52. Rename(oldname, newname string) error
  53. }
  54. // StatFS is an FS that can stat files or directories. Should match the behavior of os.Stat().
  55. type StatFS interface {
  56. FS
  57. Stat(name string) (FileInfo, error)
  58. }
  59. // LstatFS is an FS that can lstat files. Same as Stat, but returns file info of symlinks instead of their target. Should match the behavior of os.Lstat().
  60. type LstatFS interface {
  61. FS
  62. Lstat(name string) (FileInfo, error)
  63. }
  64. // ChmodFS is an FS that can change file or directory permissions. Should match the behavior of os.Chmod().
  65. type ChmodFS interface {
  66. FS
  67. Chmod(name string, mode FileMode) error
  68. }
  69. // ChownFS is an FS that can change file or directory ownership. Should match the behavior of os.Chown().
  70. type ChownFS interface {
  71. FS
  72. Chown(name string, uid, gid int) error
  73. }
  74. // ChtimesFS is an FS that can change a file's access and modified timestamps. Should match the behavior of os.Chtimes().
  75. type ChtimesFS interface {
  76. FS
  77. Chtimes(name string, atime time.Time, mtime time.Time) error
  78. }
  79. // ReadDirFS is an FS that can read a directory and return its DirEntry's. Should match the behavior of os.ReadDir().
  80. type ReadDirFS interface {
  81. FS
  82. ReadDir(name string) ([]DirEntry, error)
  83. }
  84. // ReadFileFS is an FS that can read an entire file in one pass. Should match the behavior of os.ReadFile().
  85. type ReadFileFS interface {
  86. FS
  87. ReadFile(name string) ([]byte, error)
  88. }
  89. // WriteFileFS is an FS that can write an entire file in one pass. Should match the behavior of os.WriteFile().
  90. type WriteFileFS interface {
  91. FS
  92. WriteFile(name string, data []byte, perm FileMode) error
  93. }
  94. // SymlinkFS is an FS that can create symlinks. Should match the behavior of os.Symlink().
  95. type SymlinkFS interface {
  96. FS
  97. Symlink(oldname, newname string) error
  98. }
  99. // MountFS is an FS that meshes one or more FS's together.
  100. // Returns the FS for a file located at 'name' and its 'subPath' inside that FS.
  101. type MountFS interface {
  102. FS
  103. Mount(name string) (mountFS FS, subPath string)
  104. }
  105. // ValidPath returns true if 'path' is a valid FS path. See io/fs.ValidPath() for details on FS-safe paths.
  106. func ValidPath(path string) bool {
  107. return gofs.ValidPath(path)
  108. }
  109. // WalkDirFunc is the type of function called in WalkDir().
  110. type WalkDirFunc = gofs.WalkDirFunc
  111. // WalkDir recursively scans 'fs' starting at path 'root', calling 'fn' every time a new file or directory is visited.
  112. func WalkDir(fs FS, root string, fn WalkDirFunc) error {
  113. return gofs.WalkDir(fs, root, fn)
  114. }
  115. // Sub attempts to call an optimized fs.Sub() if available. Falls back to a small MountFS implementation.
  116. func Sub(fs FS, dir string) (FS, error) {
  117. if fs, ok := fs.(SubFS); ok {
  118. return fs.Sub(dir)
  119. }
  120. if fs, ok := fs.(MountFS); ok {
  121. mountFS, subPath := fs.Mount(dir)
  122. fs, err := Sub(mountFS, subPath)
  123. return fs, stripErrPathPrefix(err, dir, subPath)
  124. }
  125. return newSubFS(fs, dir)
  126. }
  127. // OpenFile attempts to call fs.Open() or fs.OpenFile() if available. Fails with a not implemented error otherwise.
  128. func OpenFile(fs FS, name string, flag int, perm FileMode) (File, error) {
  129. if flag == FlagReadOnly {
  130. return fs.Open(name)
  131. }
  132. if fs, ok := fs.(OpenFileFS); ok {
  133. return fs.OpenFile(name, flag, perm)
  134. }
  135. if fs, ok := fs.(MountFS); ok {
  136. mountFS, subPath := fs.Mount(name)
  137. file, err := OpenFile(mountFS, subPath, flag, perm)
  138. return file, stripErrPathPrefix(err, name, subPath)
  139. }
  140. return nil, &PathError{Op: "open", Path: name, Err: ErrNotImplemented}
  141. }
  142. // Create attempts to call an optimized fs.Create() if available, falls back to OpenFile() with create flags.
  143. func Create(fs FS, name string) (File, error) {
  144. if fs, ok := fs.(CreateFS); ok {
  145. return fs.Create(name)
  146. }
  147. return OpenFile(fs, name, FlagReadWrite|FlagCreate|FlagTruncate, 0o666)
  148. }
  149. // Mkdir creates a directory. Fails with a not implemented error if it's not a MkdirFS.
  150. func Mkdir(fs FS, name string, perm FileMode) error {
  151. if fs, ok := fs.(MkdirFS); ok {
  152. return fs.Mkdir(name, perm)
  153. }
  154. if fs, ok := fs.(MountFS); ok {
  155. mountFS, subPath := fs.Mount(name)
  156. err := Mkdir(mountFS, subPath, perm)
  157. return stripErrPathPrefix(err, name, subPath)
  158. }
  159. return &PathError{Op: "mkdir", Path: name, Err: ErrNotImplemented}
  160. }
  161. // MkdirAll attempts to call an optimized fs.MkdirAll(), falls back to multiple fs.Mkdir() calls.
  162. func MkdirAll(fs FS, path string, perm FileMode) error {
  163. if fs, ok := fs.(MkdirAllFS); ok {
  164. return fs.MkdirAll(path, perm)
  165. }
  166. if fs, ok := fs.(MountFS); ok {
  167. mountFS, subPath := fs.Mount(path)
  168. err := MkdirAll(mountFS, subPath, perm)
  169. return stripErrPathPrefix(err, path, subPath)
  170. }
  171. if !ValidPath(path) {
  172. return &PathError{Op: "mkdirall", Path: path, Err: ErrInvalid}
  173. }
  174. for i := 0; i < len(path); i++ {
  175. if path[i] == '/' {
  176. err := Mkdir(fs, path[:i], perm)
  177. if err != nil {
  178. pathErr, ok := err.(*PathError)
  179. if !ok || !errors.Is(err, ErrExist) {
  180. return err
  181. }
  182. info, statErr := Stat(fs, pathErr.Path)
  183. if statErr != nil {
  184. return err
  185. }
  186. if !info.IsDir() {
  187. return &PathError{Op: "mkdir", Path: pathErr.Path, Err: ErrNotDir}
  188. }
  189. }
  190. }
  191. }
  192. return Mkdir(fs, path, perm)
  193. }
  194. // Remove removes a file with fs.Remove(). Fails with a not implemented error if it's not a RemoveFS.
  195. func Remove(fs FS, name string) error {
  196. if fs, ok := fs.(RemoveFS); ok {
  197. return fs.Remove(name)
  198. }
  199. if fs, ok := fs.(MountFS); ok {
  200. mountFS, subPath := fs.Mount(name)
  201. err := Remove(mountFS, subPath)
  202. return stripErrPathPrefix(err, name, subPath)
  203. }
  204. return &PathError{Op: "remove", Path: name, Err: ErrNotImplemented}
  205. }
  206. // RemoveAll attempts to call an optimized fs.RemoveAll(), falls back to removing files and directories recursively.
  207. func RemoveAll(fs FS, path string) error {
  208. if fs, ok := fs.(RemoveAllFS); ok {
  209. return fs.RemoveAll(path)
  210. }
  211. if fs, ok := fs.(MountFS); ok {
  212. mountFS, subPath := fs.Mount(path)
  213. err := RemoveAll(mountFS, subPath)
  214. return stripErrPathPrefix(err, path, subPath)
  215. }
  216. if !ValidPath(path) {
  217. return &PathError{Op: "removeall", Path: path, Err: ErrInvalid}
  218. }
  219. return removeAll(fs, path)
  220. }
  221. func removeAll(fs FS, path string) error {
  222. info, err := Stat(fs, path)
  223. if err != nil {
  224. if errors.Is(err, ErrNotExist) {
  225. err = nil
  226. }
  227. return err
  228. }
  229. if !info.IsDir() {
  230. err := Remove(fs, path)
  231. if errors.Is(err, ErrNotExist) {
  232. err = nil
  233. }
  234. return err
  235. }
  236. dir, err := ReadDir(fs, path)
  237. if err != nil {
  238. return &PathError{Op: "removeall", Path: path, Err: err}
  239. }
  240. for _, dirEntry := range dir {
  241. err := removeAll(fs, gopath.Join(path, dirEntry.Name()))
  242. if err != nil {
  243. return &PathError{Op: "removeall", Path: path, Err: err}
  244. }
  245. }
  246. if err := Remove(fs, path); err == nil || errors.Is(err, ErrNotExist) {
  247. return nil
  248. }
  249. return nil
  250. }
  251. // Rename moves files with fs.Rename(). Fails with a not implemented error if it's not a RenameFS.
  252. func Rename(fs FS, oldName, newName string) error {
  253. if fs, ok := fs.(RenameFS); ok {
  254. return fs.Rename(oldName, newName)
  255. }
  256. return &LinkError{Op: "rename", Old: oldName, New: newName, Err: ErrNotImplemented}
  257. }
  258. // Stat attempts to call an optimized fs.Stat(), falls back to fs.Open() and file.Stat().
  259. func Stat(fs FS, name string) (FileInfo, error) {
  260. if fs, ok := fs.(StatFS); ok {
  261. return fs.Stat(name)
  262. }
  263. if fs, ok := fs.(MountFS); ok {
  264. mountFS, subPath := fs.Mount(name)
  265. info, err := Stat(mountFS, subPath)
  266. return info, stripErrPathPrefix(err, name, subPath)
  267. }
  268. file, err := fs.Open(name)
  269. if err != nil {
  270. return nil, err
  271. }
  272. defer func() { _ = file.Close() }()
  273. return file.Stat()
  274. }
  275. // Lstat stats files and does not follow symlinks. Fails with a not implemented error if it's not a LstatFS.
  276. func Lstat(fs FS, name string) (FileInfo, error) {
  277. if fs, ok := fs.(LstatFS); ok {
  278. return fs.Lstat(name)
  279. }
  280. if fs, ok := fs.(MountFS); ok {
  281. mountFS, subPath := fs.Mount(name)
  282. info, err := Lstat(mountFS, subPath)
  283. return info, stripErrPathPrefix(err, name, subPath)
  284. }
  285. return nil, &PathError{Op: "lstat", Path: name, Err: ErrNotImplemented}
  286. }
  287. // LstatOrStat attempts to call an optimized fs.LstatOrStat(), fs.Lstat(), or fs.Stat() - whichever is supported first.
  288. func LstatOrStat(fs FS, name string) (FileInfo, error) {
  289. if fs, ok := fs.(MountFS); ok {
  290. mountFS, subPath := fs.Mount(name)
  291. info, err := LstatOrStat(mountFS, subPath)
  292. return info, stripErrPathPrefix(err, name, subPath)
  293. }
  294. info, err := Lstat(fs, name)
  295. if errors.Is(err, ErrNotImplemented) {
  296. info, err = Stat(fs, name)
  297. }
  298. return info, err
  299. }
  300. // Chmod attempts to call an optimized fs.Chmod(), falls back to opening the file and running file.Chmod().
  301. func Chmod(fs FS, name string, mode FileMode) error {
  302. if fs, ok := fs.(ChmodFS); ok {
  303. return fs.Chmod(name, mode)
  304. }
  305. if fs, ok := fs.(MountFS); ok {
  306. mountFS, subPath := fs.Mount(name)
  307. err := Chmod(mountFS, subPath, mode)
  308. return stripErrPathPrefix(err, name, subPath)
  309. }
  310. file, err := fs.Open(name)
  311. if err != nil {
  312. return &PathError{Op: "chmod", Path: name, Err: err}
  313. }
  314. defer func() { _ = file.Close() }()
  315. return ChmodFile(file, mode)
  316. }
  317. // Chown attempts to call an optimized fs.Chown(), falls back to opening the file and running file.Chown().
  318. func Chown(fs FS, name string, uid, gid int) error {
  319. if fs, ok := fs.(ChownFS); ok {
  320. return fs.Chown(name, uid, gid)
  321. }
  322. if fs, ok := fs.(MountFS); ok {
  323. mountFS, subPath := fs.Mount(name)
  324. err := Chown(mountFS, subPath, uid, gid)
  325. return stripErrPathPrefix(err, name, subPath)
  326. }
  327. file, err := fs.Open(name)
  328. if err != nil {
  329. return &PathError{Op: "chown", Path: name, Err: err}
  330. }
  331. defer func() { _ = file.Close() }()
  332. return ChownFile(file, uid, gid)
  333. }
  334. // Chtimes attempts to call an optimized fs.Chtimes(), falls back to opening the file and running file.Chtimes().
  335. func Chtimes(fs FS, name string, atime time.Time, mtime time.Time) error {
  336. if fs, ok := fs.(ChtimesFS); ok {
  337. return fs.Chtimes(name, atime, mtime)
  338. }
  339. if fs, ok := fs.(MountFS); ok {
  340. mountFS, subPath := fs.Mount(name)
  341. err := Chtimes(mountFS, subPath, atime, mtime)
  342. return stripErrPathPrefix(err, name, subPath)
  343. }
  344. file, err := fs.Open(name)
  345. if err != nil {
  346. return &PathError{Op: "chtimes", Path: name, Err: err}
  347. }
  348. defer func() { _ = file.Close() }()
  349. return ChtimesFile(file, atime, mtime)
  350. }
  351. // ReadDir attempts to call an optimized fs.ReadDir(), falls back to io/fs.ReadDir().
  352. func ReadDir(fs FS, name string) ([]DirEntry, error) {
  353. if fs, ok := fs.(ReadDirFS); ok {
  354. return fs.ReadDir(name)
  355. }
  356. if fs, ok := fs.(MountFS); ok {
  357. mountFS, subPath := fs.Mount(name)
  358. dirEntries, err := ReadDir(mountFS, subPath)
  359. return dirEntries, stripErrPathPrefix(err, name, subPath)
  360. }
  361. return gofs.ReadDir(fs, name)
  362. }
  363. // ReadFile attempts to call an optimized fs.ReadFile(), falls back to io/fs.ReadFile().
  364. func ReadFile(fs FS, name string) ([]byte, error) {
  365. if fs, ok := fs.(ReadFileFS); ok {
  366. return fs.ReadFile(name)
  367. }
  368. if fs, ok := fs.(MountFS); ok {
  369. mountFS, subPath := fs.Mount(name)
  370. b, err := ReadFile(mountFS, subPath)
  371. return b, stripErrPathPrefix(err, name, subPath)
  372. }
  373. return gofs.ReadFile(fs, name)
  374. }
  375. // WriteFullFile attempts to call an optimized fs.WriteFile(), falls back to fs.OpenFile() with file.Write().
  376. func WriteFullFile(fs FS, name string, data []byte, perm FileMode) error {
  377. if fs, ok := fs.(WriteFileFS); ok {
  378. return fs.WriteFile(name, data, perm)
  379. }
  380. if fs, ok := fs.(MountFS); ok {
  381. mountFS, subPath := fs.Mount(name)
  382. err := WriteFullFile(mountFS, subPath, data, perm)
  383. return stripErrPathPrefix(err, name, subPath)
  384. }
  385. f, err := OpenFile(fs, name, FlagWriteOnly|FlagCreate|FlagTruncate, perm)
  386. if err == nil {
  387. _, err = WriteFile(f, data)
  388. closeErr := f.Close()
  389. if err == nil {
  390. err = closeErr
  391. }
  392. }
  393. return err
  394. }
  395. // Symlink creates a symlink. Fails with a not implemented error if it's not a SymlinkFS.
  396. func Symlink(fs FS, oldname, newname string) error {
  397. if fs, ok := fs.(SymlinkFS); ok {
  398. return fs.Symlink(oldname, newname)
  399. }
  400. return &LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrNotImplemented}
  401. }