diff options
Diffstat (limited to '')
-rw-r--r-- | src/seastar/tests/unit/file_io_test.cc | 756 |
1 files changed, 756 insertions, 0 deletions
diff --git a/src/seastar/tests/unit/file_io_test.cc b/src/seastar/tests/unit/file_io_test.cc new file mode 100644 index 000000000..abece7c6b --- /dev/null +++ b/src/seastar/tests/unit/file_io_test.cc @@ -0,0 +1,756 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright (C) 2014-2015 Cloudius Systems, Ltd. + */ + +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> +#include <seastar/testing/test_runner.hh> + +#include <seastar/core/seastar.hh> +#include <seastar/core/semaphore.hh> +#include <seastar/core/condition-variable.hh> +#include <seastar/core/file.hh> +#include <seastar/core/layered_file.hh> +#include <seastar/core/thread.hh> +#include <seastar/core/stall_sampler.hh> +#include <seastar/core/aligned_buffer.hh> +#include <seastar/util/tmp_file.hh> +#include <seastar/util/alloc_failure_injector.hh> + +#include <boost/range/adaptor/transformed.hpp> +#include <iostream> +#include <sys/statfs.h> + +#include "core/file-impl.hh" + +using namespace seastar; +namespace fs = std::filesystem; + +SEASTAR_TEST_CASE(open_flags_test) { + open_flags flags = open_flags::rw | open_flags::create | open_flags::exclusive; + BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) == + (std::underlying_type_t<open_flags>(open_flags::rw) | + std::underlying_type_t<open_flags>(open_flags::create) | + std::underlying_type_t<open_flags>(open_flags::exclusive))); + + open_flags mask = open_flags::create | open_flags::exclusive; + BOOST_REQUIRE((flags & mask) == mask); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(access_flags_test) { + access_flags flags = access_flags::read | access_flags::write | access_flags::execute; + BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) == + (std::underlying_type_t<open_flags>(access_flags::read) | + std::underlying_type_t<open_flags>(access_flags::write) | + std::underlying_type_t<open_flags>(access_flags::execute))); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(file_exists_test) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + f.close().get(); + auto exists = file_exists(filename).get0(); + BOOST_REQUIRE(exists); + remove_file(filename).get(); + exists = file_exists(filename).get0(); + BOOST_REQUIRE(!exists); + }); +} + +SEASTAR_TEST_CASE(handle_bad_alloc_test) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + f.close().get(); + bool exists = false; + memory::with_allocation_failures([&] { + exists = file_exists(filename).get0(); + }); + BOOST_REQUIRE(exists); + }); +} + +SEASTAR_TEST_CASE(file_access_test) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + f.close().get(); + auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0(); + BOOST_REQUIRE(is_accessible); + }); +} + +struct file_test { + file_test(file&& f) : f(std::move(f)) {} + file f; + semaphore sem = { 0 }; + semaphore par = { 1000 }; +}; + +SEASTAR_TEST_CASE(test1) { + // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB). + return tmp_dir::do_with([] (tmp_dir& t) { + static constexpr auto max = 10000; + sstring filename = (t.get_path() / "testfile.tmp").native(); + return open_file_dma(filename, open_flags::rw | open_flags::create).then([filename] (file f) { + auto ft = new file_test{std::move(f)}; + for (size_t i = 0; i < max; ++i) { + // Don't wait for future, use semaphore to signal when done instead. + (void)ft->par.wait().then([ft, i] { + auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); + std::fill(wbuf.get(), wbuf.get() + 4096, i); + auto wb = wbuf.get(); + (void)ft->f.dma_write(i * 4096, wb, 4096).then( + [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable { + BOOST_REQUIRE(ret == 4096); + auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); + auto rb = rbuf.get(); + (void)ft->f.dma_read(i * 4096, rb, 4096).then( + [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable { + BOOST_REQUIRE(ret == 4096); + BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get())); + ft->sem.signal(1); + ft->par.signal(); + }); + }); + }); + } + return ft->sem.wait(max).then([ft] () mutable { + return ft->f.flush(); + }).then([ft] { + return ft->f.close(); + }).then([ft] () mutable { + delete ft; + }); + }); + }); +} + +SEASTAR_TEST_CASE(parallel_write_fsync) { + return internal::report_reactor_stalls([] { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + // Plan: open a file and write to it like crazy. In parallel fsync() it all the time. + auto fname = (t.get_path() / "testfile.tmp").native(); + auto sz = uint64_t(32*1024*1024); + auto buffer_size = 32768; + auto write_concurrency = 16; + auto fsync_every = 1024*1024; + auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly + auto written = uint64_t(0); + auto fsynced_at = uint64_t(0); + + file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0(); + // Avoid filesystem problems with size-extending operations + f.truncate(sz).get(); + + auto fsync_semaphore = semaphore(0); + auto may_write_condvar = condition_variable(); + auto fsync_thread = thread([&] { + auto fsynced = uint64_t(0); + while (fsynced < sz) { + fsync_semaphore.wait(fsync_every).get(); + fsynced_at = written; + // Signal the condition variable now so that writes proceed + // in parallel with the fsync + may_write_condvar.broadcast(); + f.flush().get(); + fsynced += fsync_every; + } + }); + + auto write_semaphore = semaphore(write_concurrency); + while (written < sz) { + write_semaphore.wait().get(); + may_write_condvar.wait([&] { + return written <= fsynced_at + max_write_ahead_of_fsync; + }).get(); + auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size); + memset(buf.get_write(), 0, buf.size()); + // Write asynchronously, signal when done. + (void)f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) { + fsync_semaphore.signal(buf.size()); + write_semaphore.signal(); + }); + written += buffer_size; + } + write_semaphore.wait(write_concurrency).get(); + + fsync_thread.join().get(); + f.close().get(); + remove_file(fname).get(); + }); + }).then([] (internal::stall_report sr) { + std::cout << "parallel_write_fsync: " << sr << "\n"; + }); +} + +SEASTAR_TEST_CASE(test_iov_max) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + static constexpr size_t buffer_size = 4096; + static constexpr size_t buffer_count = IOV_MAX * 2 + 1; + + std::vector<temporary_buffer<char>> original_buffers; + std::vector<iovec> iovecs; + for (auto i = 0u; i < buffer_count; i++) { + original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size)); + std::fill_n(original_buffers.back().get_write(), buffer_size, char(i)); + iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size }); + } + + auto filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + size_t left = buffer_size * buffer_count; + size_t position = 0; + while (left) { + auto written = f.dma_write(position, iovecs).get0(); + iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size); + assert(written % buffer_size == 0); + position += written; + left -= written; + } + + BOOST_CHECK(iovecs.empty()); + + std::vector<temporary_buffer<char>> read_buffers; + for (auto i = 0u; i < buffer_count; i++) { + read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size)); + std::fill_n(read_buffers.back().get_write(), buffer_size, char(0)); + iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size }); + } + + left = buffer_size * buffer_count; + position = 0; + while (left) { + auto read = f.dma_read(position, iovecs).get0(); + iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size); + assert(read % buffer_size == 0); + position += read; + left -= read; + } + + for (auto i = 0u; i < buffer_count; i++) { + BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(), + read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size())); + } + + f.close().get(); + }); +} + +SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) { + auto buf = temporary_buffer<char>::aligned(4096, 4096); + + auto iovec_equal = [] (const iovec& a, const iovec& b) { + return a.iov_base == b.iov_base && a.iov_len == b.iov_len; + }; + + { // Single fragment, sanitize is noop + auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } }; + auto actual_iovecs = original_iovecs; + auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); + BOOST_CHECK_EQUAL(actual_length, 4096); + BOOST_CHECK_EQUAL(actual_iovecs.size(), 1); + BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back())); + } + + { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers + auto original_iovecs = std::vector<iovec>{}; + for (auto i = 0u; i < IOV_MAX + 7; i++) { + original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u }); + } + auto actual_iovecs = original_iovecs; + auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); + BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); + BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1); + + original_iovecs.resize(IOV_MAX - 1); + BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), + actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); + } + + { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers + auto original_iovecs = std::vector<iovec>{}; + for (auto i = 0u; i < IOV_MAX + 7; i++) { + original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u }); + } + auto actual_iovecs = original_iovecs; + auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); + BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); + BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX); + + original_iovecs.resize(IOV_MAX); + original_iovecs.back().iov_len = 512; + BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), + actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); + } + + { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers + auto original_iovecs = std::vector<iovec>{}; + for (auto i = 0u; i < IOV_MAX + 8; i++) { + original_iovecs.emplace_back(iovec { buf.get_write(), 512 }); + } + auto actual_iovecs = original_iovecs; + auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); + BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); + BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX); + + original_iovecs.resize(IOV_MAX); + BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), + actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); + } +} + +SEASTAR_TEST_CASE(test_chmod) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create; + sstring filename = (t.get_path() / "testfile.tmp").native(); + if (file_exists(filename).get0()) { + remove_file(filename).get(); + } + + auto orig_umask = umask(0); + + // test default_file_permissions + auto f = open_file_dma(filename, oflags).get0(); + f.close().get(); + auto sd = file_stat(filename).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); + + // test chmod with new_permissions + auto new_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; + BOOST_REQUIRE(new_permissions != file_permissions::default_file_permissions); + BOOST_REQUIRE(file_exists(filename).get0()); + chmod(filename, new_permissions).get(); + sd = file_stat(filename).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(new_permissions)); + remove_file(filename).get(); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_open_file_dma_permissions) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create; + sstring filename = (t.get_path() / "testfile.tmp").native(); + if (file_exists(filename).get0()) { + remove_file(filename).get(); + } + + auto orig_umask = umask(0); + + // test default_file_permissions + auto f = open_file_dma(filename, oflags).get0(); + f.close().get(); + auto sd = file_stat(filename).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); + remove_file(filename).get(); + + // test options.create_permissions + auto options = file_open_options(); + options.create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; + BOOST_REQUIRE(options.create_permissions != file_permissions::default_file_permissions); + f = open_file_dma(filename, oflags, options).get0(); + f.close().get(); + sd = file_stat(filename).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(options.create_permissions)); + remove_file(filename).get(); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_make_directory_permissions) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring dirname = (t.get_path() / "testdir.tmp").native(); + auto orig_umask = umask(0); + + // test default_dir_permissions with make_directory + make_directory(dirname).get(); + auto sd = file_stat(dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + remove_file(dirname).get(); + + // test make_directory + auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; + BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); + make_directory(dirname, create_permissions).get(); + sd = file_stat(dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); + remove_file(dirname).get(); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_touch_directory_permissions) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring dirname = (t.get_path() / "testdir.tmp").native(); + auto orig_umask = umask(0); + + // test default_dir_permissions with touch_directory + touch_directory(dirname).get(); + auto sd = file_stat(dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + remove_file(dirname).get(); + + // test touch_directory, dir creation + auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; + BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); + BOOST_REQUIRE(!file_exists(dirname).get0()); + touch_directory(dirname, create_permissions).get(); + sd = file_stat(dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); + + // test touch_directory of existing dir, dir mode need not change + BOOST_REQUIRE(file_exists(dirname).get0()); + touch_directory(dirname, file_permissions::default_dir_permissions).get(); + sd = file_stat(dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); + remove_file(dirname).get(); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring base_dirname = (t.get_path() / "testbasedir.tmp").native(); + sstring dirpath = base_dirname + "/" + "testsubdir.tmp"; + if (file_exists(dirpath).get0()) { + remove_file(dirpath).get(); + } + if (file_exists(base_dirname).get0()) { + remove_file(base_dirname).get(); + } + + auto orig_umask = umask(0); + + // test default_dir_permissions with recursive_touch_directory + recursive_touch_directory(dirpath).get(); + auto sd = file_stat(base_dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + sd = file_stat(dirpath).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + remove_file(dirpath).get(); + + // test recursive_touch_directory, dir creation + auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; + BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); + BOOST_REQUIRE(file_exists(base_dirname).get0()); + BOOST_REQUIRE(!file_exists(dirpath).get0()); + recursive_touch_directory(dirpath, create_permissions).get(); + sd = file_stat(base_dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + sd = file_stat(dirpath).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); + + // test recursive_touch_directory of existing dir, dir mode need not change + BOOST_REQUIRE(file_exists(dirpath).get0()); + recursive_touch_directory(dirpath, file_permissions::default_dir_permissions).get(); + sd = file_stat(base_dirname).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); + sd = file_stat(dirpath).get0(); + BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); + remove_file(dirpath).get(); + remove_file(base_dirname).get(); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_file_stat_method) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create; + sstring filename = (t.get_path() / "testfile.tmp").native(); + + auto orig_umask = umask(0); + + auto f = open_file_dma(filename, oflags).get0(); + auto st = f.stat().get0(); + f.close().get(); + BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); + + umask(orig_umask); + }); +} + + +class test_layered_file : public layered_file_impl { +public: + explicit test_layered_file(file f) : layered_file_impl(std::move(f)) {} + virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len, const io_priority_class& pc) override { + abort(); + } + virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override { + abort(); + } + virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override { + abort(); + } + virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override { + abort(); + } + virtual future<> flush(void) override { + abort(); + } + virtual future<struct stat> stat(void) override { + abort(); + } + virtual future<> truncate(uint64_t length) override { + abort(); + } + virtual future<> discard(uint64_t offset, uint64_t length) override { + abort(); + } + virtual future<> allocate(uint64_t position, uint64_t length) override { + abort(); + } + virtual future<uint64_t> size(void) override { + abort(); + } + virtual future<> close() override { + abort(); + } + virtual std::unique_ptr<file_handle_impl> dup() override { + abort(); + } + virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override { + abort(); + } + virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override { + abort(); + } +}; + +SEASTAR_TEST_CASE(test_underlying_file) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create; + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, oflags).get0(); + auto lf = file(make_shared<test_layered_file>(f)); + BOOST_CHECK_EQUAL(f.memory_dma_alignment(), lf.memory_dma_alignment()); + BOOST_CHECK_EQUAL(f.disk_read_dma_alignment(), lf.disk_read_dma_alignment()); + BOOST_CHECK_EQUAL(f.disk_write_dma_alignment(), lf.disk_write_dma_alignment()); + f.close().get(); + }); +} + +SEASTAR_TEST_CASE(test_file_stat_method_with_file) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create | open_flags::truncate; + sstring filename = (t.get_path() / "testfile.tmp").native(); + file ref; + + auto orig_umask = umask(0); + + auto st = with_file(open_file_dma(filename, oflags), [&ref] (file& f) { + // make a copy of f to verify f is auto-closed when `with_file` returns. + ref = f; + return f.stat(); + }).get0(); + BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); + + // verify that the file was auto-closed + BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_open_error_with_file) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto open_file = [&t] (bool do_open) { + auto oflags = open_flags::ro; + sstring filename = (t.get_path() / "testfile.tmp").native(); + if (do_open) { + return open_file_dma(filename, oflags); + } else { + throw std::runtime_error("expected exception"); + } + }; + bool got_exception = false; + + BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file& f) { + BOOST_REQUIRE(false); + }).handle_exception_type([&got_exception] (const std::system_error& e) { + got_exception = true; + BOOST_REQUIRE(e.code().value() == ENOENT); + }).get()); + BOOST_REQUIRE(got_exception); + + got_exception = false; + BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file& f) { + BOOST_REQUIRE(false); + }).handle_exception_type([&got_exception] (const std::runtime_error& e) { + got_exception = true; + }).get(), std::runtime_error); + BOOST_REQUIRE(!got_exception); + }); +} + +SEASTAR_TEST_CASE(test_with_file_close_on_failure) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create | open_flags::truncate; + sstring filename = (t.get_path() / "testfile.tmp").native(); + + auto orig_umask = umask(0); + + // error-free case + auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) { + return f; + }).get0(); + auto st = ref.stat().get0(); + ref.close().get(); + BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); + + // close-on-error case + BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) { + ref = f; + throw std::runtime_error("expected exception"); + }).get(), std::runtime_error); + + // verify that file was auto-closed on error + BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error); + + umask(orig_umask); + }); +} + +namespace seastar { + extern bool aio_nowait_supported; +} + +SEASTAR_TEST_CASE(test_nowait_flag_correctness) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + auto oflags = open_flags::rw | open_flags::create; + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto is_tmpfs = [&] (sstring filename) { + struct ::statfs buf; + int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro)); + assert(fd != -1); + auto r = ::fstatfs(fd, &buf); + if (r == -1) { + return false; + } + return buf.f_type == 0x01021994; // TMPFS_MAGIC + }; + + if (!seastar::aio_nowait_supported) { + BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system"); + return; + } + + auto f = open_file_dma(filename, oflags).get0(); + auto close_f = defer([&] { f.close().get(); }); + + if (is_tmpfs(filename)) { + BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes"); + return; + } + + for (auto i = 0; i < 10; i++) { + auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); + std::fill(wbuf.get(), wbuf.get() + 4096, i); + auto wb = wbuf.get(); + f.dma_write(i * 4096, wb, 4096).get(); + f.flush().get0(); + } + }); +} + +SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto oflags = open_flags::rw | open_flags::create; + auto f = open_file_dma(filename, oflags).get0(); + }); +} + +SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096); + std::fill(buf.get(), buf.get() + 4096, 0); + + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + f.dma_write(0, buf.get(), 4096).get(); + }); +} + +SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + sstring filename = (t.get_path() / "testfile.tmp").native(); + auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096); + std::fill(buf.get(), buf.get() + 4096, 0); + + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + f.dma_write(0, buf.get(), 4096).get(); + f.flush().get0(); + f.close().get(); + + f = open_file_dma(filename, open_flags::rw).get0(); + f.dma_read(0, buf.get(), 4096).get(); + }); +} + +SEASTAR_TEST_CASE(test_dma_iovec) { + return tmp_dir::do_with_thread([] (tmp_dir& t) { + static constexpr size_t alignment = 4096; + auto wbuf = allocate_aligned_buffer<char>(alignment, alignment); + size_t size = 1234; + std::fill_n(wbuf.get(), alignment, char(0)); + std::fill_n(wbuf.get(), size, char(42)); + std::vector<iovec> iovecs; + + auto filename = (t.get_path() / "testfile.tmp").native(); + auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); + iovecs.push_back(iovec{ wbuf.get(), alignment }); + auto count = f.dma_write(0, iovecs).get0(); + BOOST_REQUIRE_EQUAL(count, alignment); + f.truncate(size).get(); + f.close().get(); + + auto rbuf = allocate_aligned_buffer<char>(alignment, alignment); + + // this tests the posix_file_impl + f = open_file_dma(filename, open_flags::ro).get0(); + std::fill_n(rbuf.get(), alignment, char(0)); + iovecs.clear(); + iovecs.push_back(iovec{ rbuf.get(), alignment }); + count = f.dma_read(0, iovecs).get0(); + BOOST_REQUIRE_EQUAL(count, size); + + BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment)); + + // this tests the append_challenged_posix_file_impl + f = open_file_dma(filename, open_flags::rw).get0(); + std::fill_n(rbuf.get(), alignment, char(0)); + iovecs.clear(); + iovecs.push_back(iovec{ rbuf.get(), alignment }); + count = f.dma_read(0, iovecs).get0(); + BOOST_REQUIRE_EQUAL(count, size); + + BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment)); + }); +} |