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  }