Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

hyperscan_tools.cxx 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  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 dir = file.get_dir();
  107. auto ext = file.get_extension();
  108. if (std::find_if(cache_dirs.begin(), cache_dirs.end(),
  109. [&](const auto &item) { return item == dir; }) == std::end(cache_dirs)) {
  110. cache_dirs.emplace_back(std::string{dir});
  111. }
  112. if (std::find_if(cache_extensions.begin(), cache_extensions.end(),
  113. [&](const auto &item) { return item == ext; }) == std::end(cache_extensions)) {
  114. cache_extensions.emplace_back(std::string{ext});
  115. }
  116. auto is_known = known_cached_files.insert(file.get_name());
  117. msg_debug_hyperscan("added %s hyperscan file: %*s",
  118. is_known.second ? "new" : "already known",
  119. (int) file.get_name().size(),
  120. file.get_name().data());
  121. }
  122. void add_cached_file(const char *fname)
  123. {
  124. auto fpath = std::filesystem::path{fname};
  125. std::error_code ec;
  126. fpath = std::filesystem::canonical(fpath, ec);
  127. if (ec && ec.value() != 0) {
  128. msg_err_hyperscan("invalid path: \"%s\", error message: %s", fname, ec.message().c_str());
  129. return;
  130. }
  131. auto dir = fpath.parent_path();
  132. auto ext = fpath.extension();
  133. if (std::find_if(cache_dirs.begin(), cache_dirs.end(),
  134. [&](const auto &item) { return item == dir; }) == std::end(cache_dirs)) {
  135. cache_dirs.emplace_back(dir.string());
  136. }
  137. if (std::find_if(cache_extensions.begin(), cache_extensions.end(),
  138. [&](const auto &item) { return item == ext; }) == std::end(cache_extensions)) {
  139. cache_extensions.emplace_back(ext.string());
  140. }
  141. auto is_known = known_cached_files.insert(fpath.string());
  142. msg_debug_hyperscan("added %s hyperscan file: %s",
  143. is_known.second ? "new" : "already known",
  144. fpath.c_str());
  145. }
  146. void delete_cached_file(const char *fname)
  147. {
  148. auto fpath = std::filesystem::path{fname};
  149. std::error_code ec;
  150. fpath = std::filesystem::canonical(fpath, ec);
  151. if (ec && ec.value() != 0) {
  152. msg_err_hyperscan("invalid path to remove: \"%s\", error message: %s",
  153. fname, ec.message().c_str());
  154. return;
  155. }
  156. if (fpath.empty()) {
  157. msg_err_hyperscan("attempt to remove an empty hyperscan file!");
  158. return;
  159. }
  160. if (unlink(fpath.c_str()) == -1) {
  161. msg_err_hyperscan("cannot remove hyperscan file %s: %s",
  162. fpath.c_str(), strerror(errno));
  163. }
  164. else {
  165. msg_debug_hyperscan("removed hyperscan file %s", fpath.c_str());
  166. }
  167. known_cached_files.erase(fpath.string());
  168. }
  169. auto cleanup_maybe() -> void
  170. {
  171. auto env_cleanup_disable = std::getenv("RSPAMD_NO_CLEANUP");
  172. /* We clean dir merely if we are running from the main process */
  173. if (rspamd_current_worker == nullptr && env_cleanup_disable == nullptr && loaded) {
  174. const auto *log_func = RSPAMD_LOG_FUNC;
  175. auto cleanup_dir = [&](std::string_view dir) -> void {
  176. for (const auto &ext: cache_extensions) {
  177. glob_t globbuf;
  178. auto glob_pattern = fmt::format("{}{}*.{}",
  179. dir, G_DIR_SEPARATOR_S, ext);
  180. msg_debug_hyperscan_lambda("perform glob for pattern: %s",
  181. glob_pattern.c_str());
  182. memset(&globbuf, 0, sizeof(globbuf));
  183. if (glob(glob_pattern.c_str(), 0, nullptr, &globbuf) == 0) {
  184. for (auto i = 0; i < globbuf.gl_pathc; i++) {
  185. auto path = std::string{globbuf.gl_pathv[i]};
  186. std::size_t nsz;
  187. struct stat st;
  188. rspamd_normalize_path_inplace(path.data(), path.size(), &nsz);
  189. path.resize(nsz);
  190. if (stat(path.c_str(), &st) == -1) {
  191. msg_debug_hyperscan_lambda("cannot stat file %s: %s",
  192. path.c_str(), strerror(errno));
  193. continue;
  194. }
  195. if (S_ISREG(st.st_mode)) {
  196. if (!known_cached_files.contains(path)) {
  197. msg_info_hyperscan_lambda("remove stale hyperscan file %s", path.c_str());
  198. unlink(path.c_str());
  199. }
  200. else {
  201. msg_debug_hyperscan_lambda("found known hyperscan file %s, size: %Hz",
  202. path.c_str(), st.st_size);
  203. }
  204. }
  205. }
  206. }
  207. globfree(&globbuf);
  208. }
  209. };
  210. for (const auto &dir: cache_dirs) {
  211. msg_debug_hyperscan("cleaning up directory %s", dir.c_str());
  212. cleanup_dir(dir);
  213. }
  214. cache_dirs.clear();
  215. cache_extensions.clear();
  216. known_cached_files.clear();
  217. }
  218. else if (rspamd_current_worker == nullptr && env_cleanup_disable != nullptr) {
  219. msg_debug_hyperscan("disable hyperscan cleanup: env variable RSPAMD_NO_CLEANUP is set");
  220. }
  221. else if (!loaded) {
  222. msg_debug_hyperscan("disable hyperscan cleanup: not loaded");
  223. }
  224. }
  225. auto notice_loaded() -> void
  226. {
  227. loaded = true;
  228. }
  229. };
  230. /**
  231. * This is a higher level representation of the cached hyperscan file
  232. */
  233. struct hs_shared_database {
  234. hs_database_t *db = nullptr; /**< internal database (might be in a shared memory) */
  235. std::optional<raii_mmaped_file> maybe_map;
  236. std::string cached_path;
  237. ~hs_shared_database()
  238. {
  239. if (!maybe_map) {
  240. hs_free_database(db);
  241. }
  242. // Otherwise, handled by maybe_map dtor
  243. }
  244. explicit hs_shared_database(raii_mmaped_file &&map, hs_database_t *db)
  245. : db(db), maybe_map(std::move(map))
  246. {
  247. cached_path = maybe_map.value().get_file().get_name();
  248. }
  249. explicit hs_shared_database(hs_database_t *db, const char *fname)
  250. : db(db), maybe_map(std::nullopt)
  251. {
  252. if (fname) {
  253. cached_path = fname;
  254. }
  255. else {
  256. /* Likely a test case */
  257. cached_path = "";
  258. }
  259. }
  260. hs_shared_database(const hs_shared_database &other) = delete;
  261. hs_shared_database() = default;
  262. hs_shared_database(hs_shared_database &&other) noexcept
  263. {
  264. *this = std::move(other);
  265. }
  266. hs_shared_database &operator=(hs_shared_database &&other) noexcept
  267. {
  268. std::swap(db, other.db);
  269. std::swap(maybe_map, other.maybe_map);
  270. return *this;
  271. }
  272. };
  273. struct real_hs_db {
  274. std::uint32_t magic;
  275. std::uint32_t version;
  276. std::uint32_t length;
  277. std::uint64_t platform;
  278. std::uint32_t crc32;
  279. };
  280. static auto
  281. hs_is_valid_database(void *raw, std::size_t len, std::string_view fname) -> tl::expected<bool, std::string>
  282. {
  283. if (len < sizeof(real_hs_db)) {
  284. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: too short", fname));
  285. }
  286. static real_hs_db test;
  287. memcpy(&test, raw, sizeof(test));
  288. if (test.magic != HS_DB_MAGIC) {
  289. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: invalid magic: {} ({} expected)",
  290. fname, test.magic, HS_DB_MAGIC));
  291. }
  292. #ifdef HS_DB_VERSION
  293. if (test.version != HS_DB_VERSION) {
  294. return tl::make_unexpected(fmt::format("cannot load hyperscan database from {}: invalid version: {} ({} expected)",
  295. fname, test.version, HS_DB_VERSION));
  296. }
  297. #endif
  298. return true;
  299. }
  300. static auto
  301. hs_shared_from_unserialized(hs_known_files_cache &hs_cache, raii_mmaped_file &&map) -> tl::expected<hs_shared_database, error>
  302. {
  303. auto ptr = map.get_map();
  304. auto db = (hs_database_t *) ptr;
  305. auto is_valid = hs_is_valid_database(map.get_map(), map.get_size(), map.get_file().get_name());
  306. if (!is_valid) {
  307. return tl::make_unexpected(error{is_valid.error(), -1, error_category::IMPORTANT});
  308. }
  309. hs_cache.add_cached_file(map.get_file());
  310. return tl::expected<hs_shared_database, error>{tl::in_place, std::move(map), db};
  311. }
  312. static auto
  313. hs_shared_from_serialized(hs_known_files_cache &hs_cache, raii_mmaped_file &&map, std::int64_t offset) -> tl::expected<hs_shared_database, error>
  314. {
  315. hs_database_t *target = nullptr;
  316. if (auto ret = hs_deserialize_database((const char *) map.get_map() + offset,
  317. map.get_size() - offset, &target);
  318. ret != HS_SUCCESS) {
  319. return tl::make_unexpected(error{"cannot deserialize database", ret});
  320. }
  321. hs_cache.add_cached_file(map.get_file());
  322. return tl::expected<hs_shared_database, error>{tl::in_place, target, map.get_file().get_name().data()};
  323. }
  324. auto load_cached_hs_file(const char *fname, std::int64_t offset = 0) -> tl::expected<hs_shared_database, error>
  325. {
  326. auto &hs_cache = hs_known_files_cache::get();
  327. const auto *log_func = RSPAMD_LOG_FUNC;
  328. return raii_mmaped_file::mmap_shared(fname, O_RDONLY, PROT_READ, 0)
  329. .and_then([&]<class T>(T &&cached_serialized) -> tl::expected<hs_shared_database, error> {
  330. if (cached_serialized.get_size() <= offset) {
  331. return tl::make_unexpected(error{"Invalid offset", EINVAL, error_category::CRITICAL});
  332. }
  333. #if defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4
  334. auto unserialized_fname = fmt::format("{}.unser", fname);
  335. auto unserialized_file = raii_locked_file::create(unserialized_fname.c_str(), O_CREAT | O_RDWR | O_EXCL,
  336. 00644)
  337. .and_then([&](auto &&new_file_locked) -> tl::expected<raii_file, error> {
  338. auto tmpfile_pattern = fmt::format("{}{}hsmp-XXXXXXXXXXXXXXXXXX",
  339. cached_serialized.get_file().get_dir(), G_DIR_SEPARATOR);
  340. auto tmpfile = raii_locked_file::mkstemp(tmpfile_pattern.data(), O_CREAT | O_RDWR | O_EXCL,
  341. 00644);
  342. if (!tmpfile) {
  343. return tl::make_unexpected(tmpfile.error());
  344. }
  345. else {
  346. auto &tmpfile_checked = tmpfile.value();
  347. // Store owned string
  348. auto tmpfile_name = std::string{tmpfile_checked.get_name()};
  349. std::size_t unserialized_size;
  350. if (auto ret = hs_serialized_database_size(((const char *) cached_serialized.get_map()) + offset,
  351. cached_serialized.get_size() - offset, &unserialized_size);
  352. ret != HS_SUCCESS) {
  353. return tl::make_unexpected(error{
  354. fmt::format("cannot get unserialized database size: {}", ret),
  355. EINVAL,
  356. error_category::IMPORTANT});
  357. }
  358. msg_debug_hyperscan_lambda("multipattern: create new database in %s; %Hz size",
  359. tmpfile_name.c_str(), unserialized_size);
  360. void *buf;
  361. #ifdef HAVE_GETPAGESIZE
  362. auto page_size = getpagesize();
  363. #else
  364. auto page_size = sysconf(_SC_PAGESIZE);
  365. #endif
  366. if (page_size == -1) {
  367. page_size = 4096;
  368. }
  369. auto errcode = posix_memalign(&buf, page_size, unserialized_size);
  370. if (errcode != 0 || buf == nullptr) {
  371. return tl::make_unexpected(error{"Cannot allocate memory",
  372. errno, error_category::CRITICAL});
  373. }
  374. if (auto ret = hs_deserialize_database_at(((const char *) cached_serialized.get_map()) + offset,
  375. cached_serialized.get_size() - offset, (hs_database_t *) buf);
  376. ret != HS_SUCCESS) {
  377. return tl::make_unexpected(error{
  378. fmt::format("cannot deserialize hyperscan database: {}", ret), ret});
  379. }
  380. else {
  381. if (write(tmpfile_checked.get_fd(), buf, unserialized_size) == -1) {
  382. free(buf);
  383. return tl::make_unexpected(error{fmt::format("cannot write to {}: {}",
  384. tmpfile_name, ::strerror(errno)),
  385. errno, error_category::CRITICAL});
  386. }
  387. else {
  388. free(buf);
  389. /*
  390. * Unlink target file before renaming to avoid
  391. * race condition.
  392. * So what we have is that `new_file_locked`
  393. * will have flock on that file, so it will be
  394. * replaced after unlink safely, and also unlocked.
  395. */
  396. (void) unlink(unserialized_fname.c_str());
  397. if (rename(tmpfile_name.c_str(),
  398. unserialized_fname.c_str()) == -1) {
  399. if (errno != EEXIST) {
  400. msg_info_hyperscan_lambda("cannot rename %s -> %s: %s",
  401. tmpfile_name.c_str(),
  402. unserialized_fname.c_str(),
  403. strerror(errno));
  404. }
  405. }
  406. else {
  407. /* Unlock file but mark it as immortal first to avoid deletion */
  408. tmpfile_checked.make_immortal();
  409. (void) tmpfile_checked.unlock();
  410. }
  411. }
  412. }
  413. /* Reopen in RO mode */
  414. return raii_file::open(unserialized_fname.c_str(), O_RDONLY);
  415. };
  416. })
  417. .or_else([&](auto unused) -> tl::expected<raii_file, error> {
  418. // Cannot create file, so try to open it in RO mode
  419. return raii_file::open(unserialized_fname.c_str(), O_RDONLY);
  420. });
  421. tl::expected<hs_shared_database, error> ret;
  422. if (unserialized_file.has_value()) {
  423. auto &unserialized_checked = unserialized_file.value();
  424. if (unserialized_checked.get_size() == 0) {
  425. /*
  426. * This is a case when we have a file that is currently
  427. * being created by another process.
  428. * We cannot use it!
  429. */
  430. ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  431. }
  432. else {
  433. ret = raii_mmaped_file::mmap_shared(std::move(unserialized_checked), PROT_READ)
  434. .and_then([&]<class U>(U &&mmapped_unserialized) -> auto {
  435. return hs_shared_from_unserialized(hs_cache, std::forward<U>(mmapped_unserialized));
  436. });
  437. }
  438. }
  439. else {
  440. ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  441. }
  442. #else // defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4
  443. auto ret = hs_shared_from_serialized(hs_cache, std::forward<T>(cached_serialized), offset);
  444. #endif// defined(HS_MAJOR) && defined(HS_MINOR) && HS_MAJOR >= 5 && HS_MINOR >= 4 \
  445. // Add serialized file to cache merely if we have successfully loaded the actual db
  446. if (ret.has_value()) {
  447. hs_cache.add_cached_file(cached_serialized.get_file());
  448. }
  449. return ret;
  450. });
  451. }
  452. }// namespace rspamd::util
  453. /* C API */
  454. #define CXX_DB_FROM_C(obj) (reinterpret_cast<rspamd::util::hs_shared_database *>(obj))
  455. #define C_DB_FROM_CXX(obj) (reinterpret_cast<rspamd_hyperscan_t *>(obj))
  456. rspamd_hyperscan_t *
  457. rspamd_hyperscan_maybe_load(const char *filename, goffset offset)
  458. {
  459. auto maybe_db = rspamd::util::load_cached_hs_file(filename, offset);
  460. if (maybe_db.has_value()) {
  461. auto *ndb = new rspamd::util::hs_shared_database;
  462. *ndb = std::move(maybe_db.value());
  463. return C_DB_FROM_CXX(ndb);
  464. }
  465. else {
  466. auto error = maybe_db.error();
  467. switch (error.category) {
  468. case rspamd::util::error_category::CRITICAL:
  469. msg_err_hyperscan("critical error when trying to load cached hyperscan: %s",
  470. error.error_message.data());
  471. break;
  472. case rspamd::util::error_category::IMPORTANT:
  473. msg_info_hyperscan("error when trying to load cached hyperscan: %s",
  474. error.error_message.data());
  475. break;
  476. default:
  477. msg_debug_hyperscan("error when trying to load cached hyperscan: %s",
  478. error.error_message.data());
  479. break;
  480. }
  481. }
  482. return nullptr;
  483. }
  484. hs_database_t *
  485. rspamd_hyperscan_get_database(rspamd_hyperscan_t *db)
  486. {
  487. auto *real_db = CXX_DB_FROM_C(db);
  488. return real_db->db;
  489. }
  490. rspamd_hyperscan_t *
  491. rspamd_hyperscan_from_raw_db(hs_database_t *db, const char *fname)
  492. {
  493. auto *ndb = new rspamd::util::hs_shared_database{db, fname};
  494. return C_DB_FROM_CXX(ndb);
  495. }
  496. void rspamd_hyperscan_free(rspamd_hyperscan_t *db, bool invalid)
  497. {
  498. auto *real_db = CXX_DB_FROM_C(db);
  499. if (invalid && !real_db->cached_path.empty()) {
  500. rspamd::util::hs_known_files_cache::get().delete_cached_file(real_db->cached_path.c_str());
  501. }
  502. delete real_db;
  503. }
  504. void rspamd_hyperscan_notice_known(const char *fname)
  505. {
  506. rspamd::util::hs_known_files_cache::get().add_cached_file(fname);
  507. if (rspamd_current_worker != nullptr) {
  508. /* Also notify main process */
  509. struct rspamd_srv_command notice_cmd;
  510. if (strlen(fname) >= sizeof(notice_cmd.cmd.hyperscan_cache_file.path)) {
  511. msg_err("internal error: length of the filename %d ('%s') is larger than control buffer path: %d",
  512. (int) strlen(fname), fname, (int) sizeof(notice_cmd.cmd.hyperscan_cache_file.path));
  513. }
  514. else {
  515. notice_cmd.type = RSPAMD_SRV_NOTICE_HYPERSCAN_CACHE;
  516. rspamd_strlcpy(notice_cmd.cmd.hyperscan_cache_file.path, fname, sizeof(notice_cmd.cmd.hyperscan_cache_file.path));
  517. rspamd_srv_send_command(rspamd_current_worker,
  518. rspamd_current_worker->srv->event_loop, &notice_cmd, -1,
  519. nullptr,
  520. nullptr);
  521. }
  522. }
  523. }
  524. void rspamd_hyperscan_cleanup_maybe(void)
  525. {
  526. rspamd::util::hs_known_files_cache::get().cleanup_maybe();
  527. }
  528. void rspamd_hyperscan_notice_loaded(void)
  529. {
  530. rspamd::util::hs_known_files_cache::get().notice_loaded();
  531. }
  532. #endif// WITH_HYPERSCAN