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.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
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
19 * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
22 #include <seastar/testing/test_case.hh>
23 #include <seastar/testing/thread_test_case.hh>
24 #include <seastar/testing/test_runner.hh>
26 #include <seastar/core/seastar.hh>
27 #include <seastar/core/semaphore.hh>
28 #include <seastar/core/condition-variable.hh>
29 #include <seastar/core/file.hh>
30 #include <seastar/core/layered_file.hh>
31 #include <seastar/core/thread.hh>
32 #include <seastar/core/stall_sampler.hh>
33 #include <seastar/core/aligned_buffer.hh>
34 #include <seastar/core/io_intent.hh>
35 #include <seastar/util/tmp_file.hh>
36 #include <seastar/util/alloc_failure_injector.hh>
37 #include <seastar/util/closeable.hh>
39 #include <boost/range/adaptor/transformed.hpp>
41 #include <sys/statfs.h>
44 #include "core/file-impl.hh"
46 using namespace seastar
;
47 namespace fs
= std::filesystem
;
49 SEASTAR_TEST_CASE(open_flags_test
) {
50 open_flags flags
= open_flags::rw
| open_flags::create
| open_flags::exclusive
;
51 BOOST_REQUIRE(std::underlying_type_t
<open_flags
>(flags
) ==
52 (std::underlying_type_t
<open_flags
>(open_flags::rw
) |
53 std::underlying_type_t
<open_flags
>(open_flags::create
) |
54 std::underlying_type_t
<open_flags
>(open_flags::exclusive
)));
56 open_flags mask
= open_flags::create
| open_flags::exclusive
;
57 BOOST_REQUIRE((flags
& mask
) == mask
);
58 return make_ready_future
<>();
61 SEASTAR_TEST_CASE(access_flags_test
) {
62 access_flags flags
= access_flags::read
| access_flags::write
| access_flags::execute
;
63 BOOST_REQUIRE(std::underlying_type_t
<open_flags
>(flags
) ==
64 (std::underlying_type_t
<open_flags
>(access_flags::read
) |
65 std::underlying_type_t
<open_flags
>(access_flags::write
) |
66 std::underlying_type_t
<open_flags
>(access_flags::execute
)));
67 return make_ready_future
<>();
70 SEASTAR_TEST_CASE(file_exists_test
) {
71 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
72 sstring filename
= (t
.get_path() / "testfile.tmp").native();
73 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
75 auto exists
= file_exists(filename
).get0();
76 BOOST_REQUIRE(exists
);
77 remove_file(filename
).get();
78 exists
= file_exists(filename
).get0();
79 BOOST_REQUIRE(!exists
);
83 SEASTAR_TEST_CASE(handle_bad_alloc_test
) {
84 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
85 sstring filename
= (t
.get_path() / "testfile.tmp").native();
86 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
89 memory::with_allocation_failures([&] {
90 exists
= file_exists(filename
).get0();
92 BOOST_REQUIRE(exists
);
96 SEASTAR_TEST_CASE(file_access_test
) {
97 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
98 sstring filename
= (t
.get_path() / "testfile.tmp").native();
99 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
101 auto is_accessible
= file_accessible(filename
, access_flags::read
| access_flags::write
).get0();
102 BOOST_REQUIRE(is_accessible
);
107 file_test(file
&& f
) : f(std::move(f
)) {}
109 semaphore sem
= { 0 };
110 semaphore par
= { 1000 };
113 SEASTAR_TEST_CASE(test1
) {
114 // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
115 return tmp_dir::do_with([] (tmp_dir
& t
) {
116 static constexpr auto max
= 10000;
117 sstring filename
= (t
.get_path() / "testfile.tmp").native();
118 return open_file_dma(filename
, open_flags::rw
| open_flags::create
).then([filename
] (file f
) {
119 auto ft
= new file_test
{std::move(f
)};
120 for (size_t i
= 0; i
< max
; ++i
) {
121 // Don't wait for future, use semaphore to signal when done instead.
122 (void)ft
->par
.wait().then([ft
, i
] {
123 auto wbuf
= allocate_aligned_buffer
<unsigned char>(4096, 4096);
124 std::fill(wbuf
.get(), wbuf
.get() + 4096, i
);
125 auto wb
= wbuf
.get();
126 (void)ft
->f
.dma_write(i
* 4096, wb
, 4096).then(
127 [ft
, i
, wbuf
= std::move(wbuf
)] (size_t ret
) mutable {
128 BOOST_REQUIRE(ret
== 4096);
129 auto rbuf
= allocate_aligned_buffer
<unsigned char>(4096, 4096);
130 auto rb
= rbuf
.get();
131 (void)ft
->f
.dma_read(i
* 4096, rb
, 4096).then(
132 [ft
, rbuf
= std::move(rbuf
), wbuf
= std::move(wbuf
)] (size_t ret
) mutable {
133 BOOST_REQUIRE(ret
== 4096);
134 BOOST_REQUIRE(std::equal(rbuf
.get(), rbuf
.get() + 4096, wbuf
.get()));
141 return ft
->sem
.wait(max
).then([ft
] () mutable {
142 return ft
->f
.flush();
144 return ft
->f
.close();
145 }).then([ft
] () mutable {
152 SEASTAR_TEST_CASE(parallel_write_fsync
) {
153 return internal::report_reactor_stalls([] {
154 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
155 // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
156 auto fname
= (t
.get_path() / "testfile.tmp").native();
157 auto sz
= uint64_t(32*1024*1024);
158 auto buffer_size
= 32768;
159 auto write_concurrency
= 16;
160 auto fsync_every
= 1024*1024;
161 auto max_write_ahead_of_fsync
= 4*1024*1024; // ensures writes don't complete too quickly
162 auto written
= uint64_t(0);
163 auto fsynced_at
= uint64_t(0);
165 file f
= open_file_dma(fname
, open_flags::rw
| open_flags::create
| open_flags::truncate
).get0();
166 auto close_f
= deferred_close(f
);
167 // Avoid filesystem problems with size-extending operations
168 f
.truncate(sz
).get();
170 auto fsync_semaphore
= semaphore(0);
171 auto may_write_condvar
= condition_variable();
172 auto fsync_thread
= thread([&] {
173 auto fsynced
= uint64_t(0);
174 while (fsynced
< sz
) {
175 fsync_semaphore
.wait(fsync_every
).get();
176 fsynced_at
= written
;
177 // Signal the condition variable now so that writes proceed
178 // in parallel with the fsync
179 may_write_condvar
.broadcast();
181 fsynced
+= fsync_every
;
185 auto write_semaphore
= semaphore(write_concurrency
);
186 while (written
< sz
) {
187 write_semaphore
.wait().get();
188 may_write_condvar
.wait([&] {
189 return written
<= fsynced_at
+ max_write_ahead_of_fsync
;
191 auto buf
= temporary_buffer
<char>::aligned(f
.memory_dma_alignment(), buffer_size
);
192 memset(buf
.get_write(), 0, buf
.size());
193 // Write asynchronously, signal when done.
194 (void)f
.dma_write(written
, buf
.get(), buf
.size()).then([&fsync_semaphore
, &write_semaphore
, buf
= std::move(buf
)] (size_t w
) {
195 fsync_semaphore
.signal(buf
.size());
196 write_semaphore
.signal();
198 written
+= buffer_size
;
200 write_semaphore
.wait(write_concurrency
).get();
202 fsync_thread
.join().get();
204 remove_file(fname
).get();
206 }).then([] (internal::stall_report sr
) {
207 std::cout
<< "parallel_write_fsync: " << sr
<< "\n";
211 SEASTAR_TEST_CASE(test_iov_max
) {
212 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
213 static constexpr size_t buffer_size
= 4096;
214 static constexpr size_t buffer_count
= IOV_MAX
* 2 + 1;
216 std::vector
<temporary_buffer
<char>> original_buffers
;
217 std::vector
<iovec
> iovecs
;
218 for (auto i
= 0u; i
< buffer_count
; i
++) {
219 original_buffers
.emplace_back(temporary_buffer
<char>::aligned(buffer_size
, buffer_size
));
220 std::fill_n(original_buffers
.back().get_write(), buffer_size
, char(i
));
221 iovecs
.emplace_back(iovec
{ original_buffers
.back().get_write(), buffer_size
});
224 auto filename
= (t
.get_path() / "testfile.tmp").native();
225 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
226 auto close_f
= deferred_close(f
);
227 size_t left
= buffer_size
* buffer_count
;
230 auto written
= f
.dma_write(position
, iovecs
).get0();
231 iovecs
.erase(iovecs
.begin(), iovecs
.begin() + written
/ buffer_size
);
232 assert(written
% buffer_size
== 0);
237 BOOST_CHECK(iovecs
.empty());
239 std::vector
<temporary_buffer
<char>> read_buffers
;
240 for (auto i
= 0u; i
< buffer_count
; i
++) {
241 read_buffers
.emplace_back(temporary_buffer
<char>::aligned(buffer_size
, buffer_size
));
242 std::fill_n(read_buffers
.back().get_write(), buffer_size
, char(0));
243 iovecs
.emplace_back(iovec
{ read_buffers
.back().get_write(), buffer_size
});
246 left
= buffer_size
* buffer_count
;
249 auto read
= f
.dma_read(position
, iovecs
).get0();
250 iovecs
.erase(iovecs
.begin(), iovecs
.begin() + read
/ buffer_size
);
251 assert(read
% buffer_size
== 0);
256 for (auto i
= 0u; i
< buffer_count
; i
++) {
257 BOOST_CHECK(std::equal(original_buffers
[i
].get(), original_buffers
[i
].get() + original_buffers
[i
].size(),
258 read_buffers
[i
].get(), read_buffers
[i
].get() + read_buffers
[i
].size()));
263 SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs
) {
264 auto buf
= temporary_buffer
<char>::aligned(4096, 4096);
266 auto iovec_equal
= [] (const iovec
& a
, const iovec
& b
) {
267 return a
.iov_base
== b
.iov_base
&& a
.iov_len
== b
.iov_len
;
270 { // Single fragment, sanitize is noop
271 auto original_iovecs
= std::vector
<iovec
> { { buf
.get_write(), buf
.size() } };
272 auto actual_iovecs
= original_iovecs
;
273 auto actual_length
= internal::sanitize_iovecs(actual_iovecs
, 4096);
274 BOOST_CHECK_EQUAL(actual_length
, 4096);
275 BOOST_CHECK_EQUAL(actual_iovecs
.size(), 1);
276 BOOST_CHECK(iovec_equal(original_iovecs
.back(), actual_iovecs
.back()));
279 { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
280 auto original_iovecs
= std::vector
<iovec
>{};
281 for (auto i
= 0u; i
< IOV_MAX
+ 7; i
++) {
282 original_iovecs
.emplace_back(iovec
{ buf
.get_write(), i
== 0 ? 1024u : 512u });
284 auto actual_iovecs
= original_iovecs
;
285 auto actual_length
= internal::sanitize_iovecs(actual_iovecs
, 4096);
286 BOOST_CHECK_EQUAL(actual_length
, 512 * IOV_MAX
);
287 BOOST_CHECK_EQUAL(actual_iovecs
.size(), IOV_MAX
- 1);
289 original_iovecs
.resize(IOV_MAX
- 1);
290 BOOST_CHECK(std::equal(original_iovecs
.begin(), original_iovecs
.end(),
291 actual_iovecs
.begin(), actual_iovecs
.end(), iovec_equal
));
294 { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers
295 auto original_iovecs
= std::vector
<iovec
>{};
296 for (auto i
= 0u; i
< IOV_MAX
+ 7; i
++) {
297 original_iovecs
.emplace_back(iovec
{ buf
.get_write(), i
== (IOV_MAX
- 1) ? 1024u : 512u });
299 auto actual_iovecs
= original_iovecs
;
300 auto actual_length
= internal::sanitize_iovecs(actual_iovecs
, 4096);
301 BOOST_CHECK_EQUAL(actual_length
, 512 * IOV_MAX
);
302 BOOST_CHECK_EQUAL(actual_iovecs
.size(), IOV_MAX
);
304 original_iovecs
.resize(IOV_MAX
);
305 original_iovecs
.back().iov_len
= 512;
306 BOOST_CHECK(std::equal(original_iovecs
.begin(), original_iovecs
.end(),
307 actual_iovecs
.begin(), actual_iovecs
.end(), iovec_equal
));
310 { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
311 auto original_iovecs
= std::vector
<iovec
>{};
312 for (auto i
= 0u; i
< IOV_MAX
+ 8; i
++) {
313 original_iovecs
.emplace_back(iovec
{ buf
.get_write(), 512 });
315 auto actual_iovecs
= original_iovecs
;
316 auto actual_length
= internal::sanitize_iovecs(actual_iovecs
, 4096);
317 BOOST_CHECK_EQUAL(actual_length
, 512 * IOV_MAX
);
318 BOOST_CHECK_EQUAL(actual_iovecs
.size(), IOV_MAX
);
320 original_iovecs
.resize(IOV_MAX
);
321 BOOST_CHECK(std::equal(original_iovecs
.begin(), original_iovecs
.end(),
322 actual_iovecs
.begin(), actual_iovecs
.end(), iovec_equal
));
326 SEASTAR_TEST_CASE(test_chmod
) {
327 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
328 auto oflags
= open_flags::rw
| open_flags::create
;
329 sstring filename
= (t
.get_path() / "testfile.tmp").native();
330 if (file_exists(filename
).get0()) {
331 remove_file(filename
).get();
334 auto orig_umask
= umask(0);
336 // test default_file_permissions
337 auto f
= open_file_dma(filename
, oflags
).get0();
339 auto sd
= file_stat(filename
).get0();
340 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_file_permissions
));
342 // test chmod with new_permissions
343 auto new_permissions
= file_permissions::user_read
| file_permissions::group_read
| file_permissions::others_read
;
344 BOOST_REQUIRE(new_permissions
!= file_permissions::default_file_permissions
);
345 BOOST_REQUIRE(file_exists(filename
).get0());
346 chmod(filename
, new_permissions
).get();
347 sd
= file_stat(filename
).get0();
348 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(new_permissions
));
349 remove_file(filename
).get();
355 SEASTAR_TEST_CASE(test_open_file_dma_permissions
) {
356 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
357 auto oflags
= open_flags::rw
| open_flags::create
;
358 sstring filename
= (t
.get_path() / "testfile.tmp").native();
359 if (file_exists(filename
).get0()) {
360 remove_file(filename
).get();
363 auto orig_umask
= umask(0);
365 // test default_file_permissions
366 auto f
= open_file_dma(filename
, oflags
).get0();
368 auto sd
= file_stat(filename
).get0();
369 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_file_permissions
));
370 remove_file(filename
).get();
372 // test options.create_permissions
373 auto options
= file_open_options();
374 options
.create_permissions
= file_permissions::user_read
| file_permissions::group_read
| file_permissions::others_read
;
375 BOOST_REQUIRE(options
.create_permissions
!= file_permissions::default_file_permissions
);
376 f
= open_file_dma(filename
, oflags
, options
).get0();
378 sd
= file_stat(filename
).get0();
379 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(options
.create_permissions
));
380 remove_file(filename
).get();
386 SEASTAR_TEST_CASE(test_make_directory_permissions
) {
387 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
388 sstring dirname
= (t
.get_path() / "testdir.tmp").native();
389 auto orig_umask
= umask(0);
391 // test default_dir_permissions with make_directory
392 make_directory(dirname
).get();
393 auto sd
= file_stat(dirname
).get0();
394 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_dir_permissions
));
395 remove_file(dirname
).get();
397 // test make_directory
398 auto create_permissions
= file_permissions::user_read
| file_permissions::group_read
| file_permissions::others_read
;
399 BOOST_REQUIRE(create_permissions
!= file_permissions::default_dir_permissions
);
400 make_directory(dirname
, create_permissions
).get();
401 sd
= file_stat(dirname
).get0();
402 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(create_permissions
));
403 remove_file(dirname
).get();
409 SEASTAR_TEST_CASE(test_touch_directory_permissions
) {
410 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
411 sstring dirname
= (t
.get_path() / "testdir.tmp").native();
412 auto orig_umask
= umask(0);
414 // test default_dir_permissions with touch_directory
415 touch_directory(dirname
).get();
416 auto sd
= file_stat(dirname
).get0();
417 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_dir_permissions
));
418 remove_file(dirname
).get();
420 // test touch_directory, dir creation
421 auto create_permissions
= file_permissions::user_read
| file_permissions::group_read
| file_permissions::others_read
;
422 BOOST_REQUIRE(create_permissions
!= file_permissions::default_dir_permissions
);
423 BOOST_REQUIRE(!file_exists(dirname
).get0());
424 touch_directory(dirname
, create_permissions
).get();
425 sd
= file_stat(dirname
).get0();
426 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(create_permissions
));
428 // test touch_directory of existing dir, dir mode need not change
429 BOOST_REQUIRE(file_exists(dirname
).get0());
430 touch_directory(dirname
, file_permissions::default_dir_permissions
).get();
431 sd
= file_stat(dirname
).get0();
432 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(create_permissions
));
433 remove_file(dirname
).get();
439 SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions
) {
440 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
441 sstring base_dirname
= (t
.get_path() / "testbasedir.tmp").native();
442 sstring dirpath
= base_dirname
+ "/" + "testsubdir.tmp";
443 if (file_exists(dirpath
).get0()) {
444 remove_file(dirpath
).get();
446 if (file_exists(base_dirname
).get0()) {
447 remove_file(base_dirname
).get();
450 auto orig_umask
= umask(0);
452 // test default_dir_permissions with recursive_touch_directory
453 recursive_touch_directory(dirpath
).get();
454 auto sd
= file_stat(base_dirname
).get0();
455 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_dir_permissions
));
456 sd
= file_stat(dirpath
).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 remove_file(dirpath
).get();
460 // test recursive_touch_directory, dir creation
461 auto create_permissions
= file_permissions::user_read
| file_permissions::group_read
| file_permissions::others_read
;
462 BOOST_REQUIRE(create_permissions
!= file_permissions::default_dir_permissions
);
463 BOOST_REQUIRE(file_exists(base_dirname
).get0());
464 BOOST_REQUIRE(!file_exists(dirpath
).get0());
465 recursive_touch_directory(dirpath
, create_permissions
).get();
466 sd
= file_stat(base_dirname
).get0();
467 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_dir_permissions
));
468 sd
= file_stat(dirpath
).get0();
469 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(create_permissions
));
471 // test recursive_touch_directory of existing dir, dir mode need not change
472 BOOST_REQUIRE(file_exists(dirpath
).get0());
473 recursive_touch_directory(dirpath
, file_permissions::default_dir_permissions
).get();
474 sd
= file_stat(base_dirname
).get0();
475 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_dir_permissions
));
476 sd
= file_stat(dirpath
).get0();
477 BOOST_CHECK_EQUAL(sd
.mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(create_permissions
));
478 remove_file(dirpath
).get();
479 remove_file(base_dirname
).get();
485 SEASTAR_TEST_CASE(test_file_stat_method
) {
486 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
487 auto oflags
= open_flags::rw
| open_flags::create
;
488 sstring filename
= (t
.get_path() / "testfile.tmp").native();
490 auto orig_umask
= umask(0);
492 auto f
= open_file_dma(filename
, oflags
).get0();
493 auto close_f
= deferred_close(f
);
494 auto st
= f
.stat().get0();
495 BOOST_CHECK_EQUAL(st
.st_mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_file_permissions
));
501 SEASTAR_TEST_CASE(test_file_write_lifetime_method
) {
502 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
503 auto oflags
= open_flags::rw
| open_flags::create
;
504 sstring filename
= (t
.get_path() / "testfile.tmp").native();
506 auto f1
= open_file_dma(filename
, oflags
).get0();
507 auto close_f1
= deferred_close(f1
);
508 auto f2
= open_file_dma(filename
, oflags
).get0();
509 auto close_f2
= deferred_close(f2
);
511 // Write life time hint values
512 std::vector
<uint64_t> hint_set
= {RWF_WRITE_LIFE_NOT_SET
,
514 RWH_WRITE_LIFE_SHORT
,
515 RWH_WRITE_LIFE_MEDIUM
,
517 RWH_WRITE_LIFE_EXTREME
};
519 for (auto i
= 0ul; i
< hint_set
.size(); ++i
) {
520 auto hint
= hint_set
[i
];
522 // Set and verify the lifetime hint of the inode
523 f1
.set_inode_lifetime_hint(hint
).get();
524 auto o_hint1
= f1
.get_inode_lifetime_hint().get0();
525 BOOST_CHECK_EQUAL(hint
, o_hint1
);
527 // Verfiy the lifetime_hint set previously on the inode using open fds
528 auto o_hint2
= f2
.get_file_lifetime_hint().get0();
529 BOOST_CHECK_EQUAL(hint
, o_hint2
);
531 // Set and verify on the open fd (different from the inode method)
532 f1
.set_file_lifetime_hint(hint
).get();
533 o_hint1
= f1
.get_file_lifetime_hint().get0();
534 BOOST_CHECK_EQUAL(hint
, o_hint1
);
537 // Perform invalid ops
538 uint64_t hint
= RWH_WRITE_LIFE_EXTREME
+ 1;
539 BOOST_REQUIRE_THROW(f1
.set_inode_lifetime_hint(hint
).get(), std::system_error
);
540 BOOST_REQUIRE_THROW(f1
.set_file_lifetime_hint(hint
).get(), std::system_error
);
544 SEASTAR_TEST_CASE(test_file_fcntl
) {
545 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
546 auto oflags
= open_flags::rw
| open_flags::create
;
547 sstring filename
= (t
.get_path() / "testfile.tmp").native();
549 auto f
= open_file_dma(filename
, oflags
).get0();
550 auto close_f
= deferred_close(f
);
552 // Set and verify a lease value
553 auto lease
= F_WRLCK
;
554 BOOST_REQUIRE(!f
.fcntl(F_SETLEASE
, lease
).get0());
555 auto o_lease
= f
.fcntl(F_GETLEASE
).get0();
556 BOOST_CHECK_EQUAL(lease
, o_lease
);
558 // Use _short version and test the same
559 o_lease
= f
.fcntl_short(F_GETLEASE
).get0();
560 BOOST_CHECK_EQUAL(lease
, o_lease
);
562 // Perform invalid ops
563 BOOST_REQUIRE_THROW(f
.fcntl(F_SETLEASE
, (uintptr_t)~0ul).get(), std::system_error
);
564 BOOST_REQUIRE_THROW(f
.fcntl_short(F_SETLEASE
, (uintptr_t)~0ul).get(), std::system_error
);
566 // Set and verify a life time hint value using fcntl
567 uint64_t hint
= RWH_WRITE_LIFE_SHORT
;
568 uint64_t o_hint1
= RWF_WRITE_LIFE_NOT_SET
;
569 BOOST_REQUIRE(!f
.fcntl(F_SET_FILE_RW_HINT
, (uintptr_t)&hint
).get0());
570 BOOST_REQUIRE(!f
.fcntl(F_GET_FILE_RW_HINT
, (uintptr_t)&o_hint1
).get0());
571 BOOST_CHECK_EQUAL(hint
, o_hint1
);
573 // Do the same with _short version of fcntl
574 uint64_t o_hint2
= RWF_WRITE_LIFE_NOT_SET
;
575 BOOST_REQUIRE(!f
.fcntl_short(F_GET_FILE_RW_HINT
, (uintptr_t)&o_hint2
).get0());
576 BOOST_CHECK_EQUAL(hint
, o_hint2
);
578 // perform an invalid op
579 hint
= RWH_WRITE_LIFE_EXTREME
+ 1;
580 BOOST_REQUIRE_THROW(f
.fcntl(F_SET_FILE_RW_HINT
, (uintptr_t)&hint
).get(), std::system_error
);
581 BOOST_REQUIRE_THROW(f
.fcntl_short(F_SET_FILE_RW_HINT
, (uintptr_t)&hint
).get(), std::system_error
);
585 SEASTAR_TEST_CASE(test_file_ioctl
) {
586 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
587 auto oflags
= open_flags::rw
| open_flags::create
;
588 sstring filename
= (t
.get_path() / "testfile.tmp").native();
589 uint64_t block_size
= 0;
591 auto f
= open_file_dma(filename
, oflags
).get0();
592 auto close_f
= deferred_close(f
);
594 // Issueing an FS ioctl which is applicable on regular files
595 // and can be executed as normal user
597 BOOST_REQUIRE(!f
.ioctl(FIGETBSZ
, &block_size
).get0());
598 BOOST_REQUIRE(block_size
!= 0);
600 // Use _short version and test the same
601 BOOST_REQUIRE(!f
.ioctl_short(FIGETBSZ
, &block_size
).get0());
602 BOOST_REQUIRE(block_size
!= 0);
603 } catch (std::system_error
& e
) {
604 // anon_bdev filesystems do not support FIGETBSZ, and return EINVAL
605 BOOST_REQUIRE_EQUAL(e
.code().value(), EINVAL
);
608 // Perform invalid ops
609 BOOST_REQUIRE_THROW(f
.ioctl(FIGETBSZ
, 0ul).get(), std::system_error
);
610 BOOST_REQUIRE_THROW(f
.ioctl_short(FIGETBSZ
, 0ul).get(), std::system_error
);
614 class test_layered_file
: public layered_file_impl
{
616 explicit test_layered_file(file f
) : layered_file_impl(std::move(f
)) {}
617 virtual future
<size_t> write_dma(uint64_t pos
, const void* buffer
, size_t len
, const io_priority_class
& pc
) override
{
620 virtual future
<size_t> write_dma(uint64_t pos
, std::vector
<iovec
> iov
, const io_priority_class
& pc
) override
{
623 virtual future
<size_t> read_dma(uint64_t pos
, void* buffer
, size_t len
, const io_priority_class
& pc
) override
{
626 virtual future
<size_t> read_dma(uint64_t pos
, std::vector
<iovec
> iov
, const io_priority_class
& pc
) override
{
629 virtual future
<> flush(void) override
{
632 virtual future
<struct stat
> stat(void) override
{
635 virtual future
<> truncate(uint64_t length
) override
{
638 virtual future
<> discard(uint64_t offset
, uint64_t length
) override
{
641 virtual future
<> allocate(uint64_t position
, uint64_t length
) override
{
644 virtual future
<uint64_t> size(void) override
{
647 virtual future
<> close() override
{
650 virtual std::unique_ptr
<file_handle_impl
> dup() override
{
653 virtual subscription
<directory_entry
> list_directory(std::function
<future
<> (directory_entry de
)> next
) override
{
656 virtual future
<temporary_buffer
<uint8_t>> dma_read_bulk(uint64_t offset
, size_t range_size
, const io_priority_class
& pc
) override
{
661 SEASTAR_TEST_CASE(test_underlying_file
) {
662 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
663 auto oflags
= open_flags::rw
| open_flags::create
;
664 sstring filename
= (t
.get_path() / "testfile.tmp").native();
665 auto f
= open_file_dma(filename
, oflags
).get0();
666 auto close_f
= deferred_close(f
);
667 auto lf
= file(make_shared
<test_layered_file
>(f
));
668 BOOST_CHECK_EQUAL(f
.memory_dma_alignment(), lf
.memory_dma_alignment());
669 BOOST_CHECK_EQUAL(f
.disk_read_dma_alignment(), lf
.disk_read_dma_alignment());
670 BOOST_CHECK_EQUAL(f
.disk_write_dma_alignment(), lf
.disk_write_dma_alignment());
674 SEASTAR_TEST_CASE(test_file_stat_method_with_file
) {
675 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
676 auto oflags
= open_flags::rw
| open_flags::create
| open_flags::truncate
;
677 sstring filename
= (t
.get_path() / "testfile.tmp").native();
680 auto orig_umask
= umask(0);
682 auto st
= with_file(open_file_dma(filename
, oflags
), [&ref
] (file
& f
) {
683 // make a copy of f to verify f is auto-closed when `with_file` returns.
687 BOOST_CHECK_EQUAL(st
.st_mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_file_permissions
));
689 // verify that the file was auto-closed
690 BOOST_REQUIRE_THROW(ref
.stat().get(), std::system_error
);
696 SEASTAR_TEST_CASE(test_open_error_with_file
) {
697 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
698 auto open_file
= [&t
] (bool do_open
) {
699 auto oflags
= open_flags::ro
;
700 sstring filename
= (t
.get_path() / "testfile.tmp").native();
702 return open_file_dma(filename
, oflags
);
704 throw std::runtime_error("expected exception");
707 bool got_exception
= false;
709 BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file
& f
) {
710 BOOST_REQUIRE(false);
711 }).handle_exception_type([&got_exception
] (const std::system_error
& e
) {
712 got_exception
= true;
713 BOOST_REQUIRE(e
.code().value() == ENOENT
);
715 BOOST_REQUIRE(got_exception
);
717 got_exception
= false;
718 BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file
& f
) {
719 BOOST_REQUIRE(false);
720 }).handle_exception_type([&got_exception
] (const std::runtime_error
& e
) {
721 got_exception
= true;
722 }).get(), std::runtime_error
);
723 BOOST_REQUIRE(!got_exception
);
727 SEASTAR_TEST_CASE(test_with_file_close_on_failure
) {
728 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
729 auto oflags
= open_flags::rw
| open_flags::create
| open_flags::truncate
;
730 sstring filename
= (t
.get_path() / "testfile.tmp").native();
732 auto orig_umask
= umask(0);
735 auto ref
= with_file_close_on_failure(open_file_dma(filename
, oflags
), [] (file
& f
) {
738 auto st
= ref
.stat().get0();
740 BOOST_CHECK_EQUAL(st
.st_mode
& static_cast<mode_t
>(file_permissions::all_permissions
), static_cast<mode_t
>(file_permissions::default_file_permissions
));
742 // close-on-error case
743 BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename
, oflags
), [&ref
] (file
& f
) {
745 throw std::runtime_error("expected exception");
746 }).get(), std::runtime_error
);
748 // verify that file was auto-closed on error
749 BOOST_REQUIRE_THROW(ref
.stat().get(), std::system_error
);
756 extern bool aio_nowait_supported
;
759 SEASTAR_TEST_CASE(test_nowait_flag_correctness
) {
760 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
761 auto oflags
= open_flags::rw
| open_flags::create
;
762 sstring filename
= (t
.get_path() / "testfile.tmp").native();
763 auto is_tmpfs
= [&] (sstring filename
) {
765 int fd
= ::open(filename
.c_str(), static_cast<int>(open_flags::ro
));
767 auto r
= ::fstatfs(fd
, &buf
);
771 return buf
.f_type
== 0x01021994; // TMPFS_MAGIC
774 if (!seastar::aio_nowait_supported
) {
775 BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system");
779 auto f
= open_file_dma(filename
, oflags
).get0();
780 auto close_f
= deferred_close(f
);
782 if (is_tmpfs(filename
)) {
783 BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes");
787 for (auto i
= 0; i
< 10; i
++) {
788 auto wbuf
= allocate_aligned_buffer
<unsigned char>(4096, 4096);
789 std::fill(wbuf
.get(), wbuf
.get() + 4096, i
);
790 auto wb
= wbuf
.get();
791 f
.dma_write(i
* 4096, wb
, 4096).get();
797 SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file
) {
798 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
799 sstring filename
= (t
.get_path() / "testfile.tmp").native();
800 auto oflags
= open_flags::rw
| open_flags::create
;
801 auto f
= open_file_dma(filename
, oflags
).get0();
805 SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file_with_sloppy_size
) {
806 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
807 sstring filename
= (t
.get_path() / "testfile.tmp").native();
808 auto oflags
= open_flags::rw
| open_flags::create
;
809 file_open_options opt
;
810 opt
.sloppy_size
= true;
811 auto f
= open_file_dma(filename
, oflags
, opt
).get0();
815 SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write
) {
816 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
817 sstring filename
= (t
.get_path() / "testfile.tmp").native();
818 auto buf
= allocate_aligned_buffer
<unsigned char>(4096, 4096);
819 std::fill(buf
.get(), buf
.get() + 4096, 0);
821 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
822 f
.dma_write(0, buf
.get(), 4096).get();
826 SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read
) {
827 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
828 sstring filename
= (t
.get_path() / "testfile.tmp").native();
829 auto buf
= allocate_aligned_buffer
<unsigned char>(4096, 4096);
830 std::fill(buf
.get(), buf
.get() + 4096, 0);
832 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
833 f
.dma_write(0, buf
.get(), 4096).get();
837 f
= open_file_dma(filename
, open_flags::rw
).get0();
838 f
.dma_read(0, buf
.get(), 4096).get();
842 SEASTAR_TEST_CASE(test_dma_iovec
) {
843 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
844 static constexpr size_t alignment
= 4096;
845 auto wbuf
= allocate_aligned_buffer
<char>(alignment
, alignment
);
847 std::fill_n(wbuf
.get(), alignment
, char(0));
848 std::fill_n(wbuf
.get(), size
, char(42));
849 std::vector
<iovec
> iovecs
;
851 auto filename
= (t
.get_path() / "testfile.tmp").native();
852 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
853 iovecs
.push_back(iovec
{ wbuf
.get(), alignment
});
854 auto count
= f
.dma_write(0, iovecs
).get0();
855 BOOST_REQUIRE_EQUAL(count
, alignment
);
856 f
.truncate(size
).get();
859 auto rbuf
= allocate_aligned_buffer
<char>(alignment
, alignment
);
861 // this tests the posix_file_impl
862 f
= open_file_dma(filename
, open_flags::ro
).get0();
863 std::fill_n(rbuf
.get(), alignment
, char(0));
865 iovecs
.push_back(iovec
{ rbuf
.get(), alignment
});
866 count
= f
.dma_read(0, iovecs
).get0();
867 BOOST_REQUIRE_EQUAL(count
, size
);
869 BOOST_REQUIRE(std::equal(wbuf
.get(), wbuf
.get() + alignment
, rbuf
.get(), rbuf
.get() + alignment
));
871 // this tests the append_challenged_posix_file_impl
872 f
= open_file_dma(filename
, open_flags::rw
).get0();
873 std::fill_n(rbuf
.get(), alignment
, char(0));
875 iovecs
.push_back(iovec
{ rbuf
.get(), alignment
});
876 count
= f
.dma_read(0, iovecs
).get0();
877 BOOST_REQUIRE_EQUAL(count
, size
);
879 BOOST_REQUIRE(std::equal(wbuf
.get(), wbuf
.get() + alignment
, rbuf
.get(), rbuf
.get() + alignment
));
883 SEASTAR_TEST_CASE(test_intent
) {
884 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
885 sstring filename
= (t
.get_path() / "testfile.tmp").native();
886 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
887 auto buf
= allocate_aligned_buffer
<unsigned char>(1024, 1024);
888 std::fill(buf
.get(), buf
.get() + 1024, 'a');
889 f
.dma_write(0, buf
.get(), 1024).get();
890 std::fill(buf
.get(), buf
.get() + 1024, 'b');
892 auto f1
= f
.dma_write(0, buf
.get(), 512);
893 auto f2
= f
.dma_write(512, buf
.get(), 512, default_priority_class(), &intent
);
896 bool cancelled
= false;
900 } catch (cancelled_error
& ex
) {
903 auto rbuf
= allocate_aligned_buffer
<unsigned char>(1024, 1024);
904 f
.dma_read(0, rbuf
.get(), 1024).get();
905 BOOST_REQUIRE(rbuf
.get()[0] == 'b');
907 BOOST_REQUIRE(rbuf
.get()[512] == 'a');
909 // The file::dma_write doesn't preemt, but if it
910 // suddenly will, the 2nd write will pass before
911 // the intent would be cancelled
912 BOOST_TEST_WARN(0, "Write won the race with cancellation");
913 BOOST_REQUIRE(rbuf
.get()[512] == 'b');
918 SEASTAR_TEST_CASE(parallel_overwrite
) {
919 // Avoid /tmp for tmp_dir, since it can be tmpfs
920 return tmp_dir::do_with("XXXXXXXX.tmp", [] (tmp_dir
& t
) {
922 // Check that overwrites at disk_overwrite_dma_alignment() do not cause stalls. First,
924 auto fname
= (t
.get_path() / "testfile.tmp").native();
925 auto sz
= uint64_t(1*1024*1024);
926 auto buffer_size
= 128*1024;
928 file f
= open_file_dma(fname
, open_flags::rw
| open_flags::create
| open_flags::truncate
).get0();
929 // Avoid filesystem problems with size-extending operations
930 f
.truncate(sz
).get();
931 auto buf
= allocate_aligned_buffer
<unsigned char>(buffer_size
, f
.memory_dma_alignment());
932 for (uint64_t offset
= 0; offset
< sz
; offset
+= buffer_size
) {
933 f
.dma_write(offset
, buf
.get(), buffer_size
).get();
936 auto random_engine
= testing::local_random_engine
;
937 auto dist
= std::uniform_int_distribution(uint64_t(0), sz
-1);
938 auto offsets
= std::vector
<uint64_t>();
939 std::generate_n(std::back_insert_iterator(offsets
), 5000, [&] { return align_down(dist(random_engine
), f
.disk_overwrite_dma_alignment()); });
940 auto stall_report
= internal::report_reactor_stalls([&] {
941 return max_concurrent_for_each(offsets
, 10, [&] (uint64_t offset
) {
942 return f
.dma_write(offset
, buf
.get(), f
.disk_overwrite_dma_alignment()).discard_result();
945 std::cout
<< "parallel_overwrite: " << stall_report
<< " (overwrite dma alignment " << f
.disk_overwrite_dma_alignment() << ")\n";
948 remove_file(fname
).get();
953 SEASTAR_TEST_CASE(test_oversized_io_works
) {
954 return tmp_dir::do_with_thread([] (tmp_dir
& t
) {
955 sstring filename
= (t
.get_path() / "testfile.tmp").native();
956 auto f
= open_file_dma(filename
, open_flags::rw
| open_flags::create
).get0();
958 size_t max_write
= f
.disk_write_max_length();
959 size_t max_read
= f
.disk_read_max_length();
960 size_t buf_size
= std::max(max_write
, max_read
) + 4096;
962 auto buf
= allocate_aligned_buffer
<unsigned char>(buf_size
, 4096);
963 std::fill(buf
.get(), buf
.get() + buf_size
, 'a');
965 f
.dma_write(0, buf
.get(), buf_size
).get();
969 std::fill(buf
.get(), buf
.get() + buf_size
, 'b');
970 f
= open_file_dma(filename
, open_flags::rw
).get0();
971 f
.dma_read(0, buf
.get(), buf_size
).get();
973 BOOST_REQUIRE((size_t)std::count_if(buf
.get(), buf
.get() + buf_size
, [](auto x
) { return x
== 'a'; }) == buf_size
);