| 95 | } |
| 96 | |
| 97 | std::optional<uint64_t> FindInFileRange( |
| 98 | const std::filesystem::path& path, |
| 99 | uint64_t offset, |
| 100 | uint64_t size, |
| 101 | std::span<const uint8_t> pattern, |
| 102 | size_t chunkBytes) { |
| 103 | const Stopwatch totalTimer; |
| 104 | FixedPatternScanner scanner(pattern); |
| 105 | const size_t patternSize = scanner.PatternSize(); |
| 106 | if (patternSize == 0 || size == 0) return std::nullopt; |
| 107 | |
| 108 | Windows::UniqueFileHandle file = OpenForOverlappedSequentialRead(path); |
| 109 | if (!file) { |
| 110 | OSTP_LOG_DEBUG("ByteSearch::FindInFileRange open failed path={} offset=0x{:X} size={} err={}", |
| 111 | path.string(), offset, size, ::GetLastError()); |
| 112 | return std::nullopt; |
| 113 | } |
| 114 | |
| 115 | LARGE_INTEGER sizeLi{}; |
| 116 | if (!::GetFileSizeEx(file.get(), &sizeLi) || sizeLi.QuadPart <= 0) { |
| 117 | OSTP_LOG_DEBUG("ByteSearch::FindInFileRange empty/invalid file path={} offset=0x{:X} size={}", |
| 118 | path.string(), offset, size); |
| 119 | return std::nullopt; |
| 120 | } |
| 121 | |
| 122 | const uint64_t fileSize = static_cast<uint64_t>(sizeLi.QuadPart); |
| 123 | if (offset >= fileSize) { |
| 124 | OSTP_LOG_DEBUG("ByteSearch::FindInFileRange offset past EOF path={} offset=0x{:X} file_size={}", |
| 125 | path.string(), offset, fileSize); |
| 126 | return std::nullopt; |
| 127 | } |
| 128 | |
| 129 | const uint64_t rangeEnd = offset + (std::min)(size, fileSize - offset); |
| 130 | |
| 131 | chunkBytes = (std::max)(chunkBytes, patternSize); |
| 132 | // Consecutive chunks overlap by (patternSize - 1) bytes: a match straddling a |
| 133 | // chunk boundary runs off chunk i's tail (BMH won't see it there) but is fully |
| 134 | // contained at the head of chunk i+1. Because the overlap is re-read from disk |
| 135 | // rather than carry-copied from the previous buffer, the read of chunk i+1 has |
| 136 | // no data dependency on the scan of chunk i — so the two can truly overlap. |
| 137 | const uint64_t stride = static_cast<uint64_t>(chunkBytes) - (patternSize - 1); |
| 138 | |
| 139 | // Double buffering: while BMH scans chunk i in one buffer, the kernel streams |
| 140 | // chunk i+1 into the other. make_unique_for_overwrite skips the zero-fill the |
| 141 | // buffers are about to be overwritten anyway. |
| 142 | std::unique_ptr<uint8_t[]> buffers[2] = { |
| 143 | std::make_unique_for_overwrite<uint8_t[]>(chunkBytes), |
| 144 | std::make_unique_for_overwrite<uint8_t[]>(chunkBytes), |
| 145 | }; |
| 146 | Windows::UniqueHandle events[2] = { |
| 147 | Windows::UniqueHandle(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), |
| 148 | Windows::UniqueHandle(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), |
| 149 | }; |
| 150 | if (!events[0] || !events[1]) { |
| 151 | OSTP_LOG_DEBUG("ByteSearch::FindInFileRange event create failed path={} err={}", |
| 152 | path.string(), ::GetLastError()); |
| 153 | return std::nullopt; |
| 154 | } |
no test coverage detected