You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hyperscan_tools.cxx 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /*
  2. * Copyright 2023 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "config.h"
  17. #ifdef WITH_HYPERSCAN
  18. #include <string>
  19. #include <filesystem>
  20. #include "contrib/ankerl/unordered_dense.h"
  21. #include "contrib/ankerl/svector.h"
  22. #include "fmt/core.h"
  23. #include "libutil/cxx/file_util.hxx"
  24. #include "libutil/cxx/error.hxx"
  25. #include "hs.h"
  26. #include "logger.h"
  27. #include "worker_util.h"
  28. #include "hyperscan_tools.h"
  29. #include <glob.h> /* for glob */
  30. #include <unistd.h> /* for unlink */
  31. #include <optional>
  32. #include <cstdlib> /* for std::getenv */
  33. #include "unix-std.h"
  34. #include "rspamd_control.h"
  35. #define HYPERSCAN_LOG_TAG "hsxxxx"
  36. // Hyperscan does not provide any API to check validity of it's databases
  37. // However, it is required for us to perform migrations properly without
  38. // failing at `hs_alloc_scratch` phase or even `hs_scan` which is **way too late**
  39. // Hence, we have to check hyperscan internal guts to prevent that situation...
  40. #ifdef HS_MAJOR
  41. #ifndef HS_VERSION_32BIT
  42. #define HS_VERSION_32BIT ((HS_MAJOR << 24) | (HS_MINOR << 16) | (HS_PATCH << 8) | 0)
  43. #endif
  44. #endif// defined(HS_MAJOR)
  45. #if !defined(HS_DB_VERSION) && defined(HS_VERSION_32BIT)
  46. #define HS_DB_VERSION HS_VERSION_32BIT
  47. #endif
  48. #ifndef HS_DB_MAGIC
  49. #define HS_DB_MAGIC (0xdbdbdbdbU)
  50. #endif
  51. #define msg_info_hyperscan(...) rspamd_default_log_function(G_LOG_LEVEL_INFO, \
  52. "hyperscan", HYPERSCAN_LOG_TAG, \
  53. RSPAMD_LOG_FUNC, \
  54. __VA_ARGS__)
  55. #define msg_info_hyperscan_lambda(...) rspamd_default_log_function(G_LOG_LEVEL_INFO, \
  56. "hyperscan", HYPERSCAN_LOG_TAG, \
  57. log_func, \
  58. __VA_ARGS__)
  59. #define msg_err_hyperscan(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
  60. "hyperscan", HYPERSCAN_LOG_TAG, \
  61. RSPAMD_LOG_FUNC, \
  62. __VA_ARGS__)
  63. #define msg_debug_hyperscan(...) rspamd_conditional_debug_fast(nullptr, nullptr, \
  64. rspamd_hyperscan_log_id, "hyperscan", HYPERSCAN_LOG_TAG, \
  65. RSPAMD_LOG_FUNC, \
  66. __VA_ARGS__)
  67. #define msg_debug_hyperscan_lambda(...) rspamd_conditional_debug_fast(nullptr, nullptr, \
  68. rspamd_hyperscan_log_id, "hyperscan", HYPERSCAN_LOG_TAG, \
  69. log_func, \
  70. __VA_ARGS__)
  71. INIT_LOG_MODULE_PUBLIC(hyperscan)
  72. namespace rspamd::util {
  73. /*
  74. * A singleton class that is responsible for deletion of the outdated hyperscan files
  75. * One issue is that it must know about HS files in all workers, which is a problem
  76. * TODO: we need to export hyperscan caches from all workers to a single place where
  77. * we can clean them up (probably, to the main process)
  78. */
  79. class hs_known_files_cache {
  80. private:
  81. // These fields are filled when we add new known cache files
  82. ankerl::svector<std::string, 4> cache_dirs;
  83. ankerl::svector<std::string, 8> cache_extensions;
  84. ankerl::unordered_dense::set<std::string> known_cached_files;
  85. bool loaded = false;
  86. private:
  87. hs_known_files_cache() = default;
  88. virtual ~hs_known_files_cache()
  89. {
  90. // Cleanup cache dir
  91. cleanup_maybe();
  92. }
  93. public:
  94. hs_known_files_cache(const hs_known_files_cache &) = delete;
  95. hs_known_files_cache(hs_known_files_cache &&) = delete;
  96. static auto get() -> hs_known_files_cache &
  97. {
  98. static hs_known_files_cache *singleton = nullptr;
  99. if (singleton == nullptr) {
  100. singleton = new hs_known_files_cache;
  101. }
  102. return *singleton;
  103. }
  104. void add_cached_file(const raii_file &file)
  105. {
  106. auto fpath = std::filesystem::path{file.get_name()};
  107. std::error_code ec;
  108. fpath = std::filesystem::canonical(fpath, ec);
  109. if (ec && ec.value() != 0) {
  110. msg_err_hyperscan("invalid path: \"%s\", error message: %s", fpath.c_str(), ec.message().c_str());
  111. return;
  112. }
  113. auto dir = fpath.parent_path();
  114. auto ext = fpath.extension();
  115. if (std::find_if(cache_dirs.begin(), cache_dirs.end(),
  116. [&](const auto &item) { return item == dir; }) == std::end(cache_dirs)) {
  117. cache_dirs.emplace_back(std::string{dir});
  118. }
  119. if (std::find_if(cache_extensions.begin(), cache_extensions.end(),
  120. [&](const auto &item) { return item == ext; }) == std::end(cache_extensions)) {
  121. cache_extensions.emplace_back(std::string{ext});
  122. }
  123. auto is_known = known_cached_files.insert(fpath.string());
  124. msg_debug_hyperscan("added %s hyperscan file: %s",
  125. is_known.second ? "new" : "already known",
  126. fpath.c_str());
  127. }
  128. void add_cached_file(const char *fname)
  129. {
  130. auto fpath = std::filesystem::path{fname};
  131. std::error_code ec;
  132. fpath = std::filesystem::canonical(fpath, ec);
  133. if (ec && ec.value() != 0) {
  134. msg_err_hyperscan("invalid path: \"%s\", error message: %s", fname, ec.message().c_str());
  135. return;
  136. }
  137. auto dir = fpath.parent_path();
  138. auto ext = fpath.extension();
  139. if (std::find_if(cache_dirs.begin(), cache_dirs.end(),
  140. [&](const auto &item) { return item == dir; }) == std::end(cache_dirs)) {
  141. cache_dirs.emplace_back(dir.string());
  142. }
  143. if (std::find_if(cache_extensions.begin(), cache_extensions.end(),
  144. [&](const auto &item) { return item == ext; }) == std::end(cache_extensions)) {
  145. cache_extensions.emplace_back(ext.string());
  146. }
  147. auto is_known = known_cached_files.insert(fpath.string());
  148. msg_debug_hyperscan("added %s hyperscan file: %s",
  149. is_known.second ? "new" : "already known",
  150. fpath.c_str());
  151. }
  152. void delete_cached_file(const char *fname)
  153. {
  154. auto fpath = std::filesystem::path{fname};
  155. std::error_code ec;
  156. fpath = std::filesystem::canonical(fpath, ec);
  157. if (ec && ec.value() != 0) {
  158. msg_err_hyperscan("invalid path to remove: \"%s\", error message: %s",
  159. fname, ec.message().c_str());
  160. return;
  161. }
  162. if (fpath.empty()) {
  163. msg_err_hyperscan("attempt to remove an empty hyperscan file!");
  164. return;
  165. }
  166. if (unlink(fpath.c_str()) == -1) {
  167. msg_err_hyperscan("cannot remove hyperscan file %s: %s",
  168. fpath.c_str(), strerror(errno));
  169. }
  170. else {
  171. msg_debug_hyperscan("removed hyperscan file %s", fpath.c_str());
  172. }
  173. known_cached_files.erase(fpath.string());
  174. }
  175. auto cleanup_maybe() -> void
  176. {
  177. auto env_cleanup_disable = std::getenv("RSPAMD_NO_CLEANUP");
  178. /* We clean dir merely if we are running from the main process */
  179. if (rspamd_current_worker == nullptr && env_cleanup_disable == nullptr && loaded) {
  180. const auto *log_func = RSPAMD_LOG_FUNC;
  181. auto cleanup_dir = [&](std::string_view dir) -> void {
  182. for (const auto &ext: cache_extensions) {
  183. glob_t globbuf;
  184. auto glob_pattern = fmt::format("{}{}*{}",
  185. dir, G_DIR_SEPARATOR_S, ext);
  186. msg_debug_hyperscan_lambda("perform glob for pattern: %s",
  187. glob_pattern.c_str());
  188. memset(&globbuf, 0, sizeof(globbuf));
  189. if (glob(glob_pattern.c_str(), 0, nullptr, &globbuf) == 0) {
  190. for (auto i = 0; i < globbuf.gl_pathc; i++) {
  191. auto path = std::string{globbuf.gl_pathv[i]};
  192. std::size_t nsz;
  193. struct stat st;
  194. rspamd_normalize_path_inplace(path.data(), path.size(), &nsz);
  195. path.resize(nsz);
  196. if (stat(path.c_str(), &st) == -1) {
  197. msg_debug_hyperscan_lambda("cannot stat file %s: %s",
  198. path.c_str(), strerror(errno));
  199. continue;
  200. }
  201. if (S_ISREG(st.st_mode)) {
  202. if (!known_cached_files.contains(path)) {
  203. msg_info_hyperscan_lambda("remove stale hyperscan file %s", path.c_str());
  204. unlink(path.c_str());
  205. }
  206. else {
  207. msg_debug_hyperscan_lambda("found known hyperscan file %s, size: %Hz",
  208. path.c_str(), st.st_size);
  209. }
  210. }
  211. }
  212. }
  213. globfree(&globbuf);
  214. }
  215. };
  216. for (const auto &dir: cache_dirs) {
  217. msg_info_hyperscan("cleaning up directory %s", dir.c_str());
  218. cleanup_dir(dir);
  219. }
  220. cache_dirs.clear();
  221. cache_extensions.clear();
  222. known_cached_files.clear();
  223. }
  224. else if (rspamd_current_worker == nullptr && env_cleanup_disable != nullptr) {
  225. msg_info_hyperscan("disable hyperscan cleanup: env variable RSPAMD_NO_CLEANUP is set");
  226. }
  227. else if (!loaded) {
  228. msg_info_hyperscan("disable hyperscan cleanup: not loaded");
  229. }
  230. }
  231. auto notice_loaded() -> void
  232. {
  233. loaded = true;
  234. }
  235. };
  236. /**
  237. * This is a higher level representation of the cached hyperscan file
  238. */
  239. struct hs_shared_database {
  240. hs_database_t *db = nullptr; /**< internal database (might be in a shared memory) */
  241. std::optional<raii_mmaped_file> maybe_map;
  242. std::string cached_path;
  243. ~hs_shared_database()
  244. {
  245. if (!maybe_map) {
  246. hs_free_database(db);
  247. }
  248. // Otherwise, handled by maybe_map dtor
  249. }
  250. explicit hs_shared_database(raii_mmaped_file &&map, hs_database_t *db)
  251. : db(db), maybe_map(std::move(map))
  252. {
  253. cached_path = maybe_map.value().get_file().get_name();
  254. }
  255. explicit hs_shared_database(hs_database_t *db, const char *fname)
  256. : db(db), maybe_map(std::nullopt)
  257. {
  258. if (fname) {
  259. cached_path = fname;
  260. }
  261. else {
  262. /* Likely a test case */
  263. cached_path = "";
  264. }
  265. }
  266. hs_shared_database(const hs_shared_database &other) = delete;
  267. hs_shared_database() = default;
  268. hs_shared_database(hs_shared_database &&other) noexcept
  269. {
  270. *this = std::move(other);
  271. }
  272. hs_shared_database &operator=(hs_shared_database &&other) noexcept
  273. {
  274. std::swap(db, other.db);
  275. std::swap(maybe_map, other.maybe_map);
  276. return *this;
  277. }
  278. };
  279. struct real_hs_db {
  280. std::uint32_t magic;
  281. std::uint32_t version;
  282. std::uint32_t length;
  283. std::uint64_t platform;
  284. std::uint32_t crc32;
  285. };
  286. static auto
  287. hs_is_valid_database(void *raw, std::size_t len, std::string_view fname) -> tl::expected<bool, std::string>
  288. {
  289. if (len < sizeof(real_hs_db)) {
  290. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: too short", fname));
  291. }
  292. static real_hs_db test;
  293. memcpy(&test, raw, sizeof(test));
  294. if (test.magic != HS_DB_MAGIC) {
  295. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: invalid magic: {} ({} expected)",
  296. fname, test.magic, HS_DB_MAGIC));
  297. }
  298. #ifdef HS_DB_VERSION
  299. if (test.version != HS_DB_VERSION) {
  300. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: invalid version: {} ({} expected)",
  301. fname, test.version, HS_DB_VERSION));
  302. }
  303. #endif
  304. return true;
  305. }
  306. static auto
  307. hs_shared_from_unserialized(hs_known_files_cache &hs_cache, raii_mmaped_file &&map) -> tl::expected<hs_shared_database, error>
  308. {
  309. auto ptr = map.get_map();
  310. auto db = (hs_database_t *) ptr;
  311. auto is_valid = hs_is_valid_database(map.get_map(), map.get_size(), map.get_file().get_name());
  312. if (!is_valid) {
  313. return tl::make_unexpected(error{is_valid.error(), -1, error_category::IMPORTANT});
  314. }
  315. hs_cache.add_cached_file(map.get_file());
  316. return tl::expected<hs_shared_database, error>{tl::in_place, std::move(map), db};
  317. }
  318. static auto
  319. hs_shared_from_serialized(hs_known_files_cache &hs_cache, raii_mmaped_file &&map, std::int64_t offset) -> tl::expected<hs_shared_database, error>
  320. {
  321. hs_database_t *target = nullptr;
  322. if (auto ret = hs_deserialize_database((const char *) map.get_map() + offset,
  323. map.get_size() - offset, &target);
  324. ret != HS_SUCCESS) {
  325. return tl::make_unexpected(error{"cannot deserialize database", ret});
  326. }
  327. hs_cache.add_cached_file(map.get_file());
  328. return tl::expected<hs_shared_database, error>{tl::in_place, target, map.get_file().get_name().data()};
  329. }
  330. auto load_cached_hs_file(const char *fname, std::int64_t offset = 0) -> tl::expected<hs_shared_database, error>
  331. {
  332. auto &hs_cache = hs_known_files_cache::get();
  333. const auto *log_func = RSPAMD_LOG_FUNC;
  334. return raii_mmaped_file::mmap_shared(fname, O_RDONLY, PROT_READ, 0)
  335. .and_then([&]<class T>(T &&cached_serialized) -> tl::expected<hs_shared_database, error> {
  336. if (cached_serialized.get_size() <= offset) {
  337. return tl::make_unexpected(error{"Invalid offset", EINVAL, error_category::CRITICAL});
  338. }
  339. #if defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4
  340. auto unserialized_fname = fmt::format("{}.unser", fname);
  341. auto unserialized_file = raii_locked_file::create(unserialized_fname.c_str(), O_CREAT | O_RDWR | O_EXCL,
  342. 00644)
  343. .and_then([&](auto &&new_file_locked) -> tl::expected<raii_file, error> {
  344. auto tmpfile_pattern = fmt::format("{}{}hsmp-XXXXXXXXXXXXXXXXXX",
  345. cached_serialized.get_file().get_dir(), G_DIR_SEPARATOR);
  346. auto tmpfile = raii_locked_file::mkstemp(tmpfile_pattern.data(), O_CREAT | O_RDWR | O_EXCL,
  347. 00644);
  348. if (!tmpfile) {
  349. return tl::make_unexpected(tmpfile.error());
  350. }
  351. else {
  352. auto &tmpfile_checked = tmpfile.value();
  353. // Store owned string
  354. auto tmpfile_name = std::string{tmpfile_checked.get_name()};
  355. std::size_t unserialized_size;
  356. if (auto ret = hs_serialized_database_size(((const char *) cached_serialized.get_map()) + offset,
  357. cached_serialized.get_size() - offset, &unserialized_size);
  358. ret != HS_SUCCESS) {
  359. return tl::make_unexpected(error{
  360. fmt::format("cannot get unserialized database size: {}", ret),
  361. EINVAL,
  362. error_category::IMPORTANT});
  363. }
  364. msg_debug_hyperscan_lambda("multipattern: create new database in %s; %Hz size",
  365. tmpfile_name.c_str(), unserialized_size);
  366. void *buf;
  367. #ifdef HAVE_GETPAGESIZE
  368. auto page_size = getpagesize();
  369. #else
  370. auto page_size = sysconf(_SC_PAGESIZE);
  371. #endif
  372. if (page_size == -1) {
  373. page_size = 4096;
  374. }
  375. auto errcode = posix_memalign(&buf, page_size, unserialized_size);
  376. if (errcode != 0 || buf == nullptr) {
  377. return tl::make_unexpected(error{"Cannot allocate memory",
  378. errno, error_category::CRITICAL});
  379. }
  380. if (auto ret = hs_deserialize_database_at(((const char *) cached_serialized.get_map()) + offset,
  381. cached_serialized.get_size() - offset, (hs_database_t *) buf);
  382. ret != HS_SUCCESS) {
  383. return tl::make_unexpected(error{
  384. fmt::format("cannot deserialize hyperscan database: {}", ret), ret});
  385. }
  386. else {
  387. if (write(tmpfile_checked.get_fd(), buf, unserialized_size) == -1) {
  388. free(buf);
  389. return tl::make_unexpected(error{fmt::format("cannot write to {}: {}",
  390. tmpfile_name, ::strerror(errno)),
  391. errno, error_category::CRITICAL});
  392. }
  393. else {
  394. free(buf);
  395. /*
  396. * Unlink target file before renaming to avoid
  397. * race condition.
  398. * So what we have is that `new_file_locked`
  399. * will have flock on that file, so it will be
  400. * replaced after unlink safely, and also unlocked.
  401. */
  402. (void) unlink(unserialized_fname.c_str());
  403. if (rename(tmpfile_name.c_str(),
  404. unserialized_fname.c_str()) == -1) {
  405. if (errno != EEXIST) {
  406. msg_info_hyperscan_lambda("cannot rename %s -> %s: %s",
  407. tmpfile_name.c_str(),
  408. unserialized_fname.c_str(),
  409. strerror(errno));
  410. }
  411. }
  412. else {
  413. /* Unlock file but mark it as immortal first to avoid deletion */
  414. tmpfile_checked.make_immortal();
  415. (void) tmpfile_checked.unlock();
  416. }
  417. }
  418. }
  419. /* Reopen in RO mode */
  420. return raii_file::open(unserialized_fname.c_str(), O_RDONLY);
  421. };
  422. })
  423. .or_else([&](auto unused) -> tl::expected<raii_file, error> {
  424. // Cannot create file, so try to open it in RO mode
  425. return raii_file::open(unserialized_fname.c_str(), O_RDONLY);
  426. });
  427. tl::expected<hs_shared_database, error> ret;
  428. if (unserialized_file.has_value()) {
  429. auto &unserialized_checked = unserialized_file.value();
  430. if (unserialized_checked.get_size() == 0) {
  431. /*
  432. * This is a case when we have a file that is currently
  433. * being created by another process.
  434. * We cannot use it!
  435. */
  436. ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  437. }
  438. else {
  439. ret = raii_mmaped_file::mmap_shared(std::move(unserialized_checked), PROT_READ)
  440. .and_then([&]<class U>(U &&mmapped_unserialized) -> auto {
  441. return hs_shared_from_unserialized(hs_cache, std::forward<U>(mmapped_unserialized));
  442. });
  443. }
  444. }
  445. else {
  446. ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  447. }
  448. #else // defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4
  449. auto ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  450. #endif// defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4 \
  451. // Add serialized file to cache merely if we have successfully loaded the actual db
  452. if (ret.has_value()) {
  453. hs_cache.add_cached_file(cached_serialized.get_file());
  454. }
  455. return ret;
  456. });
  457. }
  458. }// namespace rspamd::util
  459. /* C API */
  460. #define CXX_DB_FROM_C(obj) (reinterpret_cast<rspamd::util::hs_shared_database *>(obj))
  461. #define C_DB_FROM_CXX(obj) (reinterpret_cast<rspamd_hyperscan_t *>(obj))
  462. rspamd_hyperscan_t *
  463. rspamd_hyperscan_maybe_load(const char *filename, goffset offset)
  464. {
  465. auto maybe_db = rspamd::util::load_cached_hs_file(filename, offset);
  466. if (maybe_db.has_value()) {
  467. auto *ndb = new rspamd::util::hs_shared_database;
  468. *ndb = std::move(maybe_db.value());
  469. return C_DB_FROM_CXX(ndb);
  470. }
  471. else {
  472. auto error = maybe_db.error();
  473. switch (error.category) {
  474. case rspamd::util::error_category::CRITICAL:
  475. msg_err_hyperscan("critical error when trying to load cached hyperscan: %s",
  476. error.error_message.data());
  477. break;
  478. case rspamd::util::error_category::IMPORTANT:
  479. msg_info_hyperscan("error when trying to load cached hyperscan: %s",
  480. error.error_message.data());
  481. break;
  482. default:
  483. msg_debug_hyperscan("error when trying to load cached hyperscan: %s",
  484. error.error_message.data());
  485. break;
  486. }
  487. }
  488. return nullptr;
  489. }
  490. hs_database_t *
  491. rspamd_hyperscan_get_database(rspamd_hyperscan_t *db)
  492. {
  493. auto *real_db = CXX_DB_FROM_C(db);
  494. return real_db->db;
  495. }
  496. rspamd_hyperscan_t *
  497. rspamd_hyperscan_from_raw_db(hs_database_t *db, const char *fname)
  498. {
  499. auto *ndb = new rspamd::util::hs_shared_database{db, fname};
  500. return C_DB_FROM_CXX(ndb);
  501. }
  502. void rspamd_hyperscan_free(rspamd_hyperscan_t *db, bool invalid)
  503. {
  504. auto *real_db = CXX_DB_FROM_C(db);
  505. if (invalid && !real_db->cached_path.empty()) {
  506. rspamd::util::hs_known_files_cache::get().delete_cached_file(real_db->cached_path.c_str());
  507. }
  508. delete real_db;
  509. }
  510. void rspamd_hyperscan_notice_known(const char *fname)
  511. {
  512. rspamd::util::hs_known_files_cache::get().add_cached_file(fname);
  513. if (rspamd_current_worker != nullptr) {
  514. /* Also notify main process */
  515. struct rspamd_srv_command notice_cmd;
  516. if (strlen(fname) >= sizeof(notice_cmd.cmd.hyperscan_cache_file.path)) {
  517. msg_err("internal error: length of the filename %d ('%s') is larger than control buffer path: %d",
  518. (int) strlen(fname), fname, (int) sizeof(notice_cmd.cmd.hyperscan_cache_file.path));
  519. }
  520. else {
  521. notice_cmd.type = RSPAMD_SRV_NOTICE_HYPERSCAN_CACHE;
  522. rspamd_strlcpy(notice_cmd.cmd.hyperscan_cache_file.path, fname, sizeof(notice_cmd.cmd.hyperscan_cache_file.path));
  523. rspamd_srv_send_command(rspamd_current_worker,
  524. rspamd_current_worker->srv->event_loop, &notice_cmd, -1,
  525. nullptr,
  526. nullptr);
  527. }
  528. }
  529. }
  530. void rspamd_hyperscan_cleanup_maybe(void)
  531. {
  532. rspamd::util::hs_known_files_cache::get().cleanup_maybe();
  533. }
  534. void rspamd_hyperscan_notice_loaded(void)
  535. {
  536. rspamd::util::hs_known_files_cache::get().notice_loaded();
  537. }
  538. #endif// WITH_HYPERSCAN