1 /* Copyright (C) 2019 MariaDB Corporation Ab
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software Foundation,
14    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
15 
16 /*
17   Interface function used by S3 storage engine and aria_copy_for_s3
18 */
19 
20 #include "maria_def.h"
21 #include "s3_func.h"
22 #include <aria_backup.h>
23 #include <mysqld_error.h>
24 #include <sql_const.h>
25 #include <mysys_err.h>
26 #include <mysql_com.h>
27 #include <zlib.h>
28 
29 /* number of '.' to print during a copy in verbose mode */
30 #define DISPLAY_WITH 79
31 
32 static void convert_index_to_s3_format(uchar *header, ulong block_size,
33                                        int compression);
34 static void convert_index_to_disk_format(uchar *header);
35 static void convert_frm_to_s3_format(uchar *header);
36 static void convert_frm_to_disk_format(uchar *header);
37 static int s3_read_file_from_disk(const char *filename, uchar **to,
38                                   size_t *to_size, my_bool print_error);
39 
40 /* Used by ha_s3.cc and tools to define different protocol options */
41 
42 static const char *protocol_types[]= {"Auto", "Original", "Amazon", NullS};
43 TYPELIB s3_protocol_typelib= {array_elements(protocol_types)-1,"",
44                               protocol_types, NULL};
45 
46 /******************************************************************************
47  Allocations handler for libmarias3
48  To be removed when we do the init allocation in mysqld.cc
49 ******************************************************************************/
50 
s3_wrap_malloc(size_t size)51 static void *s3_wrap_malloc(size_t size)
52 {
53   return my_malloc(PSI_NOT_INSTRUMENTED, size, MYF(MY_WME));
54 }
55 
s3_wrap_calloc(size_t nmemb,size_t size)56 static void *s3_wrap_calloc(size_t nmemb, size_t size)
57 {
58   return my_malloc(PSI_NOT_INSTRUMENTED, nmemb * size,
59                    MYF(MY_WME | MY_ZEROFILL));
60 }
61 
s3_wrap_realloc(void * ptr,size_t size)62 static void *s3_wrap_realloc(void *ptr, size_t size)
63 {
64   return my_realloc(PSI_NOT_INSTRUMENTED, ptr, size,
65                     MYF(MY_WME | MY_ALLOW_ZERO_PTR));
66 }
67 
s3_wrap_strdup(const char * str)68 static char *s3_wrap_strdup(const char *str)
69 {
70   return my_strdup(PSI_NOT_INSTRUMENTED, str, MYF(MY_WME));
71 }
72 
s3_wrap_free(void * ptr)73 static void s3_wrap_free(void *ptr)
74 {
75   if (ptr)                                      /* Avoid tracing of null */
76     my_free(ptr);
77 }
78 
s3_init_library()79 void s3_init_library()
80 {
81   ms3_library_init_malloc(s3_wrap_malloc, s3_wrap_free, s3_wrap_realloc,
82                           s3_wrap_strdup, s3_wrap_calloc);
83 }
84 
s3_deinit_library()85 void s3_deinit_library()
86 {
87   ms3_library_deinit();
88 }
89 
90 /******************************************************************************
91  Functions on S3_INFO and S3_BLOCK
92 ******************************************************************************/
93 
94 /*
95   Free memory allocated by s3_get_object
96 */
97 
s3_free(S3_BLOCK * data)98 void s3_free(S3_BLOCK *data)
99 {
100   my_free(data->alloc_ptr);
101   data->alloc_ptr= 0;
102 }
103 
104 
105 /*
106   Copy a S3_INFO structure
107 */
108 
s3_info_copy(S3_INFO * old)109 S3_INFO *s3_info_copy(S3_INFO *old)
110 {
111   S3_INFO *to, tmp;
112 
113   /* Copy lengths */
114   memcpy(&tmp, old, sizeof(tmp));
115   /* Allocate new buffers */
116   if (!my_multi_malloc(PSI_NOT_INSTRUMENTED, MY_WME, &to, sizeof(S3_INFO),
117                        &tmp.access_key.str, old->access_key.length+1,
118                        &tmp.secret_key.str, old->secret_key.length+1,
119                        &tmp.region.str,     old->region.length+1,
120                        &tmp.bucket.str,     old->bucket.length+1,
121                        &tmp.database.str,   old->database.length+1,
122                        &tmp.table.str,      old->table.length+1,
123                        &tmp.base_table.str, old->base_table.length+1,
124                        NullS))
125     return 0;
126   /* Copy lengths and new pointers to to */
127   memcpy(to, &tmp, sizeof(tmp));
128   /* Copy data */
129   strmov((char*) to->access_key.str, old->access_key.str);
130   strmov((char*) to->secret_key.str, old->secret_key.str);
131   strmov((char*) to->region.str,     old->region.str);
132   strmov((char*) to->bucket.str,     old->bucket.str);
133   /* Database may not be null terminated */
134   strmake((char*) to->database.str,  old->database.str, old->database.length);
135   strmov((char*) to->table.str,      old->table.str);
136   strmov((char*) to->base_table.str, old->base_table.str);
137   return to;
138 }
139 
140 /**
141    Open a connection to s3
142 */
143 
s3_open_connection(S3_INFO * s3)144 ms3_st *s3_open_connection(S3_INFO *s3)
145 {
146   ms3_st *s3_client;
147   if (!(s3_client= ms3_init(s3->access_key.str,
148                             s3->secret_key.str,
149                             s3->region.str,
150                             s3->host_name.str)))
151   {
152     my_printf_error(HA_ERR_NO_SUCH_TABLE,
153                     "Can't open connection to S3, error: %d %s", MYF(0),
154                     errno, ms3_error(errno));
155     my_errno= HA_ERR_NO_SUCH_TABLE;
156   }
157   if (s3->protocol_version)
158     ms3_set_option(s3_client, MS3_OPT_FORCE_PROTOCOL_VERSION,
159                    &s3->protocol_version);
160   if (s3->port)
161     ms3_set_option(s3_client, MS3_OPT_PORT_NUMBER, &s3->port);
162 
163   if (s3->use_http)
164     ms3_set_option(s3_client, MS3_OPT_USE_HTTP, NULL);
165 
166   return s3_client;
167 }
168 
169 /**
170    close a connection to s3
171 */
172 
s3_deinit(ms3_st * s3_client)173 void s3_deinit(ms3_st *s3_client)
174 {
175   DBUG_PUSH("");                                /* Avoid tracing free calls */
176   ms3_deinit(s3_client);
177   DBUG_POP();
178 }
179 
180 
181 /******************************************************************************
182  High level functions to copy tables to and from S3
183 ******************************************************************************/
184 
185 /**
186    Create suffix for object name
187    @param to_end end of suffix (from previous call or 000000 at start)
188 
189    The suffix is a 6 length '0' prefixed number. If the number
190    gets longer than 6, then it's extended to 7 and more digits.
191 */
192 
fix_suffix(char * to_end,ulong nr)193 static void fix_suffix(char *to_end, ulong nr)
194 {
195   char buff[11];
196   uint length= (uint) (int10_to_str(nr, buff, 10) - buff);
197   set_if_smaller(length, 6);
198   strmov(to_end - length, buff);
199 }
200 
201 /**
202    Copy file to 'aws_path' in blocks of block_size
203 
204    @return 0   ok
205    @return 1   error. Error message is printed to stderr
206 
207    Notes:
208    file is always closed before return
209 */
210 
copy_from_file(ms3_st * s3_client,const char * aws_bucket,char * aws_path,File file,my_off_t start,my_off_t file_end,uchar * block,size_t block_size,my_bool compression,my_bool display)211 static my_bool copy_from_file(ms3_st *s3_client, const char *aws_bucket,
212                               char *aws_path,
213                               File file, my_off_t start, my_off_t file_end,
214                               uchar *block, size_t block_size,
215                               my_bool compression, my_bool display)
216 {
217   my_off_t pos;
218   char *path_end= strend(aws_path);
219   ulong bnr;
220   my_bool print_done= 0;
221   size_t length;
222 
223   for (pos= start, bnr=1 ; pos < file_end ; pos+= length, bnr++)
224   {
225     if ((length= my_pread(file, block, block_size, pos, MYF(MY_WME))) ==
226         MY_FILE_ERROR)
227       goto err;
228     if (length == 0)
229     {
230       my_error(EE_EOFERR, MYF(0), my_filename(file), my_errno);
231       goto err;
232     }
233 
234     fix_suffix(path_end, bnr);
235     if (s3_put_object(s3_client, aws_bucket, aws_path, block, length,
236                       compression))
237       goto err;
238 
239     /* Write up to DISPLAY_WITH number of '.' during copy */
240     if (display &&
241         ((pos + block_size) * DISPLAY_WITH / file_end) >
242         (pos * DISPLAY_WITH/file_end))
243     {
244       fputc('.', stdout); fflush(stdout);
245       print_done= 1;
246     }
247   }
248   if (print_done)
249   {
250     fputc('\n', stdout); fflush(stdout);
251   }
252   my_close(file, MYF(MY_WME));
253   return 0;
254 
255 err:
256   my_close(file, MYF(MY_WME));
257   if (print_done)
258   {
259     fputc('\n', stdout); fflush(stdout);
260   }
261   return 1;
262 }
263 
264 
265 /**
266    Copy an Aria table to S3
267    @param s3_client    connection to S3
268    @param aws_bucket   Aws bucket
269    @param path         Path for Aria table (can be temp table)
270    @param database     database name
271    @param table_name   table name
272    @param block_size   Block size in s3. If 0 then use block size
273                        and compression as specified in the .MAI file as
274                        specified as part of open.
275    @param compression  Compression algorithm (0 = none, 1 = zip)
276                        If block size is 0 then use .MAI file.
277    @return 0  ok
278    @return 1  error
279 
280    The table will be copied in S3 into the following locations:
281 
282    frm file (for discovery):
283    aws_bucket/database/table/frm
284 
285    First index block (contains description if the Aria file):
286    aws_bucket/database/table/aria
287 
288    Rest of the index file:
289    aws_bucket/database/table/index/block_number
290 
291    Data file:
292    aws_bucket/database/table/data/block_number
293 
294    block_number is 6 digits decimal number, prefixed with 0
295    (Can be larger than 6 numbers, the prefix is just for nice output)
296 
297    frm and base blocks are small (just the needed data).
298    index and blocks are of size 's3_block_size'
299 
300    If compression is used, then original block size is s3_block_size
301    but the stored block will be the size of the compressed block.
302 */
303 
aria_copy_to_s3(ms3_st * s3_client,const char * aws_bucket,const char * path,const char * database,const char * table_name,ulong block_size,my_bool compression,my_bool force,my_bool display,my_bool copy_frm)304 int aria_copy_to_s3(ms3_st *s3_client, const char *aws_bucket,
305                     const char *path,
306                     const char *database, const char *table_name,
307                     ulong block_size, my_bool compression,
308                     my_bool force, my_bool display, my_bool copy_frm)
309 {
310   ARIA_TABLE_CAPABILITIES cap;
311   char aws_path[FN_REFLEN+100];
312   char filename[FN_REFLEN];
313   char *aws_path_end, *end;
314   uchar *alloc_block= 0, *block;
315   ms3_status_st status;
316   File file= -1;
317   my_off_t file_size;
318   size_t frm_length;
319   int error;
320   my_bool frm_created= 0;
321   DBUG_ENTER("aria_copy_to_s3");
322   DBUG_PRINT("enter",("from: %s  database: %s  table: %s",
323                       path, database, table_name));
324 
325   aws_path_end= strxmov(aws_path, database, "/", table_name, NullS);
326   strmov(aws_path_end, "/aria");
327 
328   if (!ms3_status(s3_client, aws_bucket, aws_path, &status))
329   {
330     if (!force)
331     {
332       my_printf_error(EE_CANTCREATEFILE, "File %s exists in s3", MYF(0),
333                       aws_path);
334       DBUG_RETURN(EE_CANTCREATEFILE);
335     }
336     if ((error= aria_delete_from_s3(s3_client, aws_bucket, database,
337                                     table_name, display)))
338       DBUG_RETURN(error);
339   }
340 
341   if (copy_frm)
342   {
343     /*
344       Copy frm file if it exists
345       We do this first to ensure that .frm always exists. This is needed to
346       ensure that discovery of the table will work.
347     */
348     fn_format(filename, path, "", ".frm", MY_REPLACE_EXT);
349     if (!s3_read_file_from_disk(filename, &alloc_block, &frm_length,0))
350     {
351       if (display)
352         printf("Copying frm file %s\n", filename);
353 
354       end= strmov(aws_path_end,"/frm");
355       convert_frm_to_s3_format(alloc_block);
356 
357       /* Note that frm is not compressed! */
358       if (s3_put_object(s3_client, aws_bucket, aws_path, alloc_block, frm_length,
359                         0))
360         goto err;
361 
362       frm_created= 1;
363       my_free(alloc_block);
364       alloc_block= 0;
365     }
366   }
367 
368   if (display)
369     printf("Copying aria table: %s.%s to s3\n", database, table_name);
370 
371   /* Index file name */
372   fn_format(filename, path, "", ".MAI", MY_REPLACE_EXT);
373   if ((file= my_open(filename,
374                      O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
375                      MYF(MY_WME))) < 0)
376     DBUG_RETURN(1);
377   if ((error= aria_get_capabilities(file, &cap)))
378   {
379     fprintf(stderr, "Got error %d when reading Aria header from %s\n",
380             error, path);
381     goto err;
382   }
383   if (cap.transactional || cap.data_file_type != BLOCK_RECORD ||
384       cap.encrypted)
385   {
386     fprintf(stderr,
387             "Aria table %s doesn't match criteria to be copied to S3.\n"
388             "It should be non-transactional and should have row_format page\n",
389             path);
390     goto err;
391   }
392   /*
393     If block size is not specified, use the values specified as part of
394     create
395   */
396   if (block_size == 0)
397   {
398     block_size=  cap.s3_block_size;
399     compression= cap.compression;
400   }
401 
402   /* Align S3_BLOCK size with table block size */
403   block_size= (block_size/cap.block_size)*cap.block_size;
404 
405   /* Allocate block for data + flag for compress header */
406   if (!(alloc_block= (uchar*) my_malloc(PSI_NOT_INSTRUMENTED,
407                                         block_size+ALIGN_SIZE(1),
408                                         MYF(MY_WME))))
409     goto err;
410   /* Read/write data here, but with prefix space for compression flag */
411   block= alloc_block+ ALIGN_SIZE(1);
412 
413   if (my_pread(file, block, cap.header_size, 0, MYF(MY_WME | MY_FNABP)))
414     goto err;
415 
416   strmov(aws_path_end, "/aria");
417 
418   if (display)
419     printf("Creating aria table information %s\n", aws_path);
420 
421   convert_index_to_s3_format(block, block_size, compression);
422 
423   /*
424     The first page is not compressed as we need it to know if the rest is
425     compressed
426   */
427   if (s3_put_object(s3_client, aws_bucket, aws_path, block, cap.header_size,
428                     0 /* no compression */ ))
429     goto err;
430 
431   file_size= my_seek(file, 0L, MY_SEEK_END, MYF(0));
432 
433   end= strmov(aws_path_end,"/index");
434 
435   if (display)
436     printf("Copying index information %s\n", aws_path);
437 
438   /* The 000000 will be update with block number by fix_suffix() */
439   end= strmov(end, "/000000");
440 
441   error= copy_from_file(s3_client, aws_bucket, aws_path, file, cap.header_size,
442                         file_size, block, block_size, compression, display);
443   file= -1;
444   if (error)
445     goto err;
446 
447   /* Copy data file */
448   fn_format(filename, path, "", ".MAD", MY_REPLACE_EXT);
449   if ((file= my_open(filename,
450                            O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
451                            MYF(MY_WME))) < 0)
452     DBUG_RETURN(1);
453 
454   file_size= my_seek(file, 0L, MY_SEEK_END, MYF(0));
455 
456   end= strmov(aws_path_end, "/data");
457 
458   if (display)
459     printf("Copying data information %s\n", aws_path);
460 
461   /* The 000000 will be update with block number by fix_suffix() */
462   end= strmov(end, "/000000");
463 
464   error= copy_from_file(s3_client, aws_bucket, aws_path, file, 0, file_size,
465                         block, block_size, compression, display);
466   file= -1;
467   if (error)
468     goto err;
469 
470   my_free(alloc_block);
471   DBUG_RETURN(0);
472 
473 err:
474   if (frm_created)
475   {
476     end= strmov(aws_path_end,"/frm");
477     (void) s3_delete_object(s3_client, aws_bucket, aws_path, MYF(ME_NOTE));
478   }
479   if (file >= 0)
480     my_close(file, MYF(0));
481   my_free(alloc_block);
482   DBUG_RETURN(1);
483 }
484 
485 
486 /**
487    Copy file to 'aws_path' in blocks of block_size
488 
489    @return 0   ok
490    @return 1   error. Error message is printed to stderr
491 
492    Notes:
493    file is always closed before return
494 */
495 
copy_to_file(ms3_st * s3_client,const char * aws_bucket,char * aws_path,File file,my_off_t start,my_off_t file_end,my_bool compression,my_bool display)496 static my_bool copy_to_file(ms3_st *s3_client, const char *aws_bucket,
497                             char *aws_path, File file, my_off_t start,
498                             my_off_t file_end, my_bool compression,
499                             my_bool display)
500 {
501   my_off_t pos;
502   char *path_end= strend(aws_path);
503   size_t error;
504   ulong bnr;
505   my_bool print_done= 0;
506   S3_BLOCK block;
507   DBUG_ENTER("copy_to_file");
508   DBUG_PRINT("enter", ("path: %s  start: %llu  end: %llu",
509                        aws_path, (ulonglong) start, (ulonglong) file_end));
510 
511   for (pos= start, bnr=1 ; pos < file_end ; pos+= block.length, bnr++)
512   {
513     fix_suffix(path_end, bnr);
514     if (s3_get_object(s3_client, aws_bucket, aws_path, &block, compression, 1))
515       goto err;
516 
517     error= my_write(file, block.str, block.length, MYF(MY_WME | MY_FNABP));
518     s3_free(&block);
519     if (error == MY_FILE_ERROR)
520       goto err;
521 
522     /* Write up to DISPLAY_WITH number of '.' during copy */
523     if (display &&
524         ((pos + block.length) * DISPLAY_WITH /file_end) >
525         (pos * DISPLAY_WITH/file_end))
526     {
527       fputc('.', stdout); fflush(stdout);
528       print_done= 1;
529     }
530   }
531   if (print_done)
532   {
533     fputc('\n', stdout); fflush(stdout);
534   }
535   my_close(file, MYF(MY_WME));
536   DBUG_RETURN(0);
537 
538 err:
539   my_close(file, MYF(MY_WME));
540   if (print_done)
541   {
542     fputc('\n', stdout); fflush(stdout);
543   }
544   DBUG_RETURN(1);
545 }
546 
547 
548 /**
549    Copy a table from S3 to current directory
550 */
551 
aria_copy_from_s3(ms3_st * s3_client,const char * aws_bucket,const char * path,const char * database,my_bool compression,my_bool force,my_bool display)552 int aria_copy_from_s3(ms3_st *s3_client, const char *aws_bucket,
553                       const char *path, const char *database,
554                       my_bool compression, my_bool force, my_bool display)
555 
556 {
557   MARIA_STATE_INFO state;
558   MY_STAT stat_info;
559   char table_name[FN_REFLEN], aws_path[FN_REFLEN+100];
560   char filename[FN_REFLEN];
561   char *aws_path_end, *end;
562   File file= -1;
563   S3_BLOCK block;
564   my_off_t index_file_size, data_file_size;
565   uint offset;
566   int error;
567   DBUG_ENTER("aria_copy_from_s3");
568 
569   /* Check if index file exists */
570   fn_format(filename, path, "", ".MAI", MY_REPLACE_EXT);
571   if (!force && my_stat(filename, &stat_info, MYF(0)))
572   {
573     my_printf_error(EE_CANTCREATEFILE, "Table %s already exists on disk",
574                     MYF(0), filename);
575     DBUG_RETURN(EE_CANTCREATEFILE);
576   }
577 
578   fn_format(table_name, path, "", "", MY_REPLACE_DIR | MY_REPLACE_EXT);
579   block.str= 0;
580 
581   aws_path_end= strxmov(aws_path, database, "/", table_name, NullS);
582   strmov(aws_path_end, "/aria");
583 
584   if (s3_get_object(s3_client, aws_bucket, aws_path, &block, 0, 0))
585   {
586     my_printf_error(EE_FILENOTFOUND, "File %s/%s doesn't exist in s3", MYF(0),
587                     database,filename);
588     goto err;
589   }
590   if (block.length < MARIA_STATE_INFO_SIZE)
591   {
592     fprintf(stderr, "Wrong block length for first block: %lu\n",
593             (ulong) block.length);
594     goto err_with_free;
595   }
596 
597   if (display)
598     printf("Copying aria table: %s.%s from s3\n", database, table_name);
599 
600   /* For offset positions, check _ma_state_info_readlength() */
601   offset= sizeof(state.header) + 4+ LSN_STORE_SIZE*3 + 8*5;
602   index_file_size= mi_sizekorr(block.str + offset);
603   data_file_size=  mi_sizekorr(block.str + offset+8);
604 
605   if ((file= my_create(filename, 0,
606                        O_WRONLY | O_TRUNC | O_NOFOLLOW, MYF(MY_WME))) < 0)
607     goto err_with_free;
608 
609   convert_index_to_disk_format(block.str);
610 
611   if (my_write(file, block.str, block.length, MYF(MY_WME | MY_FNABP)))
612     goto err_with_free;
613 
614   if (display)
615     printf("Copying index information %s\n", aws_path);
616 
617   end= strmov(aws_path_end,"/index/000000");
618 
619   error= copy_to_file(s3_client, aws_bucket, aws_path, file, block.length,
620                       index_file_size, compression, display);
621   file= -1;
622   if (error)
623     goto err_with_free;
624 
625   /* Copy data file */
626   fn_format(filename, path, "", ".MAD", MY_REPLACE_EXT);
627   if ((file= my_create(filename, 0,
628                        O_WRONLY | O_TRUNC | O_NOFOLLOW, MYF(MY_WME))) < 0)
629     DBUG_RETURN(1);
630 
631   end= strmov(aws_path_end, "/data");
632 
633   if (display)
634     printf("Copying data information %s\n", aws_path);
635 
636   /* The 000000 will be update with block number by fix_suffix() */
637   strmov(end, "/000000");
638 
639   error= copy_to_file(s3_client, aws_bucket, aws_path, file, 0, data_file_size,
640                       compression, display);
641   file= -1;
642   s3_free(&block);
643   block.str= 0;
644   if (error)
645     goto err;
646 
647   /* Copy frm file if it exists */
648   strmov(aws_path_end, "/frm");
649   if (!s3_get_object(s3_client, aws_bucket, aws_path, &block, 0, 0))
650   {
651     fn_format(filename, path, "", ".frm", MY_REPLACE_EXT);
652     if ((file= my_create(filename, 0,
653                          O_WRONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
654                          MYF(0))) >= 0)
655     {
656       if (display)
657         printf("Copying frm file %s\n", filename);
658 
659       convert_frm_to_disk_format(block.str);
660 
661       if (my_write(file, block.str, block.length, MYF(MY_WME | MY_FNABP)))
662         goto err_with_free;
663     }
664     s3_free(&block);
665     my_close(file, MYF(MY_WME));
666     file= -1;
667   }
668 
669   DBUG_RETURN(0);
670 
671 err_with_free:
672   s3_free(&block);
673 err:
674   if (file >= 0)
675     my_close(file, MYF(0));
676   DBUG_RETURN(1);
677 }
678 
679 
680 /**
681    Drop all files related to a table from S3
682 */
683 
aria_delete_from_s3(ms3_st * s3_client,const char * aws_bucket,const char * database,const char * table,my_bool display)684 int aria_delete_from_s3(ms3_st *s3_client, const char *aws_bucket,
685                         const char *database, const char *table,
686                         my_bool display)
687 {
688   ms3_status_st status;
689   char aws_path[FN_REFLEN+100];
690   char *aws_path_end;
691   int error;
692   DBUG_ENTER("aria_delete_from_s3");
693 
694   aws_path_end= strxmov(aws_path, database, "/", table, NullS);
695   strmov(aws_path_end, "/aria");
696 
697   /* Check if either /aria or /frm exists */
698 
699   if (ms3_status(s3_client, aws_bucket, aws_path, &status))
700   {
701     strmov(aws_path_end, "/frm");
702     if (ms3_status(s3_client, aws_bucket, aws_path, &status))
703     {
704       my_printf_error(HA_ERR_NO_SUCH_TABLE,
705                       "Table %s.%s doesn't exist in s3", MYF(0),
706                       database, table);
707       my_errno= HA_ERR_NO_SUCH_TABLE;
708       DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
709     }
710   }
711 
712   if (display)
713     printf("Delete of aria table: %s.%s\n", database, table);
714 
715   strmov(aws_path_end,"/index");
716 
717   if (display)
718     printf("Delete of index information %s\n", aws_path);
719 
720   error= s3_delete_directory(s3_client, aws_bucket, aws_path);
721 
722   strmov(aws_path_end,"/data");
723   if (display)
724     printf("Delete of data information %s\n", aws_path);
725 
726   error|= s3_delete_directory(s3_client, aws_bucket, aws_path);
727 
728   if (display)
729     printf("Delete of base information and frm\n");
730 
731   strmov(aws_path_end,"/aria");
732   if (s3_delete_object(s3_client, aws_bucket, aws_path, MYF(MY_WME)))
733     error= 1;
734 
735   /*
736     Delete .frm last as this is used by discovery to check if a s3 table
737     exists
738   */
739   strmov(aws_path_end,"/frm");
740   /* Ignore error if .frm file doesn't exist */
741   s3_delete_object(s3_client, aws_bucket, aws_path, MYF(ME_NOTE));
742 
743   DBUG_RETURN(error);
744 }
745 
746 
747 /**
748   Rename a table in s3
749 */
750 
aria_rename_s3(ms3_st * s3_client,const char * aws_bucket,const char * from_database,const char * from_table,const char * to_database,const char * to_table,my_bool rename_frm)751 int aria_rename_s3(ms3_st *s3_client, const char *aws_bucket,
752                    const char *from_database, const char *from_table,
753                    const char *to_database, const char *to_table,
754                    my_bool rename_frm)
755 {
756   ms3_status_st status;
757   char to_aws_path[FN_REFLEN+100], from_aws_path[FN_REFLEN+100];
758   char *to_aws_path_end, *from_aws_path_end;
759   int error;
760   DBUG_ENTER("aria_rename_s3");
761 
762   from_aws_path_end= strxmov(from_aws_path, from_database, "/", from_table,
763                              NullS);
764   to_aws_path_end= strxmov(to_aws_path, to_database, "/", to_table, NullS);
765   strmov(from_aws_path_end, "/aria");
766 
767   if (ms3_status(s3_client, aws_bucket, from_aws_path, &status))
768   {
769     my_printf_error(HA_ERR_NO_SUCH_TABLE,
770                     "Table %s.%s doesn't exist in s3", MYF(0), from_database,
771                     from_table);
772     my_errno= HA_ERR_NO_SUCH_TABLE;
773     DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
774   }
775 
776   strmov(from_aws_path_end,"/index");
777   strmov(to_aws_path_end,"/index");
778 
779   error= s3_rename_directory(s3_client, aws_bucket, from_aws_path, to_aws_path,
780                              MYF(MY_WME));
781 
782   strmov(from_aws_path_end,"/data");
783   strmov(to_aws_path_end,"/data");
784 
785   error|= s3_rename_directory(s3_client, aws_bucket, from_aws_path,
786                               to_aws_path, MYF(MY_WME));
787 
788   if (rename_frm) {
789     strmov(from_aws_path_end, "/frm");
790     strmov(to_aws_path_end, "/frm");
791 
792     s3_rename_object(s3_client, aws_bucket, from_aws_path, to_aws_path,
793                      MYF(MY_WME));
794   }
795 
796   strmov(from_aws_path_end,"/aria");
797   strmov(to_aws_path_end,"/aria");
798   if (s3_rename_object(s3_client, aws_bucket, from_aws_path, to_aws_path,
799                        MYF(MY_WME)))
800     error= 1;
801   DBUG_RETURN(error);
802 }
803 
804 /**
805    Copy all partition files related to a table from S3 (.frm and .par)
806 
807    @param s3_client   s3 client connection
808    @param aws_bucket  bucket to use
809    @param path        The path to the partitioned table files (no extension)
810    @param old_path    In some cases the partioned files are not yet renamed.
811                       This points to the temporary files that will later
812                       be renamed to the partioned table
813    @param database    Database for the partitioned table
814    @param database    table name for the partitioned table
815 */
816 
partition_copy_to_s3(ms3_st * s3_client,const char * aws_bucket,const char * path,const char * old_path,const char * database,const char * table_name)817 int partition_copy_to_s3(ms3_st *s3_client, const char *aws_bucket,
818                          const char *path, const char *old_path,
819                          const char *database, const char *table_name)
820 {
821   char aws_path[FN_REFLEN+100];
822   char filename[FN_REFLEN];
823   char *aws_path_end;
824   uchar *alloc_block= 0;
825   ms3_status_st status;
826   size_t frm_length;
827   int error;
828   DBUG_ENTER("partition_copy_to_s3");
829   DBUG_PRINT("enter",("from: %s  database: %s  table: %s",
830                       path, database, table_name));
831 
832   if (!old_path)
833     old_path= path;
834 
835   aws_path_end= strxmov(aws_path, database, "/", table_name, "/", NullS);
836   strmov(aws_path_end, "frm");
837   fn_format(filename, old_path, "", ".frm", MY_REPLACE_EXT);
838 
839   /* Just to be safe, delete any conflicting object */
840   if (!ms3_status(s3_client, aws_bucket, aws_path, &status))
841   {
842     if ((error= s3_delete_object(s3_client, aws_bucket, aws_path,
843                                  MYF(ME_FATAL))))
844       DBUG_RETURN(error);
845   }
846   if ((error= s3_read_file_from_disk(filename, &alloc_block, &frm_length, 0)))
847   {
848     /*
849       In case of ADD PARTITION PARTITON the .frm file is already renamed.
850       Copy the renamed file if it exists.
851     */
852     fn_format(filename, path, "", ".frm", MY_REPLACE_EXT);
853     if ((error= s3_read_file_from_disk(filename, &alloc_block, &frm_length,
854                                        1)))
855       goto err;
856   }
857   if ((error= s3_put_object(s3_client, aws_bucket, aws_path, alloc_block,
858                             frm_length, 0)))
859     goto err;
860 
861   /*
862     Note that because ha_partiton::rename_table() is called before
863     this function, the .par table already has it's final name!
864   */
865   fn_format(filename, path, "", ".par", MY_REPLACE_EXT);
866   strmov(aws_path_end, "par");
867   if (!ms3_status(s3_client, aws_bucket, aws_path, &status))
868   {
869     if ((error= s3_delete_object(s3_client, aws_bucket, aws_path,
870                                  MYF(ME_FATAL))))
871       goto err;
872   }
873 
874   my_free(alloc_block);
875   alloc_block= 0;
876   if ((error=s3_read_file_from_disk(filename, &alloc_block, &frm_length, 1)))
877     goto err;
878   if ((error= s3_put_object(s3_client, aws_bucket, aws_path, alloc_block,
879                             frm_length, 0)))
880   {
881     /* Delete the .frm file created above */
882     strmov(aws_path_end, "frm");
883     (void) s3_delete_object(s3_client, aws_bucket, aws_path,
884                             MYF(ME_FATAL));
885     goto err;
886   }
887   error= 0;
888 
889 err:
890   my_free(alloc_block);
891   DBUG_RETURN(error);
892 }
893 
894 
895 /**
896    Drop all partition files related to a table from S3
897 */
898 
partition_delete_from_s3(ms3_st * s3_client,const char * aws_bucket,const char * database,const char * table,myf error_flags)899 int partition_delete_from_s3(ms3_st *s3_client, const char *aws_bucket,
900                              const char *database, const char *table,
901                              myf error_flags)
902 {
903   char aws_path[FN_REFLEN+100];
904   char *aws_path_end;
905   int error=0, res;
906   DBUG_ENTER("partition_delete_from_s3");
907 
908   aws_path_end= strxmov(aws_path, database, "/", table, NullS);
909   strmov(aws_path_end, "/par");
910 
911   if ((res= s3_delete_object(s3_client, aws_bucket, aws_path, error_flags)))
912     error= res;
913   /*
914     Delete .frm last as this is used by discovery to check if a s3 table
915     exists
916   */
917   strmov(aws_path_end, "/frm");
918   if ((res= s3_delete_object(s3_client, aws_bucket, aws_path, error_flags)))
919     error= res;
920 
921   DBUG_RETURN(error);
922 }
923 
924 /******************************************************************************
925  Low level functions interfacing with libmarias3
926 ******************************************************************************/
927 
928 /**
929    Create an object for index or data information
930 
931    Note that if compression is used, the data may be overwritten and
932    there must be COMPRESS_HEADER length of free space before the data!
933 
934 */
935 
s3_put_object(ms3_st * s3_client,const char * aws_bucket,const char * name,uchar * data,size_t length,my_bool compression)936 int s3_put_object(ms3_st *s3_client, const char *aws_bucket,
937                    const char *name, uchar *data, size_t length,
938                    my_bool compression)
939 {
940   uint8_t error;
941   const char *errmsg;
942   DBUG_ENTER("s3_put_object");
943   DBUG_PRINT("enter", ("name: %s", name));
944 
945   if (compression)
946   {
947     size_t comp_len;
948 
949     data[-COMPRESS_HEADER]= 0;                  // No compression
950     if (!my_compress(data, &length, &comp_len))
951       data[-COMPRESS_HEADER]= 1;                // Compressed package
952     data-=   COMPRESS_HEADER;
953     length+= COMPRESS_HEADER;
954     int3store(data+1, comp_len);               // Original length or 0
955   }
956 
957   if (likely(!(error= ms3_put(s3_client, aws_bucket, name, data, length))))
958     DBUG_RETURN(0);
959 
960   if (!(errmsg= ms3_server_error(s3_client)))
961     errmsg= ms3_error(error);
962 
963   my_printf_error(EE_WRITE, "Got error from put_object(%s): %d %s", MYF(0),
964                   name, error, errmsg);
965   DBUG_RETURN(EE_WRITE);
966 }
967 
968 
969 /**
970    Read an object for index or data information
971 
972    @param print_error 0  Don't print error
973    @param print_error 1  Print error that object doesn't exists
974    @param print_error 2  Print error that table doesn't exists
975 */
976 
s3_get_object(ms3_st * s3_client,const char * aws_bucket,const char * name,S3_BLOCK * block,my_bool compression,int print_error)977 int s3_get_object(ms3_st *s3_client, const char *aws_bucket,
978                   const char *name, S3_BLOCK *block,
979                   my_bool compression, int print_error)
980 {
981   uint8_t error;
982   int result= 0;
983   uchar *data;
984   DBUG_ENTER("s3_get_object");
985   DBUG_PRINT("enter", ("name: %s  compression: %d", name, compression));
986 
987   block->str= block->alloc_ptr= 0;
988   if (likely(!(error= ms3_get(s3_client, aws_bucket, name,
989                               (uint8_t**) &block->alloc_ptr,
990                               &block->length))))
991   {
992     block->str= block->alloc_ptr;
993     if (compression)
994     {
995       ulong length;
996 
997       /* If not compressed */
998       if (!block->str[0])
999       {
1000         block->length-= COMPRESS_HEADER;
1001         block->str+=    COMPRESS_HEADER;
1002 
1003         /* Simple check to ensure that it's a correct block */
1004         if (block->length % 1024)
1005         {
1006           s3_free(block);
1007           my_printf_error(HA_ERR_NOT_A_TABLE,
1008                           "Block '%s' is not compressed", MYF(0), name);
1009           DBUG_RETURN(HA_ERR_NOT_A_TABLE);
1010         }
1011         DBUG_RETURN(0);
1012       }
1013 
1014       if (((uchar*)block->str)[0] > 1)
1015       {
1016         s3_free(block);
1017         my_printf_error(HA_ERR_NOT_A_TABLE,
1018                         "Block '%s' is not compressed", MYF(0), name);
1019         DBUG_RETURN(HA_ERR_NOT_A_TABLE);
1020       }
1021 
1022       length= uint3korr(block->str+1);
1023 
1024       if (!(data= (uchar*) my_malloc(PSI_NOT_INSTRUMENTED,
1025                                      length, MYF(MY_WME | MY_THREAD_SPECIFIC))))
1026       {
1027         s3_free(block);
1028         DBUG_RETURN(EE_OUTOFMEMORY);
1029       }
1030       if (uncompress(data, &length, block->str + COMPRESS_HEADER,
1031                      block->length - COMPRESS_HEADER))
1032       {
1033         my_printf_error(ER_NET_UNCOMPRESS_ERROR,
1034                         "Got error uncompressing s3 packet", MYF(0));
1035         s3_free(block);
1036         my_free(data);
1037         DBUG_RETURN(ER_NET_UNCOMPRESS_ERROR);
1038       }
1039       s3_free(block);
1040       block->str= block->alloc_ptr= data;
1041       block->length= length;
1042     }
1043     DBUG_RETURN(0);
1044   }
1045 
1046   if (error == 9)
1047   {
1048     result= my_errno= (print_error == 1 ? EE_FILENOTFOUND :
1049                        HA_ERR_NO_SUCH_TABLE);
1050     if (print_error)
1051       my_printf_error(my_errno, "Expected object '%s' didn't exist",
1052                       MYF(0), name);
1053   }
1054   else
1055   {
1056     result= my_errno= EE_READ;
1057     if (print_error)
1058     {
1059       const char *errmsg;
1060       if (!(errmsg= ms3_server_error(s3_client)))
1061         errmsg= ms3_error(error);
1062 
1063       my_printf_error(EE_READ, "Got error from get_object(%s): %d %s", MYF(0),
1064                       name, error, errmsg);
1065     }
1066   }
1067   s3_free(block);
1068   DBUG_RETURN(result);
1069 }
1070 
1071 
s3_delete_object(ms3_st * s3_client,const char * aws_bucket,const char * name,myf error_flags)1072 int s3_delete_object(ms3_st *s3_client, const char *aws_bucket,
1073                          const char *name, myf error_flags)
1074 {
1075   uint8_t error;
1076   int result= 0;
1077   DBUG_ENTER("s3_delete_object");
1078   DBUG_PRINT("enter", ("name: %s", name));
1079 
1080   if (likely(!(error= ms3_delete(s3_client, aws_bucket, name))))
1081     DBUG_RETURN(0);
1082 
1083   if (error_flags)
1084   {
1085     error_flags&= ~MY_WME;
1086     if (error == 9)
1087       my_printf_error(result= EE_FILENOTFOUND,
1088                       "Expected object '%s' didn't exist",
1089                       error_flags, name);
1090     else
1091     {
1092       const char *errmsg;
1093       if (!(errmsg= ms3_server_error(s3_client)))
1094         errmsg= ms3_error(error);
1095 
1096       my_printf_error(result= EE_READ,
1097                       "Got error from delete_object(%s): %d %s",
1098                       error_flags, name, error, errmsg);
1099     }
1100   }
1101   DBUG_RETURN(result);
1102 }
1103 
1104 
1105 /*
1106   Drop all files in a 'directory' in s3
1107 */
1108 
s3_delete_directory(ms3_st * s3_client,const char * aws_bucket,const char * path)1109 int s3_delete_directory(ms3_st *s3_client, const char *aws_bucket,
1110                         const char *path)
1111 {
1112   ms3_list_st *list, *org_list= 0;
1113   my_bool error;
1114   DBUG_ENTER("delete_directory");
1115   DBUG_PRINT("enter", ("path: %s", path));
1116 
1117   if ((error= ms3_list(s3_client, aws_bucket, path, &org_list)))
1118   {
1119     const char *errmsg;
1120     if (!(errmsg= ms3_server_error(s3_client)))
1121       errmsg= ms3_error(error);
1122 
1123     my_printf_error(EE_FILENOTFOUND,
1124                     "Can't get list of files from %s. Error: %d %s", MYF(0),
1125                     path, error, errmsg);
1126     DBUG_RETURN(EE_FILENOTFOUND);
1127   }
1128 
1129   for (list= org_list ; list ; list= list->next)
1130     if (s3_delete_object(s3_client, aws_bucket, list->key, MYF(MY_WME)))
1131       error= 1;
1132   if (org_list)
1133     ms3_list_free(org_list);
1134   DBUG_RETURN(error);
1135 }
1136 
1137 
s3_rename_object(ms3_st * s3_client,const char * aws_bucket,const char * from_name,const char * to_name,myf error_flags)1138 my_bool s3_rename_object(ms3_st *s3_client, const char *aws_bucket,
1139                          const char *from_name, const char *to_name,
1140                          myf error_flags)
1141 {
1142   uint8_t error;
1143   DBUG_ENTER("s3_rename_object");
1144   DBUG_PRINT("enter", ("from: %s  to: %s", from_name, to_name));
1145 
1146   if (likely(!(error= ms3_move(s3_client,
1147                                aws_bucket, from_name,
1148                                aws_bucket, to_name))))
1149     DBUG_RETURN(FALSE);
1150 
1151   if (error_flags)
1152   {
1153     error_flags&= ~MY_WME;
1154     if (error == 9)
1155     {
1156       my_printf_error(EE_FILENOTFOUND, "Expected object '%s' didn't exist",
1157                       error_flags, from_name);
1158     }
1159     else
1160     {
1161       const char *errmsg;
1162       if (!(errmsg= ms3_server_error(s3_client)))
1163         errmsg= ms3_error(error);
1164 
1165       my_printf_error(EE_READ, "Got error from move_object(%s -> %s): %d %",
1166                       error_flags,
1167                       from_name, to_name, error, errmsg);
1168     }
1169   }
1170   DBUG_RETURN(TRUE);
1171 }
1172 
1173 
s3_rename_directory(ms3_st * s3_client,const char * aws_bucket,const char * from_name,const char * to_name,myf error_flags)1174 int s3_rename_directory(ms3_st *s3_client, const char *aws_bucket,
1175                         const char *from_name, const char *to_name,
1176                         myf error_flags)
1177 {
1178   ms3_list_st *list, *org_list= 0;
1179   my_bool error= 0;
1180   char name[AWS_PATH_LENGTH], *end;
1181   DBUG_ENTER("s3_delete_directory");
1182 
1183   if ((error= ms3_list(s3_client, aws_bucket, from_name, &org_list)))
1184   {
1185     const char *errmsg;
1186     if (!(errmsg= ms3_server_error(s3_client)))
1187       errmsg= ms3_error(error);
1188 
1189     my_printf_error(EE_FILENOTFOUND,
1190                     "Can't get list of files from %s. Error: %d %s",
1191                     MYF(error_flags & ~MY_WME),
1192                     from_name, error, errmsg);
1193     DBUG_RETURN(EE_FILENOTFOUND);
1194   }
1195 
1196   end= strmov(name, to_name);
1197   for (list= org_list ; list ; list= list->next)
1198   {
1199     const char *sep= strrchr(list->key, '/');
1200     if (sep)                                    /* Safety */
1201     {
1202       strmake(end, sep, (sizeof(name) - (end-name) - 1));
1203       if (s3_rename_object(s3_client, aws_bucket, list->key, name,
1204                            error_flags))
1205         error= 1;
1206     }
1207   }
1208   if (org_list)
1209     ms3_list_free(org_list);
1210   DBUG_RETURN(error);
1211 }
1212 
1213 
1214 /******************************************************************************
1215  Converting index and frm files to from S3 storage engine
1216 ******************************************************************************/
1217 
1218 /**
1219   Change index information to be of type s3
1220 
1221   @param header      Copy of header in index file
1222   @param block_size  S3 block size
1223   @param compression Compression algorithm to use
1224 
1225   The position are from _ma_base_info_write()
1226 */
1227 
convert_index_to_s3_format(uchar * header,ulong block_size,int compression)1228 static void convert_index_to_s3_format(uchar *header, ulong block_size,
1229                                        int compression)
1230 {
1231   MARIA_STATE_INFO state;
1232   uchar *base_pos;
1233   uint  base_offset;
1234 
1235   memcpy(state.header.file_version, header, sizeof(state.header));
1236   base_offset= mi_uint2korr(state.header.base_pos);
1237   base_pos= header + base_offset;
1238 
1239   base_pos[107]= (uchar) compression;
1240   mi_int3store(base_pos+119, block_size);
1241 }
1242 
1243 
1244 /**
1245    Change index information to be a normal disk based table
1246 */
1247 
convert_index_to_disk_format(uchar * header)1248 static void convert_index_to_disk_format(uchar *header)
1249 {
1250   MARIA_STATE_INFO state;
1251   uchar *base_pos;
1252   uint  base_offset;
1253 
1254   memcpy(state.header.file_version, header, sizeof(state.header));
1255   base_offset= mi_uint2korr(state.header.base_pos);
1256   base_pos= header + base_offset;
1257 
1258   base_pos[107]= 0;
1259   mi_int3store(base_pos+119, 0);
1260 }
1261 
1262 /**
1263   Change storage engine in the .frm file from Aria to s3
1264 
1265   For information about engine types, see legacy_db_type
1266 */
1267 
convert_frm_to_s3_format(uchar * header)1268 static void convert_frm_to_s3_format(uchar *header)
1269 {
1270   DBUG_ASSERT(header[3] == 42 || header[3] == 41); /* Aria or S3 */
1271   header[3]= 41;                                   /* S3 */
1272 }
1273 
1274 /**
1275   Change storage engine in the .frm file from S3 to Aria
1276 
1277   For information about engine types, see legacy_db_type
1278 */
1279 
convert_frm_to_disk_format(uchar * header)1280 static void convert_frm_to_disk_format(uchar *header)
1281 {
1282   DBUG_ASSERT(header[3] == 41);                 /* S3 */
1283   header[3]= 42;                                /* Aria */
1284 }
1285 
1286 
1287 /******************************************************************************
1288  Helper functions
1289 ******************************************************************************/
1290 
1291 /**
1292   Set database and table name from path
1293 
1294   s3->database and s3->table_name will be pointed into path
1295   Note that s3->database will not be null terminated!
1296 */
1297 
set_database_and_table_from_path(S3_INFO * s3,const char * path)1298 my_bool set_database_and_table_from_path(S3_INFO *s3, const char *path)
1299 {
1300   size_t org_length= dirname_length(path);
1301   size_t length= 0;
1302 
1303   if (!org_length)
1304     return 1;
1305 
1306   s3->table.str= path+org_length;
1307   s3->table.length= strlen(s3->table.str);
1308   for (length= --org_length; length > 0 ; length --)
1309   {
1310     if (path[length-1] == FN_LIBCHAR || path[length-1] == '/')
1311       break;
1312 #ifdef FN_DEVCHAR
1313     if (path[length-1] == FN_DECVHAR)
1314       break;
1315 #endif
1316   }
1317   if (length &&
1318       (path[length] != FN_CURLIB || org_length - length != 1))
1319   {
1320     s3->database.str= path + length;
1321     s3->database.length= org_length - length;
1322     return 0;
1323   }
1324   return 1;                                     /* Can't find database */
1325 }
1326 
1327 
1328 /**
1329    Read frm from the disk
1330 */
1331 
s3_read_file_from_disk(const char * filename,uchar ** to,size_t * to_size,my_bool print_error)1332 static int s3_read_file_from_disk(const char *filename, uchar **to,
1333                                   size_t *to_size, my_bool print_error)
1334 {
1335   File file;
1336   uchar *alloc_block;
1337   size_t file_size;
1338   int error;
1339 
1340   *to= 0;
1341   if ((file= my_open(filename,
1342                      O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC,
1343                      MYF(print_error ? MY_WME: 0))) < 0)
1344     return(my_errno);
1345 
1346   file_size= (size_t) my_seek(file, 0L, MY_SEEK_END, MYF(0));
1347   if (!(alloc_block= my_malloc(PSI_NOT_INSTRUMENTED, file_size, MYF(MY_WME))))
1348     goto err;
1349 
1350   if (my_pread(file, alloc_block, file_size, 0, MYF(MY_WME | MY_FNABP)))
1351     goto err;
1352 
1353   *to=      alloc_block;
1354   *to_size= file_size;
1355   my_close(file, MYF(0));
1356   return 0;
1357 
1358 err:
1359   error= my_errno;
1360   my_free(alloc_block);
1361   my_close(file, MYF(0));
1362   return error;
1363 }
1364 
1365 
1366 /**
1367    Get .frm or par from S3
1368 
1369    @return 0 ok
1370    @return 1 error
1371 */
1372 
s3_get_def(ms3_st * s3_client,S3_INFO * s3_info,S3_BLOCK * block,const char * ext)1373 my_bool s3_get_def(ms3_st *s3_client, S3_INFO *s3_info, S3_BLOCK *block,
1374                    const char *ext)
1375 {
1376   char aws_path[AWS_PATH_LENGTH];
1377 
1378   strxnmov(aws_path, sizeof(aws_path)-1, s3_info->database.str, "/",
1379            s3_info->table.str, "/", ext, NullS);
1380 
1381   return s3_get_object(s3_client, s3_info->bucket.str, aws_path, block,
1382                        0, 0);
1383 }
1384 
1385 /**
1386    Check if .frm exits in S3
1387 
1388    @return 0 frm exists
1389    @return 1 error
1390 */
1391 
s3_frm_exists(ms3_st * s3_client,S3_INFO * s3_info)1392 my_bool s3_frm_exists(ms3_st *s3_client, S3_INFO *s3_info)
1393 {
1394   char aws_path[AWS_PATH_LENGTH];
1395   ms3_status_st status;
1396 
1397   strxnmov(aws_path, sizeof(aws_path)-1, s3_info->database.str, "/",
1398            s3_info->table.str, "/frm", NullS);
1399 
1400   return ms3_status(s3_client, s3_info->bucket.str, aws_path, &status);
1401 }
1402 
1403 
1404 /**
1405    Get version from frm file
1406 
1407    @param out        Store the table_version_here. It's of size MY_UUID_SIZE
1408    @param frm_image  Frm image
1409    @param frm_length size of image
1410 
1411    @return 0  Was able to read table version
1412    @return 1  Wrong information in frm file
1413 */
1414 
1415 #define FRM_HEADER_SIZE 64
1416 #define EXTRA2_TABLEDEF_VERSION 0
1417 
is_binary_frm_header(const uchar * head)1418 static inline my_bool is_binary_frm_header(const uchar *head)
1419 {
1420   return head[0] == 254
1421       && head[1] == 1
1422       && head[2] >= FRM_VER
1423       && head[2] <= FRM_VER_CURRENT;
1424 }
1425 
get_tabledef_version_from_frm(char * out,const uchar * frm_image,size_t frm_length)1426 static my_bool get_tabledef_version_from_frm(char *out, const uchar *frm_image,
1427                                              size_t frm_length)
1428 {
1429   uint segment_len;
1430   const uchar *extra, *extra_end;
1431   if (!is_binary_frm_header(frm_image) || frm_length <= FRM_HEADER_SIZE)
1432     return 1;
1433 
1434   /* Length of the MariaDB extra2 segment in the form file. */
1435   segment_len= uint2korr(frm_image + 4);
1436   if (frm_length < FRM_HEADER_SIZE + segment_len)
1437     return 1;
1438 
1439   extra= frm_image + FRM_HEADER_SIZE;
1440   if (*extra == '/')   // old frm had '/' there
1441     return 1;
1442 
1443   extra_end= extra + segment_len;
1444   while (extra + 4 < extra_end)
1445   {
1446     uchar type= *extra++;
1447     size_t length= *extra++;
1448     if (!length)
1449     {
1450       length= uint2korr(extra);
1451       extra+= 2;
1452       if (length < 256)
1453         return 1;                               /* Something is wrong */
1454     }
1455     if (extra + length > extra_end)
1456       return 1;
1457     if (type == EXTRA2_TABLEDEF_VERSION)
1458     {
1459       if (length != MY_UUID_SIZE)
1460         return 1;
1461       memcpy(out, extra, length);
1462       return 0;                                 /* Found it */
1463     }
1464     extra+= length;
1465   }
1466   return 1;
1467 }
1468 
1469 
1470 /**
1471    Check if version in frm file matches what the server expects
1472 
1473    @return 0 table definitions matches
1474    @return 1 table definitions doesn't match
1475    @return 2 Can't find the frm version
1476    @return 3 Can't read the frm version
1477 */
1478 
s3_check_frm_version(ms3_st * s3_client,S3_INFO * s3_info)1479 int s3_check_frm_version(ms3_st *s3_client, S3_INFO *s3_info)
1480 {
1481   my_bool res= 0;
1482   char aws_path[AWS_PATH_LENGTH];
1483   char uuid[MY_UUID_SIZE];
1484   S3_BLOCK block;
1485   DBUG_ENTER("s3_check_frm_version");
1486 
1487   strxnmov(aws_path, sizeof(aws_path)-1, s3_info->database.str, "/",
1488            s3_info->base_table.str, "/frm", NullS);
1489 
1490   if (s3_get_object(s3_client, s3_info->bucket.str, aws_path, &block, 0, 0))
1491   {
1492     DBUG_PRINT("exit", ("No object found"));
1493     DBUG_RETURN(2);                    /* Ignore check, use old frm */
1494   }
1495 
1496   if (get_tabledef_version_from_frm(uuid, (uchar*) block.str, block.length) ||
1497       s3_info->tabledef_version.length != MY_UUID_SIZE)
1498   {
1499     s3_free(&block);
1500     DBUG_PRINT("error", ("Wrong definition"));
1501     DBUG_RETURN(3);                                   /* Wrong definition */
1502   }
1503   /* res is set to 1 if versions numbers doesn't match */
1504   res= bcmp(s3_info->tabledef_version.str, uuid, MY_UUID_SIZE) != 0;
1505   s3_free(&block);
1506   if (res)
1507     DBUG_PRINT("error", ("Wrong table version"));
1508   else
1509     DBUG_PRINT("exit", ("Version strings matches"));
1510   DBUG_RETURN(res);
1511 }
1512 
1513 
1514 /******************************************************************************
1515  Reading blocks from index or data from S3
1516 ******************************************************************************/
1517 
1518 /*
1519   Read the index header (first page) from the index file
1520 
1521   In case of error, my_error() is called
1522 */
1523 
read_index_header(ms3_st * client,S3_INFO * s3,S3_BLOCK * block)1524 my_bool read_index_header(ms3_st *client, S3_INFO *s3, S3_BLOCK *block)
1525 {
1526   char aws_path[AWS_PATH_LENGTH];
1527   DBUG_ENTER("read_index_header");
1528   strxnmov(aws_path, sizeof(aws_path)-1, s3->database.str, "/", s3->table.str,
1529            "/aria", NullS);
1530   DBUG_RETURN(s3_get_object(client, s3->bucket.str, aws_path, block, 0, 2));
1531 }
1532 
1533 
1534 #ifdef FOR_FUTURE_IF_NEEDED_FOR_DEBUGGING_WITHOUT_S3
1535 /**
1536    Read a big block from disk
1537 */
1538 
s3_block_read(struct st_pagecache * pagecache,PAGECACHE_IO_HOOK_ARGS * args,struct st_pagecache_file * file,LEX_STRING * data)1539 my_bool s3_block_read(struct st_pagecache *pagecache,
1540                              PAGECACHE_IO_HOOK_ARGS *args,
1541                              struct st_pagecache_file *file,
1542                              LEX_STRING *data)
1543 {
1544   MARIA_SHARE *share= (MARIA_SHARE*) file->callback_data;
1545   my_bool datafile= file != &share->kfile;
1546 
1547   DBUG_ASSERT(file->big_block_size > 0);
1548   DBUG_ASSERT(((((my_off_t) args->pageno - file->head_blocks) <<
1549                 pagecache->shift) %
1550                file->big_block_size) == 0);
1551 
1552   if (!(data->str= (char *) my_malloc(file->big_block_size, MYF(MY_WME))))
1553     return TRUE;
1554 
1555   data->length= mysql_file_pread(file->file,
1556                                  (unsigned char *)data->str,
1557                                  file->big_block_size,
1558                                  ((my_off_t) args->pageno << pagecache->shift),
1559                                  MYF(MY_WME));
1560   if (data->length == 0 || data->length == MY_FILE_ERROR)
1561   {
1562     if (data->length == 0)
1563     {
1564       LEX_STRING *file_name= (datafile ?
1565                               &share->data_file_name :
1566                               &share->index_file_name);
1567       my_error(EE_EOFERR, MYF(0), file_name->str, my_errno);
1568     }
1569     my_free(data->str);
1570     data->length= 0;
1571     data->str= 0;
1572     return TRUE;
1573   }
1574   return FALSE;
1575 }
1576 #endif
1577 
1578 
1579 /**
1580    Read a block from S3 to page cache
1581 */
1582 
s3_block_read(struct st_pagecache * pagecache,PAGECACHE_IO_HOOK_ARGS * args,struct st_pagecache_file * file,S3_BLOCK * block)1583 my_bool s3_block_read(struct st_pagecache *pagecache,
1584                       PAGECACHE_IO_HOOK_ARGS *args,
1585                       struct st_pagecache_file *file,
1586                       S3_BLOCK *block)
1587 {
1588   char aws_path[AWS_PATH_LENGTH];
1589   MARIA_SHARE *share= (MARIA_SHARE*) file->callback_data;
1590   my_bool datafile= file->file != share->kfile.file;
1591   MARIA_HA *info= (MARIA_HA*) my_thread_var->keycache_file;
1592   ms3_st *client= info->s3;
1593   const char *path_suffix= datafile ? "/data/" : "/index/";
1594   char *end;
1595   S3_INFO *s3= share->s3_path;
1596   ulong block_number;
1597   DBUG_ENTER("s3_block_read");
1598 
1599   DBUG_ASSERT(file->big_block_size > 0);
1600   DBUG_ASSERT(((((my_off_t) args->pageno - file->head_blocks) <<
1601                 pagecache->shift) %
1602                file->big_block_size) == 0);
1603 
1604   block_number= (((args->pageno - file->head_blocks) << pagecache->shift) /
1605                  file->big_block_size) + 1;
1606 
1607   end= strxnmov(aws_path, sizeof(aws_path)-12, s3->database.str, "/",
1608                 s3->table.str, path_suffix, "000000", NullS);
1609   fix_suffix(end, block_number);
1610 
1611   DBUG_RETURN(s3_get_object(client, s3->bucket.str, aws_path, block,
1612                             share->base.compression_algorithm, 1));
1613 }
1614 
1615 /*
1616   Start file numbers from 1000 to more easily find bugs when the file number
1617   could be mistaken for a real file
1618 */
1619 static volatile int32 unique_file_number= 1000;
1620 
s3_unique_file_number()1621 int32 s3_unique_file_number()
1622 {
1623   return my_atomic_add32_explicit(&unique_file_number, 1,
1624                                   MY_MEMORY_ORDER_RELAXED);
1625 }
1626