1//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_dotlk
2
3package vfs
4
5import (
6 "context"
7 "io"
8 "os"
9 "sync"
10
11 "github.com/tetratelabs/wazero/api"
12 "golang.org/x/sys/windows"
13
14 "github.com/ncruces/go-sqlite3/internal/util"
15)
16
17type vfsShm struct {
18 *os.File
19 mod api.Module
20 alloc api.Function
21 free api.Function
22 path string
23 regions []*util.MappedRegion
24 shared [][]byte
25 shadow [][_WALINDEX_PGSZ]byte
26 ptrs []ptr_t
27 stack [1]stk_t
28 fileLock bool
29 blocking bool
30 sync.Mutex
31}
32
33func (s *vfsShm) Close() error {
34 // Unmap regions.
35 for _, r := range s.regions {
36 r.Unmap()
37 }
38 s.regions = nil
39
40 // Close the file.
41 return s.File.Close()
42}
43
44func (s *vfsShm) shmOpen() _ErrorCode {
45 if s.File == nil {
46 f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
47 if err != nil {
48 return _CANTOPEN
49 }
50 s.File = f
51 }
52 if s.fileLock {
53 return _OK
54 }
55
56 // Dead man's switch.
57 if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK {
58 err := s.Truncate(0)
59 osUnlock(s.File, _SHM_DMS, 1)
60 if err != nil {
61 return _IOERR_SHMOPEN
62 }
63 }
64 rc := osReadLock(s.File, _SHM_DMS, 1, 0)
65 s.fileLock = rc == _OK
66 return rc
67}
68
69func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, rc _ErrorCode) {
70 // Ensure size is a multiple of the OS page size.
71 if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 {
72 return 0, _IOERR_SHMMAP
73 }
74 if s.mod == nil {
75 s.mod = mod
76 s.free = mod.ExportedFunction("sqlite3_free")
77 s.alloc = mod.ExportedFunction("sqlite3_malloc64")
78 }
79 if rc := s.shmOpen(); rc != _OK {
80 return 0, rc
81 }
82
83 defer s.shmAcquire(&rc)
84
85 // Check if file is big enough.
86 o, err := s.Seek(0, io.SeekEnd)
87 if err != nil {
88 return 0, _IOERR_SHMSIZE
89 }
90 if n := (int64(id) + 1) * int64(size); n > o {
91 if !extend {
92 return 0, _OK
93 }
94 if osAllocate(s.File, n) != nil {
95 return 0, _IOERR_SHMSIZE
96 }
97 }
98
99 // Maps regions into memory.
100 for int(id) >= len(s.shared) {
101 r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size)
102 if err != nil {
103 return 0, _IOERR_SHMMAP
104 }
105 s.regions = append(s.regions, r)
106 s.shared = append(s.shared, r.Data)
107 }
108
109 // Allocate shadow memory.
110 if int(id) >= len(s.shadow) {
111 s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...)
112 }
113
114 // Allocate local memory.
115 for int(id) >= len(s.ptrs) {
116 s.stack[0] = stk_t(size)
117 if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
118 panic(err)
119 }
120 if s.stack[0] == 0 {
121 panic(util.OOMErr)
122 }
123 clear(util.View(s.mod, ptr_t(s.stack[0]), _WALINDEX_PGSZ))
124 s.ptrs = append(s.ptrs, ptr_t(s.stack[0]))
125 }
126
127 s.shadow[0][4] = 1
128 return s.ptrs[id], _OK
129}
130
131func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) {
132 switch {
133 case flags&_SHM_LOCK != 0:
134 defer s.shmAcquire(&rc)
135 case flags&_SHM_EXCLUSIVE != 0:
136 s.shmRelease()
137 }
138
139 switch {
140 case flags&_SHM_UNLOCK != 0:
141 return osUnlock(s.File, _SHM_BASE+uint32(offset), uint32(n))
142 case flags&_SHM_SHARED != 0:
143 return osReadLock(s.File, _SHM_BASE+uint32(offset), uint32(n), 0)
144 case flags&_SHM_EXCLUSIVE != 0:
145 return osWriteLock(s.File, _SHM_BASE+uint32(offset), uint32(n), 0)
146 default:
147 panic(util.AssertErr())
148 }
149}
150
151func (s *vfsShm) shmUnmap(delete bool) {
152 if s.File == nil {
153 return
154 }
155
156 s.shmRelease()
157
158 // Free local memory.
159 for _, p := range s.ptrs {
160 s.stack[0] = stk_t(p)
161 if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
162 panic(err)
163 }
164 }
165 s.ptrs = nil
166 s.shadow = nil
167 s.shared = nil
168
169 // Close the file.
170 s.Close()
171 s.File = nil
172 if delete {
173 os.Remove(s.path)
174 }
175}