{"id":17475662,"url":"https://github.com/hinshun/hellofs","last_synced_at":"2025-04-09T20:37:49.109Z","repository":{"id":79445294,"uuid":"192603770","full_name":"hinshun/hellofs","owner":"hinshun","description":"example fuse filesystem using containerd's mount","archived":false,"fork":false,"pushed_at":"2019-06-18T23:07:32.000Z","size":1152,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-01T13:53:35.477Z","etag":null,"topics":["container","containerd","fuse","mount","rootfs"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hinshun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-18T19:53:44.000Z","updated_at":"2023-06-13T09:39:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"d2f751a2-8b7b-4551-a04a-f0866a081f71","html_url":"https://github.com/hinshun/hellofs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinshun%2Fhellofs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinshun%2Fhellofs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinshun%2Fhellofs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinshun%2Fhellofs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hinshun","download_url":"https://codeload.github.com/hinshun/hellofs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248108941,"owners_count":21049226,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["container","containerd","fuse","mount","rootfs"],"created_at":"2024-10-18T18:59:37.046Z","updated_at":"2025-04-09T20:37:49.088Z","avatar_url":"https://github.com/hinshun.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"hellofs\n---\n\nDemonstrating mounting a FUSE filesystem using `github.com/containerd/containerd/mount` instead of `fusermount`.\n\n# Usage\n\n```sh\n$ make\n2019/06/18 15:45:29 Mounting hellofs on \"./mnt\"\n\n$ cd ./mnt\n$ ls -lah\ntotal 0\n-rw-r--r-- 0 root root 5 Dec 31  1969 hello\n\n$ cat hello\nhello\n```\n\n`Ctrl-C` out of `make` will trigger the unmount. If you were using `./mnt` when unmounting, you may get this message:\n```sh\n^C2019/06/18 15:47:31 Unmounting \"./mnt\"\npanic: /bin/fusermount: failed to unmount /home/edgarl/go/src/github.com/hinshun/hellofs/mnt: Device or resource busy\n (code exit status 1)\n\n// ...\n\n$ ls\nls: cannot access 'mnt': Transport endpoint is not connected\nmnt  vendor  go.mod  go.sum  hellofs  LICENSE  main.go  Makefile  README.md\n```\n\nYou can cleanup the mount by ensuring no processes are still using `./mnt` and then run `make umount`:\n\n# hanwen/go-fuse changes\n\nhttps://github.com/hinshun/hellofs/blob/master/vendor/github.com/hanwen/go-fuse/fuse/mount_linux.go#L31-L71\n```golang\n// ...\nimport (\n\t// ...\n\tcmount \"github.com/containerd/containerd/mount\"\n)\n// ...\n\n// Create a FUSE FS on the specified mount point.  The returned\n// mount point is always absolute.\nfunc mount(mountPoint string, opts *MountOptions, ready chan\u003c- error) (fd int, err error) {\n\tuser, err := user.Current()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tf, err := os.OpenFile(\"/dev/fuse\", os.O_RDWR, 0666)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfd = int(f.Fd())\n\n\tm := cmount.Mount{\n\t\tType:   fmt.Sprintf(\"fuse.%s\", opts.Name),\n\t\tSource: opts.FsName,\n\t\tOptions: []string{\n\t\t\t\"nosuid\",\n\t\t\t\"nodev\",\n\t\t\tfmt.Sprintf(\"fd=%d\", fd),\n\t\t\tfmt.Sprintf(\"rootmode=%#o\", syscall.S_IFDIR),\n\t\t\tfmt.Sprintf(\"user_id=%s\", user.Uid),\n\t\t\tfmt.Sprintf(\"group_id=%s\", user.Gid),\n\t\t},\n\t}\n\n\tif opts.AllowOther {\n\t\tm.Options = append(m.Options, \"allow_other\")\n\t}\n\n\tm.Options = append(m.Options, opts.Options...)\n\n\terr = m.Mount(mountPoint)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tclose(ready)\n\treturn fd, err\n}\n```\n\n# FUSE Overview\n\nFUSE is an userspace filesystem framework, it consists of a kernel module `fuse.ko` that has a FUSE VFS interfaced by a device `/dev/fuse` on your system. Many FUSE implementations rely on `libfuse` (a C library to interact with `/dev/fuse` and its protocol), and `fusermount` (a binary owned by `root` but executable by users with a `suid` bit set, so that users can use the mount FUSE filesystems without being root). Libraries like [github.com/hanwen/go-fuse](https://github.com/hanwen/go-fuse) and [github.com/bazil/fuse](https://github.com/bazil/fuse) use `fusermount` to mount but not `libfuse`.\n\n[`fusermount`](https://github.com/libfuse/libfuse/blob/master/util/fusermount.c) is a binary commonly used to mount FUSE filesystems. FUSE implementers typically call `fusermount` as a subprocess, and provides a binary like `sshfs` that users invoke to mount the filesystem instead of calling `fusermount`, `mount(8)` or `mount(2)` directly. The subprocess needs to have an environment variable `_FUSE_COMMFD` set. The FUSE implementation needs to create a file descriptor, set `_FUSE_COMMFD=\u003cfd\u003e`, which `fusermount` will use to pass back the control fd open on `/dev/fuse`.\n\n[`mount(8)`](http://man7.org/linux/man-pages/man8/mount.8.html) is a binary that mounts a filesystem. Internally it uses `mount(2)` the syscall in order to actually get the kernel to mount. `mount(8)` registers filesystems using executables with the name `/sbin/mount.*`. These executables need to fulfill a specific interface, that `mount(8)` will invoke on the `/sbin/mount.*` binary. You can add a `/sbin/mount.\u003cyour-fuse-mount-wrapper\u003e` that should daemonize a process running your FUSE server. However, `mount(2)` will not know about these `/sbin/mount.*` binaries, this is just an implementation detail of `mount(8)`. There is a `/sbin/mount.fuse` that one of these mount wrappers that will execute `fusermount` under the hood.\n\n[`mount(2)`](http://man7.org/linux/man-pages/man2/mount.2.html) is the mount syscall. It only knows about filesystems registered in the kernel (in kernel filesystems, including `fuse`, register themselves via [register_filesystem](https://www.kernel.org/doc/htmldocs/filesystems/API-register-filesystem.html)), which are visible via `cat /proc/filesystems`. However, there seems to be undocumented behaviour in that if you call `mount(2)` with a type is prefixed like `fuse.\u003csubtype\u003e`, it will actually mount via FUSE. The `subtype` doesn't seem to actually matter other than being the type of the mount when you run `mount -l`.\n\nThe `mount(2)` signature looks like this:\n```c\n       #include \u003csys/mount.h\u003e\n\n       int mount(const char *source, const char *target,\n                 const char *filesystemtype, unsigned long mountflags,\n                 const void *data);\n```\n\nFor `filesystemtype` of `fuse.*` type:\n- `source` is unused, you can use it to supply the FUSE name like `sshfs`.\n- `target` is the mountpoint\n- `filesystemtype` looks like `fuse.*` (i.e. `fuse.sshfs`).\n- `mountflags` are flags you can find on `mount(2)`'s manpage. If mountflags is empty, then it creates a mount. You can supply `mountflags` to run `mount(2)` on existing mounts to change properties like making it readonly, change propagation types, etc. In some of those cases, `source`, `filesystemtype` or `filesystemtype` and `data` are ignored.\n- `data` is an optional buffer to provide filesystem specific options.\n\nFor `fuse.*` filesystem types, the options for `data` are delimited by comma, and must have these 4(?) fields:\n- `fd` is the file descriptor you get when you open `/dev/fuse` with `O_RDWR`, this is the control FD where FUSE protocol messages are sent between the FUSE VFS in the kernel, and your FUSE server in userspace.\n- `rootmode` is file mode of your mountpoint bitwise AND with `S_IFMT`, its a bitmask to show only bits of the file mode that says if its a regular, directory, device file, etc. I believe it expects it to be `S_IFDIR`, which is the bit that represents it being a directory.\n- `user_id` is the uid of the user mounting.\n- `group_id` is the gid of the user mounting.\n\nSo the FUSE workflow is:\n1. Open `/dev/fuse` with `O_RDWR` to produce `control FD`.\n2. `mount(2)` with `source=\u003cfuse-name\u003e`, `target=\u003cmountpoint\u003e`, `filesystemtype=fuse.\u003cfuse-name\u003e`, `mountflags=0`, `data=fd=\u003ccontrol FD\u003e,rootmode=\u003cS_IFDIR in octal str\u003e,user_id=\u003cuid\u003e,group_id=\u003cgid\u003e`.\n3. FUSE server reads initialization request from `control FD` and responds with some data about its implementation.\n4. FUSE starts listening on `control FD`.\n5. I/O performed on file in `mountpoint` is handled by FUSE VFS in kernel, which sends a request to FUSE server via `control FD`.\n6. FUSE server responds via `control FD`, to complete the I/O.\n\n## Additional reading\n\n- http://man7.org/linux/man-pages/man8/mount.fuse.8.html\n- https://stackoverflow.com/questions/1554178/how-to-register-fuse-filesystem-type-with-mount8-and-fstab\n- https://stackoverflow.com/questions/6469557/mounting-fuse-gives-invalid-argument-error-in-python\n- https://unix.stackexchange.com/questions/118090/mounting-mmcblk0p1-failed-with-invalid-argument\n- https://engineering.facile.it/blog/eng/write-filesystem-fuse/\n- https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/mounting.md#mount8-and-fstab-compatibility\n- https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/tools/mount_gcsfuse/main.go\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhinshun%2Fhellofs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhinshun%2Fhellofs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhinshun%2Fhellofs/lists"}