dynamic_fs.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package dynamicfs
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "os"
  7. "strings"
  8. "sync"
  9. "time"
  10. "golang.org/x/net/context"
  11. "github.com/jacobsa/fuse"
  12. "github.com/jacobsa/fuse/fuseops"
  13. "github.com/jacobsa/fuse/fuseutil"
  14. "github.com/jacobsa/timeutil"
  15. )
  16. // Create a file system that contains 2 files (`age` and `weekday`) and no
  17. // directories. Every time the `age` file is opened, its contents are refreshed
  18. // to show the number of seconds elapsed since the file system was created (as
  19. // opposed to mounted). Every time the `weekday` file is opened, its contents
  20. // are refreshed to reflect the current weekday.
  21. //
  22. // The contents of both of these files is updated within the filesystem itself,
  23. // i.e., these changes do not go through the kernel. Additionally, file access
  24. // times are not updated and file size is not known in advance and is set to 0.
  25. // This simulates a filesystem that is backed by a dynamic data source where
  26. // file metadata is not necessarily known before the file is read. For example,
  27. // a filesystem backed by an expensive RPC or by a stream that's generated on
  28. // the fly might not know data size ahead of time.
  29. //
  30. // This implementation depends on direct IO in fuse. Without it, all read
  31. // operations are suppressed because the kernel detects that they read beyond
  32. // the end of the files.
  33. func NewDynamicFS(clock timeutil.Clock) (server fuse.Server, err error) {
  34. createTime := clock.Now()
  35. fs := &dynamicFS{
  36. clock: clock,
  37. createTime: createTime,
  38. fileHandles: make(map[fuseops.HandleID]string),
  39. }
  40. server = fuseutil.NewFileSystemServer(fs)
  41. return
  42. }
  43. type dynamicFS struct {
  44. fuseutil.NotImplementedFileSystem
  45. mu sync.Mutex
  46. clock timeutil.Clock
  47. createTime time.Time
  48. nextHandle fuseops.HandleID
  49. fileHandles map[fuseops.HandleID]string
  50. }
  51. const (
  52. rootInode fuseops.InodeID = fuseops.RootInodeID + iota
  53. ageInode
  54. weekdayInode
  55. )
  56. type inodeInfo struct {
  57. attributes fuseops.InodeAttributes
  58. // File or directory?
  59. dir bool
  60. // For directories, children.
  61. children []fuseutil.Dirent
  62. }
  63. // We have a fixed directory structure.
  64. var gInodeInfo = map[fuseops.InodeID]inodeInfo{
  65. // root
  66. rootInode: {
  67. attributes: fuseops.InodeAttributes{
  68. Nlink: 1,
  69. Mode: 0555 | os.ModeDir,
  70. },
  71. dir: true,
  72. children: []fuseutil.Dirent{
  73. {
  74. Offset: 1,
  75. Inode: ageInode,
  76. Name: "age",
  77. Type: fuseutil.DT_File,
  78. },
  79. {
  80. Offset: 2,
  81. Inode: weekdayInode,
  82. Name: "weekday",
  83. Type: fuseutil.DT_File,
  84. },
  85. },
  86. },
  87. // age
  88. ageInode: {
  89. attributes: fuseops.InodeAttributes{
  90. Nlink: 1,
  91. Mode: 0444,
  92. },
  93. },
  94. // weekday
  95. weekdayInode: {
  96. attributes: fuseops.InodeAttributes{
  97. Nlink: 1,
  98. Mode: 0444,
  99. // Size left at 0.
  100. },
  101. },
  102. }
  103. func findChildInode(
  104. name string,
  105. children []fuseutil.Dirent) (inode fuseops.InodeID, err error) {
  106. for _, e := range children {
  107. if e.Name == name {
  108. inode = e.Inode
  109. return
  110. }
  111. }
  112. err = fuse.ENOENT
  113. return
  114. }
  115. func (fs *dynamicFS) findUnusedHandle() fuseops.HandleID {
  116. // TODO: Mutex annotation?
  117. handle := fs.nextHandle
  118. for _, exists := fs.fileHandles[handle]; exists; _, exists = fs.fileHandles[handle] {
  119. handle++
  120. }
  121. fs.nextHandle = handle + 1
  122. return handle
  123. }
  124. func (fs *dynamicFS) GetInodeAttributes(
  125. ctx context.Context,
  126. op *fuseops.GetInodeAttributesOp) (err error) {
  127. // Find the info for this inode.
  128. info, ok := gInodeInfo[op.Inode]
  129. if !ok {
  130. err = fuse.ENOENT
  131. return
  132. }
  133. // Copy over its attributes.
  134. op.Attributes = info.attributes
  135. return
  136. }
  137. func (fs *dynamicFS) LookUpInode(
  138. ctx context.Context,
  139. op *fuseops.LookUpInodeOp) (err error) {
  140. // Find the info for the parent.
  141. parentInfo, ok := gInodeInfo[op.Parent]
  142. if !ok {
  143. err = fuse.ENOENT
  144. return
  145. }
  146. // Find the child within the parent.
  147. childInode, err := findChildInode(op.Name, parentInfo.children)
  148. if err != nil {
  149. return
  150. }
  151. // Copy over information.
  152. op.Entry.Child = childInode
  153. op.Entry.Attributes = gInodeInfo[childInode].attributes
  154. return
  155. }
  156. func (fs *dynamicFS) OpenDir(
  157. ctx context.Context,
  158. op *fuseops.OpenDirOp) (err error) {
  159. // Allow opening directory.
  160. return
  161. }
  162. func (fs *dynamicFS) ReadDir(
  163. ctx context.Context,
  164. op *fuseops.ReadDirOp) (err error) {
  165. // Find the info for this inode.
  166. info, ok := gInodeInfo[op.Inode]
  167. if !ok {
  168. err = fuse.ENOENT
  169. return
  170. }
  171. if !info.dir {
  172. err = fuse.EIO
  173. return
  174. }
  175. entries := info.children
  176. // Grab the range of interest.
  177. if op.Offset > fuseops.DirOffset(len(entries)) {
  178. err = fuse.EIO
  179. return
  180. }
  181. entries = entries[op.Offset:]
  182. // Resume at the specified offset into the array.
  183. for _, e := range entries {
  184. n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], e)
  185. if n == 0 {
  186. break
  187. }
  188. op.BytesRead += n
  189. }
  190. return
  191. }
  192. func (fs *dynamicFS) OpenFile(
  193. ctx context.Context,
  194. op *fuseops.OpenFileOp) (err error) {
  195. fs.mu.Lock()
  196. defer fs.mu.Unlock()
  197. var contents string
  198. // Update file contents on (and only on) open.
  199. switch op.Inode {
  200. case ageInode:
  201. now := fs.clock.Now()
  202. ageInSeconds := int(now.Sub(fs.createTime).Seconds())
  203. contents = fmt.Sprintf("This filesystem is %d seconds old.", ageInSeconds)
  204. case weekdayInode:
  205. contents = fmt.Sprintf("Today is %s.", fs.clock.Now().Weekday())
  206. default:
  207. err = fuse.EINVAL
  208. return
  209. }
  210. handle := fs.findUnusedHandle()
  211. fs.fileHandles[handle] = contents
  212. op.UseDirectIO = true
  213. op.Handle = handle
  214. return
  215. }
  216. func (fs *dynamicFS) ReadFile(
  217. ctx context.Context,
  218. op *fuseops.ReadFileOp) (err error) {
  219. fs.mu.Lock()
  220. defer fs.mu.Unlock()
  221. contents, ok := fs.fileHandles[op.Handle]
  222. if !ok {
  223. log.Printf("ReadFile: no open file handle: %d", op.Handle)
  224. err = fuse.EIO
  225. return
  226. }
  227. reader := strings.NewReader(contents)
  228. op.BytesRead, err = reader.ReadAt(op.Dst, op.Offset)
  229. if err == io.EOF {
  230. err = nil
  231. }
  232. return
  233. }
  234. func (fs *dynamicFS) ReleaseFileHandle(
  235. ctx context.Context,
  236. op *fuseops.ReleaseFileHandleOp) (err error) {
  237. fs.mu.Lock()
  238. defer fs.mu.Unlock()
  239. _, ok := fs.fileHandles[op.Handle]
  240. if !ok {
  241. log.Printf("ReleaseFileHandle: bad handle: %d", op.Handle)
  242. err = fuse.EIO
  243. return
  244. }
  245. delete(fs.fileHandles, op.Handle)
  246. return
  247. }
  248. func (fs *dynamicFS) StatFS(ctx context.Context,
  249. op *fuseops.StatFSOp) (err error) {
  250. return
  251. }