|
1 | 1 | #include "compile_cache.h" |
| 2 | +#include <string> |
2 | 3 | #include "debug_utils-inl.h" |
3 | 4 | #include "env-inl.h" |
4 | 5 | #include "node_file.h" |
@@ -27,15 +28,19 @@ uint32_t GetHash(const char* data, size_t size) { |
27 | 28 | return crc32(crc, reinterpret_cast<const Bytef*>(data), size); |
28 | 29 | } |
29 | 30 |
|
30 | | -uint32_t GetCacheVersionTag() { |
31 | | - std::string_view node_version(NODE_VERSION); |
32 | | - uint32_t v8_tag = v8::ScriptCompiler::CachedDataVersionTag(); |
33 | | - uLong crc = crc32(0L, Z_NULL, 0); |
34 | | - crc = crc32(crc, reinterpret_cast<const Bytef*>(&v8_tag), sizeof(uint32_t)); |
35 | | - crc = crc32(crc, |
36 | | - reinterpret_cast<const Bytef*>(node_version.data()), |
37 | | - node_version.size()); |
38 | | - return crc; |
| 31 | +std::string GetCacheVersionTag() { |
| 32 | + // On platforms where uids are available, use different folders for |
| 33 | + // different users to avoid cache miss due to permission incompatibility. |
| 34 | + // On platforms where uids are not available, bare with the cache miss. |
| 35 | + // This should be fine on Windows, as there local directories tend to be |
| 36 | + // user-specific. |
| 37 | + std::string tag = std::string(NODE_VERSION) + '-' + std::string(NODE_ARCH) + |
| 38 | + '-' + |
| 39 | + Uint32ToHex(v8::ScriptCompiler::CachedDataVersionTag()); |
| 40 | +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS |
| 41 | + tag += '-' + std::to_string(getuid()); |
| 42 | +#endif |
| 43 | + return tag; |
39 | 44 | } |
40 | 45 |
|
41 | 46 | uint32_t GetCacheKey(std::string_view filename, CachedCodeType type) { |
@@ -63,6 +68,10 @@ v8::ScriptCompiler::CachedData* CompileCacheEntry::CopyCache() const { |
63 | 68 | data, cache_size, v8::ScriptCompiler::CachedData::BufferOwned); |
64 | 69 | } |
65 | 70 |
|
| 71 | +// Used for identifying and verifying a file is a compile cache file. |
| 72 | +// See comments in CompileCacheHandler::Persist(). |
| 73 | +constexpr uint32_t kCacheMagicNumber = 0x8adfdbb2; |
| 74 | + |
66 | 75 | void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) { |
67 | 76 | Debug("[compile cache] reading cache from %s for %s %s...", |
68 | 77 | entry->cache_filename, |
@@ -100,12 +109,20 @@ void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) { |
100 | 109 | return; |
101 | 110 | } |
102 | 111 |
|
103 | | - Debug("[%d %d %d %d]...", |
| 112 | + Debug("[%d %d %d %d %d]...", |
| 113 | + headers[kMagicNumberOffset], |
104 | 114 | headers[kCodeSizeOffset], |
105 | 115 | headers[kCacheSizeOffset], |
106 | 116 | headers[kCodeHashOffset], |
107 | 117 | headers[kCacheHashOffset]); |
108 | 118 |
|
| 119 | + if (headers[kMagicNumberOffset] != kCacheMagicNumber) { |
| 120 | + Debug("magic number mismatch: expected %d, actual %d\n", |
| 121 | + kCacheMagicNumber, |
| 122 | + headers[kMagicNumberOffset]); |
| 123 | + return; |
| 124 | + } |
| 125 | + |
109 | 126 | // Check the code size and hash which are already computed. |
110 | 127 | if (headers[kCodeSizeOffset] != entry->code_size) { |
111 | 128 | Debug("code size mismatch: expected %d, actual %d\n", |
@@ -202,11 +219,14 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert( |
202 | 219 | compiler_cache_store_.emplace(key, std::make_unique<CompileCacheEntry>()); |
203 | 220 | auto* result = emplaced.first->second.get(); |
204 | 221 |
|
| 222 | + std::u8string cache_filename_u8 = |
| 223 | + (compile_cache_dir_ / Uint32ToHex(key)).u8string(); |
205 | 224 | result->code_hash = code_hash; |
206 | 225 | result->code_size = code_utf8.length(); |
207 | 226 | result->cache_key = key; |
208 | 227 | result->cache_filename = |
209 | | - (compile_cache_dir_ / Uint32ToHex(result->cache_key)).string(); |
| 228 | + std::string(cache_filename_u8.begin(), cache_filename_u8.end()) + |
| 229 | + ".cache"; |
210 | 230 | result->source_filename = filename_utf8.ToString(); |
211 | 231 | result->cache = nullptr; |
212 | 232 | result->type = type; |
@@ -264,6 +284,7 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry, |
264 | 284 | } |
265 | 285 |
|
266 | 286 | // Layout of a cache file: |
| 287 | +// [uint32_t] magic number |
267 | 288 | // [uint32_t] code size |
268 | 289 | // [uint32_t] code hash |
269 | 290 | // [uint32_t] cache size |
@@ -301,14 +322,16 @@ void CompileCacheHandler::Persist() { |
301 | 322 |
|
302 | 323 | // Generating headers. |
303 | 324 | std::vector<uint32_t> headers(kHeaderCount); |
| 325 | + headers[kMagicNumberOffset] = kCacheMagicNumber; |
304 | 326 | headers[kCodeSizeOffset] = entry->code_size; |
305 | 327 | headers[kCacheSizeOffset] = cache_size; |
306 | 328 | headers[kCodeHashOffset] = entry->code_hash; |
307 | 329 | headers[kCacheHashOffset] = cache_hash; |
308 | 330 |
|
309 | | - Debug("[compile cache] writing cache for %s in %s [%d %d %d %d]...", |
| 331 | + Debug("[compile cache] writing cache for %s in %s [%d %d %d %d %d]...", |
310 | 332 | entry->source_filename, |
311 | 333 | entry->cache_filename, |
| 334 | + headers[kMagicNumberOffset], |
312 | 335 | headers[kCodeSizeOffset], |
313 | 336 | headers[kCacheSizeOffset], |
314 | 337 | headers[kCodeHashOffset], |
@@ -335,53 +358,63 @@ CompileCacheHandler::CompileCacheHandler(Environment* env) |
335 | 358 |
|
336 | 359 | // Directory structure: |
337 | 360 | // - Compile cache directory (from NODE_COMPILE_CACHE) |
338 | | -// - <cache_version_tag_1>: hash of CachedDataVersionTag + NODE_VERESION |
339 | | -// - <cache_version_tag_2> |
340 | | -// - <cache_version_tag_3> |
341 | | -// - <cache_file_1>: a hash of filename + module type |
342 | | -// - <cache_file_2> |
343 | | -// - <cache_file_3> |
344 | | -bool CompileCacheHandler::InitializeDirectory(Environment* env, |
345 | | - const std::string& dir) { |
346 | | - compiler_cache_key_ = GetCacheVersionTag(); |
347 | | - std::string compiler_cache_key_string = Uint32ToHex(compiler_cache_key_); |
348 | | - std::vector<std::string_view> paths = {dir, compiler_cache_key_string}; |
349 | | - std::string cache_dir = PathResolve(env, paths); |
350 | | - |
| 361 | +// - $NODE_VERION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID |
| 362 | +// - $FILENAME_AND_MODULE_TYPE_HASH.cache: a hash of filename + module type |
| 363 | +CompileCacheEnableResult CompileCacheHandler::Enable(Environment* env, |
| 364 | + const std::string& dir) { |
| 365 | + std::string cache_tag = GetCacheVersionTag(); |
| 366 | + std::string absolute_cache_dir_base = PathResolve(env, {dir}); |
| 367 | + std::filesystem::path cache_dir_with_tag = |
| 368 | + std::filesystem::path(absolute_cache_dir_base) / cache_tag; |
| 369 | + std::u8string cache_dir_with_tag_u8 = cache_dir_with_tag.u8string(); |
| 370 | + std::string cache_dir_with_tag_str(cache_dir_with_tag_u8.begin(), |
| 371 | + cache_dir_with_tag_u8.end()); |
| 372 | + CompileCacheEnableResult result; |
351 | 373 | Debug("[compile cache] resolved path %s + %s -> %s\n", |
352 | 374 | dir, |
353 | | - compiler_cache_key_string, |
354 | | - cache_dir); |
| 375 | + cache_tag, |
| 376 | + cache_dir_with_tag_str); |
355 | 377 |
|
356 | 378 | if (UNLIKELY(!env->permission()->is_granted( |
357 | | - env, permission::PermissionScope::kFileSystemWrite, cache_dir))) { |
358 | | - Debug("[compile cache] skipping cache because write permission for %s " |
359 | | - "is not granted\n", |
360 | | - cache_dir); |
361 | | - return false; |
| 379 | + env, |
| 380 | + permission::PermissionScope::kFileSystemWrite, |
| 381 | + cache_dir_with_tag_str))) { |
| 382 | + result.message = "Skipping compile cache because write permission for " + |
| 383 | + cache_dir_with_tag_str + " is not granted"; |
| 384 | + result.status = CompileCacheEnableStatus::kFailed; |
| 385 | + return result; |
362 | 386 | } |
363 | 387 |
|
364 | 388 | if (UNLIKELY(!env->permission()->is_granted( |
365 | | - env, permission::PermissionScope::kFileSystemRead, cache_dir))) { |
366 | | - Debug("[compile cache] skipping cache because read permission for %s " |
367 | | - "is not granted\n", |
368 | | - cache_dir); |
369 | | - return false; |
| 389 | + env, |
| 390 | + permission::PermissionScope::kFileSystemRead, |
| 391 | + cache_dir_with_tag_str))) { |
| 392 | + result.message = "Skipping compile cache because read permission for " + |
| 393 | + cache_dir_with_tag_str + " is not granted"; |
| 394 | + result.status = CompileCacheEnableStatus::kFailed; |
| 395 | + return result; |
370 | 396 | } |
371 | 397 |
|
372 | 398 | fs::FSReqWrapSync req_wrap; |
373 | | - int err = fs::MKDirpSync(nullptr, &(req_wrap.req), cache_dir, 0777, nullptr); |
| 399 | + int err = fs::MKDirpSync( |
| 400 | + nullptr, &(req_wrap.req), cache_dir_with_tag_str, 0777, nullptr); |
374 | 401 | if (is_debug_) { |
375 | 402 | Debug("[compile cache] creating cache directory %s...%s\n", |
376 | | - cache_dir, |
| 403 | + cache_dir_with_tag_str, |
377 | 404 | err < 0 ? uv_strerror(err) : "success"); |
378 | 405 | } |
379 | 406 | if (err != 0 && err != UV_EEXIST) { |
380 | | - return false; |
| 407 | + result.message = |
| 408 | + "Cannot create cache directory: " + std::string(uv_strerror(err)); |
| 409 | + result.status = CompileCacheEnableStatus::kFailed; |
| 410 | + return result; |
381 | 411 | } |
382 | 412 |
|
383 | | - compile_cache_dir_ = std::filesystem::path(cache_dir); |
384 | | - return true; |
| 413 | + compile_cache_dir_str_ = absolute_cache_dir_base; |
| 414 | + result.cache_directory = absolute_cache_dir_base; |
| 415 | + compile_cache_dir_ = cache_dir_with_tag; |
| 416 | + result.status = CompileCacheEnableStatus::kEnabled; |
| 417 | + return result; |
385 | 418 | } |
386 | 419 |
|
387 | 420 | } // namespace node |