mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:02:25 -05:00 
			
		
		
		
	This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
		
			
				
	
	
		
			213 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package wazero
 | |
| 
 | |
| import (
 | |
| 	"io/fs"
 | |
| 
 | |
| 	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
 | |
| 	"github.com/tetratelabs/wazero/internal/sys"
 | |
| 	"github.com/tetratelabs/wazero/internal/sysfs"
 | |
| )
 | |
| 
 | |
| // FSConfig configures filesystem paths the embedding host allows the wasm
 | |
| // guest to access. Unconfigured paths are not allowed, so functions like
 | |
| // `path_open` result in unsupported errors (e.g. syscall.ENOSYS).
 | |
| //
 | |
| // # Guest Path
 | |
| //
 | |
| // `guestPath` is the name of the path the guest should use a filesystem for, or
 | |
| // empty for any files.
 | |
| //
 | |
| // All `guestPath` paths are normalized, specifically removing any leading or
 | |
| // trailing slashes. This means "/", "./" or "." all coerce to empty "".
 | |
| //
 | |
| // Multiple `guestPath` values can be configured, but the last longest match
 | |
| // wins. For example, if "tmp", then "" were added, a request to open
 | |
| // "tmp/foo.txt" use the filesystem associated with "tmp" even though a wider
 | |
| // path, "" (all files), was added later.
 | |
| //
 | |
| // A `guestPath` of "." coerces to the empty string "" because the current
 | |
| // directory is handled by the guest. In other words, the guest resolves ites
 | |
| // current directory prior to requesting files.
 | |
| //
 | |
| // More notes on `guestPath`
 | |
| //   - Working directories are typically tracked in wasm, though possible some
 | |
| //     relative paths are requested. For example, TinyGo may attempt to resolve
 | |
| //     a path "../.." in unit tests.
 | |
| //   - Zig uses the first path name it sees as the initial working directory of
 | |
| //     the process.
 | |
| //
 | |
| // # Scope
 | |
| //
 | |
| // Configuration here is module instance scoped. This means you can use the
 | |
| // same configuration for multiple calls to Runtime.InstantiateModule. Each
 | |
| // module will have a different file descriptor table. Any errors accessing
 | |
| // resources allowed here are deferred to instantiation time of each module.
 | |
| //
 | |
| // Any host resources present at the time of configuration, but deleted before
 | |
| // Runtime.InstantiateModule will trap/panic when the guest wasm initializes or
 | |
| // calls functions like `fd_read`.
 | |
| //
 | |
| // # Windows
 | |
| //
 | |
| // While wazero supports Windows as a platform, all known compilers use POSIX
 | |
| // conventions at runtime. For example, even when running on Windows, paths
 | |
| // used by wasm are separated by forward slash (/), not backslash (\).
 | |
| //
 | |
| // # Notes
 | |
| //
 | |
| //   - This is an interface for decoupling, not third-party implementations.
 | |
| //     All implementations are in wazero.
 | |
| //   - FSConfig is immutable. Each WithXXX function returns a new instance
 | |
| //     including the corresponding change.
 | |
| //   - RATIONALE.md includes design background and relationship to WebAssembly
 | |
| //     System Interfaces (WASI).
 | |
| type FSConfig interface {
 | |
| 	// WithDirMount assigns a directory at `dir` to any paths beginning at
 | |
| 	// `guestPath`.
 | |
| 	//
 | |
| 	// For example, `dirPath` as / (or c:\ in Windows), makes the entire host
 | |
| 	// volume writeable to the path on the guest. The `guestPath` is always a
 | |
| 	// POSIX style path, slash (/) delimited, even if run on Windows.
 | |
| 	//
 | |
| 	// If the same `guestPath` was assigned before, this overrides its value,
 | |
| 	// retaining the original precedence. See the documentation of FSConfig for
 | |
| 	// more details on `guestPath`.
 | |
| 	//
 | |
| 	// # Isolation
 | |
| 	//
 | |
| 	// The guest will have full access to this directory including escaping it
 | |
| 	// via relative path lookups like "../../". Full access includes operations
 | |
| 	// such as creating or deleting files, limited to any host level access
 | |
| 	// controls.
 | |
| 	//
 | |
| 	// # os.DirFS
 | |
| 	//
 | |
| 	// This configuration optimizes for WASI compatibility which is sometimes
 | |
| 	// at odds with the behavior of os.DirFS. Hence, this will not behave
 | |
| 	// exactly the same as os.DirFS. See /RATIONALE.md for more.
 | |
| 	WithDirMount(dir, guestPath string) FSConfig
 | |
| 
 | |
| 	// WithReadOnlyDirMount assigns a directory at `dir` to any paths
 | |
| 	// beginning at `guestPath`.
 | |
| 	//
 | |
| 	// This is the same as WithDirMount except only read operations are
 | |
| 	// permitted. However, escaping the directory via relative path lookups
 | |
| 	// like "../../" is still allowed.
 | |
| 	WithReadOnlyDirMount(dir, guestPath string) FSConfig
 | |
| 
 | |
| 	// WithFSMount assigns a fs.FS file system for any paths beginning at
 | |
| 	// `guestPath`.
 | |
| 	//
 | |
| 	// If the same `guestPath` was assigned before, this overrides its value,
 | |
| 	// retaining the original precedence. See the documentation of FSConfig for
 | |
| 	// more details on `guestPath`.
 | |
| 	//
 | |
| 	// # Isolation
 | |
| 	//
 | |
| 	// fs.FS does not restrict the ability to overwrite returned files via
 | |
| 	// io.Writer. Moreover, os.DirFS documentation includes important notes
 | |
| 	// about isolation, which also applies to fs.Sub. As of Go 1.19, the
 | |
| 	// built-in file-systems are not jailed (chroot). See
 | |
| 	// https://github.com/golang/go/issues/42322
 | |
| 	//
 | |
| 	// # os.DirFS
 | |
| 	//
 | |
| 	// Due to limited control and functionality available in os.DirFS, we
 | |
| 	// advise using WithDirMount instead. There will be behavior differences
 | |
| 	// between os.DirFS and WithDirMount, as the latter biases towards what's
 | |
| 	// expected from WASI implementations.
 | |
| 	//
 | |
| 	// # Custom fs.FileInfo
 | |
| 	//
 | |
| 	// The underlying implementation supports data not usually in fs.FileInfo
 | |
| 	// when `info.Sys` returns *sys.Stat_t. For example, a custom fs.FS can use
 | |
| 	// this approach to generate or mask sys.Inode data. Such a filesystem
 | |
| 	// needs to decorate any functions that can return fs.FileInfo:
 | |
| 	//
 | |
| 	//   - `Stat` as defined on `fs.File` (always)
 | |
| 	//   - `Readdir` as defined on `os.File` (if defined)
 | |
| 	//
 | |
| 	// See sys.NewStat_t for examples.
 | |
| 	WithFSMount(fs fs.FS, guestPath string) FSConfig
 | |
| }
 | |
| 
 | |
| type fsConfig struct {
 | |
| 	// fs are the currently configured filesystems.
 | |
| 	fs []experimentalsys.FS
 | |
| 	// guestPaths are the user-supplied names of the filesystems, retained for
 | |
| 	// error messages and fmt.Stringer.
 | |
| 	guestPaths []string
 | |
| 	// guestPathToFS are the normalized paths to the currently configured
 | |
| 	// filesystems, used for de-duplicating.
 | |
| 	guestPathToFS map[string]int
 | |
| }
 | |
| 
 | |
| // NewFSConfig returns a FSConfig that can be used for configuring module instantiation.
 | |
| func NewFSConfig() FSConfig {
 | |
| 	return &fsConfig{guestPathToFS: map[string]int{}}
 | |
| }
 | |
| 
 | |
| // clone makes a deep copy of this module config.
 | |
| func (c *fsConfig) clone() *fsConfig {
 | |
| 	ret := *c // copy except slice and maps which share a ref
 | |
| 	ret.fs = make([]experimentalsys.FS, 0, len(c.fs))
 | |
| 	ret.fs = append(ret.fs, c.fs...)
 | |
| 	ret.guestPaths = make([]string, 0, len(c.guestPaths))
 | |
| 	ret.guestPaths = append(ret.guestPaths, c.guestPaths...)
 | |
| 	ret.guestPathToFS = make(map[string]int, len(c.guestPathToFS))
 | |
| 	for key, value := range c.guestPathToFS {
 | |
| 		ret.guestPathToFS[key] = value
 | |
| 	}
 | |
| 	return &ret
 | |
| }
 | |
| 
 | |
| // WithDirMount implements FSConfig.WithDirMount
 | |
| func (c *fsConfig) WithDirMount(dir, guestPath string) FSConfig {
 | |
| 	return c.WithSysFSMount(sysfs.DirFS(dir), guestPath)
 | |
| }
 | |
| 
 | |
| // WithReadOnlyDirMount implements FSConfig.WithReadOnlyDirMount
 | |
| func (c *fsConfig) WithReadOnlyDirMount(dir, guestPath string) FSConfig {
 | |
| 	return c.WithSysFSMount(&sysfs.ReadFS{FS: sysfs.DirFS(dir)}, guestPath)
 | |
| }
 | |
| 
 | |
| // WithFSMount implements FSConfig.WithFSMount
 | |
| func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig {
 | |
| 	var adapted experimentalsys.FS
 | |
| 	if fs != nil {
 | |
| 		adapted = &sysfs.AdaptFS{FS: fs}
 | |
| 	}
 | |
| 	return c.WithSysFSMount(adapted, guestPath)
 | |
| }
 | |
| 
 | |
| // WithSysFSMount implements sysfs.FSConfig
 | |
| func (c *fsConfig) WithSysFSMount(fs experimentalsys.FS, guestPath string) FSConfig {
 | |
| 	if _, ok := fs.(experimentalsys.UnimplementedFS); ok {
 | |
| 		return c // don't add fake paths.
 | |
| 	}
 | |
| 	cleaned := sys.StripPrefixesAndTrailingSlash(guestPath)
 | |
| 	ret := c.clone()
 | |
| 	if i, ok := ret.guestPathToFS[cleaned]; ok {
 | |
| 		ret.fs[i] = fs
 | |
| 		ret.guestPaths[i] = guestPath
 | |
| 	} else if fs != nil {
 | |
| 		ret.guestPathToFS[cleaned] = len(ret.fs)
 | |
| 		ret.fs = append(ret.fs, fs)
 | |
| 		ret.guestPaths = append(ret.guestPaths, guestPath)
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| // preopens returns the possible nil index-correlated preopened filesystems
 | |
| // with guest paths.
 | |
| func (c *fsConfig) preopens() ([]experimentalsys.FS, []string) {
 | |
| 	preopenCount := len(c.fs)
 | |
| 	if preopenCount == 0 {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	fs := make([]experimentalsys.FS, len(c.fs))
 | |
| 	copy(fs, c.fs)
 | |
| 	guestPaths := make([]string, len(c.guestPaths))
 | |
| 	copy(guestPaths, c.guestPaths)
 | |
| 	return fs, guestPaths
 | |
| }
 |