]>
Commit | Line | Data |
---|---|---|
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 TL |
33 | #include <seastar/core/aligned_buffer.hh> |
34 | #include <seastar/util/tmp_file.hh> | |
35 | #include <seastar/util/alloc_failure_injector.hh> | |
36 | ||
9f95a23c | 37 | #include <boost/range/adaptor/transformed.hpp> |
11fdf7f2 | 38 | #include <iostream> |
f67539c2 | 39 | #include <sys/statfs.h> |
11fdf7f2 | 40 | |
9f95a23c TL |
41 | #include "core/file-impl.hh" |
42 | ||
11fdf7f2 | 43 | using namespace seastar; |
f67539c2 | 44 | namespace fs = std::filesystem; |
11fdf7f2 TL |
45 | |
46 | SEASTAR_TEST_CASE(open_flags_test) { | |
47 | open_flags flags = open_flags::rw | open_flags::create | open_flags::exclusive; | |
48 | BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) == | |
49 | (std::underlying_type_t<open_flags>(open_flags::rw) | | |
50 | std::underlying_type_t<open_flags>(open_flags::create) | | |
51 | std::underlying_type_t<open_flags>(open_flags::exclusive))); | |
52 | ||
53 | open_flags mask = open_flags::create | open_flags::exclusive; | |
54 | BOOST_REQUIRE((flags & mask) == mask); | |
55 | return make_ready_future<>(); | |
56 | } | |
57 | ||
9f95a23c TL |
58 | SEASTAR_TEST_CASE(access_flags_test) { |
59 | access_flags flags = access_flags::read | access_flags::write | access_flags::execute; | |
60 | BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) == | |
61 | (std::underlying_type_t<open_flags>(access_flags::read) | | |
62 | std::underlying_type_t<open_flags>(access_flags::write) | | |
63 | std::underlying_type_t<open_flags>(access_flags::execute))); | |
64 | return make_ready_future<>(); | |
65 | } | |
66 | ||
67 | SEASTAR_TEST_CASE(file_exists_test) { | |
f67539c2 TL |
68 | return tmp_dir::do_with_thread([] (tmp_dir& t) { |
69 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
9f95a23c TL |
70 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); |
71 | f.close().get(); | |
72 | auto exists = file_exists(filename).get0(); | |
73 | BOOST_REQUIRE(exists); | |
74 | remove_file(filename).get(); | |
75 | exists = file_exists(filename).get0(); | |
76 | BOOST_REQUIRE(!exists); | |
77 | }); | |
78 | } | |
79 | ||
f67539c2 TL |
80 | SEASTAR_TEST_CASE(handle_bad_alloc_test) { |
81 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
82 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
83 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); | |
84 | f.close().get(); | |
85 | bool exists = false; | |
86 | memory::with_allocation_failures([&] { | |
87 | exists = file_exists(filename).get0(); | |
88 | }); | |
89 | BOOST_REQUIRE(exists); | |
90 | }); | |
91 | } | |
92 | ||
9f95a23c | 93 | SEASTAR_TEST_CASE(file_access_test) { |
f67539c2 TL |
94 | return tmp_dir::do_with_thread([] (tmp_dir& t) { |
95 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
9f95a23c TL |
96 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); |
97 | f.close().get(); | |
98 | auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0(); | |
99 | BOOST_REQUIRE(is_accessible); | |
9f95a23c TL |
100 | }); |
101 | } | |
102 | ||
11fdf7f2 TL |
103 | struct file_test { |
104 | file_test(file&& f) : f(std::move(f)) {} | |
105 | file f; | |
106 | semaphore sem = { 0 }; | |
107 | semaphore par = { 1000 }; | |
108 | }; | |
109 | ||
110 | SEASTAR_TEST_CASE(test1) { | |
111 | // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB). | |
f67539c2 | 112 | return tmp_dir::do_with([] (tmp_dir& t) { |
11fdf7f2 | 113 | static constexpr auto max = 10000; |
f67539c2 TL |
114 | sstring filename = (t.get_path() / "testfile.tmp").native(); |
115 | return open_file_dma(filename, open_flags::rw | open_flags::create).then([filename] (file f) { | |
11fdf7f2 TL |
116 | auto ft = new file_test{std::move(f)}; |
117 | for (size_t i = 0; i < max; ++i) { | |
9f95a23c TL |
118 | // Don't wait for future, use semaphore to signal when done instead. |
119 | (void)ft->par.wait().then([ft, i] { | |
11fdf7f2 TL |
120 | auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); |
121 | std::fill(wbuf.get(), wbuf.get() + 4096, i); | |
122 | auto wb = wbuf.get(); | |
9f95a23c | 123 | (void)ft->f.dma_write(i * 4096, wb, 4096).then( |
11fdf7f2 TL |
124 | [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable { |
125 | BOOST_REQUIRE(ret == 4096); | |
126 | auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); | |
127 | auto rb = rbuf.get(); | |
9f95a23c | 128 | (void)ft->f.dma_read(i * 4096, rb, 4096).then( |
11fdf7f2 TL |
129 | [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable { |
130 | BOOST_REQUIRE(ret == 4096); | |
131 | BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get())); | |
132 | ft->sem.signal(1); | |
133 | ft->par.signal(); | |
134 | }); | |
135 | }); | |
136 | }); | |
137 | } | |
138 | return ft->sem.wait(max).then([ft] () mutable { | |
139 | return ft->f.flush(); | |
140 | }).then([ft] { | |
141 | return ft->f.close(); | |
142 | }).then([ft] () mutable { | |
11fdf7f2 TL |
143 | delete ft; |
144 | }); | |
145 | }); | |
f67539c2 | 146 | }); |
11fdf7f2 TL |
147 | } |
148 | ||
149 | SEASTAR_TEST_CASE(parallel_write_fsync) { | |
150 | return internal::report_reactor_stalls([] { | |
f67539c2 | 151 | return tmp_dir::do_with_thread([] (tmp_dir& t) { |
11fdf7f2 | 152 | // Plan: open a file and write to it like crazy. In parallel fsync() it all the time. |
f67539c2 | 153 | auto fname = (t.get_path() / "testfile.tmp").native(); |
11fdf7f2 TL |
154 | auto sz = uint64_t(32*1024*1024); |
155 | auto buffer_size = 32768; | |
156 | auto write_concurrency = 16; | |
157 | auto fsync_every = 1024*1024; | |
158 | auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly | |
159 | auto written = uint64_t(0); | |
160 | auto fsynced_at = uint64_t(0); | |
161 | ||
162 | file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0(); | |
163 | // Avoid filesystem problems with size-extending operations | |
164 | f.truncate(sz).get(); | |
165 | ||
166 | auto fsync_semaphore = semaphore(0); | |
167 | auto may_write_condvar = condition_variable(); | |
168 | auto fsync_thread = thread([&] { | |
169 | auto fsynced = uint64_t(0); | |
170 | while (fsynced < sz) { | |
171 | fsync_semaphore.wait(fsync_every).get(); | |
172 | fsynced_at = written; | |
173 | // Signal the condition variable now so that writes proceed | |
174 | // in parallel with the fsync | |
175 | may_write_condvar.broadcast(); | |
176 | f.flush().get(); | |
177 | fsynced += fsync_every; | |
178 | } | |
179 | }); | |
180 | ||
181 | auto write_semaphore = semaphore(write_concurrency); | |
182 | while (written < sz) { | |
183 | write_semaphore.wait().get(); | |
184 | may_write_condvar.wait([&] { | |
185 | return written <= fsynced_at + max_write_ahead_of_fsync; | |
186 | }).get(); | |
187 | auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size); | |
f67539c2 | 188 | memset(buf.get_write(), 0, buf.size()); |
9f95a23c TL |
189 | // Write asynchronously, signal when done. |
190 | (void)f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) { | |
11fdf7f2 TL |
191 | fsync_semaphore.signal(buf.size()); |
192 | write_semaphore.signal(); | |
193 | }); | |
194 | written += buffer_size; | |
195 | } | |
196 | write_semaphore.wait(write_concurrency).get(); | |
197 | ||
198 | fsync_thread.join().get(); | |
199 | f.close().get(); | |
200 | remove_file(fname).get(); | |
201 | }); | |
202 | }).then([] (internal::stall_report sr) { | |
203 | std::cout << "parallel_write_fsync: " << sr << "\n"; | |
204 | }); | |
205 | } | |
9f95a23c | 206 | |
f67539c2 TL |
207 | SEASTAR_TEST_CASE(test_iov_max) { |
208 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
9f95a23c TL |
209 | static constexpr size_t buffer_size = 4096; |
210 | static constexpr size_t buffer_count = IOV_MAX * 2 + 1; | |
211 | ||
212 | std::vector<temporary_buffer<char>> original_buffers; | |
213 | std::vector<iovec> iovecs; | |
214 | for (auto i = 0u; i < buffer_count; i++) { | |
215 | original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size)); | |
216 | std::fill_n(original_buffers.back().get_write(), buffer_size, char(i)); | |
217 | iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size }); | |
218 | } | |
219 | ||
f67539c2 TL |
220 | auto filename = (t.get_path() / "testfile.tmp").native(); |
221 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); | |
9f95a23c TL |
222 | size_t left = buffer_size * buffer_count; |
223 | size_t position = 0; | |
224 | while (left) { | |
225 | auto written = f.dma_write(position, iovecs).get0(); | |
226 | iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size); | |
227 | assert(written % buffer_size == 0); | |
228 | position += written; | |
229 | left -= written; | |
230 | } | |
231 | ||
232 | BOOST_CHECK(iovecs.empty()); | |
233 | ||
234 | std::vector<temporary_buffer<char>> read_buffers; | |
235 | for (auto i = 0u; i < buffer_count; i++) { | |
236 | read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size)); | |
237 | std::fill_n(read_buffers.back().get_write(), buffer_size, char(0)); | |
238 | iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size }); | |
239 | } | |
240 | ||
241 | left = buffer_size * buffer_count; | |
242 | position = 0; | |
243 | while (left) { | |
244 | auto read = f.dma_read(position, iovecs).get0(); | |
245 | iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size); | |
246 | assert(read % buffer_size == 0); | |
247 | position += read; | |
248 | left -= read; | |
249 | } | |
250 | ||
251 | for (auto i = 0u; i < buffer_count; i++) { | |
252 | BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(), | |
253 | read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size())); | |
254 | } | |
255 | ||
256 | f.close().get(); | |
f67539c2 | 257 | }); |
9f95a23c TL |
258 | } |
259 | ||
260 | SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) { | |
261 | auto buf = temporary_buffer<char>::aligned(4096, 4096); | |
262 | ||
263 | auto iovec_equal = [] (const iovec& a, const iovec& b) { | |
264 | return a.iov_base == b.iov_base && a.iov_len == b.iov_len; | |
265 | }; | |
266 | ||
267 | { // Single fragment, sanitize is noop | |
268 | auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } }; | |
269 | auto actual_iovecs = original_iovecs; | |
270 | auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); | |
271 | BOOST_CHECK_EQUAL(actual_length, 4096); | |
272 | BOOST_CHECK_EQUAL(actual_iovecs.size(), 1); | |
273 | BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back())); | |
274 | } | |
275 | ||
276 | { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers | |
277 | auto original_iovecs = std::vector<iovec>{}; | |
278 | for (auto i = 0u; i < IOV_MAX + 7; i++) { | |
279 | original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u }); | |
280 | } | |
281 | auto actual_iovecs = original_iovecs; | |
282 | auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); | |
283 | BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); | |
284 | BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1); | |
285 | ||
286 | original_iovecs.resize(IOV_MAX - 1); | |
287 | BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), | |
288 | actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); | |
289 | } | |
290 | ||
291 | { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers | |
292 | auto original_iovecs = std::vector<iovec>{}; | |
293 | for (auto i = 0u; i < IOV_MAX + 7; i++) { | |
294 | original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u }); | |
295 | } | |
296 | auto actual_iovecs = original_iovecs; | |
297 | auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); | |
298 | BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); | |
299 | BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX); | |
300 | ||
301 | original_iovecs.resize(IOV_MAX); | |
302 | original_iovecs.back().iov_len = 512; | |
303 | BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), | |
304 | actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); | |
305 | } | |
306 | ||
307 | { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers | |
308 | auto original_iovecs = std::vector<iovec>{}; | |
309 | for (auto i = 0u; i < IOV_MAX + 8; i++) { | |
310 | original_iovecs.emplace_back(iovec { buf.get_write(), 512 }); | |
311 | } | |
312 | auto actual_iovecs = original_iovecs; | |
313 | auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096); | |
314 | BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX); | |
315 | BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX); | |
316 | ||
317 | original_iovecs.resize(IOV_MAX); | |
318 | BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(), | |
319 | actual_iovecs.begin(), actual_iovecs.end(), iovec_equal)); | |
320 | } | |
321 | } | |
322 | ||
f67539c2 TL |
323 | SEASTAR_TEST_CASE(test_chmod) { |
324 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
9f95a23c | 325 | auto oflags = open_flags::rw | open_flags::create; |
f67539c2 | 326 | sstring filename = (t.get_path() / "testfile.tmp").native(); |
9f95a23c TL |
327 | if (file_exists(filename).get0()) { |
328 | remove_file(filename).get(); | |
329 | } | |
330 | ||
331 | auto orig_umask = umask(0); | |
332 | ||
333 | // test default_file_permissions | |
334 | auto f = open_file_dma(filename, oflags).get0(); | |
335 | f.close().get(); | |
336 | auto sd = file_stat(filename).get0(); | |
337 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); | |
338 | ||
339 | // test chmod with new_permissions | |
340 | auto new_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; | |
341 | BOOST_REQUIRE(new_permissions != file_permissions::default_file_permissions); | |
342 | BOOST_REQUIRE(file_exists(filename).get0()); | |
343 | chmod(filename, new_permissions).get(); | |
344 | sd = file_stat(filename).get0(); | |
345 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(new_permissions)); | |
346 | remove_file(filename).get(); | |
347 | ||
348 | umask(orig_umask); | |
f67539c2 | 349 | }); |
9f95a23c TL |
350 | } |
351 | ||
f67539c2 TL |
352 | SEASTAR_TEST_CASE(test_open_file_dma_permissions) { |
353 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
9f95a23c | 354 | auto oflags = open_flags::rw | open_flags::create; |
f67539c2 | 355 | sstring filename = (t.get_path() / "testfile.tmp").native(); |
9f95a23c TL |
356 | if (file_exists(filename).get0()) { |
357 | remove_file(filename).get(); | |
358 | } | |
359 | ||
360 | auto orig_umask = umask(0); | |
361 | ||
362 | // test default_file_permissions | |
363 | auto f = open_file_dma(filename, oflags).get0(); | |
364 | f.close().get(); | |
365 | auto sd = file_stat(filename).get0(); | |
366 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); | |
367 | remove_file(filename).get(); | |
368 | ||
369 | // test options.create_permissions | |
370 | auto options = file_open_options(); | |
371 | options.create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; | |
372 | BOOST_REQUIRE(options.create_permissions != file_permissions::default_file_permissions); | |
373 | f = open_file_dma(filename, oflags, options).get0(); | |
374 | f.close().get(); | |
375 | sd = file_stat(filename).get0(); | |
376 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(options.create_permissions)); | |
377 | remove_file(filename).get(); | |
378 | ||
379 | umask(orig_umask); | |
f67539c2 | 380 | }); |
9f95a23c TL |
381 | } |
382 | ||
f67539c2 TL |
383 | SEASTAR_TEST_CASE(test_make_directory_permissions) { |
384 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
385 | sstring dirname = (t.get_path() / "testdir.tmp").native(); | |
9f95a23c TL |
386 | auto orig_umask = umask(0); |
387 | ||
388 | // test default_dir_permissions with make_directory | |
389 | make_directory(dirname).get(); | |
390 | auto sd = file_stat(dirname).get0(); | |
391 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
392 | remove_file(dirname).get(); | |
393 | ||
394 | // test make_directory | |
395 | auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; | |
396 | BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); | |
397 | make_directory(dirname, create_permissions).get(); | |
398 | sd = file_stat(dirname).get0(); | |
399 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); | |
400 | remove_file(dirname).get(); | |
401 | ||
402 | umask(orig_umask); | |
f67539c2 | 403 | }); |
9f95a23c TL |
404 | } |
405 | ||
f67539c2 TL |
406 | SEASTAR_TEST_CASE(test_touch_directory_permissions) { |
407 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
408 | sstring dirname = (t.get_path() / "testdir.tmp").native(); | |
9f95a23c TL |
409 | auto orig_umask = umask(0); |
410 | ||
411 | // test default_dir_permissions with touch_directory | |
412 | touch_directory(dirname).get(); | |
413 | auto sd = file_stat(dirname).get0(); | |
414 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
415 | remove_file(dirname).get(); | |
416 | ||
417 | // test touch_directory, dir creation | |
418 | auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; | |
419 | BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); | |
420 | BOOST_REQUIRE(!file_exists(dirname).get0()); | |
421 | touch_directory(dirname, create_permissions).get(); | |
422 | sd = file_stat(dirname).get0(); | |
423 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); | |
424 | ||
425 | // test touch_directory of existing dir, dir mode need not change | |
426 | BOOST_REQUIRE(file_exists(dirname).get0()); | |
427 | touch_directory(dirname, file_permissions::default_dir_permissions).get(); | |
428 | sd = file_stat(dirname).get0(); | |
429 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); | |
430 | remove_file(dirname).get(); | |
431 | ||
432 | umask(orig_umask); | |
f67539c2 | 433 | }); |
9f95a23c TL |
434 | } |
435 | ||
f67539c2 TL |
436 | SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions) { |
437 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
438 | sstring base_dirname = (t.get_path() / "testbasedir.tmp").native(); | |
439 | sstring dirpath = base_dirname + "/" + "testsubdir.tmp"; | |
9f95a23c TL |
440 | if (file_exists(dirpath).get0()) { |
441 | remove_file(dirpath).get(); | |
442 | } | |
443 | if (file_exists(base_dirname).get0()) { | |
444 | remove_file(base_dirname).get(); | |
445 | } | |
446 | ||
447 | auto orig_umask = umask(0); | |
448 | ||
449 | // test default_dir_permissions with recursive_touch_directory | |
450 | recursive_touch_directory(dirpath).get(); | |
451 | auto sd = file_stat(base_dirname).get0(); | |
452 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
453 | sd = file_stat(dirpath).get0(); | |
454 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
455 | remove_file(dirpath).get(); | |
456 | ||
457 | // test recursive_touch_directory, dir creation | |
458 | auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read; | |
459 | BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions); | |
460 | BOOST_REQUIRE(file_exists(base_dirname).get0()); | |
461 | BOOST_REQUIRE(!file_exists(dirpath).get0()); | |
462 | recursive_touch_directory(dirpath, create_permissions).get(); | |
463 | sd = file_stat(base_dirname).get0(); | |
464 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
465 | sd = file_stat(dirpath).get0(); | |
466 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); | |
467 | ||
468 | // test recursive_touch_directory of existing dir, dir mode need not change | |
469 | BOOST_REQUIRE(file_exists(dirpath).get0()); | |
470 | recursive_touch_directory(dirpath, file_permissions::default_dir_permissions).get(); | |
471 | sd = file_stat(base_dirname).get0(); | |
472 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions)); | |
473 | sd = file_stat(dirpath).get0(); | |
474 | BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions)); | |
475 | remove_file(dirpath).get(); | |
476 | remove_file(base_dirname).get(); | |
477 | ||
478 | umask(orig_umask); | |
f67539c2 TL |
479 | }); |
480 | } | |
481 | ||
482 | SEASTAR_TEST_CASE(test_file_stat_method) { | |
483 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
484 | auto oflags = open_flags::rw | open_flags::create; | |
485 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
486 | ||
487 | auto orig_umask = umask(0); | |
488 | ||
489 | auto f = open_file_dma(filename, oflags).get0(); | |
490 | auto st = f.stat().get0(); | |
491 | f.close().get(); | |
492 | BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); | |
493 | ||
494 | umask(orig_umask); | |
495 | }); | |
496 | } | |
497 | ||
498 | ||
499 | class test_layered_file : public layered_file_impl { | |
500 | public: | |
501 | explicit test_layered_file(file f) : layered_file_impl(std::move(f)) {} | |
502 | virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len, const io_priority_class& pc) override { | |
503 | abort(); | |
504 | } | |
505 | virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override { | |
506 | abort(); | |
507 | } | |
508 | virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override { | |
509 | abort(); | |
510 | } | |
511 | virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override { | |
512 | abort(); | |
513 | } | |
514 | virtual future<> flush(void) override { | |
515 | abort(); | |
516 | } | |
517 | virtual future<struct stat> stat(void) override { | |
518 | abort(); | |
519 | } | |
520 | virtual future<> truncate(uint64_t length) override { | |
521 | abort(); | |
522 | } | |
523 | virtual future<> discard(uint64_t offset, uint64_t length) override { | |
524 | abort(); | |
525 | } | |
526 | virtual future<> allocate(uint64_t position, uint64_t length) override { | |
527 | abort(); | |
528 | } | |
529 | virtual future<uint64_t> size(void) override { | |
530 | abort(); | |
531 | } | |
532 | virtual future<> close() override { | |
533 | abort(); | |
534 | } | |
535 | virtual std::unique_ptr<file_handle_impl> dup() override { | |
536 | abort(); | |
537 | } | |
538 | virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override { | |
539 | abort(); | |
540 | } | |
541 | virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override { | |
542 | abort(); | |
543 | } | |
544 | }; | |
545 | ||
546 | SEASTAR_TEST_CASE(test_underlying_file) { | |
547 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
548 | auto oflags = open_flags::rw | open_flags::create; | |
549 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
550 | auto f = open_file_dma(filename, oflags).get0(); | |
551 | auto lf = file(make_shared<test_layered_file>(f)); | |
552 | BOOST_CHECK_EQUAL(f.memory_dma_alignment(), lf.memory_dma_alignment()); | |
553 | BOOST_CHECK_EQUAL(f.disk_read_dma_alignment(), lf.disk_read_dma_alignment()); | |
554 | BOOST_CHECK_EQUAL(f.disk_write_dma_alignment(), lf.disk_write_dma_alignment()); | |
555 | f.close().get(); | |
556 | }); | |
557 | } | |
558 | ||
559 | SEASTAR_TEST_CASE(test_file_stat_method_with_file) { | |
560 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
561 | auto oflags = open_flags::rw | open_flags::create | open_flags::truncate; | |
562 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
563 | file ref; | |
564 | ||
565 | auto orig_umask = umask(0); | |
566 | ||
567 | auto st = with_file(open_file_dma(filename, oflags), [&ref] (file& f) { | |
568 | // make a copy of f to verify f is auto-closed when `with_file` returns. | |
569 | ref = f; | |
570 | return f.stat(); | |
571 | }).get0(); | |
572 | BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); | |
573 | ||
574 | // verify that the file was auto-closed | |
575 | BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error); | |
576 | ||
577 | umask(orig_umask); | |
578 | }); | |
579 | } | |
580 | ||
581 | SEASTAR_TEST_CASE(test_open_error_with_file) { | |
582 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
583 | auto open_file = [&t] (bool do_open) { | |
584 | auto oflags = open_flags::ro; | |
585 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
586 | if (do_open) { | |
587 | return open_file_dma(filename, oflags); | |
588 | } else { | |
589 | throw std::runtime_error("expected exception"); | |
590 | } | |
591 | }; | |
592 | bool got_exception = false; | |
593 | ||
594 | BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file& f) { | |
595 | BOOST_REQUIRE(false); | |
596 | }).handle_exception_type([&got_exception] (const std::system_error& e) { | |
597 | got_exception = true; | |
598 | BOOST_REQUIRE(e.code().value() == ENOENT); | |
599 | }).get()); | |
600 | BOOST_REQUIRE(got_exception); | |
601 | ||
602 | got_exception = false; | |
603 | BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file& f) { | |
604 | BOOST_REQUIRE(false); | |
605 | }).handle_exception_type([&got_exception] (const std::runtime_error& e) { | |
606 | got_exception = true; | |
607 | }).get(), std::runtime_error); | |
608 | BOOST_REQUIRE(!got_exception); | |
609 | }); | |
610 | } | |
611 | ||
612 | SEASTAR_TEST_CASE(test_with_file_close_on_failure) { | |
613 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
614 | auto oflags = open_flags::rw | open_flags::create | open_flags::truncate; | |
615 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
616 | ||
617 | auto orig_umask = umask(0); | |
618 | ||
619 | // error-free case | |
620 | auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) { | |
621 | return f; | |
622 | }).get0(); | |
623 | auto st = ref.stat().get0(); | |
624 | ref.close().get(); | |
625 | BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions)); | |
626 | ||
627 | // close-on-error case | |
628 | BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) { | |
629 | ref = f; | |
630 | throw std::runtime_error("expected exception"); | |
631 | }).get(), std::runtime_error); | |
632 | ||
633 | // verify that file was auto-closed on error | |
634 | BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error); | |
635 | ||
636 | umask(orig_umask); | |
637 | }); | |
638 | } | |
639 | ||
640 | namespace seastar { | |
641 | extern bool aio_nowait_supported; | |
642 | } | |
643 | ||
644 | SEASTAR_TEST_CASE(test_nowait_flag_correctness) { | |
645 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
646 | auto oflags = open_flags::rw | open_flags::create; | |
647 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
648 | auto is_tmpfs = [&] (sstring filename) { | |
649 | struct ::statfs buf; | |
650 | int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro)); | |
651 | assert(fd != -1); | |
652 | auto r = ::fstatfs(fd, &buf); | |
653 | if (r == -1) { | |
654 | return false; | |
655 | } | |
656 | return buf.f_type == 0x01021994; // TMPFS_MAGIC | |
657 | }; | |
658 | ||
659 | if (!seastar::aio_nowait_supported) { | |
660 | BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system"); | |
661 | return; | |
662 | } | |
663 | ||
664 | auto f = open_file_dma(filename, oflags).get0(); | |
665 | auto close_f = defer([&] { f.close().get(); }); | |
666 | ||
667 | if (is_tmpfs(filename)) { | |
668 | BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes"); | |
669 | return; | |
670 | } | |
671 | ||
672 | for (auto i = 0; i < 10; i++) { | |
673 | auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096); | |
674 | std::fill(wbuf.get(), wbuf.get() + 4096, i); | |
675 | auto wb = wbuf.get(); | |
676 | f.dma_write(i * 4096, wb, 4096).get(); | |
677 | f.flush().get0(); | |
678 | } | |
679 | }); | |
680 | } | |
681 | ||
682 | SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file) { | |
683 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
684 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
685 | auto oflags = open_flags::rw | open_flags::create; | |
686 | auto f = open_file_dma(filename, oflags).get0(); | |
687 | }); | |
688 | } | |
689 | ||
690 | SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write) { | |
691 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
692 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
693 | auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096); | |
694 | std::fill(buf.get(), buf.get() + 4096, 0); | |
695 | ||
696 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); | |
697 | f.dma_write(0, buf.get(), 4096).get(); | |
698 | }); | |
699 | } | |
700 | ||
701 | SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read) { | |
702 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
703 | sstring filename = (t.get_path() / "testfile.tmp").native(); | |
704 | auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096); | |
705 | std::fill(buf.get(), buf.get() + 4096, 0); | |
706 | ||
707 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); | |
708 | f.dma_write(0, buf.get(), 4096).get(); | |
709 | f.flush().get0(); | |
710 | f.close().get(); | |
711 | ||
712 | f = open_file_dma(filename, open_flags::rw).get0(); | |
713 | f.dma_read(0, buf.get(), 4096).get(); | |
714 | }); | |
715 | } | |
716 | ||
717 | SEASTAR_TEST_CASE(test_dma_iovec) { | |
718 | return tmp_dir::do_with_thread([] (tmp_dir& t) { | |
719 | static constexpr size_t alignment = 4096; | |
720 | auto wbuf = allocate_aligned_buffer<char>(alignment, alignment); | |
721 | size_t size = 1234; | |
722 | std::fill_n(wbuf.get(), alignment, char(0)); | |
723 | std::fill_n(wbuf.get(), size, char(42)); | |
724 | std::vector<iovec> iovecs; | |
725 | ||
726 | auto filename = (t.get_path() / "testfile.tmp").native(); | |
727 | auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0(); | |
728 | iovecs.push_back(iovec{ wbuf.get(), alignment }); | |
729 | auto count = f.dma_write(0, iovecs).get0(); | |
730 | BOOST_REQUIRE_EQUAL(count, alignment); | |
731 | f.truncate(size).get(); | |
732 | f.close().get(); | |
733 | ||
734 | auto rbuf = allocate_aligned_buffer<char>(alignment, alignment); | |
735 | ||
736 | // this tests the posix_file_impl | |
737 | f = open_file_dma(filename, open_flags::ro).get0(); | |
738 | std::fill_n(rbuf.get(), alignment, char(0)); | |
739 | iovecs.clear(); | |
740 | iovecs.push_back(iovec{ rbuf.get(), alignment }); | |
741 | count = f.dma_read(0, iovecs).get0(); | |
742 | BOOST_REQUIRE_EQUAL(count, size); | |
743 | ||
744 | BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment)); | |
745 | ||
746 | // this tests the append_challenged_posix_file_impl | |
747 | f = open_file_dma(filename, open_flags::rw).get0(); | |
748 | std::fill_n(rbuf.get(), alignment, char(0)); | |
749 | iovecs.clear(); | |
750 | iovecs.push_back(iovec{ rbuf.get(), alignment }); | |
751 | count = f.dma_read(0, iovecs).get0(); | |
752 | BOOST_REQUIRE_EQUAL(count, size); | |
753 | ||
754 | BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment)); | |
755 | }); | |
9f95a23c | 756 | } |