080-fs.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. #include "catch.hpp"
  2. #include "test-utils.h"
  3. #include "test_supervisor.h"
  4. #include "access.h"
  5. #include "db/utils.h"
  6. #include "fs/utils.h"
  7. #include "fs/fs_actor.h"
  8. #include "utils/error_code.h"
  9. #include <ostream>
  10. #include <fstream>
  11. #include <stdio.h>
  12. #include <net/names.h>
  13. namespace st = syncspirit::test;
  14. namespace fs = syncspirit::fs;
  15. using namespace syncspirit;
  16. using namespace syncspirit::fs;
  17. using namespace syncspirit::test;
  18. using namespace syncspirit::model;
  19. std::string static hash_string(const std::string_view &hash) noexcept {
  20. auto r = std::string();
  21. r.reserve(hash.size() * 2);
  22. for (size_t i = 0; i < hash.size(); ++i) {
  23. char buff[3];
  24. sprintf(buff, "%02x", (unsigned char)hash[i]);
  25. r += std::string_view(buff, 2);
  26. }
  27. return r;
  28. }
  29. static void write(const bfs::path &path, const std::string_view &data) {
  30. std::ofstream f(path.c_str());
  31. f.write(data.data(), data.size());
  32. }
  33. TEST_CASE("utils", "[fs]") {
  34. auto dir = bfs::current_path() / bfs::unique_path();
  35. bfs::create_directory(dir);
  36. auto dir_guard = st::path_guard_t(dir);
  37. auto sample_file = dir / bfs::unique_path();
  38. SECTION("file does not exists") {
  39. auto opt = prepare(sample_file);
  40. CHECK(!opt);
  41. }
  42. SECTION("empty file") {
  43. std::ofstream out(sample_file.c_str(), std::ios::app);
  44. auto opt = prepare(sample_file);
  45. CHECK(opt);
  46. CHECK(!opt.value());
  47. }
  48. SECTION("3 bytes file") {
  49. std::ofstream out(sample_file.c_str(), std::ios::app);
  50. out.write("hi\n", 3);
  51. out.close();
  52. auto opt = prepare(sample_file);
  53. CHECK(opt);
  54. CHECK(opt.value());
  55. auto &b = opt.value().value();
  56. CHECK(b.block_index == 0);
  57. CHECK(b.block_size == 3);
  58. CHECK(b.file_size == 3);
  59. CHECK(b.path == sample_file);
  60. SECTION("digest") {
  61. auto block = compute(b);
  62. REQUIRE(block);
  63. CHECK(block->get_size() == 3);
  64. CHECK(hash_string(block->get_hash()) == "98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4");
  65. CHECK(block->get_weak_hash() == 0x21700dc);
  66. }
  67. }
  68. SECTION("400kb bytes file") {
  69. std::ofstream out(sample_file.c_str(), std::ios::app);
  70. std::string data;
  71. data.resize(400 * 1024);
  72. for (size_t i = 0; i < data.size(); ++i) {
  73. data[i] = 1;
  74. }
  75. out.write(data.data(), data.size());
  76. out.close();
  77. auto opt = prepare(sample_file);
  78. CHECK(opt);
  79. CHECK(opt.value());
  80. auto &b = opt.value().value();
  81. CHECK(b.block_index == 0);
  82. CHECK(b.block_size == 128 * 1024);
  83. CHECK(b.file_size == data.size());
  84. CHECK(b.path == sample_file);
  85. SECTION("digest") {
  86. auto b1 = compute(b);
  87. REQUIRE(b1);
  88. CHECK(b1->get_size() == 128 * 1024);
  89. CHECK(hash_string(b1->get_hash()) == "4017b7a27f5d49ed213ab864b83f7d1f706ecc1039001dadcffed8df6bccddd1");
  90. CHECK(b1->get_weak_hash() == 0x1ef001f);
  91. b.block_index++;
  92. auto b2 = compute(b);
  93. CHECK(*b2 == *b1);
  94. b.block_index++;
  95. auto b3 = compute(b);
  96. CHECK(*b3 == *b1);
  97. b.block_index++;
  98. auto b4 = compute(b);
  99. CHECK(!(*b4 == *b1));
  100. CHECK(b4->get_size() == (400 - 128 * 3) * 1024);
  101. }
  102. }
  103. SECTION("relative") {
  104. CHECK(fs::relative(bfs::path("a/b/c"), bfs::path("a")).path == bfs::path("b/c"));
  105. CHECK(fs::relative(bfs::path("a/b/c.syncspirit-tmp"), bfs::path("a")).path == bfs::path("b/c"));
  106. }
  107. }
  108. struct consumer_actor_config_t : r::actor_config_t {
  109. bfs::path root_path;
  110. };
  111. template <typename Actor> struct consumer_actor_config_builder_t : r::actor_config_builder_t<Actor> {
  112. using builder_t = typename Actor::template config_builder_t<Actor>;
  113. using parent_t = r::actor_config_builder_t<Actor>;
  114. using parent_t::parent_t;
  115. builder_t &&root_path(const bfs::path &value) &&noexcept {
  116. parent_t::config.root_path = value;
  117. return std::move(*static_cast<typename parent_t::builder_t *>(this));
  118. }
  119. };
  120. struct scan_consumer_t : r::actor_base_t {
  121. using config_t = consumer_actor_config_t;
  122. using res_ptr_t = r::intrusive_ptr_t<message::scan_response_t>;
  123. using err_ptr_t = r::intrusive_ptr_t<message::scan_error_t>;
  124. using errors_t = std::vector<err_ptr_t>;
  125. template <typename Actor> using config_builder_t = consumer_actor_config_builder_t<Actor>;
  126. bfs::path root_path;
  127. r::address_ptr_t fs_actor;
  128. res_ptr_t response;
  129. errors_t errors;
  130. explicit scan_consumer_t(config_t &cfg) : r::actor_base_t{cfg}, root_path{cfg.root_path} {}
  131. void configure(r::plugin::plugin_base_t &plugin) noexcept {
  132. r::actor_base_t::configure(plugin);
  133. plugin.with_casted<r::plugin::registry_plugin_t>(
  134. [&](auto &p) { p.discover_name(net::names::fs, fs_actor, true).link(); });
  135. plugin.with_casted<r::plugin::starter_plugin_t>([&](auto &p) {
  136. p.subscribe_actor(&scan_consumer_t::on_response);
  137. p.subscribe_actor(&scan_consumer_t::on_error);
  138. });
  139. }
  140. void on_start() noexcept {
  141. r::actor_base_t::on_start();
  142. void *ptr = (void *)&scan_consumer_t::some_function;
  143. send<payload::scan_request_t>(fs_actor, root_path, get_address(), 0UL, ptr);
  144. }
  145. static void some_function() noexcept {}
  146. void on_response(message::scan_response_t &msg) noexcept {
  147. response = &msg;
  148. get_supervisor().do_shutdown();
  149. }
  150. void on_error(message::scan_error_t &msg) noexcept { errors.emplace_back(&msg); }
  151. };
  152. struct write_consumer_t : r::actor_base_t {
  153. using r::actor_base_t::actor_base_t;
  154. using res_ptr_t = r::intrusive_ptr_t<message::write_response_t>;
  155. r::address_ptr_t fs_actor;
  156. res_ptr_t response;
  157. void configure(r::plugin::plugin_base_t &plugin) noexcept {
  158. r::actor_base_t::configure(plugin);
  159. plugin.with_casted<r::plugin::registry_plugin_t>(
  160. [&](auto &p) { p.discover_name(net::names::fs, fs_actor, true).link(); });
  161. plugin.with_casted<r::plugin::starter_plugin_t>(
  162. [&](auto &p) { p.subscribe_actor(&write_consumer_t::on_response); });
  163. }
  164. void on_response(message::write_response_t &res) noexcept { response = &res; }
  165. void make_request(bfs::path path, const std::string &data, const std::string &hash, bool final, size_t offset = 0) noexcept {
  166. request<payload::write_request_t>(fs_actor, path, offset, data, hash, final).send(init_timeout);
  167. }
  168. };
  169. TEST_CASE("fs-actor", "[fs]") {
  170. auto root_path = bfs::unique_path();
  171. bfs::create_directory(root_path);
  172. auto root_path_guard = path_guard_t(root_path);
  173. r::system_context_t ctx;
  174. auto timeout = r::pt::milliseconds{10};
  175. auto sup = ctx.create_supervisor<st::supervisor_t>().timeout(timeout).create_registry().finish();
  176. sup->start();
  177. SECTION("scan") {
  178. sup->create_actor<fs_actor_t>().fs_config({1024, 5, 0}).timeout(timeout).finish();
  179. SECTION("empty path") {
  180. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  181. sup->do_process();
  182. CHECK(act->errors.empty());
  183. REQUIRE(act->response);
  184. auto &r = act->response->payload.map_info;
  185. CHECK(r->root == root_path);
  186. CHECK(r->map.empty());
  187. // custom payload is preserved
  188. void *ptr_orig = (void *)&scan_consumer_t::some_function;
  189. CHECK(act->response->payload.custom_payload == ptr_orig);
  190. }
  191. SECTION("non-existing root path") {
  192. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path / "bla-bla").finish();
  193. sup->do_process();
  194. CHECK(act->errors.empty());
  195. REQUIRE(act->response);
  196. auto &r = act->response->payload.map_info;
  197. CHECK(r->root == (root_path / "bla-bla"));
  198. CHECK(r->map.empty());
  199. }
  200. SECTION("path with dummy file, in root folder and in subfolder") {
  201. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  202. auto sub = GENERATE(as<std::string>{}, "", "a/b/");
  203. auto dir = root_path / sub;
  204. bfs::create_directories(dir);
  205. auto file = dir / "my-file";
  206. write(file, "hi\n");
  207. sup->do_process();
  208. CHECK(act->errors.empty());
  209. REQUIRE(act->response);
  210. auto &r = act->response->payload.map_info;
  211. REQUIRE(!r->map.empty());
  212. auto it = r->map.begin();
  213. CHECK(it->first.string() == bfs::path(sub) / "my-file");
  214. auto &local = it->second;
  215. auto &blocks = local.blocks;
  216. REQUIRE(blocks.size() == 1);
  217. auto &b = blocks.front();
  218. CHECK(hash_string(b->get_hash()) == "98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4");
  219. CHECK(b->get_weak_hash() == 0x21700dc);
  220. }
  221. SECTION("file with 2 identical blocks") {
  222. static const constexpr size_t SZ = (1 << 7) * 1024;
  223. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  224. auto file = root_path / "my-file";
  225. std::string data;
  226. data.resize(SZ * 2);
  227. std::fill(data.begin(), data.end(), 1);
  228. write(file, data);
  229. sup->do_process();
  230. CHECK(act->errors.empty());
  231. REQUIRE(act->response);
  232. auto &r = act->response->payload.map_info;
  233. REQUIRE(!r->map.empty());
  234. auto it = r->map.begin();
  235. CHECK(it->first == "my-file");
  236. auto &local = it->second;
  237. auto &blocks = local.blocks;
  238. REQUIRE(blocks.size() == 2);
  239. auto &b1 = blocks[0];
  240. auto &b2 = blocks[1];
  241. CHECK(*b1 == *b2);
  242. CHECK(b1 == b2);
  243. }
  244. SECTION("symlink") {
  245. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  246. auto file = root_path / "my-file";
  247. bfs::create_symlink("to-some-where", file);
  248. sup->do_process();
  249. CHECK(act->errors.empty());
  250. REQUIRE(act->response);
  251. auto &r = act->response->payload.map_info;
  252. REQUIRE(!r->map.empty());
  253. auto it = r->map.begin();
  254. CHECK(it->first == "my-file");
  255. auto &local = it->second;
  256. CHECK(local.blocks.size() == 0);
  257. CHECK(local.file_type == model::local_file_t::symlink);
  258. CHECK(local.symlink_target == "to-some-where");
  259. }
  260. SECTION("dir") {
  261. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  262. auto dir = root_path / "my-dir";
  263. bfs::create_directory(dir);
  264. sup->do_process();
  265. CHECK(act->errors.empty());
  266. REQUIRE(act->response);
  267. auto &r = act->response->payload.map_info;
  268. REQUIRE(!r->map.empty());
  269. auto it = r->map.begin();
  270. CHECK(it->first == "my-dir");
  271. auto &local = it->second;
  272. CHECK(local.blocks.size() == 0);
  273. CHECK(local.file_type == model::local_file_t::dir);
  274. }
  275. };
  276. SECTION("write") {
  277. sup->create_actor<fs_actor_t>().fs_config({1024, 5, 0}).timeout(timeout).finish();
  278. auto act = sup->create_actor<write_consumer_t>().timeout(timeout).finish();
  279. sup->do_process();
  280. const std::string data = "123456980";
  281. std::string hash;
  282. hash.resize(SHA256_DIGEST_LENGTH);
  283. utils::digest(data.data(), data.size(), hash.data());
  284. SECTION("success case") {
  285. auto path = root_path / "my-file";
  286. act->make_request(path, data, hash, false);
  287. sup->do_process();
  288. REQUIRE(act->response);
  289. auto &r = act->response->payload;
  290. CHECK(!r.ee);
  291. CHECK(read_file(root_path / "my-file.syncspirit-tmp") == data);
  292. SECTION("tmp file is missing") {
  293. auto act = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  294. sup->do_process();
  295. CHECK(act->errors.empty());
  296. REQUIRE(act->response);
  297. auto &r = act->response->payload.map_info;
  298. CHECK(r->root == root_path);
  299. CHECK(r->map.empty());
  300. }
  301. SECTION("append/final") {
  302. act->response.reset();
  303. const std::string tail = "abcdefg";
  304. utils::digest(tail.data(), tail.size(), hash.data());
  305. act->make_request(path, tail, hash, true, data.size());
  306. sup->do_process();
  307. REQUIRE(act->response);
  308. auto &r = act->response->payload;
  309. CHECK(!r.ee);
  310. CHECK(read_file(path) == data + tail);
  311. }
  312. }
  313. SECTION("success case in subfolder") {
  314. auto path = root_path / "dir" / "my-file";
  315. act->make_request(path, data, hash, false);
  316. sup->do_process();
  317. REQUIRE(act->response);
  318. auto &r = act->response->payload;
  319. CHECK(!r.ee);
  320. CHECK(read_file(root_path / "dir" / "my-file.syncspirit-tmp") == data);
  321. }
  322. SECTION("wrong hash") {
  323. auto path = root_path / "my-file-with-wrong-hash";
  324. hash[0] = hash[0] * -1;
  325. act->make_request(path, data, hash, false);
  326. sup->do_process();
  327. REQUIRE(act->response);
  328. auto &r = act->response->payload;
  329. CHECK(!bfs::exists(path));
  330. CHECK(r.ee);
  331. auto ec = r.ee->ec;
  332. CHECK(ec.value() == (int)utils::protocol_error_code_t::digest_mismatch);
  333. }
  334. }
  335. SECTION("scan temporaries") {
  336. sup->create_actor<fs_actor_t>().fs_config({1024, 5, 10}).timeout(timeout).finish();
  337. auto writer = sup->create_actor<write_consumer_t>().timeout(timeout).finish();
  338. sup->do_process();
  339. auto path = root_path / "my-file";
  340. const std::string data = "123456980";
  341. std::string hash;
  342. hash.resize(SHA256_DIGEST_LENGTH);
  343. utils::digest(data.data(), data.size(), hash.data());
  344. writer->make_request(path, data, hash, false);
  345. sup->do_process();
  346. REQUIRE(writer->response);
  347. CHECK(!writer->response->payload.ee);
  348. CHECK(read_file(root_path / "my-file.syncspirit-tmp") == data);
  349. auto scaner = sup->create_actor<scan_consumer_t>().timeout(timeout).root_path(root_path).finish();
  350. sup->do_process();
  351. CHECK(scaner->errors.empty());
  352. REQUIRE(scaner->response);
  353. auto &r = scaner->response->payload.map_info;
  354. CHECK(r->root == root_path);
  355. CHECK(!r->map.empty());
  356. auto it = r->map.begin();
  357. auto &file = it->second;
  358. CHECK(file.temp);
  359. CHECK(it->first == "my-file");
  360. }
  361. sup->shutdown();
  362. sup->do_process();
  363. }