Squid Web Cache master
Loading...
Searching...
No Matches
RockSwapDir.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 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#include "squid.h"
12#include "base/IoManip.h"
13#include "cache_cf.h"
14#include "CollapsedForwarding.h"
15#include "compat/socket.h"
16#include "compat/unistd.h"
17#include "ConfigOption.h"
18#include "DiskIO/DiskIOModule.h"
20#include "DiskIO/ReadRequest.h"
21#include "DiskIO/WriteRequest.h"
24#include "fs/rock/RockIoState.h"
25#include "fs/rock/RockSwapDir.h"
26#include "globals.h"
27#include "ipc/mem/Pages.h"
28#include "MemObject.h"
29#include "Parsing.h"
30#include "SquidConfig.h"
31#include "SquidMath.h"
32#include "tools.h"
33
34#include <cstdlib>
35#include <iomanip>
36#include <limits>
37
38#if HAVE_SYS_STAT_H
39#include <sys/stat.h>
40#endif
41
43 slotSize(HeaderSize), filePath(nullptr), map(nullptr), io(nullptr),
44 waitingForPage(nullptr)
45{
46}
47
49{
50 delete io;
51 delete map;
52 safe_free(filePath);
53}
54
55// called when Squid core needs a StoreEntry with a given key
58{
59 if (!map || !theFile || !theFile->canRead())
60 return nullptr;
61
62 sfileno filen;
63 const Ipc::StoreMapAnchor *const slot = map->openForReading(key, filen);
64 if (!slot)
65 return nullptr;
66
67 // create a brand new store entry and initialize it with stored basics
68 StoreEntry *e = new StoreEntry();
69 e->createMemObject();
70 anchorEntry(*e, filen, *slot);
71 trackReferences(*e);
72 return e;
73}
74
75bool
77{
78 Assure(!entry.hasDisk());
79
80 if (!map || !theFile || !theFile->canRead())
81 return false;
82
83 sfileno filen;
84 const Ipc::StoreMapAnchor *const slot = map->openForReading(
85 reinterpret_cast<cache_key*>(entry.key), filen);
86 if (!slot)
87 return false;
88
89 anchorEntry(entry, filen, *slot);
90 return true;
91}
92
93bool
95{
96 if (!map || !theFile || !theFile->canRead())
97 return false;
98
99 assert(entry.hasDisk(index));
100
101 const auto &anchor = map->readableEntry(entry.swap_filen);
102 entry.swap_file_sz = anchor.basics.swap_file_sz;
103 return true;
104}
105
106void
108{
109 anchor.exportInto(e);
110
111 const bool complete = anchor.complete();
112 e.store_status = complete ? STORE_OK : STORE_PENDING;
113 // SWAPOUT_WRITING: even though another worker writes?
114 e.attachToDisk(index, filen, complete ? SWAPOUT_DONE : SWAPOUT_WRITING);
115
117
119}
120
122{
123 assert(e.hasDisk(index));
124
125 ignoreReferences(e);
126
127 // do not rely on e.swap_status here because there is an async delay
128 // before it switches from SWAPOUT_WRITING to SWAPOUT_DONE.
129
130 // since e has swap_filen, its slot is locked for reading and/or writing
131 // but it is difficult to know whether THIS worker is reading or writing e,
132 // especially since we may switch from writing to reading. This code relies
133 // on Rock::IoState::writeableAnchor_ being set when we locked for writing.
134 if (e.mem_obj && e.mem_obj->swapout.sio != nullptr &&
135 dynamic_cast<IoState&>(*e.mem_obj->swapout.sio).writeableAnchor_) {
136 map->abortWriting(e.swap_filen);
137 e.detachFromDisk();
138 dynamic_cast<IoState&>(*e.mem_obj->swapout.sio).writeableAnchor_ = nullptr;
140 e.storeWriterDone();
141 } else {
142 map->closeForReading(e.swap_filen);
143 e.detachFromDisk();
144 }
145}
146
147uint64_t
149{
150 const uint64_t spaceSize = !freeSlots ?
151 maxSize() : (slotSize * freeSlots->size());
152 // everything that is not free is in use
153 return maxSize() - spaceSize;
154}
155
156uint64_t
158{
159 return map ? map->entryCount() : 0;
160}
161
164bool
166{
167 return ::SwapDir::doReportStat() && (!UsingSmp() || IamDiskProcess());
168}
169
170void
172{
173 // nothing to do; handleWriteCompletionSuccess() did everything for us
174 assert(!e.mem_obj ||
175 !e.mem_obj->swapout.sio ||
176 !dynamic_cast<IoState&>(*e.mem_obj->swapout.sio).writeableAnchor_);
177}
178
179void
181{
182 debugs(47, 5, entry);
183 disconnect(entry); // calls abortWriting() to free the disk entry
184}
185
186int64_t
188{
189 // the max value is an invalid one; all values must be below the limit
190 static_assert(std::numeric_limits<Ipc::StoreMapSliceId>::max() == std::numeric_limits<SlotId>::max());
191 return std::numeric_limits<SlotId>::max();
192}
193
194int64_t
196{
197 const int64_t sWanted = (maxSize() - HeaderSize)/slotSize;
198 const int64_t sLimitLo = map ? map->sliceLimit() : 0; // dynamic shrinking unsupported
199 const int64_t sLimitHi = slotLimitAbsolute();
200 return min(max(sLimitLo, sWanted), sLimitHi);
201}
202
203int64_t
205{
206 return min(slotLimitActual(), entryLimitAbsolute());
207}
208
209// TODO: encapsulate as a tool
210void
212{
213 assert(path);
214 assert(filePath);
215
216 if (UsingSmp() && !IamDiskProcess()) {
217 debugs (47,3, "disker will create in " << path);
218 return;
219 }
220
221 debugs (47,3, "creating in " << path);
222
223 struct stat dir_sb;
224 if (::stat(path, &dir_sb) == 0) {
225 struct stat file_sb;
226 if (::stat(filePath, &file_sb) == 0) {
227 debugs (47, DBG_IMPORTANT, "Skipping existing Rock db: " << filePath);
228 return;
229 }
230 // else the db file is not there or is not accessible, and we will try
231 // to create it later below, generating a detailed error on failures.
232 } else { // path does not exist or is inaccessible
233 // If path exists but is not accessible, mkdir() below will fail, and
234 // the admin should see the error and act accordingly, so there is
235 // no need to distinguish ENOENT from other possible stat() errors.
236 debugs (47, DBG_IMPORTANT, "Creating Rock db directory: " << path);
237 const int res = mkdir(path, 0700);
238 if (res != 0)
239 createError("mkdir");
240 }
241
242 debugs (47, DBG_IMPORTANT, "Creating Rock db: " << filePath);
243 const auto swap = xopen(filePath, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0600);
244 if (swap < 0)
245 createError("create");
246
247#if SLOWLY_FILL_WITH_ZEROS
248 char block[1024];
249 Must(maxSize() % sizeof(block) == 0);
250 memset(block, '\0', sizeof(block));
251
252 for (off_t offset = 0; offset < maxSize(); offset += sizeof(block)) {
253 if (xwrite(swap, block, sizeof(block)) != sizeof(block))
254 createError("write");
255 }
256#else
257 if (ftruncate(swap, maxSize()) != 0)
258 createError("truncate");
259
260 char header[HeaderSize];
261 memset(header, '\0', sizeof(header));
262 if (xwrite(swap, header, sizeof(header)) != sizeof(header))
263 createError("write");
264#endif
265
266 xclose(swap);
267}
268
269// report Rock DB creation error and exit
270void
271Rock::SwapDir::createError(const char *const msg)
272{
273 int xerrno = errno; // XXX: where does errno come from?
274 debugs(47, DBG_CRITICAL, "ERROR: Failed to initialize Rock Store db in " <<
275 filePath << "; " << msg << " error: " << xstrerr(xerrno));
276 fatal("Rock Store db creation error");
277}
278
279void
281{
282 debugs(47,2, MYNAME);
283
284 // XXX: SwapDirs aren't refcounted. We make IORequestor calls, which
285 // are refcounted. We up our count once to avoid implicit delete's.
286 lock();
287
288 freeSlots = shm_old(Ipc::Mem::PageStack)(freeSlotsPath());
289
290 Must(!map);
291 map = new DirMap(inodeMapPath());
292 map->cleaner = this;
293
294 const char *ioModule = needsDiskStrand() ? "IpcIo" : "Blocking";
295 if (DiskIOModule *m = DiskIOModule::Find(ioModule)) {
296 debugs(47,2, "Using DiskIO module: " << ioModule);
297 io = m->createStrategy();
298 io->init();
299 } else {
300 debugs(47, DBG_CRITICAL, "FATAL: Rock store is missing DiskIO module: " <<
301 ioModule);
302 fatal("Rock Store missing a required DiskIO module");
303 }
304
305 theFile = io->newFile(filePath);
306 theFile->configure(fileConfig);
307 theFile->open(O_RDWR, 0644, this);
308}
309
310bool
312{
313 const bool wontEvenWorkWithoutDisker = Config.workers > 1;
314 const bool wouldWorkBetterWithDisker = DiskIOModule::Find("IpcIo");
315 return InDaemonMode() && (wontEvenWorkWithoutDisker ||
316 wouldWorkBetterWithDisker);
317}
318
319void
320Rock::SwapDir::parse(int anIndex, char *aPath)
321{
322 index = anIndex;
323
324 path = xstrdup(aPath);
325
326 // cache store is located at path/db
327 String fname(path);
328 fname.append("/rock");
329 filePath = xstrdup(fname.termedBuf());
330
331 parseSize(false);
332 parseOptions(0);
333
334 // Current openForWriting() code overwrites the old slot if needed
335 // and possible, so proactively removing old slots is probably useless.
336 assert(!repl); // repl = createRemovalPolicy(Config.replPolicy);
337
338 validateOptions();
339}
340
341void
343{
344 parseSize(true);
345 parseOptions(1);
346 // TODO: can we reconfigure the replacement policy (repl)?
347 validateOptions();
348}
349
351void
352Rock::SwapDir::parseSize(const bool reconfig)
353{
354 const int i = GetInteger();
355 if (i < 0)
356 fatal("negative Rock cache_dir size value");
357 const uint64_t new_max_size =
358 static_cast<uint64_t>(i) << 20; // MBytes to Bytes
359 if (!reconfig)
360 max_size = new_max_size;
361 else if (new_max_size != max_size) {
362 debugs(3, DBG_IMPORTANT, "WARNING: cache_dir '" << path << "' size "
363 "cannot be changed dynamically, value left unchanged (" <<
364 (max_size >> 20) << " MB)");
365 }
366}
367
370{
372 ConfigOptionVector *vector = dynamic_cast<ConfigOptionVector*>(copt);
373 if (vector) {
374 // if copt is actually a ConfigOptionVector
375 vector->options.push_back(new ConfigOptionAdapter<SwapDir>(*const_cast<SwapDir *>(this), &SwapDir::parseSizeOption, &SwapDir::dumpSizeOption));
376 vector->options.push_back(new ConfigOptionAdapter<SwapDir>(*const_cast<SwapDir *>(this), &SwapDir::parseTimeOption, &SwapDir::dumpTimeOption));
377 vector->options.push_back(new ConfigOptionAdapter<SwapDir>(*const_cast<SwapDir *>(this), &SwapDir::parseRateOption, &SwapDir::dumpRateOption));
378 } else {
379 // we don't know how to handle copt, as it's not a ConfigOptionVector.
380 // free it (and return nullptr)
381 delete copt;
382 copt = nullptr;
383 }
384 return copt;
385}
386
387bool
388Rock::SwapDir::allowOptionReconfigure(const char *const option) const
389{
390 return strcmp(option, "slot-size") != 0 &&
392}
393
395bool
396Rock::SwapDir::parseTimeOption(char const *option, const char *value, int reconfig)
397{
398 // TODO: ::SwapDir or, better, Config should provide time-parsing routines,
399 // including time unit handling. Same for size and rate.
400
401 time_msec_t *storedTime;
402 if (strcmp(option, "swap-timeout") == 0)
403 storedTime = &fileConfig.ioTimeout;
404 else
405 return false;
406
407 if (!value) {
409 return false;
410 }
411
412 // TODO: handle time units and detect parsing errors better
413 const int64_t parsedValue = strtoll(value, nullptr, 10);
414 if (parsedValue < 0) {
415 debugs(3, DBG_CRITICAL, "FATAL: cache_dir " << path << ' ' << option << " must not be negative but is: " << parsedValue);
417 return false;
418 }
419
420 const time_msec_t newTime = static_cast<time_msec_t>(parsedValue);
421
422 if (!reconfig)
423 *storedTime = newTime;
424 else if (*storedTime != newTime) {
425 debugs(3, DBG_IMPORTANT, "WARNING: cache_dir " << path << ' ' << option
426 << " cannot be changed dynamically, value left unchanged: " <<
427 *storedTime);
428 }
429
430 return true;
431}
432
434void
436{
437 if (fileConfig.ioTimeout)
438 storeAppendPrintf(e, " swap-timeout=%" PRId64,
439 static_cast<int64_t>(fileConfig.ioTimeout));
440}
441
443bool
444Rock::SwapDir::parseRateOption(char const *option, const char *value, int isaReconfig)
445{
446 int *storedRate;
447 if (strcmp(option, "max-swap-rate") == 0)
448 storedRate = &fileConfig.ioRate;
449 else
450 return false;
451
452 if (!value) {
454 return false;
455 }
456
457 // TODO: handle time units and detect parsing errors better
458 const int64_t parsedValue = strtoll(value, nullptr, 10);
459 if (parsedValue < 0) {
460 debugs(3, DBG_CRITICAL, "FATAL: cache_dir " << path << ' ' << option << " must not be negative but is: " << parsedValue);
462 return false;
463 }
464
465 const int newRate = static_cast<int>(parsedValue);
466
467 if (newRate < 0) {
468 debugs(3, DBG_CRITICAL, "FATAL: cache_dir " << path << ' ' << option << " must not be negative but is: " << newRate);
470 return false;
471 }
472
473 if (!isaReconfig)
474 *storedRate = newRate;
475 else if (*storedRate != newRate) {
476 debugs(3, DBG_IMPORTANT, "WARNING: cache_dir " << path << ' ' << option
477 << " cannot be changed dynamically, value left unchanged: " <<
478 *storedRate);
479 }
480
481 return true;
482}
483
485void
487{
488 if (fileConfig.ioRate >= 0)
489 storeAppendPrintf(e, " max-swap-rate=%d", fileConfig.ioRate);
490}
491
493bool
494Rock::SwapDir::parseSizeOption(char const *option, const char *value, int reconfig)
495{
496 uint64_t *storedSize;
497 if (strcmp(option, "slot-size") == 0)
498 storedSize = &slotSize;
499 else
500 return false;
501
502 if (!value) {
504 return false;
505 }
506
507 // TODO: handle size units and detect parsing errors better
508 const uint64_t newSize = strtoll(value, nullptr, 10);
509 if (newSize <= 0) {
510 debugs(3, DBG_CRITICAL, "FATAL: cache_dir " << path << ' ' << option << " must be positive; got: " << newSize);
512 return false;
513 }
514
515 if (newSize <= sizeof(DbCellHeader)) {
516 debugs(3, DBG_CRITICAL, "FATAL: cache_dir " << path << ' ' << option << " must exceed " << sizeof(DbCellHeader) << "; got: " << newSize);
518 return false;
519 }
520
521 if (!reconfig)
522 *storedSize = newSize;
523 else if (*storedSize != newSize) {
524 debugs(3, DBG_IMPORTANT, "WARNING: cache_dir " << path << ' ' << option
525 << " cannot be changed dynamically, value left unchanged: " <<
526 *storedSize);
527 }
528
529 return true;
530}
531
533void
535{
536 storeAppendPrintf(e, " slot-size=%" PRId64, slotSize);
537}
538
540void
542{
543 if (slotSize <= 0)
544 fatal("Rock store requires a positive slot-size");
545
546 const int64_t maxSizeRoundingWaste = 1024 * 1024; // size is configured in MB
547 const int64_t slotSizeRoundingWaste = slotSize;
548 const int64_t maxRoundingWaste =
549 max(maxSizeRoundingWaste, slotSizeRoundingWaste);
550
551 // an entry consumes at least one slot; round up to reduce false warnings
552 const int64_t blockSize = static_cast<int64_t>(slotSize);
553 const int64_t maxObjSize = max(blockSize,
554 ((maxObjectSize()+blockSize-1)/blockSize)*blockSize);
555
556 // Does the "sfileno*max-size" limit match configured db capacity?
557 const double entriesMayOccupy = entryLimitAbsolute()*static_cast<double>(maxObjSize);
558 if (entriesMayOccupy + maxRoundingWaste < maxSize()) {
559 const int64_t diskWasteSize = maxSize() - static_cast<int64_t>(entriesMayOccupy);
560 debugs(47, DBG_CRITICAL, "WARNING: Rock cache_dir " << path << " wastes disk space due to entry limits:" <<
561 "\n\tconfigured db capacity: " << maxSize() << " bytes" <<
562 "\n\tconfigured db slot size: " << slotSize << " bytes" <<
563 "\n\tconfigured maximum entry size: " << maxObjectSize() << " bytes" <<
564 "\n\tmaximum number of cache_dir entries supported by Squid: " << entryLimitAbsolute() <<
565 "\n\tdisk space all entries may use: " << entriesMayOccupy << " bytes" <<
566 "\n\tdisk space wasted: " << diskWasteSize << " bytes");
567 }
568
569 // Does the "absolute slot count" limit match configured db capacity?
570 const double slotsMayOccupy = slotLimitAbsolute()*static_cast<double>(slotSize);
571 if (slotsMayOccupy + maxRoundingWaste < maxSize()) {
572 const int64_t diskWasteSize = maxSize() - static_cast<int64_t>(entriesMayOccupy);
573 debugs(47, DBG_CRITICAL, "WARNING: Rock cache_dir " << path << " wastes disk space due to slot limits:" <<
574 "\n\tconfigured db capacity: " << maxSize() << " bytes" <<
575 "\n\tconfigured db slot size: " << slotSize << " bytes" <<
576 "\n\tmaximum number of rock cache_dir slots supported by Squid: " << slotLimitAbsolute() <<
577 "\n\tdisk space all slots may use: " << slotsMayOccupy << " bytes" <<
578 "\n\tdisk space wasted: " << diskWasteSize << " bytes");
579 }
580}
581
582bool
583Rock::SwapDir::canStore(const StoreEntry &e, int64_t diskSpaceNeeded, int &load) const
584{
585 if (diskSpaceNeeded >= 0)
586 diskSpaceNeeded += sizeof(DbCellHeader);
587 if (!::SwapDir::canStore(e, diskSpaceNeeded, load))
588 return false;
589
590 if (!theFile || !theFile->canWrite())
591 return false;
592
593 if (!map)
594 return false;
595
596 // Do not start I/O transaction if there are less than 10% free pages left.
597 // TODO: reserve page instead
598 if (needsDiskStrand() &&
600 debugs(47, 5, "too few shared pages for IPC I/O left");
601 return false;
602 }
603
604 if (io->shedLoad())
605 return false;
606
607 load = io->load();
608 return true;
609}
610
613{
614 if (!theFile || theFile->error()) {
615 debugs(47,4, theFile);
616 return nullptr;
617 }
618
619 sfileno filen;
620 Ipc::StoreMapAnchor *const slot =
621 map->openForWriting(reinterpret_cast<const cache_key *>(e.key), filen);
622 if (!slot) {
623 debugs(47, 5, "map->add failed");
624 return nullptr;
625 }
626
627 assert(filen >= 0);
628 slot->set(e);
629
630 // XXX: We rely on our caller, storeSwapOutStart(), to set e.fileno.
631 // If that does not happen, the entry will not decrement the read level!
632
633 Rock::SwapDir::Pointer self(this);
634 IoState *sio = new IoState(self, &e, cbIo, cbData);
635
636 sio->swap_dirn = index;
637 sio->swap_filen = filen;
638 sio->writeableAnchor_ = slot;
639
640 debugs(47,5, "dir " << index << " created new filen " <<
641 asHex(sio->swap_filen).upperCase().minDigits(8) << " starting at " <<
642 diskOffset(sio->swap_filen));
643
644 sio->file(theFile);
645
646 trackReferences(e);
647 return sio;
648}
649
652{
653 if (!theFile || theFile->error()) {
654 debugs(47,4, theFile);
655 return nullptr;
656 }
657
658 Must(update.fresh);
659 Must(update.fresh.fileNo >= 0);
660
661 Rock::SwapDir::Pointer self(this);
662 IoState *sio = new IoState(self, update.entry, cbIo, data);
663
664 sio->swap_dirn = index;
665 sio->swap_filen = update.fresh.fileNo;
666 sio->writeableAnchor_ = update.fresh.anchor;
667
668 debugs(47,5, "dir " << index << " updating filen " <<
669 asHex(sio->swap_filen).upperCase().minDigits(8) << " starting at " <<
670 diskOffset(sio->swap_filen));
671
672 sio->file(theFile);
673 return sio;
674}
675
676int64_t
677Rock::SwapDir::diskOffset(const SlotId sid) const
678{
679 assert(sid >= 0);
680 return HeaderSize + slotSize*sid;
681}
682
683int64_t
685{
686 assert(pageId);
687 return diskOffset(pageId.number - 1);
688}
689
690int64_t
692{
693 assert(map);
694 return diskOffset(map->sliceLimit());
695}
696
699{
700 Ipc::Mem::PageId pageId;
701
702 if (freeSlots->pop(pageId)) {
703 const auto slotId = pageId.number - 1;
704 debugs(47, 5, "got a previously free slot: " << slotId);
705 map->prepFreeSlice(slotId);
706 return slotId;
707 }
708
709 // catch free slots delivered to noteFreeMapSlice()
710 assert(!waitingForPage);
711 waitingForPage = &pageId;
712 if (map->purgeOne()) {
713 assert(!waitingForPage); // noteFreeMapSlice() should have cleared it
714 assert(pageId.set());
715 const auto slotId = pageId.number - 1;
716 debugs(47, 5, "got a previously busy slot: " << slotId);
717 map->prepFreeSlice(slotId);
718 return slotId;
719 }
720 assert(waitingForPage == &pageId);
721 waitingForPage = nullptr;
722
723 // This may happen when the number of available db slots is close to the
724 // number of concurrent requests reading or writing those slots, which may
725 // happen when the db is "small" compared to the request traffic OR when we
726 // are rebuilding and have not loaded "many" entries or empty slots yet.
727 debugs(47, 3, "cannot get a slot; entries: " << map->entryCount());
728 throw TexcHere("ran out of free db slots");
729}
730
731bool
733{
734 return 0 <= slotId && slotId < slotLimitActual();
735}
736
737void
739{
740 Ipc::Mem::PageId pageId;
742 pageId.number = sliceId+1;
743 if (waitingForPage) {
744 *waitingForPage = pageId;
745 waitingForPage = nullptr;
746 } else {
747 freeSlots->push(pageId);
748 }
749}
750
751// tries to open an old entry with swap_filen for reading
753Rock::SwapDir::openStoreIO(StoreEntry &e, StoreIOState::STIOCB * const cbIo, void * const cbData)
754{
755 if (!theFile || theFile->error()) {
756 debugs(47,4, theFile);
757 return nullptr;
758 }
759
760 if (!e.hasDisk()) {
761 debugs(47,4, e);
762 return nullptr;
763 }
764
765 // Do not start I/O transaction if there are less than 10% free pages left.
766 // TODO: reserve page instead
767 if (needsDiskStrand() &&
769 debugs(47, 5, "too few shared pages for IPC I/O left");
770 return nullptr;
771 }
772
773 // The are two ways an entry can get swap_filen: our get() locked it for
774 // reading or our storeSwapOutStart() locked it for writing. Peeking at our
775 // locked entry is safe, but no support for reading the entry we swap out.
776 const Ipc::StoreMapAnchor *slot = map->peekAtReader(e.swap_filen);
777 if (!slot)
778 return nullptr; // we were writing after all
779
780 Rock::SwapDir::Pointer self(this);
781 IoState *sio = new IoState(self, &e, cbIo, cbData);
782
783 sio->swap_dirn = index;
784 sio->swap_filen = e.swap_filen;
785 sio->readableAnchor_ = slot;
786 sio->file(theFile);
787
788 debugs(47,5, "dir " << index << " has old filen: " <<
789 asHex(sio->swap_filen).upperCase().minDigits(8));
790
791 // When StoreEntry::swap_filen for e was set by our anchorEntry(), e had a
792 // public key, but it could have gone private since then (while keeping the
793 // anchor lock). The stale anchor key is not (and cannot be) erased (until
794 // the marked-for-deletion/release anchor/entry is unlocked is recycled).
795 const auto ourAnchor = [&]() {
796 if (const auto publicKey = e.publicKey())
797 return slot->sameKey(publicKey);
798 return true; // cannot check
799 };
800 assert(ourAnchor());
801
802 // For collapsed disk hits: e.swap_file_sz and slot->basics.swap_file_sz
803 // may still be zero and basics.swap_file_sz may grow.
805
806 return sio;
807}
808
809void
811{
812 if (!theFile)
813 fatalf("Rock cache_dir failed to initialize db file: %s", filePath);
814
815 if (theFile->error()) {
816 int xerrno = errno; // XXX: where does errno come from
817 fatalf("Rock cache_dir at %s failed to open db file: %s", filePath,
818 xstrerr(xerrno));
819 }
820
821 debugs(47, 2, "Rock cache_dir[" << index << "] limits: " <<
822 std::setw(12) << maxSize() << " disk bytes, " <<
823 std::setw(7) << map->entryLimit() << " entries, and " <<
824 std::setw(7) << map->sliceLimit() << " slots");
825
826 if (!Rebuild::Start(*this))
827 storeRebuildComplete(nullptr);
828}
829
830void
832{
833 theFile = nullptr;
834}
835
836void
837Rock::SwapDir::readCompleted(const char *, int rlen, int errflag, RefCount< ::ReadRequest> r)
838{
839 ReadRequest *request = dynamic_cast<Rock::ReadRequest*>(r.getRaw());
840 assert(request);
841 IoState::Pointer sio = request->sio;
842 sio->handleReadCompletion(*request, rlen, errflag);
843}
844
845void
847{
848 // TODO: Move details into IoState::handleWriteCompletion() after figuring
849 // out how to deal with map access. See readCompleted().
850
851 Rock::WriteRequest *request = dynamic_cast<Rock::WriteRequest*>(r.getRaw());
852 assert(request);
853 assert(request->sio != nullptr);
854 IoState &sio = *request->sio;
855
856 // quit if somebody called IoState::close() while we were waiting
857 if (!sio.stillWaiting()) {
858 debugs(79, 3, "ignoring closed entry " << sio.swap_filen);
859 noteFreeMapSlice(request->sidCurrent);
860 return;
861 }
862
863 debugs(79, 7, "errflag=" << errflag << " rlen=" << request->len << " eof=" << request->eof);
864
865 if (errflag != DISK_OK)
866 handleWriteCompletionProblem(errflag, *request);
867 else if (!sio.expectedReply(request->id))
868 handleWriteCompletionProblem(DISK_ERROR, *request);
869 else
870 handleWriteCompletionSuccess(*request);
871
872 if (sio.touchingStoreEntry())
874}
875
877void
879{
880 auto &sio = *(request.sio);
881 sio.splicingPoint = request.sidCurrent;
882 // do not increment sio.offset_ because we do it in sio->write()
883
884 assert(sio.writeableAnchor_);
885 if (sio.writeableAnchor_->start < 0) { // wrote the first slot
886 Must(request.sidPrevious < 0);
887 sio.writeableAnchor_->start = request.sidCurrent;
888 } else {
889 Must(request.sidPrevious >= 0);
890 map->writeableSlice(sio.swap_filen, request.sidPrevious).next = request.sidCurrent;
891 }
892
893 // finalize the shared slice info after writing slice contents to disk;
894 // the chain gets possession of the slice we were writing
895 Ipc::StoreMap::Slice &slice =
896 map->writeableSlice(sio.swap_filen, request.sidCurrent);
897 slice.size = request.len - sizeof(DbCellHeader);
898 Must(slice.next < 0);
899
900 if (request.eof) {
901 assert(sio.e);
902 if (sio.touchingStoreEntry()) {
903 sio.e->swap_file_sz = sio.writeableAnchor_->basics.swap_file_sz =
904 sio.offset_;
905
906 map->switchWritingToReading(sio.swap_filen);
907 // sio.e keeps the (now read) lock on the anchor
908 // storeSwapOutFileClosed() sets swap_status and calls storeWriterDone()
909 }
910 sio.writeableAnchor_ = nullptr;
911 sio.finishedWriting(DISK_OK);
912 }
913}
914
916void
918{
919 auto &sio = *request.sio;
920
921 noteFreeMapSlice(request.sidCurrent);
922
923 writeError(sio);
924 sio.finishedWriting(errflag);
925 // and hope that Core will call disconnect() to close the map entry
926}
927
928void
930{
931 // Do not abortWriting here. The entry should keep the write lock
932 // instead of losing association with the store and confusing core.
933 map->freeEntry(sio.swap_filen); // will mark as unusable, just in case
934
935 if (sio.touchingStoreEntry())
937 // else noop: a fresh entry update error does not affect stale entry readers
938
939 // All callers must also call IoState callback, to propagate the error.
940}
941
942void
944{
945 if (!map)
946 return;
947
948 Ipc::StoreMapUpdate update(updatedE);
949 if (!map->openForUpdating(update, updatedE->swap_filen))
950 return;
951
952 try {
953 AsyncJob::Start(new HeaderUpdater(this, update));
954 } catch (const std::exception &ex) {
955 debugs(20, 2, "error starting to update entry " << *updatedE << ": " << ex.what());
956 map->abortUpdating(update);
957 }
958}
959
960bool
962{
963 return freeSlots != nullptr && !freeSlots->size();
964}
965
966// storeSwapOutFileClosed calls this method on DISK_NO_SPACE_LEFT,
967// but it should not happen for us
968void
970{
971 debugs(20, DBG_IMPORTANT, "ERROR: Squid BUG: No space left with rock cache_dir: " <<
972 filePath);
973}
974
976void
978{
979 // The Store calls this to free some db space, but there is nothing wrong
980 // with a full() db, except when db has to shrink after reconfigure, and
981 // we do not support shrinking yet (it would have to purge specific slots).
982 // TODO: Disable maintain() requests when they are pointless.
983}
984
985void
987{
988 debugs(47, 5, &e << ' ' << e.swap_dirn << ' ' << e.swap_filen);
989 if (repl && repl->Referenced)
990 repl->Referenced(repl, &e, &e.repl);
991}
992
993bool
995{
996 debugs(47, 5, &e << ' ' << e.swap_dirn << ' ' << e.swap_filen);
997 if (repl && repl->Dereferenced)
998 repl->Dereferenced(repl, &e, &e.repl);
999
1000 // no need to keep e in the global store_table for us; we have our own map
1001 return false;
1002}
1003
1004bool
1006{
1007 // no entry-specific files to unlink
1008 return false;
1009}
1010
1011void
1013{
1014 if (map)
1015 map->freeEntryByKey(key); // may not be there
1016}
1017
1018void
1020{
1021 debugs(47, 5, e);
1022 if (e.hasDisk(index)) {
1023 if (map->freeEntry(e.swap_filen))
1025 if (!e.locked())
1026 disconnect(e);
1027 } else if (const auto key = e.publicKey()) {
1028 evictIfFound(key);
1029 }
1030}
1031
1032void
1034{
1035 debugs(47, 5, e);
1036 if (repl)
1037 repl->Add(repl, &e, &e.repl);
1038}
1039
1040void
1042{
1043 debugs(47, 5, e);
1044 if (repl)
1045 repl->Remove(repl, &e, &e.repl);
1046}
1047
1048void
1050{
1051 storeAppendPrintf(&e, "\n");
1052 storeAppendPrintf(&e, "Maximum Size: %" PRIu64 " KB\n", maxSize() >> 10);
1053 storeAppendPrintf(&e, "Current Size: %.2f KB %.2f%%\n",
1054 currentSize() / 1024.0,
1055 Math::doublePercent(currentSize(), maxSize()));
1056
1057 const int entryLimit = entryLimitActual();
1058 const int slotLimit = slotLimitActual();
1059 storeAppendPrintf(&e, "Maximum entries: %9d\n", entryLimit);
1060 if (map && entryLimit > 0) {
1061 const int entryCount = map->entryCount();
1062 storeAppendPrintf(&e, "Current entries: %9d %.2f%%\n",
1063 entryCount, (100.0 * entryCount / entryLimit));
1064 }
1065
1066 storeAppendPrintf(&e, "Maximum slots: %9d\n", slotLimit);
1067 if (map && slotLimit > 0) {
1068 const unsigned int slotsFree = !freeSlots ? 0 : freeSlots->size();
1069 if (slotsFree <= static_cast<unsigned int>(slotLimit)) {
1070 const int usedSlots = slotLimit - static_cast<int>(slotsFree);
1071 storeAppendPrintf(&e, "Used slots: %9d %.2f%%\n",
1072 usedSlots, (100.0 * usedSlots / slotLimit));
1073 }
1074 if (slotLimit < 100) { // XXX: otherwise too expensive to count
1076 map->updateStats(stats);
1077 stats.dump(e);
1078 }
1079 }
1080
1081 storeAppendPrintf(&e, "Pending operations: %d out of %d\n",
1083
1084 storeAppendPrintf(&e, "Flags:");
1085
1086 if (flags.selected)
1087 storeAppendPrintf(&e, " SELECTED");
1088
1089 if (flags.read_only)
1090 storeAppendPrintf(&e, " READ-ONLY");
1091
1092 storeAppendPrintf(&e, "\n");
1093
1094}
1095
1096SBuf
1098{
1099 return Ipc::Mem::Segment::Name(SBuf(path), "map");
1100}
1101
1102const char *
1104{
1105 static String spacesPath;
1106 spacesPath = path;
1107 spacesPath.append("_spaces");
1108 return spacesPath.termedBuf();
1109}
1110
1111bool
1113{
1114 return map->hasReadableEntry(reinterpret_cast<const cache_key*>(e.key));
1115}
1116
1118
1120{
1121 Must(mapOwners.empty() && freeSlotsOwners.empty());
1122 for (size_t i = 0; i < Config.cacheSwap.n_configured; ++i) {
1123 if (const Rock::SwapDir *const sd = dynamic_cast<Rock::SwapDir *>(INDEXSD(i))) {
1124 rebuildStatsOwners.push_back(Rebuild::Stats::Init(*sd));
1125
1126 const int64_t capacity = sd->slotLimitActual();
1127
1128 SwapDir::DirMap::Owner *const mapOwner =
1129 SwapDir::DirMap::Init(sd->inodeMapPath(), capacity);
1130 mapOwners.push_back(mapOwner);
1131
1132 // TODO: somehow remove pool id and counters from PageStack?
1135 config.pageSize = 0; // this is an index of slots on _disk_
1136 config.capacity = capacity;
1137 config.createFull = false; // Rebuild finds and pushes free slots
1138 Ipc::Mem::Owner<Ipc::Mem::PageStack> *const freeSlotsOwner =
1139 shm_new(Ipc::Mem::PageStack)(sd->freeSlotsPath(), config);
1140 freeSlotsOwners.push_back(freeSlotsOwner);
1141 }
1142 }
1143}
1144
1146{
1147 for (size_t i = 0; i < mapOwners.size(); ++i) {
1148 delete rebuildStatsOwners[i];
1149 delete mapOwners[i];
1150 delete freeSlotsOwners[i];
1151 }
1152}
1153
#define Assure(condition)
Definition Assure.h:35
AsHex< Integer > asHex(const Integer n)
a helper to ease AsHex object creation
Definition IoManip.h:169
int GetInteger(void)
Definition Parsing.cc:148
#define shm_new(Class)
Definition Pointer.h:200
#define shm_old(Class)
Definition Pointer.h:201
#define DefineRunnerRegistratorIn(Namespace, ClassName)
class SquidConfig Config
#define INDEXSD(i)
Definition SquidConfig.h:74
#define TexcHere(msg)
legacy convenience macro; it is not difficult to type Here() now
#define Must(condition)
#define assert(EX)
Definition assert.h:17
static char vector[AUTH_VECTOR_LEN]
void self_destruct(void)
Definition cache_cf.cc:275
static void Start(const Pointer &job)
Definition AsyncJob.cc:37
static void Broadcast(const StoreEntry &e, const bool includingThisWorker=false)
notify other workers about changes in entry state (e.g., new data)
std::vector< ConfigOption * > options
static DiskIOModule * Find(char const *type)
Shared memory page identifier, address, or handler.
Definition Page.h:24
PoolId pool
Definition Page.h:39
uint32_t number
page number within the segment
Definition Page.h:42
bool set() const
true if and only if both critical components have been initialized
Definition Page.h:29
PageStack construction and SharedMemorySize calculation parameters.
Definition PageStack.h:123
PageCount capacity
the maximum number of pages
Definition PageStack.h:127
size_t pageSize
page size, used to calculate shared memory size
Definition PageStack.h:126
bool createFull
whether a newly created PageStack should be prefilled with PageIds
Definition PageStack.h:130
static PoolId IdForSwapDirSpace(const int dirIdx)
stack of free rock cache_dir slot numbers
Definition PageStack.h:171
static SBuf Name(const SBuf &prefix, const char *suffix)
concatenates parts of a name to form a complete name (or its prefix)
Definition Segment.cc:51
approximate stats of a set of ReadWriteLocks
void dump(StoreEntry &e) const
bool sameKey(const cache_key *const aKey) const
Definition StoreMap.cc:952
struct Ipc::StoreMapAnchor::Basics basics
bool complete() const
Definition StoreMap.h:77
void set(const StoreEntry &anEntry, const cache_key *aKey=nullptr)
store StoreEntry key and basics for an inode slot
Definition StoreMap.cc:959
void exportInto(StoreEntry &) const
load StoreEntry basics that were previously stored with set()
Definition StoreMap.cc:979
std::atomic< StoreMapSliceId > next
ID of the next entry slice.
Definition StoreMap.h:49
std::atomic< Size > size
slice contents size
Definition StoreMap.h:48
sfileno fileNo
StoreMap::fileNos[name], for convenience/speed.
Definition StoreMap.h:194
StoreMapAnchor * anchor
StoreMap::anchors[fileNo], for convenience/speed.
Definition StoreMap.h:193
Aggregates information required for updating entry metadata and headers.
Definition StoreMap.h:182
Edition fresh
new anchor and the updated chain prefix
Definition StoreMap.h:209
StoreEntry * entry
the store entry being updated
Definition StoreMap.h:207
StoreIOState::Pointer sio
Definition MemObject.h:162
SwapOut swapout
Definition MemObject.h:169
C * getRaw() const
Definition RefCount.h:89
bool expectedReply(const IoXactionId receivedId)
Ipc::StoreMapAnchor * writeableAnchor_
starting point for writing
Definition RockIoState.h:57
void file(const RefCount< DiskFile > &aFile)
const Ipc::StoreMapAnchor * readableAnchor_
starting point for reading
Definition RockIoState.h:56
bool stillWaiting() const
whether we are still waiting for the I/O results (i.e., not closed)
Definition RockIoState.h:43
void handleReadCompletion(Rock::ReadRequest &request, const int rlen, const int errFlag)
forwards read data (or an error) to the reader that initiated this I/O
IoState::Pointer sio
static Ipc::Mem::Owner< Stats > * Init(const SwapDir &)
static bool Start(SwapDir &dir)
~SwapDirRr() override
void create() override
called when the runner should create a new memory segment
int64_t slotLimitAbsolute() const
Rock store implementation limit.
void readCompleted(const char *buf, int len, int errflag, RefCount< ::ReadRequest >) override
void handleWriteCompletionProblem(const int errflag, const WriteRequest &request)
code shared by writeCompleted() error handling cases
int64_t slotLimitActual() const
total number of slots in this db
void dumpSizeOption(StoreEntry *e) const
reports size-specific options; mimics SwapDir::optionObjectSizeDump()
SBuf inodeMapPath() const
void parseSize(const bool reconfiguring)
parses anonymous cache_dir size option
bool parseTimeOption(char const *option, const char *value, int reconfiguring)
parses time-specific options; mimics SwapDir::optionObjectSizeParse()
void validateOptions()
warns of configuration problems; may quit
void handleWriteCompletionSuccess(const WriteRequest &request)
code shared by writeCompleted() success handling cases
~SwapDir() override
bool parseRateOption(char const *option, const char *value, int reconfiguring)
parses rate-specific options; mimics SwapDir::optionObjectSizeParse()
const char * freeSlotsPath() const
bool full() const
no more entries can be stored without purging
int64_t diskOffset(Ipc::Mem::PageId &pageId) const
void writeCompleted(int errflag, size_t len, RefCount< ::WriteRequest >) override
bool validSlotId(const SlotId slotId) const
whether the given slot ID may point to a slot in this db
void ignoreReferences(StoreEntry &e)
delete from repl policy scope
void noteFreeMapSlice(const Ipc::StoreMapSliceId fileno) override
adjust slice-linked state before a locked Readable slice is erased
int64_t diskOffsetLimit() const
void trackReferences(StoreEntry &e)
add to replacement policy scope
void closeCompleted() override
void dumpRateOption(StoreEntry *e) const
reports rate-specific options; mimics SwapDir::optionObjectSizeDump()
void createError(const char *const msg)
void ioCompletedNotification() override
StoreIOState::Pointer createUpdateIO(const Ipc::StoreMapUpdate &, StoreIOState::STIOCB *, void *)
void anchorEntry(StoreEntry &e, const sfileno filen, const Ipc::StoreMapAnchor &anchor)
void dumpTimeOption(StoreEntry *e) const
reports time-specific options; mimics SwapDir::optionObjectSizeDump()
void writeError(StoreIOState &sio)
bool parseSizeOption(char const *option, const char *value, int reconfiguring)
parses size-specific options; mimics SwapDir::optionObjectSizeParse()
SlotId reserveSlotForWriting()
finds and returns a free db slot to fill or throws
int64_t entryLimitActual() const
max number of possible entries in db
SlotId sidPrevious
slot that will point to sidCurrent in the cache_dir map
bool eof
whether this is the last request for the entry
SlotId sidCurrent
slot being written using this write request
IoXactionId id
identifies this write transaction for the requesting IoState
IoState::Pointer sio
Definition SBuf.h:94
Store::DiskConfig cacheSwap
int max_open_disk_fds
uint16_t flags
Definition Store.h:231
sdirno swap_dirn
Definition Store.h:237
int locked() const
Definition Store.h:145
void detachFromDisk()
Definition store.cc:1953
bool hasDisk(const sdirno dirn=-1, const sfileno filen=-1) const
Definition store.cc:1929
const cache_key * publicKey() const
Definition Store.h:112
sfileno swap_filen
unique ID inside a cache_dir for swapped out entries; -1 for others
Definition Store.h:235
void storeWriterDone()
called when a store writer ends its work (successfully or not)
Definition store.cc:1808
void attachToDisk(const sdirno, const sfileno, const swap_status_t)
Definition store.cc:1940
RemovalPolicyNode repl
Definition Store.h:221
MemObject * mem_obj
Definition Store.h:220
ping_status_t ping_status
Definition Store.h:241
store_status_t store_status
Definition Store.h:243
void createMemObject()
Definition store.cc:1575
uint64_t swap_file_sz
Definition Store.h:229
sfileno swap_filen
StoreEntry * e
bool touchingStoreEntry() const
sdirno swap_dirn
void STIOCB(void *their_data, int errflag, StoreIOState::Pointer self)
virtual void updateHeaders(StoreEntry *)
make stored metadata and HTTP headers the same as in the given entry
Definition Controlled.h:35
virtual bool updateAnchored(StoreEntry &)
Definition Controlled.h:44
virtual bool anchorToCache(StoreEntry &)
Definition Controlled.h:39
virtual bool canStore(const StoreEntry &e, int64_t diskSpaceNeeded, int &load) const =0
check whether we can store the entry; if we can, report current load
Definition Disk.cc:164
void create() override
create system resources needed for this store to operate in the future
Definition Disk.cc:40
virtual void diskFull()
Definition Disk.cc:145
virtual void parse(int index, char *path)=0
virtual bool doReportStat() const
whether stat should be reported by this SwapDir
Definition Disk.h:35
virtual void finalizeSwapoutSuccess(const StoreEntry &)=0
finalize the successful swapout that has been already noticed by Store
virtual bool needsDiskStrand() const
needs a dedicated kid process
Definition Disk.cc:249
virtual bool allowOptionReconfigure(const char *const) const
Definition Disk.h:85
virtual void statfs(StoreEntry &) const
Definition Disk.cc:83
virtual void reconfigure()=0
virtual void disconnect(StoreEntry &)
called when the entry is about to forget its association with cache_dir
Definition Disk.h:71
virtual StoreIOState::Pointer createStoreIO(StoreEntry &, StoreIOState::STIOCB *, void *)=0
virtual bool unlinkdUseful() const =0
whether SwapDir may benefit from unlinkd
StoreEntry * get(const cache_key *) override
Definition Disk.cc:393
virtual bool hasReadableEntry(const StoreEntry &e) const =0
whether this cache dir has an entry with e.key
virtual ConfigOption * getOptionTree() const
Definition Disk.cc:258
void reference(StoreEntry &e) override
somebody needs this entry (many cache replacement policies need to know)
Definition Disk.cc:136
bool dereference(StoreEntry &e) override
Definition Disk.cc:139
virtual void finalizeSwapoutFailure(StoreEntry &)=0
abort the failed swapout that has been already noticed by Store
void maintain() override
perform regular periodic maintenance; TODO: move to UFSSwapDir::Maintain
Definition Disk.cc:86
virtual StoreIOState::Pointer openStoreIO(StoreEntry &, StoreIOState::STIOCB *, void *)=0
virtual void init()=0
virtual uint64_t currentSize() const =0
current size
virtual void evictIfFound(const cache_key *)=0
virtual uint64_t currentCount() const =0
the total number of objects stored right now
virtual void evictCached(StoreEntry &e)=0
char const * termedBuf() const
Definition SquidString.h:97
void append(char const *buf, int len)
Definition String.cc:131
A const & max(A const &lhs, A const &rhs)
A const & min(A const &lhs, A const &rhs)
#define MYNAME
Definition Stream.h:219
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define DBG_CRITICAL
Definition Stream.h:37
#define O_BINARY
Definition defines.h:134
#define EBIT_SET(flag, bit)
Definition defines.h:65
#define DISK_ERROR
Definition defines.h:28
#define DISK_OK
Definition defines.h:27
@ ENTRY_VALIDATED
Definition enums.h:108
@ PING_NONE
Has not considered whether to send ICP queries to peers yet.
Definition enums.h:36
@ SWAPOUT_WRITING
Definition enums.h:56
@ SWAPOUT_DONE
Definition enums.h:59
@ STORE_PENDING
Definition enums.h:46
@ STORE_OK
Definition enums.h:45
void fatal(const char *message)
Definition fatal.cc:28
void fatalf(const char *fmt,...)
Definition fatal.cc:68
int store_open_disk_fd
size_t PageLevel()
approximate total number of shared memory pages used now
Definition Pages.cc:80
size_t PageLimit()
the total number of shared memory pages that can be in use at any time
Definition Pages.cc:55
int32_t StoreMapSliceId
Definition StoreMap.h:24
double doublePercent(const double, const double)
Definition SquidMath.cc:25
Definition forward.h:28
sfileno SlotId
db cell number, starting with cell 0 (always occupied by the db header)
Definition forward.h:33
#define xstrdup
unsigned char cache_key
Store key.
Definition forward.h:29
signed_int32_t sfileno
Definition forward.h:22
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
void storeRebuildComplete(StoreRebuildData *dc)
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition strtoll.c:61
std::atomic< uint64_t > swap_file_sz
Definition StoreMap.h:105
bool IamDiskProcess() STUB_RETVAL_NOP(false) bool InDaemonMode() STUB_RETVAL_NOP(false) bool UsingSmp() STUB_RETVAL_NOP(false) bool IamCoordinatorProcess() STUB_RETVAL(false) bool IamPrimaryProcess() STUB_RETVAL(false) int NumberOfKids() STUB_RETVAL(0) void setMaxFD(void) STUB void setSystemLimits(void) STUB void squid_signal(int
whether the current process is dedicated to managing a cache_dir
uint64_t time_msec_t
Definition gadgets.h:16
bool InDaemonMode()
Whether we are running in daemon mode.
Definition tools.cc:683
bool UsingSmp()
Whether there should be more than one worker process running.
Definition tools.cc:689
#define PRIu64
Definition types.h:114
#define PRId64
Definition types.h:104
int xwrite(int fd, const void *buf, size_t bufSize)
POSIX write(2) equivalent.
Definition unistd.h:67
int xclose(int fd)
POSIX close(2) equivalent.
Definition unistd.h:43
int xopen(const char *filename, int oflag, int pmode=0)
POSIX open(2) equivalent.
Definition unistd.h:55
#define safe_free(x)
Definition xalloc.h:73
const char * xstrerr(int error)
Definition xstrerror.cc:83