]> git.ipfire.org Git - thirdparty/squid.git/blob - src/fs/ufs/UFSSwapDir.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / fs / ufs / UFSSwapDir.cc
1 /*
2 * Copyright (C) 1996-2017 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 47 Store Directory Routines */
10
11 #define CLEAN_BUF_SZ 16384
12
13 #include "squid.h"
14 #include "cache_cf.h"
15 #include "ConfigOption.h"
16 #include "DiskIO/DiskIOModule.h"
17 #include "DiskIO/DiskIOStrategy.h"
18 #include "fde.h"
19 #include "FileMap.h"
20 #include "fs_io.h"
21 #include "globals.h"
22 #include "Parsing.h"
23 #include "RebuildState.h"
24 #include "SquidConfig.h"
25 #include "SquidMath.h"
26 #include "SquidTime.h"
27 #include "StatCounters.h"
28 #include "store_key_md5.h"
29 #include "StoreSearchUFS.h"
30 #include "StoreSwapLogData.h"
31 #include "tools.h"
32 #include "UFSSwapDir.h"
33
34 #include <cerrno>
35 #include <cmath>
36 #include <random>
37 #if HAVE_SYS_STAT_H
38 #include <sys/stat.h>
39 #endif
40
41 int Fs::Ufs::UFSSwapDir::NumberOfUFSDirs = 0;
42 int *Fs::Ufs::UFSSwapDir::UFSDirToGlobalDirMapping = NULL;
43
44 class UFSCleanLog : public SwapDir::CleanLog
45 {
46
47 public:
48 UFSCleanLog(SwapDir *);
49 /** Get the next entry that is a candidate for clean log writing
50 */
51 virtual const StoreEntry *nextEntry();
52 /** "write" an entry to the clean log file.
53 */
54 virtual void write(StoreEntry const &);
55 char *cur;
56 char *newLog;
57 char *cln;
58 char *outbuf;
59 off_t outbuf_offset;
60 int fd;
61 RemovalPolicyWalker *walker;
62 SwapDir *sd;
63 };
64
65 UFSCleanLog::UFSCleanLog(SwapDir *aSwapDir) :
66 cur(NULL), newLog(NULL), cln(NULL), outbuf(NULL),
67 outbuf_offset(0), fd(-1),walker(NULL), sd(aSwapDir)
68 {}
69
70 const StoreEntry *
71 UFSCleanLog::nextEntry()
72 {
73 const StoreEntry *entry = NULL;
74
75 if (walker)
76 entry = walker->Next(walker);
77
78 return entry;
79 }
80
81 void
82 UFSCleanLog::write(StoreEntry const &e)
83 {
84 StoreSwapLogData s;
85 static size_t ss = sizeof(StoreSwapLogData);
86 s.op = (char) SWAP_LOG_ADD;
87 s.swap_filen = e.swap_filen;
88 s.timestamp = e.timestamp;
89 s.lastref = e.lastref;
90 s.expires = e.expires;
91 s.lastmod = e.lastModified();
92 s.swap_file_sz = e.swap_file_sz;
93 s.refcount = e.refcount;
94 s.flags = e.flags;
95 memcpy(&s.key, e.key, SQUID_MD5_DIGEST_LENGTH);
96 s.finalize();
97 memcpy(outbuf + outbuf_offset, &s, ss);
98 outbuf_offset += ss;
99 /* buffered write */
100
101 if (outbuf_offset + ss >= CLEAN_BUF_SZ) {
102 if (FD_WRITE_METHOD(fd, outbuf, outbuf_offset) < 0) {
103 int xerrno = errno;
104 /* XXX This error handling should probably move up to the caller */
105 debugs(50, DBG_CRITICAL, MYNAME << newLog << ": write: " << xstrerr(xerrno));
106 debugs(50, DBG_CRITICAL, MYNAME << "Current swap logfile not replaced.");
107 file_close(fd);
108 fd = -1;
109 unlink(newLog);
110 sd->cleanLog = NULL;
111 delete this;
112 return;
113 }
114
115 outbuf_offset = 0;
116 }
117 }
118
119 bool
120 Fs::Ufs::UFSSwapDir::canStore(const StoreEntry &e, int64_t diskSpaceNeeded, int &load) const
121 {
122 if (!SwapDir::canStore(e, diskSpaceNeeded, load))
123 return false;
124
125 if (IO->shedLoad())
126 return false;
127
128 load = IO->load();
129 return true;
130 }
131
132 static void
133 FreeObject(void *address)
134 {
135 StoreSwapLogData *anObject = static_cast <StoreSwapLogData *>(address);
136 delete anObject;
137 }
138
139 static int
140 rev_int_sort(const void *A, const void *B)
141 {
142 const int *i1 = (const int *)A;
143 const int *i2 = (const int *)B;
144 return *i2 - *i1;
145 }
146
147 void
148 Fs::Ufs::UFSSwapDir::parseSizeL1L2()
149 {
150 int i = GetInteger();
151 if (i <= 0)
152 fatal("UFSSwapDir::parseSizeL1L2: invalid size value");
153
154 const uint64_t size = static_cast<uint64_t>(i) << 20; // MBytes to Bytes
155
156 /* just reconfigure it */
157 if (reconfiguring) {
158 if (size == maxSize())
159 debugs(3, 2, "Cache dir '" << path << "' size remains unchanged at " << i << " MB");
160 else
161 debugs(3, DBG_IMPORTANT, "Cache dir '" << path << "' size changed to " << i << " MB");
162 }
163
164 max_size = size;
165
166 l1 = GetInteger();
167
168 if (l1 <= 0)
169 fatal("UFSSwapDir::parseSizeL1L2: invalid level 1 directories value");
170
171 l2 = GetInteger();
172
173 if (l2 <= 0)
174 fatal("UFSSwapDir::parseSizeL1L2: invalid level 2 directories value");
175 }
176
177 void
178 Fs::Ufs::UFSSwapDir::reconfigure()
179 {
180 parseSizeL1L2();
181 parseOptions(1);
182 }
183
184 void
185 Fs::Ufs::UFSSwapDir::parse (int anIndex, char *aPath)
186 {
187 index = anIndex;
188 path = xstrdup(aPath);
189
190 parseSizeL1L2();
191
192 /* Initialise replacement policy stuff */
193 repl = createRemovalPolicy(Config.replPolicy);
194
195 parseOptions(0);
196 }
197
198 void
199 Fs::Ufs::UFSSwapDir::changeIO(DiskIOModule *module)
200 {
201 DiskIOStrategy *anIO = module->createStrategy();
202 safe_free(ioType);
203 ioType = xstrdup(module->type());
204
205 delete IO->io;
206 IO->io = anIO;
207 /* Change the IO Options */
208
209 if (currentIOOptions && currentIOOptions->options.size() > 2) {
210 delete currentIOOptions->options.back();
211 currentIOOptions->options.pop_back();
212 }
213
214 /* TODO: factor out these 4 lines */
215 ConfigOption *ioOptions = IO->io->getOptionTree();
216
217 if (currentIOOptions && ioOptions)
218 currentIOOptions->options.push_back(ioOptions);
219 }
220
221 bool
222 Fs::Ufs::UFSSwapDir::optionIOParse(char const *option, const char *value, int isaReconfig)
223 {
224 if (strcmp(option, "IOEngine") != 0)
225 return false;
226
227 if (isaReconfig)
228 /* silently ignore this */
229 return true;
230
231 if (!value)
232 self_destruct();
233
234 DiskIOModule *module = DiskIOModule::Find(value);
235
236 if (!module)
237 self_destruct();
238
239 changeIO(module);
240
241 return true;
242 }
243
244 void
245 Fs::Ufs::UFSSwapDir::optionIODump(StoreEntry * e) const
246 {
247 storeAppendPrintf(e, " IOEngine=%s", ioType);
248 }
249
250 ConfigOption *
251 Fs::Ufs::UFSSwapDir::getOptionTree() const
252 {
253 ConfigOption *parentResult = SwapDir::getOptionTree();
254
255 if (currentIOOptions == NULL)
256 currentIOOptions = new ConfigOptionVector();
257
258 currentIOOptions->options.push_back(parentResult);
259
260 currentIOOptions->options.push_back(new ConfigOptionAdapter<UFSSwapDir>(*const_cast<UFSSwapDir *>(this), &UFSSwapDir::optionIOParse, &UFSSwapDir::optionIODump));
261
262 if (ConfigOption *ioOptions = IO->io->getOptionTree())
263 currentIOOptions->options.push_back(ioOptions);
264
265 ConfigOption* result = currentIOOptions;
266
267 currentIOOptions = NULL;
268
269 return result;
270 }
271
272 void
273 Fs::Ufs::UFSSwapDir::init()
274 {
275 debugs(47, 3, HERE << "Initialising UFS SwapDir engine.");
276 /* Parsing must be finished by now - force to NULL, don't delete */
277 currentIOOptions = NULL;
278 static int started_clean_event = 0;
279 static const char *errmsg =
280 "\tFailed to verify one of the swap directories, Check cache.log\n"
281 "\tfor details. Run 'squid -z' to create swap directories\n"
282 "\tif needed, or if running Squid for the first time.";
283 IO->init();
284
285 if (verifyCacheDirs())
286 fatal(errmsg);
287
288 openLog();
289
290 rebuild();
291
292 if (!started_clean_event) {
293 eventAdd("UFS storeDirClean", CleanEvent, NULL, 15.0, 1);
294 started_clean_event = 1;
295 }
296
297 (void) fsBlockSize(path, &fs.blksize);
298 }
299
300 void
301 Fs::Ufs::UFSSwapDir::create()
302 {
303 debugs(47, 3, "Creating swap space in " << path);
304 createDirectory(path, 0);
305 createSwapSubDirs();
306 }
307
308 Fs::Ufs::UFSSwapDir::UFSSwapDir(char const *aType, const char *anIOType) :
309 SwapDir(aType),
310 IO(NULL),
311 fsdata(NULL),
312 map(new FileMap()),
313 suggest(0),
314 l1(16),
315 l2(256),
316 swaplog_fd(-1),
317 currentIOOptions(new ConfigOptionVector()),
318 ioType(xstrdup(anIOType)),
319 cur_size(0),
320 n_disk_objects(0),
321 rebuilding_(false)
322 {
323 /* modulename is only set to disk modules that are built, by configure,
324 * so the Find call should never return NULL here.
325 */
326 IO = new Fs::Ufs::UFSStrategy(DiskIOModule::Find(anIOType)->createStrategy());
327 }
328
329 Fs::Ufs::UFSSwapDir::~UFSSwapDir()
330 {
331 if (swaplog_fd > -1) {
332 file_close(swaplog_fd);
333 swaplog_fd = -1;
334 }
335 xfree(ioType);
336 delete map;
337 delete IO;
338 delete currentIOOptions;
339 }
340
341 void
342 Fs::Ufs::UFSSwapDir::dumpEntry(StoreEntry &e) const
343 {
344 debugs(47, DBG_CRITICAL, HERE << "FILENO "<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << e.swap_filen);
345 debugs(47, DBG_CRITICAL, HERE << "PATH " << fullPath(e.swap_filen, NULL) );
346 e.dump(0);
347 }
348
349 bool
350 Fs::Ufs::UFSSwapDir::doubleCheck(StoreEntry & e)
351 {
352
353 struct stat sb;
354
355 if (::stat(fullPath(e.swap_filen, NULL), &sb) < 0) {
356 debugs(47, DBG_CRITICAL, HERE << "WARNING: Missing swap file");
357 dumpEntry(e);
358 return true;
359 }
360
361 if ((off_t)e.swap_file_sz != sb.st_size) {
362 debugs(47, DBG_CRITICAL, HERE << "WARNING: Size Mismatch. Entry size: "
363 << e.swap_file_sz << ", file size: " << sb.st_size);
364 dumpEntry(e);
365 return true;
366 }
367
368 return false;
369 }
370
371 void
372 Fs::Ufs::UFSSwapDir::statfs(StoreEntry & sentry) const
373 {
374 int totl_kb = 0;
375 int free_kb = 0;
376 int totl_in = 0;
377 int free_in = 0;
378 int x;
379 storeAppendPrintf(&sentry, "First level subdirectories: %d\n", l1);
380 storeAppendPrintf(&sentry, "Second level subdirectories: %d\n", l2);
381 storeAppendPrintf(&sentry, "Maximum Size: %" PRIu64 " KB\n", maxSize() >> 10);
382 storeAppendPrintf(&sentry, "Current Size: %.2f KB\n", currentSize() / 1024.0);
383 storeAppendPrintf(&sentry, "Percent Used: %0.2f%%\n",
384 Math::doublePercent(currentSize(), maxSize()));
385 storeAppendPrintf(&sentry, "Filemap bits in use: %d of %d (%d%%)\n",
386 map->numFilesInMap(), map->capacity(),
387 Math::intPercent(map->numFilesInMap(), map->capacity()));
388 x = fsStats(path, &totl_kb, &free_kb, &totl_in, &free_in);
389
390 if (0 == x) {
391 storeAppendPrintf(&sentry, "Filesystem Space in use: %d/%d KB (%d%%)\n",
392 totl_kb - free_kb,
393 totl_kb,
394 Math::intPercent(totl_kb - free_kb, totl_kb));
395 storeAppendPrintf(&sentry, "Filesystem Inodes in use: %d/%d (%d%%)\n",
396 totl_in - free_in,
397 totl_in,
398 Math::intPercent(totl_in - free_in, totl_in));
399 }
400
401 storeAppendPrintf(&sentry, "Flags:");
402
403 if (flags.selected)
404 storeAppendPrintf(&sentry, " SELECTED");
405
406 if (flags.read_only)
407 storeAppendPrintf(&sentry, " READ-ONLY");
408
409 storeAppendPrintf(&sentry, "\n");
410
411 IO->statfs(sentry);
412 }
413
414 void
415 Fs::Ufs::UFSSwapDir::maintain()
416 {
417 /* TODO: possible options for improvement;
418 *
419 * Note that too much aggression here is not good. It means that disk
420 * controller is getting a long queue of removals to act on, along
421 * with its regular I/O queue, and that client traffic is 'paused'
422 * and growing the network I/O queue as well while the scan happens.
423 * Possibly bad knock-on effects as Squid catches up on all that.
424 *
425 * Bug 2448 may have been a sign of what can wrong. At the least it
426 * provides a test case for aggression effects in overflow conditions.
427 *
428 * - base removal limit on space saved, instead of count ?
429 *
430 * - base removal rate on a traffic speed counter ?
431 * as the purge took up more time out of the second it would grow to
432 * a graceful full pause
433 *
434 * - pass out a value to cause another event to be scheduled immediately
435 * instead of waiting a whole second more ?
436 * knock on; schedule less if all caches are under low-water
437 *
438 * - admin configurable removal rate or count ?
439 * the current numbers are arbitrary, config helps with experimental
440 * trials and future-proofing the install base.
441 * we also have this indirectly by shifting the relative positions
442 * of low-, high- water and the total capacity limit.
443 */
444
445 // minSize() is swap_low_watermark in bytes
446 const uint64_t lowWaterSz = minSize();
447
448 if (currentSize() < lowWaterSz) {
449 debugs(47, 5, "space still available in " << path);
450 return;
451 }
452
453 /* We can't delete objects while rebuilding swap */
454 /* XXX each store should start maintaining as it comes online. */
455 if (StoreController::store_dirs_rebuilding) {
456 // suppress the warnings, except once each minute
457 static int64_t lastWarn = 0;
458 int warnLevel = 3;
459 if (lastWarn+60 < squid_curtime) {
460 lastWarn = squid_curtime;
461 warnLevel = DBG_IMPORTANT;
462 }
463 debugs(47, warnLevel, StoreController::store_dirs_rebuilding << " cache_dir still rebuilding. Skip GC for " << path);
464 return;
465 }
466
467 // maxSize() is cache_dir total size in bytes
468 const uint64_t highWaterSz = ((maxSize() * Config.Swap.highWaterMark) / 100);
469
470 // f is percentage of 'gap' filled between low- and high-water.
471 // Used to reduced purge rate when between water markers, and
472 // to multiply it more agressively the further above high-water
473 // it reaches. But in a graceful linear growth curve.
474 double f = 1.0;
475 if (highWaterSz > lowWaterSz) {
476 // might be equal. n/0 is bad.
477 f = (double) (currentSize() - lowWaterSz) / (highWaterSz - lowWaterSz);
478 }
479
480 // how deep to look for a single object that can be removed
481 int max_scan = (int) (f * 400.0 + 100.0);
482
483 // try to purge only this many objects this cycle.
484 int max_remove = (int) (f * 300.0 + 20.0);
485
486 /*
487 * This is kinda cheap, but so we need this priority hack?
488 */
489 debugs(47, 3, "f=" << f << ", max_scan=" << max_scan << ", max_remove=" << max_remove);
490
491 RemovalPurgeWalker *walker = repl->PurgeInit(repl, max_scan);
492
493 int removed = 0;
494 // only purge while above low-water
495 while (currentSize() >= lowWaterSz) {
496
497 // stop if we reached max removals for this cycle,
498 // Bug 2448 may be from this not clearing enough,
499 // but it predates the current algorithm so not sure
500 if (removed >= max_remove)
501 break;
502
503 StoreEntry *e = walker->Next(walker);
504
505 // stop if all objects are locked / in-use,
506 // or the cache is empty
507 if (!e)
508 break; /* no more objects */
509
510 ++removed;
511
512 e->release();
513 }
514
515 walker->Done(walker);
516 debugs(47, (removed ? 2 : 3), path <<
517 " removed " << removed << "/" << max_remove << " f=" <<
518 std::setprecision(4) << f << " max_scan=" << max_scan);
519
520 // what if cache is still over the high watermark ?
521 // Store::Maintain() schedules another purge in 1 second.
522 }
523
524 void
525 Fs::Ufs::UFSSwapDir::reference(StoreEntry &e)
526 {
527 debugs(47, 3, HERE << "referencing " << &e << " " <<
528 e.swap_dirn << "/" << e.swap_filen);
529
530 if (repl->Referenced)
531 repl->Referenced(repl, &e, &e.repl);
532 }
533
534 bool
535 Fs::Ufs::UFSSwapDir::dereference(StoreEntry & e)
536 {
537 debugs(47, 3, HERE << "dereferencing " << &e << " " <<
538 e.swap_dirn << "/" << e.swap_filen);
539
540 if (repl->Dereferenced)
541 repl->Dereferenced(repl, &e, &e.repl);
542
543 return true; // keep e in the global store_table
544 }
545
546 StoreIOState::Pointer
547 Fs::Ufs::UFSSwapDir::createStoreIO(StoreEntry &e, StoreIOState::STFNCB * file_callback, StoreIOState::STIOCB * aCallback, void *callback_data)
548 {
549 return IO->create (this, &e, file_callback, aCallback, callback_data);
550 }
551
552 StoreIOState::Pointer
553 Fs::Ufs::UFSSwapDir::openStoreIO(StoreEntry &e, StoreIOState::STFNCB * file_callback, StoreIOState::STIOCB * aCallback, void *callback_data)
554 {
555 return IO->open (this, &e, file_callback, aCallback, callback_data);
556 }
557
558 int
559 Fs::Ufs::UFSSwapDir::mapBitTest(sfileno filn)
560 {
561 return map->testBit(filn);
562 }
563
564 void
565 Fs::Ufs::UFSSwapDir::mapBitSet(sfileno filn)
566 {
567 map->setBit(filn);
568 }
569
570 void
571 Fs::Ufs::UFSSwapDir::mapBitReset(sfileno filn)
572 {
573 /*
574 * We have to test the bit before calling clearBit as
575 * it doesn't do bounds checking and blindly assumes
576 * filn is a valid file number, but it might not be because
577 * the map is dynamic in size. Also clearing an already clear
578 * bit puts the map counter of-of-whack.
579 */
580
581 if (map->testBit(filn))
582 map->clearBit(filn);
583 }
584
585 int
586 Fs::Ufs::UFSSwapDir::mapBitAllocate()
587 {
588 int fn;
589 fn = map->allocate(suggest);
590 map->setBit(fn);
591 suggest = fn + 1;
592 return fn;
593 }
594
595 char *
596 Fs::Ufs::UFSSwapDir::swapSubDir(int subdirn)const
597 {
598 LOCAL_ARRAY(char, fullfilename, MAXPATHLEN);
599 assert(0 <= subdirn && subdirn < l1);
600 snprintf(fullfilename, MAXPATHLEN, "%s/%02X", path, subdirn);
601 return fullfilename;
602 }
603
604 int
605 Fs::Ufs::UFSSwapDir::createDirectory(const char *aPath, int should_exist)
606 {
607 int created = 0;
608
609 struct stat st;
610 getCurrentTime();
611
612 if (0 == ::stat(aPath, &st)) {
613 if (S_ISDIR(st.st_mode)) {
614 debugs(47, (should_exist ? 3 : DBG_IMPORTANT), aPath << " exists");
615 } else {
616 fatalf("Swap directory %s is not a directory.", aPath);
617 }
618 } else if (0 == mkdir(aPath, 0755)) {
619 debugs(47, (should_exist ? DBG_IMPORTANT : 3), aPath << " created");
620 created = 1;
621 } else {
622 int xerrno = errno;
623 fatalf("Failed to make swap directory %s: %s", aPath, xstrerr(xerrno));
624 }
625
626 return created;
627 }
628
629 bool
630 Fs::Ufs::UFSSwapDir::pathIsDirectory(const char *aPath)const
631 {
632
633 struct stat sb;
634
635 if (::stat(aPath, &sb) < 0) {
636 int xerrno = errno;
637 debugs(47, DBG_CRITICAL, "ERROR: " << aPath << ": " << xstrerr(xerrno));
638 return false;
639 }
640
641 if (S_ISDIR(sb.st_mode) == 0) {
642 debugs(47, DBG_CRITICAL, "WARNING: " << aPath << " is not a directory");
643 return false;
644 }
645
646 return true;
647 }
648
649 bool
650 Fs::Ufs::UFSSwapDir::verifyCacheDirs()
651 {
652 if (!pathIsDirectory(path))
653 return true;
654
655 for (int j = 0; j < l1; ++j) {
656 char const *aPath = swapSubDir(j);
657
658 if (!pathIsDirectory(aPath))
659 return true;
660 }
661
662 return false;
663 }
664
665 void
666 Fs::Ufs::UFSSwapDir::createSwapSubDirs()
667 {
668 LOCAL_ARRAY(char, name, MAXPATHLEN);
669
670 for (int i = 0; i < l1; ++i) {
671 snprintf(name, MAXPATHLEN, "%s/%02X", path, i);
672
673 int should_exist;
674
675 if (createDirectory(name, 0))
676 should_exist = 0;
677 else
678 should_exist = 1;
679
680 debugs(47, DBG_IMPORTANT, "Making directories in " << name);
681
682 for (int k = 0; k < l2; ++k) {
683 snprintf(name, MAXPATHLEN, "%s/%02X/%02X", path, i, k);
684 createDirectory(name, should_exist);
685 }
686 }
687 }
688
689 char *
690 Fs::Ufs::UFSSwapDir::logFile(char const *ext) const
691 {
692 LOCAL_ARRAY(char, lpath, MAXPATHLEN);
693 LOCAL_ARRAY(char, pathtmp, MAXPATHLEN);
694 LOCAL_ARRAY(char, digit, 32);
695 char *pathtmp2;
696
697 if (Config.Log.swap) {
698 xstrncpy(pathtmp, path, MAXPATHLEN - 64);
699 pathtmp2 = pathtmp;
700
701 while ((pathtmp2 = strchr(pathtmp2, '/')) != NULL)
702 *pathtmp2 = '.';
703
704 while (strlen(pathtmp) && pathtmp[strlen(pathtmp) - 1] == '.')
705 pathtmp[strlen(pathtmp) - 1] = '\0';
706
707 for (pathtmp2 = pathtmp; *pathtmp2 == '.'; ++pathtmp2);
708 snprintf(lpath, MAXPATHLEN - 64, Config.Log.swap, pathtmp2);
709
710 if (strncmp(lpath, Config.Log.swap, MAXPATHLEN - 64) == 0) {
711 strcat(lpath, ".");
712 snprintf(digit, 32, "%02d", index);
713 strncat(lpath, digit, 3);
714 }
715 } else {
716 xstrncpy(lpath, path, MAXPATHLEN - 64);
717 strcat(lpath, "/swap.state");
718 }
719
720 if (ext)
721 strncat(lpath, ext, 16);
722
723 return lpath;
724 }
725
726 void
727 Fs::Ufs::UFSSwapDir::openLog()
728 {
729 assert(NumberOfUFSDirs || !UFSDirToGlobalDirMapping);
730 ++NumberOfUFSDirs;
731 assert(NumberOfUFSDirs <= Config.cacheSwap.n_configured);
732
733 if (rebuilding_) { // we did not close the temporary log used for rebuilding
734 assert(swaplog_fd >= 0);
735 return;
736 }
737
738 char *logPath;
739 logPath = logFile();
740 swaplog_fd = file_open(logPath, O_WRONLY | O_CREAT | O_BINARY);
741
742 if (swaplog_fd < 0) {
743 int xerrno = errno;
744 debugs(50, DBG_IMPORTANT, "ERROR opening swap log " << logPath << ": " << xstrerr(xerrno));
745 fatal("UFSSwapDir::openLog: Failed to open swap log.");
746 }
747
748 debugs(50, 3, HERE << "Cache Dir #" << index << " log opened on FD " << swaplog_fd);
749 }
750
751 void
752 Fs::Ufs::UFSSwapDir::closeLog()
753 {
754 if (swaplog_fd < 0) /* not open */
755 return;
756
757 --NumberOfUFSDirs;
758 assert(NumberOfUFSDirs >= 0);
759 if (!NumberOfUFSDirs)
760 safe_free(UFSDirToGlobalDirMapping);
761
762 if (rebuilding_) // we cannot close the temporary log used for rebuilding
763 return;
764
765 file_close(swaplog_fd);
766
767 debugs(47, 3, "Cache Dir #" << index << " log closed on FD " << swaplog_fd);
768
769 swaplog_fd = -1;
770 }
771
772 bool
773 Fs::Ufs::UFSSwapDir::validL1(int anInt) const
774 {
775 return anInt < l1;
776 }
777
778 bool
779 Fs::Ufs::UFSSwapDir::validL2(int anInt) const
780 {
781 return anInt < l2;
782 }
783
784 StoreEntry *
785 Fs::Ufs::UFSSwapDir::addDiskRestore(const cache_key * key,
786 sfileno file_number,
787 uint64_t swap_file_sz,
788 time_t expires,
789 time_t timestamp,
790 time_t lastref,
791 time_t lastmod,
792 uint32_t refcount,
793 uint16_t newFlags,
794 int)
795 {
796 StoreEntry *e = NULL;
797 debugs(47, 5, HERE << storeKeyText(key) <<
798 ", fileno="<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << file_number);
799 /* if you call this you'd better be sure file_number is not
800 * already in use! */
801 e = new StoreEntry();
802 e->store_status = STORE_OK;
803 e->setMemStatus(NOT_IN_MEMORY);
804 e->swap_status = SWAPOUT_DONE;
805 e->swap_filen = file_number;
806 e->swap_dirn = index;
807 e->swap_file_sz = swap_file_sz;
808 e->lastref = lastref;
809 e->timestamp = timestamp;
810 e->expires = expires;
811 e->lastModified(lastmod);
812 e->refcount = refcount;
813 e->flags = newFlags;
814 EBIT_CLR(e->flags, RELEASE_REQUEST);
815 EBIT_CLR(e->flags, KEY_PRIVATE);
816 e->ping_status = PING_NONE;
817 EBIT_CLR(e->flags, ENTRY_VALIDATED);
818 mapBitSet(e->swap_filen);
819 cur_size += fs.blksize * sizeInBlocks(e->swap_file_sz);
820 ++n_disk_objects;
821 e->hashInsert(key); /* do it after we clear KEY_PRIVATE */
822 replacementAdd (e);
823 return e;
824 }
825
826 void
827 Fs::Ufs::UFSSwapDir::undoAddDiskRestore(StoreEntry *e)
828 {
829 debugs(47, 5, HERE << *e);
830 replacementRemove(e); // checks swap_dirn so do it before we invalidate it
831 // Do not unlink the file as it might be used by a subsequent entry.
832 mapBitReset(e->swap_filen);
833 e->swap_filen = -1;
834 e->swap_dirn = -1;
835 cur_size -= fs.blksize * sizeInBlocks(e->swap_file_sz);
836 --n_disk_objects;
837 }
838
839 void
840 Fs::Ufs::UFSSwapDir::rebuild()
841 {
842 ++StoreController::store_dirs_rebuilding;
843 eventAdd("storeRebuild", Fs::Ufs::RebuildState::RebuildStep, new Fs::Ufs::RebuildState(this), 0.0, 1);
844 }
845
846 void
847 Fs::Ufs::UFSSwapDir::closeTmpSwapLog()
848 {
849 assert(rebuilding_);
850 rebuilding_ = false;
851
852 char *swaplog_path = xstrdup(logFile(NULL)); // where the swaplog should be
853 char *tmp_path = xstrdup(logFile(".new")); // the temporary file we have generated
854 int fd;
855 file_close(swaplog_fd);
856
857 if (xrename(tmp_path, swaplog_path) < 0) {
858 fatalf("Failed to rename log file %s to %s", tmp_path, swaplog_path);
859 }
860
861 fd = file_open(swaplog_path, O_WRONLY | O_CREAT | O_BINARY);
862
863 if (fd < 0) {
864 int xerrno = errno;
865 debugs(50, DBG_IMPORTANT, "ERROR: " << swaplog_path << ": " << xstrerr(xerrno));
866 fatalf("Failed to open swap log %s", swaplog_path);
867 }
868
869 xfree(swaplog_path);
870 xfree(tmp_path);
871 swaplog_fd = fd;
872 debugs(47, 3, "Cache Dir #" << index << " log opened on FD " << fd);
873 }
874
875 FILE *
876 Fs::Ufs::UFSSwapDir::openTmpSwapLog(int *clean_flag, int *zero_flag)
877 {
878 assert(!rebuilding_);
879
880 char *swaplog_path = xstrdup(logFile(NULL));
881 char *clean_path = xstrdup(logFile(".last-clean"));
882 char *new_path = xstrdup(logFile(".new"));
883
884 struct stat log_sb;
885
886 struct stat clean_sb;
887 FILE *fp;
888 int fd;
889
890 if (::stat(swaplog_path, &log_sb) < 0) {
891 debugs(47, DBG_IMPORTANT, "Cache Dir #" << index << ": No log file");
892 safe_free(swaplog_path);
893 safe_free(clean_path);
894 safe_free(new_path);
895 return NULL;
896 }
897
898 *zero_flag = log_sb.st_size == 0 ? 1 : 0;
899 /* close the existing write-only FD */
900
901 if (swaplog_fd >= 0)
902 file_close(swaplog_fd);
903
904 /* open a write-only FD for the new log */
905 fd = file_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
906
907 if (fd < 0) {
908 int xerrno = errno;
909 debugs(50, DBG_IMPORTANT, "ERROR: while opening swap log" << new_path << ": " << xstrerr(xerrno));
910 fatalf("Failed to open swap log %s", new_path);
911 }
912
913 swaplog_fd = fd;
914 rebuilding_ = true;
915
916 {
917 const StoreSwapLogHeader header;
918 MemBuf buf;
919 buf.init(header.record_size, header.record_size);
920 buf.append(reinterpret_cast<const char*>(&header), sizeof(header));
921 // Pad to keep in sync with UFSSwapDir::writeCleanStart().
922 memset(buf.space(), 0, header.gapSize());
923 buf.appended(header.gapSize());
924 file_write(swaplog_fd, -1, buf.content(), buf.contentSize(),
925 NULL, NULL, buf.freeFunc());
926 }
927
928 /* open a read-only stream of the old log */
929 fp = fopen(swaplog_path, "rb");
930
931 if (!fp) {
932 int xerrno = errno;
933 debugs(50, DBG_CRITICAL, "ERROR: while opening " << swaplog_path << ": " << xstrerr(xerrno));
934 fatalf("Failed to open swap log for reading %s", swaplog_path);
935 }
936
937 memset(&clean_sb, '\0', sizeof(struct stat));
938
939 if (::stat(clean_path, &clean_sb) < 0)
940 *clean_flag = 0;
941 else if (clean_sb.st_mtime < log_sb.st_mtime)
942 *clean_flag = 0;
943 else
944 *clean_flag = 1;
945
946 safeunlink(clean_path, 1);
947
948 safe_free(swaplog_path);
949
950 safe_free(clean_path);
951
952 safe_free(new_path);
953
954 return fp;
955 }
956
957 /*
958 * Begin the process to write clean cache state. For AUFS this means
959 * opening some log files and allocating write buffers. Return 0 if
960 * we succeed, and assign the 'func' and 'data' return pointers.
961 */
962 int
963 Fs::Ufs::UFSSwapDir::writeCleanStart()
964 {
965 UFSCleanLog *state = new UFSCleanLog(this);
966 StoreSwapLogHeader header;
967 #if HAVE_FCHMOD
968
969 struct stat sb;
970 #endif
971
972 cleanLog = NULL;
973 state->newLog = xstrdup(logFile(".clean"));
974 state->fd = file_open(state->newLog, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
975
976 if (state->fd < 0) {
977 xfree(state->newLog);
978 delete state;
979 return -1;
980 }
981
982 state->cur = xstrdup(logFile(NULL));
983 state->cln = xstrdup(logFile(".last-clean"));
984 state->outbuf = (char *)xcalloc(CLEAN_BUF_SZ, 1);
985 state->outbuf_offset = 0;
986 /*copy the header */
987 memcpy(state->outbuf, &header, sizeof(StoreSwapLogHeader));
988 // Leave a gap to keep in sync with UFSSwapDir::openTmpSwapLog().
989 memset(state->outbuf + sizeof(StoreSwapLogHeader), 0, header.gapSize());
990 state->outbuf_offset += header.record_size;
991
992 state->walker = repl->WalkInit(repl);
993 ::unlink(state->cln);
994 debugs(47, 3, HERE << "opened " << state->newLog << ", FD " << state->fd);
995 #if HAVE_FCHMOD
996
997 if (::stat(state->cur, &sb) == 0)
998 fchmod(state->fd, sb.st_mode);
999
1000 #endif
1001
1002 cleanLog = state;
1003 return 0;
1004 }
1005
1006 void
1007 Fs::Ufs::UFSSwapDir::writeCleanDone()
1008 {
1009 UFSCleanLog *state = (UFSCleanLog *)cleanLog;
1010 int fd;
1011
1012 if (NULL == state)
1013 return;
1014
1015 if (state->fd < 0)
1016 return;
1017
1018 state->walker->Done(state->walker);
1019
1020 if (FD_WRITE_METHOD(state->fd, state->outbuf, state->outbuf_offset) < 0) {
1021 int xerrno = errno;
1022 debugs(50, DBG_CRITICAL, MYNAME << state->newLog << ": write: " << xstrerr(xerrno));
1023 debugs(50, DBG_CRITICAL, MYNAME << "Current swap logfile not replaced.");
1024 file_close(state->fd);
1025 state->fd = -1;
1026 ::unlink(state->newLog);
1027 }
1028
1029 safe_free(state->outbuf);
1030 /*
1031 * You can't rename open files on Microsoft "operating systems"
1032 * so we have to close before renaming.
1033 */
1034 closeLog();
1035 /* save the fd value for a later test */
1036 fd = state->fd;
1037 /* rename */
1038
1039 if (state->fd >= 0) {
1040 #if _SQUID_OS2_ || _SQUID_WINDOWS_
1041 file_close(state->fd);
1042 state->fd = -1;
1043 #endif
1044
1045 xrename(state->newLog, state->cur);
1046 }
1047
1048 /* touch a timestamp file if we're not still validating */
1049 if (StoreController::store_dirs_rebuilding)
1050 (void) 0;
1051 else if (fd < 0)
1052 (void) 0;
1053 else
1054 file_close(file_open(state->cln, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY));
1055
1056 /* close */
1057 safe_free(state->cur);
1058
1059 safe_free(state->newLog);
1060
1061 safe_free(state->cln);
1062
1063 if (state->fd >= 0)
1064 file_close(state->fd);
1065
1066 state->fd = -1;
1067
1068 delete state;
1069
1070 cleanLog = NULL;
1071 }
1072
1073 /// safely cleans a few unused files if possible
1074 int
1075 Fs::Ufs::UFSSwapDir::HandleCleanEvent()
1076 {
1077 static int swap_index = 0;
1078 int i;
1079 int j = 0;
1080 int n = 0;
1081
1082 if (!NumberOfUFSDirs)
1083 return 0; // probably in the middle of reconfiguration
1084
1085 if (NULL == UFSDirToGlobalDirMapping) {
1086 SwapDir *sd;
1087 /*
1088 * Initialize the little array that translates UFS cache_dir
1089 * number into the Config.cacheSwap.swapDirs array index.
1090 */
1091 UFSDirToGlobalDirMapping = (int *)xcalloc(NumberOfUFSDirs, sizeof(*UFSDirToGlobalDirMapping));
1092
1093 for (i = 0, n = 0; i < Config.cacheSwap.n_configured; ++i) {
1094 /* This is bogus, the controller should just clean each instance once */
1095 sd = dynamic_cast <SwapDir *>(INDEXSD(i));
1096
1097 if (!UFSSwapDir::IsUFSDir(sd))
1098 continue;
1099
1100 UFSSwapDir *usd = dynamic_cast<UFSSwapDir *>(sd);
1101
1102 assert (usd);
1103
1104 UFSDirToGlobalDirMapping[n] = i;
1105 ++n;
1106
1107 j += (usd->l1 * usd->l2);
1108 }
1109
1110 assert(n == NumberOfUFSDirs);
1111 /*
1112 * Start the commonUfsDirClean() swap_index with a random
1113 * value. j equals the total number of UFS level 2
1114 * swap directories
1115 */
1116 std::mt19937 mt(static_cast<uint32_t>(getCurrentTime() & 0xFFFFFFFF));
1117 xuniform_int_distribution<> dist(0, j);
1118 swap_index = dist(mt);
1119 }
1120
1121 /* if the rebuild is finished, start cleaning directories. */
1122 if (0 == StoreController::store_dirs_rebuilding) {
1123 n = DirClean(swap_index);
1124 ++swap_index;
1125 }
1126
1127 return n;
1128 }
1129
1130 void
1131 Fs::Ufs::UFSSwapDir::CleanEvent(void *)
1132 {
1133 const int n = HandleCleanEvent();
1134 eventAdd("storeDirClean", CleanEvent, NULL,
1135 15.0 * exp(-0.25 * n), 1);
1136 }
1137
1138 bool
1139 Fs::Ufs::UFSSwapDir::IsUFSDir(SwapDir * sd)
1140 {
1141 UFSSwapDir *mySD = dynamic_cast<UFSSwapDir *>(sd);
1142 return (mySD != 0) ;
1143 }
1144
1145 /*
1146 * XXX: this is broken - it assumes all cache dirs use the same
1147 * l1 and l2 scheme. -RBC 20021215. Partial fix is in place -
1148 * if not UFSSwapDir return 0;
1149 */
1150 bool
1151 Fs::Ufs::UFSSwapDir::FilenoBelongsHere(int fn, int F0, int F1, int F2)
1152 {
1153 int D1, D2;
1154 int L1, L2;
1155 int filn = fn;
1156 assert(F0 < Config.cacheSwap.n_configured);
1157 assert (UFSSwapDir::IsUFSDir (dynamic_cast<SwapDir *>(INDEXSD(F0))));
1158 UFSSwapDir *sd = dynamic_cast<UFSSwapDir *>(INDEXSD(F0));
1159
1160 if (!sd)
1161 return 0;
1162
1163 L1 = sd->l1;
1164
1165 L2 = sd->l2;
1166
1167 D1 = ((filn / L2) / L2) % L1;
1168
1169 if (F1 != D1)
1170 return 0;
1171
1172 D2 = (filn / L2) % L2;
1173
1174 if (F2 != D2)
1175 return 0;
1176
1177 return 1;
1178 }
1179
1180 int
1181 Fs::Ufs::UFSSwapDir::validFileno(sfileno filn, int flag) const
1182 {
1183 if (filn < 0)
1184 return 0;
1185
1186 /*
1187 * If flag is set it means out-of-range file number should
1188 * be considered invalid.
1189 */
1190 if (flag)
1191 if (filn > map->capacity())
1192 return 0;
1193
1194 return 1;
1195 }
1196
1197 void
1198 Fs::Ufs::UFSSwapDir::unlinkFile(sfileno f)
1199 {
1200 debugs(79, 3, HERE << "unlinking fileno " << std::setfill('0') <<
1201 std::hex << std::uppercase << std::setw(8) << f << " '" <<
1202 fullPath(f,NULL) << "'");
1203 /* commonUfsDirMapBitReset(this, f); */
1204 IO->unlinkFile(fullPath(f,NULL));
1205 }
1206
1207 bool
1208 Fs::Ufs::UFSSwapDir::unlinkdUseful() const
1209 {
1210 // unlinkd may be useful only in workers
1211 return IamWorkerProcess() && IO->io->unlinkdUseful();
1212 }
1213
1214 void
1215 Fs::Ufs::UFSSwapDir::unlink(StoreEntry & e)
1216 {
1217 debugs(79, 3, HERE << "dirno " << index << ", fileno "<<
1218 std::setfill('0') << std::hex << std::uppercase << std::setw(8) << e.swap_filen);
1219 if (e.swap_status == SWAPOUT_DONE) {
1220 cur_size -= fs.blksize * sizeInBlocks(e.swap_file_sz);
1221 --n_disk_objects;
1222 }
1223 replacementRemove(&e);
1224 mapBitReset(e.swap_filen);
1225 UFSSwapDir::unlinkFile(e.swap_filen);
1226 e.swap_filen = -1;
1227 e.swap_dirn = -1;
1228 e.swap_status = SWAPOUT_NONE;
1229 }
1230
1231 void
1232 Fs::Ufs::UFSSwapDir::replacementAdd(StoreEntry * e)
1233 {
1234 debugs(47, 4, HERE << "added node " << e << " to dir " << index);
1235 repl->Add(repl, e, &e->repl);
1236 }
1237
1238 void
1239 Fs::Ufs::UFSSwapDir::replacementRemove(StoreEntry * e)
1240 {
1241 if (e->swap_dirn < 0)
1242 return;
1243
1244 SwapDirPointer SD = INDEXSD(e->swap_dirn);
1245
1246 assert (dynamic_cast<UFSSwapDir *>(SD.getRaw()) == this);
1247
1248 debugs(47, 4, HERE << "remove node " << e << " from dir " << index);
1249
1250 repl->Remove(repl, e, &e->repl);
1251 }
1252
1253 void
1254 Fs::Ufs::UFSSwapDir::dump(StoreEntry & entry) const
1255 {
1256 storeAppendPrintf(&entry, " %" PRIu64 " %d %d", maxSize() >> 20, l1, l2);
1257 dumpOptions(&entry);
1258 }
1259
1260 char *
1261 Fs::Ufs::UFSSwapDir::fullPath(sfileno filn, char *fullpath) const
1262 {
1263 LOCAL_ARRAY(char, fullfilename, MAXPATHLEN);
1264 int L1 = l1;
1265 int L2 = l2;
1266
1267 if (!fullpath)
1268 fullpath = fullfilename;
1269
1270 fullpath[0] = '\0';
1271
1272 snprintf(fullpath, MAXPATHLEN, "%s/%02X/%02X/%08X",
1273 path,
1274 ((filn / L2) / L2) % L1,
1275 (filn / L2) % L2,
1276 filn);
1277
1278 return fullpath;
1279 }
1280
1281 int
1282 Fs::Ufs::UFSSwapDir::callback()
1283 {
1284 return IO->callback();
1285 }
1286
1287 void
1288 Fs::Ufs::UFSSwapDir::sync()
1289 {
1290 IO->sync();
1291 }
1292
1293 void
1294 Fs::Ufs::UFSSwapDir::swappedOut(const StoreEntry &e)
1295 {
1296 cur_size += fs.blksize * sizeInBlocks(e.swap_file_sz);
1297 ++n_disk_objects;
1298 }
1299
1300 void
1301 Fs::Ufs::UFSSwapDir::logEntry(const StoreEntry & e, int op) const
1302 {
1303 if (swaplog_fd < 0) {
1304 debugs(36, 5, "cannot log " << e << " in the middle of reconfiguration");
1305 return;
1306 }
1307
1308 StoreSwapLogData *s = new StoreSwapLogData;
1309 s->op = (char) op;
1310 s->swap_filen = e.swap_filen;
1311 s->timestamp = e.timestamp;
1312 s->lastref = e.lastref;
1313 s->expires = e.expires;
1314 s->lastmod = e.lastModified();
1315 s->swap_file_sz = e.swap_file_sz;
1316 s->refcount = e.refcount;
1317 s->flags = e.flags;
1318 memcpy(s->key, e.key, SQUID_MD5_DIGEST_LENGTH);
1319 s->finalize();
1320 file_write(swaplog_fd,
1321 -1,
1322 s,
1323 sizeof(StoreSwapLogData),
1324 NULL,
1325 NULL,
1326 FreeObject);
1327 }
1328
1329 int
1330 Fs::Ufs::UFSSwapDir::DirClean(int swap_index)
1331 {
1332 DIR *dir_pointer = NULL;
1333
1334 LOCAL_ARRAY(char, p1, MAXPATHLEN + 1);
1335 LOCAL_ARRAY(char, p2, MAXPATHLEN + 1);
1336
1337 int files[20];
1338 int swapfileno;
1339 int fn; /* same as swapfileno, but with dirn bits set */
1340 int n = 0;
1341 int k = 0;
1342 int N0, N1, N2;
1343 int D0, D1, D2;
1344 UFSSwapDir *SD;
1345 N0 = NumberOfUFSDirs;
1346 D0 = UFSDirToGlobalDirMapping[swap_index % N0];
1347 SD = dynamic_cast<UFSSwapDir *>(INDEXSD(D0));
1348 assert (SD);
1349 N1 = SD->l1;
1350 D1 = (swap_index / N0) % N1;
1351 N2 = SD->l2;
1352 D2 = ((swap_index / N0) / N1) % N2;
1353 snprintf(p1, MAXPATHLEN, "%s/%02X/%02X",
1354 SD->path, D1, D2);
1355 debugs(36, 3, HERE << "Cleaning directory " << p1);
1356 dir_pointer = opendir(p1);
1357
1358 if (!dir_pointer) {
1359 int xerrno = errno;
1360 if (xerrno == ENOENT) {
1361 debugs(36, DBG_CRITICAL, MYNAME << "WARNING: Creating " << p1);
1362 if (mkdir(p1, 0777) == 0)
1363 return 0;
1364 }
1365
1366 debugs(50, DBG_CRITICAL, MYNAME << p1 << ": " << xstrerr(xerrno));
1367 safeunlink(p1, 1);
1368 return 0;
1369 }
1370
1371 dirent_t *de;
1372 while ((de = readdir(dir_pointer)) != NULL && k < 20) {
1373 if (sscanf(de->d_name, "%X", &swapfileno) != 1)
1374 continue;
1375
1376 fn = swapfileno; /* XXX should remove this cruft ! */
1377
1378 if (SD->validFileno(fn, 1))
1379 if (SD->mapBitTest(fn))
1380 if (UFSSwapDir::FilenoBelongsHere(fn, D0, D1, D2))
1381 continue;
1382
1383 files[k] = swapfileno;
1384 ++k;
1385 }
1386
1387 closedir(dir_pointer);
1388
1389 if (k == 0)
1390 return 0;
1391
1392 qsort(files, k, sizeof(int), rev_int_sort);
1393
1394 if (k > 10)
1395 k = 10;
1396
1397 for (n = 0; n < k; ++n) {
1398 debugs(36, 3, HERE << "Cleaning file "<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << files[n]);
1399 snprintf(p2, MAXPATHLEN + 1, "%s/%08X", p1, files[n]);
1400 safeunlink(p2, 0);
1401 ++statCounter.swap.files_cleaned;
1402 }
1403
1404 debugs(36, 3, HERE << "Cleaned " << k << " unused files from " << p1);
1405 return k;
1406 }
1407