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