vfs.go

  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}