]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/tests/unit/file_io_test.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / tests / unit / file_io_test.cc
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>
23 #include <seastar/testing/thread_test_case.hh>
24 #include <seastar/testing/test_runner.hh>
25
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>
38
39 #include <boost/range/adaptor/transformed.hpp>
40 #include <iostream>
41 #include <sys/statfs.h>
42 #include <fcntl.h>
43
44 #include "core/file-impl.hh"
45
46 using namespace seastar;
47 namespace fs = std::filesystem;
48
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)));
55
56 open_flags mask = open_flags::create | open_flags::exclusive;
57 BOOST_REQUIRE((flags & mask) == mask);
58 return make_ready_future<>();
59 }
60
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<>();
68 }
69
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();
74 f.close().get();
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);
80 });
81 }
82
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();
87 f.close().get();
88 bool exists = false;
89 memory::with_allocation_failures([&] {
90 exists = file_exists(filename).get0();
91 });
92 BOOST_REQUIRE(exists);
93 });
94 }
95
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();
100 f.close().get();
101 auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0();
102 BOOST_REQUIRE(is_accessible);
103 });
104 }
105
106 struct file_test {
107 file_test(file&& f) : f(std::move(f)) {}
108 file f;
109 semaphore sem = { 0 };
110 semaphore par = { 1000 };
111 };
112
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()));
135 ft->sem.signal(1);
136 ft->par.signal();
137 });
138 });
139 });
140 }
141 return ft->sem.wait(max).then([ft] () mutable {
142 return ft->f.flush();
143 }).then([ft] {
144 return ft->f.close();
145 }).then([ft] () mutable {
146 delete ft;
147 });
148 });
149 });
150 }
151
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);
164
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();
169
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();
180 f.flush().get();
181 fsynced += fsync_every;
182 }
183 });
184
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;
190 }).get();
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();
197 });
198 written += buffer_size;
199 }
200 write_semaphore.wait(write_concurrency).get();
201
202 fsync_thread.join().get();
203 close_f.close_now();
204 remove_file(fname).get();
205 });
206 }).then([] (internal::stall_report sr) {
207 std::cout << "parallel_write_fsync: " << sr << "\n";
208 });
209 }
210
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;
215
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 });
222 }
223
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;
228 size_t position = 0;
229 while (left) {
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);
233 position += written;
234 left -= written;
235 }
236
237 BOOST_CHECK(iovecs.empty());
238
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 });
244 }
245
246 left = buffer_size * buffer_count;
247 position = 0;
248 while (left) {
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);
252 position += read;
253 left -= read;
254 }
255
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()));
259 }
260 });
261 }
262
263 SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) {
264 auto buf = temporary_buffer<char>::aligned(4096, 4096);
265
266 auto iovec_equal = [] (const iovec& a, const iovec& b) {
267 return a.iov_base == b.iov_base && a.iov_len == b.iov_len;
268 };
269
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()));
277 }
278
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 });
283 }
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);
288
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));
292 }
293
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 });
298 }
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);
303
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));
308 }
309
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 });
314 }
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);
319
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));
323 }
324 }
325
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();
332 }
333
334 auto orig_umask = umask(0);
335
336 // test default_file_permissions
337 auto f = open_file_dma(filename, oflags).get0();
338 f.close().get();
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));
341
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();
350
351 umask(orig_umask);
352 });
353 }
354
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();
361 }
362
363 auto orig_umask = umask(0);
364
365 // test default_file_permissions
366 auto f = open_file_dma(filename, oflags).get0();
367 f.close().get();
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();
371
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();
377 f.close().get();
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();
381
382 umask(orig_umask);
383 });
384 }
385
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);
390
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();
396
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();
404
405 umask(orig_umask);
406 });
407 }
408
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);
413
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();
419
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));
427
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();
434
435 umask(orig_umask);
436 });
437 }
438
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();
445 }
446 if (file_exists(base_dirname).get0()) {
447 remove_file(base_dirname).get();
448 }
449
450 auto orig_umask = umask(0);
451
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();
459
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));
470
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();
480
481 umask(orig_umask);
482 });
483 }
484
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();
489
490 auto orig_umask = umask(0);
491
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));
496
497 umask(orig_umask);
498 });
499 }
500
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();
505
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);
510
511 // Write life time hint values
512 std::vector<uint64_t> hint_set = {RWF_WRITE_LIFE_NOT_SET,
513 RWH_WRITE_LIFE_NONE,
514 RWH_WRITE_LIFE_SHORT,
515 RWH_WRITE_LIFE_MEDIUM,
516 RWH_WRITE_LIFE_LONG,
517 RWH_WRITE_LIFE_EXTREME};
518
519 for (auto i = 0ul; i < hint_set.size(); ++i) {
520 auto hint = hint_set[i];
521
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);
526
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);
530
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);
535 }
536
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);
541 });
542 }
543
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();
548
549 auto f = open_file_dma(filename, oflags).get0();
550 auto close_f = deferred_close(f);
551
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);
557
558 // Use _short version and test the same
559 o_lease = f.fcntl_short(F_GETLEASE).get0();
560 BOOST_CHECK_EQUAL(lease, o_lease);
561
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);
565
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);
572
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);
577
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);
582 });
583 }
584
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;
590
591 auto f = open_file_dma(filename, oflags).get0();
592 auto close_f = deferred_close(f);
593
594 // Issueing an FS ioctl which is applicable on regular files
595 // and can be executed as normal user
596 try {
597 BOOST_REQUIRE(!f.ioctl(FIGETBSZ, &block_size).get0());
598 BOOST_REQUIRE(block_size != 0);
599
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);
606 }
607
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);
611 });
612 }
613
614 class test_layered_file : public layered_file_impl {
615 public:
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 {
618 abort();
619 }
620 virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
621 abort();
622 }
623 virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override {
624 abort();
625 }
626 virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
627 abort();
628 }
629 virtual future<> flush(void) override {
630 abort();
631 }
632 virtual future<struct stat> stat(void) override {
633 abort();
634 }
635 virtual future<> truncate(uint64_t length) override {
636 abort();
637 }
638 virtual future<> discard(uint64_t offset, uint64_t length) override {
639 abort();
640 }
641 virtual future<> allocate(uint64_t position, uint64_t length) override {
642 abort();
643 }
644 virtual future<uint64_t> size(void) override {
645 abort();
646 }
647 virtual future<> close() override {
648 abort();
649 }
650 virtual std::unique_ptr<file_handle_impl> dup() override {
651 abort();
652 }
653 virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override {
654 abort();
655 }
656 virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override {
657 abort();
658 }
659 };
660
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());
671 });
672 }
673
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();
678 file ref;
679
680 auto orig_umask = umask(0);
681
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.
684 ref = f;
685 return f.stat();
686 }).get0();
687 BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
688
689 // verify that the file was auto-closed
690 BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
691
692 umask(orig_umask);
693 });
694 }
695
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();
701 if (do_open) {
702 return open_file_dma(filename, oflags);
703 } else {
704 throw std::runtime_error("expected exception");
705 }
706 };
707 bool got_exception = false;
708
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);
714 }).get());
715 BOOST_REQUIRE(got_exception);
716
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);
724 });
725 }
726
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();
731
732 auto orig_umask = umask(0);
733
734 // error-free case
735 auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) {
736 return f;
737 }).get0();
738 auto st = ref.stat().get0();
739 ref.close().get();
740 BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
741
742 // close-on-error case
743 BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) {
744 ref = f;
745 throw std::runtime_error("expected exception");
746 }).get(), std::runtime_error);
747
748 // verify that file was auto-closed on error
749 BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
750
751 umask(orig_umask);
752 });
753 }
754
755 namespace seastar {
756 extern bool aio_nowait_supported;
757 }
758
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) {
764 struct ::statfs buf;
765 int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro));
766 assert(fd != -1);
767 auto r = ::fstatfs(fd, &buf);
768 if (r == -1) {
769 return false;
770 }
771 return buf.f_type == 0x01021994; // TMPFS_MAGIC
772 };
773
774 if (!seastar::aio_nowait_supported) {
775 BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system");
776 return;
777 }
778
779 auto f = open_file_dma(filename, oflags).get0();
780 auto close_f = deferred_close(f);
781
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");
784 return;
785 }
786
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();
792 f.flush().get0();
793 }
794 });
795 }
796
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();
802 });
803 }
804
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();
812 });
813 }
814
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);
820
821 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
822 f.dma_write(0, buf.get(), 4096).get();
823 });
824 }
825
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);
831
832 auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
833 f.dma_write(0, buf.get(), 4096).get();
834 f.flush().get0();
835 f.close().get();
836
837 f = open_file_dma(filename, open_flags::rw).get0();
838 f.dma_read(0, buf.get(), 4096).get();
839 });
840 }
841
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);
846 size_t size = 1234;
847 std::fill_n(wbuf.get(), alignment, char(0));
848 std::fill_n(wbuf.get(), size, char(42));
849 std::vector<iovec> iovecs;
850
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();
857 f.close().get();
858
859 auto rbuf = allocate_aligned_buffer<char>(alignment, alignment);
860
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));
864 iovecs.clear();
865 iovecs.push_back(iovec{ rbuf.get(), alignment });
866 count = f.dma_read(0, iovecs).get0();
867 BOOST_REQUIRE_EQUAL(count, size);
868
869 BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
870
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));
874 iovecs.clear();
875 iovecs.push_back(iovec{ rbuf.get(), alignment });
876 count = f.dma_read(0, iovecs).get0();
877 BOOST_REQUIRE_EQUAL(count, size);
878
879 BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
880 });
881 }
882
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');
891 io_intent intent;
892 auto f1 = f.dma_write(0, buf.get(), 512);
893 auto f2 = f.dma_write(512, buf.get(), 512, default_priority_class(), &intent);
894 intent.cancel();
895
896 bool cancelled = false;
897 f1.get();
898 try {
899 f2.get();
900 } catch (cancelled_error& ex) {
901 cancelled = true;
902 }
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');
906 if (cancelled) {
907 BOOST_REQUIRE(rbuf.get()[512] == 'a');
908 } else {
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');
914 }
915 });
916 }
917
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) {
921 return async([&] {
922 // Check that overwrites at disk_overwrite_dma_alignment() do not cause stalls. First,
923 // create a file.
924 auto fname = (t.get_path() / "testfile.tmp").native();
925 auto sz = uint64_t(1*1024*1024);
926 auto buffer_size = 128*1024;
927
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();
934 }
935
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();
943 });
944 }).get0();
945 std::cout << "parallel_overwrite: " << stall_report << " (overwrite dma alignment " << f.disk_overwrite_dma_alignment() << ")\n";
946
947 f.close().get();
948 remove_file(fname).get();
949 });
950 });
951 }
952
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();
957
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;
961
962 auto buf = allocate_aligned_buffer<unsigned char>(buf_size, 4096);
963 std::fill(buf.get(), buf.get() + buf_size, 'a');
964
965 f.dma_write(0, buf.get(), buf_size).get();
966 f.flush().get0();
967 f.close().get();
968
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();
972
973 BOOST_REQUIRE((size_t)std::count_if(buf.get(), buf.get() + buf_size, [](auto x) { return x == 'a'; }) == buf_size);
974 });
975 }