Skip to content

Instantly share code, notes, and snippets.

@djdv
Last active November 10, 2020 23:24
Show Gist options
  • Save djdv/8341762e571ef799cf7613f2f3c1f506 to your computer and use it in GitHub Desktop.
Save djdv/8341762e571ef799cf7613f2f3c1f506 to your computer and use it in GitHub Desktop.
go-ipfs file system document

File System

Overview

Editors note:
Nothing is final and this document is temporary. It may be replaced with some user documentation around the command line interface if the help text can't be written succinctly enough.

The code currently lives in a temporary branch here: djdv/go-ipfs

The "filesystem" packages contain maps between the various IPFS APIs and a common file system interface.

The "core/commands/filesystem" packages provide a means to parse and dispatch requests, as well as manage instances of file system bindings.

dependency-graph

Building

The FUSE portions require the dependencies of cgofuse
If you do not have fuse on your system, or simply don't use it. You may disable building it into the binary entirely, by specifying the nofuse build constraint.

(go install -tags=nofuse./cmd/ipfs)

Without a C compiler

On non-Windows platforms, fuse is expected to exist as part of an OS standard, or disabled via nofuse. Dynamic linking of fuse, is usually handled by the OS, as a kernel module.

On Windows, cgofuse can be built without a C compiler by disabling cgo when building ipfs.
(CMD: SET CGO_ENABLED=0, PowerShell: $ENV:CGO_ENABLED=0)

This will dynamically link with the WinFSP libraries where possible.

With a C compiler

The dependencies of cgofuse should be installed (see: Building).

On non-Windows platforms, go should be aware of how to link with fuse if the dependencies are set up on the system.

On Windows, to build with CGO enabled, you must have WinFSP installed, with its "Developer" feature included (the library headers).

When building, if you encounter an error like this:

pkg\mod\github.com\billziss-gh\cgofuse@v1.2.0\fuse\fsop_cgo.go:19:2: error: #error platform not supported
   19 | #error platform not supported
      |  ^~~~~

it means you're using an unsupported compiler.

TODO: move a bunch of this to here: cgo · golang/go Wiki · GitHub and just link to it. (have mingw-w64 link to a sub-page that describes how to set it up.)

setup compiler

A Windows native compiler, like mingw-w64 works for my builds with the Windows native go compiler. (that is, mingw64/mingw-w64-x86_64-gcc a MinGW compiler, not msys/gcc a Cygwin compiler)

*Cygwin compilers in a Cygwin environment may work, as WinFSP supports it, but I have not tested it. (if anyone tries it, please update this.)

include library headers

The C compiler needs to be aware of the WinFSP library headers somehow. Otherwise you'll see an error like this:

pkg\mod\github.com\billziss-gh\cgofuse@v1.2.0\fuse\host_cgo.go:103:10: fatal error: fuse_common.h: No such file or directory
  103 | #include <fuse_common.h>
      |          ^~~~~~~~~~~~~~~
compilation terminated.

The easiest way to make the compiler aware of these files is put them into the CPATH environment variable before before building ipfs.
By default they're in "%ProgramFiles(x86)%\WinFsp\inc\fuse", so you may set the CPATH environment variable to include that path in its search. (CMD: SET CPATH=%CPATH%;%ProgramFiles(x86)%\WinFsp\inc\fuse, PowerShell: $ENV:CPATH += ";${ENV:ProgramFiles(x86)}\Winfspinc\fuse"
As stated above, Cygwin equivalents may work, but have not been tested and are not being covered by this document. Feel free to add information here if you try using WinFSP's Cygwin compatibility.

If the WinFSP service is not running, commands that utilize it will return an error. After installing/enabling the WinFSP "Core" service, commands should start returning successful responses.

Important interfaces

Command line

TODO: discuss and finalize parameter names and write good --help text.

NOTE: Below is outdated, but still relevant.

Changes to be made include making ipfs mount list out the current mountpoints (currently done via ipfs mount -l).

ipfs mount -a will mount targets specified in the node's config file, ignoring/erroring on other command line parameters.

ipfs unmount -a will remain as is, disconnecting all active binding between the node and the host.

ipfs (un)mount --parameterX --parameterY ... commands will parse targets from the command line, ignoring config file settings.


The command line sub-commands ipfs daemon and ipfs mount and parsers for their parameters live in core/commands/filesystem.
Values are populated (in priority order) from the parameters of the sub-command, the node's config file, or a platform specific fall back value.

Issuing ipfs mount will mount a set of targets based on the above, but may be customized using combinations of parameters. A complex example would be ipfs mount --system=fuse --subsystem="IPFS,IPNS,File" --target="/ipfs,/ipns,/file" which mimic's the current defaults on Linux, but more explicitly.

Anything that can be determined by the implementation may be omitted. Such as the provider, or the targets if they're within your config file.
e.g. ipfs mount --subsystem="IPFS" is valid and would expand to ipfs mount --system=fuse--namespace="IPFS" --target="$(ipfs config Mounts.IPFS)"

It is also possible to specify any combination of namespaces and targets so long as the argument count matches.
For example, this is a valid way to map IPFS to 2 different mountpoints ipfs mount --subsystem="IPFS,IPFS" -target="/ipfs,/mnt/ipfs"

At any tme, you may list the currently active mounts via ipfs mount --list or shorthand ipfs mount -l


ipfs unmount shares the same parameters as ipfs mount with the addition of a -a to unmount all previously mounted targets


ipfs daemon shares the same parameters as ipfs mount simply prefixed with --mount-.
e.g. ipfs daemon --mount --mount-system="fuse"" --mount-subsystem="IPFS,IPNS" --mount-target="/ipfs,/ipns"
It carries the same auto expansion rules, picking up missing parameters through the same deduction methods. (checks arguments, then config, then environment)

File System Interface

(temporary godoc host) filesystem - Go Documentation Server

The well named Interface acts as a common interface between various APIs for use with our provider implementations. Allowing things to present themselves as a file system.

Currently, we maintain a list of ID's for implementations to use, such as IPFS, IPNS, et al. (see godoc for full list)

IPFS cmds

fscmds - Go Documentation Server

Responsible for defining cmds.Commands such as mount, unmount, their parameters, their request parsers, *translations methods for requests, etc. (*e.g daemon --mount -... -> mount -...)

Node Manager

manager - Go Documentation Server

Defines and implements a Dispatcher, which is responsible for taking in a list of requests, and returning a stream of their results. Dispatching them to their Header{Host-API-ID:FS-ID} pair's implementation method. (e.g. internally one could map dispatcher.Attach({fuse:IPFS}{/mnt/ipfs}, {fuse:IPFS}{/mnt/ipfs2}, ...) to dispatch(fuse, ipfs).mount("/mnt/ipfs", "/mnt/ipfs2", ...) and plex these requests/results)

Host instances

Host implementations for the managers API list, provide interfaces to make requests and communicate with the host system.

They return types of their own, such as fuse's mounter, or p9fsps attacher.

Which take in their own specific requests, but reply with a common message format (a host.Response), which contain a host.Binding, the result of binding the deconstructor returned from the host, with a symbolic representation (the request that caused its construction).

(conceptually: host.Binding{"/n/ipfs":io.Closer})

Implementation details

System Table / Config

How to store portable target values for various API standards in the node's config file. in progress: [notes document](fs config.md)

(FUSE/9P) API mappings themselves

API mappings utilize the file system Interface and simply transform data from it into external API specific constructs. For example this is the Gettattr for IPFS under FUSE.

    ...
    iStat, _, err := fs.intf.Info(path, filesystem.StatRequestAll)
    if err != nil {
        errNo := interpretError(err)
        if errNo != -fuselib.ENOENT { // don't flood the logs with "not found" errors
            fs.log.Error(err)
        }
        return errNo
    }

    var ids statIDGroup
    ids.uid, ids.gid, _ = fuselib.Getcontext()
    applyIntermediateStat(stat, iStat)
    applyCommonsToStat(stat, fs.filesWritable, fs.mountTimeGroup, ids)
    return operationSuccess

and under 9P

    ...

    fidInfo, infoFilled, err := f.intf.Info(f.path.String(), requestFrom9P(req))
    if err != nil {
        return f.QID, ninelib.AttrMask{}, ninelib.Attr{}, interpretError(err)
    }

    attr := attrFromCore(fidInfo) // TODO: maybe resolve IDs
    tg := timeGroup{atime: f.initTime, mtime: f.initTime, ctime: f.initTime, btime: f.initTime}
    applyCommonsToAttr(&attr, f.filesWritable, tg, idGroup{uid: ninelib.NoUID, gid: ninelib.NoGID})

    return f.QID, filledFromCore(infoFilled), attr, nil

File System Directory interface

filesystem.Directory - Go Documentation Server

This is (for the moment) our semantics for what a directory is. It may need to change, or be redefined. But currently returns a stream of entries, containing an offset and an error value if one was encountered during operation.

This is an example usage of translating these entries from filesystem.DirectoryEntry to FUSE native entries.

package fuse

func OpenDir() {
    directory, err := fs.intf.OpenDirectory(path)
...
}

func Readdir() {
    callCtx, cancel := context.WithCancel(ctx)
    defer cancel()

    errNo, err := fillDir(callCtx, directory, fill, ofst)
    if err != nil {
        fs.log.Error(err)
    }

    return errNo
}

func fillDir(ctx context.Context, directory filesystem.irectory, fill fuseFillFunc, offset int64) (int, error) {
...
    if offset == 0 {
        if err := directory.Reset(); err != nil {
            // NOTE: POSIX `rewinddir` is not expected to fail
            // if this happens, we'll inform FUSE's `readdir` that the stream position is (now) invalid
            return -fuselib.ENOENT, err // see: SUSv7 `readdir` "Errors"
        }
    }

    readCtx, cancel := context.WithCancel(ctx)
    defer cancel()

    for ent := range directory.List(readCtx, uint64(offset)) {
        if err := ent.Error(); err != nil {
            return -fuselib.ENOENT, err
        }
        stat = statFunc(ent.Name())
        if !fill(ent.Name(), stat, int64(ent.Offset())) {
            break // fill asked us to stop filling
        }
    }

    return operationSuccess, nil
}

Plugin/Standalone implementation

It's possible to break this out into a standalone or plugin implementation. We only need a minimum of access to the CoreAPI. Other remote things would be possible with minor additions to the API to allow for things like remote resource locking, and RPC methods to publish a CID as the node's FilesAPI root similar to how IPNS keys may be published over RPC. notes document

Cross boundary locking (incomplete)

In order to allow the daemon to perform normal operations without locking the user out of certain features, such as publishing to IPNS keys or using the FilesAPI via the ipfs command, or other API instances. We'll want to incorporate a shared resource lock on the daemon for these namespaces to use. For example, within the ipfs name publish command we would like to acquire a lock for the key we are about to publish to, which may or may not also be in use by an ipfs mount instance, or other instance of the CoreAPI. Likewise with ipfs files in general. As a result we'll need some kind of interface such as this

type ResourceLock interface {
    Request(namespace mountinter.Namespace, resourceReference string, ltype LockType, timeout time.Duration) error
    Release(namespace mountinter.Namespace, resourceReference string, ltype LockType)
}

usable within the name publish cmd as

err := daemonNode.???.Request(mountinter.NamespaceIPNS, "/${key-hash}", mountinter.LockDataWrite, 0)

where the same instance is used by the rest of the services on the daemon, such as files, and mount. Any may hold the lock at various points, preventing one another from colliding and creating inconsistency without entirely disabling functionality on the node / holding exclusive access of the entire node.

NOTE: a quick hack was written to implement this but I don't trust myself to implement it correctly/efficiently.
This will require research to see how other systems perform ancestry style path locking and which libraries already exist that could help with it.

Misc Notes

FUSE

It should be noted somewhere, the behaviour of (Go)fuse.Getcontext/(C)fuse_get_context.
None of the implementations have useful documentation for this call, other than saying the pointer to the structure should not be held past the operation call that invoked it.
The various implementations have varying results. For example, consider the non-exhaustive table below.

FreeBSD (fusefs)
NetBSD (PUFFS)
macOS (FUSE for macOS)
Linux (fuse) Windows (WinFSP)
opendir: populated opendir: populated opendir: populated
readdir: populated readdir: populated readdir: NULL
releasedir: populated releasedir: NULL releasedir: NULL

Inherently, but not via any spec, the context is only required to be populated within operations that create system files and/or check system access. (Without them, you wouldn't be able to implement file systems that adhere to POSIX specifications.)
i.e. opendir must know the UID/GID of the caller in order to check access permissions, but readdir does not, since readdir implies that the check was already done in opendir (as it must receive a valid reference that was previously returned from opendir).

As such, for our readdir implementations, we obtain the context during opendir, and bind it with the associated handle construct, if it's needed.
During normal operation it's not, but for systems that supporting FUSE's "readdirplus" capability, we need the context of the caller who opened the directory at the time of readdir operation.

Dots

Directory entries "." and ".." are to be referenced in this section as "dot" and "dot-dot" respectively.

9P

Reference point: Plan 9 manual - Section 5: the Plan 9 File Protocol, 9P

While not forbidden or required, 9P convention is not to provide explicit entries for dot or dot-dot, within directories.

In addition, (while not expressly forbidden or required at the protocol level;) the reference implementation demands that walks must support dot-dot, and that walks to dot are to be interpreted locally. (i.e. a walk to dot does not generate a 9P message nor does it initiate any communication with the remote 9P node)

A walk to dot-dot in the root directory shall be a no-op. (i.e. the parent of the root directory, is itself)

FUSE

POSIX states (in opendir) that dots are optional, and readdir expressly forbids them if they do not exist. "If entries for dot or dot-dot exist, one entry shall be returned for dot and one entry shall be returned for dot-dot; otherwise, they shall not be returned."

We have previously included them and supported them, however this support was removed in this commit. Meaning the current FUSE directory functions do not supply dot entries, nor do they expect stat requests for them.

However, support for them can easily be (re-)added. All that needs to change is for the directory filler function to include the dots, and for the stat function associated with a directory to fill in their stat struct. Our API is set up in a functional way that would allow this to easily be dynamic, if so desired. Allowing particular directories to support or not support dots. Although there should be no reason for this outside of some potential edge cases to support non-POSIX-compliant programs.

NetBSD

is only allowing 1 mountpoint to be active at a time, if a second mountpoint is requested, it will be mapped, but the previous mountpoint will be overtaken by the new one.
e.g. consider the sequence:
ipfs mount --namespace=pinfs --target=/ipfs will mount the pinfs to /ipfs
ipfs mount --namespace=keyfs --target=/ipns will mount the keyfs to /ipns
at this moment, listing either /ipfs or /ipns will return results from the keyfs.
This is likely a cgofuse bug, needs looking into.
Otherwise, things seem to work as expected.
(Env: NetBSD 9.0, Go 1.14.2)
TODO: NetBSD has support for mounting via 9P2000 and 9p2000.u (but not .L)
look into adding support for either in the p9 library we use (would add support for a bunch more platforms and tools than NetBSD as well)

OpenBSD

Update: this is an upstream issue OpenBSD: Readdir oddity · Issue #49 · billziss-gh/cgofuse · GitHub

Original text follows:

is allowing traversal and cating of files, but getdents is failing in ls.

 57206 ls       CALL  fstat(4,0x7f7ffffd9fe8)
 57206 ls       STRU  struct stat { dev=9733, ino=1, mode=dr-xr-xr-- , nlink=0, uid=0<"root">, gid=0<"wheel">, rdev=0, atime=0, mtime=0, ctime=0, size=0, blocks=4, blksize=512, flags=0x0, gen=0x0 }
 57206 ls       RET   fstat 0
 57206 ls       CALL  fchdir(4)
 57206 ls       RET   fchdir 0
 57206 ls       CALL  getdents(4,0x95a668ff000,0x1000)
 57206 ls       RET   getdents -1 errno 2 No such file or directory
 57206 ls       CALL  close(4)
 57206 ls       RET   close 0

The daemon is receiving a very large offset/seekdir value for some reason.

2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:113      Opendir - "/"
2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:157      Readdir - {1|0}"/"
2020-05-07T05:32:11.856-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:157      Readdir - {1|208}"/"
2020-05-07T05:32:11.856-0400    ERROR   fuse/pinfs      pinfs/pinfs.go:171      offset 206 is not/no-longer valid
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:139      Releasedir - {1}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:113      Opendir - "/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:90       Getattr - {FFFFFFFFFFFFFFFF}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:157      Readdir - {2|0}"/"
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:157      Readdir - {2|208}"/"
2020-05-07T05:32:11.857-0400    ERROR   fuse/pinfs      pinfs/pinfs.go:171      offset 206 is not/no-longer valid
2020-05-07T05:32:11.857-0400    DEBUG   fuse/pinfs      pinfs/pinfs.go:139      Releasedir - {2}"/"

Readdir tests are passing within Go on the platform, so this is likely a cgofuse issue.
This is also the only platform currently where ls doesn't work.
Needs investigating.
(Env: OpenBSD 6.6, Go 1.13.1; OpenBSD 6.7, Go 1.14.4; (b23023597) OpenBSD 6.7, Go 1.14.6)

I wonder if this is something thread related. I tried locking the goroutine that calls Mount to the OS thread, but it didn't seem to influence anything. I need to see if I can break the cgofuse memfs examples on OpenBSD by mounting it via goroutine.

fstab

We need to store a file system table setting somewhere in the node's config.

One potential structure is to have a list of structured requests. Containing everything necessary to bind node APIs with the host FS/APIs.

"Filesystem": [
    {
        "NodeAPI": "IPFS",
        "HostAPI": "Fuse",
        "Target": "/ipfs",
        "Options": "uid=80,gid=80",
    },
    {
        "NodeAPI": "IPNS",
        "HostAPI": "Fuse",
        "Target": "/ipns",
        "Options": "uid=60,gid=60",
    },
    {
        "NodeAPI": "FilesAPI",
        "HostAPI": "P9P",
        "Target": "/ip4/127.0.0.1/tcp/564",
        "Options": "",
    }
]

An alternate format is to have a list of strings that resemble a multipath. Leaving interpretation up to client and server implementations and/or conventions in a "fs hierarchy" library.

"Filesystem": [
    "/fuse/ipfs/host/ipfs",
    "/fuse/ipns/host/ipns",
    "/9p/files/socket/ip4/127.0.0.1/tcp/564"
]

If arguments on a per target level are necessary (which they might need to be) We could figure out a way of stuffing them into this format, like/fuse/opts/param1/arg1/param2/arg2/$systemID/$target or something else. (Relevant issue: Proposal: Add keyword arguments to protocols)

Note:

The host API implementations (like Fuse) don't have 1:1 mappings for their options. For example, setting the mount's "type" is done differently across various implementations, via: volname=, volumeprefix=, fsname=, and hypothetically others.

Various conventional names could be defined for the sake of portability, and used when the platform supports it. Otherwise the options will be ignored, and the console potentially warned. (e.g. "UID option set for I:, but platform Windows does not support this")

[Plugins that can add themselves as ipfs subcommands, while easily running standalone]

As a plugins.Plugin, the cmds lib would need some way for plugins to add cmd.Commands dynamically to ipfs This could probably be done during init of the root Command, by adding either a new type of Plugin (PluginCmds), or extending Plugin to have Command() *cmds.Command method

// /plugin/plugin.go
type Plugin interface {
	Name() string
	Version() string
	Init(env *Environment) error
+	Command() *cmds.Command
+(or maybe)  Command(string) (*cmds.Command, error) this seems less good though, it kind of looks like dll loading though
}

// /core/commands/root.go
init(){
	for _, plug := range plugin.CmdsPlugins... {
	if plugCmd := plug.Command(); plugCmd != nil
		plugCmdName := plug.Name()
		rootSubcommands[plugCmdName] = plugCmd
}

* this could also be done wherever rootSubcommands is used, instead
i.e. before doing a command lookup on the index, check if plugins are loaded, check if they contain the cmd you want, if they do, call its `.Run`, and if not, look in the rootSubcommands index like normal.

Then, when ipfs initializes itself, it should now be aware of commands that were added by plugins, like ipfs mount.

This also allows us to create a standalone mount.exe which simply connects to an existing daemon instance that has the plugin loaded (if it's running), or construct our own local node (if there's no ipfs daemon instance) and preloads the filesystem plugin, or utilizes it's methods directly, with the locally constructed node.

In mount.exe we'd simply re-use the same cmds.Command on our own constructed node if the API isn't available.

The things we need to do mounting is a core.IpfsNode for its FilesRoot, any way to get a CoreAPI (from the node, from the cmdenv, whatever), and a place to store our fs-table that lasts between mount and unmount calls. (currently in the go-ipfs fork, our index maintains a mapping of (un)mount request{API:FS-ID:host-target-string} -> instance.Closer the closers are constructed and inserted on mount and looked up then closed on unmount we store this in an interface on the node as node.FileSystem which is of type (filesystem/manager).Dispatcher

// Dispatcher parses requests and couples them with their intended APIs.
type Dispatcher interface {
	Attach(...Request) <-chan Response // `ipfs mount ...` returns cmds.EncodableValue
	Detach(...Request) <-chan Response // `ipfs unmount ...` ^same
	List() <-chan Response // List provides streams of prior (processed) requests
	//io.Closer              // closes all active `Attacher`s
}

If the daemon is already running on the system, we can simply use the go-ipfs-api to write a simple wrapper of the cmd.Command the node is already using.

An example of the current output via the HTTP api that would be wrapped by a go-ipfs-api shell: (the message format might change)

> curl -X POST "http://127.0.0.1:5002/api/v0/mount?encoding=text&system=fuse,fuse,9p&subsystem=pinfs,keyfs,file&target=I:,N:,/ip4/127.0.0.1/tcp/564"
binding to host:
/9p/file/socket/ip4/127.0.0.1/tcp/564
/fuse/pinfs/host/I:
/fuse/keyfs/host/N:

> curl -X POST "http://127.0.0.1:5002/api/v0/mount?list&encoding=json"
{"Info":"host bindings:"}
{"Target":"/9p/file/socket/ip4/127.0.0.1/tcp/564","Arguments":["/ip4/127.0.0.1/tcp/564"]}
{"Target":"/fuse/pinfs/host/I:","Arguments":["-o","FileSystemName=PinFS,volname=PinFS,uid=-1,gid=-1"]}
{"Target":"/fuse/keyfs/host/N:","Arguments":["-o","FileSystemName=KeyFS,volname=KeyFS,uid=-1,gid=-1"]}

All this would allow people to then, run a node with or without system binding capabilities, via a plugin. And/or use a standalone binary which does everything it needs to fulfill the CLI request somehow. Either on its own, or via a remote node's API. The implication here is that people might be able to include something like mount-ipfs in their /sbin as a hook for mount -t ipfs.

Considering we're using 9P, we also get the benefit of the standalone binary being able to mount a remote node locally (We can simple ask the node to spawn a 9P server socket on a listening TCP port, and on the local machine, mount the 9P server) e.g. the interaction would look like this: client tells server mount 9P server responds with []responses{socketAddr,...} client does PlatformMount(response)

On Linux this translates to a syscall.Mount(socketAddr, localPath) On other platforms it would call out to the shell command of Plan9Port which implements 9P mounting for the OS (Windows also supports native mounting of 9P but this API isn't documented, I need to see if I can contact some of the people at MSFT who worked on it about their plans with it)

This works fine, but our 9P library lacks the AUTH protocol, and message encryption. But those could be added so that clients would need to auth with the remote 9P server on the node, and data traffic between them could be encrypted via the protocol. Making it more viable over WAN than just LAN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment