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