1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
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, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
22 
23 /* open a MyISAM MERGE table */
24 
25 #include "myrg_def.h"
26 #include <stddef.h>
27 #include <errno.h>
28 
29 /*
30 	open a MyISAM MERGE table
31 	if handle_locking is 0 then exit with error if some table is locked
32 	if handle_locking is 1 then wait if table is locked
33 
34         NOTE: This function is not used in the MySQL server. It is for
35         MERGE use independent from MySQL. Currently there is some code
36         duplication between myrg_open() and myrg_parent_open() +
37         myrg_attach_children(). Please duplicate changes in these
38         functions or make common sub-functions.
39 */
40 
myrg_open(const char * name,int mode,int handle_locking)41 MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
42 {
43   int save_errno,errpos=0;
44   uint files= 0, i, dir_length, length, key_parts= 0, min_keys= 0;
45   ulonglong file_offset=0;
46   char name_buff[FN_REFLEN*2],buff[FN_REFLEN],*end;
47   MYRG_INFO *m_info=0;
48   File fd;
49   IO_CACHE file;
50   MI_INFO *isam=0;
51   uint found_merge_insert_method= 0;
52   size_t name_buff_length;
53   my_bool bad_children= FALSE;
54   DBUG_ENTER("myrg_open");
55 
56   memset(&file, 0, sizeof(file));
57   if ((fd= mysql_file_open(rg_key_file_MRG,
58                            fn_format(name_buff, name, "", MYRG_NAME_EXT,
59                                      MY_UNPACK_FILENAME|MY_APPEND_EXT),
60                            O_RDONLY | O_SHARE, MYF(0))) < 0)
61     goto err;
62   errpos=1;
63   if (init_io_cache(&file, fd, 4*IO_SIZE, READ_CACHE, 0, 0,
64 		    MYF(MY_WME | MY_NABP)))
65     goto err;
66   errpos=2;
67   dir_length=dirname_part(name_buff, name, &name_buff_length);
68   while ((length=my_b_gets(&file,buff,FN_REFLEN-1)))
69   {
70     if ((end=buff+length)[-1] == '\n')
71       end[-1]='\0';
72     if (buff[0] && buff[0] != '#')
73       files++;
74   }
75 
76   my_b_seek(&file, 0);
77   while ((length=my_b_gets(&file,buff,FN_REFLEN-1)))
78   {
79     if ((end=buff+length)[-1] == '\n')
80       *--end='\0';
81     if (!buff[0])
82       continue;		/* Skip empty lines */
83     if (buff[0] == '#')
84     {
85       if (!strncmp(buff+1,"INSERT_METHOD=",14))
86       {			/* Lookup insert method */
87 	int tmp= find_type(buff + 15, &merge_insert_method, FIND_TYPE_BASIC);
88 	found_merge_insert_method = (uint) (tmp >= 0 ? tmp : 0);
89       }
90       continue;		/* Skip comments */
91     }
92 
93     if (!has_path(buff))
94     {
95       (void) strmake(name_buff+dir_length,buff,
96                    sizeof(name_buff)-1-dir_length);
97       (void) cleanup_dirname(buff,name_buff);
98     }
99     else
100       fn_format(buff, buff, "", "", 0);
101     if (!(isam=mi_open(buff,mode,(handle_locking?HA_OPEN_WAIT_IF_LOCKED:0))))
102     {
103       if (handle_locking & HA_OPEN_FOR_REPAIR)
104       {
105         myrg_print_wrong_table(buff);
106         bad_children= TRUE;
107         continue;
108       }
109       goto bad_children;
110     }
111     if (!m_info)                                /* First file */
112     {
113       key_parts=isam->s->base.key_parts;
114       if (!(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
115                                            sizeof(MYRG_INFO) +
116                                            files*sizeof(MYRG_TABLE) +
117                                            key_parts*sizeof(long),
118                                            MYF(MY_WME|MY_ZEROFILL))))
119         goto err;
120       assert(files);
121       m_info->open_tables=(MYRG_TABLE *) (m_info+1);
122       m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
123       m_info->tables= files;
124       files= 0;
125       m_info->reclength=isam->s->base.reclength;
126       min_keys= isam->s->base.keys;
127       errpos=3;
128     }
129     m_info->open_tables[files].table= isam;
130     m_info->open_tables[files].file_offset=(my_off_t) file_offset;
131     file_offset+=isam->state->data_file_length;
132     files++;
133     if (m_info->reclength != isam->s->base.reclength)
134     {
135       if (handle_locking & HA_OPEN_FOR_REPAIR)
136       {
137         myrg_print_wrong_table(buff);
138         bad_children= TRUE;
139         continue;
140       }
141       goto bad_children;
142     }
143     m_info->options|= isam->s->options;
144     m_info->records+= isam->state->records;
145     m_info->del+= isam->state->del;
146     m_info->data_file_length+= isam->state->data_file_length;
147     if (min_keys > isam->s->base.keys)
148       min_keys= isam->s->base.keys;
149     for (i=0; i < key_parts; i++)
150       m_info->rec_per_key_part[i]+= (isam->s->state.rec_per_key_part[i] /
151                                      m_info->tables);
152   }
153 
154   if (bad_children)
155     goto bad_children;
156   if (!m_info && !(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
157                                                   sizeof(MYRG_INFO),
158                                                   MYF(MY_WME | MY_ZEROFILL))))
159     goto err;
160   /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
161   m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
162   m_info->merge_insert_method= found_merge_insert_method;
163 
164   if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
165   {
166     set_my_errno(HA_ERR_RECORD_FILE_FULL);
167     goto err;
168   }
169   m_info->keys= min_keys;
170   memset(&m_info->by_key, 0, sizeof(m_info->by_key));
171 
172   /* this works ok if the table list is empty */
173   m_info->end_table=m_info->open_tables+files;
174   m_info->last_used_table=m_info->open_tables;
175   m_info->children_attached= TRUE;
176 
177   (void) mysql_file_close(fd, MYF(0));
178   end_io_cache(&file);
179   mysql_mutex_init(rg_key_mutex_MYRG_INFO_mutex,
180                    &m_info->mutex, MY_MUTEX_INIT_FAST);
181   m_info->open_list.data=(void*) m_info;
182   mysql_mutex_lock(&THR_LOCK_open);
183   myrg_open_list=list_add(myrg_open_list,&m_info->open_list);
184   mysql_mutex_unlock(&THR_LOCK_open);
185   DBUG_RETURN(m_info);
186 
187 bad_children:
188   set_my_errno(HA_ERR_WRONG_MRG_TABLE_DEF);
189 err:
190   save_errno=my_errno();
191   switch (errpos) {
192   case 3:
193     while (files)
194       (void) mi_close(m_info->open_tables[--files].table);
195     my_free(m_info);
196     /* Fall through */
197   case 2:
198     end_io_cache(&file);
199     /* Fall through */
200   case 1:
201     (void) mysql_file_close(fd, MYF(0));
202   }
203   set_my_errno(save_errno);
204   DBUG_RETURN (NULL);
205 }
206 
207 
208 /**
209   @brief Open parent table of a MyISAM MERGE table.
210 
211   @detail Open MERGE meta file to get the table name paths for the child
212     tables. Count the children. Allocate and initialize MYRG_INFO
213     structure. Call a callback function for each child table.
214 
215   @param[in]    parent_name     merge table name path as "database/table"
216   @param[in]    callback        function to call for each child table
217   @param[in]    callback_param  data pointer to give to the callback
218 
219   @return MYRG_INFO pointer
220     @retval     != NULL         OK
221     @retval     NULL            Error
222 
223   @note: Currently there is some code duplication between myrg_open()
224     and myrg_parent_open() + myrg_attach_children(). Please duplicate
225     changes in these functions or make common sub-functions.
226 */
227 
myrg_parent_open(const char * parent_name,int (* callback)(void *,const char *),void * callback_param)228 MYRG_INFO *myrg_parent_open(const char *parent_name,
229                             int (*callback)(void*, const char*),
230                             void *callback_param)
231 {
232   MYRG_INFO *m_info= NULL;
233   int       rc;
234   int       errpos;
235   int       save_errno;
236   int       insert_method;
237   uint      length;
238   uint      child_count;
239   File      fd;
240   IO_CACHE  file_cache;
241   char      parent_name_buff[FN_REFLEN * 2];
242   char      child_name_buff[FN_REFLEN];
243   DBUG_ENTER("myrg_parent_open");
244 
245   rc= 1;
246   errpos= 0;
247   memset(&file_cache, 0, sizeof(file_cache));
248 
249   /* Open MERGE meta file. */
250   if ((fd= mysql_file_open(rg_key_file_MRG,
251                            fn_format(parent_name_buff, parent_name,
252                                      "", MYRG_NAME_EXT,
253                                      MY_UNPACK_FILENAME|MY_APPEND_EXT),
254                            O_RDONLY | O_SHARE, MYF(0))) < 0)
255     goto err; /* purecov: inspected */
256   errpos= 1;
257 
258   if (init_io_cache(&file_cache, fd, 4 * IO_SIZE, READ_CACHE, 0, 0,
259                     MYF(MY_WME | MY_NABP)))
260     goto err; /* purecov: inspected */
261   errpos= 2;
262 
263   /* Count children. Determine insert method. */
264   child_count= 0;
265   insert_method= 0;
266   while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
267   {
268     /* Remove line terminator. */
269     if (child_name_buff[length - 1] == '\n')
270       child_name_buff[--length]= '\0';
271 
272     /* Skip empty lines. */
273     if (!child_name_buff[0])
274       continue; /* purecov: inspected */
275 
276     /* Skip comments, but evaluate insert method. */
277     if (child_name_buff[0] == '#')
278     {
279       if (!strncmp(child_name_buff + 1, "INSERT_METHOD=", 14))
280       {
281         /* Compare buffer with global methods list: merge_insert_method. */
282         insert_method= find_type(child_name_buff + 15,
283                                  &merge_insert_method, FIND_TYPE_BASIC);
284       }
285       continue;
286     }
287 
288     /* Count the child. */
289     child_count++;
290   }
291 
292   /* Allocate MERGE parent table structure. */
293   if (!(m_info= (MYRG_INFO*) my_malloc(rg_key_memory_MYRG_INFO,
294                                        sizeof(MYRG_INFO) +
295                                        child_count * sizeof(MYRG_TABLE),
296                                        MYF(MY_WME | MY_ZEROFILL))))
297     goto err; /* purecov: inspected */
298   errpos= 3;
299   m_info->open_tables= (MYRG_TABLE*) (m_info + 1);
300   m_info->tables= child_count;
301   m_info->merge_insert_method= insert_method > 0 ? insert_method : 0;
302   /* This works even if the table list is empty. */
303   m_info->end_table= m_info->open_tables + child_count;
304   if (!child_count)
305   {
306     /* Do not attach/detach an empty child list. */
307     m_info->children_attached= TRUE;
308   }
309 
310   /* Call callback for each child. */
311   my_b_seek(&file_cache, 0);
312   while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
313   {
314     /* Remove line terminator. */
315     if (child_name_buff[length - 1] == '\n')
316       child_name_buff[--length]= '\0';
317 
318     /* Skip empty lines and comments. */
319     if (!child_name_buff[0] || (child_name_buff[0] == '#'))
320       continue;
321 
322     DBUG_PRINT("info", ("child: '%s'", child_name_buff));
323 
324     /* Callback registers child with handler table. */
325     if ((rc= (*callback)(callback_param, child_name_buff)))
326       goto err; /* purecov: inspected */
327   }
328 
329   end_io_cache(&file_cache);
330   (void) mysql_file_close(fd, MYF(0));
331   mysql_mutex_init(rg_key_mutex_MYRG_INFO_mutex,
332                    &m_info->mutex, MY_MUTEX_INIT_FAST);
333 
334   m_info->open_list.data= (void*) m_info;
335   mysql_mutex_lock(&THR_LOCK_open);
336   myrg_open_list= list_add(myrg_open_list, &m_info->open_list);
337   mysql_mutex_unlock(&THR_LOCK_open);
338 
339   DBUG_RETURN(m_info);
340 
341   /* purecov: begin inspected */
342  err:
343   save_errno= my_errno();
344   switch (errpos) {
345   case 3:
346     my_free(m_info);
347     /* Fall through */
348   case 2:
349     end_io_cache(&file_cache);
350     /* Fall through */
351   case 1:
352     (void) mysql_file_close(fd, MYF(0));
353   }
354   set_my_errno(save_errno);
355   DBUG_RETURN (NULL);
356   /* purecov: end */
357 }
358 
359 
360 /**
361   @brief Attach children to a MyISAM MERGE parent table.
362 
363   @detail Call a callback function for each child table.
364     The callback returns the MyISAM table handle of the child table.
365     Check table definition match.
366 
367   @param[in]    m_info            MERGE parent table structure
368   @param[in]    handle_locking    if contains HA_OPEN_FOR_REPAIR, warn about
369                                   incompatible child tables, but continue
370   @param[in]    callback          function to call for each child table
371   @param[in]    callback_param    data pointer to give to the callback
372   @param[in]    need_compat_check pointer to ha_myisammrg::need_compat_check
373                                   (we need this one to decide if previously
374                                   allocated buffers can be reused).
375 
376   @return status
377     @retval     0               OK
378     @retval     != 0            Error
379 
380   @note: Currently there is some code duplication between myrg_open()
381     and myrg_parent_open() + myrg_attach_children(). Please duplicate
382     changes in these functions or make common sub-functions.
383 */
384 
myrg_attach_children(MYRG_INFO * m_info,int handle_locking,MI_INFO * (* callback)(void *),void * callback_param,my_bool * need_compat_check)385 int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
386                          MI_INFO *(*callback)(void*),
387                          void *callback_param, my_bool *need_compat_check)
388 {
389   ulonglong  file_offset;
390   MI_INFO    *myisam;
391   int        errpos;
392   int        save_errno;
393   uint       idx;
394   uint       child_nr;
395   uint       key_parts= 0;
396   uint       min_keys;
397   my_bool    bad_children= FALSE;
398   my_bool    first_child= TRUE;
399   DBUG_ENTER("myrg_attach_children");
400   DBUG_PRINT("myrg", ("handle_locking: %d", handle_locking));
401 
402   /*
403     This function can be called while another thread is trying to abort
404     locks of this MERGE table. If the processor reorders instructions or
405     write to memory, 'children_attached' could be set before
406     'open_tables' has all the pointers to the children. Use of a mutex
407     here and in ha_myisammrg::store_lock() forces consistent data.
408   */
409   mysql_mutex_lock(&m_info->mutex);
410   errpos= 0;
411   file_offset= 0;
412   min_keys= 0;
413   for (child_nr= 0; child_nr < m_info->tables; child_nr++)
414   {
415     if (! (myisam= (*callback)(callback_param)))
416     {
417       if (handle_locking & HA_OPEN_FOR_REPAIR)
418       {
419         /* An appropriate error should've been already pushed by callback. */
420         bad_children= TRUE;
421         continue;
422       }
423       goto bad_children;
424     }
425 
426     DBUG_PRINT("myrg", ("child_nr: %u  table: '%s'",
427                         child_nr, myisam->filename));
428 
429     /* Special handling when the first child is attached. */
430     if (first_child)
431     {
432       first_child= FALSE;
433       m_info->reclength= myisam->s->base.reclength;
434       min_keys=  myisam->s->base.keys;
435       key_parts= myisam->s->base.key_parts;
436       if (*need_compat_check && m_info->rec_per_key_part)
437       {
438         my_free(m_info->rec_per_key_part);
439         m_info->rec_per_key_part= NULL;
440       }
441       if (!m_info->rec_per_key_part)
442       {
443         if(!(m_info->rec_per_key_part= (ulong*)
444              my_malloc(rg_key_memory_MYRG_INFO,
445                        key_parts * sizeof(long), MYF(MY_WME))))
446           goto err; /* purecov: inspected */
447         errpos= 1;
448       }
449       memset(m_info->rec_per_key_part, 0, key_parts * sizeof(long));
450     }
451 
452     /* Add MyISAM table info. */
453     m_info->open_tables[child_nr].table= myisam;
454     m_info->open_tables[child_nr].file_offset= (my_off_t) file_offset;
455     file_offset+= myisam->state->data_file_length;
456 
457     /* Check table definition match. */
458     if (m_info->reclength != myisam->s->base.reclength)
459     {
460       DBUG_PRINT("error", ("definition mismatch table: '%s'  repair: %d",
461                            myisam->filename,
462                            (handle_locking & HA_OPEN_FOR_REPAIR)));
463       if (handle_locking & HA_OPEN_FOR_REPAIR)
464       {
465         myrg_print_wrong_table(myisam->filename);
466         bad_children= TRUE;
467         continue;
468       }
469       goto bad_children;
470     }
471 
472     m_info->options|= myisam->s->options;
473     m_info->records+= myisam->state->records;
474     m_info->del+= myisam->state->del;
475     m_info->data_file_length+= myisam->state->data_file_length;
476     if (min_keys > myisam->s->base.keys)
477       min_keys= myisam->s->base.keys; /* purecov: inspected */
478     for (idx= 0; idx < key_parts; idx++)
479       m_info->rec_per_key_part[idx]+= (myisam->s->state.rec_per_key_part[idx] /
480                                        m_info->tables);
481   }
482 
483   if (bad_children)
484     goto bad_children;
485 
486   if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
487   {
488     set_my_errno(HA_ERR_RECORD_FILE_FULL);
489     goto err;
490   }
491   /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
492   m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
493   m_info->keys= min_keys;
494   m_info->last_used_table= m_info->open_tables;
495   m_info->children_attached= TRUE;
496   mysql_mutex_unlock(&m_info->mutex);
497   DBUG_RETURN(0);
498 
499 bad_children:
500   set_my_errno(HA_ERR_WRONG_MRG_TABLE_DEF);
501 err:
502   save_errno= my_errno();
503   switch (errpos) {
504   case 1:
505     my_free(m_info->rec_per_key_part);
506     m_info->rec_per_key_part= NULL;
507   }
508   mysql_mutex_unlock(&m_info->mutex);
509   set_my_errno(save_errno);
510   DBUG_RETURN(1);
511 }
512 
513 
514 /**
515   @brief Detach children from a MyISAM MERGE parent table.
516 
517   @param[in]    m_info          MERGE parent table structure
518 
519   @note Detach must not touch the children in any way.
520     They may have been closed at ths point already.
521     All references to the children should be removed.
522 
523   @return status
524     @retval     0               OK
525 */
526 
myrg_detach_children(MYRG_INFO * m_info)527 int myrg_detach_children(MYRG_INFO *m_info)
528 {
529   DBUG_ENTER("myrg_detach_children");
530   /* For symmetry with myrg_attach_children() we use the mutex here. */
531   mysql_mutex_lock(&m_info->mutex);
532   if (m_info->tables)
533   {
534     /* Do not attach/detach an empty child list. */
535     m_info->children_attached= FALSE;
536     memset(m_info->open_tables, 0, m_info->tables * sizeof(MYRG_TABLE));
537   }
538   m_info->records= 0;
539   m_info->del= 0;
540   m_info->data_file_length= 0;
541   m_info->options= 0;
542   mysql_mutex_unlock(&m_info->mutex);
543   DBUG_RETURN(0);
544 }
545 
546