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