1package vfs
2
3import (
4 "context"
5 "crypto/rand"
6 "io"
7 "reflect"
8 "strings"
9 "time"
10
11 "github.com/tetratelabs/wazero"
12 "github.com/tetratelabs/wazero/api"
13
14 "github.com/ncruces/go-sqlite3/internal/util"
15 "github.com/ncruces/go-sqlite3/util/sql3util"
16 "github.com/ncruces/julianday"
17)
18
19// ExportHostFunctions is an internal API users need not call directly.
20//
21// ExportHostFunctions registers the required VFS host functions
22// with the provided env module.
23func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
24 util.ExportFuncII(env, "go_vfs_find", vfsFind)
25 util.ExportFuncIIJ(env, "go_localtime", vfsLocaltime)
26 util.ExportFuncIIII(env, "go_randomness", vfsRandomness)
27 util.ExportFuncIII(env, "go_sleep", vfsSleep)
28 util.ExportFuncIII(env, "go_current_time_64", vfsCurrentTime64)
29 util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname)
30 util.ExportFuncIIII(env, "go_delete", vfsDelete)
31 util.ExportFuncIIIII(env, "go_access", vfsAccess)
32 util.ExportFuncIIIIIII(env, "go_open", vfsOpen)
33 util.ExportFuncII(env, "go_close", vfsClose)
34 util.ExportFuncIIIIJ(env, "go_read", vfsRead)
35 util.ExportFuncIIIIJ(env, "go_write", vfsWrite)
36 util.ExportFuncIIJ(env, "go_truncate", vfsTruncate)
37 util.ExportFuncIII(env, "go_sync", vfsSync)
38 util.ExportFuncIII(env, "go_file_size", vfsFileSize)
39 util.ExportFuncIIII(env, "go_file_control", vfsFileControl)
40 util.ExportFuncII(env, "go_sector_size", vfsSectorSize)
41 util.ExportFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics)
42 util.ExportFuncIII(env, "go_lock", vfsLock)
43 util.ExportFuncIII(env, "go_unlock", vfsUnlock)
44 util.ExportFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock)
45 util.ExportFuncIIIIII(env, "go_shm_map", vfsShmMap)
46 util.ExportFuncIIIII(env, "go_shm_lock", vfsShmLock)
47 util.ExportFuncIII(env, "go_shm_unmap", vfsShmUnmap)
48 util.ExportFuncVI(env, "go_shm_barrier", vfsShmBarrier)
49 return env
50}
51
52func vfsFind(ctx context.Context, mod api.Module, zVfsName ptr_t) uint32 {
53 name := util.ReadString(mod, zVfsName, _MAX_NAME)
54 if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
55 return 1
56 }
57 return 0
58}
59
60func vfsLocaltime(ctx context.Context, mod api.Module, pTm ptr_t, t int64) _ErrorCode {
61 const size = 32 / 8
62 tm := time.Unix(t, 0)
63 // https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
64 util.Write32(mod, pTm+0*size, int32(tm.Second()))
65 util.Write32(mod, pTm+1*size, int32(tm.Minute()))
66 util.Write32(mod, pTm+2*size, int32(tm.Hour()))
67 util.Write32(mod, pTm+3*size, int32(tm.Day()))
68 util.Write32(mod, pTm+4*size, int32(tm.Month()-time.January))
69 util.Write32(mod, pTm+5*size, int32(tm.Year()-1900))
70 util.Write32(mod, pTm+6*size, int32(tm.Weekday()-time.Sunday))
71 util.Write32(mod, pTm+7*size, int32(tm.YearDay()-1))
72 util.WriteBool(mod, pTm+8*size, tm.IsDST())
73 return _OK
74}
75
76func vfsRandomness(ctx context.Context, mod api.Module, pVfs ptr_t, nByte int32, zByte ptr_t) uint32 {
77 mem := util.View(mod, zByte, int64(nByte))
78 n, _ := rand.Reader.Read(mem)
79 return uint32(n)
80}
81
82func vfsSleep(ctx context.Context, mod api.Module, pVfs ptr_t, nMicro int32) _ErrorCode {
83 time.Sleep(time.Duration(nMicro) * time.Microsecond)
84 return _OK
85}
86
87func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow ptr_t) _ErrorCode {
88 day, nsec := julianday.Date(time.Now())
89 msec := day*86_400_000 + nsec/1_000_000
90 util.Write64(mod, piNow, msec)
91 return _OK
92}
93
94func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative ptr_t, nFull int32, zFull ptr_t) _ErrorCode {
95 vfs := vfsGet(mod, pVfs)
96 path := util.ReadString(mod, zRelative, _MAX_PATHNAME)
97
98 path, err := vfs.FullPathname(path)
99
100 if len(path) >= int(nFull) {
101 return _CANTOPEN_FULLPATH
102 }
103 util.WriteString(mod, zFull, path)
104
105 return vfsErrorCode(err, _CANTOPEN_FULLPATH)
106}
107
108func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, syncDir int32) _ErrorCode {
109 vfs := vfsGet(mod, pVfs)
110 path := util.ReadString(mod, zPath, _MAX_PATHNAME)
111
112 err := vfs.Delete(path, syncDir != 0)
113 return vfsErrorCode(err, _IOERR_DELETE)
114}
115
116func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, flags AccessFlag, pResOut ptr_t) _ErrorCode {
117 vfs := vfsGet(mod, pVfs)
118 path := util.ReadString(mod, zPath, _MAX_PATHNAME)
119
120 ok, err := vfs.Access(path, flags)
121 util.WriteBool(mod, pResOut, ok)
122 return vfsErrorCode(err, _IOERR_ACCESS)
123}
124
125func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flags OpenFlag, pOutFlags, pOutVFS ptr_t) _ErrorCode {
126 vfs := vfsGet(mod, pVfs)
127 name := GetFilename(ctx, mod, zPath, flags)
128
129 var file File
130 var err error
131 if ffs, ok := vfs.(VFSFilename); ok {
132 file, flags, err = ffs.OpenFilename(name, flags)
133 } else {
134 file, flags, err = vfs.Open(name.String(), flags)
135 }
136 if err != nil {
137 return vfsErrorCode(err, _CANTOPEN)
138 }
139
140 if file, ok := file.(FilePowersafeOverwrite); ok {
141 if b, ok := sql3util.ParseBool(name.URIParameter("psow")); ok {
142 file.SetPowersafeOverwrite(b)
143 }
144 }
145 if file, ok := file.(FileSharedMemory); ok && pOutVFS != 0 {
146 util.WriteBool(mod, pOutVFS, file.SharedMemory() != nil)
147 }
148 if pOutFlags != 0 {
149 util.Write32(mod, pOutFlags, flags)
150 }
151 file = cksmWrapFile(name, flags, file)
152 vfsFileRegister(ctx, mod, pFile, file)
153 return _OK
154}
155
156func vfsClose(ctx context.Context, mod api.Module, pFile ptr_t) _ErrorCode {
157 err := vfsFileClose(ctx, mod, pFile)
158 return vfsErrorCode(err, _IOERR_CLOSE)
159}
160
161func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32, iOfst int64) _ErrorCode {
162 file := vfsFileGet(ctx, mod, pFile).(File)
163 buf := util.View(mod, zBuf, int64(iAmt))
164
165 n, err := file.ReadAt(buf, iOfst)
166 if n == int(iAmt) {
167 return _OK
168 }
169 if err != io.EOF {
170 return vfsErrorCode(err, _IOERR_READ)
171 }
172 clear(buf[n:])
173 return _IOERR_SHORT_READ
174}
175
176func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32, iOfst int64) _ErrorCode {
177 file := vfsFileGet(ctx, mod, pFile).(File)
178 buf := util.View(mod, zBuf, int64(iAmt))
179
180 _, err := file.WriteAt(buf, iOfst)
181 return vfsErrorCode(err, _IOERR_WRITE)
182}
183
184func vfsTruncate(ctx context.Context, mod api.Module, pFile ptr_t, nByte int64) _ErrorCode {
185 file := vfsFileGet(ctx, mod, pFile).(File)
186 err := file.Truncate(nByte)
187 return vfsErrorCode(err, _IOERR_TRUNCATE)
188}
189
190func vfsSync(ctx context.Context, mod api.Module, pFile ptr_t, flags SyncFlag) _ErrorCode {
191 file := vfsFileGet(ctx, mod, pFile).(File)
192 err := file.Sync(flags)
193 return vfsErrorCode(err, _IOERR_FSYNC)
194}
195
196func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize ptr_t) _ErrorCode {
197 file := vfsFileGet(ctx, mod, pFile).(File)
198 size, err := file.Size()
199 util.Write64(mod, pSize, size)
200 return vfsErrorCode(err, _IOERR_SEEK)
201}
202
203func vfsLock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode {
204 file := vfsFileGet(ctx, mod, pFile).(File)
205 err := file.Lock(eLock)
206 return vfsErrorCode(err, _IOERR_LOCK)
207}
208
209func vfsUnlock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode {
210 file := vfsFileGet(ctx, mod, pFile).(File)
211 err := file.Unlock(eLock)
212 return vfsErrorCode(err, _IOERR_UNLOCK)
213}
214
215func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ptr_t) _ErrorCode {
216 file := vfsFileGet(ctx, mod, pFile).(File)
217 locked, err := file.CheckReservedLock()
218 util.WriteBool(mod, pResOut, locked)
219 return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
220}
221
222func vfsFileControl(ctx context.Context, mod api.Module, pFile ptr_t, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
223 file := vfsFileGet(ctx, mod, pFile).(File)
224 if file, ok := file.(fileControl); ok {
225 return file.fileControl(ctx, mod, op, pArg)
226 }
227 return vfsFileControlImpl(ctx, mod, file, op, pArg)
228}
229
230func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _FcntlOpcode, pArg ptr_t) _ErrorCode {
231 switch op {
232 case _FCNTL_LOCKSTATE:
233 if file, ok := file.(FileLockState); ok {
234 if lk := file.LockState(); lk <= LOCK_EXCLUSIVE {
235 util.Write32(mod, pArg, lk)
236 return _OK
237 }
238 }
239
240 case _FCNTL_PERSIST_WAL:
241 if file, ok := file.(FilePersistWAL); ok {
242 if i := util.Read32[int32](mod, pArg); i < 0 {
243 util.WriteBool(mod, pArg, file.PersistWAL())
244 } else {
245 file.SetPersistWAL(i != 0)
246 }
247 return _OK
248 }
249
250 case _FCNTL_POWERSAFE_OVERWRITE:
251 if file, ok := file.(FilePowersafeOverwrite); ok {
252 if i := util.Read32[int32](mod, pArg); i < 0 {
253 util.WriteBool(mod, pArg, file.PowersafeOverwrite())
254 } else {
255 file.SetPowersafeOverwrite(i != 0)
256 }
257 return _OK
258 }
259
260 case _FCNTL_CHUNK_SIZE:
261 if file, ok := file.(FileChunkSize); ok {
262 size := util.Read32[int32](mod, pArg)
263 file.ChunkSize(int(size))
264 return _OK
265 }
266
267 case _FCNTL_SIZE_HINT:
268 if file, ok := file.(FileSizeHint); ok {
269 size := util.Read64[int64](mod, pArg)
270 err := file.SizeHint(size)
271 return vfsErrorCode(err, _IOERR_TRUNCATE)
272 }
273
274 case _FCNTL_HAS_MOVED:
275 if file, ok := file.(FileHasMoved); ok {
276 moved, err := file.HasMoved()
277 util.WriteBool(mod, pArg, moved)
278 return vfsErrorCode(err, _IOERR_FSTAT)
279 }
280
281 case _FCNTL_OVERWRITE:
282 if file, ok := file.(FileOverwrite); ok {
283 err := file.Overwrite()
284 return vfsErrorCode(err, _IOERR)
285 }
286
287 case _FCNTL_SYNC:
288 if file, ok := file.(FileSync); ok {
289 var name string
290 if pArg != 0 {
291 name = util.ReadString(mod, pArg, _MAX_PATHNAME)
292 }
293 err := file.SyncSuper(name)
294 return vfsErrorCode(err, _IOERR)
295 }
296
297 case _FCNTL_COMMIT_PHASETWO:
298 if file, ok := file.(FileCommitPhaseTwo); ok {
299 err := file.CommitPhaseTwo()
300 return vfsErrorCode(err, _IOERR)
301 }
302
303 case _FCNTL_BEGIN_ATOMIC_WRITE:
304 if file, ok := file.(FileBatchAtomicWrite); ok {
305 err := file.BeginAtomicWrite()
306 return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC)
307 }
308 case _FCNTL_COMMIT_ATOMIC_WRITE:
309 if file, ok := file.(FileBatchAtomicWrite); ok {
310 err := file.CommitAtomicWrite()
311 return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC)
312 }
313 case _FCNTL_ROLLBACK_ATOMIC_WRITE:
314 if file, ok := file.(FileBatchAtomicWrite); ok {
315 err := file.RollbackAtomicWrite()
316 return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC)
317 }
318
319 case _FCNTL_CKPT_START:
320 if file, ok := file.(FileCheckpoint); ok {
321 file.CheckpointStart()
322 return _OK
323 }
324 case _FCNTL_CKPT_DONE:
325 if file, ok := file.(FileCheckpoint); ok {
326 file.CheckpointDone()
327 return _OK
328 }
329
330 case _FCNTL_PRAGMA:
331 if file, ok := file.(FilePragma); ok {
332 ptr := util.Read32[ptr_t](mod, pArg+1*ptrlen)
333 name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
334 var value string
335 if ptr := util.Read32[ptr_t](mod, pArg+2*ptrlen); ptr != 0 {
336 value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
337 }
338
339 out, err := file.Pragma(strings.ToLower(name), value)
340
341 ret := vfsErrorCode(err, _ERROR)
342 if ret == _ERROR {
343 out = err.Error()
344 }
345 if out != "" {
346 fn := mod.ExportedFunction("sqlite3_malloc64")
347 stack := [...]stk_t{stk_t(len(out) + 1)}
348 if err := fn.CallWithStack(ctx, stack[:]); err != nil {
349 panic(err)
350 }
351 util.Write32(mod, pArg, ptr_t(stack[0]))
352 util.WriteString(mod, ptr_t(stack[0]), out)
353 }
354 return ret
355 }
356
357 case _FCNTL_BUSYHANDLER:
358 if file, ok := file.(FileBusyHandler); ok {
359 arg := util.Read64[stk_t](mod, pArg)
360 fn := mod.ExportedFunction("sqlite3_invoke_busy_handler_go")
361 file.BusyHandler(func() bool {
362 stack := [...]stk_t{arg}
363 if err := fn.CallWithStack(ctx, stack[:]); err != nil {
364 panic(err)
365 }
366 return uint32(stack[0]) != 0
367 })
368 return _OK
369 }
370
371 case _FCNTL_LOCK_TIMEOUT:
372 if file, ok := file.(FileSharedMemory); ok {
373 if shm, ok := file.SharedMemory().(blockingSharedMemory); ok {
374 shm.shmEnableBlocking(util.ReadBool(mod, pArg))
375 return _OK
376 }
377 }
378
379 case _FCNTL_PDB:
380 if file, ok := file.(filePDB); ok {
381 file.SetDB(ctx.Value(util.ConnKey{}))
382 return _OK
383 }
384 }
385
386 return _NOTFOUND
387}
388
389func vfsSectorSize(ctx context.Context, mod api.Module, pFile ptr_t) uint32 {
390 file := vfsFileGet(ctx, mod, pFile).(File)
391 return uint32(file.SectorSize())
392}
393
394func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile ptr_t) DeviceCharacteristic {
395 file := vfsFileGet(ctx, mod, pFile).(File)
396 return file.DeviceCharacteristics()
397}
398
399func vfsShmBarrier(ctx context.Context, mod api.Module, pFile ptr_t) {
400 shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
401 shm.shmBarrier()
402}
403
404func vfsShmMap(ctx context.Context, mod api.Module, pFile ptr_t, iRegion, szRegion, bExtend int32, pp ptr_t) _ErrorCode {
405 shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
406 p, rc := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0)
407 util.Write32(mod, pp, p)
408 return rc
409}
410
411func vfsShmLock(ctx context.Context, mod api.Module, pFile ptr_t, offset, n int32, flags _ShmFlag) _ErrorCode {
412 shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
413 return shm.shmLock(offset, n, flags)
414}
415
416func vfsShmUnmap(ctx context.Context, mod api.Module, pFile ptr_t, bDelete int32) _ErrorCode {
417 shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
418 shm.shmUnmap(bDelete != 0)
419 return _OK
420}
421
422func vfsGet(mod api.Module, pVfs ptr_t) VFS {
423 var name string
424 if pVfs != 0 {
425 const zNameOffset = 16
426 ptr := util.Read32[ptr_t](mod, pVfs+zNameOffset)
427 name = util.ReadString(mod, ptr, _MAX_NAME)
428 }
429 if vfs := Find(name); vfs != nil {
430 return vfs
431 }
432 panic(util.NoVFSErr + util.ErrorString(name))
433}
434
435func vfsFileRegister(ctx context.Context, mod api.Module, pFile ptr_t, file File) {
436 const fileHandleOffset = 4
437 id := util.AddHandle(ctx, file)
438 util.Write32(mod, pFile+fileHandleOffset, id)
439}
440
441func vfsFileGet(ctx context.Context, mod api.Module, pFile ptr_t) any {
442 const fileHandleOffset = 4
443 id := util.Read32[ptr_t](mod, pFile+fileHandleOffset)
444 return util.GetHandle(ctx, id)
445}
446
447func vfsFileClose(ctx context.Context, mod api.Module, pFile ptr_t) error {
448 const fileHandleOffset = 4
449 id := util.Read32[ptr_t](mod, pFile+fileHandleOffset)
450 return util.DelHandle(ctx, id)
451}
452
453func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
454 if err == nil {
455 return _OK
456 }
457 switch v := reflect.ValueOf(err); v.Kind() {
458 case reflect.Uint8, reflect.Uint16, reflect.Uint32:
459 return _ErrorCode(v.Uint())
460 }
461 return def
462}