1//go:build sqlite3_dotlk
2
3package vfs
4
5import (
6 "context"
7 "errors"
8 "io/fs"
9 "sync"
10
11 "github.com/tetratelabs/wazero/api"
12
13 "github.com/ncruces/go-sqlite3/internal/dotlk"
14 "github.com/ncruces/go-sqlite3/internal/util"
15)
16
17type vfsShmParent struct {
18 shared [][_WALINDEX_PGSZ]byte
19 refs int // +checklocks:vfsShmListMtx
20
21 lock [_SHM_NLOCK]int8 // +checklocks:Mutex
22 sync.Mutex
23}
24
25var (
26 // +checklocks:vfsShmListMtx
27 vfsShmList = map[string]*vfsShmParent{}
28 vfsShmListMtx sync.Mutex
29)
30
31type vfsShm struct {
32 *vfsShmParent
33 mod api.Module
34 alloc api.Function
35 free api.Function
36 path string
37 shadow [][_WALINDEX_PGSZ]byte
38 ptrs []ptr_t
39 stack [1]stk_t
40 lock [_SHM_NLOCK]bool
41}
42
43func (s *vfsShm) Close() error {
44 if s.vfsShmParent == nil {
45 return nil
46 }
47
48 vfsShmListMtx.Lock()
49 defer vfsShmListMtx.Unlock()
50
51 // Unlock everything.
52 s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
53
54 // Decrease reference count.
55 if s.vfsShmParent.refs > 0 {
56 s.vfsShmParent.refs--
57 s.vfsShmParent = nil
58 return nil
59 }
60
61 if err := dotlk.Unlock(s.path); err != nil {
62 return _IOERR_UNLOCK
63 }
64 delete(vfsShmList, s.path)
65 s.vfsShmParent = nil
66 return nil
67}
68
69func (s *vfsShm) shmOpen() _ErrorCode {
70 if s.vfsShmParent != nil {
71 return _OK
72 }
73
74 vfsShmListMtx.Lock()
75 defer vfsShmListMtx.Unlock()
76
77 // Find a shared buffer, increase the reference count.
78 if g, ok := vfsShmList[s.path]; ok {
79 s.vfsShmParent = g
80 g.refs++
81 return _OK
82 }
83
84 // Dead man's switch.
85 err := dotlk.LockShm(s.path)
86 if errors.Is(err, fs.ErrExist) {
87 return _BUSY
88 }
89 if err != nil {
90 return _IOERR_LOCK
91 }
92
93 // Add the new shared buffer.
94 s.vfsShmParent = &vfsShmParent{}
95 vfsShmList[s.path] = s.vfsShmParent
96 return _OK
97}
98
99func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) {
100 if size != _WALINDEX_PGSZ {
101 return 0, _IOERR_SHMMAP
102 }
103 if s.mod == nil {
104 s.mod = mod
105 s.free = mod.ExportedFunction("sqlite3_free")
106 s.alloc = mod.ExportedFunction("sqlite3_malloc64")
107 }
108 if rc := s.shmOpen(); rc != _OK {
109 return 0, rc
110 }
111
112 s.Lock()
113 defer s.Unlock()
114 defer s.shmAcquire(nil)
115
116 // Extend shared memory.
117 if int(id) >= len(s.shared) {
118 if !extend {
119 return 0, _OK
120 }
121 s.shared = append(s.shared, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shared)+1)...)
122 }
123
124 // Allocate shadow memory.
125 if int(id) >= len(s.shadow) {
126 s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...)
127 }
128
129 // Allocate local memory.
130 for int(id) >= len(s.ptrs) {
131 s.stack[0] = stk_t(size)
132 if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
133 panic(err)
134 }
135 if s.stack[0] == 0 {
136 panic(util.OOMErr)
137 }
138 clear(util.View(s.mod, ptr_t(s.stack[0]), _WALINDEX_PGSZ))
139 s.ptrs = append(s.ptrs, ptr_t(s.stack[0]))
140 }
141
142 s.shadow[0][4] = 1
143 return s.ptrs[id], _OK
144}
145
146func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) {
147 s.Lock()
148 defer s.Unlock()
149
150 switch {
151 case flags&_SHM_LOCK != 0:
152 defer s.shmAcquire(&rc)
153 case flags&_SHM_EXCLUSIVE != 0:
154 s.shmRelease()
155 }
156
157 return s.shmMemLock(offset, n, flags)
158}
159
160func (s *vfsShm) shmUnmap(delete bool) {
161 if s.vfsShmParent == nil {
162 return
163 }
164 defer s.Close()
165
166 s.Lock()
167 s.shmRelease()
168 defer s.Unlock()
169
170 for _, p := range s.ptrs {
171 s.stack[0] = stk_t(p)
172 if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
173 panic(err)
174 }
175 }
176 s.ptrs = nil
177 s.shadow = nil
178}