/******************************************************
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 = CREATE_TYPELIB_FOR(storage_names);
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 ) {
snprintf(p, 3 , "%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;
size_t hdr_buf_size = 0 ;
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_size = 14 + max(strlen(opt_swift_user),
strlen(opt_swift_key));
hdr_buf = (char *)(calloc(hdr_buf_size, 1 ));
if (!hdr_buf) {
res = CURLE_FAILED_INIT;
goto cleanup;
}
snprintf(hdr_buf, hdr_buf_size, "X-Auth-User: %s" , opt_swift_user);
slist = curl_slist_append(slist, hdr_buf);
snprintf(hdr_buf, hdr_buf_size, "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, 1 L);
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, 1 L);
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, 0 L);
curl_easy_setopt(curl, CURLOPT_PUT, 1 L);
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, 1 L);
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, 1 L);
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 5 L);
curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1024 L);
curl_easy_setopt(conn->easy, CURLOPT_PUT, 1 L);
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 container. */
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, 1 L);
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 responsible 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, 1 L);
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1 L);
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, 1 L);
curl_easy_setopt(curl, CURLOPT_URL, auth_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1 L);
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);
}
Messung V0.5 in Prozent C=98 H=89 G=93
¤ Dauer der Verarbeitung: 0.39 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland