You didn't specify a programming language, though FUSE is native C++ there are native Golang bindings, implemented at bazil.org/fuse.
I'd say that the primary parts of the answer need to include the following:
- A data structure to handle the file system tree in memory
- Descriptions of Nodes and their relationship to iNodes
- Hooks to capture FUSE server requests to handle the cli commands
- A description of mounting a folder with the FUSE server.
I recently wrote an in-memory file system using this adapter: github.com/bbengfort/memfs. My writeup about its performance is here: In-Memory File System with FUSE. Quickly, a few choices I made:
The in memory data structure contains 2 primary structs, dir and file that are both nodes:
type Node struct {
ID uint64
Name string
Attrs fuse.Attr
Parent *Dir
}
type Dir struct {
Node
Children map[string]Node
}
type File struct {
Node
Data []byte
}
As you can see, this is a simple tree that is traversable up and down through the Children
and Parent
links. The Data
attribute of the File holds all the contents of files. The file system therefore just needs to create a "root" directory called "\"
at the mount point, and then on mkdir
a Dir
is added to its children, and on cp
a File
is added. In Go, this is as simple as:
type FS struct {
root *Dir
}
func Mount(path string) error {
// Unmount the FS in case it was mounted with errors.
fuse.Unmount(path)
// Mount the FS with the specified options
conn, err := fuse.Mount(path)
if err != nil {
return err
}
// Ensure that the file system is shutdown
defer conn.Close()
// Create the root dir and file system
memfs := FS{
root: &Dir{
ID: 1,
Name: "\",
Parent: nil,
},
}
// Serve the file system
if err := fs.Serve(conn, memfs); err != nil {
return err
}
}
Now you need hooks to implement the various FUSE requests and calls. Here is an example for mkdir
:
func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
// Update the directory Atime
d.Attrs.Atime = time.Now()
// Create the child directory
c := new(Dir)
c.Init(req.Name, req.Mode, d)
// Set the directory's UID and GID to that of the caller
c.Attrs.Uid = req.Header.Uid
c.Attrs.Gid = req.Header.Gid
// Add the directory to the directory
d.Children[c.Name] = c
// Update the directory Mtime
d.Attrs.Mtime = time.Now()
return c, nil
}
Finally, close the interview question with a discussion about how to compile and run the server, mounting to a path and perhaps how FUSE intercepts kernel calls and passes them to your process in user space.