1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
/* Copyright 2017-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
#include "ContentHash.h"
#include "ThreadPool.h"
#include "watchman_hash.h"
#include "watchman_stream.h"
#ifdef __APPLE__
#define COMMON_DIGEST_FOR_OPENSSL
#include "CommonCrypto/CommonDigest.h"
#elif defined(_WIN32)
#include <Wincrypt.h>
#else
#include <openssl/sha.h>
#endif
#include <string>
#include "Logging.h"
#include "FileSystem.h"
#include "watchman_scopeguard.h"
namespace watchman {
using HashValue = typename ContentHashCache::HashValue;
using Node = typename ContentHashCache::Node;
bool ContentHashCacheKey::operator==(const ContentHashCacheKey& other) const {
return fileSize == other.fileSize && mtime.tv_sec == other.mtime.tv_sec &&
mtime.tv_nsec == other.mtime.tv_nsec &&
relativePath == other.relativePath;
}
std::size_t ContentHashCacheKey::hashValue() const {
return hash_128_to_64(
w_string_hval(relativePath),
hash_128_to_64(fileSize, hash_128_to_64(mtime.tv_sec, mtime.tv_nsec)));
}
ContentHashCache::ContentHashCache(
const w_string& rootPath,
size_t maxItems,
std::chrono::milliseconds errorTTL)
: cache_(maxItems, errorTTL), rootPath_(rootPath) {}
Future<std::shared_ptr<const Node>> ContentHashCache::get(
const ContentHashCacheKey& key) {
return cache_.get(
key, [this](const ContentHashCacheKey& k) { return computeHash(k); });
}
HashValue ContentHashCache::computeHashImmediate(
const ContentHashCacheKey& key) const {
HashValue result;
uint8_t buf[8192];
auto fullPath = w_string::pathCat({rootPath_, key.relativePath});
auto stm = w_stm_open(fullPath.c_str(), O_RDONLY);
if (!stm) {
throw std::system_error(
errno,
std::generic_category(),
to<std::string>("w_stm_open ", fullPath));
}
#ifndef _WIN32
SHA_CTX ctx;
SHA1_Init(&ctx);
while (true) {
auto n = stm->read(buf, sizeof(buf));
if (n == 0) {
break;
}
if (n < 0) {
throw std::system_error(
errno,
std::generic_category(),
to<std::string>("while reading from ", fullPath));
}
SHA1_Update(&ctx, buf, n);
}
SHA1_Final(result.data(), &ctx);
#else
// Use the built-in crypt provider API on windows to avoid introducing a
// dependency on openssl in the windows build.
HCRYPTPROV provider{0};
HCRYPTHASH ctx{0};
if (!CryptAcquireContext(
&provider,
nullptr,
nullptr,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
throw std::system_error(
GetLastError(), std::system_category(), "CryptAcquireContext");
}
SCOPE_EXIT {
CryptReleaseContext(provider, 0);
};
if (!CryptCreateHash(provider, CALG_SHA1, 0, 0, &ctx)) {
throw std::system_error(
GetLastError(), std::system_category(), "CryptCreateHash");
}
SCOPE_EXIT {
CryptDestroyHash(ctx);
};
while (true) {
auto n = stm->read(buf, sizeof(buf));
if (n == 0) {
break;
}
if (n < 0) {
throw std::system_error(
errno,
std::generic_category(),
to<std::string>("while reading from ", fullPath));
}
if (!CryptHashData(ctx, buf, n, 0)) {
throw std::system_error(
GetLastError(), std::system_category(), "CryptHashData");
}
}
DWORD size = result.size();
if (!CryptGetHashParam(ctx, HP_HASHVAL, result.data(), &size, 0)) {
throw std::system_error(
GetLastError(), std::system_category(), "CryptGetHashParam HP_HASHVAL");
}
#endif
// Since TOCTOU is everywhere and everything, double check to make sure that
// the file looks like we were expecting at the start. If it isn't, then
// we want to throw an exception and avoid associating the hash of whatever
// state we just read with this cache key.
auto stat = getFileInformation(fullPath.c_str());
if (size_t(stat.size) != key.fileSize ||
stat.mtime.tv_sec != key.mtime.tv_sec ||
stat.mtime.tv_nsec != key.mtime.tv_nsec) {
throw std::runtime_error(
"metadata changed during hashing; query again to get latest status");
}
return result;
}
Future<HashValue> ContentHashCache::computeHash(
const ContentHashCacheKey& key) const {
return makeFuture(key)
.via(&getThreadPool())
.then([this](Result<ContentHashCacheKey>&& key) {
return computeHashImmediate(key.value());
});
}
const w_string& ContentHashCache::rootPath() const {
return rootPath_;
}
CacheStats ContentHashCache::stats() const {
return cache_.stats();
}
}
|