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