1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 54 Interprocess Communication */
10
11 #include "squid.h"
12 #include "ipc/MemMap.h"
13 #include "store_key_md5.h"
14 #include "tools.h"
15
MemMap(const char * const aPath)16 Ipc::MemMap::MemMap(const char *const aPath) :
17 cleaner(NULL),
18 path(aPath),
19 shared(shm_old(Shared)(aPath))
20 {
21 assert(shared->limit > 0); // we should not be created otherwise
22 debugs(54, 5, "attached map [" << path << "] created: " <<
23 shared->limit);
24 }
25
26 Ipc::MemMap::Owner *
Init(const char * const path,const int limit,const size_t extrasSize)27 Ipc::MemMap::Init(const char *const path, const int limit, const size_t extrasSize)
28 {
29 assert(limit > 0); // we should not be created otherwise
30 Owner *const owner = shm_new(Shared)(path, limit, extrasSize);
31 debugs(54, 5, "new map [" << path << "] created: " << limit);
32 return owner;
33 }
34
35 Ipc::MemMap::Owner *
Init(const char * const path,const int limit)36 Ipc::MemMap::Init(const char *const path, const int limit)
37 {
38 return Init(path, limit, 0);
39 }
40
41 Ipc::MemMap::Slot *
openForWriting(const cache_key * const key,sfileno & fileno)42 Ipc::MemMap::openForWriting(const cache_key *const key, sfileno &fileno)
43 {
44 debugs(54, 5, "trying to open slot for key " << storeKeyText(key)
45 << " for writing in map [" << path << ']');
46 const int idx = slotIndexByKey(key);
47
48 if (Slot *slot = openForWritingAt(idx)) {
49 fileno = idx;
50 return slot;
51 }
52
53 return NULL;
54 }
55
56 Ipc::MemMap::Slot *
openForWritingAt(const sfileno fileno,bool overwriteExisting)57 Ipc::MemMap::openForWritingAt(const sfileno fileno, bool overwriteExisting)
58 {
59 Slot &s = shared->slots[fileno];
60 ReadWriteLock &lock = s.lock;
61
62 if (lock.lockExclusive()) {
63 assert(s.writing() && !s.reading());
64
65 // bail if we cannot empty this position
66 if (!s.waitingToBeFreed && !s.empty() && !overwriteExisting) {
67 lock.unlockExclusive();
68 debugs(54, 5, "cannot open existing entry " << fileno <<
69 " for writing " << path);
70 return NULL;
71 }
72
73 // free if the entry was used, keeping the entry locked
74 if (s.waitingToBeFreed || !s.empty())
75 freeLocked(s, true);
76
77 assert(s.empty());
78 ++shared->count;
79
80 debugs(54, 5, "opened slot at " << fileno <<
81 " for writing in map [" << path << ']');
82 return &s; // and keep the entry locked
83 }
84
85 debugs(54, 5, "failed to open slot at " << fileno <<
86 " for writing in map [" << path << ']');
87 return NULL;
88 }
89
90 void
closeForWriting(const sfileno fileno)91 Ipc::MemMap::closeForWriting(const sfileno fileno)
92 {
93 debugs(54, 5, "stop writing slot at " << fileno <<
94 " in map [" << path << ']');
95 assert(valid(fileno));
96 Slot &s = shared->slots[fileno];
97 assert(s.writing());
98 s.lock.unlockExclusive();
99 }
100
101 void
switchWritingToReading(const sfileno fileno)102 Ipc::MemMap::switchWritingToReading(const sfileno fileno)
103 {
104 debugs(54, 5, "switching writing slot at " << fileno <<
105 " to reading in map [" << path << ']');
106 assert(valid(fileno));
107 Slot &s = shared->slots[fileno];
108 assert(s.writing());
109 s.lock.switchExclusiveToShared();
110 }
111
112 /// terminate writing the entry, freeing its slot for others to use
113 void
abortWriting(const sfileno fileno)114 Ipc::MemMap::abortWriting(const sfileno fileno)
115 {
116 debugs(54, 5, "abort writing slot at " << fileno <<
117 " in map [" << path << ']');
118 assert(valid(fileno));
119 Slot &s = shared->slots[fileno];
120 assert(s.writing());
121 freeLocked(s, false);
122 }
123
124 const Ipc::MemMap::Slot *
peekAtReader(const sfileno fileno) const125 Ipc::MemMap::peekAtReader(const sfileno fileno) const
126 {
127 assert(valid(fileno));
128 const Slot &s = shared->slots[fileno];
129 if (s.reading())
130 return &s; // immediate access by lock holder so no locking
131 if (s.writing())
132 return NULL; // cannot read the slot when it is being written
133 assert(false); // must be locked for reading or writing
134 return NULL;
135 }
136
137 void
free(const sfileno fileno)138 Ipc::MemMap::free(const sfileno fileno)
139 {
140 debugs(54, 5, "marking slot at " << fileno << " to be freed in"
141 " map [" << path << ']');
142
143 assert(valid(fileno));
144 Slot &s = shared->slots[fileno];
145
146 if (s.lock.lockExclusive())
147 freeLocked(s, false);
148 else
149 s.waitingToBeFreed = true; // mark to free it later
150 }
151
152 const Ipc::MemMap::Slot *
openForReading(const cache_key * const key,sfileno & fileno)153 Ipc::MemMap::openForReading(const cache_key *const key, sfileno &fileno)
154 {
155 debugs(54, 5, "trying to open slot for key " << storeKeyText(key)
156 << " for reading in map [" << path << ']');
157 const int idx = slotIndexByKey(key);
158 if (const Slot *slot = openForReadingAt(idx)) {
159 if (slot->sameKey(key)) {
160 fileno = idx;
161 debugs(54, 5, "opened slot at " << fileno << " for key "
162 << storeKeyText(key) << " for reading in map [" << path <<
163 ']');
164 return slot; // locked for reading
165 }
166 slot->lock.unlockShared();
167 }
168 debugs(54, 5, "failed to open slot for key " << storeKeyText(key)
169 << " for reading in map [" << path << ']');
170 return NULL;
171 }
172
173 const Ipc::MemMap::Slot *
openForReadingAt(const sfileno fileno)174 Ipc::MemMap::openForReadingAt(const sfileno fileno)
175 {
176 debugs(54, 5, "trying to open slot at " << fileno << " for "
177 "reading in map [" << path << ']');
178 assert(valid(fileno));
179 Slot &s = shared->slots[fileno];
180
181 if (!s.lock.lockShared()) {
182 debugs(54, 5, "failed to lock slot at " << fileno << " for "
183 "reading in map [" << path << ']');
184 return NULL;
185 }
186
187 if (s.empty()) {
188 s.lock.unlockShared();
189 debugs(54, 7, "empty slot at " << fileno << " for "
190 "reading in map [" << path << ']');
191 return NULL;
192 }
193
194 if (s.waitingToBeFreed) {
195 s.lock.unlockShared();
196 debugs(54, 7, "dirty slot at " << fileno << " for "
197 "reading in map [" << path << ']');
198 return NULL;
199 }
200
201 debugs(54, 5, "opened slot at " << fileno << " for reading in"
202 " map [" << path << ']');
203 return &s;
204 }
205
206 void
closeForReading(const sfileno fileno)207 Ipc::MemMap::closeForReading(const sfileno fileno)
208 {
209 debugs(54, 5, "closing slot at " << fileno << " for reading in "
210 "map [" << path << ']');
211 assert(valid(fileno));
212 Slot &s = shared->slots[fileno];
213 assert(s.reading());
214 s.lock.unlockShared();
215 }
216
217 int
entryLimit() const218 Ipc::MemMap::entryLimit() const
219 {
220 return shared->limit;
221 }
222
223 int
entryCount() const224 Ipc::MemMap::entryCount() const
225 {
226 return shared->count;
227 }
228
229 bool
full() const230 Ipc::MemMap::full() const
231 {
232 return entryCount() >= entryLimit();
233 }
234
235 void
updateStats(ReadWriteLockStats & stats) const236 Ipc::MemMap::updateStats(ReadWriteLockStats &stats) const
237 {
238 for (int i = 0; i < shared->limit; ++i)
239 shared->slots[i].lock.updateStats(stats);
240 }
241
242 bool
valid(const int pos) const243 Ipc::MemMap::valid(const int pos) const
244 {
245 return 0 <= pos && pos < entryLimit();
246 }
247
248 static
249 unsigned int
hash_key(const unsigned char * data,unsigned int len,unsigned int hashSize)250 hash_key(const unsigned char *data, unsigned int len, unsigned int hashSize)
251 {
252 unsigned int n;
253 unsigned int j;
254 for (j = 0, n = 0; j < len; j++ ) {
255 n ^= 271 * *data;
256 ++data;
257 }
258 return (n ^ (j * 271)) % hashSize;
259 }
260
261 int
slotIndexByKey(const cache_key * const key) const262 Ipc::MemMap::slotIndexByKey(const cache_key *const key) const
263 {
264 const unsigned char *k = reinterpret_cast<const unsigned char *>(key);
265 return hash_key(k, MEMMAP_SLOT_KEY_SIZE, shared->limit);
266 }
267
268 Ipc::MemMap::Slot &
slotByKey(const cache_key * const key)269 Ipc::MemMap::slotByKey(const cache_key *const key)
270 {
271 return shared->slots[slotIndexByKey(key)];
272 }
273
274 /// unconditionally frees the already exclusively locked slot and releases lock
275 void
freeLocked(Slot & s,bool keepLocked)276 Ipc::MemMap::freeLocked(Slot &s, bool keepLocked)
277 {
278 if (!s.empty() && cleaner)
279 cleaner->noteFreeMapSlot(&s - shared->slots.raw());
280
281 s.waitingToBeFreed = false;
282 memset(s.key, 0, sizeof(s.key));
283 if (!keepLocked)
284 s.lock.unlockExclusive();
285 --shared->count;
286 debugs(54, 5, "freed slot at " << (&s - shared->slots.raw()) <<
287 " in map [" << path << ']');
288 }
289
290 /* Ipc::MemMapSlot */
MemMapSlot()291 Ipc::MemMapSlot::MemMapSlot() :
292 pSize(0),
293 expire(0)
294 {
295 memset(key, 0, sizeof(key));
296 memset(p, 0, sizeof(p));
297 }
298
299 void
set(const unsigned char * aKey,const void * block,size_t blockSize,time_t expireAt)300 Ipc::MemMapSlot::set(const unsigned char *aKey, const void *block, size_t blockSize, time_t expireAt)
301 {
302 memcpy(key, aKey, sizeof(key));
303 if (block)
304 memcpy(p, block, blockSize);
305 pSize = blockSize;
306 expire = expireAt;
307 }
308
309 bool
sameKey(const cache_key * const aKey) const310 Ipc::MemMapSlot::sameKey(const cache_key *const aKey) const
311 {
312 return (memcmp(key, aKey, sizeof(key)) == 0);
313 }
314
315 bool
empty() const316 Ipc::MemMapSlot::empty() const
317 {
318 for (unsigned char const*u = key; u < key + sizeof(key); ++u) {
319 if (*u)
320 return false;
321 }
322 return true;
323 }
324
325 /* Ipc::MemMap::Shared */
326
Shared(const int aLimit,const size_t anExtrasSize)327 Ipc::MemMap::Shared::Shared(const int aLimit, const size_t anExtrasSize):
328 limit(aLimit), extrasSize(anExtrasSize), count(0), slots(aLimit)
329 {
330 }
331
~Shared()332 Ipc::MemMap::Shared::~Shared()
333 {
334 }
335
336 size_t
sharedMemorySize() const337 Ipc::MemMap::Shared::sharedMemorySize() const
338 {
339 return SharedMemorySize(limit, extrasSize);
340 }
341
342 size_t
SharedMemorySize(const int limit,const size_t extrasSize)343 Ipc::MemMap::Shared::SharedMemorySize(const int limit, const size_t extrasSize)
344 {
345 return sizeof(Shared) + limit * (sizeof(Slot) + extrasSize);
346 }
347
348