mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
520
vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go
generated
vendored
Normal file
520
vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go
generated
vendored
Normal file
@ -0,0 +1,520 @@
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
|
||||
// Return constant stat, which has fake times, but keep the underlying
|
||||
// file mode. Fake times are needed to pass wasi-testsuite.
|
||||
// https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19
|
||||
var mode fs.FileMode
|
||||
if st, err := f.Stat(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
mode = st.Mode()
|
||||
}
|
||||
var flag experimentalsys.Oflag
|
||||
if stdin {
|
||||
flag = experimentalsys.O_RDONLY
|
||||
} else {
|
||||
flag = experimentalsys.O_WRONLY
|
||||
}
|
||||
var file fsapi.File
|
||||
if of, ok := f.(*os.File); ok {
|
||||
// This is ok because functions that need path aren't used by stdioFile
|
||||
file = newOsFile("", flag, 0, of)
|
||||
} else {
|
||||
file = &fsFile{file: f}
|
||||
}
|
||||
return &stdioFile{File: file, st: sys.Stat_t{Mode: mode, Nlink: 1}}, nil
|
||||
}
|
||||
|
||||
func OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (*os.File, experimentalsys.Errno) {
|
||||
if flag&experimentalsys.O_DIRECTORY != 0 && flag&(experimentalsys.O_WRONLY|experimentalsys.O_RDWR) != 0 {
|
||||
return nil, experimentalsys.EISDIR // invalid to open a directory writeable
|
||||
}
|
||||
return openFile(path, flag, perm)
|
||||
}
|
||||
|
||||
func OpenOSFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
|
||||
f, errno := OpenFile(path, flag, perm)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
return newOsFile(path, flag, perm, f), 0
|
||||
}
|
||||
|
||||
func OpenFSFile(fs fs.FS, path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
|
||||
if flag&experimentalsys.O_DIRECTORY != 0 && flag&(experimentalsys.O_WRONLY|experimentalsys.O_RDWR) != 0 {
|
||||
return nil, experimentalsys.EISDIR // invalid to open a directory writeable
|
||||
}
|
||||
f, err := fs.Open(path)
|
||||
if errno := experimentalsys.UnwrapOSError(err); errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
// Don't return an os.File because the path is not absolute. osFile needs
|
||||
// the path to be real and certain FS.File impls are subrooted.
|
||||
return &fsFile{fs: fs, name: path, file: f}, 0
|
||||
}
|
||||
|
||||
type stdioFile struct {
|
||||
fsapi.File
|
||||
st sys.Stat_t
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (f *stdioFile) SetAppend(bool) experimentalsys.Errno {
|
||||
// Ignore for stdio.
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsAppend implements File.SetAppend
|
||||
func (f *stdioFile) IsAppend() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Stat implements File.Stat
|
||||
func (f *stdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
return f.st, 0
|
||||
}
|
||||
|
||||
// Close implements File.Close
|
||||
func (f *stdioFile) Close() experimentalsys.Errno {
|
||||
return 0
|
||||
}
|
||||
|
||||
// fsFile is used for wrapped fs.File, like os.Stdin or any fs.File
|
||||
// implementation. Notably, this does not have access to the full file path.
|
||||
// so certain operations can't be supported, such as inode lookups on Windows.
|
||||
type fsFile struct {
|
||||
experimentalsys.UnimplementedFile
|
||||
|
||||
// fs is the file-system that opened the file, or nil when wrapped for
|
||||
// pre-opens like stdio.
|
||||
fs fs.FS
|
||||
|
||||
// name is what was used in fs for Open, so it may not be the actual path.
|
||||
name string
|
||||
|
||||
// file is always set, possibly an os.File like os.Stdin.
|
||||
file fs.File
|
||||
|
||||
// reopenDir is true if reopen should be called before Readdir. This flag
|
||||
// is deferred until Readdir to prevent redundant rewinds. This could
|
||||
// happen if Seek(0) was called twice, or if in Windows, Seek(0) was called
|
||||
// before Readdir.
|
||||
reopenDir bool
|
||||
|
||||
// closed is true when closed was called. This ensures proper sys.EBADF
|
||||
closed bool
|
||||
|
||||
// cachedStat includes fields that won't change while a file is open.
|
||||
cachedSt *cachedStat
|
||||
}
|
||||
|
||||
type cachedStat struct {
|
||||
// dev is the same as sys.Stat_t Dev.
|
||||
dev uint64
|
||||
|
||||
// dev is the same as sys.Stat_t Ino.
|
||||
ino sys.Inode
|
||||
|
||||
// isDir is sys.Stat_t Mode masked with fs.ModeDir
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// cachedStat returns the cacheable parts of sys.Stat_t or an error if they
|
||||
// couldn't be retrieved.
|
||||
func (f *fsFile) cachedStat() (dev uint64, ino sys.Inode, isDir bool, errno experimentalsys.Errno) {
|
||||
if f.cachedSt == nil {
|
||||
if _, errno = f.Stat(); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
return f.cachedSt.dev, f.cachedSt.ino, f.cachedSt.isDir, 0
|
||||
}
|
||||
|
||||
// Dev implements the same method as documented on sys.File
|
||||
func (f *fsFile) Dev() (uint64, experimentalsys.Errno) {
|
||||
dev, _, _, errno := f.cachedStat()
|
||||
return dev, errno
|
||||
}
|
||||
|
||||
// Ino implements the same method as documented on sys.File
|
||||
func (f *fsFile) Ino() (sys.Inode, experimentalsys.Errno) {
|
||||
_, ino, _, errno := f.cachedStat()
|
||||
return ino, errno
|
||||
}
|
||||
|
||||
// IsDir implements the same method as documented on sys.File
|
||||
func (f *fsFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
_, _, isDir, errno := f.cachedStat()
|
||||
return isDir, errno
|
||||
}
|
||||
|
||||
// IsAppend implements the same method as documented on sys.File
|
||||
func (f *fsFile) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements the same method as documented on sys.File
|
||||
func (f *fsFile) SetAppend(bool) (errno experimentalsys.Errno) {
|
||||
return fileError(f, f.closed, experimentalsys.ENOSYS)
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on sys.File
|
||||
func (f *fsFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
if f.closed {
|
||||
return sys.Stat_t{}, experimentalsys.EBADF
|
||||
}
|
||||
|
||||
st, errno := statFile(f.file)
|
||||
switch errno {
|
||||
case 0:
|
||||
f.cachedSt = &cachedStat{dev: st.Dev, ino: st.Ino, isDir: st.Mode&fs.ModeDir == fs.ModeDir}
|
||||
case experimentalsys.EIO:
|
||||
errno = experimentalsys.EBADF
|
||||
}
|
||||
return st, errno
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on sys.File
|
||||
func (f *fsFile) Read(buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if n, errno = read(f.file, buf); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pread implements the same method as documented on sys.File
|
||||
func (f *fsFile) Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if ra, ok := f.file.(io.ReaderAt); ok {
|
||||
if n, errno = pread(ra, buf, off); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// See /RATIONALE.md "fd_pread: io.Seeker fallback when io.ReaderAt is not supported"
|
||||
if rs, ok := f.file.(io.ReadSeeker); ok {
|
||||
// Determine the current position in the file, as we need to revert it.
|
||||
currentOffset, err := rs.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err))
|
||||
}
|
||||
|
||||
// Put the read position back when complete.
|
||||
defer func() { _, _ = rs.Seek(currentOffset, io.SeekStart) }()
|
||||
|
||||
// If the current offset isn't in sync with this reader, move it.
|
||||
if off != currentOffset {
|
||||
if _, err = rs.Seek(off, io.SeekStart); err != nil {
|
||||
return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err))
|
||||
}
|
||||
}
|
||||
|
||||
n, err = rs.Read(buf)
|
||||
if errno = experimentalsys.UnwrapOSError(err); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Seek implements the same method as documented on sys.File
|
||||
func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
|
||||
// If this is a directory, and we're attempting to seek to position zero,
|
||||
// we have to re-open the file to ensure the directory state is reset.
|
||||
var isDir bool
|
||||
if offset == 0 && whence == io.SeekStart {
|
||||
if isDir, errno = f.IsDir(); errno == 0 && isDir {
|
||||
f.reopenDir = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := f.file.(io.Seeker); ok {
|
||||
if newOffset, errno = seek(s, offset, whence); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Readdir implements the same method as documented on sys.File
|
||||
//
|
||||
// Notably, this uses readdirFile or fs.ReadDirFile if available. This does not
|
||||
// return inodes on windows.
|
||||
func (f *fsFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) {
|
||||
// Windows lets you Readdir after close, FS.File also may not implement
|
||||
// close in a meaningful way. read our closed field to return consistent
|
||||
// results.
|
||||
if f.closed {
|
||||
errno = experimentalsys.EBADF
|
||||
return
|
||||
}
|
||||
|
||||
if f.reopenDir { // re-open the directory if needed.
|
||||
f.reopenDir = false
|
||||
if errno = adjustReaddirErr(f, f.closed, f.reopen()); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if of, ok := f.file.(readdirFile); ok {
|
||||
// We can't use f.name here because it is the path up to the sys.FS,
|
||||
// not necessarily the real path. For this reason, Windows may not be
|
||||
// able to populate inodes. However, Darwin and Linux will.
|
||||
if dirents, errno = readdir(of, "", n); errno != 0 {
|
||||
errno = adjustReaddirErr(f, f.closed, errno)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Try with FS.ReadDirFile which is available on api.FS implementations
|
||||
// like embed:FS.
|
||||
if rdf, ok := f.file.(fs.ReadDirFile); ok {
|
||||
entries, e := rdf.ReadDir(n)
|
||||
if errno = adjustReaddirErr(f, f.closed, e); errno != 0 {
|
||||
return
|
||||
}
|
||||
dirents = make([]experimentalsys.Dirent, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
// By default, we don't attempt to read inode data
|
||||
dirents = append(dirents, experimentalsys.Dirent{Name: e.Name(), Type: e.Type()})
|
||||
}
|
||||
} else {
|
||||
errno = experimentalsys.EBADF // not a directory
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on sys.File.
|
||||
func (f *fsFile) Write(buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if w, ok := f.file.(io.Writer); ok {
|
||||
if n, errno = write(w, buf); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pwrite implements the same method as documented on sys.File.
|
||||
func (f *fsFile) Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if wa, ok := f.file.(io.WriterAt); ok {
|
||||
if n, errno = pwrite(wa, buf, off); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close implements the same method as documented on sys.File.
|
||||
func (f *fsFile) Close() experimentalsys.Errno {
|
||||
if f.closed {
|
||||
return 0
|
||||
}
|
||||
f.closed = true
|
||||
return f.close()
|
||||
}
|
||||
|
||||
func (f *fsFile) close() experimentalsys.Errno {
|
||||
return experimentalsys.UnwrapOSError(f.file.Close())
|
||||
}
|
||||
|
||||
// IsNonblock implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) IsNonblock() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetNonblock implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) SetNonblock(bool) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Poll implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) {
|
||||
return false, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// dirError is used for commands that work against a directory, but not a file.
|
||||
func dirError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno {
|
||||
if vErrno := validate(f, isClosed, false, true); vErrno != 0 {
|
||||
return vErrno
|
||||
}
|
||||
return errno
|
||||
}
|
||||
|
||||
// fileError is used for commands that work against a file, but not a directory.
|
||||
func fileError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno {
|
||||
if vErrno := validate(f, isClosed, true, false); vErrno != 0 {
|
||||
return vErrno
|
||||
}
|
||||
return errno
|
||||
}
|
||||
|
||||
// validate is used to making syscalls which will fail.
|
||||
func validate(f experimentalsys.File, isClosed, wantFile, wantDir bool) experimentalsys.Errno {
|
||||
if isClosed {
|
||||
return experimentalsys.EBADF
|
||||
}
|
||||
|
||||
isDir, errno := f.IsDir()
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if wantFile && isDir {
|
||||
return experimentalsys.EISDIR
|
||||
} else if wantDir && !isDir {
|
||||
return experimentalsys.ENOTDIR
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func read(r io.Reader, buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length reads.
|
||||
}
|
||||
|
||||
n, err := r.Read(buf)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func pread(ra io.ReaderAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length reads.
|
||||
}
|
||||
|
||||
n, err := ra.ReadAt(buf, off)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func seek(s io.Seeker, offset int64, whence int) (int64, experimentalsys.Errno) {
|
||||
if uint(whence) > io.SeekEnd {
|
||||
return 0, experimentalsys.EINVAL // negative or exceeds the largest valid whence
|
||||
}
|
||||
|
||||
newOffset, err := s.Seek(offset, whence)
|
||||
return newOffset, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// reopenFile allows re-opening a file for reasons such as applying flags or
|
||||
// directory iteration.
|
||||
type reopenFile func() experimentalsys.Errno
|
||||
|
||||
// compile-time check to ensure fsFile.reopen implements reopenFile.
|
||||
var _ reopenFile = (*fsFile)(nil).reopen
|
||||
|
||||
// reopen implements the same method as documented on reopenFile.
|
||||
func (f *fsFile) reopen() experimentalsys.Errno {
|
||||
_ = f.close()
|
||||
var err error
|
||||
f.file, err = f.fs.Open(f.name)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// readdirFile allows masking the `Readdir` function on os.File.
|
||||
type readdirFile interface {
|
||||
Readdir(n int) ([]fs.FileInfo, error)
|
||||
}
|
||||
|
||||
// readdir uses readdirFile.Readdir, special casing windows when path !="".
|
||||
func readdir(f readdirFile, path string, n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) {
|
||||
fis, e := f.Readdir(n)
|
||||
if errno = experimentalsys.UnwrapOSError(e); errno != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dirents = make([]experimentalsys.Dirent, 0, len(fis))
|
||||
|
||||
// linux/darwin won't have to fan out to lstat, but windows will.
|
||||
var ino sys.Inode
|
||||
for fi := range fis {
|
||||
t := fis[fi]
|
||||
// inoFromFileInfo is more efficient than sys.NewStat_t, as it gets the
|
||||
// inode without allocating an instance and filling other fields.
|
||||
if ino, errno = inoFromFileInfo(path, t); errno != 0 {
|
||||
return
|
||||
}
|
||||
dirents = append(dirents, experimentalsys.Dirent{Name: t.Name(), Ino: ino, Type: t.Mode().Type()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func write(w io.Writer, buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length writes.
|
||||
}
|
||||
|
||||
n, err := w.Write(buf)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length writes.
|
||||
}
|
||||
|
||||
n, err := w.WriteAt(buf, off)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func chtimes(path string, atim, mtim int64) (errno experimentalsys.Errno) { //nolint:unused
|
||||
// When both inputs are omitted, there is nothing to change.
|
||||
if atim == experimentalsys.UTIME_OMIT && mtim == experimentalsys.UTIME_OMIT {
|
||||
return
|
||||
}
|
||||
|
||||
// UTIME_OMIT is expensive until progress is made in Go, as it requires a
|
||||
// stat to read-back the value to re-apply.
|
||||
// - https://github.com/golang/go/issues/32558.
|
||||
// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
|
||||
var st sys.Stat_t
|
||||
if atim == experimentalsys.UTIME_OMIT || mtim == experimentalsys.UTIME_OMIT {
|
||||
if st, errno = stat(path); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var atime, mtime time.Time
|
||||
if atim == experimentalsys.UTIME_OMIT {
|
||||
atime = epochNanosToTime(st.Atim)
|
||||
mtime = epochNanosToTime(mtim)
|
||||
} else if mtim == experimentalsys.UTIME_OMIT {
|
||||
atime = epochNanosToTime(atim)
|
||||
mtime = epochNanosToTime(st.Mtim)
|
||||
} else {
|
||||
atime = epochNanosToTime(atim)
|
||||
mtime = epochNanosToTime(mtim)
|
||||
}
|
||||
return experimentalsys.UnwrapOSError(os.Chtimes(path, atime, mtime))
|
||||
}
|
||||
|
||||
func epochNanosToTime(epochNanos int64) time.Time { //nolint:unused
|
||||
seconds := epochNanos / 1e9
|
||||
nanos := epochNanos % 1e9
|
||||
return time.Unix(seconds, nanos)
|
||||
}
|
Reference in New Issue
Block a user