fs.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // Package mount contains an implementation of hackpadfs.MountFS.
  2. package mount
  3. import (
  4. "io"
  5. "path"
  6. "strings"
  7. "sync"
  8. "github.com/hack-pad/hackpadfs"
  9. )
  10. var (
  11. _ interface {
  12. hackpadfs.FS
  13. hackpadfs.MountFS
  14. hackpadfs.RenameFS
  15. } = &FS{}
  16. )
  17. // FS is mesh of several file systems mounted at different paths.
  18. // Mount a file system with AddMount().
  19. //
  20. // For ease of use, call the standard operations via hackpadfs.OpenFile(fs, ...), hackpadfs.Mkdir(fs, ...), etc.
  21. type FS struct {
  22. rootFS hackpadfs.FS
  23. mounts sync.Map // map[string]hackpadfs.FS
  24. mountMu sync.Mutex
  25. }
  26. // NewFS returns a new FS.
  27. func NewFS(rootFS hackpadfs.FS) (*FS, error) {
  28. return &FS{
  29. rootFS: rootFS,
  30. }, nil
  31. }
  32. // AddMount mounts 'mount' at 'path'. The mount point must already exist as a directory.
  33. func (fs *FS) AddMount(path string, mount hackpadfs.FS) error {
  34. err := fs.addMount(path, mount)
  35. if err != nil {
  36. return &hackpadfs.PathError{Op: "mount", Path: path, Err: err}
  37. }
  38. return nil
  39. }
  40. func (fs *FS) addMount(p string, mountFS hackpadfs.FS) error {
  41. if !hackpadfs.ValidPath(p) || p == "." {
  42. return hackpadfs.ErrInvalid
  43. }
  44. _, loaded := fs.mounts.Load(p)
  45. if loaded {
  46. // cannot mount at same point as existing mount
  47. return hackpadfs.ErrExist
  48. }
  49. fs.mountMu.Lock()
  50. defer fs.mountMu.Unlock()
  51. dir, base := path.Split(p)
  52. parentFS, subPath := fs.Mount(dir) // get this mount point's parent mount, verify dir exists
  53. f, err := parentFS.Open(path.Join(subPath, base))
  54. if err != nil {
  55. return err
  56. }
  57. defer func() { _ = f.Close() }()
  58. info, err := f.Stat()
  59. if err != nil {
  60. return err
  61. }
  62. if !info.IsDir() {
  63. return hackpadfs.ErrNotDir
  64. }
  65. // TODO Handle data race when directory is removed or becomes a file between the Stat and the mount.
  66. _, loaded = fs.mounts.LoadOrStore(p, mountFS)
  67. if loaded {
  68. // cannot mount at same point as existing mount
  69. return hackpadfs.ErrExist
  70. }
  71. return nil
  72. }
  73. // Mount implements hackpadfs.MountFS
  74. func (fs *FS) Mount(path string) (mount hackpadfs.FS, subPath string) {
  75. mount, mountPath, subPath := fs.mountPoint(path)
  76. if mountPath == "." {
  77. return mount, path
  78. }
  79. return mount, subPath
  80. }
  81. func (fs *FS) mountPoint(path string) (_ hackpadfs.FS, mountPoint, subPath string) {
  82. var resultPath string
  83. resultFS := fs.rootFS
  84. fs.mounts.Range(func(key, value interface{}) bool {
  85. mountPath, mountFS := key.(string), value.(hackpadfs.FS)
  86. switch {
  87. case strings.HasPrefix(path, mountPath+"/"):
  88. if len(mountPath) > len(resultPath) {
  89. resultPath, resultFS = mountPath, mountFS
  90. }
  91. return true
  92. case mountPath == path:
  93. // exact match
  94. resultPath, resultFS = mountPath, mountFS
  95. return false
  96. default:
  97. return true
  98. }
  99. })
  100. subPath = path
  101. subPath = strings.TrimPrefix(subPath, resultPath)
  102. subPath = strings.TrimPrefix(subPath, "/")
  103. if resultPath == "" {
  104. resultPath = "."
  105. }
  106. if subPath == "" {
  107. subPath = "."
  108. }
  109. return resultFS, resultPath, subPath
  110. }
  111. // Open implements hackpadfs.FS
  112. func (fs *FS) Open(name string) (hackpadfs.File, error) {
  113. mountFS, subPath := fs.Mount(name)
  114. return mountFS.Open(subPath)
  115. }
  116. // Point represents a mount point, including any relevant metadata
  117. type Point struct {
  118. Path string
  119. }
  120. // MountPoints returns a slice of mount points every mounted file system.
  121. func (fs *FS) MountPoints() []Point {
  122. var points []Point
  123. fs.mounts.Range(func(key, _ interface{}) bool {
  124. path := key.(string)
  125. points = append(points, Point{path})
  126. return true
  127. })
  128. return points
  129. }
  130. // Rename implements hackpadfs.RenameFS
  131. func (fs *FS) Rename(oldname, newname string) error {
  132. oldMount, oldPoint, oldSubPath := fs.mountPoint(oldname)
  133. newMount, newPoint, newSubPath := fs.mountPoint(newname)
  134. oldInfo, err := hackpadfs.Stat(oldMount, oldSubPath)
  135. if err != nil {
  136. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: err}
  137. }
  138. if oldname == newname {
  139. if !oldInfo.IsDir() {
  140. return nil
  141. }
  142. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: hackpadfs.ErrExist}
  143. }
  144. if oldPoint == newPoint {
  145. return hackpadfs.Rename(oldMount, oldSubPath, newSubPath)
  146. }
  147. if oldInfo.IsDir() {
  148. // TODO support renaming directories
  149. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: hackpadfs.ErrNotImplemented}
  150. }
  151. oldFile, err := oldMount.Open(oldSubPath)
  152. if err != nil {
  153. return err
  154. }
  155. defer func() { _ = oldFile.Close() }()
  156. newFile, err := hackpadfs.OpenFile(newMount, newSubPath, hackpadfs.FlagWriteOnly|hackpadfs.FlagCreate|hackpadfs.FlagTruncate, oldInfo.Mode())
  157. if err != nil {
  158. return err
  159. }
  160. newFileWriter, ok := newFile.(io.Writer)
  161. if !ok {
  162. return &hackpadfs.LinkError{Op: "rename", Old: oldname, New: newname, Err: hackpadfs.ErrPermission}
  163. }
  164. defer func() { _ = newFile.Close() }()
  165. _, err = io.Copy(newFileWriter, oldFile)
  166. if err != nil {
  167. _ = hackpadfs.Remove(newMount, newSubPath)
  168. return err
  169. }
  170. return hackpadfs.Remove(oldMount, oldSubPath)
  171. }