]>
Commit | Line | Data |
---|---|---|
8638fc66 | 1 | /* |
f70aedc4 | 2 | * Copyright (C) 1996-2021 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
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. | |
8638fc66 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 20 Store Rebuild Routines */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
c59baaa8 | 12 | #include "DebugMessages.h" |
a553a5a3 | 13 | #include "event.h" |
582c2af2 FC |
14 | #include "globals.h" |
15 | #include "md5.h" | |
602d9612 A |
16 | #include "SquidConfig.h" |
17 | #include "SquidTime.h" | |
e4f1fdae | 18 | #include "StatCounters.h" |
e6ccf245 | 19 | #include "Store.h" |
b3f7fd88 | 20 | #include "store/Disk.h" |
35a28a37 | 21 | #include "store_digest.h" |
602d9612 | 22 | #include "store_key_md5.h" |
687f5275 | 23 | #include "store_rebuild.h" |
c8f4eac4 | 24 | #include "StoreSearch.h" |
ed6e9fb9 AJ |
25 | // for tvSubDsec() which should be in SquidTime.h |
26 | #include "util.h" | |
f09f5b26 | 27 | |
1a30fdf5 AJ |
28 | #include <cerrno> |
29 | ||
211e9ce2 | 30 | static StoreRebuildData counts; |
62e76326 | 31 | |
9bea1d5b | 32 | static void storeCleanup(void *); |
f09f5b26 | 33 | |
8ecbe78d EB |
34 | // TODO: Either convert to Progress or replace with StoreRebuildData. |
35 | // TODO: Handle unknown totals (UFS cache_dir that lost swap.state) correctly. | |
26ac0430 | 36 | typedef struct { |
5a797987 | 37 | /* total number of "swap.state" entries that will be read */ |
38 | int total; | |
39 | /* number of entries read so far */ | |
40 | int scanned; | |
2fadd50d | 41 | } store_rebuild_progress; |
5a797987 | 42 | |
43 | static store_rebuild_progress *RebuildProgress = NULL; | |
44 | ||
8ecbe78d EB |
45 | void |
46 | StoreRebuildData::updateStartTime(const timeval &dirStartTime) | |
47 | { | |
48 | startTime = started() ? std::min(startTime, dirStartTime) : dirStartTime; | |
49 | } | |
50 | ||
12784378 | 51 | static void |
ced8def3 | 52 | storeCleanup(void *) |
f09f5b26 | 53 | { |
9bea1d5b | 54 | static int store_errors = 0; |
c8f4eac4 | 55 | static StoreSearchPointer currentSearch; |
56 | static int validated = 0; | |
52dce2fe | 57 | static int seen = 0; |
c8f4eac4 | 58 | |
59 | if (currentSearch == NULL || currentSearch->isDone()) | |
2745fea5 | 60 | currentSearch = Store::Root().search(); |
c8f4eac4 | 61 | |
62 | size_t statCount = 500; | |
63 | ||
26031546 | 64 | // TODO: Avoid the loop (and ENTRY_VALIDATED) unless opt_store_doublecheck. |
c8f4eac4 | 65 | while (statCount-- && !currentSearch->isDone() && currentSearch->next()) { |
c8f4eac4 | 66 | StoreEntry *e; |
67 | ||
68 | e = currentSearch->currentItem(); | |
69 | ||
52dce2fe AR |
70 | ++seen; |
71 | ||
c8f4eac4 | 72 | if (EBIT_TEST(e->flags, ENTRY_VALIDATED)) |
73 | continue; | |
74 | ||
75 | /* | |
5f33b71d | 76 | * Calling StoreEntry->release() has no effect because we're |
c8f4eac4 | 77 | * still in 'store_rebuilding' state |
78 | */ | |
4310f8b0 | 79 | if (!e->hasDisk()) |
c8f4eac4 | 80 | continue; |
81 | ||
82 | if (opt_store_doublecheck) | |
5d84beb5 | 83 | if (e->disk().doubleCheck(*e)) |
5db6bf73 | 84 | ++store_errors; |
c8f4eac4 | 85 | |
86 | EBIT_SET(e->flags, ENTRY_VALIDATED); | |
87 | ||
88 | /* | |
89 | * Only set the file bit if we know its a valid entry | |
90 | * otherwise, set it in the validation procedure | |
91 | */ | |
e2851fe7 | 92 | |
c8f4eac4 | 93 | if ((++validated & 0x3FFFF) == 0) |
94 | /* TODO format the int with with a stream operator */ | |
e0236918 | 95 | debugs(20, DBG_IMPORTANT, " " << validated << " Entries Validated so far."); |
f09f5b26 | 96 | } |
62e76326 | 97 | |
c8f4eac4 | 98 | if (currentSearch->isDone()) { |
52dce2fe | 99 | debugs(20, 2, "Seen: " << seen << " entries"); |
c59baaa8 EB |
100 | debugs(20, Important(43), "Completed Validation Procedure" << |
101 | Debug::Extra << "Validated " << validated << " Entries" << | |
102 | Debug::Extra << "store_swap_size = " << (Store::Root().currentSize()/1024.0) << " KB"); | |
5e263176 | 103 | --StoreController::store_dirs_rebuilding; |
bef81ea5 | 104 | assert(0 == StoreController::store_dirs_rebuilding); |
c8f4eac4 | 105 | |
2f6fcab1 AR |
106 | if (opt_store_doublecheck && store_errors) { |
107 | fatalf("Quitting after finding %d cache index inconsistencies. " \ | |
108 | "Removing cache index will force its slow rebuild. " \ | |
109 | "Removing -S will let Squid start with an inconsistent " \ | |
110 | "cache index (at your own risk).\n", store_errors); | |
111 | } | |
c8f4eac4 | 112 | |
113 | if (store_digest) | |
114 | storeDigestNoteStoreReady(); | |
115 | ||
116 | currentSearch = NULL; | |
117 | } else | |
118 | eventAdd("storeCleanup", storeCleanup, NULL, 0.0, 1); | |
f09f5b26 | 119 | } |
120 | ||
f09f5b26 | 121 | /* meta data recreated from disk image in swap directory */ |
b2c141d4 | 122 | void |
62e76326 | 123 | |
211e9ce2 | 124 | storeRebuildComplete(StoreRebuildData *dc) |
f09f5b26 | 125 | { |
8ecbe78d EB |
126 | if (dc) { |
127 | counts.objcount += dc->objcount; | |
128 | counts.expcount += dc->expcount; | |
129 | counts.scancount += dc->scancount; | |
130 | counts.clashcount += dc->clashcount; | |
131 | counts.dupcount += dc->dupcount; | |
132 | counts.cancelcount += dc->cancelcount; | |
133 | counts.invalid += dc->invalid; | |
134 | counts.badflags += dc->badflags; | |
135 | counts.bad_log_op += dc->bad_log_op; | |
136 | counts.zero_object_sz += dc->zero_object_sz; | |
137 | counts.validations += dc->validations; | |
138 | counts.updateStartTime(dc->startTime); | |
139 | } | |
140 | // else the caller was not responsible for indexing its cache_dir | |
141 | ||
142 | assert(StoreController::store_dirs_rebuilding > 1); | |
143 | --StoreController::store_dirs_rebuilding; | |
144 | ||
9bea1d5b | 145 | /* |
b07b21cc | 146 | * When store_dirs_rebuilding == 1, it means we are done reading |
9bea1d5b | 147 | * or scanning all cache_dirs. Now report the stats and start |
148 | * the validation (storeCleanup()) thread. | |
149 | */ | |
62e76326 | 150 | |
e4b1808b | 151 | if (StoreController::store_dirs_rebuilding > 1) |
62e76326 | 152 | return; |
153 | ||
8ecbe78d | 154 | const auto dt = tvSubDsec(counts.startTime, current_time); |
62e76326 | 155 | |
c59baaa8 EB |
156 | debugs(20, Important(46), "Finished rebuilding storage from disk." << |
157 | Debug::Extra << std::setw(7) << counts.scancount << " Entries scanned" << | |
158 | Debug::Extra << std::setw(7) << counts.invalid << " Invalid entries" << | |
159 | Debug::Extra << std::setw(7) << counts.badflags << " With invalid flags" << | |
160 | Debug::Extra << std::setw(7) << counts.objcount << " Objects loaded" << | |
161 | Debug::Extra << std::setw(7) << counts.expcount << " Objects expired" << | |
162 | Debug::Extra << std::setw(7) << counts.cancelcount << " Objects canceled" << | |
163 | Debug::Extra << std::setw(7) << counts.dupcount << " Duplicate URLs purged" << | |
164 | Debug::Extra << std::setw(7) << counts.clashcount << " Swapfile clashes avoided" << | |
165 | Debug::Extra << "Took " << std::setprecision(2) << dt << " seconds (" << | |
bf8fe701 | 166 | ((double) counts.objcount / (dt > 0.0 ? dt : 1.0)) << " objects/sec)."); |
c59baaa8 | 167 | debugs(20, Important(56), "Beginning Validation Procedure"); |
62e76326 | 168 | |
9bea1d5b | 169 | eventAdd("storeCleanup", storeCleanup, NULL, 0.0, 1); |
62e76326 | 170 | |
9bea1d5b | 171 | xfree(RebuildProgress); |
62e76326 | 172 | |
9bea1d5b | 173 | RebuildProgress = NULL; |
f09f5b26 | 174 | } |
175 | ||
b2c141d4 | 176 | /* |
177 | * this is ugly. We don't actually start any rebuild threads here, | |
178 | * but only initialize counters, etc. The rebuild threads are | |
179 | * actually started by the filesystem "fooDirInit" function. | |
180 | */ | |
f09f5b26 | 181 | void |
9bea1d5b | 182 | storeRebuildStart(void) |
f09f5b26 | 183 | { |
b56b37cf | 184 | counts = StoreRebuildData(); // reset counters |
9bea1d5b | 185 | /* |
b07b21cc | 186 | * Note: store_dirs_rebuilding is initialized to 1. |
26ac0430 AJ |
187 | * |
188 | * When we parse the configuration and construct each swap dir, | |
bef81ea5 | 189 | * the construction of that raises the rebuild count. |
190 | * | |
9bea1d5b | 191 | * This prevents us from trying to write clean logs until we |
bef81ea5 | 192 | * finished rebuilding - including after a reconfiguration that opens an |
193 | * existing swapdir. The corresponding decrement * occurs in | |
194 | * storeCleanup(), when it is finished. | |
9bea1d5b | 195 | */ |
e6ccf245 | 196 | RebuildProgress = (store_rebuild_progress *)xcalloc(Config.cacheSwap.n_configured, |
62e76326 | 197 | sizeof(store_rebuild_progress)); |
5a797987 | 198 | } |
199 | ||
200 | /* | |
201 | * A fs-specific rebuild procedure periodically reports its | |
202 | * progress. | |
203 | */ | |
204 | void | |
9bea1d5b | 205 | storeRebuildProgress(int sd_index, int total, int sofar) |
5a797987 | 206 | { |
9bea1d5b | 207 | static time_t last_report = 0; |
8ecbe78d | 208 | // TODO: Switch to int64_t and fix handling of unknown totals. |
9bea1d5b | 209 | double n = 0.0; |
210 | double d = 0.0; | |
62e76326 | 211 | |
9bea1d5b | 212 | if (sd_index < 0) |
62e76326 | 213 | return; |
214 | ||
9bea1d5b | 215 | if (sd_index >= Config.cacheSwap.n_configured) |
62e76326 | 216 | return; |
217 | ||
9bea1d5b | 218 | if (NULL == RebuildProgress) |
62e76326 | 219 | return; |
220 | ||
9bea1d5b | 221 | RebuildProgress[sd_index].total = total; |
62e76326 | 222 | |
9bea1d5b | 223 | RebuildProgress[sd_index].scanned = sofar; |
62e76326 | 224 | |
9bea1d5b | 225 | if (squid_curtime - last_report < 15) |
62e76326 | 226 | return; |
227 | ||
5db6bf73 | 228 | for (sd_index = 0; sd_index < Config.cacheSwap.n_configured; ++sd_index) { |
62e76326 | 229 | n += (double) RebuildProgress[sd_index].scanned; |
230 | d += (double) RebuildProgress[sd_index].total; | |
5a797987 | 231 | } |
62e76326 | 232 | |
c59baaa8 | 233 | debugs(20, Important(57), "Indexing cache entries: " << Progress(n, d)); |
9bea1d5b | 234 | last_report = squid_curtime; |
f09f5b26 | 235 | } |
e2851fe7 | 236 | |
8ecbe78d EB |
237 | void |
238 | Progress::print(std::ostream &os) const | |
239 | { | |
240 | if (goal > 0) { | |
9fa45f86 | 241 | const auto savedPrecision = os.precision(2); |
8ecbe78d | 242 | const auto percent = 100.0 * completed / goal; |
9fa45f86 | 243 | os << percent << "% (" << completed << " out of " << goal << ")"; |
70ac5b29 | 244 | (void)os.precision(savedPrecision); |
8ecbe78d EB |
245 | } else if (!completed && !goal) { |
246 | os << "nothing to do"; | |
247 | } else { | |
248 | // unknown (i.e. negative) or buggy (i.e. zero when completed != 0) goal | |
249 | os << completed; | |
250 | } | |
251 | } | |
252 | ||
e2851fe7 | 253 | #include "fde.h" |
e2851fe7 | 254 | #include "Generic.h" |
602d9612 A |
255 | #include "StoreMeta.h" |
256 | #include "StoreMetaUnpacker.h" | |
e2851fe7 AR |
257 | |
258 | struct InitStoreEntry : public unary_function<StoreMeta, void> { | |
259 | InitStoreEntry(StoreEntry *anEntry, cache_key *aKey):what(anEntry),index(aKey) {} | |
260 | ||
261 | void operator()(StoreMeta const &x) { | |
262 | switch (x.getType()) { | |
263 | ||
264 | case STORE_META_KEY: | |
265 | assert(x.length == SQUID_MD5_DIGEST_LENGTH); | |
266 | memcpy(index, x.value, SQUID_MD5_DIGEST_LENGTH); | |
267 | break; | |
268 | ||
269 | case STORE_META_STD: | |
270 | struct old_metahdr { | |
271 | time_t timestamp; | |
272 | time_t lastref; | |
273 | time_t expires; | |
274 | time_t lastmod; | |
275 | size_t swap_file_sz; | |
89924985 AR |
276 | uint16_t refcount; |
277 | uint16_t flags; | |
e2851fe7 AR |
278 | } *tmp; |
279 | tmp = (struct old_metahdr *)x.value; | |
280 | assert(x.length == STORE_HDR_METASIZE_OLD); | |
281 | what->timestamp = tmp->timestamp; | |
282 | what->lastref = tmp->lastref; | |
283 | what->expires = tmp->expires; | |
438b41ba | 284 | what->lastModified(tmp->lastmod); |
e2851fe7 AR |
285 | what->swap_file_sz = tmp->swap_file_sz; |
286 | what->refcount = tmp->refcount; | |
287 | what->flags = tmp->flags; | |
288 | break; | |
289 | ||
290 | case STORE_META_STD_LFS: | |
291 | assert(x.length == STORE_HDR_METASIZE); | |
292 | memcpy(&what->timestamp, x.value, STORE_HDR_METASIZE); | |
293 | break; | |
294 | ||
295 | default: | |
296 | break; | |
297 | } | |
298 | } | |
299 | ||
300 | StoreEntry *what; | |
301 | cache_key *index; | |
302 | }; | |
303 | ||
304 | bool | |
3254023f | 305 | storeRebuildLoadEntry(int fd, int diskIndex, MemBuf &buf, StoreRebuildData &) |
e2851fe7 AR |
306 | { |
307 | if (fd < 0) | |
308 | return false; | |
309 | ||
aa1a691e | 310 | assert(buf.hasSpace()); // caller must allocate |
e2851fe7 | 311 | |
aa1a691e | 312 | const int len = FD_READ_METHOD(fd, buf.space(), buf.spaceSize()); |
5db6bf73 | 313 | ++ statCounter.syscalls.disk.reads; |
aa1a691e AR |
314 | if (len < 0) { |
315 | const int xerrno = errno; | |
28edca61 | 316 | debugs(47, DBG_IMPORTANT, "WARNING: cache_dir[" << diskIndex << "]: " << |
9199139f | 317 | "Ignoring cached entry after meta data read failure: " << xstrerr(xerrno)); |
e2851fe7 AR |
318 | return false; |
319 | } | |
320 | ||
aa1a691e AR |
321 | buf.appended(len); |
322 | return true; | |
323 | } | |
324 | ||
325 | bool | |
326 | storeRebuildParseEntry(MemBuf &buf, StoreEntry &tmpe, cache_key *key, | |
3254023f | 327 | StoreRebuildData &stats, |
aa1a691e AR |
328 | uint64_t expectedSize) |
329 | { | |
e2851fe7 | 330 | int swap_hdr_len = 0; |
aa1a691e | 331 | StoreMetaUnpacker aBuilder(buf.content(), buf.contentSize(), &swap_hdr_len); |
e2851fe7 AR |
332 | if (aBuilder.isBufferZero()) { |
333 | debugs(47,5, HERE << "skipping empty record."); | |
334 | return false; | |
335 | } | |
336 | ||
4c2f8b72 EB |
337 | StoreMeta *tlv_list = nullptr; |
338 | try { | |
339 | tlv_list = aBuilder.createStoreMeta(); | |
340 | } catch (const std::exception &e) { | |
341 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring store entry because " << e.what()); | |
e2851fe7 AR |
342 | return false; |
343 | } | |
4c2f8b72 | 344 | assert(tlv_list); |
e2851fe7 | 345 | |
aa1a691e AR |
346 | // TODO: consume parsed metadata? |
347 | ||
539283df | 348 | debugs(47,7, "successful swap meta unpacking; swap_file_sz=" << tmpe.swap_file_sz); |
e2851fe7 AR |
349 | memset(key, '\0', SQUID_MD5_DIGEST_LENGTH); |
350 | ||
351 | InitStoreEntry visitor(&tmpe, key); | |
352 | for_each(*tlv_list, visitor); | |
353 | storeSwapTLVFree(tlv_list); | |
354 | tlv_list = NULL; | |
355 | ||
356 | if (storeKeyNull(key)) { | |
28edca61 | 357 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring keyless cache entry"); |
e2851fe7 AR |
358 | return false; |
359 | } | |
360 | ||
361 | tmpe.key = key; | |
362 | /* check sizes */ | |
363 | ||
364 | if (expectedSize > 0) { | |
365 | if (tmpe.swap_file_sz == 0) { | |
366 | tmpe.swap_file_sz = expectedSize; | |
367 | } else if (tmpe.swap_file_sz == (uint64_t)(expectedSize - swap_hdr_len)) { | |
368 | tmpe.swap_file_sz = expectedSize; | |
369 | } else if (tmpe.swap_file_sz != expectedSize) { | |
28edca61 AR |
370 | debugs(47, DBG_IMPORTANT, "WARNING: Ignoring cache entry due to a " << |
371 | "SIZE MISMATCH " << tmpe.swap_file_sz << "!=" << expectedSize); | |
e2851fe7 AR |
372 | return false; |
373 | } | |
9199139f | 374 | } else if (tmpe.swap_file_sz <= 0) { |
52dce2fe AR |
375 | // if caller cannot handle unknown sizes, it must check after the call. |
376 | debugs(47, 7, "unknown size: " << tmpe); | |
e2851fe7 AR |
377 | } |
378 | ||
379 | if (EBIT_TEST(tmpe.flags, KEY_PRIVATE)) { | |
3254023f | 380 | ++ stats.badflags; |
e2851fe7 AR |
381 | return false; |
382 | } | |
383 | ||
384 | return true; | |
385 | } | |
386 |