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