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