mirror of
https://github.com/HDFGroup/hdf5.git
synced 2026-09-25 04:09:44 +03:00
Fix formation of Host header when specified URL is a virtual-hosted-style URL
2319 lines
93 KiB
C
2319 lines
93 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* Copyright by The HDF Group. *
|
|
* All rights reserved. *
|
|
* *
|
|
* This file is part of HDF5. The full HDF5 copyright notice, including *
|
|
* terms governing use, modification, and redistribution, is contained in *
|
|
* the LICENSE file, which can be found at the root of the source code *
|
|
* distribution tree, or in https://www.hdfgroup.org/licenses. *
|
|
* If you do not have access to either file, you may request a copy from *
|
|
* help@hdfgroup.org. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*****************************************************************************
|
|
* Module for performing S3 communications using the aws-c-s3 library
|
|
*
|
|
* ***NOT A FILE DRIVER***
|
|
*
|
|
* Provides functions and structures required for interfacing with Amazon
|
|
* Simple Storage Service (S3).
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/****************/
|
|
/* Module Setup */
|
|
/****************/
|
|
|
|
/***********/
|
|
/* Headers */
|
|
/***********/
|
|
|
|
#include "H5private.h"
|
|
#include "H5Eprivate.h"
|
|
#include "H5FDros3_s3comms.h"
|
|
#include "H5MMprivate.h"
|
|
|
|
#ifdef H5_HAVE_ROS3_VFD
|
|
|
|
#include <aws/s3/s3.h>
|
|
#include <aws/s3/s3_client.h>
|
|
#include <aws/auth/credentials.h>
|
|
#include <aws/common/condition_variable.h>
|
|
#include <aws/common/mutex.h>
|
|
#include <aws/io/channel_bootstrap.h>
|
|
#include <aws/io/event_loop.h>
|
|
#include <aws/io/uri.h>
|
|
#include <aws/http/request_response.h>
|
|
#include <aws/sdkutils/aws_profile.h>
|
|
|
|
/****************/
|
|
/* Local Macros */
|
|
/****************/
|
|
|
|
/*
|
|
* Size of buffer to allocate for host name
|
|
*/
|
|
#define HOST_NAME_LEN 128
|
|
|
|
/*
|
|
* Size of buffer to allocate for "bytes=<first_byte>[-<last_byte>]"
|
|
* HTTP Range value string (including a NUL terminator)
|
|
*/
|
|
#define S3COMMS_MAX_RANGE_STRING_SIZE 128
|
|
|
|
/*
|
|
* Size of buffer to allocate for User-Agent HTTP header
|
|
*/
|
|
#define MAX_USER_AGENT_STRING_SIZE 128
|
|
|
|
/*
|
|
* The maximum number of entries for the host resolver to
|
|
* keep cached
|
|
*/
|
|
#define HOST_RESOLVER_MAX_NUM_ENTRIES 8
|
|
|
|
/******************/
|
|
/* Local Typedefs */
|
|
/******************/
|
|
|
|
/********************/
|
|
/* Local Structures */
|
|
/********************/
|
|
|
|
/*
|
|
* Parameters that aws-c-s3 was configured with. Most of these parameters
|
|
* must be freed when no longer in use:
|
|
*
|
|
* - aws_s3_client_release(client);
|
|
* - aws_client_bootstrap_release(client_bootstrap);
|
|
* - aws_credentials_provider_release(credentials_provider);
|
|
* - aws_host_resolver_release(host_resolver);
|
|
* - aws_event_loop_group_release(event_loop_group);
|
|
* - aws_uri_clean_up(&parsed_uri);
|
|
* - aws_uri_clean_up(&alt_parsed_uri);
|
|
*/
|
|
typedef struct H5FD__s3comms_aws_params_t {
|
|
struct aws_allocator *allocator;
|
|
struct aws_s3_client *client;
|
|
struct aws_client_bootstrap *client_bootstrap;
|
|
struct aws_credentials_provider *credentials_provider;
|
|
struct aws_host_resolver *host_resolver;
|
|
struct aws_event_loop_group *event_loop_group;
|
|
struct aws_signing_config_aws signing_config;
|
|
struct aws_uri parsed_uri;
|
|
struct aws_uri alt_parsed_uri;
|
|
bool force_path_style;
|
|
|
|
/* Shared MT state for requests */
|
|
struct aws_mutex req_mutex; /* Mutex for condition variable */
|
|
struct aws_condition_variable req_cvar; /* Condition variable for waiting on request to finish */
|
|
} H5FD__s3comms_aws_params_t;
|
|
|
|
/*
|
|
* Common structure for requests
|
|
*/
|
|
typedef struct H5FD__s3comms_request_t {
|
|
/* Final status of operation - used for error checking */
|
|
herr_t status;
|
|
|
|
/* Flag for when request is finished - must ONLY be set in a finish callback */
|
|
bool finished;
|
|
|
|
/* Error message to be returned through HDF5 error stack */
|
|
const char *err_msg;
|
|
|
|
/* Mutex for condition variable */
|
|
struct aws_mutex *mutex;
|
|
|
|
/* Condition variable for waiting on request to finish */
|
|
struct aws_condition_variable *cvar;
|
|
} H5FD__s3comms_request_t;
|
|
|
|
/*
|
|
* Parameters for the 'read' operation to get data from an S3 object.
|
|
*/
|
|
typedef struct H5FD__s3comms_read_params_t {
|
|
H5FD__s3comms_request_t request; /* Must be first */
|
|
|
|
uint8_t *read_buf;
|
|
size_t read_buf_size;
|
|
haddr_t read_offset;
|
|
} H5FD__s3comms_read_params_t;
|
|
|
|
/*
|
|
* Parameters for the 'getsize' operation to get the size of an S3 object.
|
|
*/
|
|
typedef struct H5FD__s3comms_getsize_params_t {
|
|
H5FD__s3comms_request_t request; /* Must be first */
|
|
|
|
struct aws_byte_buf response_body;
|
|
struct aws_byte_cursor response_body_cursor;
|
|
size_t object_size;
|
|
} H5FD__s3comms_getsize_params_t;
|
|
|
|
typedef struct H5FD__s3comms_check_cred_provider_params_t {
|
|
H5FD__s3comms_request_t request; /* Must be first */
|
|
|
|
struct aws_credentials *credentials;
|
|
int error_code;
|
|
} H5FD__s3comms_check_cred_provider_params_t;
|
|
|
|
/********************/
|
|
/* Local Prototypes */
|
|
/********************/
|
|
|
|
/* atexit() function to avoid issue when cleaning up aws-c-s3 library
|
|
* from HDF5's default atexit() handler.
|
|
*/
|
|
static void H5FD__s3comms_term_func(void);
|
|
|
|
/* Callbacks for processing general requests */
|
|
static void H5FD__s3comms_s3r_req_finish_cb(struct aws_s3_meta_request *meta_request,
|
|
const struct aws_s3_meta_request_result *meta_request_result,
|
|
void *user_data);
|
|
static bool H5FD__s3comms_s3r_req_finish_pred(void *user_data);
|
|
|
|
/* Callbacks for operation to read an s3 object */
|
|
static int H5FD__s3comms_s3r_read_cb(struct aws_s3_meta_request *meta_request,
|
|
const struct aws_byte_cursor *body, uint64_t range_start,
|
|
void *user_data);
|
|
|
|
/* Callbacks for operation to get the size of an s3 object */
|
|
static herr_t H5FD__s3comms_s3r_getsize(s3r_t *handle);
|
|
static int H5FD__s3comms_s3r_getsize_headers_cb(struct aws_s3_meta_request *meta_request,
|
|
const struct aws_http_headers *headers, int response_status,
|
|
void *user_data);
|
|
|
|
/* Utility functions */
|
|
static parsed_url_t *H5FD__s3comms_parse_url(s3r_t *handle, const char *url, struct aws_uri *uri);
|
|
static herr_t H5FD__s3comms_free_purl(parsed_url_t *purl);
|
|
|
|
static herr_t H5FD__s3comms_get_aws_region(const H5FD__s3comms_aws_params_t *aws_params,
|
|
const H5FD_ros3_fapl_t *fa, char **aws_region_out);
|
|
static herr_t
|
|
H5FD__s3comms_get_credentials_provider(H5FD__s3comms_aws_params_t *aws_params, const H5FD_ros3_fapl_t *fa,
|
|
const char *fapl_token,
|
|
struct aws_credentials_provider **credentials_provider_out);
|
|
|
|
static herr_t H5FD__s3comms_format_http_request_message(const H5FD__s3comms_aws_params_t *aws_params,
|
|
const char *HTTP_method, const char *bucket_name,
|
|
const char *object_key,
|
|
struct aws_http_message **message_out);
|
|
static herr_t H5FD__s3comms_format_host_header(const H5FD__s3comms_aws_params_t *aws_params,
|
|
struct aws_http_message *message, const char *bucket_name,
|
|
const char *host_name, const char *port);
|
|
static herr_t H5FD__s3comms_format_range_header(struct aws_http_message *message, haddr_t offset, size_t len);
|
|
static herr_t H5FD__s3comms_format_user_agent_header(struct aws_http_message *message);
|
|
|
|
static const char *H5FD__s3comms_httpcode_to_str(long httpcode, bool *handled);
|
|
|
|
/*********************/
|
|
/* Package Variables */
|
|
/*********************/
|
|
|
|
/*****************************/
|
|
/* Library Private Variables */
|
|
/*****************************/
|
|
|
|
/*******************/
|
|
/* Local Variables */
|
|
/*******************/
|
|
|
|
/* Boolean to determine if the AWS library has been initialized */
|
|
static bool H5FD_ros3_aws_init_g = false;
|
|
|
|
/* Pointer to allocator used for AWS operations */
|
|
static struct aws_allocator *H5FD_ros3_aws_allocator_g = NULL;
|
|
|
|
/* Pointer to event loop group used for AWS client and host resolver */
|
|
static struct aws_event_loop_group *H5FD_ros3_aws_event_loop_group_g = NULL;
|
|
|
|
/* Pointer to host resolver used for resolving and caching hosts */
|
|
static struct aws_host_resolver *H5FD_ros3_aws_host_resolver_g = NULL;
|
|
|
|
/* Structure used for logging when enabled */
|
|
static struct aws_logger H5FD_ros3_aws_logger_g = {0};
|
|
static bool H5FD_ros3_aws_log_init_g = false;
|
|
|
|
/* Boolean controlling whether debugging output is enabled */
|
|
static bool H5FD_ros3_debug_g = false;
|
|
|
|
/*************/
|
|
/* Functions */
|
|
/*************/
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_init
|
|
*
|
|
* Purpose: Initialize the S3 communications interface.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
herr_t
|
|
H5FD__s3comms_init(void)
|
|
{
|
|
struct aws_event_loop_group_options event_loop_group_opts = {0};
|
|
struct aws_host_resolver_default_options host_resolver_opts = {0};
|
|
struct aws_logger_standard_options log_opts = {0};
|
|
char *debug = NULL;
|
|
char *log_level = NULL;
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
if (H5FD_ros3_aws_init_g)
|
|
HGOTO_DONE(SUCCEED);
|
|
|
|
/* Initialize aws-c-s3 with default allocator. Refer to allocator.h
|
|
* in the aws-c-common dependency for alternative allocators that
|
|
* could be better for specific use cases.
|
|
*/
|
|
H5FD_ros3_aws_allocator_g = aws_default_allocator();
|
|
|
|
aws_s3_library_init(H5FD_ros3_aws_allocator_g);
|
|
|
|
/* Set up an event loop group using platform default event loop */
|
|
event_loop_group_opts.loop_count = 0;
|
|
event_loop_group_opts.type = AWS_EVENT_LOOP_PLATFORM_DEFAULT;
|
|
H5FD_ros3_aws_event_loop_group_g =
|
|
aws_event_loop_group_new(H5FD_ros3_aws_allocator_g, &event_loop_group_opts);
|
|
if (!H5FD_ros3_aws_event_loop_group_g)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL, "couldn't initialize AWS event loop group");
|
|
|
|
/* Set up host resolver using default options taken from AWS sample code */
|
|
host_resolver_opts.el_group = H5FD_ros3_aws_event_loop_group_g;
|
|
host_resolver_opts.max_entries = HOST_RESOLVER_MAX_NUM_ENTRIES;
|
|
H5FD_ros3_aws_host_resolver_g =
|
|
aws_host_resolver_new_default(H5FD_ros3_aws_allocator_g, &host_resolver_opts);
|
|
if (!H5FD_ros3_aws_host_resolver_g)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL, "couldn't initialize AWS host resolver");
|
|
|
|
/* Check if debugging output should be enabled */
|
|
#if S3COMMS_DEBUG > 0
|
|
H5FD_ros3_debug_g = true;
|
|
#else
|
|
debug = getenv(HDF5_ROS3_VFD_DEBUG);
|
|
if (debug && (*debug != '\0')) {
|
|
if (0 != HDstrcasecmp(debug, "false") && 0 != HDstrcasecmp(debug, "off") && 0 != strcmp(debug, "0"))
|
|
H5FD_ros3_debug_g = true;
|
|
}
|
|
#endif
|
|
|
|
/* Configure aws-c-s3 logging if enabled */
|
|
log_level = getenv(HDF5_ROS3_VFD_LOG_LEVEL);
|
|
if (log_level && (*log_level != '\0')) {
|
|
log_opts.level = AWS_LL_NONE;
|
|
if (0 == HDstrcasecmp(log_level, "trace"))
|
|
log_opts.level = AWS_LL_TRACE;
|
|
else if (0 == HDstrcasecmp(log_level, "debug"))
|
|
log_opts.level = AWS_LL_DEBUG;
|
|
else if (0 == HDstrcasecmp(log_level, "info"))
|
|
log_opts.level = AWS_LL_INFO;
|
|
else if (0 == HDstrcasecmp(log_level, "error"))
|
|
log_opts.level = AWS_LL_ERROR;
|
|
|
|
if (log_opts.level != AWS_LL_NONE) {
|
|
char *log_file = NULL;
|
|
|
|
log_file = getenv(HDF5_ROS3_VFD_LOG_FILE);
|
|
if (log_file) {
|
|
if (0 == HDstrcasecmp(log_file, "stdout"))
|
|
log_opts.file = stdout;
|
|
else if (0 == HDstrcasecmp(log_file, "stderr"))
|
|
log_opts.file = stderr;
|
|
else
|
|
log_opts.filename = log_file;
|
|
}
|
|
else {
|
|
log_opts.filename = H5FD_ROS3_VFD_DEFAULT_LOG_FILE;
|
|
}
|
|
|
|
aws_logger_init_standard(&H5FD_ros3_aws_logger_g, H5FD_ros3_aws_allocator_g, &log_opts);
|
|
aws_logger_set(&H5FD_ros3_aws_logger_g);
|
|
H5FD_ros3_aws_log_init_g = true;
|
|
}
|
|
}
|
|
|
|
/* Work around issue where aws-c-s3 library doesn't shut down
|
|
* cleanly when called from HDF5's default atexit() handler.
|
|
*/
|
|
if (0 != atexit(H5FD__s3comms_term_func))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't register function for cleaning up aws-c-s3 library");
|
|
|
|
H5FD_ros3_aws_init_g = true;
|
|
|
|
done:
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_init() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_term_func
|
|
*
|
|
* Purpose: atexit() handler function to terminate the S3 communications
|
|
* interface. This is currently needed to work around ordering
|
|
* issues that cause a crash when the aws-c-s3 library is cleaned
|
|
* up from HDF5's default atexit() handler. A better solution
|
|
* would be to require a call to H5close() to explicitly
|
|
* terminate access to HDF5 before exiting main(), but this will
|
|
* suffice for the time being.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static void
|
|
H5FD__s3comms_term_func(void)
|
|
{
|
|
if (H5FD_ros3_aws_init_g) {
|
|
aws_host_resolver_release(H5FD_ros3_aws_host_resolver_g);
|
|
aws_event_loop_group_release(H5FD_ros3_aws_event_loop_group_g);
|
|
aws_s3_library_clean_up();
|
|
|
|
/* Clean up logger interface after being sure everything else
|
|
* has finished cleaning up
|
|
*/
|
|
if (H5FD_ros3_aws_log_init_g)
|
|
aws_logger_clean_up(&H5FD_ros3_aws_logger_g);
|
|
|
|
H5FD_ros3_debug_g = false;
|
|
|
|
H5FD_ros3_aws_init_g = false;
|
|
}
|
|
} /* end H5FD__s3comms_term_func() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_term
|
|
*
|
|
* Purpose: Terminate the S3 communications interface.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
herr_t
|
|
H5FD__s3comms_term(void)
|
|
{
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE_NOERR
|
|
|
|
/* Currently handled by atexit() function above to work around cleanup
|
|
* ordering issues.
|
|
*/
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_term() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_req_finish_cb
|
|
*
|
|
* Purpose: Callback to process a finished request
|
|
*
|
|
* Return: Nothing
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static void
|
|
H5FD__s3comms_s3r_req_finish_cb(struct aws_s3_meta_request H5_ATTR_UNUSED *meta_request,
|
|
const struct aws_s3_meta_request_result *meta_request_result, void *user_data)
|
|
{
|
|
H5FD__s3comms_request_t *s3comms_request = NULL;
|
|
int ret_value = AWS_OP_SUCCESS;
|
|
|
|
s3comms_request = (H5FD__s3comms_request_t *)user_data;
|
|
if (!s3comms_request) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* From aws-c-s3 docs: If error_code is equal to AWS_ERROR_S3_INVALID_RESPONSE_STATUS,
|
|
* then error_response_headers, error_response_body, and response_status will be
|
|
* populated by the failed request.
|
|
*
|
|
* For all other error codes, response_status will be 0, and the error_response variables
|
|
* will be NULL.
|
|
*/
|
|
if (AWS_ERROR_SUCCESS != meta_request_result->error_code) {
|
|
bool handled_http_status = false;
|
|
|
|
/* Process some generic errors */
|
|
s3comms_request->err_msg =
|
|
H5FD__s3comms_httpcode_to_str(meta_request_result->response_status, &handled_http_status);
|
|
|
|
if (handled_http_status)
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
else {
|
|
/* Propagate previous error */
|
|
s3comms_request->err_msg = aws_error_str(meta_request_result->error_code);
|
|
ret_value = AWS_OP_ERR;
|
|
}
|
|
|
|
/* If debugging is enabled, print out the error from the AWS library and the
|
|
* HTTP response headers.
|
|
*/
|
|
if (H5FD_ros3_debug_g) {
|
|
if (meta_request_result->error_response_operation_name)
|
|
fprintf(stderr, " -- %s request failed with error: %s\n",
|
|
aws_string_bytes(meta_request_result->error_response_operation_name),
|
|
aws_error_str(meta_request_result->error_code));
|
|
else
|
|
fprintf(stderr, " -- request failed with error: %s\n",
|
|
aws_error_str(meta_request_result->error_code));
|
|
|
|
if (AWS_ERROR_S3_INVALID_RESPONSE_STATUS == meta_request_result->error_code) {
|
|
if (meta_request_result->error_response_headers) {
|
|
size_t num_headers = aws_http_headers_count(meta_request_result->error_response_headers);
|
|
|
|
if (num_headers) {
|
|
fprintf(stderr, " -- response headers:\n");
|
|
for (size_t hdr_idx = 0; hdr_idx < num_headers; hdr_idx++) {
|
|
struct aws_http_header header = {0};
|
|
int ret = 0;
|
|
|
|
ret = aws_http_headers_get_index(meta_request_result->error_response_headers,
|
|
hdr_idx, &header);
|
|
if (AWS_ERROR_INVALID_INDEX == ret) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
fprintf(stderr, PRInSTR ": " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(header.name),
|
|
AWS_BYTE_CURSOR_PRI(header.value));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (meta_request_result->error_response_body) {
|
|
fprintf(stderr, " -- response body:\n");
|
|
fprintf(stderr, " ");
|
|
fprintf(stderr, PRInSTR "\n",
|
|
AWS_BYTE_BUF_PRI((*meta_request_result->error_response_body)));
|
|
}
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
|
|
if (H5FD_ros3_debug_g) {
|
|
fprintf(stderr, " -- final HTTP status code: %d\n", meta_request_result->response_status);
|
|
fflush(stderr);
|
|
}
|
|
|
|
done:
|
|
if (s3comms_request) {
|
|
/* Note that if we fail to lock the mutex here, any thread
|
|
* waiting on the condition variable until s3comms_request->finished
|
|
* is true will hang, unless the wait has a timeout.
|
|
*/
|
|
if (AWS_OP_SUCCESS != aws_mutex_lock(s3comms_request->mutex)) {
|
|
/* Propagate previous error */
|
|
s3comms_request->err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
}
|
|
else {
|
|
s3comms_request->status = (ret_value == AWS_OP_SUCCESS) ? SUCCEED : FAIL;
|
|
s3comms_request->finished = true;
|
|
|
|
if (AWS_OP_SUCCESS != aws_condition_variable_notify_one(s3comms_request->cvar)) {
|
|
/* Propagate previous error */
|
|
s3comms_request->err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_unlock(s3comms_request->mutex)) {
|
|
/* Propagate previous error */
|
|
s3comms_request->err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
}
|
|
}
|
|
}
|
|
} /* end H5FD__s3comms_s3r_req_finish_cb() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_req_finish_pred
|
|
*
|
|
* Purpose: Predicate to signal that a request has finished and a thread
|
|
* waiting on the condition variable for the request may proceed
|
|
*
|
|
* Return: true if request is finished / false otherwise
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static bool
|
|
H5FD__s3comms_s3r_req_finish_pred(void *user_data)
|
|
{
|
|
H5FD__s3comms_request_t *request = (H5FD__s3comms_request_t *)user_data;
|
|
|
|
return request->finished;
|
|
} /* end H5FD__s3comms_s3r_req_finish_pred() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_cred_provider_get_creds_cb
|
|
*
|
|
* Purpose: Callback function called when sourcing credentials from a
|
|
* credentials provider. Once this callback has completed
|
|
* successfully, a reference will be held on the credentials
|
|
* object if credentials were available and the caller should
|
|
* release that reference with aws_credentials_release().
|
|
*
|
|
* Return: Nothing
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static void
|
|
H5FD__s3comms_cred_provider_get_creds_cb(struct aws_credentials *credentials, int error_code, void *user_data)
|
|
{
|
|
H5FD__s3comms_check_cred_provider_params_t *check_params = NULL;
|
|
bool cred_ref_inc = false;
|
|
int ret_value = AWS_OP_SUCCESS;
|
|
|
|
check_params = (H5FD__s3comms_check_cred_provider_params_t *)user_data;
|
|
if (!check_params) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
/* Note that if we fail to lock the mutex here, any thread
|
|
* waiting on the condition variable until check_params->request.finished
|
|
* is true will hang, unless the wait has a timeout.
|
|
*/
|
|
if (AWS_OP_SUCCESS != aws_mutex_lock(check_params->request.mutex)) {
|
|
/* Propagate previous error */
|
|
check_params->request.err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
goto done;
|
|
}
|
|
|
|
check_params->credentials = credentials;
|
|
if (credentials) {
|
|
/* Hold a reference on the credentials until the code waiting on this callback can check them */
|
|
aws_credentials_acquire(credentials);
|
|
cred_ref_inc = true;
|
|
}
|
|
|
|
check_params->error_code = error_code;
|
|
check_params->request.status = (ret_value == AWS_OP_SUCCESS) ? SUCCEED : FAIL;
|
|
check_params->request.finished = true;
|
|
|
|
if (AWS_OP_SUCCESS != aws_condition_variable_notify_one(check_params->request.cvar)) {
|
|
/* Propagate previous error */
|
|
aws_mutex_unlock(check_params->request.mutex);
|
|
check_params->request.err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
goto done;
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_unlock(check_params->request.mutex)) {
|
|
/* Propagate previous error */
|
|
check_params->request.err_msg = aws_error_str(aws_last_error());
|
|
ret_value = AWS_OP_ERR;
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
if (AWS_OP_SUCCESS != ret_value) {
|
|
if (cred_ref_inc)
|
|
aws_credentials_release(credentials);
|
|
}
|
|
} /* end H5FD__s3comms_cred_provider_get_creds_cb() */
|
|
|
|
static bool
|
|
H5FD__s3comms_cred_provider_pred(void *user_data)
|
|
{
|
|
H5FD__s3comms_request_t *request = (H5FD__s3comms_request_t *)user_data;
|
|
|
|
return request->finished;
|
|
} /* end H5FD__s3comms_cred_provider_pred() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_open
|
|
*
|
|
* Purpose: Logically open a file hosted on S3
|
|
*
|
|
* fa can be NULL (implies no authentication)
|
|
* fapl_token can be NULL
|
|
*
|
|
* Return: SUCCESS: Pointer to new request handle.
|
|
* FAILURE: NULL
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
s3r_t *
|
|
H5FD__s3comms_s3r_open(const char *url, const H5FD_ros3_fapl_t *fa, const char *fapl_token,
|
|
const char *alt_endpoint)
|
|
{
|
|
struct aws_s3_client *client = NULL;
|
|
struct aws_client_bootstrap *client_bootstrap = NULL;
|
|
struct aws_credentials_provider *credentials_provider = NULL;
|
|
struct aws_s3_client_config client_config = {0};
|
|
struct aws_client_bootstrap_options client_bootstrap_opts = {0};
|
|
struct aws_byte_cursor region_cursor = {0};
|
|
H5FD__s3comms_aws_params_t *aws_params = NULL;
|
|
s3r_t *handle = NULL;
|
|
char *pathstyle_env = NULL;
|
|
char *endpoint_env = NULL;
|
|
bool mutex_init = false;
|
|
bool cond_var_init = false;
|
|
s3r_t *ret_value = NULL;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
if (!url)
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, NULL, "url cannot be NULL");
|
|
if (url[0] == '\0')
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, NULL, "url cannot be an empty string");
|
|
|
|
/* Create handle and set fields */
|
|
if (NULL == (handle = calloc(1, sizeof(s3r_t))))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "could not allocate space for handle");
|
|
|
|
if (NULL == (aws_params = calloc(1, sizeof(H5FD__s3comms_aws_params_t))))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "could not allocate space for private handle data");
|
|
handle->priv_data = aws_params;
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_init(&aws_params->req_mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL, "couldn't initialize mutex: %s",
|
|
aws_error_str(aws_last_error()));
|
|
mutex_init = true;
|
|
|
|
if (AWS_OP_SUCCESS != aws_condition_variable_init(&aws_params->req_cvar))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL, "couldn't initialize condition variable: %s",
|
|
aws_error_str(aws_last_error()));
|
|
cond_var_init = true;
|
|
|
|
/* Check if path-style requests should be forced */
|
|
pathstyle_env = getenv(HDF5_ROS3_VFD_FORCE_PATH_STYLE);
|
|
if (pathstyle_env && (*pathstyle_env != '\0')) {
|
|
if (0 != HDstrcasecmp(pathstyle_env, "false") && 0 != HDstrcasecmp(pathstyle_env, "off") &&
|
|
0 != strcmp(pathstyle_env, "0"))
|
|
aws_params->force_path_style = true;
|
|
}
|
|
|
|
/*
|
|
* Set up a new AWS client with default options
|
|
*/
|
|
|
|
aws_params->allocator = H5FD_ros3_aws_allocator_g;
|
|
aws_params->event_loop_group = H5FD_ros3_aws_event_loop_group_g;
|
|
aws_params->host_resolver = H5FD_ros3_aws_host_resolver_g;
|
|
|
|
/* Create client bootstrap information from event loop group and host resolver */
|
|
client_bootstrap_opts.event_loop_group = H5FD_ros3_aws_event_loop_group_g;
|
|
client_bootstrap_opts.host_resolver = H5FD_ros3_aws_host_resolver_g;
|
|
client_bootstrap = aws_client_bootstrap_new(aws_params->allocator, &client_bootstrap_opts);
|
|
if (!client_bootstrap)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL, "couldn't bootstrap AWS client: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
aws_params->client_bootstrap = client_bootstrap;
|
|
|
|
/* Setup AWS region */
|
|
if (H5FD__s3comms_get_aws_region(aws_params, fa, &handle->aws_region) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL, "couldn't determine AWS region");
|
|
|
|
/* Require that a region is specified rather than defaulting to
|
|
* a pre-chosen region, as this could potentially incur unintended
|
|
* data transfer costs.
|
|
*/
|
|
if (!handle->aws_region)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, NULL, "AWS region wasn't specified");
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
region_cursor = aws_byte_cursor_from_c_str(handle->aws_region);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
/* Create a credentials provider for authentication */
|
|
if (H5FD__s3comms_get_credentials_provider(aws_params, fa, fapl_token, &credentials_provider) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL,
|
|
"couldn't create AWS credentials provider for authentication");
|
|
|
|
assert(credentials_provider);
|
|
aws_params->credentials_provider = credentials_provider;
|
|
|
|
/* Initialize signing configuration information */
|
|
aws_s3_init_default_signing_config(&aws_params->signing_config, region_cursor, credentials_provider);
|
|
|
|
/* AWS C S3 sample code turns this flag off, but it's not clear when this is needed */
|
|
aws_params->signing_config.flags.use_double_uri_encode = false;
|
|
|
|
client_config.client_bootstrap = client_bootstrap;
|
|
client_config.region = region_cursor;
|
|
client_config.signing_config = &aws_params->signing_config;
|
|
|
|
client = aws_s3_client_new(aws_params->allocator, &client_config);
|
|
if (!client)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, NULL, "couldn't initialize AWS client: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
aws_params->client = client;
|
|
|
|
/* Parse URL */
|
|
if (NULL == (handle->purl = H5FD__s3comms_parse_url(handle, url, &aws_params->parsed_uri)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "could not allocate and create parsed URL");
|
|
if (!handle->purl->bucket_name)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "invalid URL specified - could not parse bucket name");
|
|
if (!handle->purl->key)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "invalid URL specified - could not parse object key");
|
|
|
|
/* If no alternate endpoint URL was specified in the FAPL, check to see
|
|
* if one of the AWS environment variables were specified
|
|
*/
|
|
if (!alt_endpoint) {
|
|
endpoint_env = getenv("AWS_ENDPOINT_URL_S3");
|
|
if (endpoint_env && (*endpoint_env != '\0'))
|
|
alt_endpoint = endpoint_env;
|
|
else {
|
|
endpoint_env = getenv("AWS_ENDPOINT_URL");
|
|
if (endpoint_env && (*endpoint_env != '\0'))
|
|
alt_endpoint = endpoint_env;
|
|
}
|
|
}
|
|
|
|
if (alt_endpoint && (*alt_endpoint != '\0')) {
|
|
if (H5FD_ros3_debug_g)
|
|
fprintf(stderr, " -- parsing alternative endpoint URL\n");
|
|
|
|
if (NULL == (handle->alternate_purl =
|
|
H5FD__s3comms_parse_url(handle, alt_endpoint, &aws_params->alt_parsed_uri)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL,
|
|
"could not allocate and create parsed alternate endpoint URL");
|
|
if (!handle->alternate_purl->host)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL,
|
|
"invalid alternate endpoint URL specified - could not parse host name");
|
|
}
|
|
|
|
/* Get the S3 object's size. This is the only time we touch the S3 object
|
|
* (and thus ensure it exists) during the VFD's open callback.
|
|
*/
|
|
if (H5FD__s3comms_s3r_getsize(handle) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, NULL, "couldn't get S3 object's size");
|
|
|
|
ret_value = handle;
|
|
|
|
done:
|
|
if (ret_value == NULL) {
|
|
aws_s3_client_release(client);
|
|
aws_credentials_provider_release(credentials_provider);
|
|
aws_client_bootstrap_release(client_bootstrap);
|
|
|
|
if (aws_params) {
|
|
if (cond_var_init)
|
|
aws_condition_variable_clean_up(&aws_params->req_cvar);
|
|
if (mutex_init)
|
|
aws_mutex_clean_up(&aws_params->req_mutex);
|
|
|
|
aws_uri_clean_up(&aws_params->parsed_uri);
|
|
if (alt_endpoint)
|
|
aws_uri_clean_up(&aws_params->alt_parsed_uri);
|
|
free(aws_params);
|
|
}
|
|
|
|
if (handle) {
|
|
/* Freed above */
|
|
handle->priv_data = NULL;
|
|
|
|
if (H5FD__s3comms_s3r_close(handle) < 0)
|
|
HDONE_ERROR(H5E_VFL, H5E_CANTFREE, NULL, "can't free handle");
|
|
}
|
|
}
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_s3r_open() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_close
|
|
*
|
|
* Purpose: Close communications through given S3 Request Handle (s3r_t)
|
|
* and clean up associated resources
|
|
*
|
|
* Return: SUCCEED/FAIL
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
herr_t
|
|
H5FD__s3comms_s3r_close(s3r_t *handle)
|
|
{
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
if (handle == NULL)
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "handle cannot be NULL");
|
|
|
|
free(handle->aws_region);
|
|
|
|
if (handle->priv_data) {
|
|
H5FD__s3comms_aws_params_t *aws_params;
|
|
|
|
aws_params = (H5FD__s3comms_aws_params_t *)handle->priv_data;
|
|
|
|
aws_uri_clean_up(&aws_params->parsed_uri);
|
|
if (handle->alternate_purl)
|
|
aws_uri_clean_up(&aws_params->alt_parsed_uri);
|
|
|
|
aws_s3_client_release(aws_params->client);
|
|
aws_params->client = NULL;
|
|
|
|
aws_credentials_provider_release(aws_params->credentials_provider);
|
|
aws_params->credentials_provider = NULL;
|
|
|
|
aws_client_bootstrap_release(aws_params->client_bootstrap);
|
|
aws_params->client_bootstrap = NULL;
|
|
|
|
aws_condition_variable_clean_up(&aws_params->req_cvar);
|
|
aws_mutex_clean_up(&aws_params->req_mutex);
|
|
|
|
free(handle->priv_data);
|
|
}
|
|
|
|
if (H5FD__s3comms_free_purl(handle->purl) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "unable to release parsed url structure");
|
|
if (H5FD__s3comms_free_purl(handle->alternate_purl) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "unable to release parsed url structure");
|
|
|
|
free(handle);
|
|
|
|
done:
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* H5FD__s3comms_s3r_close */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_read
|
|
*
|
|
* Purpose: Read file pointed to by request handle, writing specified
|
|
* offset .. (offset + len - 1) bytes to buffer dest
|
|
*
|
|
* If `len` is 0, reads entirety of file starting at `offset`.
|
|
* If `offset` and `len` are both 0, reads entire file.
|
|
*
|
|
* If `offset + len` is greater than the file size, read is
|
|
* aborted and returns FAIL.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
herr_t
|
|
H5FD__s3comms_s3r_read(s3r_t *handle, haddr_t offset, size_t len, void *dest, size_t dest_size)
|
|
{
|
|
H5FD__s3comms_read_params_t read_params = {0};
|
|
H5FD__s3comms_aws_params_t *aws_params = NULL;
|
|
struct aws_http_message *request_http_message = NULL;
|
|
struct aws_s3_meta_request *request = NULL;
|
|
struct aws_s3_meta_request_options request_opts = {0};
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
if (!handle)
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "invalid handle");
|
|
if (!handle->purl)
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "invalid parsed url in handle");
|
|
if (!handle->priv_data)
|
|
HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "invalid handle private data");
|
|
if ((offset >= handle->filesize) || (offset + len > handle->filesize))
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "unable to read past EOF (%zu)", handle->filesize);
|
|
|
|
if (H5FD_ros3_debug_g) {
|
|
if (len > 0) {
|
|
fprintf(stderr, " -- GET: Bytes %" PRIuHADDR " - %" PRIuHADDR ", Request Size: %zu\n", offset,
|
|
offset + len - 1, len);
|
|
}
|
|
else {
|
|
fprintf(stderr, " -- GET: Bytes %" PRIuHADDR " - %" PRIuHADDR ", Request Size: %zu\n", offset,
|
|
handle->filesize - 1, handle->filesize - offset);
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
|
|
aws_params = (H5FD__s3comms_aws_params_t *)handle->priv_data;
|
|
|
|
/* Setup HTTP GET request message */
|
|
if (H5FD__s3comms_format_http_request_message(aws_params, "GET", handle->purl->bucket_name,
|
|
handle->purl->key, &request_http_message) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP request message");
|
|
|
|
/* Add "Host" header to HTTP message */
|
|
if (H5FD__s3comms_format_host_header(
|
|
aws_params, request_http_message, handle->purl->bucket_name,
|
|
(handle->alternate_purl ? handle->alternate_purl->host : handle->purl->host),
|
|
(handle->alternate_purl ? handle->alternate_purl->port : handle->purl->port)) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP 'Host' header");
|
|
|
|
/* Add "User-Agent" header to HTTP message */
|
|
if (H5FD__s3comms_format_user_agent_header(request_http_message) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP 'User-Agent' header");
|
|
|
|
/* Setup "Range: " header for retrieving a specific byte range if desired */
|
|
if (H5FD__s3comms_format_range_header(request_http_message, offset, len) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP 'Range' header");
|
|
|
|
/* Print out request headers if debugging is enabled */
|
|
if (H5FD_ros3_debug_g) {
|
|
struct aws_http_headers *request_headers;
|
|
|
|
if ((request_headers = aws_http_message_get_headers(request_http_message))) {
|
|
size_t num_headers = aws_http_headers_count(request_headers);
|
|
|
|
if (num_headers) {
|
|
fprintf(stderr, " -- request headers:\n");
|
|
for (size_t hdr_idx = 0; hdr_idx < num_headers; hdr_idx++) {
|
|
struct aws_http_header header = {0};
|
|
int ret = 0;
|
|
|
|
ret = aws_http_headers_get_index(request_headers, hdr_idx, &header);
|
|
if (AWS_ERROR_INVALID_INDEX != ret) {
|
|
fprintf(stderr, PRInSTR ": " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(header.name),
|
|
AWS_BYTE_CURSOR_PRI(header.value));
|
|
}
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initialize callback parameters */
|
|
read_params.request.status = SUCCEED;
|
|
read_params.request.finished = false;
|
|
read_params.request.mutex = &aws_params->req_mutex;
|
|
read_params.request.cvar = &aws_params->req_cvar;
|
|
read_params.read_buf = dest;
|
|
read_params.read_buf_size = dest_size;
|
|
read_params.read_offset = offset;
|
|
|
|
/* Setup a GetObject request to retrieve the requested data */
|
|
request_opts.type = AWS_S3_META_REQUEST_TYPE_GET_OBJECT;
|
|
request_opts.signing_config = NULL; /* Use signing config from client */
|
|
request_opts.message = request_http_message;
|
|
request_opts.body_callback = H5FD__s3comms_s3r_read_cb;
|
|
request_opts.finish_callback = H5FD__s3comms_s3r_req_finish_cb;
|
|
request_opts.user_data = &read_params;
|
|
|
|
/* Set alternate endpoint if specified */
|
|
if (handle->alternate_purl)
|
|
request_opts.endpoint = &aws_params->alt_parsed_uri;
|
|
|
|
request = aws_s3_client_make_meta_request(aws_params->client, &request_opts);
|
|
if (!request)
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't create AWS meta-request: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
/* Wait for request to complete */
|
|
if (AWS_OP_SUCCESS != aws_mutex_lock(read_params.request.mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't lock mutex: %s", aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS !=
|
|
aws_condition_variable_wait_pred(read_params.request.cvar, read_params.request.mutex,
|
|
H5FD__s3comms_s3r_req_finish_pred, &read_params.request)) {
|
|
aws_mutex_unlock(read_params.request.mutex);
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't wait on condition variable: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_unlock(read_params.request.mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't unlock mutex: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
assert(read_params.request.finished);
|
|
|
|
if (read_params.request.status != SUCCEED) {
|
|
/* If error message isn't set, some internal error occurred in aws-c-s3 */
|
|
if (!read_params.request.err_msg)
|
|
read_params.request.err_msg = "internal error during request setup";
|
|
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "error occurred while reading s3 object: %s",
|
|
read_params.request.err_msg);
|
|
}
|
|
|
|
done:
|
|
aws_http_message_release(request_http_message);
|
|
aws_s3_meta_request_release(request);
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_s3r_read() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_read_cb
|
|
*
|
|
* Purpose: Callback to receive the body for the request to get an
|
|
* object's data
|
|
*
|
|
* Return: AWS_OP_SUCCESS on success / AWS_OP_ERR on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static int
|
|
H5FD__s3comms_s3r_read_cb(struct aws_s3_meta_request H5_ATTR_UNUSED *meta_request,
|
|
const struct aws_byte_cursor *body, uint64_t range_start, void *user_data)
|
|
{
|
|
H5FD__s3comms_read_params_t *read_params = NULL;
|
|
size_t buf_offset = 0;
|
|
int ret_value = AWS_OP_SUCCESS;
|
|
|
|
read_params = (H5FD__s3comms_read_params_t *)user_data;
|
|
if (!read_params) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
/* If no read buffer is supplied, do nothing */
|
|
if (!read_params->read_buf)
|
|
goto done;
|
|
|
|
if (range_start < read_params->read_offset) {
|
|
read_params->request.err_msg =
|
|
"internal error - data chunk offset was smaller than initial buffer offset";
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
if (read_params->read_offset > UINT64_MAX) {
|
|
read_params->request.err_msg = "internal error - initial buffer offset was too large";
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
buf_offset = range_start - (uint64_t)read_params->read_offset;
|
|
|
|
if (buf_offset >= read_params->read_buf_size) {
|
|
read_params->request.err_msg = "internal error - data chunk offset was outside read buffer";
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
/* Check for overflowing read buffer */
|
|
if (buf_offset + body->len > read_params->read_buf_size) {
|
|
read_params->request.err_msg = "internal error - buffer overflow during read";
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
memcpy(read_params->read_buf + buf_offset, body->ptr, body->len);
|
|
|
|
done:
|
|
if (AWS_OP_SUCCESS != ret_value) {
|
|
if (read_params)
|
|
read_params->request.status = FAIL;
|
|
}
|
|
|
|
return ret_value;
|
|
} /* end H5FD__s3comms_s3r_read_cb() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_get_filesize
|
|
*
|
|
* Purpose: Retrieve the filesize of an open request handle
|
|
*
|
|
* Return: SUCCEED/FAIL
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
size_t
|
|
H5FD__s3comms_s3r_get_filesize(s3r_t *handle)
|
|
{
|
|
size_t ret_value = 0;
|
|
|
|
FUNC_ENTER_PACKAGE_NOERR
|
|
|
|
if (handle != NULL)
|
|
ret_value = handle->filesize;
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_s3r_get_filesize() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_getsize
|
|
*
|
|
* Purpose: Get the number of bytes of handle's target resource
|
|
*
|
|
* Return: SUCCEED/FAIL
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_s3r_getsize(s3r_t *handle)
|
|
{
|
|
H5FD__s3comms_getsize_params_t getsize_params = {0};
|
|
H5FD__s3comms_aws_params_t *aws_params = NULL;
|
|
struct aws_http_message *request_http_message = NULL;
|
|
struct aws_s3_meta_request *request = NULL;
|
|
struct aws_s3_meta_request_options request_opts = {0};
|
|
struct aws_byte_cursor aws_op_name_cursor = {0};
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(handle);
|
|
|
|
if (H5FD_ros3_debug_g) {
|
|
fprintf(stderr, " -- HEAD: Bucket: %s / Key: %s\n", handle->purl->bucket_name, handle->purl->key);
|
|
fflush(stderr);
|
|
}
|
|
|
|
aws_params = (H5FD__s3comms_aws_params_t *)handle->priv_data;
|
|
|
|
/* Setup HTTP HEAD request message */
|
|
if (H5FD__s3comms_format_http_request_message(aws_params, "HEAD", handle->purl->bucket_name,
|
|
handle->purl->key, &request_http_message) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP request message");
|
|
|
|
/* Add "Host" header to HTTP message */
|
|
if (H5FD__s3comms_format_host_header(
|
|
aws_params, request_http_message, handle->purl->bucket_name,
|
|
(handle->alternate_purl ? handle->alternate_purl->host : handle->purl->host),
|
|
(handle->alternate_purl ? handle->alternate_purl->port : handle->purl->port)) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP 'Host' header");
|
|
|
|
/* Add "User-Agent" header to HTTP message */
|
|
if (H5FD__s3comms_format_user_agent_header(request_http_message) < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't create HTTP 'User-Agent' header");
|
|
|
|
/* Print out request headers if debugging is enabled */
|
|
if (H5FD_ros3_debug_g) {
|
|
struct aws_http_headers *request_headers;
|
|
|
|
if ((request_headers = aws_http_message_get_headers(request_http_message))) {
|
|
size_t num_headers = aws_http_headers_count(request_headers);
|
|
|
|
if (num_headers) {
|
|
fprintf(stderr, " -- request headers:\n");
|
|
for (size_t hdr_idx = 0; hdr_idx < num_headers; hdr_idx++) {
|
|
struct aws_http_header header = {0};
|
|
int ret = 0;
|
|
|
|
ret = aws_http_headers_get_index(request_headers, hdr_idx, &header);
|
|
if (AWS_ERROR_INVALID_INDEX != ret) {
|
|
fprintf(stderr, PRInSTR ": " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(header.name),
|
|
AWS_BYTE_CURSOR_PRI(header.value));
|
|
}
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initialize callback parameters */
|
|
getsize_params.request.status = SUCCEED;
|
|
getsize_params.request.finished = false;
|
|
getsize_params.request.mutex = &aws_params->req_mutex;
|
|
getsize_params.request.cvar = &aws_params->req_cvar;
|
|
aws_byte_buf_init(&getsize_params.response_body, aws_params->allocator, 1024);
|
|
|
|
/* Setup a HeadObject request to get the object's size */
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
aws_op_name_cursor = aws_byte_cursor_from_c_str("HeadObject");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
request_opts.type = AWS_S3_META_REQUEST_TYPE_DEFAULT;
|
|
request_opts.operation_name = aws_op_name_cursor;
|
|
request_opts.signing_config = NULL; /* Use signing config from client */
|
|
request_opts.message = request_http_message;
|
|
request_opts.headers_callback = H5FD__s3comms_s3r_getsize_headers_cb;
|
|
request_opts.finish_callback = H5FD__s3comms_s3r_req_finish_cb;
|
|
request_opts.user_data = &getsize_params;
|
|
|
|
/* Set alternate endpoint if specified */
|
|
if (handle->alternate_purl)
|
|
request_opts.endpoint = &aws_params->alt_parsed_uri;
|
|
|
|
request = aws_s3_client_make_meta_request(aws_params->client, &request_opts);
|
|
if (!request)
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't create AWS meta-request: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
/* Wait for request to complete */
|
|
if (AWS_OP_SUCCESS != aws_mutex_lock(getsize_params.request.mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't lock mutex: %s", aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS !=
|
|
aws_condition_variable_wait_pred(getsize_params.request.cvar, getsize_params.request.mutex,
|
|
H5FD__s3comms_s3r_req_finish_pred, &getsize_params.request)) {
|
|
aws_mutex_unlock(getsize_params.request.mutex);
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't wait on condition variable: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_unlock(getsize_params.request.mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "couldn't unlock mutex: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
assert(getsize_params.request.finished);
|
|
|
|
if (getsize_params.request.status != SUCCEED) {
|
|
/* If error message isn't set, some internal error occurred in aws-c-s3 */
|
|
if (!getsize_params.request.err_msg)
|
|
getsize_params.request.err_msg = "internal error during request setup";
|
|
|
|
HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "error occurred while getting s3 object size: %s",
|
|
getsize_params.request.err_msg);
|
|
}
|
|
|
|
if (getsize_params.object_size == 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "Content-Length of S3 object was 0");
|
|
|
|
handle->filesize = getsize_params.object_size;
|
|
|
|
if (H5FD_ros3_debug_g) {
|
|
fprintf(stderr, " -- file size: %zu bytes\n", handle->filesize);
|
|
fflush(stderr);
|
|
}
|
|
|
|
done:
|
|
aws_http_message_release(request_http_message);
|
|
aws_byte_buf_clean_up(&getsize_params.response_body);
|
|
aws_s3_meta_request_release(request);
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_s3r_getsize() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_s3r_getsize_headers_cb
|
|
*
|
|
* Purpose: Callback to receive the response headers for the request to
|
|
* get an object's size
|
|
*
|
|
* Return: AWS_OP_SUCCESS on success / AWS_OP_ERR on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static int
|
|
H5FD__s3comms_s3r_getsize_headers_cb(struct aws_s3_meta_request H5_ATTR_UNUSED *meta_request,
|
|
const struct aws_http_headers *headers, int response_status,
|
|
void *user_data)
|
|
{
|
|
H5FD__s3comms_getsize_params_t *params = NULL;
|
|
struct aws_byte_cursor header_name = {0};
|
|
struct aws_byte_cursor out_cursor = {0};
|
|
uint64_t content_length = 0;
|
|
int ret_value = AWS_OP_SUCCESS;
|
|
|
|
params = (H5FD__s3comms_getsize_params_t *)user_data;
|
|
if (!params) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
if (H5FD_ros3_debug_g) {
|
|
fprintf(stderr, " -- response status: %d\n", response_status);
|
|
fflush(stderr);
|
|
}
|
|
|
|
if (HTTP_CLIENT_SUCCESS(response_status)) {
|
|
/* If debugging is enabled, print the response headers. On error,
|
|
* they will be printed in the request finish callback.
|
|
*/
|
|
if (H5FD_ros3_debug_g) {
|
|
size_t num_headers = aws_http_headers_count(headers);
|
|
|
|
if (num_headers) {
|
|
fprintf(stderr, " -- response headers:\n");
|
|
for (size_t hdr_idx = 0; hdr_idx < num_headers; hdr_idx++) {
|
|
struct aws_http_header header = {0};
|
|
int ret = 0;
|
|
|
|
ret = aws_http_headers_get_index(headers, hdr_idx, &header);
|
|
if (AWS_ERROR_INVALID_INDEX == ret) {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
fprintf(stderr, PRInSTR ": " PRInSTR "\n", AWS_BYTE_CURSOR_PRI(header.name),
|
|
AWS_BYTE_CURSOR_PRI(header.value));
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
header_name = aws_byte_cursor_from_c_str("Content-Length");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_headers_get(headers, header_name, &out_cursor)) {
|
|
if (AWS_ERROR_HTTP_HEADER_NOT_FOUND == aws_last_error()) {
|
|
params->request.err_msg = aws_error_str(AWS_ERROR_S3_MISSING_CONTENT_LENGTH_HEADER);
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_MISSING_CONTENT_LENGTH_HEADER);
|
|
}
|
|
else {
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_CANCELED);
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_cursor_utf8_parse_u64(out_cursor, &content_length)) {
|
|
params->request.err_msg = "couldn't parse valid value from Content-Length header";
|
|
ret_value = aws_raise_error(AWS_ERROR_S3_INVALID_CONTENT_LENGTH_HEADER);
|
|
goto done;
|
|
}
|
|
|
|
params->object_size = (size_t)content_length;
|
|
}
|
|
|
|
done:
|
|
if (AWS_OP_SUCCESS != ret_value) {
|
|
if (params)
|
|
params->request.status = FAIL;
|
|
}
|
|
|
|
return ret_value;
|
|
} /* end H5FD__s3comms_s3r_getsize_headers_cb() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_parse_url
|
|
*
|
|
* Purpose: Parse a URL into separate components. An aws_uri structure
|
|
* is populated after parsing the URL, but a more generic
|
|
* parsed_url_t structure is also returned in order to keep
|
|
* compatibility with the generic s3comms interface.
|
|
*
|
|
* Return: Success: A pointer to a parsed_url_t
|
|
* Failure: NULL
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static parsed_url_t *
|
|
H5FD__s3comms_parse_url(s3r_t *handle, const char *url, struct aws_uri *uri)
|
|
{
|
|
H5FD__s3comms_aws_params_t *aws_params = NULL;
|
|
struct aws_byte_cursor url_cursor = {0};
|
|
struct aws_byte_cursor s3_cursor = {0};
|
|
parsed_url_t *purl = NULL;
|
|
char *host_copy = NULL;
|
|
char *host_component = NULL;
|
|
bool is_s3_url = false;
|
|
bool is_virtual_style = false;
|
|
parsed_url_t *ret_value = NULL;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(handle);
|
|
assert(url);
|
|
assert(uri);
|
|
|
|
aws_params = (H5FD__s3comms_aws_params_t *)handle->priv_data;
|
|
|
|
/* Allocate memory for the parsed URL to return */
|
|
if (NULL == (purl = calloc(1, sizeof(parsed_url_t))))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for parsed_url_t");
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
url_cursor = aws_byte_cursor_from_c_str(url);
|
|
s3_cursor = aws_byte_cursor_from_c_str("s3");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_uri_init_parse(uri, aws_params->allocator, &url_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, NULL, "unable to parse url");
|
|
|
|
/* Is this a 's3://' url? */
|
|
if (aws_byte_cursor_eq_ignore_case(&uri->scheme, &s3_cursor))
|
|
is_s3_url = true;
|
|
|
|
/* URI section buffers use library-specific structure and may also not
|
|
* be NUL-terminated, so make our own s3comms API-compatible C string
|
|
* copies
|
|
*/
|
|
if (NULL == (purl->scheme = malloc(uri->scheme.len + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL scheme portion");
|
|
memcpy(purl->scheme, uri->scheme.ptr, uri->scheme.len);
|
|
purl->scheme[uri->scheme.len] = '\0';
|
|
|
|
/* Special processing for 's3://' urls */
|
|
if (is_s3_url) {
|
|
if (NULL == (purl->host = malloc(HOST_NAME_LEN)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL host portion");
|
|
|
|
/* Bucket name will be prepended to host later on if necessary */
|
|
snprintf(purl->host, HOST_NAME_LEN, "s3.%s.amazonaws.com", handle->aws_region);
|
|
}
|
|
else {
|
|
if (NULL == (purl->host = malloc(uri->host_name.len + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL host portion");
|
|
memcpy(purl->host, uri->host_name.ptr, uri->host_name.len);
|
|
purl->host[uri->host_name.len] = '\0';
|
|
|
|
if (uri->port != 0) {
|
|
if (NULL == (purl->port = malloc(11)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL port portion");
|
|
snprintf(purl->port, 11, "%" PRIu32, uri->port);
|
|
}
|
|
}
|
|
|
|
if (NULL == (purl->path = malloc(uri->path.len + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL path portion");
|
|
memcpy(purl->path, uri->path.ptr, uri->path.len);
|
|
purl->path[uri->path.len] = '\0';
|
|
|
|
if (NULL == (purl->query = malloc(uri->query_string.len + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't allocate space for URL query string portion");
|
|
memcpy(purl->query, uri->query_string.ptr, uri->query_string.len);
|
|
purl->query[uri->query_string.len] = '\0';
|
|
|
|
/* Parse out the bucket name and object key */
|
|
if (is_s3_url) {
|
|
if (NULL == (purl->bucket_name = HDstrndup((char *)uri->host_name.ptr, uri->host_name.len)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't duplicate bucket name");
|
|
|
|
if (uri->path.len == 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, NULL, "invalid path parsed from URL");
|
|
|
|
/* Path will always include a leading '/' */
|
|
if (NULL == (purl->key = HDstrndup((char *)uri->path.ptr + 1, uri->path.len - 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't duplicate path");
|
|
}
|
|
else if (uri->path.len > 0) {
|
|
/* Attempt to determine whether the URL is a virtual-hosted-style
|
|
* or path-style URL using very simple heuristics
|
|
*/
|
|
if (NULL == (host_copy = strdup(purl->host)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't duplicate host string");
|
|
|
|
if (NULL != strstr(host_copy, ".")) {
|
|
if (NULL == (host_component = strtok(host_copy, ".")))
|
|
HGOTO_ERROR(H5E_VFL, H5E_INTERNAL, NULL, "internal error occurred when parsing url");
|
|
|
|
/* If the first component is 's3' or a specific endpoint
|
|
* or legacy s3-region_code format, assume this is a
|
|
* path-style URL. This could be problematic for specific
|
|
* bucket names like 's3-files', but should be good enough
|
|
* for now.
|
|
*/
|
|
if (strcmp(host_component, "s3") == 0 || strncmp(host_component, "s3-", 3) == 0)
|
|
is_virtual_style = false;
|
|
else {
|
|
/* Attempt to find the ".s3." or ".s3-" portion of the string */
|
|
do {
|
|
host_component = strtok(NULL, ".");
|
|
} while (host_component && strcmp(host_component, "s3") != 0 &&
|
|
strncmp(host_component, "s3-", 3) != 0);
|
|
|
|
if (!host_component) {
|
|
/* No '.s3.' or '.s3-' in host; assume path-style for now
|
|
* to cover testing cases like http://localhost/bucket/key,
|
|
* though this could be problematic with aliasing mechanisms
|
|
* or URLs to other s3-compatible storage.
|
|
*/
|
|
is_virtual_style = false;
|
|
}
|
|
else {
|
|
is_virtual_style = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* No '.' in host; assume path-style for now to cover testing
|
|
* cases like http://localhost/bucket/key, though this could
|
|
* be problematic with aliasing mechanisms.
|
|
*/
|
|
is_virtual_style = false;
|
|
}
|
|
|
|
if (is_virtual_style) {
|
|
ptrdiff_t bucket_name_len;
|
|
char *s3_begin;
|
|
|
|
/* Copy up to the ".s3." or ".s3-" portion of the string to cover
|
|
* bucket names with "." in them. Note that this could have issues
|
|
* with specific bucket names like 's3-files', but should be good
|
|
* enough for now.
|
|
*/
|
|
s3_begin = strstr(purl->host, ".s3.");
|
|
if (!s3_begin)
|
|
s3_begin = strstr(purl->host, ".s3-");
|
|
if (!s3_begin)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't parse bucket name from url");
|
|
|
|
bucket_name_len = s3_begin - purl->host;
|
|
if (bucket_name_len < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_INTERNAL, NULL,
|
|
"internal error occurred when parsing bucket name from url");
|
|
|
|
if (NULL == (purl->bucket_name = HDstrndup(purl->host, (size_t)bucket_name_len)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "can't duplicate bucket name");
|
|
|
|
/* Path will always include a leading '/' */
|
|
if (NULL == (purl->key = strdup(purl->path + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't duplicate path");
|
|
}
|
|
else {
|
|
char *slash;
|
|
|
|
/* Assume path-style URI */
|
|
|
|
/* Path will always include a leading '/' */
|
|
if (NULL == (purl->bucket_name = strdup(purl->path + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't duplicate path");
|
|
|
|
/* Copy object key string before mutating bucket name string */
|
|
if (NULL == (slash = strstr(purl->bucket_name, "/")))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't parse object key from path");
|
|
|
|
if (NULL == (purl->key = strdup(slash + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't duplicate path");
|
|
|
|
/* Isolate bucket name component */
|
|
if (NULL == strtok(purl->bucket_name, "/"))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, NULL, "can't parse bucket name from path");
|
|
}
|
|
}
|
|
|
|
ret_value = purl;
|
|
|
|
done:
|
|
free(host_copy);
|
|
|
|
if (ret_value == NULL) {
|
|
if (H5FD__s3comms_free_purl(purl) < 0)
|
|
HDONE_ERROR(H5E_VFL, H5E_BADVALUE, NULL, "unable to free parsed url structure");
|
|
}
|
|
else if (H5FD_ros3_debug_g && purl) {
|
|
fprintf(stderr, " -- parsed URL as:\n");
|
|
fprintf(stderr, " - Scheme: %s\n", purl->scheme);
|
|
fprintf(stderr, " - Host: %s\n", purl->host);
|
|
if (purl->port)
|
|
fprintf(stderr, " - Port: %s\n", purl->port);
|
|
fprintf(stderr, " - Path: %s\n", purl->path);
|
|
fprintf(stderr, " - Query: %s\n", purl->query);
|
|
fprintf(stderr, " - Bucket: %s\n", purl->bucket_name);
|
|
fprintf(stderr, " - Key: %s\n", purl->key);
|
|
}
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_parse_url() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_free_purl
|
|
*
|
|
* Purpose: Release resources from a parsed_url_t pointer
|
|
*
|
|
* Return: SUCCEED (Can't fail - passing NULL is okay)
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_free_purl(parsed_url_t *purl)
|
|
{
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE_NOERR
|
|
|
|
if (NULL == purl)
|
|
HGOTO_DONE(SUCCEED);
|
|
|
|
free(purl->scheme);
|
|
free(purl->host);
|
|
free(purl->port);
|
|
free(purl->path);
|
|
free(purl->query);
|
|
free(purl->bucket_name);
|
|
free(purl->key);
|
|
|
|
free(purl);
|
|
|
|
done:
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_free_purl() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_get_aws_region
|
|
*
|
|
* Purpose: Helper function to get a specified AWS region string and
|
|
* return an allocated copy of it, which the caller must free
|
|
* with free(). The following are checked in order:
|
|
*
|
|
* - The File Access Property List specified during file open
|
|
* - The AWS_REGION environment variable
|
|
* - The AWS_DEFAULT_REGION environment variable
|
|
* - The AWS configuration file (~/.aws/config by default)
|
|
* - The 'default' profile from this file is used, unless a
|
|
* different profile is specified with the AWS_PROFILE
|
|
* environment variable
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_get_aws_region(const H5FD__s3comms_aws_params_t *aws_params, const H5FD_ros3_fapl_t *fa,
|
|
char **aws_region_out)
|
|
{
|
|
struct aws_profile_collection *config_coll = NULL;
|
|
const struct aws_profile *aws_profile = NULL;
|
|
struct aws_string *config_file_path = NULL;
|
|
struct aws_string *profile_name_str = NULL;
|
|
struct aws_string *region_str = NULL;
|
|
char *region_copy = NULL;
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(aws_params);
|
|
assert(aws_region_out);
|
|
|
|
/* From FAPL */
|
|
if (fa && fa->aws_region[0] != '\0') {
|
|
if (NULL == (region_copy = HDstrndup(fa->aws_region, H5FD_ROS3_MAX_REGION_LEN + 1)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't copy AWS region from FAPL");
|
|
}
|
|
|
|
/* From AWS_REGION environment variable */
|
|
if (!region_copy) {
|
|
char *env_region = getenv("AWS_REGION");
|
|
|
|
if (env_region && (*env_region != '\0')) {
|
|
if (NULL == (region_copy = strdup(env_region)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't copy AWS region string from environment variable");
|
|
}
|
|
}
|
|
|
|
/* From AWS_DEFAULT_REGION environment variable */
|
|
if (!region_copy) {
|
|
char *env_default_region = getenv("AWS_DEFAULT_REGION");
|
|
|
|
if (env_default_region && (*env_default_region != '\0')) {
|
|
if (NULL == (region_copy = strdup(env_default_region)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't copy default AWS region string from environment variable");
|
|
}
|
|
}
|
|
|
|
/* From AWS configuration file */
|
|
if (!region_copy) {
|
|
const struct aws_profile_property *region = NULL;
|
|
const struct aws_string *prop_val = NULL;
|
|
|
|
/* Resolve the configuration file name, which could be overridden with AWS_CONFIG_FILE */
|
|
if (NULL == (config_file_path = aws_get_config_file_path(aws_params->allocator, NULL)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, FAIL, "couldn't get AWS configuration file path");
|
|
|
|
config_coll =
|
|
aws_profile_collection_new_from_file(aws_params->allocator, config_file_path, AWS_PST_CONFIG);
|
|
if (!config_coll)
|
|
HGOTO_DONE(SUCCEED); /* No configuration file to read */
|
|
|
|
/* Resolve the profile name to use, which could be overridden with AWS_PROFILE */
|
|
if (NULL == (profile_name_str = aws_get_profile_name(aws_params->allocator, NULL)))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTGET, FAIL,
|
|
"couldn't determine AWS profile to retrieve region information from");
|
|
|
|
/* Read from the profile if it exists in the configuration file */
|
|
aws_profile = aws_profile_collection_get_profile(config_coll, profile_name_str);
|
|
if (!aws_profile)
|
|
HGOTO_DONE(SUCCEED); /* No such profile in configuration file */
|
|
|
|
if (NULL == (region_str = aws_string_new_from_c_str(aws_params->allocator, "region")))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate space for AWS string");
|
|
|
|
region = aws_profile_get_property(aws_profile, region_str);
|
|
if (!region)
|
|
HGOTO_DONE(SUCCEED); /* No 'region' entry under this profile in configuration file */
|
|
|
|
prop_val = aws_profile_property_get_value(region);
|
|
if (prop_val)
|
|
if (NULL == (region_copy = strdup(aws_string_c_str(prop_val))))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't copy AWS region string from AWS configuration file");
|
|
}
|
|
|
|
*aws_region_out = region_copy;
|
|
|
|
done:
|
|
aws_profile_collection_destroy(config_coll);
|
|
aws_string_destroy(config_file_path);
|
|
aws_string_destroy(profile_name_str);
|
|
aws_string_destroy(region_str);
|
|
|
|
if (ret_value < 0) {
|
|
free(region_copy);
|
|
*aws_region_out = NULL;
|
|
}
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_get_aws_region() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_get_credentials_provider
|
|
*
|
|
* Purpose: Helper function to create a credentials provider object for
|
|
* use in authentication. If a FAPL is provided and
|
|
* 'authenticate' is true in the FAPL, credentials are only
|
|
* sourced from the FAPL. If 'authenticate' is false (or no FAPL
|
|
* is provided), an attempt is made to source credentials with
|
|
* the default provider chain from the aws-c-s3 library, which
|
|
* looks at several sources, including the standard AWS
|
|
* configuration files and environment variables. If no
|
|
* credentials can be sourced from those places, an anonymous
|
|
* credentials provider is used.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_get_credentials_provider(H5FD__s3comms_aws_params_t *aws_params, const H5FD_ros3_fapl_t *fa,
|
|
const char *fapl_token,
|
|
struct aws_credentials_provider **credentials_provider_out)
|
|
{
|
|
struct aws_credentials_provider *credentials_provider = NULL;
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(aws_params);
|
|
assert(credentials_provider_out);
|
|
|
|
/* From FAPL */
|
|
if (fa && fa->authenticate) {
|
|
struct aws_credentials_provider_static_options credentials_provider_opts = {0};
|
|
|
|
if (fa->secret_id[0] == '\0' || fa->secret_key[0] == '\0')
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL,
|
|
"'secret_id' and 'secret_key' must both be non-empty strings when 'authenticate' is "
|
|
"true in FAPL");
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
credentials_provider_opts.access_key_id = aws_byte_cursor_from_c_str(fa->secret_id);
|
|
credentials_provider_opts.secret_access_key = aws_byte_cursor_from_c_str(fa->secret_key);
|
|
if (fapl_token)
|
|
credentials_provider_opts.session_token = aws_byte_cursor_from_c_str(fapl_token);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
credentials_provider =
|
|
aws_credentials_provider_new_static(aws_params->allocator, &credentials_provider_opts);
|
|
if (!credentials_provider)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't create credentials provider for AWS client: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
/* From standard AWS sources */
|
|
if (!credentials_provider) {
|
|
struct aws_credentials_provider_chain_default_options credentials_provider_opts = {0};
|
|
H5FD__s3comms_check_cred_provider_params_t check_cred_provider_params = {0};
|
|
struct aws_byte_cursor access_key_id = {0};
|
|
struct aws_byte_cursor secret_access_key = {0};
|
|
|
|
/* Use default credential provider chain. This will currently look for
|
|
* credentials in the following order:
|
|
*
|
|
* - From the environment (environment variables)
|
|
* - From profile files (~/.aws/config and ~/.aws/credentials by default)
|
|
* - STS web identity (temporary security credentials)
|
|
* - EC2 instance metadata
|
|
*/
|
|
credentials_provider_opts.bootstrap = aws_params->client_bootstrap;
|
|
|
|
credentials_provider =
|
|
aws_credentials_provider_new_chain_default(aws_params->allocator, &credentials_provider_opts);
|
|
if (!credentials_provider)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't create default credentials provider chain for AWS client: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
/* Check to see if credentials could be sourced from this provider */
|
|
check_cred_provider_params.request.status = SUCCEED;
|
|
check_cred_provider_params.request.finished = false;
|
|
check_cred_provider_params.request.mutex = &aws_params->req_mutex;
|
|
check_cred_provider_params.request.cvar = &aws_params->req_cvar;
|
|
if (AWS_OP_SUCCESS !=
|
|
aws_credentials_provider_get_credentials(
|
|
credentials_provider, H5FD__s3comms_cred_provider_get_creds_cb, &check_cred_provider_params))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't check for credentials sourced from default credentials provider chain: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
/* Wait for request to complete */
|
|
if (AWS_OP_SUCCESS != aws_mutex_lock(&aws_params->req_mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL, "couldn't lock mutex: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_condition_variable_wait_pred(&aws_params->req_cvar, &aws_params->req_mutex,
|
|
H5FD__s3comms_cred_provider_pred,
|
|
&check_cred_provider_params.request)) {
|
|
aws_mutex_unlock(&aws_params->req_mutex);
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL, "couldn't wait on condition variable: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_mutex_unlock(&aws_params->req_mutex))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL, "couldn't unlock mutex: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
assert(check_cred_provider_params.request.finished);
|
|
|
|
if (check_cred_provider_params.request.status != SUCCEED) {
|
|
/* If error message isn't set, some internal error occurred in aws-c-s3 */
|
|
if (!check_cred_provider_params.request.err_msg)
|
|
check_cred_provider_params.request.err_msg = "internal error during request setup";
|
|
|
|
aws_credentials_release(check_cred_provider_params.credentials);
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't determine if credentials could be source from default credentials provider "
|
|
"chain: %s",
|
|
check_cred_provider_params.request.err_msg);
|
|
}
|
|
|
|
if (check_cred_provider_params.credentials) {
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
access_key_id = aws_credentials_get_access_key_id(check_cred_provider_params.credentials);
|
|
secret_access_key = aws_credentials_get_secret_access_key(check_cred_provider_params.credentials);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
}
|
|
|
|
if (!check_cred_provider_params.credentials ||
|
|
(access_key_id.len == 0 && secret_access_key.len == 0)) {
|
|
/* Fallback to anonymous credentials */
|
|
aws_credentials_provider_release(credentials_provider);
|
|
credentials_provider = NULL;
|
|
}
|
|
|
|
/* Release potential reference taken on credentials object by callback function */
|
|
aws_credentials_release(check_cred_provider_params.credentials);
|
|
}
|
|
|
|
/* Use anonymous credentials */
|
|
if (!credentials_provider) {
|
|
credentials_provider = aws_credentials_provider_new_anonymous(aws_params->allocator, NULL);
|
|
if (!credentials_provider)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTINIT, FAIL,
|
|
"couldn't create anonymous credentials provider for AWS client: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
*credentials_provider_out = credentials_provider;
|
|
|
|
done:
|
|
if (ret_value < 0) {
|
|
aws_credentials_provider_release(credentials_provider);
|
|
*credentials_provider_out = NULL;
|
|
}
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_get_credentials_provider() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_format_http_request_message
|
|
*
|
|
* Purpose: Helper function to format the HTTP message for a request. Note
|
|
* this only includes the leading request line and excludes any
|
|
* additional headers, request body, etc.
|
|
*
|
|
* The returned struct aws_http_message pointer must be freed
|
|
* by the caller with aws_http_message_release() when finished
|
|
* with it.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_format_http_request_message(const H5FD__s3comms_aws_params_t *aws_params,
|
|
const char *HTTP_method, const char *bucket_name,
|
|
const char *object_key, struct aws_http_message **message_out)
|
|
{
|
|
struct aws_http_message *request_http_message = NULL;
|
|
struct aws_byte_cursor http_method_cursor = {0};
|
|
struct aws_byte_cursor path_cursor = {0};
|
|
struct aws_byte_cursor key_cursor = {0};
|
|
struct aws_byte_cursor slash_cursor = {0};
|
|
struct aws_byte_buf path_buf = {0};
|
|
bool use_virtual_style = true; /* Use virtual-hosted-style request by default */
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(aws_params);
|
|
assert(HTTP_method);
|
|
assert(bucket_name);
|
|
assert(object_key);
|
|
assert(message_out);
|
|
|
|
if (aws_params->force_path_style)
|
|
use_virtual_style = false;
|
|
|
|
/*
|
|
* If an alternate endpoint URL was specified, use a path-style
|
|
* request for now. This isn't the most ideal, but is currently
|
|
* required for testing.
|
|
*/
|
|
if (aws_params->alt_parsed_uri.self_size != 0)
|
|
use_virtual_style = false;
|
|
|
|
/*
|
|
* If bucket has a "." in the name, use a path-style request
|
|
* as virtual-hosted-style requests don't directly support buckets
|
|
* with "." when using HTTPS.
|
|
*/
|
|
if (strstr(bucket_name, "."))
|
|
use_virtual_style = false;
|
|
|
|
AWS_ZERO_STRUCT(path_buf);
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
http_method_cursor = aws_byte_cursor_from_c_str(HTTP_method);
|
|
key_cursor = aws_byte_cursor_from_c_str(object_key);
|
|
slash_cursor = aws_byte_cursor_from_c_str("/");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
request_http_message = aws_http_message_new_request(aws_params->allocator);
|
|
if (!request_http_message)
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate memory for HTTP header: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_message_set_request_method(request_http_message, http_method_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't set HTTP request method: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_init_copy_from_cursor(&path_buf, aws_params->allocator, slash_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate memory for path string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (!use_virtual_style) {
|
|
struct aws_byte_cursor bucket_cursor;
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
bucket_cursor = aws_byte_cursor_from_c_str(bucket_name);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&path_buf, &bucket_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate memory for HTTP request string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&path_buf, &slash_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate memory for HTTP request string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
}
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&path_buf, &key_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "couldn't allocate memory for HTTP request string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
path_cursor = aws_byte_cursor_from_buf(&path_buf);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_message_set_request_path(request_http_message, path_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't set HTTP request path: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
/* Message body not supported here currently */
|
|
aws_http_message_set_body_stream(request_http_message, NULL);
|
|
|
|
*message_out = request_http_message;
|
|
|
|
done:
|
|
if (ret_value < 0) {
|
|
aws_http_message_release(request_http_message);
|
|
}
|
|
|
|
aws_byte_buf_clean_up(&path_buf);
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value);
|
|
} /* end H5FD__s3comms_format_http_request_message() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_format_host_header
|
|
*
|
|
* Purpose: Helper function to format the "Host: " header for a request
|
|
* and add it to the specified HTTP message structure.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_format_host_header(const H5FD__s3comms_aws_params_t *aws_params,
|
|
struct aws_http_message *message, const char *bucket_name,
|
|
const char *host_name, const char *port)
|
|
{
|
|
struct aws_http_header host_header = {0};
|
|
struct aws_byte_cursor host_cursor = {0};
|
|
struct aws_byte_buf host_buf = {0};
|
|
bool use_virtual_style = true; /* Use virtual-hosted-style request by default */
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(aws_params);
|
|
assert(message);
|
|
assert(bucket_name);
|
|
assert(host_name);
|
|
|
|
if (aws_params->force_path_style)
|
|
use_virtual_style = false;
|
|
|
|
/*
|
|
* If an alternate endpoint URL was specified, use a path-style
|
|
* request for now. This isn't the most ideal, but is currently
|
|
* required for testing.
|
|
*/
|
|
if (aws_params->alt_parsed_uri.self_size != 0)
|
|
use_virtual_style = false;
|
|
|
|
/*
|
|
* If bucket has a "." in the name, use a path-style request
|
|
* as virtual-hosted-style requests don't directly support buckets
|
|
* with "." when using HTTPS.
|
|
*/
|
|
if (strstr(bucket_name, "."))
|
|
use_virtual_style = false;
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
host_cursor = aws_byte_cursor_from_c_str(host_name);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (use_virtual_style) {
|
|
struct aws_byte_cursor bucket_cursor;
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
bucket_cursor = aws_byte_cursor_from_c_str(bucket_name);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
AWS_ZERO_STRUCT(host_buf);
|
|
|
|
/*
|
|
* Check to see if the original host name already has the bucket
|
|
* name included (i.e., a virtual-hosted style URL), in which
|
|
* case we can just use it directly. Otherwise, form the host
|
|
* header by prepending the bucket name to the host name.
|
|
*/
|
|
if (aws_byte_cursor_starts_with_ignore_case(&host_cursor, &bucket_cursor)) {
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
host_header.value = host_cursor;
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
}
|
|
else {
|
|
struct aws_byte_cursor period_cursor;
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
period_cursor = aws_byte_cursor_from_c_str(".");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS !=
|
|
aws_byte_buf_init_copy_from_cursor(&host_buf, aws_params->allocator, bucket_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&host_buf, &period_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&host_buf, &host_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
host_header.value = aws_byte_cursor_from_buf(&host_buf);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
}
|
|
}
|
|
else {
|
|
/* If port was specified, include in Host header instead of using default */
|
|
if (port) {
|
|
struct aws_byte_cursor port_cursor;
|
|
struct aws_byte_cursor colon_cursor;
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
port_cursor = aws_byte_cursor_from_c_str(port);
|
|
colon_cursor = aws_byte_cursor_from_c_str(":");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
AWS_ZERO_STRUCT(host_buf);
|
|
|
|
if (AWS_OP_SUCCESS !=
|
|
aws_byte_buf_init_copy_from_cursor(&host_buf, aws_params->allocator, host_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&host_buf, &colon_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
if (AWS_OP_SUCCESS != aws_byte_buf_append_dynamic(&host_buf, &port_cursor))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL,
|
|
"couldn't allocate memory for 'Host' header string: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
host_header.value = aws_byte_cursor_from_buf(&host_buf);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
}
|
|
else {
|
|
host_header.value = host_cursor;
|
|
}
|
|
}
|
|
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
host_header.name = aws_byte_cursor_from_c_str("Host");
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_message_add_header(message, host_header))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't add 'Host' header to HTTP message: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
done:
|
|
aws_byte_buf_clean_up(&host_buf);
|
|
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_format_host_header() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_format_range_header
|
|
*
|
|
* Purpose: Helper function to format the "Range: " header for a request
|
|
* and add it to the specified HTTP message structure. No action
|
|
* is performed if both `offset` and `len` are 0, as this implies
|
|
* reading of the whole file.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_format_range_header(struct aws_http_message *message, haddr_t offset, size_t len)
|
|
{
|
|
struct aws_http_header range_header = {0};
|
|
int ret = 0;
|
|
char byte_range_str[S3COMMS_MAX_RANGE_STRING_SIZE];
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(message);
|
|
|
|
if (offset == 0 && len == 0)
|
|
HGOTO_DONE(SUCCEED);
|
|
|
|
if (len > 0) {
|
|
ret = snprintf(byte_range_str, sizeof(byte_range_str), "bytes=%" PRIuHADDR "-%" PRIuHADDR, offset,
|
|
offset + len - 1);
|
|
}
|
|
else if (offset > 0) {
|
|
ret = snprintf(byte_range_str, sizeof(byte_range_str), "bytes=%" PRIuHADDR "-", offset);
|
|
}
|
|
|
|
if (ret < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_SYSTEM, FAIL, "snprintf error");
|
|
if ((size_t)ret >= sizeof(byte_range_str))
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "unable to format HTTP Range value");
|
|
|
|
/* Add "Range: " header to HTTP message */
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
range_header.name = aws_byte_cursor_from_c_str("Range");
|
|
range_header.value = aws_byte_cursor_from_c_str(byte_range_str);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_message_add_header(message, range_header))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't add 'Range' header to HTTP message: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
done:
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_format_range_header() */
|
|
|
|
/*----------------------------------------------------------------------------
|
|
* Function: H5FD__s3comms_format_user_agent_header
|
|
*
|
|
* Purpose: Helper function to format the "User-Agent: " header for a
|
|
* request and add it to the specified HTTP message structure.
|
|
*
|
|
* Return: Non-negative on success/Negative on failure
|
|
*----------------------------------------------------------------------------
|
|
*/
|
|
static herr_t
|
|
H5FD__s3comms_format_user_agent_header(struct aws_http_message *message)
|
|
{
|
|
struct aws_http_header user_agent_header = {0};
|
|
int ret = 0;
|
|
char user_agent_str[MAX_USER_AGENT_STRING_SIZE];
|
|
herr_t ret_value = SUCCEED;
|
|
|
|
FUNC_ENTER_PACKAGE
|
|
|
|
assert(message);
|
|
|
|
ret = snprintf(user_agent_str, sizeof(user_agent_str), "libhdf5/%u.%u.%u (vfd:ros3) libaws-c-s3",
|
|
H5_VERS_MAJOR, H5_VERS_MINOR, H5_VERS_RELEASE);
|
|
|
|
if (ret < 0)
|
|
HGOTO_ERROR(H5E_VFL, H5E_SYSTEM, FAIL, "snprintf error");
|
|
if ((size_t)ret >= sizeof(user_agent_str))
|
|
HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "unable to format HTTP User-Agent value");
|
|
|
|
/* Add "User-Agent: " header to HTTP message */
|
|
H5_WARN_AGGREGATE_RETURN_OFF
|
|
user_agent_header.name = aws_byte_cursor_from_c_str("User-Agent");
|
|
user_agent_header.value = aws_byte_cursor_from_c_str(user_agent_str);
|
|
H5_WARN_AGGREGATE_RETURN_ON
|
|
|
|
if (AWS_OP_SUCCESS != aws_http_message_add_header(message, user_agent_header))
|
|
HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "couldn't add 'User-Agent' header to HTTP message: %s",
|
|
aws_error_str(aws_last_error()));
|
|
|
|
done:
|
|
FUNC_LEAVE_NOAPI(ret_value)
|
|
} /* end H5FD__s3comms_format_user_agent_header() */
|
|
|
|
/*
|
|
* Maps HTTP status codes to generic strings for cases where
|
|
* the aws-c-s3 library doesn't have a particular message
|
|
* for the response
|
|
*/
|
|
static const char *
|
|
H5FD__s3comms_httpcode_to_str(long httpcode, bool *handled)
|
|
{
|
|
*handled = true;
|
|
|
|
switch (httpcode) {
|
|
case 301:
|
|
return "resource has been permanently moved";
|
|
break;
|
|
case 400:
|
|
return "malformed/Bad request for resource; possible mismatch between specified AWS region and "
|
|
"region in URL (if any)";
|
|
break;
|
|
case 401:
|
|
return "valid authentication needed to access resource";
|
|
break;
|
|
case 403:
|
|
return "unauthorized access to resource";
|
|
break;
|
|
case 404:
|
|
return "resource not found";
|
|
break;
|
|
case 405:
|
|
return "method not allowed";
|
|
break;
|
|
case 408:
|
|
return "request timed out";
|
|
break;
|
|
case 409:
|
|
return "resource already exists";
|
|
break;
|
|
case 410:
|
|
return "resource has been deleted";
|
|
break;
|
|
case 413:
|
|
return "request for resource was too large";
|
|
break;
|
|
case 416:
|
|
return "requested resource byte range was not satisfiable";
|
|
break;
|
|
case 429:
|
|
return "too many requests";
|
|
break;
|
|
case 500:
|
|
return "internal server error";
|
|
break;
|
|
case 501:
|
|
return "request method not implemented";
|
|
break;
|
|
case 502:
|
|
return "bad gateway";
|
|
break;
|
|
case 503:
|
|
return "service unavailable";
|
|
break;
|
|
default:
|
|
*handled = false;
|
|
return "unknown/unhandled HTTP status code";
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif /* H5_HAVE_ROS3_VFD */
|