package vfs import ( "context" "crypto/rand" "io" "reflect" "sync" "time" "github.com/ncruces/go-sqlite3/internal/util" "github.com/ncruces/julianday" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" ) // ExportHostFunctions is an internal API users need not call directly. // // ExportHostFunctions registers the required VFS host functions // with the provided env module. func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { util.ExportFuncII(env, "go_vfs_find", vfsFind) util.ExportFuncIIJ(env, "go_localtime", vfsLocaltime) util.ExportFuncIIII(env, "go_randomness", vfsRandomness) util.ExportFuncIII(env, "go_sleep", vfsSleep) util.ExportFuncIII(env, "go_current_time_64", vfsCurrentTime64) util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname) util.ExportFuncIIII(env, "go_delete", vfsDelete) util.ExportFuncIIIII(env, "go_access", vfsAccess) util.ExportFuncIIIIIII(env, "go_open", vfsOpen) util.ExportFuncII(env, "go_close", vfsClose) util.ExportFuncIIIIJ(env, "go_read", vfsRead) util.ExportFuncIIIIJ(env, "go_write", vfsWrite) util.ExportFuncIIJ(env, "go_truncate", vfsTruncate) util.ExportFuncIII(env, "go_sync", vfsSync) util.ExportFuncIII(env, "go_file_size", vfsFileSize) util.ExportFuncIIII(env, "go_file_control", vfsFileControl) util.ExportFuncII(env, "go_sector_size", vfsSectorSize) util.ExportFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics) util.ExportFuncIII(env, "go_lock", vfsLock) util.ExportFuncIII(env, "go_unlock", vfsUnlock) util.ExportFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock) util.ExportFuncIIIIII(env, "go_shm_map", vfsShmMap) util.ExportFuncIIIII(env, "go_shm_lock", vfsShmLock) util.ExportFuncIII(env, "go_shm_unmap", vfsShmUnmap) util.ExportFuncVI(env, "go_shm_barrier", vfsShmBarrier) return env } func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 { name := util.ReadString(mod, zVfsName, _MAX_NAME) if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) { return 1 } return 0 } func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode { tm := time.Unix(t, 0) var isdst int if tm.IsDST() { isdst = 1 } const size = 32 / 8 // https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html util.WriteUint32(mod, pTm+0*size, uint32(tm.Second())) util.WriteUint32(mod, pTm+1*size, uint32(tm.Minute())) util.WriteUint32(mod, pTm+2*size, uint32(tm.Hour())) util.WriteUint32(mod, pTm+3*size, uint32(tm.Day())) util.WriteUint32(mod, pTm+4*size, uint32(tm.Month()-time.January)) util.WriteUint32(mod, pTm+5*size, uint32(tm.Year()-1900)) util.WriteUint32(mod, pTm+6*size, uint32(tm.Weekday()-time.Sunday)) util.WriteUint32(mod, pTm+7*size, uint32(tm.YearDay()-1)) util.WriteUint32(mod, pTm+8*size, uint32(isdst)) return _OK } func vfsRandomness(ctx context.Context, mod api.Module, pVfs uint32, nByte int32, zByte uint32) uint32 { mem := util.View(mod, zByte, uint64(nByte)) n, _ := rand.Reader.Read(mem) return uint32(n) } func vfsSleep(ctx context.Context, mod api.Module, pVfs uint32, nMicro int32) _ErrorCode { time.Sleep(time.Duration(nMicro) * time.Microsecond) return _OK } func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode { day, nsec := julianday.Date(time.Now()) msec := day*86_400_000 + nsec/1_000_000 util.WriteUint64(mod, piNow, uint64(msec)) return _OK } func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative uint32, nFull int32, zFull uint32) _ErrorCode { vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zRelative, _MAX_PATHNAME) path, err := vfs.FullPathname(path) if len(path) >= int(nFull) { return _CANTOPEN_FULLPATH } util.WriteString(mod, zFull, path) return vfsErrorCode(err, _CANTOPEN_FULLPATH) } func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode { vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zPath, _MAX_PATHNAME) err := vfs.Delete(path, syncDir != 0) return vfsErrorCode(err, _IOERR_DELETE) } func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) _ErrorCode { vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zPath, _MAX_PATHNAME) ok, err := vfs.Access(path, flags) var res uint32 if ok { res = 1 } util.WriteUint32(mod, pResOut, res) return vfsErrorCode(err, _IOERR_ACCESS) } func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode { vfs := vfsGet(mod, pVfs) var path string if zPath != 0 { path = util.ReadString(mod, zPath, _MAX_PATHNAME) } var file File var err error if ffs, ok := vfs.(VFSFilename); ok { name := OpenFilename(ctx, mod, zPath, flags) file, flags, err = ffs.OpenFilename(name, flags) } else { file, flags, err = vfs.Open(path, flags) } if err != nil { return vfsErrorCode(err, _CANTOPEN) } if file, ok := file.(FilePowersafeOverwrite); ok { name := OpenFilename(ctx, mod, zPath, flags) if b, ok := util.ParseBool(name.URIParameter("psow")); ok { file.SetPowersafeOverwrite(b) } } if file, ok := file.(FileSharedMemory); ok && pOutVFS != 0 && file.SharedMemory() != nil { util.WriteUint32(mod, pOutVFS, 1) } if pOutFlags != 0 { util.WriteUint32(mod, pOutFlags, uint32(flags)) } vfsFileRegister(ctx, mod, pFile, file) return _OK } func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode { err := vfsFileClose(ctx, mod, pFile) if err != nil { return vfsErrorCode(err, _IOERR_CLOSE) } return _OK } func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) buf := util.View(mod, zBuf, uint64(iAmt)) n, err := file.ReadAt(buf, iOfst) if n == int(iAmt) { return _OK } if err != io.EOF { return vfsErrorCode(err, _IOERR_READ) } clear(buf[n:]) return _IOERR_SHORT_READ } func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) buf := util.View(mod, zBuf, uint64(iAmt)) _, err := file.WriteAt(buf, iOfst) if err != nil { return vfsErrorCode(err, _IOERR_WRITE) } return _OK } func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Truncate(nByte) return vfsErrorCode(err, _IOERR_TRUNCATE) } func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Sync(flags) return vfsErrorCode(err, _IOERR_FSYNC) } func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) size, err := file.Size() util.WriteUint64(mod, pSize, uint64(size)) return vfsErrorCode(err, _IOERR_SEEK) } func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Lock(eLock) return vfsErrorCode(err, _IOERR_LOCK) } func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Unlock(eLock) return vfsErrorCode(err, _IOERR_UNLOCK) } func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) locked, err := file.CheckReservedLock() var res uint32 if locked { res = 1 } util.WriteUint32(mod, pResOut, res) return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK) } func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) switch op { case _FCNTL_LOCKSTATE: if file, ok := file.(FileLockState); ok { util.WriteUint32(mod, pArg, uint32(file.LockState())) return _OK } case _FCNTL_PERSIST_WAL: if file, ok := file.(FilePersistentWAL); ok { if i := util.ReadUint32(mod, pArg); int32(i) >= 0 { file.SetPersistentWAL(i != 0) } else if file.PersistentWAL() { util.WriteUint32(mod, pArg, 1) } else { util.WriteUint32(mod, pArg, 0) } return _OK } case _FCNTL_POWERSAFE_OVERWRITE: if file, ok := file.(FilePowersafeOverwrite); ok { if i := util.ReadUint32(mod, pArg); int32(i) >= 0 { file.SetPowersafeOverwrite(i != 0) } else if file.PowersafeOverwrite() { util.WriteUint32(mod, pArg, 1) } else { util.WriteUint32(mod, pArg, 0) } return _OK } case _FCNTL_CHUNK_SIZE: if file, ok := file.(FileChunkSize); ok { size := util.ReadUint32(mod, pArg) file.ChunkSize(int(size)) return _OK } case _FCNTL_SIZE_HINT: if file, ok := file.(FileSizeHint); ok { size := util.ReadUint64(mod, pArg) err := file.SizeHint(int64(size)) return vfsErrorCode(err, _IOERR_TRUNCATE) } case _FCNTL_HAS_MOVED: if file, ok := file.(FileHasMoved); ok { moved, err := file.HasMoved() var res uint32 if moved { res = 1 } util.WriteUint32(mod, pArg, res) return vfsErrorCode(err, _IOERR_FSTAT) } case _FCNTL_OVERWRITE: if file, ok := file.(FileOverwrite); ok { err := file.Overwrite() return vfsErrorCode(err, _IOERR) } case _FCNTL_COMMIT_PHASETWO: if file, ok := file.(FileCommitPhaseTwo); ok { err := file.CommitPhaseTwo() return vfsErrorCode(err, _IOERR) } case _FCNTL_BEGIN_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.BeginAtomicWrite() return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC) } case _FCNTL_COMMIT_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.CommitAtomicWrite() return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC) } case _FCNTL_ROLLBACK_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.RollbackAtomicWrite() return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC) } case _FCNTL_CKPT_DONE: if file, ok := file.(FileCheckpoint); ok { err := file.CheckpointDone() return vfsErrorCode(err, _IOERR) } case _FCNTL_CKPT_START: if file, ok := file.(FileCheckpoint); ok { err := file.CheckpointStart() return vfsErrorCode(err, _IOERR) } case _FCNTL_PRAGMA: if file, ok := file.(FilePragma); ok { ptr := util.ReadUint32(mod, pArg+1*ptrlen) name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH) var value string if ptr := util.ReadUint32(mod, pArg+2*ptrlen); ptr != 0 { value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH) } out, err := file.Pragma(name, value) ret := vfsErrorCode(err, _ERROR) if ret == _ERROR { out = err.Error() } if out != "" { fn := mod.ExportedFunction("malloc") stack := [...]uint64{uint64(len(out) + 1)} if err := fn.CallWithStack(ctx, stack[:]); err != nil { panic(err) } util.WriteUint32(mod, pArg, uint32(stack[0])) util.WriteString(mod, uint32(stack[0]), out) } return ret } } // Consider also implementing these opcodes (in use by SQLite): // _FCNTL_BUSYHANDLER // _FCNTL_LAST_ERRNO // _FCNTL_SYNC return _NOTFOUND } func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 { file := vfsFileGet(ctx, mod, pFile).(File) return uint32(file.SectorSize()) } func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) DeviceCharacteristic { file := vfsFileGet(ctx, mod, pFile).(File) return file.DeviceCharacteristics() } var shmBarrier sync.Mutex func vfsShmBarrier(ctx context.Context, mod api.Module, pFile uint32) { shmBarrier.Lock() defer shmBarrier.Unlock() } func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szRegion int32, bExtend, pp uint32) _ErrorCode { shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() p, rc := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0) util.WriteUint32(mod, pp, p) return rc } func vfsShmLock(ctx context.Context, mod api.Module, pFile uint32, offset, n int32, flags _ShmFlag) _ErrorCode { shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() return shm.shmLock(offset, n, flags) } func vfsShmUnmap(ctx context.Context, mod api.Module, pFile, bDelete uint32) _ErrorCode { shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() shm.shmUnmap(bDelete != 0) return _OK } func vfsGet(mod api.Module, pVfs uint32) VFS { var name string if pVfs != 0 { const zNameOffset = 16 name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_NAME) } if vfs := Find(name); vfs != nil { return vfs } panic(util.NoVFSErr + util.ErrorString(name)) } func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file File) { const fileHandleOffset = 4 id := util.AddHandle(ctx, file) util.WriteUint32(mod, pFile+fileHandleOffset, id) } func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) any { const fileHandleOffset = 4 id := util.ReadUint32(mod, pFile+fileHandleOffset) return util.GetHandle(ctx, id) } func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error { const fileHandleOffset = 4 id := util.ReadUint32(mod, pFile+fileHandleOffset) return util.DelHandle(ctx, id) } func vfsErrorCode(err error, def _ErrorCode) _ErrorCode { if err == nil { return _OK } switch v := reflect.ValueOf(err); v.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32: return _ErrorCode(v.Uint()) } return def }