1 /******************************************************
2 Copyright (c) 2014-2020 Percona LLC and/or its affiliates.
3
4 xbcloud utility. Manage backups on cloud storage services.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; version 2 of the License.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18
19 *******************************************************/
20
21 #include <my_alloc.h>
22 #include <my_default.h>
23 #include <my_dir.h>
24 #include <my_getopt.h>
25 #include <my_sys.h>
26 #include <mysql/service_mysql_alloc.h>
27 #include <signal.h>
28 #include <typelib.h>
29 #include <cstdlib>
30 #include <fstream>
31 #include <iostream>
32 #include <set>
33 #include <string>
34 #include <unordered_map>
35 #include "template_utils.h"
36
37 #include <curl/curl.h>
38
39 #include "common.h"
40 #include "xbstream.h"
41 #include "xtrabackup_version.h"
42
43 #include "crc_glue.h"
44
45 #include "xbcloud/s3.h"
46 #include "xbcloud/swift.h"
47 #include "xbcloud/util.h"
48
49 using namespace xbcloud;
50
51 #define XBCLOUD_VERSION XTRABACKUP_VERSION
52
53 /*****************************************************************************/
54
55 const char *config_file = "my"; /* Default config file */
56
57 enum { SWIFT, S3, GOOGLE };
58 const char *storage_names[] = {"SWIFT", "S3", "GOOGLE", NullS};
59
60 const char *s3_bucket_lookup_names[] = {"AUTO", "DNS", "PATH", NullS};
61
62 static bool opt_verbose = 0;
63
64 static ulong opt_storage = SWIFT;
65
66 static char *opt_swift_user = nullptr;
67 static char *opt_swift_user_id = nullptr;
68 static char *opt_swift_password = nullptr;
69 static char *opt_swift_tenant = nullptr;
70 static char *opt_swift_tenant_id = nullptr;
71 static char *opt_swift_project = nullptr;
72 static char *opt_swift_project_id = nullptr;
73 static char *opt_swift_domain = nullptr;
74 static char *opt_swift_domain_id = nullptr;
75 static char *opt_swift_project_domain = nullptr;
76 static char *opt_swift_project_domain_id = nullptr;
77 static char *opt_swift_region = nullptr;
78 static char *opt_swift_container = nullptr;
79 static char *opt_swift_storage_url = nullptr;
80 static char *opt_swift_auth_url = nullptr;
81 static char *opt_swift_key = nullptr;
82 static char *opt_swift_auth_version = nullptr;
83
84 static char *opt_s3_region = nullptr;
85 static char *opt_s3_endpoint = nullptr;
86 static char *opt_s3_access_key = nullptr;
87 static char *opt_s3_secret_key = nullptr;
88 static char *opt_s3_session_token = nullptr;
89 static char *opt_s3_bucket = nullptr;
90 static ulong opt_s3_bucket_lookup;
91 static ulong opt_s3_api_version = 0;
92
93 static char *opt_google_region = nullptr;
94 static char *opt_google_endpoint = nullptr;
95 static char *opt_google_access_key = nullptr;
96 static char *opt_google_secret_key = nullptr;
97 static char *opt_google_session_token = nullptr;
98 static char *opt_google_bucket = nullptr;
99
100 static std::string backup_name;
101 static char *opt_cacert = nullptr;
102 static ulong opt_parallel = 1;
103 static bool opt_insecure = false;
104 static bool opt_md5 = false;
105 static enum { MODE_GET, MODE_PUT, MODE_DELETE } opt_mode;
106
107 static std::map<std::string, std::string> extra_http_headers;
108
109 const char *s3_api_version_names[] = {"AUTO", "2", "4", NullS};
110
111 static std::set<std::string> file_list;
112
113 TYPELIB storage_typelib = {array_elements(storage_names) - 1, "", storage_names,
114 nullptr};
115
116 TYPELIB s3_bucket_lookup_typelib = {array_elements(s3_bucket_lookup_names) - 1,
117 "", s3_bucket_lookup_names, nullptr};
118
119 TYPELIB s3_api_version_typelib = {array_elements(s3_api_version_names) - 1, "",
120 s3_api_version_names, nullptr};
121
122 enum {
123 OPT_STORAGE = 256,
124
125 OPT_SWIFT_CONTAINER,
126 OPT_SWIFT_AUTH_URL,
127 OPT_SWIFT_KEY,
128 OPT_SWIFT_USER,
129 OPT_SWIFT_USER_ID,
130 OPT_SWIFT_PASSWORD,
131 OPT_SWIFT_TENANT,
132 OPT_SWIFT_TENANT_ID,
133 OPT_SWIFT_PROJECT,
134 OPT_SWIFT_PROJECT_ID,
135 OPT_SWIFT_DOMAIN,
136 OPT_SWIFT_DOMAIN_ID,
137 OPT_SWIFT_PROJECT_DOMAIN,
138 OPT_SWIFT_PROJECT_DOMAIN_ID,
139 OPT_SWIFT_REGION,
140 OPT_SWIFT_STORAGE_URL,
141 OPT_SWIFT_AUTH_VERSION,
142
143 OPT_S3_REGION,
144 OPT_S3_ENDPOINT,
145 OPT_S3_ACCESS_KEY,
146 OPT_S3_SECRET_KEY,
147 OPT_S3_SESSION_TOKEN,
148 OPT_S3_BUCKET,
149 OPT_S3_BUCKET_LOOKUP,
150 OPT_S3_API_VERSION,
151
152 OPT_GOOGLE_REGION,
153 OPT_GOOGLE_ENDPOINT,
154 OPT_GOOGLE_ACCESS_KEY,
155 OPT_GOOGLE_SECRET_KEY,
156 OPT_GOOGLE_SESSION_TOKEN,
157 OPT_GOOGLE_BUCKET,
158
159 OPT_PARALLEL,
160 OPT_CACERT,
161 OPT_HEADER,
162 OPT_INSECURE,
163 OPT_MD5,
164 OPT_VERBOSE
165 };
166
167 static struct my_option my_long_options[] = {
168 {"defaults-file", 'c',
169 "Name of config file to read; if no extension is given, default "
170 "extension (e.g., .ini or .cnf) will be added",
171 &config_file, &config_file, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
172
173 {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0,
174 0, 0, 0, 0, 0},
175
176 {"storage", OPT_STORAGE, "Specify storage type S3/SWIFT.", &opt_storage,
177 &opt_storage, &storage_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
178
179 {"swift-auth-version", OPT_SWIFT_AUTH_VERSION,
180 "Swift authentication verison to use.", &opt_swift_auth_version,
181 &opt_swift_auth_version, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
182
183 {"swift-container", OPT_SWIFT_CONTAINER,
184 "Swift container to store backups into.", &opt_swift_container,
185 &opt_swift_container, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
186
187 {"swift-user", OPT_SWIFT_USER, "Swift user name.", &opt_swift_user,
188 &opt_swift_user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
189
190 {"swift-user-id", OPT_SWIFT_USER_ID, "Swift user ID.", &opt_swift_user_id,
191 &opt_swift_user_id, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
192
193 {"swift-auth-url", OPT_SWIFT_AUTH_URL,
194 "Base URL of SWIFT authentication service.", &opt_swift_auth_url,
195 &opt_swift_auth_url, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
196
197 {"swift-storage-url", OPT_SWIFT_STORAGE_URL,
198 "URL of object-store endpoint. Usually received from authentication "
199 "service. Specify to override this value.",
200 &opt_swift_storage_url, &opt_swift_storage_url, 0, GET_STR_ALLOC,
201 REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
202
203 {"swift-key", OPT_SWIFT_KEY, "Swift key.", &opt_swift_key, &opt_swift_key,
204 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
205
206 {"swift-tenant", OPT_SWIFT_TENANT,
207 "The tenant name. Both the --swift-tenant and --swift-tenant-id "
208 "options are optional, but should not be specified together.",
209 &opt_swift_tenant, &opt_swift_tenant, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0,
210 0, 0, 0, 0},
211
212 {"swift-tenant-id", OPT_SWIFT_TENANT_ID,
213 "The tenant ID. Both the --swift-tenant and --swift-tenant-id "
214 "options are optional, but should not be specified together.",
215 &opt_swift_tenant_id, &opt_swift_tenant_id, 0, GET_STR_ALLOC, REQUIRED_ARG,
216 0, 0, 0, 0, 0, 0},
217
218 {"swift-project", OPT_SWIFT_PROJECT, "The project name.",
219 &opt_swift_project, &opt_swift_project, 0, GET_STR_ALLOC, REQUIRED_ARG, 0,
220 0, 0, 0, 0, 0},
221
222 {"swift-project-id", OPT_SWIFT_PROJECT_ID, "The project ID.",
223 &opt_swift_project_id, &opt_swift_project_id, 0, GET_STR_ALLOC,
224 REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
225
226 {"swift-domain", OPT_SWIFT_DOMAIN, "The user domain name.",
227 &opt_swift_domain, &opt_swift_domain, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0,
228 0, 0, 0, 0},
229
230 {"swift-domain-id", OPT_SWIFT_DOMAIN_ID, "The user domain ID.",
231 &opt_swift_domain_id, &opt_swift_domain_id, 0, GET_STR_ALLOC, REQUIRED_ARG,
232 0, 0, 0, 0, 0, 0},
233
234 {"swift-project-domain", OPT_SWIFT_PROJECT_DOMAIN,
235 "The project domain name.", &opt_swift_project_domain,
236 &opt_swift_project_domain, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0,
237 0},
238
239 {"swift-project-domain-id", OPT_SWIFT_PROJECT_DOMAIN_ID,
240 "The project domain ID.", &opt_swift_project_domain_id,
241 &opt_swift_project_domain_id, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0,
242 0, 0},
243
244 {"swift-password", OPT_SWIFT_PASSWORD, "The password of the user.",
245 &opt_swift_password, &opt_swift_password, 0, GET_STR_ALLOC, REQUIRED_ARG,
246 0, 0, 0, 0, 0, 0},
247
248 {"swift-region", OPT_SWIFT_REGION, "The region object-store endpoint.",
249 &opt_swift_region, &opt_swift_region, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0,
250 0, 0, 0, 0},
251
252 {"parallel", OPT_PARALLEL, "Number of parallel chunk uploads.",
253 &opt_parallel, &opt_parallel, 0, GET_ULONG, REQUIRED_ARG, 1, 1, ULONG_MAX,
254 0, 0, 0},
255
256 {"s3-region", OPT_S3_REGION, "S3 region.", &opt_s3_region, &opt_s3_region,
257 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
258
259 {"s3-endpoint", OPT_S3_ENDPOINT, "S3 endpoint.", &opt_s3_endpoint,
260 &opt_s3_endpoint, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
261
262 {"s3-access-key", OPT_S3_ACCESS_KEY, "S3 access key.", &opt_s3_access_key,
263 &opt_s3_access_key, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
264
265 {"s3-secret-key", OPT_S3_SECRET_KEY, "S3 secret key.", &opt_s3_secret_key,
266 &opt_s3_secret_key, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
267
268 {"s3-session-token", OPT_S3_SESSION_TOKEN, "S3 session token.",
269 &opt_s3_session_token, &opt_s3_session_token, 0, GET_STR_ALLOC,
270 REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
271
272 {"s3-bucket", OPT_S3_BUCKET, "S3 bucket.", &opt_s3_bucket, &opt_s3_bucket,
273 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
274
275 {"s3-bucket-lookup", OPT_S3_BUCKET_LOOKUP, "Bucket lookup method.",
276 &opt_s3_bucket_lookup, &opt_s3_bucket_lookup, &s3_bucket_lookup_typelib,
277 GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
278
279 {"s3-api-version", OPT_S3_API_VERSION, "S3 API version.",
280 &opt_s3_api_version, &opt_s3_api_version, &s3_api_version_typelib,
281 GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
282
283 {"google-region", OPT_GOOGLE_REGION, "Google cloud storage region.",
284 &opt_google_region, &opt_google_region, 0, GET_STR_ALLOC, REQUIRED_ARG, 0,
285 0, 0, 0, 0, 0},
286
287 {"google-endpoint", OPT_GOOGLE_ENDPOINT, "Google cloud storage endpoint.",
288 &opt_google_endpoint, &opt_google_endpoint, 0, GET_STR_ALLOC, REQUIRED_ARG,
289 0, 0, 0, 0, 0, 0},
290
291 {"google-access-key", OPT_GOOGLE_ACCESS_KEY,
292 "Goolge cloud storage access key.", &opt_google_access_key,
293 &opt_google_access_key, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
294
295 {"google-secret-key", OPT_GOOGLE_SECRET_KEY,
296 "Goolge cloud storage secret key.", &opt_google_secret_key,
297 &opt_google_secret_key, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
298
299 {"google-session-token", OPT_GOOGLE_SESSION_TOKEN,
300 "Goolge cloud storage session token.", &opt_google_session_token,
301 &opt_google_session_token, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0,
302 0},
303
304 {"google-bucket", OPT_GOOGLE_BUCKET, "Goolge cloud storage bucket.",
305 &opt_google_bucket, &opt_google_bucket, 0, GET_STR_ALLOC, REQUIRED_ARG, 0,
306 0, 0, 0, 0, 0},
307
308 {"cacert", OPT_CACERT, "CA certificate file.", &opt_cacert, &opt_cacert, 0,
309 GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
310
311 {"header", OPT_HEADER, "Extra header.", NULL, NULL, 0, GET_STR,
312 REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
313
314 {"insecure", OPT_INSECURE, "Do not verify server SSL certificate.",
315 &opt_insecure, &opt_insecure, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
316
317 {"md5", OPT_MD5, "Upload MD5 file into the backup dir.", &opt_md5, &opt_md5,
318 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
319
320 {"verbose", OPT_VERBOSE, "Turn ON cURL tracing.", &opt_verbose,
321 &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
322
323 {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}};
324
print_version()325 static void print_version() {
326 printf("%s Ver %s for %s (%s)\n", my_progname, XBCLOUD_VERSION, SYSTEM_TYPE,
327 MACHINE_TYPE);
328 }
329
usage()330 static void usage() {
331 print_version();
332 puts("Copyright (C) 2015-2019 Percona LLC and/or its affiliates.");
333 puts(
334 "This software comes with ABSOLUTELY NO WARRANTY. "
335 "This is free software,\nand you are welcome to modify and "
336 "redistribute it under the GPL license.\n");
337
338 puts("Manage backups on the cloud services.\n");
339
340 puts("Usage: ");
341 printf(
342 " %s put [OPTIONS...] <NAME> upload backup from STDIN into "
343 "the cloud service with given name.\n",
344 my_progname);
345 printf(
346 " %s get [OPTIONS...] <NAME> [FILES...] stream specified "
347 "backup or individual files from the cloud service into STDOUT.\n",
348 my_progname);
349 printf(
350 " %s delete [OPTIONS...] <NAME> [FILES...] delete specified "
351 "backup or individual files from the cloud service.\n",
352 my_progname);
353
354 puts("\nOptions:");
355 my_print_help(my_long_options);
356 }
357
get_one_option(int optid,const struct my_option * opt,char * argument)358 static bool get_one_option(int optid,
359 const struct my_option *opt __attribute__((unused)),
360 char *argument) {
361 switch (optid) {
362 case '?':
363 usage();
364 exit(0);
365 case OPT_SWIFT_PASSWORD:
366 case OPT_SWIFT_KEY:
367 case OPT_SWIFT_TENANT:
368 case OPT_SWIFT_TENANT_ID:
369 case OPT_S3_ACCESS_KEY:
370 case OPT_S3_SECRET_KEY:
371 case OPT_S3_SESSION_TOKEN:
372 case OPT_GOOGLE_ACCESS_KEY:
373 case OPT_GOOGLE_SECRET_KEY:
374 case OPT_GOOGLE_SESSION_TOKEN:
375 if (argument != nullptr) {
376 while (*argument) *argument++ = 0; // Destroy argument
377 }
378 break;
379 case OPT_HEADER:
380 if (argument != nullptr) {
381 extra_http_headers.insert(parse_http_header(argument));
382 }
383 break;
384 }
385
386 return (false);
387 }
388
389 static const char *load_default_groups[] = {"xbcloud", 0};
390
get_env_value(char * & var,const char * env)391 static void get_env_value(char *&var, const char *env) {
392 char *val;
393 if (var == nullptr && (val = getenv(env)) != nullptr) {
394 var = my_strdup(PSI_NOT_INSTRUMENTED, val, MYF(MY_FAE));
395 }
396 }
397
get_env_args()398 static void get_env_args() {
399 get_env_value(opt_swift_auth_url, "OS_AUTH_URL");
400 get_env_value(opt_swift_tenant, "OS_TENANT_NAME");
401 get_env_value(opt_swift_tenant_id, "OS_TENANT_ID");
402 get_env_value(opt_swift_user, "OS_USERNAME");
403 get_env_value(opt_swift_password, "OS_PASSWORD");
404 get_env_value(opt_swift_domain, "OS_USER_DOMAIN");
405 get_env_value(opt_swift_domain_id, "OS_USER_DOMAIN_ID");
406 get_env_value(opt_swift_project_domain, "OS_PROJECT_DOMAIN");
407 get_env_value(opt_swift_project_domain_id, "OS_PROJECT_DOMAIN_ID");
408 get_env_value(opt_swift_region, "OS_REGION_NAME");
409 get_env_value(opt_swift_storage_url, "OS_STORAGE_URL");
410 get_env_value(opt_cacert, "OS_CACERT");
411
412 /* Below block should always be above AWS_* and should not be moved because
413 the order of prefrence are like S3_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID and
414 ACCESS_KEY */
415 get_env_value(opt_s3_access_key, "S3_ACCESS_KEY_ID");
416 get_env_value(opt_s3_secret_key, "S3_SECRET_ACCESS_KEY");
417 get_env_value(opt_s3_session_token, "S3_SESSION_TOKEN");
418 get_env_value(opt_s3_region, "S3_DEFAULT_REGION");
419 get_env_value(opt_cacert, "S3_CA_BUNDLE");
420 get_env_value(opt_s3_endpoint, "S3_ENDPOINT");
421
422 get_env_value(opt_s3_access_key, "AWS_ACCESS_KEY_ID");
423 get_env_value(opt_s3_secret_key, "AWS_SECRET_ACCESS_KEY");
424 get_env_value(opt_s3_session_token, "AWS_SESSION_TOKEN");
425 get_env_value(opt_s3_region, "AWS_DEFAULT_REGION");
426 get_env_value(opt_cacert, "AWS_CA_BUNDLE");
427 get_env_value(opt_s3_endpoint, "AWS_ENDPOINT");
428
429 get_env_value(opt_s3_access_key, "ACCESS_KEY_ID");
430 get_env_value(opt_s3_secret_key, "SECRET_ACCESS_KEY");
431 get_env_value(opt_s3_region, "DEFAULT_REGION");
432 get_env_value(opt_s3_endpoint, "ENDPOINT");
433
434 get_env_value(opt_google_access_key, "ACCESS_KEY_ID");
435 get_env_value(opt_google_secret_key, "SECRET_ACCESS_KEY");
436 get_env_value(opt_google_session_token, "SESSION_TOKEN");
437 get_env_value(opt_google_region, "DEFAULT_REGION");
438 get_env_value(opt_google_endpoint, "ENDPOINT");
439 }
440
441 static char **defaults_argv = nullptr;
442 static MEM_ROOT argv_alloc{PSI_NOT_INSTRUMENTED, 512};
443
parse_args(int argc,char ** argv)444 static bool parse_args(int argc, char **argv) {
445 if (load_defaults("my", load_default_groups, &argc, &argv, &argv_alloc)) {
446 return true;
447 }
448
449 defaults_argv = argv;
450
451 if (handle_options(&argc, &argv, my_long_options, get_one_option)) {
452 return true;
453 }
454
455 const char *command;
456
457 if (argc < 1) {
458 msg_ts("Command isn't specified. Supported commands are put and get\n");
459 usage();
460 return true;
461 }
462
463 command = argv[0];
464 argc--;
465 argv++;
466
467 get_env_args();
468
469 if (strcasecmp(command, "put") == 0) {
470 opt_mode = MODE_PUT;
471 } else if (strcasecmp(command, "get") == 0) {
472 opt_mode = MODE_GET;
473 } else if (strcasecmp(command, "delete") == 0) {
474 opt_mode = MODE_DELETE;
475 } else {
476 msg_ts("Unknown command %s. Supported commands are put and get\n", command);
477 usage();
478 return true;
479 }
480
481 /* make sure name is specified */
482 if (argc < 1) {
483 msg_ts("Backup name is required argument\n");
484 return true;
485 }
486 backup_name = argv[0];
487 argc--;
488 argv++;
489
490 std::string backup_uri = backup_name;
491 std::string bucket_name;
492 bool backup_name_uri_formatted = false;
493
494 /* parse the backup name */
495 if (starts_with(backup_uri, "s3://")) {
496 backup_name_uri_formatted = true;
497 opt_storage = S3;
498 backup_uri = backup_uri.substr(5);
499 } else if (starts_with(backup_uri, "google://")) {
500 backup_name_uri_formatted = true;
501 opt_storage = GOOGLE;
502 backup_uri = backup_uri.substr(9);
503 } else if (starts_with(backup_uri, "swift://")) {
504 backup_name_uri_formatted = true;
505 opt_storage = SWIFT;
506 backup_uri = backup_uri.substr(8);
507 }
508
509 if (backup_name_uri_formatted) {
510 auto slash_pos = backup_uri.find('/');
511 if (slash_pos != std::string::npos) {
512 bucket_name = backup_uri.substr(0, slash_pos);
513 backup_name = backup_uri.substr(slash_pos + 1);
514 rtrim_slashes(backup_name);
515 if (opt_storage == S3) {
516 my_free(opt_s3_bucket);
517 opt_s3_bucket =
518 my_strdup(PSI_NOT_INSTRUMENTED, bucket_name.c_str(), MYF(MY_WME));
519 } else if (opt_storage == GOOGLE) {
520 my_free(opt_google_bucket);
521 opt_google_bucket =
522 my_strdup(PSI_NOT_INSTRUMENTED, bucket_name.c_str(), MYF(MY_WME));
523 } else if (opt_storage == SWIFT) {
524 my_free(opt_swift_container);
525 opt_swift_container =
526 my_strdup(PSI_NOT_INSTRUMENTED, bucket_name.c_str(), MYF(MY_WME));
527 }
528 }
529 }
530
531 /* validate arguments */
532 if (opt_storage == SWIFT) {
533 if (opt_swift_user == nullptr) {
534 msg_ts("Swift user is not specified\n");
535 return true;
536 }
537 if (opt_swift_container == nullptr) {
538 msg_ts("Swift container is not specified\n");
539 return true;
540 }
541 if (opt_swift_auth_url == nullptr) {
542 msg_ts("Swift auth URL is not specified\n");
543 return true;
544 }
545 } else if (opt_storage == S3) {
546 if (opt_s3_access_key == nullptr) {
547 msg_ts("S3 access key is not specified\n");
548 return true;
549 }
550 if (opt_s3_secret_key == nullptr) {
551 msg_ts("S3 secret key is not specified\n");
552 return true;
553 }
554 }
555
556 while (argc > 0) {
557 file_list.insert(*argv);
558 --argc;
559 ++argv;
560 }
561
562 return (0);
563 }
564
xbcloud_put(Object_store * store,const std::string & container,const std::string & backup_name,bool upload_md5)565 bool xbcloud_put(Object_store *store, const std::string &container,
566 const std::string &backup_name, bool upload_md5) {
567 bool exists;
568
569 if (!store->container_exists(container, exists)) {
570 return false;
571 }
572
573 if (!exists) {
574 if (!store->create_container(container)) {
575 return false;
576 }
577 }
578
579 std::vector<std::string> object_list;
580 if (!store->list_objects_in_directory(container, backup_name, object_list)) {
581 return false;
582 }
583
584 if (!object_list.empty()) {
585 msg_ts("%s: error: backup named %s already exists!\n", my_progname,
586 backup_name.c_str());
587 return false;
588 }
589
590 struct file_entry_t {
591 my_off_t chunk_idx;
592 my_off_t offset;
593 std::string path;
594 };
595
596 std::unordered_map<std::string, std::unique_ptr<file_entry_t>> filehash;
597 xb_rstream_t *stream = xb_stream_read_new();
598 if (stream == nullptr) {
599 msg_ts("%s: xb_stream_read_new() failed.", my_progname);
600 return false;
601 }
602
603 xb_rstream_chunk_t chunk;
604 xb_rstream_result_t res;
605
606 bool has_errors = false;
607
608 memset(&chunk, 0, sizeof(chunk));
609
610 Http_buffer buf_md5;
611
612 Event_handler h(opt_parallel > 0 ? opt_parallel : 1);
613 if (!h.init()) {
614 msg_ts("%s: Failed to initialize event handler.\n", my_progname);
615 return false;
616 }
617 auto thread = h.run();
618
619 while (!has_errors) {
620 res = xb_stream_read_chunk(stream, &chunk);
621 if (res != XB_STREAM_READ_CHUNK) {
622 my_free(chunk.raw_data);
623 my_free(chunk.sparse_map);
624 break;
625 }
626 if (chunk.type == XB_CHUNK_TYPE_UNKNOWN &&
627 !(chunk.flags & XB_STREAM_FLAG_IGNORABLE)) {
628 continue;
629 }
630
631 file_entry_t *entry = filehash[chunk.path].get();
632 if (entry == nullptr) {
633 entry = (filehash[chunk.path] = make_unique<file_entry_t>()).get();
634 entry->path = chunk.path;
635 }
636
637 if (chunk.type == XB_CHUNK_TYPE_PAYLOAD) {
638 res = (xb_rstream_result_t)xb_stream_validate_checksum(&chunk);
639 if (res != XB_STREAM_READ_CHUNK) {
640 break;
641 }
642
643 if (entry->offset != chunk.offset) {
644 msg_ts(
645 "%s: out-of-order chunk: real offset = 0x%llx, "
646 "expected offset = 0x%llx\n",
647 my_progname, chunk.offset, entry->offset);
648 res = XB_STREAM_READ_ERROR;
649 break;
650 }
651 }
652
653 std::stringstream file_name_s;
654 file_name_s << chunk.path << "." << std::setw(20) << std::setfill('0')
655 << entry->chunk_idx;
656 std::string file_name = file_name_s.str();
657 std::string object_name = backup_name;
658 object_name.append("/").append(file_name);
659
660 Http_buffer buf = Http_buffer();
661 buf.assign_buffer(static_cast<char *>(chunk.raw_data), chunk.buflen,
662 chunk.raw_length);
663
664 if (upload_md5) {
665 buf_md5.append(hex_encode(buf.md5()));
666 buf_md5.append(" ");
667 buf_md5.append(file_name);
668 buf_md5.append("\n");
669 }
670
671 store->async_upload_object(
672 container, object_name, buf, &h,
673 std::bind(
674 [](bool ok, std::string path, size_t length, bool *err) {
675 if (ok) {
676 msg_ts("%s: successfully uploaded chunk: %s, size: %zu\n",
677 my_progname, path.c_str(), length);
678 } else {
679 msg_ts("%s: error: failed to upload chunk: %s, size: %zu\n",
680 my_progname, path.c_str(), length);
681 *err = true;
682 }
683 },
684 std::placeholders::_1, object_name, chunk.raw_length, &has_errors));
685
686 entry->offset += chunk.length;
687 entry->chunk_idx++;
688
689 if (chunk.type == XB_CHUNK_TYPE_EOF) {
690 filehash.erase(entry->path);
691 }
692
693 memset(&chunk, 0, sizeof(chunk));
694 }
695
696 h.stop();
697 thread.join();
698
699 xb_stream_read_done(stream);
700
701
702 // check if backup directory exists and it has some files
703 if (res == XB_STREAM_READ_ERROR || has_errors ||
704 !store->list_objects_in_directory(container, backup_name, object_list) ||
705 object_list.size() == 0) {
706 msg_ts("%s: Upload failed.\n", my_progname);
707 return false;
708 }
709
710 // check if xtrabackup_info, almost the last file exists in backup
711 if (std::count(object_list.begin(), object_list.end(),
712 backup_name + "/xtrabackup_info.00000000000000000000") == 0) {
713 msg_ts("%s: Upload failed: backup is incomplete.\n", my_progname);
714 return false;
715 }
716
717 if (upload_md5) {
718 msg_ts("%s: Uploading md5\n", my_progname);
719 if (!store->upload_object(container, backup_name + ".md5", buf_md5)) {
720 msg_ts("%s: Upload failed: Error uploading md5.\n", my_progname);
721 return false;
722 }
723 }
724
725 msg_ts("%s: Upload completed.\n", my_progname);
726 return true;
727 }
728
chunk_name_to_file_name(const std::string & chunk_name,std::string & file_name,my_off_t & idx)729 bool chunk_name_to_file_name(const std::string &chunk_name,
730 std::string &file_name, my_off_t &idx) {
731 if (chunk_name.size() < 22 && chunk_name[chunk_name.size() - 21] != '.') {
732 /* chunk name is invalid */
733 return false;
734 }
735 file_name = chunk_name.substr(0, chunk_name.size() - 21);
736 idx = atoll(&chunk_name.c_str()[chunk_name.size() - 20]);
737 return true;
738 }
739
xbcloud_delete(Object_store * store,const std::string & container,const std::string & backup_name)740 bool xbcloud_delete(Object_store *store, const std::string &container,
741 const std::string &backup_name) {
742 std::vector<std::string> object_list;
743
744 if (!store->list_objects_in_directory(container, backup_name, object_list)) {
745 msg_ts("%s: Delete failed. Cannot list %s.\n", my_progname,
746 backup_name.c_str());
747 return false;
748 }
749 if (object_list.empty()) {
750 msg_ts("%s: error: backup named %s doesn't exists!\n", my_progname,
751 backup_name.c_str());
752 return false;
753 }
754
755 Event_handler h(opt_parallel > 0 ? opt_parallel : 1);
756 if (!h.init()) {
757 msg_ts("%s: Failed to initialize event handler.\n", my_progname);
758 return false;
759 }
760 auto thread = h.run();
761
762 bool error = false;
763 for (const auto &obj : object_list) {
764 std::string file_name;
765 my_off_t idx;
766 if (error) break;
767 if (!chunk_name_to_file_name(obj, file_name, idx)) {
768 continue;
769 }
770 if (!file_list.empty() &&
771 file_list.count(file_name.substr(backup_name.length() + 1)) < 1) {
772 continue;
773 }
774 msg_ts("%s: Deleting %s.\n", my_progname, obj.c_str());
775 if (!store->async_delete_object(
776 container, obj, &h,
777 std::bind(
778 [](bool success, std::string obj, bool *error) {
779 if (!success) {
780 msg_ts("%s: Delete failed. Cannot delete %s.\n",
781 my_progname, obj.c_str());
782 *error = true;
783 }
784 },
785 std::placeholders::_1, obj, &error))) {
786 return false;
787 }
788 }
789
790 h.stop();
791 thread.join();
792
793 if (error) {
794 msg_ts("%s: Delete failed.\n", my_progname);
795 } else {
796 msg_ts("%s: Delete completed.\n", my_progname);
797 }
798
799 return !error;
800 }
801
xbcloud_download(Object_store * store,const std::string & container,const std::string & backup_name)802 bool xbcloud_download(Object_store *store, const std::string &container,
803 const std::string &backup_name) {
804 std::vector<std::string> object_list;
805
806 if (!store->list_objects_in_directory(container, backup_name, object_list) ||
807 object_list.size() == 0) {
808 msg_ts("%s: Download failed. Cannot list %s.\n", my_progname,
809 backup_name.c_str());
810 return false;
811 }
812
813 struct download_state {
814 std::mutex m;
815 std::set<std::string> chunks;
816
817 bool empty() {
818 std::lock_guard<std::mutex> g(m);
819 return chunks.empty();
820 }
821
822 bool start_chunk(const std::string &chunk) {
823 std::lock_guard<std::mutex> g(m);
824 if (chunks.count(chunk) > 0) {
825 return false;
826 }
827 chunks.insert(chunk);
828 return true;
829 }
830
831 void complete_chunk(const std::string &chunk) {
832 std::lock_guard<std::mutex> g(m);
833 chunks.erase(chunk);
834 }
835 };
836
837 struct chunk_state {
838 my_off_t next;
839 my_off_t last;
840 };
841
842 std::map<std::string, struct chunk_state> chunks;
843 struct download_state download_state;
844
845 for (const auto &obj : object_list) {
846 my_off_t idx;
847 std::string file_name;
848 if (!chunk_name_to_file_name(obj, file_name, idx)) {
849 continue;
850 }
851 if (!file_list.empty() &&
852 file_list.count(file_name.substr(backup_name.length() + 1)) < 1) {
853 continue;
854 }
855 chunks[file_name] = {0, idx};
856 }
857
858 Event_handler h(opt_parallel > 0 ? opt_parallel : 1);
859 if (!h.init()) {
860 msg_ts("%s: Failed to initialize event handler.\n", my_progname);
861 return false;
862 }
863 auto thread = h.run();
864
865 bool error{false};
866
867 while (true) {
868 if (chunks.empty() && download_state.empty()) {
869 break;
870 }
871 if (error && !download_state.empty()) {
872 continue;
873 }
874 if (error) break;
875 for (auto it = chunks.begin(); it != chunks.end();) {
876 if (error) break;
877 if (!download_state.start_chunk(it->first)) {
878 ++it;
879 continue;
880 }
881 std::stringstream s_object_name;
882 s_object_name << it->first << "." << std::setw(20) << std::setfill('0')
883 << it->second.next;
884 std::string object_name = s_object_name.str();
885 msg_ts("%s: Downloading %s.\n", my_progname, object_name.c_str());
886 store->async_download_object(
887 container, object_name, &h,
888 std::bind(
889 [](bool success, const Http_buffer &contents, std::string name,
890 std::string basename, struct download_state *download_state,
891 bool *error) {
892 if (!success) {
893 *error = true;
894 msg_ts("%s: Download failed. Cannot download %s.\n",
895 my_progname, name.c_str());
896 download_state->complete_chunk(basename);
897 return;
898 }
899 msg_ts("%s: Download successfull %s, size %zu\n", my_progname,
900 name.c_str(), contents.size());
901 std::cout.write(&contents[0], contents.size());
902 std::cout.flush();
903 download_state->complete_chunk(basename);
904 if (std::cout.fail()) {
905 msg_ts(
906 "%s: Download failed. Cannot write to the standard "
907 "output.\n",
908 my_progname);
909 *error = true;
910 }
911 },
912 std::placeholders::_1, std::placeholders::_2, object_name,
913 it->first, &download_state, &error));
914 if (++it->second.next > it->second.last) {
915 it = chunks.erase(it);
916 } else {
917 ++it;
918 }
919 }
920 }
921
922 h.stop();
923 thread.join();
924
925 if (error) {
926 msg_ts("%s: Download failed.\n", my_progname);
927 } else {
928 msg_ts("%s: Download completed.\n", my_progname);
929 }
930
931 return !error;
932 }
933
934 struct main_exit_hook {
~main_exit_hookmain_exit_hook935 ~main_exit_hook() {
936 http_cleanup();
937 my_end(0);
938 }
939 };
940
main(int argc,char ** argv)941 int main(int argc, char **argv) {
942 MY_INIT(argv[0]);
943
944 #ifndef NO_SIGPIPE
945 signal(SIGPIPE, SIG_IGN);
946 #endif
947
948 http_init();
949 crc_init();
950
951 /* trick to automatically release some globally alocated resources */
952 main_exit_hook exit_hook;
953
954 if (parse_args(argc, argv)) {
955 return EXIT_FAILURE;
956 }
957
958 std::unique_ptr<Object_store> object_store = nullptr;
959 Http_client http_client;
960 if (opt_verbose) {
961 http_client.set_verbose(true);
962 }
963 if (opt_insecure) {
964 http_client.set_insecure(true);
965 }
966 if (opt_cacert != nullptr) {
967 http_client.set_cacaert(opt_cacert);
968 }
969
970 std::string container_name;
971
972 if (opt_storage == SWIFT) {
973 std::string auth_url = opt_swift_auth_url;
974 if (!ends_with(auth_url, "/")) {
975 auth_url.append("/");
976 }
977
978 const char *valid_versions[] = {"/v1/", "/v2/", "/v3/",
979 "/v1.0/", "/v2.0/", "/v3.0/"};
980 bool versioned_url = false;
981 for (auto version : valid_versions) {
982 if (ends_with(opt_swift_auth_url, version)) {
983 my_free(opt_swift_auth_version);
984 opt_swift_auth_version =
985 my_strdup(PSI_NOT_INSTRUMENTED,
986 std::string(version + 2, strlen(version + 2) - 1).c_str(),
987 MYF(MY_FAE));
988 versioned_url = true;
989 }
990 }
991 if (!versioned_url) {
992 if (opt_swift_auth_version != nullptr) {
993 auth_url.append("v");
994 auth_url.append(opt_swift_auth_version);
995 auth_url.append("/");
996 } else {
997 opt_swift_auth_version =
998 my_strdup(PSI_NOT_INSTRUMENTED, "1.0", MYF(MY_FAE));
999 auth_url.append("v");
1000 auth_url.append(opt_swift_auth_version);
1001 auth_url.append("/");
1002 }
1003 }
1004
1005 Keystone_client keystone_client(&http_client, auth_url);
1006
1007 if (opt_swift_key != nullptr) {
1008 keystone_client.set_key(opt_swift_key);
1009 }
1010 if (opt_swift_user != nullptr) {
1011 keystone_client.set_user(opt_swift_user);
1012 }
1013 if (opt_swift_password != nullptr) {
1014 keystone_client.set_password(opt_swift_password);
1015 }
1016 if (opt_swift_tenant != nullptr) {
1017 keystone_client.set_tenant(opt_swift_tenant);
1018 }
1019 if (opt_swift_tenant_id != nullptr) {
1020 keystone_client.set_tenant_id(opt_swift_tenant_id);
1021 }
1022 if (opt_swift_domain != nullptr) {
1023 keystone_client.set_domain(opt_swift_domain);
1024 }
1025 if (opt_swift_domain_id != nullptr) {
1026 keystone_client.set_domain_id(opt_swift_domain_id);
1027 }
1028 if (opt_swift_project_domain != nullptr) {
1029 keystone_client.set_project_domain(opt_swift_project_domain);
1030 }
1031 if (opt_swift_project_domain_id != nullptr) {
1032 keystone_client.set_project_domain_id(opt_swift_project_domain_id);
1033 }
1034 if (opt_swift_project != nullptr) {
1035 keystone_client.set_project(opt_swift_project);
1036 }
1037 if (opt_swift_project_id != nullptr) {
1038 keystone_client.set_project_id(opt_swift_project_id);
1039 }
1040
1041 Keystone_client::auth_info_t auth_info;
1042
1043 if (opt_swift_auth_version == NULL || *opt_swift_auth_version == '1') {
1044 /* TempAuth */
1045 if (!keystone_client.temp_auth(auth_info)) {
1046 return EXIT_FAILURE;
1047 }
1048 } else if (*opt_swift_auth_version == '2') {
1049 /* Keystone v2 */
1050 if (!keystone_client.auth_v2(
1051 opt_swift_region != nullptr ? opt_swift_region : "", auth_info)) {
1052 return EXIT_FAILURE;
1053 }
1054 } else if (*opt_swift_auth_version == '3') {
1055 /* Keystone v3 */
1056 if (!keystone_client.auth_v3(
1057 opt_swift_region != nullptr ? opt_swift_region : "", auth_info)) {
1058 return EXIT_FAILURE;
1059 }
1060 }
1061
1062 if (opt_swift_storage_url != nullptr) {
1063 /* override storage url */
1064 auth_info.url = opt_swift_storage_url;
1065 }
1066
1067 msg_ts("Object store URL: %s\n", auth_info.url.c_str());
1068
1069 object_store = std::unique_ptr<Object_store>(
1070 new Swift_object_store(&http_client, auth_info.url, auth_info.token));
1071
1072 container_name = opt_swift_container;
1073
1074 } else if (opt_storage == S3) {
1075 std::string region = opt_s3_region != nullptr ? opt_s3_region : "us-east-1";
1076 std::string access_key =
1077 opt_s3_access_key != nullptr ? opt_s3_access_key : "";
1078 std::string secret_key =
1079 opt_s3_secret_key != nullptr ? opt_s3_secret_key : "";
1080 std::string session_token =
1081 opt_s3_session_token != nullptr ? opt_s3_session_token : "";
1082 object_store = std::unique_ptr<Object_store>(new S3_object_store(
1083 &http_client, region, access_key, secret_key, session_token,
1084 opt_s3_endpoint != nullptr ? opt_s3_endpoint : "",
1085 static_cast<s3_bucket_lookup_t>(opt_s3_bucket_lookup),
1086 static_cast<s3_api_version_t>(opt_s3_api_version)));
1087
1088 if (opt_s3_bucket == nullptr) {
1089 msg_ts("%s: S3 bucket is not specified.\n", my_progname);
1090 return EXIT_FAILURE;
1091 }
1092
1093 reinterpret_cast<S3_object_store *>(object_store.get())
1094 ->set_extra_http_headers(extra_http_headers);
1095
1096 container_name = opt_s3_bucket;
1097
1098 if (!reinterpret_cast<S3_object_store *>(object_store.get())
1099 ->probe_api_version_and_lookup(container_name)) {
1100 return EXIT_FAILURE;
1101 }
1102 } else if (opt_storage == GOOGLE) {
1103 std::string region =
1104 opt_google_region != nullptr ? opt_google_region : "us-central-1";
1105 std::string access_key =
1106 opt_google_access_key != nullptr ? opt_google_access_key : "";
1107 std::string secret_key =
1108 opt_google_secret_key != nullptr ? opt_google_secret_key : "";
1109 std::string session_token =
1110 opt_google_session_token != nullptr ? opt_google_session_token : "";
1111 object_store = std::unique_ptr<Object_store>(new S3_object_store(
1112 &http_client, region, access_key, secret_key, session_token,
1113 opt_google_endpoint != nullptr ? opt_google_endpoint
1114 : "https://storage.googleapis.com/",
1115 LOOKUP_DNS, S3_V2));
1116
1117 if (opt_google_bucket == nullptr) {
1118 msg_ts("%s: Google bucket is not specified.\n", my_progname);
1119 return EXIT_FAILURE;
1120 }
1121
1122 reinterpret_cast<S3_object_store *>(object_store.get())
1123 ->set_extra_http_headers(extra_http_headers);
1124
1125 container_name = opt_google_bucket;
1126
1127 if (!reinterpret_cast<S3_object_store *>(object_store.get())
1128 ->probe_api_version_and_lookup(container_name)) {
1129 return EXIT_FAILURE;
1130 }
1131 }
1132
1133 if (opt_mode == MODE_PUT) {
1134 if (!xbcloud_put(object_store.get(), container_name, backup_name,
1135 opt_md5)) {
1136 return EXIT_FAILURE;
1137 }
1138 } else if (opt_mode == MODE_GET) {
1139 if (!xbcloud_download(object_store.get(), container_name, backup_name)) {
1140 return EXIT_FAILURE;
1141 }
1142 } else if (opt_mode == MODE_DELETE) {
1143 if (!xbcloud_delete(object_store.get(), container_name, backup_name)) {
1144 return EXIT_FAILURE;
1145 }
1146 } else {
1147 msg_ts("Unknown command supplied.\n");
1148 return EXIT_FAILURE;
1149 }
1150
1151 return EXIT_SUCCESS;
1152 }
1153