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