|
17 | 17 | * License-Filename: LICENSE |
18 | 18 | */ |
19 | 19 |
|
| 20 | +#include <dirent.h> |
20 | 21 | #include <chrono> |
| 22 | +#include <fstream> |
21 | 23 | #include <thread> |
22 | 24 |
|
23 | 25 | #include <gtest/gtest.h> |
|
31 | 33 | #define WIN32_LEAN_AND_MEAN |
32 | 34 | #include <Windows.h> |
33 | 35 | #undef max |
| 36 | +#else |
| 37 | +#include <sys/stat.h> |
| 38 | +#include <sys/types.h> |
34 | 39 | #endif |
35 | 40 |
|
36 | 41 | #include "Helpers.h" |
@@ -1399,21 +1404,23 @@ class DefaultCacheImplOpenTest |
1399 | 1404 | public testing::WithParamInterface<OpenTestParameters> {}; |
1400 | 1405 |
|
1401 | 1406 | TEST_P(DefaultCacheImplOpenTest, ReadOnlyDir) { |
1402 | | - const auto setup_dir = [&](const olp::porting::optional<std::string>& cache_path) { |
1403 | | - if (cache_path) { |
1404 | | - if (olp::utils::Dir::Exists(*cache_path)) { |
1405 | | - ASSERT_TRUE(olp::utils::Dir::Remove(*cache_path)); |
1406 | | - } |
1407 | | - ASSERT_TRUE(olp::utils::Dir::Create(*cache_path)); |
1408 | | - ASSERT_TRUE(SetRights(*cache_path, true)); |
1409 | | - } |
1410 | | - }; |
1411 | | - |
1412 | | - const auto reset_dir = [&](const olp::porting::optional<std::string>& cache_path) { |
1413 | | - if (cache_path) { |
1414 | | - ASSERT_TRUE(olp::utils::Dir::Remove(*cache_path)); |
1415 | | - } |
1416 | | - }; |
| 1407 | + const auto setup_dir = |
| 1408 | + [&](const olp::porting::optional<std::string>& cache_path) { |
| 1409 | + if (cache_path) { |
| 1410 | + if (olp::utils::Dir::Exists(*cache_path)) { |
| 1411 | + ASSERT_TRUE(olp::utils::Dir::Remove(*cache_path)); |
| 1412 | + } |
| 1413 | + ASSERT_TRUE(olp::utils::Dir::Create(*cache_path)); |
| 1414 | + ASSERT_TRUE(SetRights(*cache_path, true)); |
| 1415 | + } |
| 1416 | + }; |
| 1417 | + |
| 1418 | + const auto reset_dir = |
| 1419 | + [&](const olp::porting::optional<std::string>& cache_path) { |
| 1420 | + if (cache_path) { |
| 1421 | + ASSERT_TRUE(olp::utils::Dir::Remove(*cache_path)); |
| 1422 | + } |
| 1423 | + }; |
1417 | 1424 |
|
1418 | 1425 | const OpenTestParameters test_params = GetParam(); |
1419 | 1426 |
|
@@ -1447,4 +1454,95 @@ std::vector<OpenTestParameters> DefaultCacheImplOpenParams() { |
1447 | 1454 | INSTANTIATE_TEST_SUITE_P(, DefaultCacheImplOpenTest, |
1448 | 1455 | testing::ValuesIn(DefaultCacheImplOpenParams())); |
1449 | 1456 |
|
| 1457 | +TEST_F(DefaultCacheImplTest, ProtectedCacheIOErrorFallbackToReadOnly) { |
| 1458 | + SCOPED_TRACE("IOError fallback to read-only for protected cache"); |
| 1459 | + |
| 1460 | + const std::string ioerror_path = |
| 1461 | + olp::utils::Dir::TempDirectory() + "/unittest_ioerror_fallback"; |
| 1462 | + |
| 1463 | + if (olp::utils::Dir::Exists(ioerror_path)) { |
| 1464 | + helpers::MakeDirectoryAndContentReadonly(ioerror_path, false); |
| 1465 | + ASSERT_TRUE(olp::utils::Dir::Remove(ioerror_path)); |
| 1466 | + } |
| 1467 | + |
| 1468 | + ASSERT_TRUE(olp::utils::Dir::Create(ioerror_path)); |
| 1469 | + |
| 1470 | + { |
| 1471 | + cache::CacheSettings temp_settings; |
| 1472 | + temp_settings.disk_path_protected = ioerror_path; |
| 1473 | + DefaultCacheImplHelper temp_cache(temp_settings); |
| 1474 | + ASSERT_EQ(temp_cache.Open(), |
| 1475 | + cache::DefaultCache::StorageOpenResult::Success); |
| 1476 | + temp_cache.Close(); |
| 1477 | + } |
| 1478 | + |
| 1479 | + // Make all the database files read-only, but keep directory writable. |
| 1480 | + // This way Dir::IsReadOnly(directory) returns false (directory is writable), |
| 1481 | + // but LevelDB gets IOError when trying to write to the read-only DB files. |
| 1482 | +#ifndef _WIN32 |
| 1483 | + DIR* dir = opendir(ioerror_path.c_str()); |
| 1484 | + ASSERT_TRUE(dir != nullptr); |
| 1485 | + struct dirent* entry; |
| 1486 | + while ((entry = readdir(dir)) != nullptr) { |
| 1487 | + if (entry->d_type == DT_REG) { |
| 1488 | + std::string file_path = ioerror_path + "/" + entry->d_name; |
| 1489 | + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) |
| 1490 | + continue; |
| 1491 | + ASSERT_EQ(chmod(file_path.c_str(), S_IRUSR | S_IRGRP | S_IROTH), 0); |
| 1492 | + } |
| 1493 | + } |
| 1494 | + closedir(dir); |
| 1495 | + |
| 1496 | + chmod(ioerror_path.c_str(), |
| 1497 | + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); |
| 1498 | +#else |
| 1499 | + WIN32_FIND_DATAA find_data; |
| 1500 | + HANDLE hFind = FindFirstFileA((ioerror_path + "\\*").c_str(), &find_data); |
| 1501 | + ASSERT_NE(hFind, INVALID_HANDLE_VALUE); |
| 1502 | + |
| 1503 | + do { |
| 1504 | + if (strcmp(find_data.cFileName, ".") == 0 || |
| 1505 | + strcmp(find_data.cFileName, "..") == 0) { |
| 1506 | + continue; |
| 1507 | + } |
| 1508 | + if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { |
| 1509 | + // It's a file, make it read-only |
| 1510 | + std::string file_path = ioerror_path + "\\" + find_data.cFileName; |
| 1511 | + DWORD attrs = GetFileAttributesA(file_path.c_str()); |
| 1512 | + ASSERT_NE(attrs, INVALID_FILE_ATTRIBUTES); |
| 1513 | + ASSERT_TRUE(SetFileAttributesA(file_path.c_str(), |
| 1514 | + attrs | FILE_ATTRIBUTE_READONLY)); |
| 1515 | + } |
| 1516 | + } while (FindNextFileA(hFind, &find_data)); |
| 1517 | + FindClose(hFind); |
| 1518 | + |
| 1519 | + // Ensure directory itself is not read-only (keep it writable) |
| 1520 | + DWORD dir_attrs = GetFileAttributesA(ioerror_path.c_str()); |
| 1521 | + ASSERT_NE(dir_attrs, INVALID_FILE_ATTRIBUTES); |
| 1522 | + if (dir_attrs & FILE_ATTRIBUTE_READONLY) { |
| 1523 | + ASSERT_TRUE(SetFileAttributesA(ioerror_path.c_str(), |
| 1524 | + dir_attrs & ~FILE_ATTRIBUTE_READONLY)); |
| 1525 | + } |
| 1526 | +#endif |
| 1527 | + ASSERT_FALSE(olp::utils::Dir::IsReadOnly(ioerror_path)); |
| 1528 | + |
| 1529 | + cache::CacheSettings settings; |
| 1530 | + settings.disk_path_protected = ioerror_path; |
| 1531 | + settings.openOptions = cache::OpenOptions::Default; |
| 1532 | + DefaultCacheImplHelper cache(settings); |
| 1533 | + |
| 1534 | + // Open should attempt R/W first, get IOError because files are read-only, |
| 1535 | + // then retry in read-only mode. |
| 1536 | + auto open_result = cache.Open(); |
| 1537 | + EXPECT_TRUE( |
| 1538 | + open_result == cache::DefaultCache::StorageOpenResult::Success || |
| 1539 | + open_result == |
| 1540 | + cache::DefaultCache::StorageOpenResult::ProtectedCacheCorrupted || |
| 1541 | + open_result == |
| 1542 | + cache::DefaultCache::StorageOpenResult::OpenDiskPathFailure); |
| 1543 | + |
| 1544 | + helpers::MakeDirectoryAndContentReadonly(ioerror_path, false); |
| 1545 | + ASSERT_TRUE(olp::utils::Dir::Remove(ioerror_path)); |
| 1546 | +} |
| 1547 | + |
1450 | 1548 | } // namespace |
0 commit comments