]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/tests/unit/file_io_test.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / seastar / tests / unit / file_io_test.cc
CommitLineData
11fdf7f2
TL
1/*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18/*
19 * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
20 */
21
22#include <seastar/testing/test_case.hh>
9f95a23c 23#include <seastar/testing/thread_test_case.hh>
f67539c2 24#include <seastar/testing/test_runner.hh>
11fdf7f2 25
f67539c2 26#include <seastar/core/seastar.hh>
11fdf7f2
TL
27#include <seastar/core/semaphore.hh>
28#include <seastar/core/condition-variable.hh>
29#include <seastar/core/file.hh>
f67539c2 30#include <seastar/core/layered_file.hh>
11fdf7f2
TL
31#include <seastar/core/thread.hh>
32#include <seastar/core/stall_sampler.hh>
f67539c2 33#include <seastar/core/aligned_buffer.hh>
20effc67 34#include <seastar/core/io_intent.hh>
f67539c2
TL
35#include <seastar/util/tmp_file.hh>
36#include <seastar/util/alloc_failure_injector.hh>
20effc67 37#include <seastar/util/closeable.hh>
1e59de90
TL
38#include <seastar/util/internal/magic.hh>
39#include <seastar/util/internal/iovec_utils.hh>
f67539c2 40
9f95a23c 41#include <boost/range/adaptor/transformed.hpp>
11fdf7f2 42#include <iostream>
f67539c2 43#include <sys/statfs.h>
20effc67 44#include <fcntl.h>
11fdf7f2 45
9f95a23c
TL
46#include "core/file-impl.hh"
47
11fdf7f2 48using namespace seastar;
f67539c2 49namespace fs = std::filesystem;
11fdf7f2
TL
50
51SEASTAR_TEST_CASE(open_flags_test) {
52 open_flags flags = open_flags::rw | open_flags::create | open_flags::exclusive;
53 BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
54 (std::underlying_type_t<open_flags>(open_flags::rw) |
55 std::underlying_type_t<open_flags>(open_flags::create) |
56 std::underlying_type_t<open_flags>(open_flags::exclusive)));
57
58 open_flags mask = open_flags::create | open_flags::exclusive;
59 BOOST_REQUIRE((flags & mask) == mask);
60 return make_ready_future<>();
61}
62
9f95a23c
TL
63SEASTAR_TEST_CASE(access_flags_test) {
64 access_flags flags = access_flags::read | access_flags::write | access_flags::execute;
65 BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
66 (std::underlying_type_t<open_flags>(access_flags::read) |
67 std::underlying_type_t<open_flags>(access_flags::write) |
68 std::underlying_type_t<open_flags>(access_flags::execute)));
69 return make_ready_future<>();
70}
71
72SEASTAR_TEST_CASE(file_exists_test) {
f67539c2
TL
73 return tmp_dir::do_with_thread([] (tmp_dir& t) {
74 sstring filename = (t.get_path() / "testfile.tmp").native();
9f95a23c
TL
75 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
76 f.close().get();
77 auto exists = file_exists(filename).get0();
78 BOOST_REQUIRE(exists);
79 remove_file(filename).get();
80 exists = file_exists(filename).get0();
81 BOOST_REQUIRE(!exists);
82 });
83}
84
f67539c2
TL
85SEASTAR_TEST_CASE(handle_bad_alloc_test) {
86 return tmp_dir::do_with_thread([] (tmp_dir& t) {
87 sstring filename = (t.get_path() / "testfile.tmp").native();
88 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
89 f.close().get();
90 bool exists = false;
91 memory::with_allocation_failures([&] {
92 exists = file_exists(filename).get0();
93 });
94 BOOST_REQUIRE(exists);
95 });
96}
97
9f95a23c 98SEASTAR_TEST_CASE(file_access_test) {
f67539c2
TL
99 return tmp_dir::do_with_thread([] (tmp_dir& t) {
100 sstring filename = (t.get_path() / "testfile.tmp").native();
9f95a23c
TL
101 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
102 f.close().get();
103 auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0();
104 BOOST_REQUIRE(is_accessible);
9f95a23c
TL
105 });
106}
107
11fdf7f2
TL
108struct file_test {
109 file_test(file&& f) : f(std::move(f)) {}
110 file f;
111 semaphore sem = { 0 };
112 semaphore par = { 1000 };
113};
114
115SEASTAR_TEST_CASE(test1) {
116 // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
f67539c2 117 return tmp_dir::do_with([] (tmp_dir& t) {
11fdf7f2 118 static constexpr auto max = 10000;
f67539c2
TL
119 sstring filename = (t.get_path() / "testfile.tmp").native();
120 return open_file_dma(filename, open_flags::rw | open_flags::create).then([filename] (file f) {
11fdf7f2
TL
121 auto ft = new file_test{std::move(f)};
122 for (size_t i = 0; i < max; ++i) {
9f95a23c
TL
123 // Don't wait for future, use semaphore to signal when done instead.
124 (void)ft->par.wait().then([ft, i] {
11fdf7f2
TL
125 auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
126 std::fill(wbuf.get(), wbuf.get() + 4096, i);
127 auto wb = wbuf.get();
9f95a23c 128 (void)ft->f.dma_write(i * 4096, wb, 4096).then(
11fdf7f2
TL
129 [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable {
130 BOOST_REQUIRE(ret == 4096);
131 auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
132 auto rb = rbuf.get();
9f95a23c 133 (void)ft->f.dma_read(i * 4096, rb, 4096).then(
11fdf7f2
TL
134 [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable {
135 BOOST_REQUIRE(ret == 4096);
136 BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get()));
137 ft->sem.signal(1);
138 ft->par.signal();
139 });
140 });
141 });
142 }
143 return ft->sem.wait(max).then([ft] () mutable {
144 return ft->f.flush();
145 }).then([ft] {
146 return ft->f.close();
147 }).then([ft] () mutable {
11fdf7f2
TL
148 delete ft;
149 });
150 });
f67539c2 151 });
11fdf7f2
TL
152}
153
154SEASTAR_TEST_CASE(parallel_write_fsync) {
155 return internal::report_reactor_stalls([] {
f67539c2 156 return tmp_dir::do_with_thread([] (tmp_dir& t) {
11fdf7f2 157 // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
f67539c2 158 auto fname = (t.get_path() / "testfile.tmp").native();
11fdf7f2
TL
159 auto sz = uint64_t(32*1024*1024);
160 auto buffer_size = 32768;
161 auto write_concurrency = 16;
162 auto fsync_every = 1024*1024;
163 auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly
164 auto written = uint64_t(0);
165 auto fsynced_at = uint64_t(0);
166
167 file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
20effc67 168 auto close_f = deferred_close(f);
11fdf7f2
TL
169 // Avoid filesystem problems with size-extending operations
170 f.truncate(sz).get();
171
172 auto fsync_semaphore = semaphore(0);
173 auto may_write_condvar = condition_variable();
174 auto fsync_thread = thread([&] {
175 auto fsynced = uint64_t(0);
176 while (fsynced < sz) {
177 fsync_semaphore.wait(fsync_every).get();
178 fsynced_at = written;
179 // Signal the condition variable now so that writes proceed
180 // in parallel with the fsync
181 may_write_condvar.broadcast();
182 f.flush().get();
183 fsynced += fsync_every;
184 }
185 });
186
187 auto write_semaphore = semaphore(write_concurrency);
188 while (written < sz) {
189 write_semaphore.wait().get();
190 may_write_condvar.wait([&] {
191 return written <= fsynced_at + max_write_ahead_of_fsync;
192 }).get();
193 auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size);
f67539c2 194 memset(buf.get_write(), 0, buf.size());
9f95a23c
TL
195 // Write asynchronously, signal when done.
196 (void)f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) {
11fdf7f2
TL
197 fsync_semaphore.signal(buf.size());
198 write_semaphore.signal();
199 });
200 written += buffer_size;
201 }
202 write_semaphore.wait(write_concurrency).get();
203
204 fsync_thread.join().get();
20effc67 205 close_f.close_now();
11fdf7f2
TL
206 remove_file(fname).get();
207 });
208 }).then([] (internal::stall_report sr) {
209 std::cout << "parallel_write_fsync: " << sr << "\n";
210 });
211}
9f95a23c 212
f67539c2
TL
213SEASTAR_TEST_CASE(test_iov_max) {
214 return tmp_dir::do_with_thread([] (tmp_dir& t) {
9f95a23c
TL
215 static constexpr size_t buffer_size = 4096;
216 static constexpr size_t buffer_count = IOV_MAX * 2 + 1;
217
218 std::vector<temporary_buffer<char>> original_buffers;
219 std::vector<iovec> iovecs;
220 for (auto i = 0u; i < buffer_count; i++) {
221 original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
222 std::fill_n(original_buffers.back().get_write(), buffer_size, char(i));
223 iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size });
224 }
225
f67539c2
TL
226 auto filename = (t.get_path() / "testfile.tmp").native();
227 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
20effc67 228 auto close_f = deferred_close(f);
9f95a23c
TL
229 size_t left = buffer_size * buffer_count;
230 size_t position = 0;
231 while (left) {
232 auto written = f.dma_write(position, iovecs).get0();
233 iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size);
234 assert(written % buffer_size == 0);
235 position += written;
236 left -= written;
237 }
238
239 BOOST_CHECK(iovecs.empty());
240
241 std::vector<temporary_buffer<char>> read_buffers;
242 for (auto i = 0u; i < buffer_count; i++) {
243 read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
244 std::fill_n(read_buffers.back().get_write(), buffer_size, char(0));
245 iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size });
246 }
247
248 left = buffer_size * buffer_count;
249 position = 0;
250 while (left) {
251 auto read = f.dma_read(position, iovecs).get0();
252 iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size);
253 assert(read % buffer_size == 0);
254 position += read;
255 left -= read;
256 }
257
258 for (auto i = 0u; i < buffer_count; i++) {
259 BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(),
260 read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size()));
261 }
f67539c2 262 });
9f95a23c
TL
263}
264
265SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) {
266 auto buf = temporary_buffer<char>::aligned(4096, 4096);
267
268 auto iovec_equal = [] (const iovec& a, const iovec& b) {
269 return a.iov_base == b.iov_base && a.iov_len == b.iov_len;
270 };
271
272 { // Single fragment, sanitize is noop
273 auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } };
274 auto actual_iovecs = original_iovecs;
275 auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
276 BOOST_CHECK_EQUAL(actual_length, 4096);
277 BOOST_CHECK_EQUAL(actual_iovecs.size(), 1);
278 BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back()));
279 }
280
281 { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
282 auto original_iovecs = std::vector<iovec>{};
283 for (auto i = 0u; i < IOV_MAX + 7; i++) {
284 original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u });
285 }
286 auto actual_iovecs = original_iovecs;
287 auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
288 BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
289 BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1);
290
291 original_iovecs.resize(IOV_MAX - 1);
292 BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
293 actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
294 }
295
296 { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers
297 auto original_iovecs = std::vector<iovec>{};
298 for (auto i = 0u; i < IOV_MAX + 7; i++) {
299 original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u });
300 }
301 auto actual_iovecs = original_iovecs;
302 auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
303 BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
304 BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
305
306 original_iovecs.resize(IOV_MAX);
307 original_iovecs.back().iov_len = 512;
308 BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
309 actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
310 }
311
312 { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
313 auto original_iovecs = std::vector<iovec>{};
314 for (auto i = 0u; i < IOV_MAX + 8; i++) {
315 original_iovecs.emplace_back(iovec { buf.get_write(), 512 });
316 }
317 auto actual_iovecs = original_iovecs;
318 auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
319 BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
320 BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
321
322 original_iovecs.resize(IOV_MAX);
323 BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
324 actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
325 }
326}
327
f67539c2
TL
328SEASTAR_TEST_CASE(test_chmod) {
329 return tmp_dir::do_with_thread([] (tmp_dir& t) {
9f95a23c 330 auto oflags = open_flags::rw | open_flags::create;
f67539c2 331 sstring filename = (t.get_path() / "testfile.tmp").native();
9f95a23c
TL
332 if (file_exists(filename).get0()) {
333 remove_file(filename).get();
334 }
335
336 auto orig_umask = umask(0);
337
338 // test default_file_permissions
339 auto f = open_file_dma(filename, oflags).get0();
340 f.close().get();
341 auto sd = file_stat(filename).get0();
342 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
343
344 // test chmod with new_permissions
345 auto new_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
346 BOOST_REQUIRE(new_permissions != file_permissions::default_file_permissions);
347 BOOST_REQUIRE(file_exists(filename).get0());
348 chmod(filename, new_permissions).get();
349 sd = file_stat(filename).get0();
350 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(new_permissions));
351 remove_file(filename).get();
352
353 umask(orig_umask);
f67539c2 354 });
9f95a23c
TL
355}
356
f67539c2
TL
357SEASTAR_TEST_CASE(test_open_file_dma_permissions) {
358 return tmp_dir::do_with_thread([] (tmp_dir& t) {
9f95a23c 359 auto oflags = open_flags::rw | open_flags::create;
f67539c2 360 sstring filename = (t.get_path() / "testfile.tmp").native();
9f95a23c
TL
361 if (file_exists(filename).get0()) {
362 remove_file(filename).get();
363 }
364
365 auto orig_umask = umask(0);
366
367 // test default_file_permissions
368 auto f = open_file_dma(filename, oflags).get0();
369 f.close().get();
370 auto sd = file_stat(filename).get0();
371 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
372 remove_file(filename).get();
373
374 // test options.create_permissions
375 auto options = file_open_options();
376 options.create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
377 BOOST_REQUIRE(options.create_permissions != file_permissions::default_file_permissions);
378 f = open_file_dma(filename, oflags, options).get0();
379 f.close().get();
380 sd = file_stat(filename).get0();
381 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(options.create_permissions));
382 remove_file(filename).get();
383
384 umask(orig_umask);
f67539c2 385 });
9f95a23c
TL
386}
387
f67539c2
TL
388SEASTAR_TEST_CASE(test_make_directory_permissions) {
389 return tmp_dir::do_with_thread([] (tmp_dir& t) {
390 sstring dirname = (t.get_path() / "testdir.tmp").native();
9f95a23c
TL
391 auto orig_umask = umask(0);
392
393 // test default_dir_permissions with make_directory
394 make_directory(dirname).get();
395 auto sd = file_stat(dirname).get0();
396 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
397 remove_file(dirname).get();
398
399 // test make_directory
400 auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
401 BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
402 make_directory(dirname, create_permissions).get();
403 sd = file_stat(dirname).get0();
404 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
405 remove_file(dirname).get();
406
407 umask(orig_umask);
f67539c2 408 });
9f95a23c
TL
409}
410
f67539c2
TL
411SEASTAR_TEST_CASE(test_touch_directory_permissions) {
412 return tmp_dir::do_with_thread([] (tmp_dir& t) {
413 sstring dirname = (t.get_path() / "testdir.tmp").native();
9f95a23c
TL
414 auto orig_umask = umask(0);
415
416 // test default_dir_permissions with touch_directory
417 touch_directory(dirname).get();
418 auto sd = file_stat(dirname).get0();
419 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
420 remove_file(dirname).get();
421
422 // test touch_directory, dir creation
423 auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
424 BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
425 BOOST_REQUIRE(!file_exists(dirname).get0());
426 touch_directory(dirname, create_permissions).get();
427 sd = file_stat(dirname).get0();
428 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
429
430 // test touch_directory of existing dir, dir mode need not change
431 BOOST_REQUIRE(file_exists(dirname).get0());
432 touch_directory(dirname, file_permissions::default_dir_permissions).get();
433 sd = file_stat(dirname).get0();
434 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
435 remove_file(dirname).get();
436
437 umask(orig_umask);
f67539c2 438 });
9f95a23c
TL
439}
440
f67539c2
TL
441SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions) {
442 return tmp_dir::do_with_thread([] (tmp_dir& t) {
443 sstring base_dirname = (t.get_path() / "testbasedir.tmp").native();
444 sstring dirpath = base_dirname + "/" + "testsubdir.tmp";
9f95a23c
TL
445 if (file_exists(dirpath).get0()) {
446 remove_file(dirpath).get();
447 }
448 if (file_exists(base_dirname).get0()) {
449 remove_file(base_dirname).get();
450 }
451
452 auto orig_umask = umask(0);
453
454 // test default_dir_permissions with recursive_touch_directory
455 recursive_touch_directory(dirpath).get();
456 auto sd = file_stat(base_dirname).get0();
457 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
458 sd = file_stat(dirpath).get0();
459 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
460 remove_file(dirpath).get();
461
462 // test recursive_touch_directory, dir creation
463 auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
464 BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
465 BOOST_REQUIRE(file_exists(base_dirname).get0());
466 BOOST_REQUIRE(!file_exists(dirpath).get0());
467 recursive_touch_directory(dirpath, create_permissions).get();
468 sd = file_stat(base_dirname).get0();
469 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
470 sd = file_stat(dirpath).get0();
471 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
472
473 // test recursive_touch_directory of existing dir, dir mode need not change
474 BOOST_REQUIRE(file_exists(dirpath).get0());
475 recursive_touch_directory(dirpath, file_permissions::default_dir_permissions).get();
476 sd = file_stat(base_dirname).get0();
477 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
478 sd = file_stat(dirpath).get0();
479 BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
480 remove_file(dirpath).get();
481 remove_file(base_dirname).get();
482
483 umask(orig_umask);
f67539c2
TL
484 });
485}
486
487SEASTAR_TEST_CASE(test_file_stat_method) {
488 return tmp_dir::do_with_thread([] (tmp_dir& t) {
489 auto oflags = open_flags::rw | open_flags::create;
490 sstring filename = (t.get_path() / "testfile.tmp").native();
491
492 auto orig_umask = umask(0);
493
494 auto f = open_file_dma(filename, oflags).get0();
20effc67 495 auto close_f = deferred_close(f);
f67539c2 496 auto st = f.stat().get0();
f67539c2
TL
497 BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
498
499 umask(orig_umask);
500 });
501}
502
20effc67
TL
503SEASTAR_TEST_CASE(test_file_write_lifetime_method) {
504 return tmp_dir::do_with_thread([] (tmp_dir& t) {
505 auto oflags = open_flags::rw | open_flags::create;
506 sstring filename = (t.get_path() / "testfile.tmp").native();
507
508 auto f1 = open_file_dma(filename, oflags).get0();
509 auto close_f1 = deferred_close(f1);
510 auto f2 = open_file_dma(filename, oflags).get0();
511 auto close_f2 = deferred_close(f2);
512
513 // Write life time hint values
514 std::vector<uint64_t> hint_set = {RWF_WRITE_LIFE_NOT_SET,
515 RWH_WRITE_LIFE_NONE,
516 RWH_WRITE_LIFE_SHORT,
517 RWH_WRITE_LIFE_MEDIUM,
518 RWH_WRITE_LIFE_LONG,
519 RWH_WRITE_LIFE_EXTREME};
520
521 for (auto i = 0ul; i < hint_set.size(); ++i) {
522 auto hint = hint_set[i];
523
524 // Set and verify the lifetime hint of the inode
525 f1.set_inode_lifetime_hint(hint).get();
526 auto o_hint1 = f1.get_inode_lifetime_hint().get0();
527 BOOST_CHECK_EQUAL(hint, o_hint1);
20effc67
TL
528 }
529
530 // Perform invalid ops
531 uint64_t hint = RWH_WRITE_LIFE_EXTREME + 1;
532 BOOST_REQUIRE_THROW(f1.set_inode_lifetime_hint(hint).get(), std::system_error);
20effc67
TL
533 });
534}
535
536SEASTAR_TEST_CASE(test_file_fcntl) {
537 return tmp_dir::do_with_thread([] (tmp_dir& t) {
538 auto oflags = open_flags::rw | open_flags::create;
539 sstring filename = (t.get_path() / "testfile.tmp").native();
540
541 auto f = open_file_dma(filename, oflags).get0();
542 auto close_f = deferred_close(f);
543
544 // Set and verify a lease value
545 auto lease = F_WRLCK;
546 BOOST_REQUIRE(!f.fcntl(F_SETLEASE, lease).get0());
547 auto o_lease = f.fcntl(F_GETLEASE).get0();
548 BOOST_CHECK_EQUAL(lease, o_lease);
549
550 // Use _short version and test the same
551 o_lease = f.fcntl_short(F_GETLEASE).get0();
552 BOOST_CHECK_EQUAL(lease, o_lease);
553
554 // Perform invalid ops
555 BOOST_REQUIRE_THROW(f.fcntl(F_SETLEASE, (uintptr_t)~0ul).get(), std::system_error);
556 BOOST_REQUIRE_THROW(f.fcntl_short(F_SETLEASE, (uintptr_t)~0ul).get(), std::system_error);
20effc67
TL
557 });
558}
559
560SEASTAR_TEST_CASE(test_file_ioctl) {
561 return tmp_dir::do_with_thread([] (tmp_dir& t) {
562 auto oflags = open_flags::rw | open_flags::create;
563 sstring filename = (t.get_path() / "testfile.tmp").native();
564 uint64_t block_size = 0;
565
566 auto f = open_file_dma(filename, oflags).get0();
567 auto close_f = deferred_close(f);
568
569 // Issueing an FS ioctl which is applicable on regular files
570 // and can be executed as normal user
571 try {
572 BOOST_REQUIRE(!f.ioctl(FIGETBSZ, &block_size).get0());
573 BOOST_REQUIRE(block_size != 0);
574
575 // Use _short version and test the same
576 BOOST_REQUIRE(!f.ioctl_short(FIGETBSZ, &block_size).get0());
577 BOOST_REQUIRE(block_size != 0);
578 } catch (std::system_error& e) {
579 // anon_bdev filesystems do not support FIGETBSZ, and return EINVAL
580 BOOST_REQUIRE_EQUAL(e.code().value(), EINVAL);
581 }
582
583 // Perform invalid ops
584 BOOST_REQUIRE_THROW(f.ioctl(FIGETBSZ, 0ul).get(), std::system_error);
585 BOOST_REQUIRE_THROW(f.ioctl_short(FIGETBSZ, 0ul).get(), std::system_error);
586 });
587}
f67539c2
TL
588
589class test_layered_file : public layered_file_impl {
590public:
591 explicit test_layered_file(file f) : layered_file_impl(std::move(f)) {}
592 virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len, const io_priority_class& pc) override {
593 abort();
594 }
595 virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
596 abort();
597 }
598 virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override {
599 abort();
600 }
601 virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
602 abort();
603 }
604 virtual future<> flush(void) override {
605 abort();
606 }
607 virtual future<struct stat> stat(void) override {
608 abort();
609 }
610 virtual future<> truncate(uint64_t length) override {
611 abort();
612 }
613 virtual future<> discard(uint64_t offset, uint64_t length) override {
614 abort();
615 }
616 virtual future<> allocate(uint64_t position, uint64_t length) override {
617 abort();
618 }
619 virtual future<uint64_t> size(void) override {
620 abort();
621 }
622 virtual future<> close() override {
623 abort();
624 }
625 virtual std::unique_ptr<file_handle_impl> dup() override {
626 abort();
627 }
628 virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override {
629 abort();
630 }
631 virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override {
632 abort();
633 }
634};
635
636SEASTAR_TEST_CASE(test_underlying_file) {
637 return tmp_dir::do_with_thread([] (tmp_dir& t) {
638 auto oflags = open_flags::rw | open_flags::create;
639 sstring filename = (t.get_path() / "testfile.tmp").native();
640 auto f = open_file_dma(filename, oflags).get0();
20effc67 641 auto close_f = deferred_close(f);
f67539c2
TL
642 auto lf = file(make_shared<test_layered_file>(f));
643 BOOST_CHECK_EQUAL(f.memory_dma_alignment(), lf.memory_dma_alignment());
644 BOOST_CHECK_EQUAL(f.disk_read_dma_alignment(), lf.disk_read_dma_alignment());
645 BOOST_CHECK_EQUAL(f.disk_write_dma_alignment(), lf.disk_write_dma_alignment());
f67539c2
TL
646 });
647}
648
649SEASTAR_TEST_CASE(test_file_stat_method_with_file) {
650 return tmp_dir::do_with_thread([] (tmp_dir& t) {
651 auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
652 sstring filename = (t.get_path() / "testfile.tmp").native();
653 file ref;
654
655 auto orig_umask = umask(0);
656
657 auto st = with_file(open_file_dma(filename, oflags), [&ref] (file& f) {
658 // make a copy of f to verify f is auto-closed when `with_file` returns.
659 ref = f;
660 return f.stat();
661 }).get0();
662 BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
663
664 // verify that the file was auto-closed
665 BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
666
667 umask(orig_umask);
668 });
669}
670
671SEASTAR_TEST_CASE(test_open_error_with_file) {
672 return tmp_dir::do_with_thread([] (tmp_dir& t) {
673 auto open_file = [&t] (bool do_open) {
674 auto oflags = open_flags::ro;
675 sstring filename = (t.get_path() / "testfile.tmp").native();
676 if (do_open) {
677 return open_file_dma(filename, oflags);
678 } else {
679 throw std::runtime_error("expected exception");
680 }
681 };
682 bool got_exception = false;
683
684 BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file& f) {
685 BOOST_REQUIRE(false);
686 }).handle_exception_type([&got_exception] (const std::system_error& e) {
687 got_exception = true;
688 BOOST_REQUIRE(e.code().value() == ENOENT);
689 }).get());
690 BOOST_REQUIRE(got_exception);
691
692 got_exception = false;
693 BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file& f) {
694 BOOST_REQUIRE(false);
695 }).handle_exception_type([&got_exception] (const std::runtime_error& e) {
696 got_exception = true;
697 }).get(), std::runtime_error);
698 BOOST_REQUIRE(!got_exception);
699 });
700}
701
702SEASTAR_TEST_CASE(test_with_file_close_on_failure) {
703 return tmp_dir::do_with_thread([] (tmp_dir& t) {
704 auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
705 sstring filename = (t.get_path() / "testfile.tmp").native();
706
707 auto orig_umask = umask(0);
708
709 // error-free case
710 auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) {
711 return f;
712 }).get0();
713 auto st = ref.stat().get0();
714 ref.close().get();
715 BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
716
717 // close-on-error case
718 BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) {
719 ref = f;
720 throw std::runtime_error("expected exception");
721 }).get(), std::runtime_error);
722
723 // verify that file was auto-closed on error
724 BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
725
726 umask(orig_umask);
727 });
728}
729
730namespace seastar {
731 extern bool aio_nowait_supported;
732}
733
734SEASTAR_TEST_CASE(test_nowait_flag_correctness) {
735 return tmp_dir::do_with_thread([] (tmp_dir& t) {
736 auto oflags = open_flags::rw | open_flags::create;
737 sstring filename = (t.get_path() / "testfile.tmp").native();
738 auto is_tmpfs = [&] (sstring filename) {
739 struct ::statfs buf;
740 int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro));
741 assert(fd != -1);
742 auto r = ::fstatfs(fd, &buf);
743 if (r == -1) {
744 return false;
745 }
1e59de90 746 return buf.f_type == internal::fs_magic::tmpfs;
f67539c2
TL
747 };
748
749 if (!seastar::aio_nowait_supported) {
750 BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system");
751 return;
752 }
753
754 auto f = open_file_dma(filename, oflags).get0();
20effc67 755 auto close_f = deferred_close(f);
f67539c2
TL
756
757 if (is_tmpfs(filename)) {
758 BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes");
759 return;
760 }
761
762 for (auto i = 0; i < 10; i++) {
763 auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
764 std::fill(wbuf.get(), wbuf.get() + 4096, i);
765 auto wb = wbuf.get();
766 f.dma_write(i * 4096, wb, 4096).get();
767 f.flush().get0();
768 }
769 });
770}
771
772SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file) {
773 return tmp_dir::do_with_thread([] (tmp_dir& t) {
774 sstring filename = (t.get_path() / "testfile.tmp").native();
775 auto oflags = open_flags::rw | open_flags::create;
776 auto f = open_file_dma(filename, oflags).get0();
777 });
778}
779
20effc67
TL
780SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file_with_sloppy_size) {
781 return tmp_dir::do_with_thread([] (tmp_dir& t) {
782 sstring filename = (t.get_path() / "testfile.tmp").native();
783 auto oflags = open_flags::rw | open_flags::create;
784 file_open_options opt;
785 opt.sloppy_size = true;
786 auto f = open_file_dma(filename, oflags, opt).get0();
787 });
788}
789
f67539c2
TL
790SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write) {
791 return tmp_dir::do_with_thread([] (tmp_dir& t) {
792 sstring filename = (t.get_path() / "testfile.tmp").native();
793 auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
794 std::fill(buf.get(), buf.get() + 4096, 0);
795
796 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
797 f.dma_write(0, buf.get(), 4096).get();
798 });
799}
800
801SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read) {
802 return tmp_dir::do_with_thread([] (tmp_dir& t) {
803 sstring filename = (t.get_path() / "testfile.tmp").native();
804 auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
805 std::fill(buf.get(), buf.get() + 4096, 0);
806
807 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
808 f.dma_write(0, buf.get(), 4096).get();
809 f.flush().get0();
810 f.close().get();
811
812 f = open_file_dma(filename, open_flags::rw).get0();
813 f.dma_read(0, buf.get(), 4096).get();
814 });
815}
816
817SEASTAR_TEST_CASE(test_dma_iovec) {
818 return tmp_dir::do_with_thread([] (tmp_dir& t) {
819 static constexpr size_t alignment = 4096;
820 auto wbuf = allocate_aligned_buffer<char>(alignment, alignment);
821 size_t size = 1234;
822 std::fill_n(wbuf.get(), alignment, char(0));
823 std::fill_n(wbuf.get(), size, char(42));
824 std::vector<iovec> iovecs;
825
826 auto filename = (t.get_path() / "testfile.tmp").native();
827 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
828 iovecs.push_back(iovec{ wbuf.get(), alignment });
829 auto count = f.dma_write(0, iovecs).get0();
830 BOOST_REQUIRE_EQUAL(count, alignment);
831 f.truncate(size).get();
832 f.close().get();
833
834 auto rbuf = allocate_aligned_buffer<char>(alignment, alignment);
835
836 // this tests the posix_file_impl
837 f = open_file_dma(filename, open_flags::ro).get0();
838 std::fill_n(rbuf.get(), alignment, char(0));
839 iovecs.clear();
840 iovecs.push_back(iovec{ rbuf.get(), alignment });
841 count = f.dma_read(0, iovecs).get0();
842 BOOST_REQUIRE_EQUAL(count, size);
843
844 BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
845
846 // this tests the append_challenged_posix_file_impl
847 f = open_file_dma(filename, open_flags::rw).get0();
848 std::fill_n(rbuf.get(), alignment, char(0));
849 iovecs.clear();
850 iovecs.push_back(iovec{ rbuf.get(), alignment });
851 count = f.dma_read(0, iovecs).get0();
852 BOOST_REQUIRE_EQUAL(count, size);
853
854 BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
855 });
9f95a23c 856}
20effc67
TL
857
858SEASTAR_TEST_CASE(test_intent) {
859 return tmp_dir::do_with_thread([] (tmp_dir& t) {
860 sstring filename = (t.get_path() / "testfile.tmp").native();
861 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
862 auto buf = allocate_aligned_buffer<unsigned char>(1024, 1024);
863 std::fill(buf.get(), buf.get() + 1024, 'a');
864 f.dma_write(0, buf.get(), 1024).get();
865 std::fill(buf.get(), buf.get() + 1024, 'b');
866 io_intent intent;
867 auto f1 = f.dma_write(0, buf.get(), 512);
868 auto f2 = f.dma_write(512, buf.get(), 512, default_priority_class(), &intent);
869 intent.cancel();
870
871 bool cancelled = false;
872 f1.get();
873 try {
874 f2.get();
875 } catch (cancelled_error& ex) {
876 cancelled = true;
877 }
878 auto rbuf = allocate_aligned_buffer<unsigned char>(1024, 1024);
879 f.dma_read(0, rbuf.get(), 1024).get();
880 BOOST_REQUIRE(rbuf.get()[0] == 'b');
881 if (cancelled) {
882 BOOST_REQUIRE(rbuf.get()[512] == 'a');
883 } else {
884 // The file::dma_write doesn't preemt, but if it
885 // suddenly will, the 2nd write will pass before
886 // the intent would be cancelled
887 BOOST_TEST_WARN(0, "Write won the race with cancellation");
888 BOOST_REQUIRE(rbuf.get()[512] == 'b');
889 }
890 });
891}
892
893SEASTAR_TEST_CASE(parallel_overwrite) {
894 // Avoid /tmp for tmp_dir, since it can be tmpfs
895 return tmp_dir::do_with("XXXXXXXX.tmp", [] (tmp_dir& t) {
896 return async([&] {
897 // Check that overwrites at disk_overwrite_dma_alignment() do not cause stalls. First,
898 // create a file.
899 auto fname = (t.get_path() / "testfile.tmp").native();
900 auto sz = uint64_t(1*1024*1024);
901 auto buffer_size = 128*1024;
902
903 file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
904 // Avoid filesystem problems with size-extending operations
905 f.truncate(sz).get();
906 auto buf = allocate_aligned_buffer<unsigned char>(buffer_size, f.memory_dma_alignment());
907 for (uint64_t offset = 0; offset < sz; offset += buffer_size) {
908 f.dma_write(offset, buf.get(), buffer_size).get();
909 }
910
911 auto random_engine = testing::local_random_engine;
912 auto dist = std::uniform_int_distribution(uint64_t(0), sz-1);
913 auto offsets = std::vector<uint64_t>();
914 std::generate_n(std::back_insert_iterator(offsets), 5000, [&] { return align_down(dist(random_engine), f.disk_overwrite_dma_alignment()); });
915 auto stall_report = internal::report_reactor_stalls([&] {
916 return max_concurrent_for_each(offsets, 10, [&] (uint64_t offset) {
917 return f.dma_write(offset, buf.get(), f.disk_overwrite_dma_alignment()).discard_result();
918 });
919 }).get0();
920 std::cout << "parallel_overwrite: " << stall_report << " (overwrite dma alignment " << f.disk_overwrite_dma_alignment() << ")\n";
921
922 f.close().get();
923 remove_file(fname).get();
924 });
925 });
926}
927
928SEASTAR_TEST_CASE(test_oversized_io_works) {
929 return tmp_dir::do_with_thread([] (tmp_dir& t) {
930 sstring filename = (t.get_path() / "testfile.tmp").native();
931 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
932
933 size_t max_write = f.disk_write_max_length();
934 size_t max_read = f.disk_read_max_length();
935 size_t buf_size = std::max(max_write, max_read) + 4096;
936
937 auto buf = allocate_aligned_buffer<unsigned char>(buf_size, 4096);
938 std::fill(buf.get(), buf.get() + buf_size, 'a');
939
940 f.dma_write(0, buf.get(), buf_size).get();
941 f.flush().get0();
942 f.close().get();
943
944 std::fill(buf.get(), buf.get() + buf_size, 'b');
945 f = open_file_dma(filename, open_flags::rw).get0();
946 f.dma_read(0, buf.get(), buf_size).get();
947
948 BOOST_REQUIRE((size_t)std::count_if(buf.get(), buf.get() + buf_size, [](auto x) { return x == 'a'; }) == buf_size);
949 });
950}