Skip to content

Commit 502cd62

Browse files
authored
S3 receive filepath (#449)
1 parent 76096ad commit 502cd62

File tree

9 files changed

+503
-35
lines changed

9 files changed

+503
-35
lines changed

include/aws/s3/private/s3_meta_request_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@ struct aws_s3_meta_request {
279279

280280
/* running checksum of all the parts of a default get, or ranged get meta request*/
281281
struct aws_s3_checksum *meta_request_level_running_response_sum;
282+
283+
/* The receiving file handler */
284+
FILE *recv_file;
285+
struct aws_string *recv_filepath;
286+
bool recv_file_delete_on_failure;
282287
};
283288

284289
/* Info for each part, that we need to remember until we send CompleteMultipartUpload */

include/aws/s3/s3.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ enum aws_s3_errors {
4646
AWS_ERROR_S3EXPRESS_CREATE_SESSION_FAILED,
4747
AWS_ERROR_S3_INTERNAL_PART_SIZE_MISMATCH_RETRYING_WITH_RANGE,
4848
AWS_ERROR_S3_REQUEST_HAS_COMPLETED,
49+
AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS,
50+
AWS_ERROR_S3_RECV_FILE_NOT_FOUND,
4951

5052
AWS_ERROR_S3_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_S3_PACKAGE_ID)
5153
};

include/aws/s3/s3_client.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,28 @@ enum aws_s3_checksum_location {
249249
AWS_SCL_TRAILER,
250250
};
251251

252+
enum aws_s3_recv_file_option {
253+
/**
254+
* Create a new file if it doesn't exist, otherwise replace the existing file.
255+
*/
256+
AWS_S3_RECV_FILE_CREATE_OR_REPLACE = 0,
257+
/**
258+
* Always create a new file. If the file already exists, AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS will be raised.
259+
*/
260+
AWS_S3_RECV_FILE_CREATE_NEW,
261+
/**
262+
* Create a new file if it doesn't exist, otherwise append to the existing file.
263+
*/
264+
AWS_S3_RECV_FILE_CREATE_OR_APPEND,
265+
266+
/**
267+
* Write to an existing file at the specified position, defined by the `recv_file_position`.
268+
* If the file does not exist, AWS_ERROR_S3_RECV_FILE_NOT_FOUND will be raised.
269+
* If `recv_file_position` is not configured, start overwriting data at the beginning of the
270+
* file (byte 0).
271+
*/
272+
AWS_S3_RECV_FILE_WRITE_TO_POSITION,
273+
};
252274
/**
253275
* Info about a single part, for you to review before the upload completes.
254276
*/
@@ -632,6 +654,34 @@ struct aws_s3_meta_request_options {
632654
* Do not set the message's body-stream if the body is being passed by other means (see note above) */
633655
struct aws_http_message *message;
634656

657+
/**
658+
* Optional.
659+
* If set, the received data will be written into this file.
660+
* the `body_callback` will NOT be invoked.
661+
* This gives a better performance when receiving data to write to a file.
662+
* See `aws_s3_recv_file_option` for the configuration on the receive file.
663+
*/
664+
struct aws_byte_cursor recv_filepath;
665+
666+
/**
667+
* Optional.
668+
* Default to AWS_S3_RECV_FILE_CREATE_OR_REPLACE.
669+
* This only works with recv_filepath set.
670+
* See `aws_s3_recv_file_option`.
671+
*/
672+
enum aws_s3_recv_file_option recv_file_option;
673+
/**
674+
* Optional.
675+
* The specified position to start writing at for the recv file when `recv_file_option` is set to
676+
* AWS_S3_RECV_FILE_WRITE_TO_POSITION, ignored otherwise.
677+
*/
678+
uint64_t recv_file_position;
679+
/**
680+
* Set it to be true to delete the receive file on failure, otherwise, the file will be left as-is.
681+
* This only works with recv_filepath set.
682+
*/
683+
bool recv_file_delete_on_failure;
684+
635685
/**
636686
* Optional.
637687
* If set, this file is sent as the request body.

source/s3.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ static struct aws_error_info s_errors[] = {
4949
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3EXPRESS_CREATE_SESSION_FAILED, "CreateSession call failed when signing with S3 Express."),
5050
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_INTERNAL_PART_SIZE_MISMATCH_RETRYING_WITH_RANGE, "part_size mismatch, possibly due to wrong object_size_hint. Retrying with Range instead of partNumber."),
5151
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_REQUEST_HAS_COMPLETED, "Request has already completed, action cannot be performed."),
52+
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS, "File already exists, cannot create as new."),
53+
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RECV_FILE_NOT_FOUND, "The receive file doesn't exist, cannot create as configuration required."),
5254
};
5355
/* clang-format on */
5456

source/s3_meta_request.c

Lines changed: 110 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
#include <aws/auth/signing_result.h>
1919
#include <aws/common/clock.h>
2020
#include <aws/common/encoding.h>
21+
#include <aws/common/file.h>
2122
#include <aws/common/string.h>
2223
#include <aws/common/system_info.h>
2324
#include <aws/io/async_stream.h>
2425
#include <aws/io/event_loop.h>
2526
#include <aws/io/retry_strategy.h>
2627
#include <aws/io/socket.h>
2728
#include <aws/io/stream.h>
29+
#include <errno.h>
2830
#include <inttypes.h>
2931

3032
static const size_t s_dynamic_body_initial_buf_size = KB_TO_BYTES(1);
@@ -232,6 +234,57 @@ int aws_s3_meta_request_init_base(
232234
/* Keep original message around, for headers, method, and synchronous body-stream (if any) */
233235
meta_request->initial_request_message = aws_http_message_acquire(options->message);
234236

237+
if (options->recv_filepath.len > 0) {
238+
239+
meta_request->recv_filepath = aws_string_new_from_cursor(allocator, &options->recv_filepath);
240+
switch (options->recv_file_option) {
241+
case AWS_S3_RECV_FILE_CREATE_OR_REPLACE:
242+
meta_request->recv_file = aws_fopen(aws_string_c_str(meta_request->recv_filepath), "wb");
243+
break;
244+
245+
case AWS_S3_RECV_FILE_CREATE_NEW:
246+
if (aws_path_exists(meta_request->recv_filepath)) {
247+
AWS_LOGF_ERROR(
248+
AWS_LS_S3_META_REQUEST,
249+
"id=%p Cannot receive file via CREATE_NEW: file already exists",
250+
(void *)meta_request);
251+
aws_raise_error(AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS);
252+
break;
253+
} else {
254+
meta_request->recv_file = aws_fopen(aws_string_c_str(meta_request->recv_filepath), "wb");
255+
break;
256+
}
257+
case AWS_S3_RECV_FILE_CREATE_OR_APPEND:
258+
meta_request->recv_file = aws_fopen(aws_string_c_str(meta_request->recv_filepath), "ab");
259+
break;
260+
case AWS_S3_RECV_FILE_WRITE_TO_POSITION:
261+
if (!aws_path_exists(meta_request->recv_filepath)) {
262+
AWS_LOGF_ERROR(
263+
AWS_LS_S3_META_REQUEST,
264+
"id=%p Cannot receive file via WRITE_TO_POSITION: file not found.",
265+
(void *)meta_request);
266+
aws_raise_error(AWS_ERROR_S3_RECV_FILE_NOT_FOUND);
267+
break;
268+
} else {
269+
meta_request->recv_file = aws_fopen(aws_string_c_str(meta_request->recv_filepath), "r+");
270+
if (meta_request->recv_file &&
271+
aws_fseek(meta_request->recv_file, options->recv_file_position, SEEK_SET) != AWS_OP_SUCCESS) {
272+
/* error out. */
273+
goto error;
274+
}
275+
break;
276+
}
277+
278+
default:
279+
AWS_ASSERT(false);
280+
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
281+
break;
282+
}
283+
if (!meta_request->recv_file) {
284+
goto error;
285+
}
286+
}
287+
235288
/* If the request's body is being passed in some other way, set that up.
236289
* (we checked earlier that the request body is not being passed multiple ways) */
237290
if (options->send_filepath.len > 0) {
@@ -440,6 +493,15 @@ static void s_s3_meta_request_destroy(void *user_data) {
440493
/* endpoint should have already been released and set NULL by the meta request finish call.
441494
* But call release() again, just in case we're tearing down a half-initialized meta request */
442495
aws_s3_endpoint_release(meta_request->endpoint);
496+
if (meta_request->recv_file) {
497+
fclose(meta_request->recv_file);
498+
meta_request->recv_file = NULL;
499+
if (meta_request->recv_file_delete_on_failure) {
500+
/* If the meta request succeed, the file should be closed from finish call. So it must be failing. */
501+
aws_file_delete(meta_request->recv_filepath);
502+
}
503+
}
504+
aws_string_destroy(meta_request->recv_filepath);
443505

444506
/* Client may be NULL if meta request failed mid-creation (or this some weird testing mock with no client) */
445507
if (meta_request->client != NULL) {
@@ -1779,19 +1841,47 @@ static void s_s3_meta_request_event_delivery_task(struct aws_task *task, void *a
17791841

17801842
if (error_code == AWS_ERROR_SUCCESS && response_body.len > 0) {
17811843
if (meta_request->meta_request_level_running_response_sum) {
1782-
aws_checksum_update(meta_request->meta_request_level_running_response_sum, &response_body);
1844+
if (aws_checksum_update(
1845+
meta_request->meta_request_level_running_response_sum, &response_body)) {
1846+
error_code = aws_last_error();
1847+
AWS_LOGF_ERROR(
1848+
AWS_LS_S3_META_REQUEST,
1849+
"id=%p Failed to update checksum. last error:%s",
1850+
(void *)meta_request,
1851+
aws_error_name(error_code));
1852+
}
17831853
}
1784-
if (meta_request->body_callback != NULL &&
1785-
meta_request->body_callback(
1786-
meta_request, &response_body, request->part_range_start, meta_request->user_data)) {
1787-
1788-
error_code = aws_last_error_or_unknown();
1789-
AWS_LOGF_ERROR(
1790-
AWS_LS_S3_META_REQUEST,
1791-
"id=%p Response body callback raised error %d (%s).",
1792-
(void *)meta_request,
1793-
error_code,
1794-
aws_error_str(error_code));
1854+
if (error_code == AWS_ERROR_SUCCESS) {
1855+
if (meta_request->recv_file) {
1856+
/* Write the data directly to the file. No need to seek, since the event will always be
1857+
* delivered with the right order. */
1858+
if (fwrite((void *)response_body.ptr, response_body.len, 1, meta_request->recv_file) < 1) {
1859+
int errno_value = ferror(meta_request->recv_file) ? errno : 0; /* Always cache errno */
1860+
aws_translate_and_raise_io_error_or(errno_value, AWS_ERROR_FILE_WRITE_FAILURE);
1861+
error_code = aws_last_error();
1862+
AWS_LOGF_ERROR(
1863+
AWS_LS_S3_META_REQUEST,
1864+
"id=%p Failed writing to file. errno:%d. aws-error:%s",
1865+
(void *)meta_request,
1866+
errno_value,
1867+
aws_error_name(error_code));
1868+
}
1869+
if (meta_request->client->enable_read_backpressure) {
1870+
aws_s3_meta_request_increment_read_window(meta_request, response_body.len);
1871+
}
1872+
} else if (
1873+
meta_request->body_callback != NULL &&
1874+
meta_request->body_callback(
1875+
meta_request, &response_body, request->part_range_start, meta_request->user_data)) {
1876+
1877+
error_code = aws_last_error_or_unknown();
1878+
AWS_LOGF_ERROR(
1879+
AWS_LS_S3_META_REQUEST,
1880+
"id=%p Response body callback raised error %d (%s).",
1881+
(void *)meta_request,
1882+
error_code,
1883+
aws_error_str(error_code));
1884+
}
17951885
}
17961886
}
17971887
aws_atomic_fetch_sub(&client->stats.num_requests_streaming_response, 1);
@@ -1979,6 +2069,14 @@ void aws_s3_meta_request_finish_default(struct aws_s3_meta_request *meta_request
19792069
pending_async_write_waker(pending_async_write_waker_user_data);
19802070
}
19812071

2072+
if (meta_request->recv_file) {
2073+
fclose(meta_request->recv_file);
2074+
meta_request->recv_file = NULL;
2075+
if (finish_result.error_code && meta_request->recv_file_delete_on_failure) {
2076+
aws_file_delete(meta_request->recv_filepath);
2077+
}
2078+
}
2079+
19822080
while (!aws_linked_list_empty(&release_request_list)) {
19832081
struct aws_linked_list_node *request_node = aws_linked_list_pop_front(&release_request_list);
19842082
struct aws_s3_request *release_request = AWS_CONTAINER_OF(request_node, struct aws_s3_request, node);

tests/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ add_net_test_case(test_s3_get_object_tls_disabled)
6767
add_net_test_case(test_s3_get_object_tls_enabled)
6868
add_net_test_case(test_s3_get_object_tls_default)
6969
add_net_test_case(test_s3_get_object_less_than_part_size)
70+
add_net_test_case(test_s3_get_object_file_path)
71+
add_net_test_case(test_s3_get_object_file_path_create_new)
72+
add_net_test_case(test_s3_get_object_file_path_append)
73+
add_net_test_case(test_s3_get_object_file_path_to_position)
7074
add_net_test_case(test_s3_get_object_empty_object)
7175
add_net_test_case(test_s3_get_object_multiple)
7276
add_net_test_case(test_s3_get_object_multiple_serial)
@@ -77,6 +81,9 @@ add_net_test_case(test_s3_default_get_object_looks_like_async_error_xml)
7781
add_net_test_case(test_s3_get_object_backpressure_small_increments)
7882
add_net_test_case(test_s3_get_object_backpressure_big_increments)
7983
add_net_test_case(test_s3_get_object_backpressure_initial_size_zero)
84+
add_net_test_case(test_s3_get_object_backpressure_small_increments_recv_filepath)
85+
add_net_test_case(test_s3_get_object_backpressure_big_increments_recv_filepath)
86+
add_net_test_case(test_s3_get_object_backpressure_initial_size_zero_recv_filepath)
8087
add_net_test_case(test_s3_get_object_part)
8188
add_net_test_case(test_s3_no_signing)
8289
add_net_test_case(test_s3_signing_override)

0 commit comments

Comments
 (0)