/* Copyright (C) 2020-2021 Carl Hetherington This file is part of DCP-o-matic. DCP-o-matic is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DCP-o-matic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DCP-o-matic. If not, see . */ #include "lib/cross.h" #include "lib/ext.h" #include "lib/io_context.h" #include "test.h" #include #include #include #include #include #include #include #include #include #include #include #include using std::future; using std::string; using std::vector; #ifdef DCPOMATIC_BOOST_PROCESS_V1 vector ext2_ls (vector arguments) { using namespace boost::process; dcpomatic::io_context ios; future data; child ch (search_path("e2ls"), arguments, std_in.close(), std_out > data, ios); ios.run(); auto output = data.get(); boost::trim (output); vector parts; boost::split (parts, output, boost::is_any_of("\t "), boost::token_compress_on); return parts; } #else static string collect_from_pipe(boost::asio::readable_pipe& pipe) { std::string output; while (true) { string block; boost::system::error_code ec; boost::asio::read(pipe, boost::asio::dynamic_buffer(block), ec); output += block; if (ec && ec == boost::asio::error::eof) { break; } } return output; } vector ext2_ls(vector arguments) { using namespace boost::process::v2; dcpomatic::io_context ios; boost::asio::readable_pipe out{ios}; process ch(ios, environment::find_executable("e2ls"), arguments, process_stdio{ {}, out, {}}); std::string output = collect_from_pipe(out); boost::trim(output); vector parts; boost::split(parts, output, boost::is_any_of("\t "), boost::token_compress_on); return parts; } #endif static void make_empty_file(boost::filesystem::path file, off_t size) { auto fd = open (file.string().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); BOOST_REQUIRE (fd != -1); auto const r = posix_fallocate (fd, 0, size); BOOST_REQUIRE_EQUAL (r, 0); close (fd); } /** Use the writer code to make a disk and partition and copy a file (in a directory) * to it, then check that: * - the partition has inode size 128 * - the file and directory have reasonable timestamps * - the file can be copied back off the disk */ BOOST_AUTO_TEST_CASE (disk_writer_test1) { using namespace boost::filesystem; #ifdef DCPOMATIC_BOOST_PROCESS_V1 using namespace boost::process; #else using namespace boost::process::v2; #endif Cleanup cl; path disk = "build/test/disk_writer_test1.disk"; path partition = "build/test/disk_writer_test1.partition"; cl.add(disk); cl.add(partition); /* lwext4 has a lower limit of correct ext2 partition sizes it can make; 32Mb * does not work here: fsck gives errors about an incorrect free blocks count. */ make_random_file(disk, 256 * 1024 * 1024); make_random_file(partition, 256 * 1024 * 1024); path dcp = "build/test/disk_writer_test1"; create_directory (dcp); /* Some arbitrary file size here */ make_random_file (dcp / "foo", 1024 * 1024 * 32 - 6128); dcpomatic::write ({dcp}, disk.string(), partition.string(), nullptr); BOOST_CHECK_EQUAL (system("/sbin/e2fsck -fn build/test/disk_writer_test1.partition"), 0); { dcpomatic::io_context ios; #ifdef DCPOMATIC_BOOST_PROCESS_V1 future data; child ch ("/sbin/tune2fs", args({"-l", partition.string()}), std_in.close(), std_out > data, ios); ios.run(); string output = data.get(); #else boost::asio::readable_pipe out{ios}; process ch(ios, "/sbin/tune2fs", {"-l", partition.string()}, process_stdio{ {}, out, {}}); string output = collect_from_pipe(out); #endif std::smatch matches; std::regex reg("Inode size:\\s*(.*)"); BOOST_REQUIRE (std::regex_search(output, matches, reg)); BOOST_REQUIRE (matches.size() == 2); BOOST_CHECK_EQUAL (matches[1].str(), "128"); } BOOST_CHECK (ext2_ls({partition.string()}) == vector({"disk_writer_test1", "lost+found"})); string const unset_date = "1-Jan-1970"; /* Check timestamp of the directory has been set */ auto details = ext2_ls({"-l", partition.string()}); BOOST_REQUIRE (details.size() >= 6); BOOST_CHECK (details[5] != unset_date); auto const dir = partition.string() + ":disk_writer_test1"; BOOST_CHECK (ext2_ls({dir}) == vector({"foo"})); /* Check timestamp of foo */ details = ext2_ls({"-l", dir}); BOOST_REQUIRE (details.size() >= 6); BOOST_CHECK (details[5] != unset_date); int const r = system(fmt::format("e2cp {}:disk_writer_test1/foo build/test/disk_writer_test1_foo_back", partition.string()).c_str()); BOOST_CHECK_EQUAL(r, 0); check_file ("build/test/disk_writer_test1/foo", "build/test/disk_writer_test1_foo_back"); cl.run(); } BOOST_AUTO_TEST_CASE (disk_writer_test2) { using namespace boost::filesystem; using namespace boost::process; remove_all("build/test/disk_writer_test2.disk"); remove_all("build/test/disk_writer_test2.partition"); remove_all("build/test/disk_writer_test2"); Cleanup cl; path const disk = "build/test/disk_writer_test2.disk"; path const partition = "build/test/disk_writer_test2.partition"; cl.add(disk); cl.add(partition); /* Using empty files here still triggers the bug and is much quicker than using random data */ make_empty_file(disk, 31043616768LL); make_empty_file(partition, 31043571712LL); auto const dcp = TestPaths::private_data() / "xm"; dcpomatic::write({dcp}, disk.string(), partition.string(), nullptr); BOOST_CHECK_EQUAL(system("/sbin/e2fsck -fn build/test/disk_writer_test2.partition"), 0); path const check = "build/test/disk_writer_test2"; create_directory(check); cl.add(check); for (auto original: directory_iterator(dcp)) { auto path_in_copy = path("xm") / original.path().filename(); auto path_in_check = check / original.path().filename(); int const r = system(fmt::format("e2cp {}:{} {}", partition.string(), path_in_copy.string(), path_in_check.string()).c_str()); BOOST_CHECK_EQUAL(r, 0); check_file(original.path(), path_in_check); } cl.run(); } BOOST_AUTO_TEST_CASE (disk_writer_test3) { using namespace boost::filesystem; using namespace boost::process; remove_all("build/test/disk_writer_test3.disk"); remove_all("build/test/disk_writer_test3.partition"); remove_all("build/test/disk_writer_test3"); Cleanup cl; path const disk = "build/test/disk_writer_test3.disk"; path const partition = "build/test/disk_writer_test3.partition"; cl.add(disk); cl.add(partition); /* Using empty files here still triggers the bug and is much quicker than using random data */ make_empty_file(disk, 31043616768LL); make_empty_file(partition, 31043571712LL); vector const dcps = { TestPaths::private_data() / "xm", TestPaths::private_data() / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV" }; dcpomatic::write(dcps, disk.string(), partition.string(), nullptr); BOOST_CHECK_EQUAL(system("/sbin/e2fsck -fn build/test/disk_writer_test3.partition"), 0); path const check = "build/test/disk_writer_test3"; create_directory(check); cl.add(check); for (auto dcp: dcps) { for (auto original: directory_iterator(dcp)) { auto path_in_copy = dcp.filename() / original.path().filename(); auto path_in_check = check / original.path().filename(); int const r = system(fmt::format("e2cp {}:{} {}", partition.string(), path_in_copy.string(), path_in_check.string()).c_str()); BOOST_CHECK_EQUAL(r, 0); check_file(original.path(), path_in_check); } } cl.run(); }