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