2 * $Id: ufscommon.cc,v 1.13 2007/08/13 17:20:57 hno Exp $
5 * DEBUG: section 47 Store Directory Routines
6 * AUTHOR: Robert Collins
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
37 #include "ufscommon.h"
40 #include "StoreMeta.h"
42 #include "StoreMetaUnpacker.h"
44 #include "StoreSwapLogData.h"
46 CBDATA_CLASS_INIT(RebuildState
);
49 class UFSSwapLogParser_old
:public UFSSwapLogParser
{
51 struct StoreSwapLogDataOld
{
61 unsigned char key
[MD5_DIGEST_CHARS
];
63 UFSSwapLogParser_old(FILE *fp
):UFSSwapLogParser(fp
)
65 record_size
= sizeof(UFSSwapLogParser_old::StoreSwapLogDataOld
);
67 bool ReadRecord(StoreSwapLogData
&swapData
);
71 bool UFSSwapLogParser_old::ReadRecord(StoreSwapLogData
&swapData
){
72 UFSSwapLogParser_old::StoreSwapLogDataOld readData
;
73 int bytes
= sizeof(UFSSwapLogParser_old::StoreSwapLogDataOld
);
77 if (fread(&readData
, bytes
, 1, log
) != 1){
80 swapData
.op
= readData
.op
;
81 swapData
.swap_filen
= readData
.swap_filen
;
82 swapData
.timestamp
= readData
.timestamp
;
83 swapData
.lastref
= readData
.lastref
;
84 swapData
.expires
= readData
.expires
;
85 swapData
.lastmod
= readData
.lastmod
;
86 swapData
.swap_file_sz
= readData
.swap_file_sz
;
87 swapData
.refcount
= readData
.refcount
;
88 swapData
.flags
= readData
.flags
;
89 xmemcpy(swapData
.key
, readData
.key
, MD5_DIGEST_CHARS
);
94 class UFSSwapLogParser_v1
:public UFSSwapLogParser
{
96 UFSSwapLogParser_v1(FILE *fp
):UFSSwapLogParser(fp
)
98 record_size
= sizeof(StoreSwapLogData
);
100 bool ReadRecord(StoreSwapLogData
&swapData
);
104 bool UFSSwapLogParser_v1::ReadRecord(StoreSwapLogData
&swapData
)
106 int bytes
= sizeof(StoreSwapLogData
);
110 if (fread(&swapData
, bytes
, 1, log
) != 1){
117 UFSSwapLogParser
*UFSSwapLogParser::GetUFSSwapLogParser(FILE *fp
)
119 StoreSwapLogHeader header
;
123 if (fread(&header
, sizeof(StoreSwapLogHeader
), 1, fp
) != 1)
126 if (header
.op
!= SWAP_LOG_VERSION
){
127 debugs(47, 1, "Old swap file detected... ");
128 fseek(fp
, 0, SEEK_SET
);
129 return new UFSSwapLogParser_old(fp
);
132 if (header
.version
== 1){
133 if (fseek(fp
, header
.record_size
, SEEK_SET
) != 0)
136 if (header
.record_size
== sizeof(struct UFSSwapLogParser_old::StoreSwapLogDataOld
)){
137 debugs(47, 1, "Version 1 of swap file without LFS support detected... ");
138 return new UFSSwapLogParser_old(fp
);
141 if (header
.record_size
== sizeof(StoreSwapLogData
)){
142 debugs(47, 1, "Version 1 of swap file with LFS support detected... ");
143 return new UFSSwapLogParser_v1(fp
);
146 debugs(47, 1, "The swap file has wrong format!... ");
153 int UFSSwapLogParser::SwapLogEntries(){
156 if (log_entries
>= 0)
159 if (log
&& record_size
&& 0 == fstat(fileno(log
), &sb
)){
160 log_entries
= sb
.st_size
/record_size
;
170 RebuildState::RebuildState (RefCount
<UFSSwapDir
> aSwapDir
) : sd (aSwapDir
),LogParser(NULL
), e(NULL
), fromLog(true), _done (false)
172 speed
= opt_foreground_rebuild
? 1 << 30 : 50;
174 * If the swap.state file exists in the cache_dir, then
175 * we'll use commonUfsDirRebuildFromSwapLog(), otherwise we'll
176 * use commonUfsDirRebuildFromDirectory() to open up each file
177 * and suck in the meta data.
180 int zeroLengthLog
= 0;
181 FILE *fp
= sd
->openTmpSwapLog(&clean
, &zeroLengthLog
);
183 if (fp
&& !zeroLengthLog
)
184 LogParser
= UFSSwapLogParser::GetUFSSwapLogParser(fp
);
186 if (LogParser
== NULL
) {
194 flags
.clean
= (unsigned int) clean
;
198 flags
.need_to_validate
= 1;
200 debugs(47, 1, "Rebuilding storage in " << sd
->path
<< " (" << (clean
? "CLEAN" : "DIRTY") << ")");
203 RebuildState::~RebuildState()
205 sd
->closeTmpSwapLog();
212 RebuildState::RebuildStep(void *data
)
214 RebuildState
*rb
= (RebuildState
*)data
;
218 eventAdd("storeRebuild", RebuildStep
, rb
, 0.0, 1);
220 StoreController::store_dirs_rebuilding
--;
221 storeRebuildComplete(&rb
->counts
);
227 RebuildState::rebuildStep()
230 rebuildFromSwapLog();
232 rebuildFromDirectory();
235 struct InitStoreEntry
: public unary_function
<StoreMeta
, void>
237 InitStoreEntry(StoreEntry
*anEntry
, cache_key
*aKey
):what(anEntry
),index(aKey
){}
239 void operator()(StoreMeta
const &x
)
241 switch (x
.getType()) {
244 assert(x
.length
== MD5_DIGEST_CHARS
);
245 xmemcpy(index
, x
.value
, MD5_DIGEST_CHARS
);
258 tmp
= (struct old_metahdr
*)x
.value
;
259 assert(x
.length
== STORE_HDR_METASIZE_OLD
);
260 what
->timestamp
= tmp
->timestamp
;
261 what
->lastref
= tmp
->lastref
;
262 what
->expires
= tmp
->expires
;
263 what
->lastmod
= tmp
->lastmod
;
264 what
->swap_file_sz
= tmp
->swap_file_sz
;
265 what
->refcount
= tmp
->refcount
;
266 what
->flags
= tmp
->flags
;
269 case STORE_META_STD_LFS
:
270 assert(x
.length
== STORE_HDR_METASIZE
);
271 xmemcpy(&what
->timestamp
, x
.value
, STORE_HDR_METASIZE
);
284 RebuildState::rebuildFromDirectory()
286 LOCAL_ARRAY(char, hdr_buf
, SM_PAGE_SIZE
);
289 cache_key key
[MD5_DIGEST_CHARS
];
295 assert(this != NULL
);
296 debugs(47, 3, "commonUfsDirRebuildFromDirectory: DIR #" << sd
->index
);
298 for (int count
= 0; count
< speed
; count
++) {
302 fd
= getNextFile(&filn
, &size
);
305 debugs(47, 1, "Done scanning " << sd
->path
<< " swaplog (" << n_read
<< " entries)");
313 /* lets get file stats here */
315 if (fstat(fd
, &sb
) < 0) {
316 debugs(47, 1, "commonUfsDirRebuildFromDirectory: fstat(FD " << fd
<< "): " << xstrerror());
318 store_open_disk_fd
--;
323 if ((++counts
.scancount
& 0xFFFF) == 0)
324 debugs(47, 3, " " << sd
->path
<< " " << std::setw(7) << counts
.scancount
<< " files opened so far.");
325 debugs(47, 9, "file_in: fd=" << fd
<< " "<< std::setfill('0') << std::hex
<< std::uppercase
<< std::setw(8) << filn
);
328 statCounter
.syscalls
.disk
.reads
++;
332 if ((len
= FD_READ_METHOD(fd
, hdr_buf
, SM_PAGE_SIZE
)) < 0) {
333 debugs(47, 1, "commonUfsDirRebuildFromDirectory: read(FD " << fd
<< "): " << xstrerror());
335 store_open_disk_fd
--;
341 store_open_disk_fd
--;
345 StoreMetaUnpacker
aBuilder(hdr_buf
, len
, &swap_hdr_len
);
347 if (!aBuilder
.isBufferSane()) {
348 debugs(47, 1, "commonUfsDirRebuildFromDirectory: Swap data buffer length is not sane.");
349 /* XXX shouldn't this be a call to commonUfsUnlink ? */
350 sd
->unlinkFile ( filn
);
354 tlv_list
= aBuilder
.createStoreMeta ();
356 if (tlv_list
== NULL
) {
357 debugs(47, 1, "commonUfsDirRebuildFromDirectory: failed to get meta data");
358 /* XXX shouldn't this be a call to commonUfsUnlink ? */
359 sd
->unlinkFile (filn
);
363 debugs(47, 3, "commonUfsDirRebuildFromDirectory: successful swap meta unpacking");
364 memset(key
, '\0', MD5_DIGEST_CHARS
);
365 memset(&tmpe
, '\0', sizeof(StoreEntry
));
366 InitStoreEntry
visitor(&tmpe
, key
);
367 for_each(*tlv_list
, visitor
);
368 storeSwapTLVFree(tlv_list
);
371 if (storeKeyNull(key
)) {
372 debugs(47, 1, "commonUfsDirRebuildFromDirectory: NULL key");
373 sd
->unlinkFile(filn
);
380 if (tmpe
.swap_file_sz
== 0) {
381 tmpe
.swap_file_sz
= (uint64_t) sb
.st_size
;
382 } else if (tmpe
.swap_file_sz
== (uint64_t)(sb
.st_size
- swap_hdr_len
)) {
383 tmpe
.swap_file_sz
= (uint64_t) sb
.st_size
;
384 } else if (tmpe
.swap_file_sz
!= (uint64_t)sb
.st_size
) {
385 debugs(47, 1, "commonUfsDirRebuildFromDirectory: SIZE MISMATCH " <<
386 tmpe
.swap_file_sz
<< "!=" <<
389 sd
->unlinkFile(filn
);
393 if (EBIT_TEST(tmpe
.flags
, KEY_PRIVATE
)) {
394 sd
->unlinkFile(filn
);
399 /* this needs to become
401 * 2) make synthetic request with headers ?? or otherwise search
402 * for a matching object in the store
403 * TODO FIXME change to new async api
404 * TODO FIXME I think there is a race condition here with the
406 * store A reads in object foo, searchs for it, and finds nothing.
407 * store B reads in object foo, searchs for it, finds nothing.
408 * store A gets called back with nothing, so registers the object
409 * store B gets called back with nothing, so registers the object,
410 * which will conflict when the in core index gets around to scanning
413 * this suggests that rather than searching for duplicates, the
414 * index rebuild should just assume its the most recent accurate
415 * store entry and whoever indexes the stores handles duplicates.
417 e
= Store::Root().get(key
);
419 if (e
&& e
->lastref
>= tmpe
.lastref
) {
420 /* key already exists, current entry is newer */
421 /* keep old, ignore new */
424 } else if (NULL
!= e
) {
425 /* URL already exists, this swapfile not being used */
426 /* junk old, load new */
427 e
->release(); /* release old entry */
433 currentEntry(sd
->addDiskRestore(key
,
440 tmpe
.refcount
, /* refcount */
441 tmpe
.flags
, /* flags */
443 storeDirSwapLog(currentEntry(), SWAP_LOG_ADD
);
449 RebuildState::currentEntry() const
455 RebuildState::currentEntry(StoreEntry
*newValue
)
461 RebuildState::rebuildFromSwapLog()
465 /* load a number of objects per invocation */
467 for (int count
= 0; count
< speed
; count
++) {
468 StoreSwapLogData swapData
;
470 if (LogParser
->ReadRecord(swapData
) != 1) {
471 debugs(47, 1, "Done reading " << sd
->path
<< " swaplog (" << n_read
<< " entries)");
481 if (swapData
.op
<= SWAP_LOG_NOP
)
484 if (swapData
.op
>= SWAP_LOG_MAX
)
488 * BC: during 2.4 development, we changed the way swap file
489 * numbers are assigned and stored. The high 16 bits used
490 * to encode the SD index number. There used to be a call
491 * to storeDirProperFileno here that re-assigned the index
492 * bits. Now, for backwards compatibility, we just need
495 swapData
.swap_filen
&= 0x00FFFFFF;
497 debugs(47, 3, "commonUfsDirRebuildFromSwapLog: " <<
498 swap_log_op_str
[(int) swapData
.op
] << " " <<
499 storeKeyText(swapData
.key
) << " "<< std::setfill('0') <<
500 std::hex
<< std::uppercase
<< std::setw(8) <<
501 swapData
.swap_filen
);
503 if (swapData
.op
== SWAP_LOG_ADD
) {
505 } else if (swapData
.op
== SWAP_LOG_DEL
) {
506 /* Delete unless we already have a newer copy anywhere in any store */
507 /* this needs to become
509 * 2) make synthetic request with headers ?? or otherwise search
510 * for a matching object in the store
511 * TODO FIXME change to new async api
513 currentEntry (Store::Root().get(swapData
.key
));
515 if (currentEntry() != NULL
&& swapData
.lastref
>= e
->lastref
) {
517 * Make sure we don't unlink the file, it might be
518 * in use by a subsequent entry. Also note that
519 * we don't have to subtract from store_swap_size
520 * because adding to store_swap_size happens in
521 * the cleanup procedure.
523 currentEntry()->expireNow();
524 currentEntry()->releaseRequest();
526 if (currentEntry()->swap_filen
> -1) {
527 UFSSwapDir
*sdForThisEntry
= dynamic_cast<UFSSwapDir
*>(INDEXSD(currentEntry()->swap_dirn
));
528 assert (sdForThisEntry
);
529 sdForThisEntry
->replacementRemove(currentEntry());
530 sdForThisEntry
->mapBitReset(currentEntry()->swap_filen
);
531 currentEntry()->swap_filen
= -1;
532 currentEntry()->swap_dirn
= -1;
535 currentEntry()->release();
537 counts
.cancelcount
++;
542 x
= ::log(static_cast<double>(++counts
.bad_log_op
)) / ::log(10.0);
544 if (0.0 == x
- (double) (int) x
)
545 debugs(47, 1, "WARNING: " << counts
.bad_log_op
<< " invalid swap log entries found");
552 if ((++counts
.scancount
& 0xFFF) == 0) {
554 int swap_entries
= LogParser
->SwapLogEntries();
556 if (0 != swap_entries
)
557 storeRebuildProgress(sd
->index
,
558 swap_entries
, n_read
);
561 if (!sd
->validFileno(swapData
.swap_filen
, 0)) {
566 if (EBIT_TEST(swapData
.flags
, KEY_PRIVATE
)) {
571 /* this needs to become
573 * 2) make synthetic request with headers ?? or otherwise search
574 * for a matching object in the store
575 * TODO FIXME change to new async api
577 currentEntry (Store::Root().get(swapData
.key
));
579 int used
; /* is swapfile already in use? */
581 used
= sd
->mapBitTest(swapData
.swap_filen
);
583 /* If this URL already exists in the cache, does the swap log
584 * appear to have a newer entry? Compare 'lastref' from the
585 * swap log to e->lastref. */
586 /* is the log entry newer than current entry? */
587 int disk_entry_newer
= currentEntry() ? (swapData
.lastref
> currentEntry()->lastref
? 1 : 0) : 0;
589 if (used
&& !disk_entry_newer
) {
590 /* log entry is old, ignore it */
593 } else if (used
&& currentEntry() && currentEntry()->swap_filen
== swapData
.swap_filen
&& currentEntry()->swap_dirn
== sd
->index
) {
594 /* swapfile taken, same URL, newer, update meta */
596 if (currentEntry()->store_status
== STORE_OK
) {
597 currentEntry()->lastref
= swapData
.timestamp
;
598 currentEntry()->timestamp
= swapData
.timestamp
;
599 currentEntry()->expires
= swapData
.expires
;
600 currentEntry()->lastmod
= swapData
.lastmod
;
601 currentEntry()->flags
= swapData
.flags
;
602 currentEntry()->refcount
+= swapData
.refcount
;
603 sd
->dereference(*currentEntry());
605 debug_trap("commonUfsDirRebuildFromSwapLog: bad condition");
606 debugs(47, 1, "\tSee " << __FILE__
<< ":" << __LINE__
);
611 /* swapfile in use, not by this URL, log entry is newer */
612 /* This is sorta bad: the log entry should NOT be newer at this
613 * point. If the log is dirty, the filesize check should have
614 * caught this. If the log is clean, there should never be a
616 debugs(47, 1, "WARNING: newer swaplog entry for dirno " <<
617 sd
->index
<< ", fileno "<< std::setfill('0') << std::hex
<<
618 std::uppercase
<< std::setw(8) << swapData
.swap_filen
);
620 /* I'm tempted to remove the swapfile here just to be safe,
621 * but there is a bad race condition in the NOVM version if
622 * the swapfile has recently been opened for writing, but
623 * not yet opened for reading. Because we can't map
624 * swapfiles back to StoreEntrys, we don't know the state
625 * of the entry using that file. */
626 /* We'll assume the existing entry is valid, probably because
627 * were in a slow rebuild and the the swap file number got taken
628 * and the validation procedure hasn't run. */
629 assert(flags
.need_to_validate
);
632 } else if (currentEntry() && !disk_entry_newer
) {
633 /* key already exists, current entry is newer */
634 /* keep old, ignore new */
637 } else if (currentEntry()) {
638 /* key already exists, this swapfile not being used */
639 /* junk old, load new */
640 currentEntry()->expireNow();
641 currentEntry()->releaseRequest();
643 if (currentEntry()->swap_filen
> -1) {
644 UFSSwapDir
*sdForThisEntry
= dynamic_cast<UFSSwapDir
*>(INDEXSD(currentEntry()->swap_dirn
));
645 sdForThisEntry
->replacementRemove(currentEntry());
646 /* Make sure we don't actually unlink the file */
647 sdForThisEntry
->mapBitReset(currentEntry()->swap_filen
);
648 currentEntry()->swap_filen
= -1;
649 currentEntry()->swap_dirn
= -1;
652 currentEntry()->release();
655 /* URL doesnt exist, swapfile not in use */
660 /* update store_swap_size */
663 currentEntry(sd
->addDiskRestore(swapData
.key
,
665 swapData
.swap_file_sz
,
674 storeDirSwapLog(currentEntry(), SWAP_LOG_ADD
);
680 RebuildState::getNextFile(sfileno
* filn_p
, int *size
)
684 debugs(47, 3, "commonUfsDirGetNextFile: flag=" << flags
.init
<< ", " <<
685 sd
->index
<< ": /"<< std::setfill('0') << std::hex
<<
686 std::uppercase
<< std::setw(2) << curlvl1
<< "/" << std::setw(2) <<
692 while (fd
< 0 && done
== 0) {
695 if (0 == flags
.init
) { /* initialize, open first file */
701 assert(Config
.cacheSwap
.n_configured
> 0);
704 if (0 == in_dir
) { /* we need to read in a new directory */
705 snprintf(fullpath
, SQUID_MAXPATHLEN
, "%s/%02X/%02X",
712 td
= opendir(fullpath
);
717 debugs(47, 1, "commonUfsDirGetNextFile: opendir: " << fullpath
<< ": " << xstrerror());
719 entry
= readdir(td
); /* skip . and .. */
722 if (entry
== NULL
&& errno
== ENOENT
)
723 debugs(47, 1, "commonUfsDirGetNextFile: directory does not exist!.");
724 debugs(47, 3, "commonUfsDirGetNextFile: Directory " << fullpath
);
728 if (td
!= NULL
&& (entry
= readdir(td
)) != NULL
) {
731 if (sscanf(entry
->d_name
, "%x", &fn
) != 1) {
732 debugs(47, 3, "commonUfsDirGetNextFile: invalid " << entry
->d_name
);
736 if (!UFSSwapDir::FilenoBelongsHere(fn
, sd
->index
, curlvl1
, curlvl2
)) {
737 debugs(47, 3, "commonUfsDirGetNextFile: "<< std::setfill('0') <<
738 std::hex
<< std::uppercase
<< std::setw(8) << fn
<<
739 " does not belong in " << std::dec
<< sd
->index
<< "/" <<
740 curlvl1
<< "/" << curlvl2
);
745 if (sd
->mapBitTest(fn
)) {
746 debugs(47, 3, "commonUfsDirGetNextFile: Locked, continuing with next.");
750 snprintf(fullfilename
, SQUID_MAXPATHLEN
, "%s/%s",
751 fullpath
, entry
->d_name
);
752 debugs(47, 3, "commonUfsDirGetNextFile: Opening " << fullfilename
);
753 fd
= file_open(fullfilename
, O_RDONLY
| O_BINARY
);
756 debugs(47, 1, "commonUfsDirGetNextFile: " << fullfilename
<< ": " << xstrerror());
758 store_open_disk_fd
++;
770 if (sd
->validL2(++curlvl2
))
775 if (sd
->validL1(++curlvl1
))
788 RebuildState::next(void (callback
)(void *cbdata
), void *cbdata
)
790 /* for now, we don't cache at all */
794 while (!isDone() && currentEntry() == NULL
)
807 RebuildState::error() const
813 RebuildState::isDone() const
819 RebuildState::currentItem()
821 return currentEntry();
825 #include "ufscommon.cci"