mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
7bdd878ae4
While performing SAST scanning using Cppcheck against source code of commit81196469
, several code vulnerabilities were found. Fix following issues: 1. Parameters of `snprintf` function are incorrect. Cppcheck error: client/mysql_plugin.c:1228: error: snprintf format string requires 6 parameters but only 5 are given. It is due to commit630d7229
introduced option `--lc-messages-dir` in the bootstrap command. However the parameter was not even given in the `snprintf` after changing the format string. Fix: Restructure the code logic and correct the function parameters for `snprintf`. 2. Null pointer is used in a `snprintf` which could cause a crash. Cppcheck error: extra/mariabackup/xbcloud.cc:2534: error: Null pointer dereference The code intended to print the swift_project name, if the opt_swift_project_id is NULL but opt_swift_project is not NULL. However the parameter of `snprintf` was mistakenly using `opt_swift_project_id`. Fix: Change to use the correct string from `opt_swift_project`. 3. Potential double release of a memory Cppcheck error: plugin/auth_pam/testing/pam_mariadb_mtr.c:69: error: Memory pointed to by 'resp' is freed twice. A pointer `resp` is reused and allocated new memory after it has been freed. However, `resp` was not set to NULL after freed. Potential double release of the same pointer if the call back function doesn't allocate new memory for `resp` pointer. Fix: Set the `resp` pointer to NULL after the first free() to make sure the same address is not freed twice. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc.
2722 lines
68 KiB
C++
2722 lines
68 KiB
C++
/******************************************************
|
|
Copyright (c) 2014 Percona LLC and/or its affiliates.
|
|
|
|
The xbstream utility: serialize/deserialize files in the XBSTREAM format.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
|
|
|
*******************************************************/
|
|
|
|
#include <my_global.h>
|
|
#include <my_default.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <curl/curl.h>
|
|
#include <ev.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <gcrypt.h>
|
|
#include <assert.h>
|
|
#include <my_sys.h>
|
|
#include <my_dir.h>
|
|
#include <my_getopt.h>
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <string>
|
|
#include <jsmn.h>
|
|
#include "xbstream.h"
|
|
|
|
using std::min;
|
|
using std::max;
|
|
using std::map;
|
|
using std::string;
|
|
|
|
#define XBCLOUD_VERSION "1.0"
|
|
|
|
#define SWIFT_MAX_URL_SIZE 8192
|
|
#define SWIFT_MAX_HDR_SIZE 8192
|
|
|
|
#define SWIFT_CHUNK_SIZE 11 * 1024 * 1024
|
|
|
|
#if ((LIBCURL_VERSION_MAJOR >= 7) && (LIBCURL_VERSION_MINOR >= 16))
|
|
#define OLD_CURL_MULTI 0
|
|
#else
|
|
#define OLD_CURL_MULTI 1
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct swift_auth_info_struct swift_auth_info;
|
|
typedef struct connection_info_struct connection_info;
|
|
typedef struct socket_info_struct socket_info;
|
|
typedef struct global_io_info_struct global_io_info;
|
|
typedef struct slo_chunk_struct slo_chunk;
|
|
typedef struct container_list_struct container_list;
|
|
typedef struct object_info_struct object_info;
|
|
|
|
struct swift_auth_info_struct {
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
char token[SWIFT_MAX_HDR_SIZE];
|
|
};
|
|
|
|
struct global_io_info_struct {
|
|
struct ev_loop *loop;
|
|
struct ev_io input_event;
|
|
struct ev_timer timer_event;
|
|
CURLM *multi;
|
|
int still_running;
|
|
int eof;
|
|
curl_socket_t input_fd;
|
|
connection_info **connections;
|
|
long chunk_no;
|
|
connection_info *current_connection;
|
|
const char *url;
|
|
const char *container;
|
|
const char *token;
|
|
const char *backup_name;
|
|
};
|
|
|
|
struct socket_info_struct {
|
|
curl_socket_t sockfd;
|
|
CURL *easy;
|
|
int action;
|
|
long timeout;
|
|
struct ev_io ev;
|
|
int evset;
|
|
global_io_info *global;
|
|
};
|
|
|
|
struct connection_info_struct {
|
|
CURL *easy;
|
|
global_io_info *global;
|
|
char *buffer;
|
|
size_t buffer_size;
|
|
size_t filled_size;
|
|
size_t upload_size;
|
|
bool chunk_uploaded;
|
|
bool chunk_acked;
|
|
char error[CURL_ERROR_SIZE];
|
|
struct curl_slist *slist;
|
|
char *name;
|
|
size_t name_len;
|
|
char hash[33];
|
|
size_t chunk_no;
|
|
bool magic_verified;
|
|
size_t chunk_path_len;
|
|
xb_chunk_type_t chunk_type;
|
|
size_t payload_size;
|
|
size_t chunk_size;
|
|
int retry_count;
|
|
bool upload_started;
|
|
ulong global_idx;
|
|
};
|
|
|
|
struct slo_chunk_struct {
|
|
char name[SWIFT_MAX_URL_SIZE];
|
|
char md5[33];
|
|
int idx;
|
|
size_t size;
|
|
};
|
|
|
|
struct object_info_struct {
|
|
char hash[33];
|
|
char name[SWIFT_MAX_URL_SIZE];
|
|
size_t bytes;
|
|
};
|
|
|
|
struct container_list_struct {
|
|
size_t content_length;
|
|
size_t content_bufsize;
|
|
char *content_json;
|
|
size_t object_count;
|
|
size_t idx;
|
|
object_info *objects;
|
|
bool final;
|
|
};
|
|
|
|
enum {SWIFT, S3};
|
|
const char *storage_names[] =
|
|
{ "SWIFT", "S3", NullS};
|
|
|
|
static my_bool opt_verbose = 0;
|
|
static ulong opt_storage = SWIFT;
|
|
static const char *opt_swift_user = NULL;
|
|
static const char *opt_swift_user_id = NULL;
|
|
static const char *opt_swift_password = NULL;
|
|
static const char *opt_swift_tenant = NULL;
|
|
static const char *opt_swift_tenant_id = NULL;
|
|
static const char *opt_swift_project = NULL;
|
|
static const char *opt_swift_project_id = NULL;
|
|
static const char *opt_swift_domain = NULL;
|
|
static const char *opt_swift_domain_id = NULL;
|
|
static const char *opt_swift_region = NULL;
|
|
static const char *opt_swift_container = NULL;
|
|
static const char *opt_swift_storage_url = NULL;
|
|
static const char *opt_swift_auth_url = NULL;
|
|
static const char *opt_swift_key = NULL;
|
|
static const char *opt_swift_auth_version = NULL;
|
|
static const char *opt_name = NULL;
|
|
static const char *opt_cacert = NULL;
|
|
static ulong opt_parallel = 1;
|
|
static my_bool opt_insecure = 0;
|
|
static enum {MODE_GET, MODE_PUT, MODE_DELETE} opt_mode;
|
|
|
|
static char **file_list = NULL;
|
|
static int file_list_size = 0;
|
|
|
|
TYPELIB storage_typelib =
|
|
{array_elements(storage_names)-1, "", storage_names, NULL};
|
|
|
|
enum {
|
|
OPT_STORAGE = 256,
|
|
OPT_SWIFT_CONTAINER,
|
|
OPT_SWIFT_AUTH_URL,
|
|
OPT_SWIFT_KEY,
|
|
OPT_SWIFT_USER,
|
|
OPT_SWIFT_USER_ID,
|
|
OPT_SWIFT_PASSWORD,
|
|
OPT_SWIFT_TENANT,
|
|
OPT_SWIFT_TENANT_ID,
|
|
OPT_SWIFT_PROJECT,
|
|
OPT_SWIFT_PROJECT_ID,
|
|
OPT_SWIFT_DOMAIN,
|
|
OPT_SWIFT_DOMAIN_ID,
|
|
OPT_SWIFT_REGION,
|
|
OPT_SWIFT_STORAGE_URL,
|
|
OPT_SWIFT_AUTH_VERSION,
|
|
OPT_PARALLEL,
|
|
OPT_CACERT,
|
|
OPT_INSECURE,
|
|
OPT_VERBOSE
|
|
};
|
|
|
|
|
|
static struct my_option my_long_options[] =
|
|
{
|
|
{"help", '?', "Display this help and exit.",
|
|
0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
|
|
|
|
{"storage", OPT_STORAGE, "Specify storage type S3/SWIFT.",
|
|
&opt_storage, &opt_storage, &storage_typelib,
|
|
GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-auth-version", OPT_SWIFT_AUTH_VERSION,
|
|
"Swift authentication verison to use.",
|
|
&opt_swift_auth_version, &opt_swift_auth_version, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-container", OPT_SWIFT_CONTAINER,
|
|
"Swift container to store backups into.",
|
|
&opt_swift_container, &opt_swift_container, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-user", OPT_SWIFT_USER,
|
|
"Swift user name.",
|
|
&opt_swift_user, &opt_swift_user, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-user-id", OPT_SWIFT_USER_ID,
|
|
"Swift user ID.",
|
|
&opt_swift_user_id, &opt_swift_user_id, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-auth-url", OPT_SWIFT_AUTH_URL,
|
|
"Base URL of SWIFT authentication service.",
|
|
&opt_swift_auth_url, &opt_swift_auth_url, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-storage-url", OPT_SWIFT_STORAGE_URL,
|
|
"URL of object-store endpoint. Usually received from authentication "
|
|
"service. Specify to override this value.",
|
|
&opt_swift_storage_url, &opt_swift_storage_url, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-key", OPT_SWIFT_KEY,
|
|
"Swift key.",
|
|
&opt_swift_key, &opt_swift_key, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-tenant", OPT_SWIFT_TENANT,
|
|
"The tenant name. Both the --swift-tenant and --swift-tenant-id "
|
|
"options are optional, but should not be specified together.",
|
|
&opt_swift_tenant, &opt_swift_tenant, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-tenant-id", OPT_SWIFT_TENANT_ID,
|
|
"The tenant ID. Both the --swift-tenant and --swift-tenant-id "
|
|
"options are optional, but should not be specified together.",
|
|
&opt_swift_tenant_id, &opt_swift_tenant_id, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-project", OPT_SWIFT_PROJECT,
|
|
"The project name.",
|
|
&opt_swift_project, &opt_swift_project, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-project-id", OPT_SWIFT_PROJECT_ID,
|
|
"The project ID.",
|
|
&opt_swift_project_id, &opt_swift_project_id, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-domain", OPT_SWIFT_DOMAIN,
|
|
"The domain name.",
|
|
&opt_swift_domain, &opt_swift_domain, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-domain-id", OPT_SWIFT_DOMAIN_ID,
|
|
"The domain ID.",
|
|
&opt_swift_domain_id, &opt_swift_domain_id, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-password", OPT_SWIFT_PASSWORD,
|
|
"The password of the user.",
|
|
&opt_swift_password, &opt_swift_password, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"swift-region", OPT_SWIFT_REGION,
|
|
"The region object-store endpoint.",
|
|
&opt_swift_region, &opt_swift_region, 0,
|
|
GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"parallel", OPT_PARALLEL,
|
|
"Number of parallel chunk uploads.",
|
|
&opt_parallel, &opt_parallel, 0, GET_ULONG, REQUIRED_ARG,
|
|
1, 0, 0, 0, 0, 0},
|
|
|
|
{"cacert", OPT_CACERT,
|
|
"CA certificate file.",
|
|
&opt_cacert, &opt_cacert, 0, GET_STR_ALLOC, REQUIRED_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"insecure", OPT_INSECURE,
|
|
"Do not verify server SSL certificate.",
|
|
&opt_insecure, &opt_insecure, 0, GET_BOOL, NO_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{"verbose", OPT_VERBOSE,
|
|
"Turn ON cURL tracing.",
|
|
&opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG,
|
|
0, 0, 0, 0, 0, 0},
|
|
|
|
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
/* The values of these arguments should be masked
|
|
on the command line */
|
|
static const char * const masked_args[] = {
|
|
"--swift-password",
|
|
"--swift-key",
|
|
"--swift-auth-url",
|
|
"--swift-storage-url",
|
|
"--swift-container",
|
|
"--swift-user",
|
|
"--swift-tenant",
|
|
"--swift-user-id",
|
|
"--swift-tenant-id",
|
|
0
|
|
};
|
|
|
|
static map<string, ulonglong> file_chunk_count;
|
|
|
|
static
|
|
void
|
|
print_version()
|
|
{
|
|
printf("%s Ver %s for %s (%s)\n", my_progname, XBCLOUD_VERSION,
|
|
SYSTEM_TYPE, MACHINE_TYPE);
|
|
}
|
|
|
|
static
|
|
void
|
|
usage()
|
|
{
|
|
print_version();
|
|
puts("Copyright (C) 2015 Percona LLC and/or its affiliates.");
|
|
puts("This software comes with ABSOLUTELY NO WARRANTY. "
|
|
"This is free software,\nand you are welcome to modify and "
|
|
"redistribute it under the GPL license.\n");
|
|
|
|
puts("Manage backups on Cloud services.\n");
|
|
|
|
puts("Usage: ");
|
|
printf(" %s -c put [OPTIONS...] <NAME> upload backup from STDIN into "
|
|
"the cloud service with given name.\n", my_progname);
|
|
printf(" %s -c get [OPTIONS...] <NAME> [FILES...] stream specified "
|
|
"backup or individual files from cloud service into STDOUT.\n",
|
|
my_progname);
|
|
|
|
puts("\nOptions:");
|
|
my_print_help(my_long_options);
|
|
}
|
|
|
|
static
|
|
my_bool
|
|
get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
|
|
char *argument __attribute__((unused)))
|
|
{
|
|
switch (optid) {
|
|
case '?':
|
|
usage();
|
|
exit(0);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
static const char *load_default_groups[]=
|
|
{ "xbcloud", 0 };
|
|
|
|
/*********************************************************************//**
|
|
mask sensitive values on the command line */
|
|
static
|
|
void
|
|
mask_args(int argc, char **argv)
|
|
{
|
|
int i;
|
|
for (i = 0; i < argc-1; i++) {
|
|
int j = 0;
|
|
if (argv[i]) while (masked_args[j]) {
|
|
char *p;
|
|
if ((p = strstr(argv[i], masked_args[j]))) {
|
|
p += strlen(masked_args[j]);
|
|
while (*p && *p != '=') {
|
|
p++;
|
|
}
|
|
if (*p == '=') {
|
|
p++;
|
|
while (*p) {
|
|
*p++ = 'x';
|
|
}
|
|
}
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
int parse_args(int argc, char **argv)
|
|
{
|
|
const char *command;
|
|
|
|
if (argc < 2) {
|
|
fprintf(stderr, "Command isn't specified. "
|
|
"Supported commands are put and get\n");
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
command = argv[1];
|
|
argc--; argv++;
|
|
|
|
if (strcasecmp(command, "put") == 0) {
|
|
opt_mode = MODE_PUT;
|
|
} else if (strcasecmp(command, "get") == 0) {
|
|
opt_mode = MODE_GET;
|
|
} else if (strcasecmp(command, "delete") == 0) {
|
|
opt_mode = MODE_DELETE;
|
|
} else {
|
|
fprintf(stderr, "Unknown command %s. "
|
|
"Supported commands are put and get\n", command);
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
load_defaults_or_exit("my", load_default_groups, &argc, &argv);
|
|
|
|
if (handle_options(&argc, &argv, my_long_options, get_one_option)) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* make sure name is specified */
|
|
if (argc < 1) {
|
|
fprintf(stderr, "Backup name is required argument\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
opt_name = argv[0];
|
|
argc--; argv++;
|
|
|
|
/* validate arguments */
|
|
if (opt_storage == SWIFT) {
|
|
if (opt_swift_user == NULL) {
|
|
fprintf(stderr, "Swift user is not specified\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (opt_swift_container == NULL) {
|
|
fprintf(stderr,
|
|
"Swift container is not specified\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (opt_swift_auth_url == NULL) {
|
|
fprintf(stderr, "Swift auth URL is not specified\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Swift is only supported storage API\n");
|
|
}
|
|
|
|
if (argc > 0) {
|
|
file_list = argv;
|
|
file_list_size = argc;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
static char *hex_md5(const unsigned char *hash, char *out)
|
|
{
|
|
enum { hash_len = 16 };
|
|
char *p;
|
|
int i;
|
|
|
|
for (i = 0, p = out; i < hash_len; i++, p+=2) {
|
|
sprintf(p, "%02x", hash[i]);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/* If header starts with prefix it's value will be copied into output buffer */
|
|
static
|
|
int get_http_header(const char *prefix, const char *buffer,
|
|
char *out, size_t out_size)
|
|
{
|
|
const char *beg, *end;
|
|
size_t len, prefix_len;
|
|
|
|
prefix_len = strlen(prefix);
|
|
|
|
if (strncasecmp(buffer, prefix, prefix_len) == 0) {
|
|
beg = buffer + prefix_len;
|
|
end = strchr(beg, '\r');
|
|
|
|
len = min<size_t>(end - beg, out_size - 1);
|
|
|
|
strncpy(out, beg, len);
|
|
|
|
out[len] = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
size_t swift_auth_header_read_cb(char *ptr, size_t size, size_t nmemb,
|
|
void *data)
|
|
{
|
|
swift_auth_info *info = (swift_auth_info*)(data);
|
|
|
|
get_http_header("X-Storage-Url: ", ptr,
|
|
info->url, array_elements(info->url));
|
|
get_http_header("X-Auth-Token: ", ptr,
|
|
info->token, array_elements(info->token));
|
|
|
|
return nmemb * size;
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
|
|
Uses creadentials privided as global variables.
|
|
@returns true if access is granted and token received. */
|
|
static
|
|
bool
|
|
swift_temp_auth(const char *auth_url, swift_auth_info *info)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
long http_code;
|
|
char *hdr_buf = NULL;
|
|
struct curl_slist *slist = NULL;
|
|
|
|
if (opt_swift_user == NULL) {
|
|
fprintf(stderr, "Swift user must be specified for TempAuth.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_key == NULL) {
|
|
fprintf(stderr, "Swift key must be specified for TempAuth.\n");
|
|
return(false);
|
|
}
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
|
|
hdr_buf = (char *)(calloc(14 + max(strlen(opt_swift_user),
|
|
strlen(opt_swift_key)), 1));
|
|
|
|
if (!hdr_buf) {
|
|
res = CURLE_FAILED_INIT;
|
|
goto cleanup;
|
|
}
|
|
|
|
sprintf(hdr_buf, "X-Auth-User: %s", opt_swift_user);
|
|
slist = curl_slist_append(slist, hdr_buf);
|
|
|
|
sprintf(hdr_buf, "X-Auth-Key: %s", opt_swift_key);
|
|
slist = curl_slist_append(slist, hdr_buf);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
|
|
swift_auth_header_read_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, info);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr, "error: authentication failed: "
|
|
"curl_easy_perform(): %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code != 200 &&
|
|
http_code != 204) {
|
|
fprintf(stderr, "error: authentication failed "
|
|
"with response code: %ld\n", http_code);
|
|
res = CURLE_LOGIN_DENIED;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
res = CURLE_FAILED_INIT;
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (hdr_buf) {
|
|
free(hdr_buf);
|
|
}
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
if (res == CURLE_OK) {
|
|
/* check that we received token and storage URL */
|
|
if (*info->url == 0) {
|
|
fprintf(stderr, "error: malformed response: "
|
|
"X-Storage-Url is missing\n");
|
|
return(false);
|
|
}
|
|
if (*info->token == 0) {
|
|
fprintf(stderr, "error: malformed response: "
|
|
"X-Auth-Token is missing\n");
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
static
|
|
size_t
|
|
write_null_cb(char *buffer, size_t size, size_t nmemb, void *stream)
|
|
{
|
|
return fwrite(buffer, size, nmemb, stderr);
|
|
}
|
|
|
|
|
|
static
|
|
size_t
|
|
read_null_cb(char *ptr, size_t size, size_t nmemb, void *data)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static
|
|
int
|
|
swift_create_container(swift_auth_info *info, const char *name)
|
|
{
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
char auth_token[SWIFT_MAX_HDR_SIZE];
|
|
CURLcode res;
|
|
long http_code;
|
|
CURL *curl;
|
|
struct curl_slist *slist = NULL;
|
|
|
|
snprintf(url, array_elements(url), "%s/%s", info->url, name);
|
|
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
|
|
info->token);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
slist = curl_slist_append(slist, auth_token);
|
|
slist = curl_slist_append(slist, "Content-Length: 0");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_null_cb);
|
|
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_null_cb);
|
|
curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0L);
|
|
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr,
|
|
"error: curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code != 201 && /* created */
|
|
http_code != 202 /* accepted (already exists) */) {
|
|
fprintf(stderr, "error: request failed "
|
|
"with response code: %ld\n", http_code);
|
|
res = CURLE_LOGIN_DENIED;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
res = CURLE_FAILED_INIT;
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Delete object with given url.
|
|
@returns true if object deleted successfully. */
|
|
static
|
|
bool
|
|
swift_delete_object(swift_auth_info *info, const char *url)
|
|
{
|
|
char auth_token[SWIFT_MAX_HDR_SIZE];
|
|
CURLcode res;
|
|
long http_code;
|
|
CURL *curl;
|
|
struct curl_slist *slist = NULL;
|
|
bool ret = false;
|
|
|
|
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
|
|
info->token);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
slist = curl_slist_append(slist, auth_token);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr,
|
|
"error: curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code != 200 && /* OK */
|
|
http_code != 204 /* no content */) {
|
|
fprintf(stderr, "error: request failed "
|
|
"with response code: %ld\n", http_code);
|
|
goto cleanup;
|
|
}
|
|
ret = true;
|
|
} else {
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int conn_upload_init(connection_info *conn);
|
|
static void conn_buffer_updated(connection_info *conn);
|
|
static connection_info *conn_new(global_io_info *global, ulong global_idx);
|
|
static void conn_cleanup(connection_info *conn);
|
|
static void conn_upload_retry(connection_info *conn);
|
|
|
|
/* Check for completed transfers, and remove their easy handles */
|
|
static void check_multi_info(global_io_info *g)
|
|
{
|
|
char *eff_url;
|
|
CURLMsg *msg;
|
|
int msgs_left;
|
|
connection_info *conn;
|
|
CURL *easy;
|
|
|
|
while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
|
|
if (msg->msg == CURLMSG_DONE) {
|
|
easy = msg->easy_handle;
|
|
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
|
|
curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL,
|
|
&eff_url);
|
|
curl_multi_remove_handle(g->multi, easy);
|
|
curl_easy_cleanup(easy);
|
|
conn->easy = NULL;
|
|
if (conn->chunk_acked) {
|
|
conn->chunk_uploaded = true;
|
|
fprintf(stderr, "%s is done\n", conn->hash);
|
|
} else {
|
|
fprintf(stderr, "error: chunk %zu '%s' %s "
|
|
"is not uploaded, but socket closed "
|
|
"(%zu bytes of %zu left to upload)\n",
|
|
conn->chunk_no,
|
|
conn->name,
|
|
conn->hash,
|
|
conn->chunk_size - conn->upload_size,
|
|
conn->chunk_size);
|
|
conn_upload_retry(conn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Die if we get a bad CURLMcode somewhere */
|
|
static void mcode_or_die(const char *where, CURLMcode code)
|
|
{
|
|
if (code != CURLM_OK)
|
|
{
|
|
const char *s;
|
|
switch (code)
|
|
{
|
|
case CURLM_BAD_HANDLE:
|
|
s = "CURLM_BAD_HANDLE";
|
|
break;
|
|
case CURLM_BAD_EASY_HANDLE:
|
|
s = "CURLM_BAD_EASY_HANDLE";
|
|
break;
|
|
case CURLM_OUT_OF_MEMORY:
|
|
s = "CURLM_OUT_OF_MEMORY";
|
|
break;
|
|
case CURLM_INTERNAL_ERROR:
|
|
s = "CURLM_INTERNAL_ERROR";
|
|
break;
|
|
case CURLM_UNKNOWN_OPTION:
|
|
s = "CURLM_UNKNOWN_OPTION";
|
|
break;
|
|
case CURLM_LAST:
|
|
s = "CURLM_LAST";
|
|
break;
|
|
default:
|
|
s = "CURLM_unknown";
|
|
break;
|
|
case CURLM_BAD_SOCKET:
|
|
s = "CURLM_BAD_SOCKET";
|
|
fprintf(stderr, "error: %s returns (%d) %s\n",
|
|
where, code, s);
|
|
/* ignore this error */
|
|
return;
|
|
}
|
|
fprintf(stderr, "error: %s returns (%d) %s\n",
|
|
where, code, s);
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* Called by libev when we get action on a multi socket */
|
|
static void event_cb(EV_P_ struct ev_io *w, int revents)
|
|
{
|
|
global_io_info *global = (global_io_info*)(w->data);
|
|
CURLMcode rc;
|
|
|
|
#if !(OLD_CURL_MULTI)
|
|
int action = (revents & EV_READ ? CURL_POLL_IN : 0) |
|
|
(revents & EV_WRITE ? CURL_POLL_OUT : 0);
|
|
|
|
do {
|
|
rc = curl_multi_socket_action(global->multi, w->fd, action,
|
|
&global->still_running);
|
|
} while (rc == CURLM_CALL_MULTI_PERFORM);
|
|
#else
|
|
do {
|
|
rc = curl_multi_socket(global->multi, w->fd,
|
|
&global->still_running);
|
|
} while (rc == CURLM_CALL_MULTI_PERFORM);
|
|
#endif
|
|
mcode_or_die("error: event_cb: curl_multi_socket_action", rc);
|
|
check_multi_info(global);
|
|
if (global->still_running <= 0) {
|
|
ev_timer_stop(global->loop, &global->timer_event);
|
|
}
|
|
}
|
|
|
|
static void remsock(curl_socket_t s, socket_info *fdp, global_io_info *global)
|
|
{
|
|
if (fdp) {
|
|
if (fdp->evset) {
|
|
ev_io_stop(global->loop, &fdp->ev);
|
|
}
|
|
free(fdp);
|
|
}
|
|
}
|
|
|
|
static void setsock(socket_info *fdp, curl_socket_t s, CURL *easy, int action,
|
|
global_io_info *global)
|
|
{
|
|
int kind = (action & CURL_POLL_IN ? (int)(EV_READ) : 0) |
|
|
(action & CURL_POLL_OUT ? (int)(EV_WRITE) : 0);
|
|
|
|
fdp->sockfd = s;
|
|
fdp->action = action;
|
|
fdp->easy = easy;
|
|
if (fdp->evset)
|
|
ev_io_stop(global->loop, &fdp->ev);
|
|
ev_io_init(&fdp->ev, event_cb, fdp->sockfd, kind);
|
|
fdp->ev.data = global;
|
|
fdp->evset = 1;
|
|
ev_io_start(global->loop, &fdp->ev);
|
|
}
|
|
|
|
static void addsock(curl_socket_t s, CURL *easy, int action,
|
|
global_io_info *global)
|
|
{
|
|
socket_info *fdp = (socket_info *)(calloc(sizeof(socket_info), 1));
|
|
|
|
fdp->global = global;
|
|
setsock(fdp, s, easy, action, global);
|
|
curl_multi_assign(global->multi, s, fdp);
|
|
}
|
|
|
|
static int sock_cb(CURL *easy, curl_socket_t s, int what, void *cbp,
|
|
void *sockp)
|
|
{
|
|
global_io_info *global = (global_io_info*)(cbp);
|
|
socket_info *fdp = (socket_info*)(sockp);
|
|
|
|
if (what == CURL_POLL_REMOVE) {
|
|
remsock(s, fdp, global);
|
|
} else {
|
|
if (!fdp) {
|
|
addsock(s, easy, what, global);
|
|
} else {
|
|
setsock(fdp, s, easy, what, global);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Called by libev when our timeout expires */
|
|
static void timer_cb(EV_P_ struct ev_timer *w, int revents)
|
|
{
|
|
global_io_info *io_global = (global_io_info*)(w->data);
|
|
CURLMcode rc;
|
|
|
|
#if !(OLD_CURL_MULTI)
|
|
do {
|
|
rc = curl_multi_socket_action(io_global->multi,
|
|
CURL_SOCKET_TIMEOUT, 0,
|
|
&io_global->still_running);
|
|
} while (rc == CURLM_CALL_MULTI_PERFORM);
|
|
#else
|
|
do {
|
|
rc = curl_multi_socket_all(io_global->multi,
|
|
&io_global->still_running);
|
|
} while (rc == CURLM_CALL_MULTI_PERFORM);
|
|
#endif
|
|
mcode_or_die("timer_cb: curl_multi_socket_action", rc);
|
|
check_multi_info(io_global);
|
|
}
|
|
|
|
static connection_info *get_current_connection(global_io_info *global)
|
|
{
|
|
connection_info *conn = global->current_connection;
|
|
ulong i;
|
|
|
|
if (conn && conn->filled_size < conn->chunk_size)
|
|
return conn;
|
|
|
|
for (i = 0; i < opt_parallel; i++) {
|
|
conn = global->connections[i];
|
|
if (conn->chunk_uploaded || conn->filled_size == 0) {
|
|
global->current_connection = conn;
|
|
conn_upload_init(conn);
|
|
return conn;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* This gets called whenever data is received from the input */
|
|
static void input_cb(EV_P_ struct ev_io *w, int revents)
|
|
{
|
|
global_io_info *io_global = (global_io_info *)(w->data);
|
|
connection_info *conn = get_current_connection(io_global);
|
|
|
|
if (conn == NULL)
|
|
return;
|
|
|
|
if (conn->filled_size < conn->chunk_size) {
|
|
if (revents & EV_READ) {
|
|
ssize_t nbytes = read(io_global->input_fd,
|
|
conn->buffer + conn->filled_size,
|
|
conn->chunk_size -
|
|
conn->filled_size);
|
|
if (nbytes > 0) {
|
|
conn->filled_size += nbytes;
|
|
conn_buffer_updated(conn);
|
|
} else if (nbytes < 0) {
|
|
if (errno != EAGAIN && errno != EINTR) {
|
|
char error[200];
|
|
my_strerror(error, sizeof(error),
|
|
errno);
|
|
fprintf(stderr, "error: failed to read "
|
|
"input stream (%s)\n", error);
|
|
/* failed to read input */
|
|
exit(1);
|
|
}
|
|
} else {
|
|
io_global->eof = 1;
|
|
ev_io_stop(io_global->loop, w);
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(conn->filled_size <= conn->chunk_size);
|
|
}
|
|
|
|
static int swift_upload_read_cb(char *ptr, size_t size, size_t nmemb,
|
|
void *data)
|
|
{
|
|
size_t realsize;
|
|
|
|
connection_info *conn = (connection_info*)(data);
|
|
|
|
if (conn->filled_size == conn->upload_size &&
|
|
conn->upload_size < conn->chunk_size && !conn->global->eof) {
|
|
ssize_t nbytes;
|
|
assert(conn->global->current_connection == conn);
|
|
do {
|
|
nbytes = read(conn->global->input_fd,
|
|
conn->buffer + conn->filled_size,
|
|
conn->chunk_size - conn->filled_size);
|
|
} while (nbytes == -1 && errno == EAGAIN);
|
|
if (nbytes > 0) {
|
|
conn->filled_size += nbytes;
|
|
conn_buffer_updated(conn);
|
|
} else {
|
|
conn->global->eof = 1;
|
|
}
|
|
}
|
|
|
|
realsize = min(size * nmemb, conn->filled_size - conn->upload_size);
|
|
|
|
memcpy(ptr, conn->buffer + conn->upload_size, realsize);
|
|
conn->upload_size += realsize;
|
|
|
|
assert(conn->filled_size <= conn->chunk_size);
|
|
assert(conn->upload_size <= conn->filled_size);
|
|
|
|
return realsize;
|
|
}
|
|
|
|
static
|
|
size_t upload_header_read_cb(char *ptr, size_t size, size_t nmemb,
|
|
void *data)
|
|
{
|
|
connection_info *conn = (connection_info *)(data);
|
|
char etag[33];
|
|
|
|
if (get_http_header("Etag: ", ptr, etag, array_elements(etag))) {
|
|
if (strcmp(conn->hash, etag) != 0) {
|
|
fprintf(stderr, "error: ETag mismatch\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fprintf(stderr, "acked chunk %s\n", etag);
|
|
conn->chunk_acked = true;
|
|
}
|
|
|
|
return nmemb * size;
|
|
}
|
|
|
|
static int conn_upload_init(connection_info *conn)
|
|
{
|
|
conn->filled_size = 0;
|
|
conn->upload_size = 0;
|
|
conn->chunk_uploaded = false;
|
|
conn->chunk_acked = false;
|
|
conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN;
|
|
conn->magic_verified = false;
|
|
conn->chunk_path_len = 0;
|
|
conn->chunk_type = XB_CHUNK_TYPE_UNKNOWN;
|
|
conn->payload_size = 0;
|
|
conn->upload_started = false;
|
|
conn->retry_count = 0;
|
|
if (conn->name != NULL) {
|
|
conn->name[0] = 0;
|
|
}
|
|
|
|
if (conn->easy != NULL) {
|
|
conn->easy = 0;
|
|
}
|
|
|
|
if (conn->slist != NULL) {
|
|
curl_slist_free_all(conn->slist);
|
|
conn->slist = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conn_upload_prepare(connection_info *conn)
|
|
{
|
|
gcry_md_hd_t md5;
|
|
|
|
gcry_md_open(&md5, GCRY_MD_MD5, 0);
|
|
gcry_md_write(md5, conn->buffer, conn->chunk_size);
|
|
hex_md5(gcry_md_read(md5, GCRY_MD_MD5), conn->hash);
|
|
gcry_md_close(md5);
|
|
}
|
|
|
|
static int conn_upload_start(connection_info *conn)
|
|
{
|
|
char token_header[SWIFT_MAX_HDR_SIZE];
|
|
char object_url[SWIFT_MAX_URL_SIZE];
|
|
char content_len[200], etag[200];
|
|
global_io_info *global;
|
|
CURLMcode rc;
|
|
|
|
global = conn->global;
|
|
|
|
fprintf(stderr, "uploading chunk %s/%s/%s.%020zu "
|
|
"(md5: %s, size: %zu)\n",
|
|
global->container, global->backup_name, conn->name,
|
|
conn->chunk_no, conn->hash, conn->chunk_size);
|
|
|
|
snprintf(object_url, array_elements(object_url), "%s/%s/%s/%s.%020zu",
|
|
global->url, global->container, global->backup_name,
|
|
conn->name, conn->chunk_no);
|
|
|
|
snprintf(content_len, sizeof(content_len), "Content-Length: %lu",
|
|
(ulong)(conn->chunk_size));
|
|
|
|
snprintf(etag, sizeof(etag), "ETag: %s", conn->hash);
|
|
|
|
snprintf(token_header, array_elements(token_header),
|
|
"X-Auth-Token: %s", global->token);
|
|
|
|
conn->slist = curl_slist_append(conn->slist, token_header);
|
|
conn->slist = curl_slist_append(conn->slist,
|
|
"Connection: keep-alive");
|
|
conn->slist = curl_slist_append(conn->slist,
|
|
"Content-Type: "
|
|
"application/octet-stream");
|
|
conn->slist = curl_slist_append(conn->slist, content_len);
|
|
conn->slist = curl_slist_append(conn->slist, etag);
|
|
|
|
conn->easy = curl_easy_init();
|
|
if (!conn->easy) {
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
return 1;
|
|
}
|
|
curl_easy_setopt(conn->easy, CURLOPT_URL, object_url);
|
|
curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION,
|
|
swift_upload_read_cb);
|
|
curl_easy_setopt(conn->easy, CURLOPT_READDATA, conn);
|
|
curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
|
|
curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
|
|
curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L);
|
|
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 5L);
|
|
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1024L);
|
|
curl_easy_setopt(conn->easy, CURLOPT_PUT, 1L);
|
|
curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->slist);
|
|
curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION,
|
|
upload_header_read_cb);
|
|
curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn);
|
|
curl_easy_setopt(conn->easy, CURLOPT_INFILESIZE,
|
|
(long) conn->chunk_size);
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(conn->easy, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
rc = curl_multi_add_handle(conn->global->multi, conn->easy);
|
|
mcode_or_die("conn_upload_init: curl_multi_add_handle", rc);
|
|
|
|
#if (OLD_CURL_MULTI)
|
|
do {
|
|
rc = curl_multi_socket_all(global->multi,
|
|
&global->still_running);
|
|
} while(rc == CURLM_CALL_MULTI_PERFORM);
|
|
#endif
|
|
|
|
conn->upload_started = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conn_cleanup(connection_info *conn)
|
|
{
|
|
if (conn) {
|
|
free(conn->name);
|
|
free(conn->buffer);
|
|
if (conn->slist) {
|
|
curl_slist_free_all(conn->slist);
|
|
conn->slist = NULL;
|
|
}
|
|
if (conn->easy) {
|
|
curl_easy_cleanup(conn->easy);
|
|
conn->easy = NULL;
|
|
}
|
|
}
|
|
free(conn);
|
|
}
|
|
|
|
static void conn_upload_retry(connection_info *conn)
|
|
{
|
|
/* already closed by cURL */
|
|
conn->easy = NULL;
|
|
|
|
if (conn->slist != NULL) {
|
|
curl_slist_free_all(conn->slist);
|
|
conn->slist = NULL;
|
|
}
|
|
|
|
if (conn->retry_count++ > 3) {
|
|
fprintf(stderr, "error: retry count limit reached\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
fprintf(stderr, "warning: retrying to upload chunk %zu of '%s'\n",
|
|
conn->chunk_no, conn->name);
|
|
|
|
conn->upload_size = 0;
|
|
|
|
conn_upload_start(conn);
|
|
}
|
|
|
|
static connection_info *conn_new(global_io_info *global, ulong global_idx)
|
|
{
|
|
connection_info *conn;
|
|
|
|
conn = (connection_info *)(calloc(1, sizeof(connection_info)));
|
|
if (conn == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
conn->global = global;
|
|
conn->global_idx = global_idx;
|
|
conn->buffer_size = SWIFT_CHUNK_SIZE;
|
|
if ((conn->buffer = (char *)(calloc(conn->buffer_size, 1))) ==
|
|
NULL) {
|
|
goto error;
|
|
}
|
|
|
|
return conn;
|
|
|
|
error:
|
|
if (conn != NULL) {
|
|
conn_cleanup(conn);
|
|
}
|
|
|
|
fprintf(stderr, "error: out of memory\n");
|
|
exit(EXIT_FAILURE);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Handle input buffer updates. Parse chunk header and set appropriate
|
|
buffer size. */
|
|
static
|
|
void
|
|
conn_buffer_updated(connection_info *conn)
|
|
{
|
|
bool ready_for_upload = false;
|
|
|
|
/* chunk header */
|
|
if (!conn->magic_verified &&
|
|
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN) {
|
|
if (strncmp(XB_STREAM_CHUNK_MAGIC, conn->buffer,
|
|
sizeof(XB_STREAM_CHUNK_MAGIC) - 1) != 0) {
|
|
|
|
fprintf(stderr, "Error: magic expected\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
conn->magic_verified = true;
|
|
conn->chunk_path_len = uint4korr(conn->buffer
|
|
+ PATH_LENGTH_OFFSET);
|
|
conn->chunk_type = (xb_chunk_type_t)
|
|
(conn->buffer[CHUNK_TYPE_OFFSET]);
|
|
conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN +
|
|
conn->chunk_path_len;
|
|
if (conn->chunk_type != XB_CHUNK_TYPE_EOF) {
|
|
conn->chunk_size += 16;
|
|
}
|
|
}
|
|
|
|
/* ordinary chunk */
|
|
if (conn->magic_verified &&
|
|
conn->payload_size == 0 &&
|
|
conn->chunk_type != XB_CHUNK_TYPE_EOF &&
|
|
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
|
|
+ conn->chunk_path_len + 16) {
|
|
|
|
conn->payload_size = uint8korr(conn->buffer +
|
|
CHUNK_HEADER_CONSTANT_LEN +
|
|
conn->chunk_path_len);
|
|
|
|
conn->chunk_size = conn->payload_size + 4 + 16 +
|
|
conn->chunk_path_len +
|
|
CHUNK_HEADER_CONSTANT_LEN;
|
|
|
|
if (conn->name == NULL) {
|
|
conn->name = (char*)(malloc(conn->chunk_path_len + 1));
|
|
} else if (conn->name_len < conn->chunk_path_len + 1) {
|
|
conn->name = (char*)(realloc(conn->name,
|
|
conn->chunk_path_len + 1));
|
|
}
|
|
conn->name_len = conn->chunk_path_len + 1;
|
|
|
|
memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
|
|
conn->chunk_path_len);
|
|
conn->name[conn->chunk_path_len] = 0;
|
|
|
|
if (conn->buffer_size < conn->chunk_size) {
|
|
conn->buffer =
|
|
(char *)(realloc(conn->buffer, conn->chunk_size));
|
|
conn->buffer_size = conn->chunk_size;
|
|
}
|
|
}
|
|
|
|
/* EOF chunk has no payload */
|
|
if (conn->magic_verified &&
|
|
conn->chunk_type == XB_CHUNK_TYPE_EOF &&
|
|
conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
|
|
+ conn->chunk_path_len) {
|
|
|
|
if (conn->name == NULL) {
|
|
conn->name = (char*)(malloc(conn->chunk_path_len + 1));
|
|
} else if (conn->name_len < conn->chunk_path_len + 1) {
|
|
conn->name = (char*)(realloc(conn->name,
|
|
conn->chunk_path_len + 1));
|
|
}
|
|
conn->name_len = conn->chunk_path_len + 1;
|
|
|
|
memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
|
|
conn->chunk_path_len);
|
|
conn->name[conn->chunk_path_len] = 0;
|
|
}
|
|
|
|
if (conn->filled_size > 0 && conn->filled_size == conn->chunk_size) {
|
|
ready_for_upload = true;
|
|
}
|
|
|
|
/* start upload once recieved the size of the chunk */
|
|
if (!conn->upload_started && ready_for_upload) {
|
|
conn->chunk_no = file_chunk_count[conn->name]++;
|
|
conn_upload_prepare(conn);
|
|
conn_upload_start(conn);
|
|
}
|
|
}
|
|
|
|
static int init_input(global_io_info *io_global)
|
|
{
|
|
ev_io_init(&io_global->input_event, input_cb, STDIN_FILENO, EV_READ);
|
|
io_global->input_event.data = io_global;
|
|
ev_io_start(io_global->loop, &io_global->input_event);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Update the event timer after curl_multi library calls */
|
|
static int multi_timer_cb(CURLM *multi, long timeout_ms, global_io_info *global)
|
|
{
|
|
ev_timer_stop(global->loop, &global->timer_event);
|
|
if (timeout_ms > 0) {
|
|
double t = timeout_ms / 1000.0;
|
|
ev_timer_init(&global->timer_event, timer_cb, t, 0.);
|
|
ev_timer_start(global->loop, &global->timer_event);
|
|
} else {
|
|
timer_cb(global->loop, &global->timer_event, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int swift_upload_parts(swift_auth_info *auth, const char *container,
|
|
const char *name)
|
|
{
|
|
global_io_info io_global;
|
|
ulong i;
|
|
#if (OLD_CURL_MULTI)
|
|
long timeout;
|
|
#endif
|
|
CURLMcode rc;
|
|
int n_dirty_buffers;
|
|
|
|
memset(&io_global, 0, sizeof(io_global));
|
|
|
|
io_global.loop = ev_default_loop(0);
|
|
init_input(&io_global);
|
|
io_global.multi = curl_multi_init();
|
|
ev_timer_init(&io_global.timer_event, timer_cb, 0., 0.);
|
|
io_global.timer_event.data = &io_global;
|
|
io_global.connections = (connection_info **)
|
|
(calloc(opt_parallel, sizeof(connection_info)));
|
|
io_global.url = auth->url;
|
|
io_global.container = container;
|
|
io_global.backup_name = name;
|
|
io_global.token = auth->token;
|
|
for (i = 0; i < opt_parallel; i++) {
|
|
io_global.connections[i] = conn_new(&io_global, i);
|
|
}
|
|
|
|
/* setup the generic multi interface options we want */
|
|
curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
|
|
curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETDATA, &io_global);
|
|
#if !(OLD_CURL_MULTI)
|
|
curl_multi_setopt(io_global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
|
|
curl_multi_setopt(io_global.multi, CURLMOPT_TIMERDATA, &io_global);
|
|
do {
|
|
rc = curl_multi_socket_action(io_global.multi,
|
|
CURL_SOCKET_TIMEOUT, 0,
|
|
&io_global.still_running);
|
|
} while (rc == CURLM_CALL_MULTI_PERFORM);
|
|
#else
|
|
curl_multi_timeout(io_global.multi, &timeout);
|
|
if (timeout >= 0) {
|
|
multi_timer_cb(io_global.multi, timeout, &io_global);
|
|
}
|
|
do {
|
|
rc = curl_multi_socket_all(io_global.multi, &io_global.still_running);
|
|
} while(rc == CURLM_CALL_MULTI_PERFORM);
|
|
#endif
|
|
|
|
ev_loop(io_global.loop, 0);
|
|
check_multi_info(&io_global);
|
|
curl_multi_cleanup(io_global.multi);
|
|
|
|
n_dirty_buffers = 0;
|
|
for (i = 0; i < opt_parallel; i++) {
|
|
connection_info *conn = io_global.connections[i];
|
|
if (conn && conn->upload_size != conn->filled_size) {
|
|
fprintf(stderr, "error: upload failed: %lu bytes left "
|
|
"in the buffer %s (uploaded = %d)\n",
|
|
(ulong)(conn->filled_size - conn->upload_size),
|
|
conn->name, conn->chunk_uploaded);
|
|
++n_dirty_buffers;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < opt_parallel; i++) {
|
|
if (io_global.connections[i] != NULL) {
|
|
conn_cleanup(io_global.connections[i]);
|
|
}
|
|
}
|
|
free(io_global.connections);
|
|
|
|
if (n_dirty_buffers > 0) {
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct download_buffer_info {
|
|
off_t offset;
|
|
size_t size;
|
|
size_t result_len;
|
|
char *buf;
|
|
curl_read_callback custom_header_callback;
|
|
void *custom_header_callback_data;
|
|
};
|
|
|
|
/*********************************************************************//**
|
|
Callback to parse header of GET request on swift contaier. */
|
|
static
|
|
size_t fetch_buffer_header_cb(char *ptr, size_t size, size_t nmemb,
|
|
void *data)
|
|
{
|
|
download_buffer_info *buffer_info = (download_buffer_info*)(data);
|
|
size_t buf_size;
|
|
char content_length_str[100];
|
|
char *endptr;
|
|
|
|
if (get_http_header("Content-Length: ", ptr,
|
|
content_length_str, sizeof(content_length_str))) {
|
|
|
|
buf_size = strtoull(content_length_str, &endptr, 10);
|
|
|
|
if (buffer_info->buf == NULL) {
|
|
buffer_info->buf = (char*)(malloc(buf_size));
|
|
buffer_info->size = buf_size;
|
|
}
|
|
|
|
if (buf_size > buffer_info->size) {
|
|
buffer_info->buf = (char*)
|
|
(realloc(buffer_info->buf, buf_size));
|
|
buffer_info->size = buf_size;
|
|
}
|
|
|
|
buffer_info->result_len = buf_size;
|
|
}
|
|
|
|
if (buffer_info->custom_header_callback) {
|
|
buffer_info->custom_header_callback(ptr, size, nmemb,
|
|
buffer_info->custom_header_callback_data);
|
|
}
|
|
|
|
return nmemb * size;
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Write contents into string buffer */
|
|
static
|
|
size_t
|
|
fetch_buffer_cb(char *buffer, size_t size, size_t nmemb, void *out_buffer)
|
|
{
|
|
download_buffer_info *buffer_info = (download_buffer_info*)(out_buffer);
|
|
|
|
assert(buffer_info->size >= buffer_info->offset + size * nmemb);
|
|
|
|
memcpy(buffer_info->buf + buffer_info->offset, buffer, size * nmemb);
|
|
buffer_info->offset += size * nmemb;
|
|
|
|
return size * nmemb;
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Downloads contents of URL into buffer. Caller is responsible for
|
|
deallocating the buffer.
|
|
@return pointer to a buffer or NULL */
|
|
static
|
|
char *
|
|
swift_fetch_into_buffer(swift_auth_info *auth, const char *url,
|
|
char **buf, size_t *buf_size, size_t *result_len,
|
|
curl_read_callback header_callback,
|
|
void *header_callback_data)
|
|
{
|
|
char auth_token[SWIFT_MAX_HDR_SIZE];
|
|
download_buffer_info buffer_info;
|
|
struct curl_slist *slist = NULL;
|
|
long http_code;
|
|
CURL *curl;
|
|
CURLcode res;
|
|
|
|
memset(&buffer_info, 0, sizeof(buffer_info));
|
|
buffer_info.buf = *buf;
|
|
buffer_info.size = *buf_size;
|
|
buffer_info.custom_header_callback = header_callback;
|
|
buffer_info.custom_header_callback_data = header_callback_data;
|
|
|
|
snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
|
|
auth->token);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
slist = curl_slist_append(slist, auth_token);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer_info);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
|
|
fetch_buffer_header_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
|
|
&buffer_info);
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr,
|
|
"error: curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code < 200 || http_code >= 300) {
|
|
fprintf(stderr, "error: request failed "
|
|
"with response code: %ld\n", http_code);
|
|
res = CURLE_LOGIN_DENIED;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
res = CURLE_FAILED_INIT;
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
if (res == CURLE_OK) {
|
|
*buf = buffer_info.buf;
|
|
*buf_size = buffer_info.size;
|
|
*result_len = buffer_info.result_len;
|
|
return(buffer_info.buf);
|
|
}
|
|
|
|
free(buffer_info.buf);
|
|
*buf = NULL;
|
|
*buf_size = 0;
|
|
*result_len = 0;
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
static
|
|
container_list *
|
|
container_list_new()
|
|
{
|
|
container_list *list =
|
|
(container_list *)(calloc(1, sizeof(container_list)));
|
|
|
|
list->object_count = 1000;
|
|
list->objects = (object_info*)
|
|
(calloc(list->object_count, sizeof(object_info)));
|
|
|
|
if (list->objects == NULL) {
|
|
fprintf(stderr, "error: out of memory\n");
|
|
free(list);
|
|
return(NULL);
|
|
}
|
|
|
|
return(list);
|
|
}
|
|
|
|
static
|
|
void
|
|
container_list_free(container_list *list)
|
|
{
|
|
free(list->content_json);
|
|
free(list->objects);
|
|
free(list);
|
|
}
|
|
|
|
static
|
|
void
|
|
container_list_add_object(container_list *list, const char *name,
|
|
const char *hash, size_t bytes)
|
|
{
|
|
const size_t object_count_step = 1000;
|
|
|
|
if (list->idx >= list->object_count) {
|
|
list->objects = (object_info*)
|
|
realloc(list->objects,
|
|
(list->object_count + object_count_step) *
|
|
sizeof(object_info));
|
|
memset(list->objects + list->object_count, 0,
|
|
object_count_step * sizeof(object_info));
|
|
list->object_count += object_count_step;
|
|
}
|
|
assert(list->idx <= list->object_count);
|
|
safe_strcpy(list->objects[list->idx].name,
|
|
sizeof(list->objects[list->idx].name), name);
|
|
safe_strcpy(list->objects[list->idx].hash,
|
|
sizeof(list->objects[list->idx].hash), hash);
|
|
|
|
list->objects[list->idx].bytes = bytes;
|
|
++list->idx;
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Tokenize json string. Return array of tokens. Caller is responsoble for
|
|
deallocating the array. */
|
|
jsmntok_t *
|
|
json_tokenise(char *json, size_t len, int initial_tokens)
|
|
{
|
|
jsmn_parser parser;
|
|
jsmn_init(&parser);
|
|
|
|
unsigned int n = initial_tokens;
|
|
jsmntok_t *tokens = (jsmntok_t *)(malloc(sizeof(jsmntok_t) * n));
|
|
|
|
int ret = jsmn_parse(&parser, json, len, tokens, n);
|
|
|
|
while (ret == JSMN_ERROR_NOMEM)
|
|
{
|
|
n = n * 2 + 1;
|
|
tokens = (jsmntok_t*)(realloc(tokens, sizeof(jsmntok_t) * n));
|
|
ret = jsmn_parse(&parser, json, len, tokens, n);
|
|
}
|
|
|
|
if (ret == JSMN_ERROR_INVAL) {
|
|
fprintf(stderr, "error: invalid JSON string\n");
|
|
|
|
}
|
|
if (ret == JSMN_ERROR_PART) {
|
|
fprintf(stderr, "error: truncated JSON string\n");
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Return true if token representation equal to given string. */
|
|
static
|
|
bool
|
|
json_token_eq(const char *buf, jsmntok_t *t, const char *s)
|
|
{
|
|
size_t len = strlen(s);
|
|
|
|
assert(t->end > t->start);
|
|
|
|
return((size_t)(t->end - t->start) == len &&
|
|
(strncmp(buf + t->start, s, len) == 0));
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Copy given token as string. */
|
|
static
|
|
bool
|
|
json_token_str(const char *buf, jsmntok_t *t, char *out, int out_size)
|
|
{
|
|
size_t len = min(t->end - t->start, out_size - 1);
|
|
|
|
memcpy(out, buf + t->start, len);
|
|
out[len] = 0;
|
|
|
|
return(true);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Parse SWIFT container list response and fill output array with values
|
|
sorted by object name. */
|
|
static
|
|
bool
|
|
swift_parse_container_list(container_list *list)
|
|
{
|
|
enum {MAX_DEPTH=20};
|
|
enum label_t {NONE, OBJECT};
|
|
|
|
char name[SWIFT_MAX_URL_SIZE];
|
|
char hash[33];
|
|
char bytes[30];
|
|
char *response = list->content_json;
|
|
|
|
struct stack_t {
|
|
jsmntok_t *t;
|
|
int n_items;
|
|
label_t label;
|
|
};
|
|
|
|
stack_t stack[MAX_DEPTH];
|
|
jsmntok_t *tokens;
|
|
int level;
|
|
size_t count = 0;
|
|
|
|
tokens = json_tokenise(list->content_json, list->content_length, 200);
|
|
|
|
stack[0].t = &tokens[0];
|
|
stack[0].label = NONE;
|
|
stack[0].n_items = 1;
|
|
level = 0;
|
|
|
|
for (size_t i = 0, j = 1; j > 0; i++, j--) {
|
|
jsmntok_t *t = &tokens[i];
|
|
|
|
assert(t->start != -1 && t->end != -1);
|
|
assert(level >= 0);
|
|
|
|
--stack[level].n_items;
|
|
|
|
switch (t->type) {
|
|
case JSMN_ARRAY:
|
|
case JSMN_OBJECT:
|
|
if (level < MAX_DEPTH - 1) {
|
|
level++;
|
|
}
|
|
stack[level].t = t;
|
|
stack[level].label = NONE;
|
|
if (t->type == JSMN_ARRAY) {
|
|
stack[level].n_items = t->size;
|
|
j += t->size;
|
|
} else {
|
|
stack[level].n_items = t->size * 2;
|
|
j += t->size * 2;
|
|
}
|
|
break;
|
|
case JSMN_PRIMITIVE:
|
|
case JSMN_STRING:
|
|
if (stack[level].t->type == JSMN_OBJECT &&
|
|
stack[level].n_items % 2 == 1) {
|
|
/* key */
|
|
if (json_token_eq(response, t, "name")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
name, sizeof(name));
|
|
}
|
|
if (json_token_eq(response, t, "hash")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
hash, sizeof(hash));
|
|
}
|
|
if (json_token_eq(response, t, "bytes")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
bytes, sizeof(bytes));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
while (stack[level].n_items == 0 && level > 0) {
|
|
if (stack[level].t->type == JSMN_OBJECT
|
|
&& level == 2) {
|
|
char *endptr;
|
|
container_list_add_object(list, name, hash,
|
|
strtoull(bytes, &endptr, 10));
|
|
++count;
|
|
}
|
|
--level;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
list->final = true;
|
|
}
|
|
|
|
free(tokens);
|
|
|
|
return(true);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
List swift container with given name. Return list of objects sorted by
|
|
object name. */
|
|
static
|
|
container_list *
|
|
swift_list(swift_auth_info *auth, const char *container, const char *path)
|
|
{
|
|
container_list *list;
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
|
|
list = container_list_new();
|
|
|
|
while (!list->final) {
|
|
|
|
/* download the list in json format */
|
|
snprintf(url, array_elements(url),
|
|
"%s/%s?format=json&limit=1000%s%s%s%s",
|
|
auth->url, container, path ? "&prefix=" : "",
|
|
path ? path : "", list->idx > 0 ? "&marker=" : "",
|
|
list->idx > 0 ?
|
|
list->objects[list->idx - 1].name : "");
|
|
|
|
list->content_json = swift_fetch_into_buffer(auth, url,
|
|
&list->content_json, &list->content_bufsize,
|
|
&list->content_length, NULL, NULL);
|
|
|
|
if (list->content_json == NULL) {
|
|
container_list_free(list);
|
|
return(NULL);
|
|
}
|
|
|
|
/* parse downloaded list */
|
|
if (!swift_parse_container_list(list)) {
|
|
fprintf(stderr, "error: unable to parse "
|
|
"container list\n");
|
|
container_list_free(list);
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
return(list);
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Return true if chunk is a part of backup with given name. */
|
|
static
|
|
bool
|
|
chunk_belongs_to(const char *chunk_name, const char *backup_name)
|
|
{
|
|
size_t backup_name_len = strlen(backup_name);
|
|
|
|
return((strlen(chunk_name) > backup_name_len)
|
|
&& (chunk_name[backup_name_len] == '/')
|
|
&& strncmp(chunk_name, backup_name, backup_name_len) == 0);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Return true if chunk is in given list. */
|
|
static
|
|
bool
|
|
chunk_in_list(const char *chunk_name, char **list, int list_size)
|
|
{
|
|
size_t chunk_name_len;
|
|
|
|
if (list_size == 0) {
|
|
return(true);
|
|
}
|
|
|
|
chunk_name_len = strlen(chunk_name);
|
|
if (chunk_name_len < 20) {
|
|
return(false);
|
|
}
|
|
|
|
for (int i = 0; i < list_size; i++) {
|
|
size_t item_len = strlen(list[i]);
|
|
|
|
if ((strncmp(chunk_name - item_len + chunk_name_len - 21,
|
|
list[i], item_len) == 0)
|
|
&& (chunk_name[chunk_name_len - 21] == '.')
|
|
&& (chunk_name[chunk_name_len - item_len - 22] == '/')) {
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
static
|
|
int swift_download(swift_auth_info *auth, const char *container,
|
|
const char *name)
|
|
{
|
|
container_list *list;
|
|
char *buf = NULL;
|
|
size_t buf_size = 0;
|
|
size_t result_len = 0;
|
|
|
|
if ((list = swift_list(auth, container, name)) == NULL) {
|
|
return(CURLE_FAILED_INIT);
|
|
}
|
|
|
|
for (size_t i = 0; i < list->idx; i++) {
|
|
const char *chunk_name = list->objects[i].name;
|
|
|
|
if (chunk_belongs_to(chunk_name, name)
|
|
&& chunk_in_list(chunk_name, file_list, file_list_size)) {
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
|
|
snprintf(url, sizeof(url), "%s/%s/%s",
|
|
auth->url, container, chunk_name);
|
|
|
|
if ((buf = swift_fetch_into_buffer(
|
|
auth, url, &buf, &buf_size, &result_len,
|
|
NULL, NULL)) == NULL) {
|
|
fprintf(stderr, "error: failed to download "
|
|
"chunk %s\n", chunk_name);
|
|
container_list_free(list);
|
|
return(CURLE_FAILED_INIT);
|
|
}
|
|
|
|
fwrite(buf, 1, result_len, stdout);
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
|
|
container_list_free(list);
|
|
|
|
return(CURLE_OK);
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Delete backup with given name from given container.
|
|
@return true if backup deleted successfully */
|
|
static
|
|
bool swift_delete(swift_auth_info *auth, const char *container,
|
|
const char *name)
|
|
{
|
|
container_list *list;
|
|
|
|
if ((list = swift_list(auth, container, name)) == NULL) {
|
|
return(CURLE_FAILED_INIT);
|
|
}
|
|
|
|
for (size_t i = 0; i < list->object_count; i++) {
|
|
const char *chunk_name = list->objects[i].name;
|
|
|
|
if (chunk_belongs_to(chunk_name, name)) {
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
|
|
snprintf(url, sizeof(url), "%s/%s/%s",
|
|
auth->url, container, chunk_name);
|
|
|
|
fprintf(stderr, "delete %s\n", chunk_name);
|
|
if (!swift_delete_object(auth, url)) {
|
|
fprintf(stderr, "error: failed to delete "
|
|
"chunk %s\n", chunk_name);
|
|
container_list_free(list);
|
|
return(CURLE_FAILED_INIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
container_list_free(list);
|
|
|
|
return(CURLE_OK);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Check if backup with given name exists.
|
|
@return true if backup exists */
|
|
static
|
|
bool swift_backup_exists(swift_auth_info *auth, const char *container,
|
|
const char *backup_name)
|
|
{
|
|
container_list *list;
|
|
|
|
if ((list = swift_list(auth, container, backup_name)) == NULL) {
|
|
fprintf(stderr, "error: unable to list container %s\n",
|
|
container);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for (size_t i = 0; i < list->object_count; i++) {
|
|
if (chunk_belongs_to(list->objects[i].name, backup_name)) {
|
|
container_list_free(list);
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
container_list_free(list);
|
|
|
|
return(false);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Fills auth_info with response from keystone response.
|
|
@return true is response parsed successfully */
|
|
static
|
|
bool
|
|
swift_parse_keystone_response_v2(char *response, size_t response_length,
|
|
swift_auth_info *auth_info)
|
|
{
|
|
enum {MAX_DEPTH=20};
|
|
enum label_t {NONE, ACCESS, CATALOG, ENDPOINTS, TOKEN};
|
|
|
|
char filtered_url[SWIFT_MAX_URL_SIZE];
|
|
char public_url[SWIFT_MAX_URL_SIZE];
|
|
char region[SWIFT_MAX_URL_SIZE];
|
|
char id[SWIFT_MAX_URL_SIZE];
|
|
char token_id[SWIFT_MAX_URL_SIZE];
|
|
char type[SWIFT_MAX_URL_SIZE];
|
|
|
|
struct stack_t {
|
|
jsmntok_t *t;
|
|
int n_items;
|
|
label_t label;
|
|
};
|
|
|
|
stack_t stack[MAX_DEPTH];
|
|
jsmntok_t *tokens;
|
|
int level;
|
|
|
|
tokens = json_tokenise(response, response_length, 200);
|
|
|
|
stack[0].t = &tokens[0];
|
|
stack[0].label = NONE;
|
|
stack[0].n_items = 1;
|
|
level = 0;
|
|
|
|
for (size_t i = 0, j = 1; j > 0; i++, j--) {
|
|
jsmntok_t *t = &tokens[i];
|
|
|
|
assert(t->start != -1 && t->end != -1);
|
|
assert(level >= 0);
|
|
|
|
--stack[level].n_items;
|
|
|
|
switch (t->type) {
|
|
case JSMN_ARRAY:
|
|
case JSMN_OBJECT:
|
|
if (level < MAX_DEPTH - 1) {
|
|
level++;
|
|
}
|
|
stack[level].t = t;
|
|
stack[level].label = NONE;
|
|
if (t->type == JSMN_ARRAY) {
|
|
stack[level].n_items = t->size;
|
|
j += t->size;
|
|
} else {
|
|
stack[level].n_items = t->size * 2;
|
|
j += t->size * 2;
|
|
}
|
|
break;
|
|
case JSMN_PRIMITIVE:
|
|
case JSMN_STRING:
|
|
if (stack[level].t->type == JSMN_OBJECT &&
|
|
stack[level].n_items % 2 == 1) {
|
|
/* key */
|
|
if (json_token_eq(response, t, "access")) {
|
|
stack[level].label = ACCESS;
|
|
}
|
|
if (json_token_eq(response, t,
|
|
"serviceCatalog")) {
|
|
stack[level].label = CATALOG;
|
|
}
|
|
if (json_token_eq(response, t, "endpoints")) {
|
|
stack[level].label = ENDPOINTS;
|
|
}
|
|
if (json_token_eq(response, t, "token")) {
|
|
stack[level].label = TOKEN;
|
|
}
|
|
if (json_token_eq(response, t, "id")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
id, sizeof(id));
|
|
}
|
|
if (json_token_eq(response, t, "id")
|
|
&& stack[level - 1].label == TOKEN) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
token_id, sizeof(token_id));
|
|
}
|
|
if (json_token_eq(response, t, "region")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
region, sizeof(region));
|
|
}
|
|
if (json_token_eq(response, t, "publicURL")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
public_url, sizeof(public_url));
|
|
}
|
|
if (json_token_eq(response, t, "type")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
type, sizeof(type));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
while (stack[level].n_items == 0 && level > 0) {
|
|
if (stack[level].t->type == JSMN_OBJECT
|
|
&& level == 6
|
|
&& stack[level - 1].t->type == JSMN_ARRAY
|
|
&& stack[level - 2].label == ENDPOINTS) {
|
|
if (opt_swift_region == NULL
|
|
|| strcmp(opt_swift_region, region) == 0) {
|
|
strncpy(filtered_url, public_url,
|
|
sizeof(filtered_url));
|
|
}
|
|
}
|
|
if (stack[level].t->type == JSMN_OBJECT &&
|
|
level == 4 &&
|
|
stack[level - 1].t->type == JSMN_ARRAY &&
|
|
stack[level - 2].label == CATALOG) {
|
|
if (strcmp(type, "object-store") == 0) {
|
|
strncpy(auth_info->url, filtered_url,
|
|
sizeof(auth_info->url));
|
|
}
|
|
}
|
|
--level;
|
|
}
|
|
}
|
|
|
|
free(tokens);
|
|
|
|
strncpy(auth_info->token, token_id, sizeof(auth_info->token));
|
|
|
|
assert(level == 0);
|
|
|
|
if (*auth_info->token == 0) {
|
|
fprintf(stderr, "error: can not receive token from response\n");
|
|
return(false);
|
|
}
|
|
|
|
if (*auth_info->url == 0) {
|
|
fprintf(stderr, "error: can not get URL from response\n");
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
|
|
Uses creadentials privided as global variables.
|
|
@returns true if access is granted and token received. */
|
|
static
|
|
bool
|
|
swift_keystone_auth_v2(const char *auth_url, swift_auth_info *info)
|
|
{
|
|
char tenant_arg[SWIFT_MAX_URL_SIZE];
|
|
char payload[SWIFT_MAX_URL_SIZE];
|
|
struct curl_slist *slist = NULL;
|
|
download_buffer_info buf_info;
|
|
long http_code;
|
|
CURLcode res;
|
|
CURL *curl;
|
|
bool auth_res = false;
|
|
|
|
memset(&buf_info, 0, sizeof(buf_info));
|
|
|
|
if (opt_swift_user == NULL) {
|
|
fprintf(stderr, "error: both --swift-user is required "
|
|
"for keystone authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_password == NULL) {
|
|
fprintf(stderr, "error: both --swift-password is required "
|
|
"for keystone authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_tenant != NULL && opt_swift_tenant_id != NULL) {
|
|
fprintf(stderr, "error: both --swift-tenant and "
|
|
"--swift-tenant-id specified for keystone "
|
|
"authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_tenant != NULL) {
|
|
snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
|
|
"tenantName", opt_swift_tenant);
|
|
} else if (opt_swift_tenant_id != NULL) {
|
|
snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
|
|
"tenantId", opt_swift_tenant_id);
|
|
} else {
|
|
*tenant_arg = 0;
|
|
}
|
|
|
|
snprintf(payload, sizeof(payload), "{\"auth\": "
|
|
"{\"passwordCredentials\": {\"username\":\"%s\","
|
|
"\"password\":\"%s\"}%s}}",
|
|
opt_swift_user, opt_swift_password, tenant_arg);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
|
|
slist = curl_slist_append(slist,
|
|
"Content-Type: application/json");
|
|
slist = curl_slist_append(slist,
|
|
"Accept: application/json");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
|
|
fetch_buffer_header_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
|
|
&buf_info);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr,
|
|
"error: curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code < 200 || http_code >= 300) {
|
|
fprintf(stderr, "error: request failed "
|
|
"with response code: %ld\n", http_code);
|
|
res = CURLE_LOGIN_DENIED;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
res = CURLE_FAILED_INIT;
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!swift_parse_keystone_response_v2(buf_info.buf,
|
|
buf_info.size, info)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
auth_res = true;
|
|
|
|
cleanup:
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
free(buf_info.buf);
|
|
|
|
return(auth_res);
|
|
}
|
|
|
|
|
|
/*********************************************************************//**
|
|
Fills auth_info with response from keystone response.
|
|
@return true is response parsed successfully */
|
|
static
|
|
bool
|
|
swift_parse_keystone_response_v3(char *response, size_t response_length,
|
|
swift_auth_info *auth_info)
|
|
{
|
|
enum {MAX_DEPTH=20};
|
|
enum label_t {NONE, TOKEN, CATALOG, ENDPOINTS};
|
|
|
|
char url[SWIFT_MAX_URL_SIZE];
|
|
char filtered_url[SWIFT_MAX_URL_SIZE];
|
|
char region[SWIFT_MAX_URL_SIZE];
|
|
char interface[SWIFT_MAX_URL_SIZE];
|
|
char type[SWIFT_MAX_URL_SIZE];
|
|
|
|
struct stack_t {
|
|
jsmntok_t *t;
|
|
int n_items;
|
|
label_t label;
|
|
};
|
|
|
|
stack_t stack[MAX_DEPTH];
|
|
jsmntok_t *tokens;
|
|
int level;
|
|
|
|
tokens = json_tokenise(response, response_length, 200);
|
|
|
|
stack[0].t = &tokens[0];
|
|
stack[0].label = NONE;
|
|
stack[0].n_items = 1;
|
|
level = 0;
|
|
|
|
for (size_t i = 0, j = 1; j > 0; i++, j--) {
|
|
jsmntok_t *t = &tokens[i];
|
|
|
|
assert(t->start != -1 && t->end != -1);
|
|
assert(level >= 0);
|
|
|
|
--stack[level].n_items;
|
|
|
|
switch (t->type) {
|
|
case JSMN_ARRAY:
|
|
case JSMN_OBJECT:
|
|
if (level < MAX_DEPTH - 1) {
|
|
level++;
|
|
}
|
|
stack[level].t = t;
|
|
stack[level].label = NONE;
|
|
if (t->type == JSMN_ARRAY) {
|
|
stack[level].n_items = t->size;
|
|
j += t->size;
|
|
} else {
|
|
stack[level].n_items = t->size * 2;
|
|
j += t->size * 2;
|
|
}
|
|
break;
|
|
case JSMN_PRIMITIVE:
|
|
case JSMN_STRING:
|
|
if (stack[level].t->type == JSMN_OBJECT &&
|
|
stack[level].n_items % 2 == 1) {
|
|
/* key */
|
|
if (json_token_eq(response, t, "token")) {
|
|
stack[level].label = TOKEN;
|
|
fprintf(stderr, "token\n");
|
|
}
|
|
if (json_token_eq(response, t,
|
|
"catalog")) {
|
|
stack[level].label = CATALOG;
|
|
fprintf(stderr, "catalog\n");
|
|
}
|
|
if (json_token_eq(response, t, "endpoints")) {
|
|
stack[level].label = ENDPOINTS;
|
|
}
|
|
if (json_token_eq(response, t, "region")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
region, sizeof(region));
|
|
}
|
|
if (json_token_eq(response, t, "url")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
url, sizeof(url));
|
|
}
|
|
if (json_token_eq(response, t, "interface")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
interface, sizeof(interface));
|
|
}
|
|
if (json_token_eq(response, t, "type")) {
|
|
json_token_str(response, &tokens[i + 1],
|
|
type, sizeof(type));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
while (stack[level].n_items == 0 && level > 0) {
|
|
if (stack[level].t->type == JSMN_OBJECT
|
|
&& level == 6
|
|
&& stack[level - 1].t->type == JSMN_ARRAY
|
|
&& stack[level - 2].label == ENDPOINTS) {
|
|
if ((opt_swift_region == NULL
|
|
|| strcmp(opt_swift_region, region) == 0)
|
|
&& strcmp(interface, "public") == 0) {
|
|
strncpy(filtered_url, url,
|
|
sizeof(filtered_url));
|
|
}
|
|
}
|
|
if (stack[level].t->type == JSMN_OBJECT &&
|
|
level == 4 &&
|
|
stack[level - 1].t->type == JSMN_ARRAY &&
|
|
stack[level - 2].label == CATALOG) {
|
|
if (strcmp(type, "object-store") == 0) {
|
|
strncpy(auth_info->url, filtered_url,
|
|
sizeof(auth_info->url));
|
|
}
|
|
}
|
|
--level;
|
|
}
|
|
}
|
|
|
|
free(tokens);
|
|
|
|
assert(level == 0);
|
|
|
|
if (*auth_info->url == 0) {
|
|
fprintf(stderr, "error: can not get URL from response\n");
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Captures X-Subject-Token header. */
|
|
static
|
|
size_t keystone_v3_header_cb(char *ptr, size_t size, size_t nmemb, void *data)
|
|
{
|
|
swift_auth_info *info = (swift_auth_info*)(data);
|
|
|
|
get_http_header("X-Subject-Token: ", ptr,
|
|
info->token, array_elements(info->token));
|
|
|
|
return nmemb * size;
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Authenticate against Swift TempAuth. Fills swift_auth_info struct.
|
|
Uses creadentials privided as global variables.
|
|
@returns true if access is granted and token received. */
|
|
static
|
|
bool
|
|
swift_keystone_auth_v3(const char *auth_url, swift_auth_info *info)
|
|
{
|
|
char scope[SWIFT_MAX_URL_SIZE];
|
|
char domain[SWIFT_MAX_URL_SIZE];
|
|
char payload[SWIFT_MAX_URL_SIZE];
|
|
struct curl_slist *slist = NULL;
|
|
download_buffer_info buf_info;
|
|
long http_code;
|
|
CURLcode res;
|
|
CURL *curl;
|
|
bool auth_res = false;
|
|
|
|
memset(&buf_info, 0, sizeof(buf_info));
|
|
buf_info.custom_header_callback = keystone_v3_header_cb;
|
|
buf_info.custom_header_callback_data = info;
|
|
|
|
if (opt_swift_user == NULL) {
|
|
fprintf(stderr, "error: both --swift-user is required "
|
|
"for keystone authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_password == NULL) {
|
|
fprintf(stderr, "error: both --swift-password is required "
|
|
"for keystone authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_project_id != NULL && opt_swift_project != NULL) {
|
|
fprintf(stderr, "error: both --swift-project and "
|
|
"--swift-project-id specified for keystone "
|
|
"authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_domain_id != NULL && opt_swift_domain != NULL) {
|
|
fprintf(stderr, "error: both --swift-domain and "
|
|
"--swift-domain-id specified for keystone "
|
|
"authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_project_id != NULL && opt_swift_domain != NULL) {
|
|
fprintf(stderr, "error: both --swift-project-id and "
|
|
"--swift-domain specified for keystone "
|
|
"authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
if (opt_swift_project_id != NULL && opt_swift_domain_id != NULL) {
|
|
fprintf(stderr, "error: both --swift-project-id and "
|
|
"--swift-domain-id specified for keystone "
|
|
"authentication.\n");
|
|
return(false);
|
|
}
|
|
|
|
scope[0] = 0; domain[0] = 0;
|
|
|
|
if (opt_swift_domain != NULL) {
|
|
snprintf(domain, sizeof(domain),
|
|
",{\"domain\":{\"name\":\"%s\"}}",
|
|
opt_swift_domain);
|
|
} else if (opt_swift_domain_id != NULL) {
|
|
snprintf(domain, sizeof(domain),
|
|
",{\"domain\":{\"id\":\"%s\"}}",
|
|
opt_swift_domain_id);
|
|
}
|
|
|
|
if (opt_swift_project_id != NULL) {
|
|
snprintf(scope, sizeof(scope),
|
|
",\"scope\":{\"project\":{\"id\":\"%s\"}}",
|
|
opt_swift_project_id);
|
|
} else if (opt_swift_project != NULL) {
|
|
snprintf(scope, sizeof(scope),
|
|
",\"scope\":{\"project\":{\"name\":\"%s\"%s}}",
|
|
opt_swift_project, domain);
|
|
}
|
|
|
|
snprintf(payload, sizeof(payload), "{\"auth\":{\"identity\":"
|
|
"{\"methods\":[\"password\"],\"password\":{\"user\":"
|
|
"{\"name\":\"%s\",\"password\":\"%s\"%s}}}%s}}",
|
|
opt_swift_user, opt_swift_password,
|
|
*scope ? "" : ",\"domain\":{\"id\":\"default\"}",
|
|
scope);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
if (curl != NULL) {
|
|
|
|
slist = curl_slist_append(slist,
|
|
"Content-Type: application/json");
|
|
slist = curl_slist_append(slist,
|
|
"Accept: application/json");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
|
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
|
|
fetch_buffer_header_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
|
|
&buf_info);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
|
|
|
if (opt_cacert != NULL)
|
|
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
|
|
if (opt_insecure)
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr,
|
|
"error: curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (http_code < 200 || http_code >= 300) {
|
|
fprintf(stderr, "error: request failed "
|
|
"with response code: %ld\n", http_code);
|
|
res = CURLE_LOGIN_DENIED;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
res = CURLE_FAILED_INIT;
|
|
fprintf(stderr, "error: curl_easy_init() failed\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!swift_parse_keystone_response_v3(buf_info.buf,
|
|
buf_info.size, info)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
auth_res = true;
|
|
|
|
cleanup:
|
|
if (slist) {
|
|
curl_slist_free_all(slist);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
free(buf_info.buf);
|
|
|
|
return(auth_res);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
swift_auth_info info;
|
|
char auth_url[SWIFT_MAX_URL_SIZE];
|
|
|
|
MY_INIT(argv[0]);
|
|
|
|
/* handle_options in parse_args is destructive so
|
|
* we make a copy of our argument pointers so we can
|
|
* mask the sensitive values afterwards */
|
|
char **mask_argv = (char **)malloc(sizeof(char *) * (argc - 1));
|
|
memcpy(mask_argv, argv + 1, sizeof(char *) * (argc - 1));
|
|
|
|
if (parse_args(argc, argv)) {
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
mask_args(argc, mask_argv); /* mask args on cmdline */
|
|
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
if (opt_swift_auth_version == NULL || *opt_swift_auth_version == '1') {
|
|
/* TempAuth */
|
|
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sauth/v%s/",
|
|
opt_swift_auth_url, opt_swift_auth_version ?
|
|
opt_swift_auth_version : "1.0");
|
|
|
|
if (!swift_temp_auth(auth_url, &info)) {
|
|
fprintf(stderr, "error: failed to authenticate\n");
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
} else if (*opt_swift_auth_version == '2') {
|
|
/* Keystone v2 */
|
|
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/tokens",
|
|
opt_swift_auth_url, opt_swift_auth_version);
|
|
|
|
if (!swift_keystone_auth_v2(auth_url, &info)) {
|
|
fprintf(stderr, "error: failed to authenticate\n");
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
} else if (*opt_swift_auth_version == '3') {
|
|
/* Keystone v3 */
|
|
snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/auth/tokens",
|
|
opt_swift_auth_url, opt_swift_auth_version);
|
|
|
|
if (!swift_keystone_auth_v3(auth_url, &info)) {
|
|
fprintf(stderr, "error: failed to authenticate\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
}
|
|
|
|
if (opt_swift_storage_url != NULL) {
|
|
snprintf(info.url, sizeof(info.url), "%s",
|
|
opt_swift_storage_url);
|
|
}
|
|
|
|
fprintf(stderr, "Object store URL: %s\n", info.url);
|
|
|
|
if (opt_mode == MODE_PUT) {
|
|
|
|
if (swift_create_container(&info, opt_swift_container) != 0) {
|
|
fprintf(stderr, "error: failed to create "
|
|
"container %s\n",
|
|
opt_swift_container);
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
if (swift_backup_exists(&info, opt_swift_container, opt_name)) {
|
|
fprintf(stderr, "error: backup named '%s' "
|
|
"already exists!\n",
|
|
opt_name);
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
if (swift_upload_parts(&info, opt_swift_container,
|
|
opt_name) != 0) {
|
|
fprintf(stderr, "error: upload failed\n");
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
} else if (opt_mode == MODE_GET) {
|
|
|
|
if (swift_download(&info, opt_swift_container, opt_name)
|
|
!= CURLE_OK) {
|
|
fprintf(stderr, "error: download failed\n");
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
} else if (opt_mode == MODE_DELETE) {
|
|
|
|
if (swift_delete(&info, opt_swift_container, opt_name)
|
|
!= CURLE_OK) {
|
|
fprintf(stderr, "error: delete failed\n");
|
|
return(EXIT_FAILURE);
|
|
}
|
|
|
|
} else {
|
|
fprintf(stderr, "Unknown command supplied.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
curl_global_cleanup();
|
|
|
|
return(EXIT_SUCCESS);
|
|
}
|