From 451ebf24ce532f8d59f929efd486104fcebf1aa6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Apr 2016 14:12:38 +0200 Subject: Cache path info lookups in SQLite This re-implements the binary cache database in C++, allowing it to be used by other Store backends, in particular the S3 backend. --- src/libstore/binary-cache-store.cc | 3 +- src/libstore/derivations.cc | 2 +- src/libstore/download.cc | 2 +- src/libstore/http-binary-cache-store.cc | 15 ++- src/libstore/local-store.cc | 2 - src/libstore/nar-info-disk-cache.cc | 217 ++++++++++++++++++++++++++++++++ src/libstore/nar-info-disk-cache.hh | 28 +++++ src/libstore/nar-info.cc | 20 +-- src/libstore/nar-info.hh | 4 - src/libstore/remote-store.cc | 1 - src/libstore/sqlite.cc | 5 + src/libstore/sqlite.hh | 1 + src/libstore/store-api.cc | 50 +++++++- src/libstore/store-api.hh | 7 ++ src/libutil/hash.cc | 32 ++++- src/libutil/hash.hh | 12 +- src/libutil/util.cc | 12 ++ src/libutil/util.hh | 3 + 18 files changed, 380 insertions(+), 36 deletions(-) create mode 100644 src/libstore/nar-info-disk-cache.cc create mode 100644 src/libstore/nar-info-disk-cache.hh diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index 81800d4cb2..3857ed93e2 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -59,7 +59,7 @@ void BinaryCacheStore::addToCache(const ValidPathInfo & info, narInfo->narSize = nar.size(); narInfo->narHash = hashString(htSHA256, nar); - if (info.narHash.type != htUnknown && info.narHash != narInfo->narHash) + if (info.narHash && info.narHash != narInfo->narHash) throw Error(format("refusing to copy corrupted path ‘%1%’ to binary cache") % info.path); /* Compress the NAR. */ @@ -96,7 +96,6 @@ void BinaryCacheStore::addToCache(const ValidPathInfo & info, { auto state_(state.lock()); state_->pathInfoCache.upsert(narInfo->path, std::shared_ptr(narInfo)); - stats.pathInfoCacheSize = state_->pathInfoCache.size(); } stats.narInfoWrite++; diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index d9b009d403..becf852454 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -290,7 +290,7 @@ Hash hashDerivationModulo(Store & store, Derivation drv) DerivationInputs inputs2; for (auto & i : drv.inputDrvs) { Hash h = drvHashes[i.first]; - if (h.type == htUnknown) { + if (!h) { assert(store.isValidPath(i.first)); Derivation drv2 = readDerivation(i.first); h = hashDerivationModulo(store, drv2); diff --git a/src/libstore/download.cc b/src/libstore/download.cc index 8cd3ad741e..eed630517d 100644 --- a/src/libstore/download.cc +++ b/src/libstore/download.cc @@ -225,7 +225,7 @@ Path Downloader::downloadCached(ref store, const string & url_, bool unpa { auto url = resolveUri(url_); - Path cacheDir = getEnv("XDG_CACHE_HOME", getEnv("HOME", "") + "/.cache") + "/nix/tarballs"; + Path cacheDir = getCacheDir() + "/nix/tarballs"; createDirs(cacheDir); string urlHash = printHash32(hashString(htSHA256, url)); diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc index 6dcea1cbf0..771eb42ee5 100644 --- a/src/libstore/http-binary-cache-store.cc +++ b/src/libstore/http-binary-cache-store.cc @@ -1,6 +1,7 @@ #include "binary-cache-store.hh" #include "download.hh" #include "globals.hh" +#include "nar-info-disk-cache.hh" namespace nix { @@ -24,13 +25,23 @@ public: { if (cacheUri.back() == '/') cacheUri.pop_back(); + + diskCache = getNarInfoDiskCache(); + } + + std::string getUri() override + { + return cacheUri; } void init() override { // FIXME: do this lazily? - if (!fileExists("nix-cache-info")) - throw Error(format("‘%s’ does not appear to be a binary cache") % cacheUri); + if (!diskCache->cacheExists(cacheUri)) { + if (!fileExists("nix-cache-info")) + throw Error(format("‘%s’ does not appear to be a binary cache") % cacheUri); + diskCache->createCache(cacheUri); + } } protected: diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index cef2eb3f0f..9bc164e19f 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -580,7 +580,6 @@ uint64_t LocalStore::addValidPath(State & state, { auto state_(Store::state.lock()); state_->pathInfoCache.upsert(info.path, std::make_shared(info)); - stats.pathInfoCacheSize = state_->pathInfoCache.size(); } return id; @@ -1069,7 +1068,6 @@ void LocalStore::invalidatePath(State & state, const Path & path) { auto state_(Store::state.lock()); state_->pathInfoCache.erase(path); - stats.pathInfoCacheSize = state_->pathInfoCache.size(); } } diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc new file mode 100644 index 0000000000..30ef7b36c9 --- /dev/null +++ b/src/libstore/nar-info-disk-cache.cc @@ -0,0 +1,217 @@ +#include "nar-info-disk-cache.hh" +#include "sync.hh" +#include "sqlite.hh" +#include "globals.hh" + +#include + +namespace nix { + +static const char * schema = R"sql( + +create table if not exists BinaryCaches ( + id integer primary key autoincrement not null, + url text unique not null, + timestamp integer not null, + storeDir text not null, + wantMassQuery integer not null, + priority integer not null +); + +create table if not exists NARs ( + cache integer not null, + storePath text not null, + url text, + compression text, + fileHash text, + fileSize integer, + narHash text, + narSize integer, + refs text, + deriver text, + sigs text, + timestamp integer not null, + primary key (cache, storePath), + foreign key (cache) references BinaryCaches(id) on delete cascade +); + +create table if not exists NARExistence ( + cache integer not null, + storePath text not null, + exist integer not null, + timestamp integer not null, + primary key (cache, storePath), + foreign key (cache) references BinaryCaches(id) on delete cascade +); + +)sql"; + +class NarInfoDiskCacheImpl : public NarInfoDiskCache +{ +public: + + /* How long negative lookups are valid. */ + const int ttlNegative = 3600; + + struct State + { + SQLite db; + SQLiteStmt insertCache, queryCache, insertNAR, queryNAR, insertNARExistence, queryNARExistence; + std::map caches; + }; + + Sync _state; + + NarInfoDiskCacheImpl() + { + auto state(_state.lock()); + + Path dbPath = getCacheDir() + "/nix/binary-cache-v3.sqlite"; + createDirs(dirOf(dbPath)); + + if (sqlite3_open_v2(dbPath.c_str(), &state->db.db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0) != SQLITE_OK) + throw Error(format("cannot open store cache ‘%s’") % dbPath); + + if (sqlite3_busy_timeout(state->db, 60 * 60 * 1000) != SQLITE_OK) + throwSQLiteError(state->db, "setting timeout"); + + // We can always reproduce the cache. + if (sqlite3_exec(state->db, "pragma synchronous = off", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(state->db, "making database asynchronous"); + if (sqlite3_exec(state->db, "pragma main.journal_mode = truncate", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(state->db, "setting journal mode"); + + if (sqlite3_exec(state->db, schema, 0, 0, 0) != SQLITE_OK) + throwSQLiteError(state->db, "initialising database schema"); + + state->insertCache.create(state->db, + "insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)"); + + state->queryCache.create(state->db, + "select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ?"); + + state->insertNAR.create(state->db, + "insert or replace into NARs(cache, storePath, url, compression, fileHash, fileSize, narHash, " + "narSize, refs, deriver, sigs, timestamp) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + state->queryNAR.create(state->db, + "select * from NARs where cache = ? and storePath = ?"); + + state->insertNARExistence.create(state->db, + "insert or replace into NARExistence(cache, storePath, exist, timestamp) values (?, ?, ?, ?)"); + + state->queryNARExistence.create(state->db, + "select exist, timestamp from NARExistence where cache = ? and storePath = ?"); + } + + int uriToInt(State & state, const std::string & uri) + { + auto i = state.caches.find(uri); + if (i == state.caches.end()) abort(); + return i->second; + } + + void createCache(const std::string & uri) override + { + auto state(_state.lock()); + + // FIXME: race + + state->insertCache.use()(uri)(time(0))(settings.nixStore)(1)(0).exec(); + assert(sqlite3_changes(state->db) == 1); + state->caches[uri] = sqlite3_last_insert_rowid(state->db); + } + + bool cacheExists(const std::string & uri) override + { + auto state(_state.lock()); + + auto i = state->caches.find(uri); + if (i != state->caches.end()) return true; + + auto queryCache(state->queryCache.use()(uri)); + + if (queryCache.next()) { + state->caches[uri] = queryCache.getInt(0); + return true; + } + + return false; + } + + std::pair> lookupNarInfo( + const std::string & uri, const Path & storePath) override + { + auto state(_state.lock()); + + auto queryNAR(state->queryNAR.use() + (uriToInt(*state, uri)) + (baseNameOf(storePath))); + + if (!queryNAR.next()) + // FIXME: check NARExistence + return {oUnknown, 0}; + + auto narInfo = make_ref(); + + // FIXME: implement TTL. + + narInfo->path = storePath; + narInfo->url = queryNAR.getStr(2); + narInfo->compression = queryNAR.getStr(3); + if (!queryNAR.isNull(4)) + narInfo->fileHash = parseHash(queryNAR.getStr(4)); + narInfo->fileSize = queryNAR.getInt(5); + narInfo->narHash = parseHash(queryNAR.getStr(6)); + narInfo->narSize = queryNAR.getInt(7); + for (auto & r : tokenizeString(queryNAR.getStr(8), " ")) + narInfo->references.insert(settings.nixStore + "/" + r); + if (!queryNAR.isNull(9)) + narInfo->deriver = settings.nixStore + "/" + queryNAR.getStr(9); + for (auto & sig : tokenizeString(queryNAR.getStr(10), " ")) + narInfo->sigs.insert(sig); + + return {oValid, narInfo}; + } + + void upsertNarInfo( + const std::string & uri, std::shared_ptr info) override + { + auto state(_state.lock()); + + if (info) { + + auto narInfo = std::dynamic_pointer_cast(info); + + state->insertNAR.use() + (uriToInt(*state, uri)) + (baseNameOf(info->path)) + (narInfo ? narInfo->url : "", narInfo != 0) + (narInfo ? narInfo->compression : "", narInfo != 0) + (narInfo && narInfo->fileHash ? narInfo->fileHash.to_string() : "", narInfo && narInfo->fileHash) + (narInfo ? narInfo->fileSize : 0, narInfo != 0 && narInfo->fileSize) + (info->narHash.to_string()) + (info->narSize) + (concatStringsSep(" ", info->shortRefs())) + (info->deriver != "" ? baseNameOf(info->deriver) : "", info->deriver != "") + (concatStringsSep(" ", info->sigs)) + (time(0)).exec(); + + } else { + // not implemented + abort(); + } + } +}; + +ref getNarInfoDiskCache() +{ + static Sync> cache; + + auto cache_(cache.lock()); + if (!*cache_) *cache_ = std::make_shared(); + return ref(*cache_); +} + +} diff --git a/src/libstore/nar-info-disk-cache.hh b/src/libstore/nar-info-disk-cache.hh new file mode 100644 index 0000000000..ee1aafc63a --- /dev/null +++ b/src/libstore/nar-info-disk-cache.hh @@ -0,0 +1,28 @@ +#pragma once + +#include "ref.hh" +#include "nar-info.hh" + +namespace nix { + +class NarInfoDiskCache +{ +public: + typedef enum { oValid, oInvalid, oUnknown } Outcome; + + virtual void createCache(const std::string & uri) = 0; + + virtual bool cacheExists(const std::string & uri) = 0; + + virtual std::pair> lookupNarInfo( + const std::string & uri, const Path & storePath) = 0; + + virtual void upsertNarInfo( + const std::string & uri, std::shared_ptr narInfo) = 0; +}; + +/* Return a singleton cache object that can be used concurrently by + multiple threads. */ +ref getNarInfoDiskCache(); + +} diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc index 680facdcfe..c0c5cecd17 100644 --- a/src/libstore/nar-info.cc +++ b/src/libstore/nar-info.cc @@ -5,16 +5,16 @@ namespace nix { NarInfo::NarInfo(const std::string & s, const std::string & whence) { - auto corrupt = [&]() { + auto corrupt = [&]() [[noreturn]] { throw Error("NAR info file ‘%1%’ is corrupt"); }; auto parseHashField = [&](const string & s) { - string::size_type colon = s.find(':'); - if (colon == string::npos) corrupt(); - HashType ht = parseHashType(string(s, 0, colon)); - if (ht == htUnknown) corrupt(); - return parseHash16or32(ht, string(s, colon + 1)); + try { + return parseHash(s); + } catch (BadHash &) { + corrupt(); + } }; size_t pos = 0; @@ -103,12 +103,4 @@ std::string NarInfo::to_string() const return res; } -Strings NarInfo::shortRefs() const -{ - Strings refs; - for (auto & r : references) - refs.push_back(baseNameOf(r)); - return refs; -} - } diff --git a/src/libstore/nar-info.hh b/src/libstore/nar-info.hh index 3c783cf83f..6bc2f03b13 100644 --- a/src/libstore/nar-info.hh +++ b/src/libstore/nar-info.hh @@ -19,10 +19,6 @@ struct NarInfo : ValidPathInfo NarInfo(const std::string & s, const std::string & whence); std::string to_string() const; - -private: - - Strings shortRefs() const; }; } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 5519639766..430d0ecf11 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -489,7 +489,6 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results) { auto state_(Store::state.lock()); state_->pathInfoCache.clear(); - stats.pathInfoCacheSize = 0; } } diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index f93fa08575..816f9984d6 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -139,6 +139,11 @@ int64_t SQLiteStmt::Use::getInt(int col) return sqlite3_column_int64(stmt, col); } +bool SQLiteStmt::Use::isNull(int col) +{ + return sqlite3_column_type(stmt, col) == SQLITE_NULL; +} + SQLiteTxn::SQLiteTxn(sqlite3 * db) { this->db = db; diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 326e4a4855..d6b4a8d911 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -58,6 +58,7 @@ struct SQLiteStmt std::string getStr(int col); int64_t getInt(int col); + bool isNull(int col); }; Use use() diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 6543ed1f6d..cac137a99d 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -2,6 +2,7 @@ #include "globals.hh" #include "store-api.hh" #include "util.hh" +#include "nar-info-disk-cache.hh" namespace nix { @@ -225,6 +226,12 @@ Path computeStorePathForText(const string & name, const string & s, } +std::string Store::getUri() +{ + return ""; +} + + bool Store::isValidPath(const Path & storePath) { { @@ -236,7 +243,19 @@ bool Store::isValidPath(const Path & storePath) } } + if (diskCache) { + auto res = diskCache->lookupNarInfo(getUri(), storePath); + if (res.first != NarInfoDiskCache::oUnknown) { + auto state_(state.lock()); + state_->pathInfoCache.upsert(storePath, + res.first == NarInfoDiskCache::oInvalid ? 0 : res.second); + return res.first == NarInfoDiskCache::oValid; + } + } + return isValidPathUncached(storePath); + + // FIXME: insert result into NARExistence table of diskCache. } @@ -253,12 +272,26 @@ ref Store::queryPathInfo(const Path & storePath) } } + if (diskCache) { + auto res = diskCache->lookupNarInfo(getUri(), storePath); + if (res.first != NarInfoDiskCache::oUnknown) { + auto state_(state.lock()); + state_->pathInfoCache.upsert(storePath, + res.first == NarInfoDiskCache::oInvalid ? 0 : res.second); + if (res.first == NarInfoDiskCache::oInvalid) + throw InvalidPath(format("path ‘%s’ is not valid") % storePath); + return ref(res.second); + } + } + auto info = queryPathInfoUncached(storePath); + if (diskCache && info) + diskCache->upsertNarInfo(getUri(), info); + { auto state_(state.lock()); state_->pathInfoCache.upsert(storePath, info); - stats.pathInfoCacheSize = state_->pathInfoCache.size(); } if (!info) { @@ -303,6 +336,10 @@ string Store::makeValidityRegistration(const PathSet & paths, const Store::Stats & Store::getStats() { + { + auto state_(state.lock()); + stats.pathInfoCacheSize = state_->pathInfoCache.size(); + } return stats; } @@ -356,7 +393,7 @@ void Store::exportPaths(const Paths & paths, std::string ValidPathInfo::fingerprint() const { - if (narSize == 0 || narHash.type == htUnknown) + if (narSize == 0 || !narHash) throw Error(format("cannot calculate fingerprint of path ‘%s’ because its size/hash is not known") % path); return @@ -389,6 +426,15 @@ bool ValidPathInfo::checkSignature(const PublicKeys & publicKeys, const std::str } +Strings ValidPathInfo::shortRefs() const +{ + Strings refs; + for (auto & r : references) + refs.push_back(baseNameOf(r)); + return refs; +} + + } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index d45e401c39..eab6da91ee 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -134,6 +134,8 @@ struct ValidPathInfo /* Verify a single signature. */ bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const; + Strings shortRefs() const; + virtual ~ValidPathInfo() { } }; @@ -170,6 +172,7 @@ struct BuildResult struct BasicDerivation; struct Derivation; class FSAccessor; +class NarInfoDiskCache; class Store : public std::enable_shared_from_this @@ -183,10 +186,14 @@ protected: Sync state; + std::shared_ptr diskCache; + public: virtual ~Store() { } + virtual std::string getUri(); + /* Check whether a path is valid. */ bool isValidPath(const Path & path); diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 6473930030..c17f1c4d51 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -33,7 +33,7 @@ Hash::Hash(HashType type) else if (type == htSHA1) hashSize = sha1HashSize; else if (type == htSHA256) hashSize = sha256HashSize; else if (type == htSHA512) hashSize = sha512HashSize; - else throw Error("unknown hash type"); + else abort(); assert(hashSize <= maxHashSize); memset(hash, 0, maxHashSize); } @@ -64,6 +64,12 @@ bool Hash::operator < (const Hash & h) const } +std::string Hash::to_string(bool base32) const +{ + return printHashType(type) + ":" + (base32 ? printHash32(*this) : printHash(*this)); +} + + const string base16Chars = "0123456789abcdef"; @@ -78,15 +84,28 @@ string printHash(const Hash & hash) } +Hash parseHash(const string & s) +{ + string::size_type colon = s.find(':'); + if (colon == string::npos) + throw BadHash(format("invalid hash ‘%s’") % s); + string hts = string(s, 0, colon); + HashType ht = parseHashType(hts); + if (ht == htUnknown) + throw BadHash(format("unknown hash type ‘%s’") % hts); + return parseHash16or32(ht, string(s, colon + 1)); +} + + Hash parseHash(HashType ht, const string & s) { Hash hash(ht); if (s.length() != hash.hashSize * 2) - throw Error(format("invalid hash ‘%1%’") % s); + throw BadHash(format("invalid hash ‘%1%’") % s); for (unsigned int i = 0; i < hash.hashSize; i++) { string s2(s, i * 2, 2); if (!isxdigit(s2[0]) || !isxdigit(s2[1])) - throw Error(format("invalid hash ‘%1%’") % s); + throw BadHash(format("invalid hash ‘%1%’") % s); std::istringstream str(s2); int n; str >> std::hex >> n; @@ -103,6 +122,7 @@ const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz"; string printHash32(const Hash & hash) { size_t len = hash.base32Len(); + assert(len); string s; s.reserve(len); @@ -139,7 +159,7 @@ Hash parseHash32(HashType ht, const string & s) for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */ if (base32Chars[digit] == c) break; if (digit >= 32) - throw Error(format("invalid base-32 hash ‘%1%’") % s); + throw BadHash(format("invalid base-32 hash ‘%1%’") % s); unsigned int b = n * 5; unsigned int i = b / 8; unsigned int j = b % 8; @@ -161,7 +181,7 @@ Hash parseHash16or32(HashType ht, const string & s) /* base-32 representation */ hash = parseHash32(ht, s); else - throw Error(format("hash ‘%1%’ has wrong length for hash type ‘%2%’") + throw BadHash(format("hash ‘%1%’ has wrong length for hash type ‘%2%’") % s % printHashType(ht)); return hash; } @@ -322,7 +342,7 @@ string printHashType(HashType ht) else if (ht == htSHA1) return "sha1"; else if (ht == htSHA256) return "sha256"; else if (ht == htSHA512) return "sha512"; - else throw Error("cannot print unknown hash type"); + else abort(); } diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index bac2ebf2dc..02e213fc7b 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -7,6 +7,9 @@ namespace nix { +MakeError(BadHash, Error); + + enum HashType : char { htUnknown, htMD5, htSHA1, htSHA256, htSHA512 }; @@ -26,12 +29,15 @@ struct Hash HashType type; - /* Create an unusable hash object. */ + /* Create an unset hash object. */ Hash(); /* Create a zero-filled hash object. */ Hash(HashType type); + /* Check whether a hash is set. */ + operator bool () const { return type != htUnknown; } + /* Check whether two hash are equal. */ bool operator == (const Hash & h2) const; @@ -52,12 +58,16 @@ struct Hash { return (hashSize * 8 - 1) / 5 + 1; } + + std::string to_string(bool base32 = true) const; }; /* Convert a hash to a hexadecimal representation. */ string printHash(const Hash & hash); +Hash parseHash(const string & s); + /* Parse a hexadecimal representation of a hash code. */ Hash parseHash(HashType ht, const string & s); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 55d4909921..8ffa6973dd 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -403,6 +403,18 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, } +Path getCacheDir() +{ + Path cacheDir = getEnv("XDG_CACHE_HOME"); + if (cacheDir.empty()) { + Path homeDir = getEnv("HOME"); + if (homeDir.empty()) throw Error("$XDG_CACHE_HOME and $HOME are not set"); + cacheDir = homeDir + "/.cache"; + } + return cacheDir; +} + + Paths createDirs(const Path & path) { Paths created; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 20bd62a0e7..dabfafa7fb 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -102,6 +102,9 @@ void deletePath(const Path & path, unsigned long long & bytesFreed); Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755); +/* Return the path to $XDG_CACHE_HOME/.cache. */ +Path getCacheDir(); + /* Create a directory and all its parents, if necessary. Returns the list of created directories, in order of creation. */ Paths createDirs(const Path & path); -- cgit 1.4.1