channel-scripts/file-cache.hh
Samuel Dionne-Riel 0effeee537 file-cache: use the right exception type for parse error
```
error: [json.exception.parse_error.101] parse error at 195: syntax error - invalid string: ill-formed UTF-8 byte; last read: '"bokml'; expected string literal
```

This is most probably caused by the upgrade in nixos-org-configurations/1b74e019b166d37a1510a99aeea86a0fa656854a
that, in turn, caused nlohmann_json to go from 2.1.0 -> 3.2.0

I haven't found concrete evidence in the changelog as to a change
changing the exception type. Though, the timing for failures matches
with this update.

Furthermore, once this fix is applied, launching the update locally
works fine.
2019-01-30 20:56:40 -05:00

182 lines
6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* A local disk cache for fast lookups of NAR index files in a binary
cache. */
#include "binary-cache-store.hh"
#include "fs-accessor.hh"
#include "sqlite.hh"
#include "sync.hh"
#include <sqlite3.h>
#include <nlohmann/json.hpp>
using namespace nix;
MakeError(BadJSON, Error);
class FileCache
{
struct State
{
SQLite db;
SQLiteStmt queryPath, insertPath, queryFiles, insertFile;
};
Sync<State> state_;
struct Stat : FSAccessor::Stat
{
std::string target;
};
public:
FileCache(const Path & path)
{
auto state(state_.lock());
static std::string cacheSchema = R"sql(
create table if not exists StorePaths (
id integer primary key autoincrement not null,
path text unique not null
);
create table if not exists StorePathContents (
storePath integer not null,
subPath text not null,
type integer not null,
fileSize integer,
isExecutable integer,
target text,
primary key (storePath, subPath),
foreign key (storePath) references StorePaths(id) on delete cascade
);
)sql";
state->db = SQLite(path);
state->db.exec("pragma foreign_keys = 1");
state->db.exec(cacheSchema);
if (sqlite3_busy_timeout(state->db, 60 * 60 * 1000) != SQLITE_OK)
throwSQLiteError(state->db, "setting timeout");
state->queryPath.create(state->db,
"select id from StorePaths where path = ?");
state->insertPath.create(state->db,
"insert or ignore into StorePaths(path) values (?)");
state->queryFiles.create(state->db,
"select subPath, type, fileSize, isExecutable, target from StorePathContents where storePath = ?");
state->insertFile.create(state->db,
"insert into StorePathContents(storePath, subPath, type, fileSize, isExecutable, target) values (?, ?, ?, ?, ?, ?)");
}
/* Return the files in a store path, using a SQLite database to
cache the results. */
std::map<std::string, Stat>
getFiles(ref<BinaryCacheStore> binaryCache, const Path & storePath)
{
std::map<std::string, Stat> files;
/* Look up the path in the SQLite cache. */
{
auto state(state_.lock());
auto useQueryPath(state->queryPath.use()(storePath));
if (useQueryPath.next()) {
auto id = useQueryPath.getInt(0);
auto useQueryFiles(state->queryFiles.use()(id));
while (useQueryFiles.next()) {
Stat st;
st.type = (FSAccessor::Type) useQueryFiles.getInt(1);
st.fileSize = (uint64_t) useQueryFiles.getInt(2);
st.isExecutable = useQueryFiles.getInt(3) != 0;
if (!useQueryFiles.isNull(4))
st.target = useQueryFiles.getStr(4);
files.emplace(useQueryFiles.getStr(0), st);
}
return files;
}
}
using json = nlohmann::json;
std::function<void(const std::string &, json &)> recurse;
recurse = [&](const std::string & relPath, json & v) {
Stat st;
std::string type = v["type"];
if (type == "directory") {
st.type = FSAccessor::Type::tDirectory;
for (auto i = v["entries"].begin(); i != v["entries"].end(); ++i) {
std::string name = i.key();
recurse(relPath.empty() ? name : relPath + "/" + name, i.value());
}
} else if (type == "regular") {
st.type = FSAccessor::Type::tRegular;
st.fileSize = v["size"];
st.isExecutable = v.value("executable", false);
} else if (type == "symlink") {
st.type = FSAccessor::Type::tSymlink;
st.target = v.value("target", "");
} else return;
files[relPath] = st;
};
/* It's not in the cache, so get the .ls.xz file (which
contains a JSON serialisation of the listing of the NAR
contents) from the binary cache. */
auto now1 = std::chrono::steady_clock::now();
auto s = binaryCache->getFile(storePathToHash(storePath) + ".ls");
if (!s)
printInfo("warning: no listing of %s in binary cache", storePath);
else {
try {
json ls = json::parse(*s);
if (ls.value("version", 0) != 1)
throw Error("NAR index for %s has an unsupported version", storePath);
recurse("", ls.at("root"));
} catch (json::parse_error & e) {
// FIXME: some filenames have non-UTF8 characters in them,
// which is not supported by nlohmann::json. So we have to
// skip the entire package.
throw BadJSON(e.what());
}
}
/* Insert the store path into the database. */
{
auto state(state_.lock());
SQLiteTxn txn(state->db);
if (state->queryPath.use()(storePath).next()) return files;
state->insertPath.use()(storePath).exec();
uint64_t id = sqlite3_last_insert_rowid(state->db);
for (auto & x : files) {
state->insertFile.use()
(id)
(x.first)
(x.second.type)
(x.second.fileSize, x.second.type == FSAccessor::Type::tRegular)
(x.second.isExecutable, x.second.type == FSAccessor::Type::tRegular)
(x.second.target, x.second.type == FSAccessor::Type::tSymlink)
.exec();
}
txn.commit();
}
auto now2 = std::chrono::steady_clock::now();
printInfo("processed %s in %d ms", storePath,
std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1).count());
return files;
}
};