]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipc/mem/Segment.cc
Convert Vary handling to SBuf
[thirdparty/squid.git] / src / ipc / mem / Segment.cc
1 /*
2 * Copyright (C) 1996-2016 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 "base/TextException.h"
13 #include "compat/shm.h"
14 #include "Debug.h"
15 #include "fatal.h"
16 #include "ipc/mem/Segment.h"
17 #include "sbuf/SBuf.h"
18 #include "SquidConfig.h"
19 #include "tools.h"
20
21 #if HAVE_FCNTL_H
22 #include <fcntl.h>
23 #endif
24 #if HAVE_SYS_MMAN_H
25 #include <sys/mman.h>
26 #endif
27 #if HAVE_SYS_STAT_H
28 #include <sys/stat.h>
29 #endif
30 #if HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 // test cases change this
35 const char *Ipc::Mem::Segment::BasePath = DEFAULT_STATEDIR;
36
37 void *
38 Ipc::Mem::Segment::reserve(size_t chunkSize)
39 {
40 Must(theMem);
41 // check for overflows
42 // chunkSize >= 0 may result in warnings on systems where off_t is unsigned
43 assert(!chunkSize || static_cast<off_t>(chunkSize) > 0);
44 assert(static_cast<off_t>(chunkSize) <= theSize);
45 assert(theReserved <= theSize - static_cast<off_t>(chunkSize));
46 void *result = reinterpret_cast<char*>(theMem) + theReserved;
47 theReserved += chunkSize;
48 return result;
49 }
50
51 SBuf
52 Ipc::Mem::Segment::Name(const SBuf &prefix, const char *suffix)
53 {
54 SBuf result = prefix;
55 result.append("_");
56 result.append(suffix);
57 return result;
58 }
59
60 #if HAVE_SHM
61
62 Ipc::Mem::Segment::Segment(const char *const id):
63 theFD(-1), theName(GenerateName(id)), theMem(NULL),
64 theSize(0), theReserved(0), doUnlink(false)
65 {
66 }
67
68 Ipc::Mem::Segment::~Segment()
69 {
70 if (theFD >= 0) {
71 detach();
72 if (close(theFD) != 0)
73 debugs(54, 5, HERE << "close " << theName << ": " << xstrerror());
74 }
75 if (doUnlink)
76 unlink();
77 }
78
79 // fake Ipc::Mem::Segment::Enabled (!HAVE_SHM) is more selective
80 bool
81 Ipc::Mem::Segment::Enabled()
82 {
83 return true;
84 }
85
86 void
87 Ipc::Mem::Segment::create(const off_t aSize)
88 {
89 assert(aSize > 0);
90 assert(theFD < 0);
91
92 // Why a brand new segment? A Squid crash may leave a reusable segment, but
93 // our placement-new code requires an all-0s segment. We could truncate and
94 // resize the old segment, but OS X does not allow using O_TRUNC with
95 // shm_open() and does not support ftruncate() for old segments.
96 if (!createFresh() && errno == EEXIST) {
97 unlink();
98 createFresh();
99 }
100
101 if (theFD < 0) {
102 debugs(54, 5, HERE << "shm_open " << theName << ": " << xstrerror());
103 fatalf("Ipc::Mem::Segment::create failed to shm_open(%s): %s\n",
104 theName.termedBuf(), xstrerror());
105 }
106
107 if (ftruncate(theFD, aSize)) {
108 const int savedError = errno;
109 unlink();
110 debugs(54, 5, HERE << "ftruncate " << theName << ": " << xstrerr(savedError));
111 fatalf("Ipc::Mem::Segment::create failed to ftruncate(%s): %s\n",
112 theName.termedBuf(), xstrerr(savedError));
113 }
114 // We assume that the shm_open(O_CREAT)+ftruncate() combo zeros the segment.
115
116 theSize = statSize("Ipc::Mem::Segment::create");
117
118 // OS X will round up to a full page, so not checking for exact size match.
119 assert(theSize >= aSize);
120
121 theReserved = 0;
122 doUnlink = true;
123
124 debugs(54, 3, HERE << "created " << theName << " segment: " << theSize);
125
126 attach();
127 }
128
129 void
130 Ipc::Mem::Segment::open()
131 {
132 assert(theFD < 0);
133
134 theFD = shm_open(theName.termedBuf(), O_RDWR, 0);
135 if (theFD < 0) {
136 debugs(54, 5, HERE << "shm_open " << theName << ": " << xstrerror());
137 fatalf("Ipc::Mem::Segment::open failed to shm_open(%s): %s\n",
138 theName.termedBuf(), xstrerror());
139 }
140
141 theSize = statSize("Ipc::Mem::Segment::open");
142
143 debugs(54, 3, HERE << "opened " << theName << " segment: " << theSize);
144
145 attach();
146 }
147
148 /// Creates a brand new shared memory segment and returns true.
149 /// Fails and returns false if there exist an old segment with the same name.
150 bool
151 Ipc::Mem::Segment::createFresh()
152 {
153 theFD = shm_open(theName.termedBuf(),
154 O_EXCL | O_CREAT | O_RDWR,
155 S_IRUSR | S_IWUSR);
156 return theFD >= 0;
157 }
158
159 /// Map the shared memory segment to the process memory space.
160 void
161 Ipc::Mem::Segment::attach()
162 {
163 assert(theFD >= 0);
164 assert(!theMem);
165
166 // mmap() accepts size_t for the size; we give it off_t which might
167 // be bigger; assert overflows until we support multiple mmap()s?
168 assert(theSize == static_cast<off_t>(static_cast<size_t>(theSize)));
169
170 void *const p =
171 mmap(NULL, theSize, PROT_READ | PROT_WRITE, MAP_SHARED, theFD, 0);
172 if (p == MAP_FAILED) {
173 debugs(54, 5, HERE << "mmap " << theName << ": " << xstrerror());
174 fatalf("Ipc::Mem::Segment::attach failed to mmap(%s): %s\n",
175 theName.termedBuf(), xstrerror());
176 }
177 theMem = p;
178
179 lock();
180 }
181
182 /// Unmap the shared memory segment from the process memory space.
183 void
184 Ipc::Mem::Segment::detach()
185 {
186 if (!theMem)
187 return;
188
189 if (munmap(theMem, theSize)) {
190 debugs(54, 5, HERE << "munmap " << theName << ": " << xstrerror());
191 fatalf("Ipc::Mem::Segment::detach failed to munmap(%s): %s\n",
192 theName.termedBuf(), xstrerror());
193 }
194 theMem = 0;
195 }
196
197 /// Lock the segment into RAM, ensuring that the OS has enough RAM for it [now]
198 /// and preventing segment bytes from being swapped out to disk later by the OS.
199 void
200 Ipc::Mem::Segment::lock()
201 {
202 if (!Config.shmLocking) {
203 debugs(54, 5, "mlock(2)-ing disabled");
204 return;
205 }
206
207 #if defined(_POSIX_MEMLOCK_RANGE)
208 debugs(54, 7, "mlock(" << theName << ',' << theSize << ") starts");
209 if (mlock(theMem, theSize) != 0) {
210 const int savedError = errno;
211 fatalf("shared_memory_locking on but failed to mlock(%s, %" PRId64 "): %s\n",
212 theName.termedBuf(),static_cast<int64_t>(theSize), xstrerr(savedError));
213 }
214 // TODO: Warn if it took too long.
215 debugs(54, 7, "mlock(" << theName << ',' << theSize << ") OK");
216 #else
217 debugs(54, 5, "insufficient mlock(2) support");
218 if (Config.shmLocking.configured()) { // set explicitly
219 static bool warnedOnce = false;
220 if (!warnedOnce) {
221 debugs(54, DBG_IMPORTANT, "ERROR: insufficient mlock(2) support prevents " <<
222 "honoring `shared_memory_locking on`. " <<
223 "If you lack RAM, kernel will kill Squid later.");
224 warnedOnce = true;
225 }
226 }
227 #endif
228 }
229
230 void
231 Ipc::Mem::Segment::unlink()
232 {
233 if (shm_unlink(theName.termedBuf()) != 0)
234 debugs(54, 5, HERE << "shm_unlink(" << theName << "): " << xstrerror());
235 else
236 debugs(54, 3, HERE << "unlinked " << theName << " segment");
237 }
238
239 /// determines the size of the underlying "file"
240 off_t
241 Ipc::Mem::Segment::statSize(const char *context) const
242 {
243 Must(theFD >= 0);
244
245 struct stat s;
246 memset(&s, 0, sizeof(s));
247
248 if (fstat(theFD, &s) != 0) {
249 debugs(54, 5, HERE << context << " fstat " << theName << ": " << xstrerror());
250 fatalf("Ipc::Mem::Segment::statSize: %s failed to fstat(%s): %s\n",
251 context, theName.termedBuf(), xstrerror());
252 }
253
254 return s.st_size;
255 }
256
257 /// Generate name for shared memory segment. Starts with a prefix required
258 /// for cross-platform portability and replaces all slashes in ID with dots.
259 String
260 Ipc::Mem::Segment::GenerateName(const char *id)
261 {
262 assert(BasePath && *BasePath);
263 static const bool nameIsPath = shm_portable_segment_name_is_path();
264 String name;
265 if (nameIsPath) {
266 name.append(BasePath);
267 if (name[name.size()-1] != '/')
268 name.append('/');
269 } else {
270 name.append('/');
271 name.append(service_name.c_str());
272 name.append('-');
273 }
274
275 // append id, replacing slashes with dots
276 for (const char *slash = strchr(id, '/'); slash; slash = strchr(id, '/')) {
277 if (id != slash) {
278 name.append(id, slash - id);
279 name.append('.');
280 }
281 id = slash + 1;
282 }
283 name.append(id);
284
285 name.append(".shm"); // to distinguish from non-segments when nameIsPath
286 return name;
287 }
288
289 #else // HAVE_SHM
290
291 #include <map>
292
293 typedef std::map<String, Ipc::Mem::Segment *> SegmentMap;
294 static SegmentMap Segments;
295
296 Ipc::Mem::Segment::Segment(const char *const id):
297 theName(id), theMem(NULL), theSize(0), theReserved(0), doUnlink(false)
298 {
299 }
300
301 Ipc::Mem::Segment::~Segment()
302 {
303 if (doUnlink) {
304 delete [] static_cast<char *>(theMem);
305 theMem = NULL;
306 Segments.erase(theName);
307 debugs(54, 3, HERE << "unlinked " << theName << " fake segment");
308 }
309 }
310
311 bool
312 Ipc::Mem::Segment::Enabled()
313 {
314 return !UsingSmp() && IamWorkerProcess();
315 }
316
317 void
318 Ipc::Mem::Segment::create(const off_t aSize)
319 {
320 assert(aSize > 0);
321 assert(!theMem);
322 checkSupport("Fake segment creation");
323
324 const bool inserted = Segments.insert(std::make_pair(theName, this)).second;
325 if (!inserted)
326 fatalf("Duplicate fake segment creation: %s", theName.termedBuf());
327
328 theMem = new char[aSize];
329 theSize = aSize;
330 doUnlink = true;
331
332 debugs(54, 3, HERE << "created " << theName << " fake segment: " << theSize);
333 }
334
335 void
336 Ipc::Mem::Segment::open()
337 {
338 assert(!theMem);
339 checkSupport("Fake segment open");
340
341 const SegmentMap::const_iterator i = Segments.find(theName);
342 if (i == Segments.end())
343 fatalf("Fake segment not found: %s", theName.termedBuf());
344
345 const Segment &segment = *i->second;
346 theMem = segment.theMem;
347 theSize = segment.theSize;
348
349 debugs(54, 3, HERE << "opened " << theName << " fake segment: " << theSize);
350 }
351
352 void
353 Ipc::Mem::Segment::checkSupport(const char *const context)
354 {
355 if (!Enabled()) {
356 debugs(54, 5, HERE << context <<
357 ": True shared memory segments are not supported. "
358 "Cannot fake shared segments in SMP config.");
359 fatalf("Ipc::Mem::Segment: Cannot fake shared segments in SMP config (%s)\n",
360 context);
361 }
362 }
363
364 #endif // HAVE_SHM
365
366 void
367 Ipc::Mem::RegisteredRunner::useConfig()
368 {
369 // If Squid is built with real segments, we create() real segments
370 // in the master process only. Otherwise, we create() fake
371 // segments in each worker process. We assume that only workers
372 // need and can work with fake segments.
373 #if HAVE_SHM
374 if (IamMasterProcess())
375 #else
376 if (IamWorkerProcess())
377 #endif
378 create();
379
380 // we assume that master process does not need shared segments
381 // unless it is also a worker
382 if (!InDaemonMode() || !IamMasterProcess())
383 open();
384 }
385