1 /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 /*
24   locking of isam-tables.
25   reads info from a isam-table. Must be first request before doing any furter
26   calls to any isamfunktion.  Is used to allow many process use the same
27   isamdatabase.
28 */
29 
30 #include "ftdefs.h"
31 
32 	/* lock table by F_UNLCK, F_RDLCK or F_WRLCK */
33 
mi_lock_database(MI_INFO * info,int lock_type)34 int mi_lock_database(MI_INFO *info, int lock_type)
35 {
36   int error;
37   uint count;
38   MYISAM_SHARE *share=info->s;
39   DBUG_ENTER("mi_lock_database");
40   DBUG_PRINT("enter",("lock_type: %d  old lock %d  r_locks: %u  w_locks: %u "
41                       "global_changed:  %d  open_count: %u  name: '%s'",
42                       lock_type, info->lock_type, share->r_locks,
43                       share->w_locks,
44                       share->global_changed, share->state.open_count,
45                       share->index_file_name));
46   if (share->options & HA_OPTION_READ_ONLY_DATA ||
47       info->lock_type == lock_type)
48     DBUG_RETURN(0);
49   if (lock_type == F_EXTRA_LCK)                 /* Used by TMP tables */
50   {
51     ++share->w_locks;
52     ++share->tot_locks;
53     info->lock_type= lock_type;
54     info->s->in_use= list_add(info->s->in_use, &info->in_use);
55     DBUG_RETURN(0);
56   }
57 
58   error= 0;
59   mysql_mutex_lock(&share->intern_lock);
60   if (share->kfile >= 0)		/* May only be false on windows */
61   {
62     switch (lock_type) {
63     case F_UNLCK:
64       ftparser_call_deinitializer(info);
65       if (info->lock_type == F_RDLCK)
66 	count= --share->r_locks;
67       else
68 	count= --share->w_locks;
69       --share->tot_locks;
70       if (info->lock_type == F_WRLCK && !share->w_locks &&
71 	  !share->delay_key_write && flush_key_blocks(share->key_cache,
72 						      share->kfile,FLUSH_KEEP))
73       {
74 	error=my_errno;
75         mi_print_error(info->s, HA_ERR_CRASHED);
76 	mi_mark_crashed(info);		/* Mark that table must be checked */
77       }
78       if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED))
79       {
80 	if (end_io_cache(&info->rec_cache))
81 	{
82 	  error=my_errno;
83           mi_print_error(info->s, HA_ERR_CRASHED);
84 	  mi_mark_crashed(info);
85 	}
86       }
87       if (!count)
88       {
89 	DBUG_PRINT("info",("changed: %u  w_locks: %u",
90 			   (uint) share->changed, share->w_locks));
91 	if (share->changed && !share->w_locks)
92 	{
93 #ifdef HAVE_MMAP
94     if ((info->s->mmaped_length != info->s->state.state.data_file_length) &&
95         (info->s->nonmmaped_inserts > MAX_NONMAPPED_INSERTS))
96     {
97       if (info->s->concurrent_insert)
98         mysql_rwlock_wrlock(&info->s->mmap_lock);
99       mi_remap_file(info, info->s->state.state.data_file_length);
100       info->s->nonmmaped_inserts= 0;
101       if (info->s->concurrent_insert)
102         mysql_rwlock_unlock(&info->s->mmap_lock);
103     }
104 #endif
105 	  share->state.process= share->last_process=share->this_process;
106 	  share->state.unique=   info->last_unique=  info->this_unique;
107 	  share->state.update_count= info->last_loop= ++info->this_loop;
108           if (mi_state_info_write(share->kfile, &share->state, 1))
109 	    error=my_errno;
110 	  share->changed=0;
111 	  if (myisam_flush)
112 	  {
113             if (share->file_map)
114               my_msync(info->dfile, share->file_map, share->mmaped_length, MS_SYNC);
115             if (mysql_file_sync(share->kfile, MYF(0)))
116 	      error= my_errno;
117             if (mysql_file_sync(info->dfile, MYF(0)))
118 	      error= my_errno;
119 	  }
120 	  else
121 	    share->not_flushed=1;
122 	  if (error)
123           {
124             mi_print_error(info->s, HA_ERR_CRASHED);
125 	    mi_mark_crashed(info);
126           }
127 	}
128 	if (info->lock_type != F_EXTRA_LCK)
129 	{
130 	  if (share->r_locks)
131 	  {					/* Only read locks left */
132 	    if (my_lock(share->kfile,F_RDLCK,0L,F_TO_EOF,
133 			MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
134 	      error=my_errno;
135 	  }
136 	  else if (!share->w_locks)
137 	  {					/* No more locks */
138 	    if (my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
139 			MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
140 	      error=my_errno;
141 	  }
142 	}
143       }
144       info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED);
145       info->lock_type= F_UNLCK;
146       info->s->in_use= list_delete(info->s->in_use, &info->in_use);
147       break;
148     case F_RDLCK:
149       if (info->lock_type == F_WRLCK)
150       {
151         /*
152           Change RW to READONLY
153 
154           mysqld does not turn write locks to read locks,
155           so we're never here in mysqld.
156         */
157 	if (share->w_locks == 1)
158 	{
159           if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
160 		      MYF(MY_SEEK_NOT_DONE)))
161 	  {
162 	    error=my_errno;
163 	    break;
164 	  }
165 	}
166 	share->w_locks--;
167 	share->r_locks++;
168 	info->lock_type=lock_type;
169 	break;
170       }
171       if (!share->r_locks && !share->w_locks)
172       {
173 	if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
174 		    info->lock_wait | MY_SEEK_NOT_DONE))
175 	{
176 	  error=my_errno;
177 	  break;
178 	}
179 	if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
180 	{
181 	  error=my_errno;
182 	  (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,MYF(MY_SEEK_NOT_DONE));
183 	  my_errno=error;
184 	  break;
185 	}
186       }
187       (void) _mi_test_if_changed(info);
188       share->r_locks++;
189       share->tot_locks++;
190       info->lock_type=lock_type;
191       info->s->in_use= list_add(info->s->in_use, &info->in_use);
192       break;
193     case F_WRLCK:
194       if (info->lock_type == F_RDLCK)
195       {						/* Change READONLY to RW */
196 	if (share->r_locks == 1)
197 	{
198 	  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
199 		      MYF(info->lock_wait | MY_SEEK_NOT_DONE)))
200 	  {
201 	    error=my_errno;
202 	    break;
203 	  }
204 	  share->r_locks--;
205 	  share->w_locks++;
206 	  info->lock_type=lock_type;
207 	  break;
208 	}
209       }
210       if (!(share->options & HA_OPTION_READ_ONLY_DATA))
211       {
212 	if (!share->w_locks)
213 	{
214 	  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
215 		      info->lock_wait | MY_SEEK_NOT_DONE))
216 	  {
217 	    error=my_errno;
218 	    break;
219 	  }
220 	  if (!share->r_locks)
221 	  {
222 	    if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
223 	    {
224 	      error=my_errno;
225 	      (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
226 			   info->lock_wait | MY_SEEK_NOT_DONE);
227 	      my_errno=error;
228 	      break;
229 	    }
230 	  }
231 	}
232       }
233       (void) _mi_test_if_changed(info);
234 
235       info->lock_type=lock_type;
236       info->invalidator=info->s->invalidator;
237       share->w_locks++;
238       share->tot_locks++;
239 
240       DBUG_EXECUTE_IF("simulate_incorrect_share_wlock_value",
241                       DEBUG_SYNC_C("after_share_wlock_increment"););
242 
243       info->s->in_use= list_add(info->s->in_use, &info->in_use);
244       break;
245     default:
246       break;				/* Impossible */
247     }
248   }
249 #ifdef _WIN32
250   else
251   {
252     /*
253        Check for bad file descriptors if this table is part
254        of a merge union. Failing to capture this may cause
255        a crash on windows if the table is renamed and
256        later on referenced by the merge table.
257      */
258     if( info->owned_by_merge && (info->s)->kfile < 0 )
259     {
260       error = HA_ERR_NO_SUCH_TABLE;
261     }
262   }
263 #endif
264   mysql_mutex_unlock(&share->intern_lock);
265   DBUG_RETURN(error);
266 } /* mi_lock_database */
267 
268 
269 /****************************************************************************
270   The following functions are called by thr_lock() in threaded applications
271 ****************************************************************************/
272 
273 /*
274   Create a copy of the current status for the table
275 
276   SYNOPSIS
277     mi_get_status()
278     param		Pointer to Myisam handler
279     concurrent_insert	Set to 1 if we are going to do concurrent inserts
280 			(THR_WRITE_CONCURRENT_INSERT was used)
281 */
282 
mi_get_status(void * param,int concurrent_insert)283 void mi_get_status(void* param, int concurrent_insert)
284 {
285   MI_INFO *info=(MI_INFO*) param;
286   DBUG_ENTER("mi_get_status");
287   DBUG_PRINT("info",("key_file: %ld  data_file: %ld  concurrent_insert: %d",
288 		     (long) info->s->state.state.key_file_length,
289 		     (long) info->s->state.state.data_file_length,
290                      concurrent_insert));
291 #ifndef DBUG_OFF
292   if (info->state->key_file_length > info->s->state.state.key_file_length ||
293       info->state->data_file_length > info->s->state.state.data_file_length)
294     DBUG_PRINT("warning",("old info:  key_file: %ld  data_file: %ld",
295 			  (long) info->state->key_file_length,
296 			  (long) info->state->data_file_length));
297 #endif
298   info->save_state=info->s->state.state;
299   info->state= &info->save_state;
300   info->append_insert_at_end= concurrent_insert;
301   if (concurrent_insert)
302     info->s->state.state.uncacheable= TRUE;
303   DBUG_VOID_RETURN;
304 }
305 
306 
mi_update_status(void * param)307 void mi_update_status(void* param)
308 {
309   MI_INFO *info=(MI_INFO*) param;
310   /*
311     Because someone may have closed the table we point at, we only
312     update the state if its our own state.  This isn't a problem as
313     we are always pointing at our own lock or at a read lock.
314     (This is enforced by thr_multi_lock.c)
315   */
316   if (info->state == &info->save_state)
317   {
318 #ifndef DBUG_OFF
319     DBUG_PRINT("info",("updating status:  key_file: %ld  data_file: %ld",
320 		       (long) info->state->key_file_length,
321 		       (long) info->state->data_file_length));
322     if (info->state->key_file_length < info->s->state.state.key_file_length ||
323 	info->state->data_file_length < info->s->state.state.data_file_length)
324       DBUG_PRINT("warning",("old info:  key_file: %ld  data_file: %ld",
325 			    (long) info->s->state.state.key_file_length,
326 			    (long) info->s->state.state.data_file_length));
327 #endif
328     info->s->state.state= *info->state;
329   }
330   info->state= &info->s->state.state;
331   info->append_insert_at_end= 0;
332 
333   /*
334     We have to flush the write cache here as other threads may start
335     reading the table before mi_lock_database() is called
336   */
337   if (info->opt_flag & WRITE_CACHE_USED)
338   {
339     if (end_io_cache(&info->rec_cache))
340     {
341       mi_print_error(info->s, HA_ERR_CRASHED);
342       mi_mark_crashed(info);
343     }
344     info->opt_flag&= ~WRITE_CACHE_USED;
345   }
346 }
347 
348 
mi_restore_status(void * param)349 void mi_restore_status(void *param)
350 {
351   MI_INFO *info= (MI_INFO*) param;
352   info->state= &info->s->state.state;
353   info->append_insert_at_end= 0;
354 }
355 
356 
mi_copy_status(void * to,void * from)357 void mi_copy_status(void* to,void *from)
358 {
359   ((MI_INFO*) to)->state= &((MI_INFO*) from)->save_state;
360 }
361 
362 
363 /*
364   Check if should allow concurrent inserts
365 
366   IMPLEMENTATION
367     Allow concurrent inserts if we don't have a hole in the table or
368     if there is no active write lock and there is active read locks and
369     myisam_concurrent_insert == 2. In this last case the new
370     row('s) are inserted at end of file instead of filling up the hole.
371 
372     The last case is to allow one to inserts into a heavily read-used table
373     even if there is holes.
374 
375   NOTES
376     If there is a an rtree indexes in the table, concurrent inserts are
377     disabled in mi_open()
378 
379   RETURN
380     0  ok to use concurrent inserts
381     1  not ok
382 */
383 
mi_check_status(void * param)384 my_bool mi_check_status(void *param)
385 {
386   MI_INFO *info=(MI_INFO*) param;
387   /*
388     The test for w_locks == 1 is here because this thread has already done an
389     external lock (in other words: w_locks == 1 means no other threads has
390     a write lock)
391   */
392   DBUG_PRINT("info",("dellink: %ld  r_locks: %u  w_locks: %u",
393                      (long) info->s->state.dellink, (uint) info->s->r_locks,
394                      (uint) info->s->w_locks));
395   return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR ||
396                      (myisam_concurrent_insert == 2 && info->s->r_locks &&
397                       info->s->w_locks == 1));
398 }
399 
400 
401 /****************************************************************************
402  ** functions to read / write the state
403 ****************************************************************************/
404 
_mi_readinfo(MI_INFO * info,int lock_type,int check_keybuffer)405 int _mi_readinfo(MI_INFO *info, int lock_type, int check_keybuffer)
406 {
407   DBUG_ENTER("_mi_readinfo");
408 
409   if (info->lock_type == F_UNLCK)
410   {
411     MYISAM_SHARE *share=info->s;
412     if (!share->tot_locks)
413     {
414       if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
415 		  info->lock_wait | MY_SEEK_NOT_DONE))
416 	DBUG_RETURN(1);
417       if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
418       {
419 	int error=my_errno ? my_errno : -1;
420 	(void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
421 		     MYF(MY_SEEK_NOT_DONE));
422 	my_errno=error;
423 	DBUG_RETURN(1);
424       }
425     }
426     if (check_keybuffer)
427       (void) _mi_test_if_changed(info);
428     info->invalidator=info->s->invalidator;
429   }
430   else if (lock_type == F_WRLCK && info->lock_type == F_RDLCK)
431   {
432     my_errno=EACCES;				/* Not allowed to change */
433     DBUG_RETURN(-1);				/* when have read_lock() */
434   }
435   DBUG_RETURN(0);
436 } /* _mi_readinfo */
437 
438 
439 /*
440   Every isam-function that uppdates the isam-database MUST end with this
441   request
442 */
443 
_mi_writeinfo(MI_INFO * info,uint operation)444 int _mi_writeinfo(MI_INFO *info, uint operation)
445 {
446   int error,olderror;
447   MYISAM_SHARE *share=info->s;
448   DBUG_ENTER("_mi_writeinfo");
449   DBUG_PRINT("info",("operation: %u  tot_locks: %u", operation,
450 		     share->tot_locks));
451 
452   error=0;
453   if (share->tot_locks == 0)
454   {
455     olderror=my_errno;			/* Remember last error */
456     if (operation)
457     {					/* Two threads can't be here */
458       share->state.process= share->last_process=   share->this_process;
459       share->state.unique=  info->last_unique=	   info->this_unique;
460       share->state.update_count= info->last_loop= ++info->this_loop;
461       if ((error=mi_state_info_write(share->kfile, &share->state, 1)))
462 	olderror=my_errno;
463 #ifdef _WIN32
464       if (myisam_flush)
465       {
466        if (share->file_map)
467          my_msync(info->dfile, share->file_map, share->mmaped_length, MS_SYNC);
468         mysql_file_sync(share->kfile, 0);
469         mysql_file_sync(info->dfile, 0);
470       }
471 #endif
472     }
473     if (!(operation & WRITEINFO_NO_UNLOCK) &&
474 	my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
475 		MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
476       DBUG_RETURN(1);
477     my_errno=olderror;
478   }
479   else if (operation)
480     share->changed= 1;			/* Mark keyfile changed */
481   DBUG_RETURN(error);
482 } /* _mi_writeinfo */
483 
484 
485 	/* Test if someone has changed the database */
486 	/* (Should be called after readinfo) */
487 
_mi_test_if_changed(MI_INFO * info)488 int _mi_test_if_changed(MI_INFO *info)
489 {
490   MYISAM_SHARE *share=info->s;
491   if (share->state.process != share->last_process ||
492       share->state.unique  != info->last_unique ||
493       share->state.update_count != info->last_loop)
494   {						/* Keyfile has changed */
495     DBUG_PRINT("info",("index file changed"));
496     if (share->state.process != share->this_process)
497       (void) flush_key_blocks(share->key_cache, share->kfile, FLUSH_RELEASE);
498     share->last_process=share->state.process;
499     info->last_unique=	share->state.unique;
500     info->last_loop=	share->state.update_count;
501     info->update|=	HA_STATE_WRITTEN;	/* Must use file on next */
502     info->data_changed= 1;			/* For mi_is_changed */
503     return 1;
504   }
505   return (!(info->update & HA_STATE_AKTIV) ||
506 	  (info->update & (HA_STATE_WRITTEN | HA_STATE_DELETED |
507 			   HA_STATE_KEY_CHANGED)));
508 } /* _mi_test_if_changed */
509 
510 
511 /*
512   Put a mark in the .MYI file that someone is updating the table
513 
514 
515   DOCUMENTATION
516 
517   state.open_count in the .MYI file is used the following way:
518   - For the first change of the .MYI file in this process open_count is
519     incremented by mi_mark_file_change(). (We have a write lock on the file
520     when this happens)
521   - In mi_close() it's decremented by _mi_decrement_open_count() if it
522     was incremented in the same process.
523 
524   This mean that if we are the only process using the file, the open_count
525   tells us if the MYISAM file wasn't properly closed. (This is true if
526   my_disable_locking is set).
527 */
528 
529 
_mi_mark_file_changed(MI_INFO * info)530 int _mi_mark_file_changed(MI_INFO *info)
531 {
532   uchar buff[3];
533   MYISAM_SHARE *share=info->s;
534   DBUG_ENTER("_mi_mark_file_changed");
535 
536   if (!(share->state.changed & STATE_CHANGED) || ! share->global_changed)
537   {
538     share->state.changed|=(STATE_CHANGED | STATE_NOT_ANALYZED |
539 			   STATE_NOT_OPTIMIZED_KEYS);
540     if (!share->global_changed)
541     {
542       share->global_changed=1;
543       share->state.open_count++;
544     }
545     if (!share->temporary)
546     {
547       mi_int2store(buff,share->state.open_count);
548       buff[2]=1;				/* Mark that it's changed */
549       DBUG_RETURN(mysql_file_pwrite(share->kfile, buff, sizeof(buff),
550                                     sizeof(share->state.header),
551                                     MYF(MY_NABP)));
552     }
553   }
554   DBUG_RETURN(0);
555 }
556 
557 
558 /*
559   This is only called by close or by extra(HA_FLUSH) if the OS has the pwrite()
560   call.  In these context the following code should be safe!
561  */
562 
_mi_decrement_open_count(MI_INFO * info)563 int _mi_decrement_open_count(MI_INFO *info)
564 {
565   uchar buff[2];
566   MYISAM_SHARE *share=info->s;
567   int lock_error=0,write_error=0;
568   if (share->global_changed)
569   {
570     uint old_lock=info->lock_type;
571     share->global_changed=0;
572     lock_error=mi_lock_database(info,F_WRLCK);
573     /* Its not fatal even if we couldn't get the lock ! */
574     if (share->state.open_count > 0)
575     {
576       share->state.open_count--;
577       mi_int2store(buff,share->state.open_count);
578       write_error= mysql_file_pwrite(share->kfile, buff, sizeof(buff),
579                                      sizeof(share->state.header),
580                                      MYF(MY_NABP));
581     }
582     if (!lock_error)
583       lock_error=mi_lock_database(info,old_lock);
584   }
585   return MY_TEST(lock_error || write_error);
586 }
587