Skip to content

Commit b039c65

Browse files
committed
continues
1 parent b59f4eb commit b039c65

5 files changed

Lines changed: 189 additions & 51 deletions

File tree

include/aws/s3/private/s3_meta_request_impl.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,6 @@ struct aws_s3_meta_request {
319319
bool recv_file_delete_on_failure;
320320
/* When true, use O_DIRECT for writing received data to file */
321321
bool recv_file_direct_io;
322-
/* Base file position for O_DIRECT writes (from recv_file_position option) */
323-
uint64_t recv_file_base_position;
324322

325323
/* File I/O options. */
326324
struct aws_s3_file_io_options fio_opts;

include/aws/s3/s3_client.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ struct aws_s3_file_io_options {
350350
* Notes:
351351
* - Only supported on linux for now.
352352
* - Supported for both upload (send_filepath) and download (recv_filepath).
353-
* - For download, O_DIRECT is not supported with AWS_S3_RECV_FILE_CREATE_OR_APPEND.
353+
* - For download, O_DIRECT is only supported with AWS_S3_RECV_FILE_CREATE_OR_REPLACE
354+
* and AWS_S3_RECV_FILE_CREATE_NEW (i.e. writing from the beginning of the file).
355+
* APPEND and WRITE_TO_POSITION are not supported with O_DIRECT.
354356
* - Check NOTES for O_DIRECT for additional info https://man7.org/linux/man-pages/man2/openat.2.html
355357
* In summary, O_DIRECT is a potentially powerful tool that should be used with caution.
356358
*/

source/s3_meta_request.c

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,10 @@ int aws_s3_meta_request_init_base(
290290
meta_request->recv_file_delete_on_failure = options->recv_file_delete_on_failure;
291291

292292
/* When direct_io is enabled, use O_DIRECT fd-based writes instead of FILE* fwrite.
293-
* Supported for CREATE_OR_REPLACE, CREATE_NEW, and WRITE_TO_POSITION.
294-
* APPEND is incompatible with O_DIRECT (no offset-based writes). */
293+
* Only supported for CREATE_OR_REPLACE and CREATE_NEW (writing from the beginning). */
295294
if (meta_request->fio_opts.direct_io &&
296-
options->recv_file_option != AWS_S3_RECV_FILE_CREATE_OR_APPEND) {
295+
(options->recv_file_option == AWS_S3_RECV_FILE_CREATE_OR_REPLACE ||
296+
options->recv_file_option == AWS_S3_RECV_FILE_CREATE_NEW)) {
297297

298298
/* Validate preconditions same as the FILE* path */
299299
if (options->recv_file_option == AWS_S3_RECV_FILE_CREATE_NEW &&
@@ -305,26 +305,23 @@ int aws_s3_meta_request_init_base(
305305
aws_raise_error(AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS);
306306
goto error;
307307
}
308-
if (options->recv_file_option == AWS_S3_RECV_FILE_WRITE_TO_POSITION &&
309-
!aws_path_exists(meta_request->recv_filepath)) {
310-
AWS_LOGF_ERROR(
311-
AWS_LS_S3_META_REQUEST,
312-
"id=%p Cannot receive file via WRITE_TO_POSITION: file not found.",
313-
(void *)meta_request);
314-
aws_raise_error(AWS_ERROR_S3_RECV_FILE_NOT_FOUND);
315-
goto error;
316-
}
317308

318309
meta_request->recv_file_direct_io = true;
319-
meta_request->recv_file_base_position =
320-
(options->recv_file_option == AWS_S3_RECV_FILE_WRITE_TO_POSITION) ? options->recv_file_position : 0;
321310

322311
AWS_LOGF_DEBUG(
323312
AWS_LS_S3_META_REQUEST,
324-
"id=%p: O_DIRECT enabled for download write path. base_position:%" PRIu64,
325-
(void *)meta_request,
326-
meta_request->recv_file_base_position);
313+
"id=%p: O_DIRECT enabled for download write path.",
314+
(void *)meta_request);
327315
} else {
316+
/* Fail if user requested O_DIRECT with APPEND or WRITE_TO_POSITION */
317+
if (meta_request->fio_opts.direct_io) {
318+
AWS_LOGF_ERROR(
319+
AWS_LS_S3_META_REQUEST,
320+
"id=%p O_DIRECT for download is only supported with CREATE_OR_REPLACE and CREATE_NEW",
321+
(void *)meta_request);
322+
aws_raise_error(AWS_ERROR_UNSUPPORTED_OPERATION);
323+
goto error;
324+
}
328325
/* Standard FILE* path */
329326
switch (options->recv_file_option) {
330327
case AWS_S3_RECV_FILE_CREATE_OR_REPLACE:
@@ -2185,29 +2182,26 @@ static void s_s3_meta_request_event_delivery_task(struct aws_task *task, void *a
21852182

21862183
if (meta_request->recv_file_direct_io) {
21872184
/* O_DIRECT write path — use offset-based direct I/O */
2188-
uint64_t write_offset =
2189-
meta_request->recv_file_base_position + delivery_range_start;
2185+
uint64_t write_offset = delivery_range_start;
21902186
struct aws_byte_cursor write_cursor =
21912187
aws_byte_cursor_from_array(response_body.ptr, response_body.len);
21922188
if (aws_file_path_write_to_offset_direct_io(
21932189
meta_request->recv_filepath, write_offset, write_cursor)) {
21942190
if (aws_last_error() == AWS_ERROR_UNSUPPORTED_OPERATION) {
2195-
/* O_DIRECT not supported, fall back to FILE* for remainder */
2191+
/* Platform doesn't support O_DIRECT, fall back to buffered I/O */
21962192
AWS_LOGF_WARN(
21972193
AWS_LS_S3_META_REQUEST,
2198-
"id=%p: O_DIRECT write not supported, falling back to buffered I/O",
2194+
"id=%p: O_DIRECT write not supported on this platform, "
2195+
"falling back to buffered I/O",
21992196
(void *)meta_request);
22002197
meta_request->recv_file_direct_io = false;
22012198
aws_reset_error();
2202-
/* Open FILE* and seek to current position */
2199+
/* Open FILE* and write this chunk */
22032200
meta_request->recv_file = aws_fopen(
22042201
aws_string_c_str(meta_request->recv_filepath), "r+");
22052202
if (meta_request->recv_file &&
2206-
aws_fseek(
2207-
meta_request->recv_file,
2208-
(int64_t)(meta_request->recv_file_base_position + delivery_range_start),
2209-
SEEK_SET) == AWS_OP_SUCCESS) {
2210-
/* Retry this write with fwrite */
2203+
aws_fseek(meta_request->recv_file, (int64_t)write_offset, SEEK_SET) ==
2204+
AWS_OP_SUCCESS) {
22112205
if (fwrite(
22122206
(void *)response_body.ptr,
22132207
response_body.len,
@@ -2222,14 +2216,8 @@ static void s_s3_meta_request_event_delivery_task(struct aws_task *task, void *a
22222216
} else {
22232217
error_code = aws_last_error();
22242218
}
2225-
if (error_code != AWS_ERROR_SUCCESS) {
2226-
AWS_LOGF_ERROR(
2227-
AWS_LS_S3_META_REQUEST,
2228-
"id=%p Failed O_DIRECT fallback to buffered write. aws-error:%s",
2229-
(void *)meta_request,
2230-
aws_error_name(error_code));
2231-
}
22322219
} else {
2220+
/* Real I/O error — hard fail */
22332221
error_code = aws_last_error();
22342222
AWS_LOGF_ERROR(
22352223
AWS_LS_S3_META_REQUEST,

tests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ add_net_test_case(test_s3_get_object_file_path_create_new)
7979
add_net_test_case(test_s3_get_object_file_path_append)
8080
add_net_test_case(test_s3_get_object_file_path_to_position)
8181
add_net_test_case(test_s3_get_object_file_path_direct_io)
82-
add_net_test_case(test_s3_get_object_file_path_direct_io_to_position)
82+
add_net_test_case(test_s3_get_object_file_path_direct_io_content_verify)
83+
add_net_test_case(test_s3_get_object_file_path_direct_io_unsupported_append)
84+
add_net_test_case(test_s3_get_object_file_path_direct_io_unsupported_write_to_position)
8385
add_net_test_case(test_s3_get_object_empty_object)
8486
add_net_test_case(test_s3_get_object_multiple)
8587
add_net_test_case(test_s3_get_object_multiple_serial)

tests/s3_data_plane_tests.c

Lines changed: 161 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "aws/s3/private/s3_checksums.h"
77
#include "aws/s3/private/s3_client_impl.h"
8+
#include <aws/checksums/crc.h>
89
#include "aws/s3/private/s3_default_buffer_pool.h"
910
#include "aws/s3/private/s3_meta_request_impl.h"
1011
#include "aws/s3/private/s3_util.h"
@@ -28,6 +29,12 @@
2829
#include <aws/testing/stream_tester.h>
2930
#include <inttypes.h>
3031

32+
/* Forward declaration for checksum validation callback used in O_DIRECT tests */
33+
void s_s3_test_validate_checksum(
34+
struct aws_s3_meta_request *meta_request,
35+
const struct aws_s3_meta_request_result *result,
36+
void *user_data);
37+
3138
AWS_TEST_CASE(test_s3_client_create_destroy, s_test_s3_client_create_destroy)
3239
static int s_test_s3_client_create_destroy(struct aws_allocator *allocator, void *ctx) {
3340
(void)ctx;
@@ -1688,21 +1695,122 @@ static int s_test_s3_get_object_file_path_direct_io(struct aws_allocator *alloca
16881695
}
16891696

16901697
AWS_TEST_CASE(
1691-
test_s3_get_object_file_path_direct_io_to_position,
1692-
s_test_s3_get_object_file_path_direct_io_to_position)
1693-
static int s_test_s3_get_object_file_path_direct_io_to_position(struct aws_allocator *allocator, void *ctx) {
1698+
test_s3_get_object_file_path_direct_io_content_verify,
1699+
s_test_s3_get_object_file_path_direct_io_content_verify)
1700+
static int s_test_s3_get_object_file_path_direct_io_content_verify(struct aws_allocator *allocator, void *ctx) {
16941701
(void)ctx;
16951702

16961703
struct aws_s3_tester tester;
1697-
AWS_ZERO_STRUCT(tester);
16981704
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
16991705

17001706
struct aws_s3_tester_client_options client_options = {
17011707
.part_size = MB_TO_BYTES(5),
17021708
};
17031709

1710+
struct aws_s3_client *client = NULL;
1711+
ASSERT_SUCCESS(aws_s3_tester_client_new(&tester, &client_options, &client));
1712+
1713+
/* Upload with CRC32 checksum */
1714+
struct aws_byte_buf path_buf;
1715+
AWS_ZERO_STRUCT(path_buf);
1716+
ASSERT_SUCCESS(aws_s3_tester_upload_file_path_init(
1717+
allocator, &path_buf, aws_byte_cursor_from_c_str("/prefix/round_trip/direct_io_verify.txt")));
1718+
struct aws_byte_cursor object_path = aws_byte_cursor_from_buf(&path_buf);
1719+
1720+
struct aws_s3_tester_meta_request_options put_options = {
1721+
.allocator = allocator,
1722+
.meta_request_type = AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,
1723+
.client = client,
1724+
.checksum_algorithm = AWS_SCA_CRC32,
1725+
.put_options =
1726+
{
1727+
.object_size_mb = 1,
1728+
.object_path_override = object_path,
1729+
},
1730+
};
1731+
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &put_options, NULL));
1732+
1733+
/* Download with O_DIRECT to a known file path */
1734+
const char *local_file_path = "/tmp/aws_s3_direct_io_crc32_verify";
1735+
struct aws_string *host_name =
1736+
aws_s3_tester_build_endpoint_string(allocator, &g_test_bucket_name, &g_test_s3_region);
1737+
struct aws_byte_cursor host_cursor = aws_byte_cursor_from_string(host_name);
1738+
struct aws_http_message *message = aws_s3_test_get_object_request_new(allocator, host_cursor, object_path);
1739+
1740+
struct aws_s3_checksum_config checksum_config = {
1741+
.validate_response_checksum = true,
1742+
};
1743+
struct aws_s3_file_io_options fio_opts = {
1744+
.direct_io = true,
1745+
};
1746+
struct aws_s3_meta_request_options meta_request_options = {
1747+
.type = AWS_S3_META_REQUEST_TYPE_GET_OBJECT,
1748+
.message = message,
1749+
.recv_filepath = aws_byte_cursor_from_c_str(local_file_path),
1750+
.checksum_config = &checksum_config,
1751+
.fio_opts = &fio_opts,
1752+
};
1753+
17041754
struct aws_s3_meta_request_test_results meta_request_test_results;
17051755
aws_s3_meta_request_test_results_init(&meta_request_test_results, allocator);
1756+
ASSERT_SUCCESS(aws_s3_tester_bind_meta_request(&tester, &meta_request_options, &meta_request_test_results));
1757+
1758+
struct aws_s3_meta_request *meta_request = aws_s3_client_make_meta_request(client, &meta_request_options);
1759+
ASSERT_NOT_NULL(meta_request);
1760+
1761+
aws_s3_tester_wait_for_meta_request_finish(&tester);
1762+
ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, meta_request_test_results.finished_error_code);
1763+
/* S3 response checksum was validated */
1764+
ASSERT_TRUE(meta_request_test_results.did_validate);
1765+
ASSERT_INT_EQUALS(AWS_SCA_CRC32, meta_request_test_results.validation_algorithm);
1766+
1767+
aws_s3_meta_request_release(meta_request);
1768+
aws_s3_tester_wait_for_meta_request_shutdown(&tester);
1769+
1770+
/* Read file back from disk and compute CRC32.
1771+
* The tester uploads content using AWS_AUTOGEN_LOREM_IPSUM pattern. */
1772+
size_t expected_size = MB_TO_BYTES(1);
1773+
struct aws_byte_buf expected_buf;
1774+
s_byte_buf_init_autogenned(&expected_buf, allocator, expected_size, AWS_AUTOGEN_LOREM_IPSUM);
1775+
uint32_t expected_crc = aws_checksums_crc32(expected_buf.buffer, (int)expected_buf.len, 0);
1776+
1777+
FILE *verify_file = aws_fopen(local_file_path, "rb");
1778+
ASSERT_NOT_NULL(verify_file);
1779+
struct aws_byte_buf file_buf;
1780+
aws_byte_buf_init(&file_buf, allocator, expected_size);
1781+
file_buf.len = fread(file_buf.buffer, 1, expected_size, verify_file);
1782+
fclose(verify_file);
1783+
1784+
ASSERT_UINT_EQUALS(expected_size, file_buf.len);
1785+
uint32_t actual_crc = aws_checksums_crc32(file_buf.buffer, (int)file_buf.len, 0);
1786+
ASSERT_UINT_EQUALS(expected_crc, actual_crc);
1787+
1788+
/* Cleanup */
1789+
remove(local_file_path);
1790+
aws_byte_buf_clean_up(&file_buf);
1791+
aws_byte_buf_clean_up(&expected_buf);
1792+
aws_s3_meta_request_test_results_clean_up(&meta_request_test_results);
1793+
aws_http_message_release(message);
1794+
aws_string_destroy(host_name);
1795+
aws_byte_buf_clean_up(&path_buf);
1796+
aws_s3_client_release(client);
1797+
aws_s3_tester_clean_up(&tester);
1798+
return 0;
1799+
}
1800+
1801+
AWS_TEST_CASE(
1802+
test_s3_get_object_file_path_direct_io_unsupported_append,
1803+
s_test_s3_get_object_file_path_direct_io_unsupported_append)
1804+
static int s_test_s3_get_object_file_path_direct_io_unsupported_append(struct aws_allocator *allocator, void *ctx) {
1805+
(void)ctx;
1806+
1807+
struct aws_s3_tester tester;
1808+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
1809+
1810+
struct aws_s3_tester_client_options client_options = {
1811+
.part_size = MB_TO_BYTES(5),
1812+
};
1813+
17061814
struct aws_s3_client *client = NULL;
17071815
ASSERT_SUCCESS(aws_s3_tester_client_new(&tester, &client_options, &client));
17081816

@@ -1711,29 +1819,69 @@ static int s_test_s3_get_object_file_path_direct_io_to_position(struct aws_alloc
17111819
};
17121820

17131821
struct aws_byte_cursor object_path = aws_byte_cursor_from_c_str("/pre-existing-1MB");
1714-
uint64_t pre_exist_file_length = 10;
17151822
struct aws_s3_tester_meta_request_options get_options = {
17161823
.allocator = allocator,
17171824
.meta_request_type = AWS_S3_META_REQUEST_TYPE_GET_OBJECT,
1718-
.validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_SUCCESS,
1825+
.validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_FAILURE,
1826+
.client = client,
1827+
.fio_opts = &fio_opts,
1828+
.get_options =
1829+
{
1830+
.object_path = object_path,
1831+
.file_on_disk = true,
1832+
.recv_file_option = AWS_S3_RECV_FILE_CREATE_OR_APPEND,
1833+
},
1834+
};
1835+
1836+
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &get_options, NULL));
1837+
1838+
aws_s3_client_release(client);
1839+
aws_s3_tester_clean_up(&tester);
1840+
return 0;
1841+
}
1842+
1843+
AWS_TEST_CASE(
1844+
test_s3_get_object_file_path_direct_io_unsupported_write_to_position,
1845+
s_test_s3_get_object_file_path_direct_io_unsupported_write_to_position)
1846+
static int s_test_s3_get_object_file_path_direct_io_unsupported_write_to_position(
1847+
struct aws_allocator *allocator,
1848+
void *ctx) {
1849+
(void)ctx;
1850+
1851+
struct aws_s3_tester tester;
1852+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
1853+
1854+
struct aws_s3_tester_client_options client_options = {
1855+
.part_size = MB_TO_BYTES(5),
1856+
};
1857+
1858+
struct aws_s3_client *client = NULL;
1859+
ASSERT_SUCCESS(aws_s3_tester_client_new(&tester, &client_options, &client));
1860+
1861+
struct aws_s3_file_io_options fio_opts = {
1862+
.direct_io = true,
1863+
};
1864+
1865+
struct aws_byte_cursor object_path = aws_byte_cursor_from_c_str("/pre-existing-1MB");
1866+
struct aws_s3_tester_meta_request_options get_options = {
1867+
.allocator = allocator,
1868+
.meta_request_type = AWS_S3_META_REQUEST_TYPE_GET_OBJECT,
1869+
.validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_FAILURE,
17191870
.client = client,
17201871
.fio_opts = &fio_opts,
17211872
.get_options =
17221873
{
17231874
.object_path = object_path,
17241875
.file_on_disk = true,
17251876
.recv_file_option = AWS_S3_RECV_FILE_WRITE_TO_POSITION,
1726-
.recv_file_position = 20,
1727-
.pre_exist_file_length = pre_exist_file_length,
1877+
.recv_file_position = 4096,
1878+
.pre_exist_file_length = 10,
17281879
},
17291880
};
17301881

1731-
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &get_options, &meta_request_test_results));
1732-
ASSERT_UINT_EQUALS(
1733-
get_options.get_options.recv_file_position + MB_TO_BYTES(1), meta_request_test_results.received_file_size);
1882+
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &get_options, NULL));
17341883

1735-
aws_s3_meta_request_test_results_clean_up(&meta_request_test_results);
1736-
client = aws_s3_client_release(client);
1884+
aws_s3_client_release(client);
17371885
aws_s3_tester_clean_up(&tester);
17381886
return 0;
17391887
}

0 commit comments

Comments
 (0)