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 20 Swap Dir base object */
10
11 #include "squid.h"
12 #include "cache_cf.h"
13 #include "compat/strtoll.h"
14 #include "ConfigOption.h"
15 #include "ConfigParser.h"
16 #include "globals.h"
17 #include "Parsing.h"
18 #include "SquidConfig.h"
19 #include "Store.h"
20 #include "store/Disk.h"
21 #include "StoreFileSystem.h"
22 #include "tools.h"
23
Disk(char const * aType)24 Store::Disk::Disk(char const *aType): theType(aType),
25 max_size(0), min_objsize(-1), max_objsize (-1),
26 path(NULL), index(-1), disker(-1),
27 repl(NULL), removals(0), scanned(0),
28 cleanLog(NULL)
29 {
30 fs.blksize = 1024;
31 }
32
~Disk()33 Store::Disk::~Disk()
34 {
35 // TODO: should we delete repl?
36 xfree(path);
37 }
38
39 void
create()40 Store::Disk::create() {}
41
42 void
dump(StoreEntry &) const43 Store::Disk::dump(StoreEntry &)const {}
44
45 bool
doubleCheck(StoreEntry &)46 Store::Disk::doubleCheck(StoreEntry &)
47 {
48 return false;
49 }
50
51 void
getStats(StoreInfoStats & stats) const52 Store::Disk::getStats(StoreInfoStats &stats) const
53 {
54 if (!doReportStat())
55 return;
56
57 stats.swap.size = currentSize();
58 stats.swap.capacity = maxSize();
59 stats.swap.count = currentCount();
60 }
61
62 void
stat(StoreEntry & output) const63 Store::Disk::stat(StoreEntry &output) const
64 {
65 if (!doReportStat())
66 return;
67
68 storeAppendPrintf(&output, "Store Directory #%d (%s): %s\n", index, type(),
69 path);
70 storeAppendPrintf(&output, "FS Block Size %d Bytes\n",
71 fs.blksize);
72 statfs(output);
73
74 if (repl) {
75 storeAppendPrintf(&output, "Removal policy: %s\n", repl->_type);
76
77 if (repl->Stats)
78 repl->Stats(repl, &output);
79 }
80 }
81
82 void
statfs(StoreEntry &) const83 Store::Disk::statfs(StoreEntry &)const {}
84
85 void
maintain()86 Store::Disk::maintain() {}
87
88 uint64_t
minSize() const89 Store::Disk::minSize() const
90 {
91 // XXX: Not all disk stores use Config.Swap.lowWaterMark
92 return ((maxSize() * Config.Swap.lowWaterMark) / 100);
93 }
94
95 int64_t
minObjectSize() const96 Store::Disk::minObjectSize() const
97 {
98 // per-store min-size=N value is authoritative
99 return min_objsize > -1 ? min_objsize : Config.Store.minObjectSize;
100 }
101
102 int64_t
maxObjectSize() const103 Store::Disk::maxObjectSize() const
104 {
105 // per-store max-size=N value is authoritative
106 if (max_objsize > -1)
107 return max_objsize;
108
109 // store with no individual max limit is limited by configured maximum_object_size
110 // or the total store size, whichever is smaller
111 return min(static_cast<int64_t>(maxSize()), Config.Store.maxObjectSize);
112 }
113
114 void
maxObjectSize(int64_t newMax)115 Store::Disk::maxObjectSize(int64_t newMax)
116 {
117 // negative values mean no limit (-1)
118 if (newMax < 0) {
119 max_objsize = -1; // set explicitly in case it had a non-default value previously
120 return;
121 }
122
123 // prohibit values greater than total storage area size
124 // but set max_objsize to the maximum allowed to override maximum_object_size global config
125 if (static_cast<uint64_t>(newMax) > maxSize()) {
126 debugs(47, DBG_PARSE_NOTE(2), "WARNING: Ignoring 'max-size' option for " << path <<
127 " which is larger than total cache_dir size of " << maxSize() << " bytes.");
128 max_objsize = maxSize();
129 return;
130 }
131
132 max_objsize = newMax;
133 }
134
135 void
reference(StoreEntry &)136 Store::Disk::reference(StoreEntry &) {}
137
138 bool
dereference(StoreEntry &)139 Store::Disk::dereference(StoreEntry &)
140 {
141 return true; // keep in global store_table
142 }
143
144 void
diskFull()145 Store::Disk::diskFull()
146 {
147 if (currentSize() >= maxSize())
148 return;
149
150 max_size = currentSize();
151
152 debugs(20, DBG_IMPORTANT, "WARNING: Shrinking cache_dir #" << index << " to " << currentSize() / 1024.0 << " KB");
153 }
154
155 bool
objectSizeIsAcceptable(int64_t objsize) const156 Store::Disk::objectSizeIsAcceptable(int64_t objsize) const
157 {
158 // need either the expected or the already accumulated object size
159 assert(objsize >= 0);
160 return minObjectSize() <= objsize && objsize <= maxObjectSize();
161 }
162
163 bool
canStore(const StoreEntry & e,int64_t diskSpaceNeeded,int & load) const164 Store::Disk::canStore(const StoreEntry &e, int64_t diskSpaceNeeded, int &load) const
165 {
166 debugs(47,8, HERE << "cache_dir[" << index << "]: needs " <<
167 diskSpaceNeeded << " <? " << max_objsize);
168
169 if (EBIT_TEST(e.flags, ENTRY_SPECIAL))
170 return false; // we do not store Squid-generated entries
171
172 if (!objectSizeIsAcceptable(diskSpaceNeeded))
173 return false; // does not satisfy size limits
174
175 if (flags.read_only)
176 return false; // cannot write at all
177
178 if (currentSize() > maxSize())
179 return false; // already overflowing
180
181 /* Return 999 (99.9%) constant load; TODO: add a named constant for this */
182 load = 999;
183 return true; // kids may provide more tests and should report true load
184 }
185
186 /* Move to StoreEntry ? */
187 bool
canLog(StoreEntry const & e) const188 Store::Disk::canLog(StoreEntry const &e)const
189 {
190 if (!e.hasDisk())
191 return false;
192
193 if (!e.swappedOut())
194 return false;
195
196 if (e.swap_file_sz <= 0)
197 return false;
198
199 if (EBIT_TEST(e.flags, RELEASE_REQUEST))
200 return false;
201
202 if (EBIT_TEST(e.flags, KEY_PRIVATE))
203 return false;
204
205 if (EBIT_TEST(e.flags, ENTRY_SPECIAL))
206 return false;
207
208 return true;
209 }
210
211 void
openLog()212 Store::Disk::openLog() {}
213
214 void
closeLog()215 Store::Disk::closeLog() {}
216
217 int
writeCleanStart()218 Store::Disk::writeCleanStart()
219 {
220 return 0;
221 }
222
223 void
writeCleanDone()224 Store::Disk::writeCleanDone() {}
225
226 void
logEntry(const StoreEntry &,int) const227 Store::Disk::logEntry(const StoreEntry &, int) const {}
228
229 char const *
type() const230 Store::Disk::type() const
231 {
232 return theType;
233 }
234
235 bool
active() const236 Store::Disk::active() const
237 {
238 if (IamWorkerProcess())
239 return true;
240
241 // we are inside a disker dedicated to this disk
242 if (KidIdentifier == disker)
243 return true;
244
245 return false; // Coordinator, wrong disker, etc.
246 }
247
248 bool
needsDiskStrand() const249 Store::Disk::needsDiskStrand() const
250 {
251 return false;
252 }
253
254 /* NOT performance critical. Really. Don't bother optimising for speed
255 * - RBC 20030718
256 */
257 ConfigOption *
getOptionTree() const258 Store::Disk::getOptionTree() const
259 {
260 ConfigOptionVector *result = new ConfigOptionVector;
261 result->options.push_back(new ConfigOptionAdapter<Disk>(*const_cast<Disk*>(this), &Store::Disk::optionReadOnlyParse, &Store::Disk::optionReadOnlyDump));
262 result->options.push_back(new ConfigOptionAdapter<Disk>(*const_cast<Disk*>(this), &Store::Disk::optionObjectSizeParse, &Store::Disk::optionObjectSizeDump));
263 return result;
264 }
265
266 void
parseOptions(int isaReconfig)267 Store::Disk::parseOptions(int isaReconfig)
268 {
269 const bool old_read_only = flags.read_only;
270 char *name, *value;
271
272 ConfigOption *newOption = getOptionTree();
273
274 while ((name = ConfigParser::NextToken()) != NULL) {
275 value = strchr(name, '=');
276
277 if (value) {
278 *value = '\0'; /* cut on = */
279 ++value;
280 }
281
282 debugs(3,2, "cache_dir " << name << '=' << (value ? value : ""));
283
284 if (newOption)
285 if (!newOption->parse(name, value, isaReconfig))
286 self_destruct();
287 }
288
289 delete newOption;
290
291 /*
292 * Handle notifications about reconfigured single-options with no value
293 * where the removal of the option cannot be easily detected in the
294 * parsing...
295 */
296
297 if (isaReconfig) {
298 if (old_read_only != flags.read_only) {
299 debugs(3, DBG_IMPORTANT, "Cache dir '" << path << "' now " << (flags.read_only ? "No-Store" : "Read-Write"));
300 }
301 }
302 }
303
304 void
dumpOptions(StoreEntry * entry) const305 Store::Disk::dumpOptions(StoreEntry * entry) const
306 {
307 ConfigOption *newOption = getOptionTree();
308
309 if (newOption)
310 newOption->dump(entry);
311
312 delete newOption;
313 }
314
315 bool
optionReadOnlyParse(char const * option,const char * value,int)316 Store::Disk::optionReadOnlyParse(char const *option, const char *value, int)
317 {
318 if (strcmp(option, "no-store") != 0 && strcmp(option, "read-only") != 0)
319 return false;
320
321 if (strcmp(option, "read-only") == 0) {
322 debugs(3, DBG_PARSE_NOTE(3), "UPGRADE WARNING: Replace cache_dir option 'read-only' with 'no-store'.");
323 }
324
325 bool read_only = 0;
326
327 if (value)
328 read_only = (xatoi(value) != 0);
329 else
330 read_only = true;
331
332 flags.read_only = read_only;
333
334 return true;
335 }
336
337 void
optionReadOnlyDump(StoreEntry * e) const338 Store::Disk::optionReadOnlyDump(StoreEntry * e) const
339 {
340 if (flags.read_only)
341 storeAppendPrintf(e, " no-store");
342 }
343
344 bool
optionObjectSizeParse(char const * option,const char * value,int isaReconfig)345 Store::Disk::optionObjectSizeParse(char const *option, const char *value, int isaReconfig)
346 {
347 int64_t *val;
348 if (strcmp(option, "max-size") == 0) {
349 val = &max_objsize;
350 } else if (strcmp(option, "min-size") == 0) {
351 val = &min_objsize;
352 } else
353 return false;
354
355 if (!value)
356 self_destruct();
357
358 int64_t size = strtoll(value, NULL, 10);
359
360 if (isaReconfig && *val != size) {
361 if (allowOptionReconfigure(option)) {
362 debugs(3, DBG_IMPORTANT, "cache_dir '" << path << "' object " <<
363 option << " now " << size << " Bytes");
364 } else {
365 debugs(3, DBG_IMPORTANT, "WARNING: cache_dir '" << path << "' "
366 "object " << option << " cannot be changed dynamically, " <<
367 "value left unchanged (" << *val << " Bytes)");
368 return true;
369 }
370 }
371
372 *val = size;
373
374 return true;
375 }
376
377 void
optionObjectSizeDump(StoreEntry * e) const378 Store::Disk::optionObjectSizeDump(StoreEntry * e) const
379 {
380 if (min_objsize != -1)
381 storeAppendPrintf(e, " min-size=%" PRId64, min_objsize);
382
383 if (max_objsize != -1)
384 storeAppendPrintf(e, " max-size=%" PRId64, max_objsize);
385 }
386
387 // some SwapDirs may maintain their indexes and be able to lookup an entry key
388 StoreEntry *
get(const cache_key *)389 Store::Disk::get(const cache_key *)
390 {
391 return NULL;
392 }
393
394