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 mysql_mutex_lock(&share->intern_lock);
57 if (share->kfile >= 0) /* May only be false on windows */
58 {
59 switch (lock_type) {
60 case F_UNLCK:
61 ftparser_call_deinitializer(info);
62 if (info->lock_type == F_RDLCK)
63 {
64 count= --share->r_locks;
65 mi_restore_status(info);
66 }
67 else
68 {
69 count= --share->w_locks;
70 mi_update_status_with_lock(info);
71 }
72 --share->tot_locks;
73 if (info->lock_type == F_WRLCK && !share->w_locks &&
74 !share->delay_key_write && flush_key_blocks(share->key_cache,
75 share->kfile,
76 &share->dirty_part_map,
77 FLUSH_KEEP))
78 {
79 mark_crashed= error=my_errno;
80 mi_print_error(info->s, HA_ERR_CRASHED);
81 }
82 if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED))
83 {
84 if (end_io_cache(&info->rec_cache))
85 {
86 mark_crashed= error=my_errno;
87 mi_print_error(info->s, HA_ERR_CRASHED);
88 }
89 }
90 if (!count)
91 {
92 DBUG_PRINT("info",("changed: %u w_locks: %u",
93 (uint) share->changed, share->w_locks));
94 if (share->changed && !share->w_locks)
95 {
96 #ifdef HAVE_MMAP
97 if ((info->s->mmaped_length != info->s->state.state.data_file_length) &&
98 (info->s->nonmmaped_inserts > MAX_NONMAPPED_INSERTS))
99 {
100 if (info->s->concurrent_insert)
101 mysql_rwlock_wrlock(&info->s->mmap_lock);
102 mi_remap_file(info, info->s->state.state.data_file_length);
103 info->s->nonmmaped_inserts= 0;
104 if (info->s->concurrent_insert)
105 mysql_rwlock_unlock(&info->s->mmap_lock);
106 }
107 #endif
108 share->state.process= share->last_process=share->this_process;
109 share->state.unique= info->last_unique= info->this_unique;
110 share->state.update_count= info->last_loop= ++info->this_loop;
111 if (mi_state_info_write(share->kfile, &share->state, 1))
112 mark_crashed= error=my_errno;
113 share->changed=0;
114 if (myisam_flush)
115 {
116 if (share->file_map)
117 my_msync(info->dfile, share->file_map, share->mmaped_length, MS_SYNC);
118 if (mysql_file_sync(share->kfile, MYF(0)))
119 mark_crashed= error= my_errno;
120 if (mysql_file_sync(info->dfile, MYF(0)))
121 mark_crashed= error= my_errno;
122 }
123 else
124 share->not_flushed=1;
125 if (error)
126 mi_print_error(info->s, HA_ERR_CRASHED);
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->open_flag & HA_OPEN_MERGE_TABLE) && (info->s)->kfile < 0)
259 {
260 error = HA_ERR_NO_SUCH_TABLE;
261 }
262 }
263 #endif
264 mysql_mutex_unlock(&share->intern_lock);
265 if (mark_crashed)
266 mi_mark_crashed(info);
267 DBUG_RETURN(error);
268 } /* mi_lock_database */
269
270
271 /****************************************************************************
272 The following functions are called by thr_lock() in threaded applications
273 ****************************************************************************/
274
275 /*
276 Create a copy of the current status for the table
277
278 SYNOPSIS
279 mi_get_status()
280 param Pointer to Myisam handler
281 concurrent_insert Set to 1 if we are going to do concurrent inserts
282 (THR_WRITE_CONCURRENT_INSERT was used)
283 */
284
mi_get_status(void * param,my_bool concurrent_insert)285 void mi_get_status(void* param, my_bool concurrent_insert)
286 {
287 MI_INFO *info=(MI_INFO*) param;
288 DBUG_ENTER("mi_get_status");
289 DBUG_PRINT("info",("name: %s key_file: %lu data_file: %lu rows: %lu concurrent_insert: %d",
290 info->s->index_file_name,
291 (ulong) info->s->state.state.key_file_length,
292 (ulong) info->s->state.state.data_file_length,
293 (ulong) info->s->state.state.records,
294 concurrent_insert));
295 #ifndef DBUG_OFF
296 if (info->state->key_file_length > info->s->state.state.key_file_length ||
297 info->state->data_file_length > info->s->state.state.data_file_length)
298 DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld",
299 (long) info->state->key_file_length,
300 (long) info->state->data_file_length));
301 #endif
302 info->save_state=info->s->state.state;
303 info->state= &info->save_state;
304 info->append_insert_at_end= concurrent_insert;
305 if (concurrent_insert)
306 info->s->state.state.uncacheable= TRUE;
307 DBUG_VOID_RETURN;
308 }
309
310
mi_update_status(void * param)311 void mi_update_status(void* param)
312 {
313 MI_INFO *info=(MI_INFO*) param;
314 DBUG_ENTER("mi_update_status");
315 /*
316 Because someone may have closed the table we point at, we only
317 update the state if its our own state. This isn't a problem as
318 we are always pointing at our own lock or at a read lock.
319 (This is enforced by thr_multi_lock.c)
320 */
321 if (info->state == &info->save_state)
322 {
323 DBUG_PRINT("info",
324 ("updating status: key_file: %lu data_file: %lu rows: %lu",
325 (ulong) info->state->key_file_length,
326 (ulong) info->state->data_file_length,
327 (ulong) info->state->records));
328 if (info->state->key_file_length < info->s->state.state.key_file_length ||
329 info->state->data_file_length < info->s->state.state.data_file_length)
330 DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld",
331 (long) info->s->state.state.key_file_length,
332 (long) info->s->state.state.data_file_length));
333 info->s->state.state= *info->state;
334 #ifdef HAVE_QUERY_CACHE
335 DBUG_PRINT("info", ("invalidator... '%s' (status update)",
336 info->filename));
337 DBUG_ASSERT(info->s->chst_invalidator != NULL);
338 (*info->s->chst_invalidator)((const char *)info->filename);
339 #endif
340 }
341
342 info->state= &info->s->state.state;
343 info->append_insert_at_end= 0;
344
345 /*
346 We have to flush the write cache here as other threads may start
347 reading the table before mi_lock_database() is called
348 */
349 if (info->opt_flag & WRITE_CACHE_USED)
350 {
351 if (end_io_cache(&info->rec_cache))
352 {
353 mi_print_error(info->s, HA_ERR_CRASHED);
354 mi_mark_crashed(info);
355 }
356 info->opt_flag&= ~WRITE_CACHE_USED;
357 }
358 DBUG_VOID_RETURN;
359 }
360
361 /*
362 Same as mi_update_status() but take a lock in the table lock, to protect
363 against someone calling mi_get_status() from thr_lock() at the same time.
364 */
365
mi_update_status_with_lock(MI_INFO * info)366 static void mi_update_status_with_lock(MI_INFO *info)
367 {
368 my_bool locked= 0;
369 if (info->state == &info->save_state)
370 {
371 locked= 1;
372 mysql_mutex_lock(&info->s->lock.mutex);
373 }
374 mi_update_status(info);
375 if (locked)
376 mysql_mutex_unlock(&info->s->lock.mutex);
377 }
378
379
mi_restore_status(void * param)380 void mi_restore_status(void *param)
381 {
382 MI_INFO *info= (MI_INFO*) param;
383 DBUG_ENTER("mi_restore_status");
384 DBUG_PRINT("info",("key_file: %ld data_file: %ld",
385 (long) info->s->state.state.key_file_length,
386 (long) info->s->state.state.data_file_length));
387 info->state= &info->s->state.state;
388 info->append_insert_at_end= 0;
389 DBUG_VOID_RETURN;
390 }
391
392
mi_copy_status(void * to,void * from)393 void mi_copy_status(void* to,void *from)
394 {
395 MI_INFO *info= (MI_INFO*) to;
396 DBUG_ENTER("mi_copy_status");
397 info->state= &((MI_INFO*) from)->save_state;
398 DBUG_PRINT("info",("key_file: %ld data_file: %ld",
399 (long) info->state->key_file_length,
400 (long) info->state->data_file_length));
401 DBUG_VOID_RETURN;
402 }
403
404
405 /*
406 Check if should allow concurrent inserts
407
408 IMPLEMENTATION
409 Allow concurrent inserts if we don't have a hole in the table or
410 if there is no active write lock and there is active read locks and
411 myisam_concurrent_insert == 2. In this last case the new
412 row('s) are inserted at end of file instead of filling up the hole.
413
414 The last case is to allow one to inserts into a heavily read-used table
415 even if there is holes.
416
417 NOTES
418 If there is a an rtree indexes in the table, concurrent inserts are
419 disabled in mi_open()
420
421 RETURN
422 0 ok to use concurrent inserts
423 1 not ok
424 */
425
mi_check_status(void * param)426 my_bool mi_check_status(void *param)
427 {
428 MI_INFO *info=(MI_INFO*) param;
429 DBUG_ENTER("mi_check_status");
430 DBUG_PRINT("info",("dellink: %ld r_locks: %u w_locks: %u",
431 (long) info->s->state.dellink, (uint) info->s->r_locks,
432 (uint) info->s->w_locks));
433 /*
434 The test for w_locks == 1 is here because this thread has already done an
435 external lock (in other words: w_locks == 1 means no other threads has
436 a write lock)
437 */
438 DBUG_RETURN((my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR ||
439 (myisam_concurrent_insert == 2 && info->s->r_locks &&
440 info->s->w_locks == 1)));
441 }
442
443
444 /**
445 Fix status for thr_lock_merge()
446
447 @param org_table
448 @param new_table that should point on org_lock. new_table is 0
449 in case this is the first occurrence of the table in the lock
450 structure.
451 */
452
mi_fix_status(MI_INFO * org_table,MI_INFO * new_table)453 void mi_fix_status(MI_INFO *org_table, MI_INFO *new_table)
454 {
455 DBUG_ENTER("mi_fix_status");
456 if (!new_table)
457 {
458 /* First in group. Set state as in mi_get_status() */
459 org_table->state= &org_table->save_state;
460 }
461 else
462 {
463 /* Set new_table to use state from org_table (first lock of this table) */
464 new_table->state= org_table->state;
465 }
466 DBUG_VOID_RETURN;
467 }
468
469
470 /****************************************************************************
471 ** functions to read / write the state
472 ****************************************************************************/
473
_mi_readinfo(register MI_INFO * info,int lock_type,int check_keybuffer)474 int _mi_readinfo(register MI_INFO *info, int lock_type, int check_keybuffer)
475 {
476 DBUG_ENTER("_mi_readinfo");
477
478 if (info->lock_type == F_UNLCK)
479 {
480 MYISAM_SHARE *share=info->s;
481 if (!share->tot_locks)
482 {
483 if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
484 info->lock_wait | MY_SEEK_NOT_DONE))
485 DBUG_RETURN(1);
486 if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
487 {
488 int error= my_errno ? my_errno : HA_ERR_FILE_TOO_SHORT;
489 (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
490 MYF(MY_SEEK_NOT_DONE));
491 my_errno= error;
492 DBUG_RETURN(1);
493 }
494 }
495 if (check_keybuffer)
496 (void) _mi_test_if_changed(info);
497 info->invalidator=info->s->invalidator;
498 }
499 else if (lock_type == F_WRLCK && info->lock_type == F_RDLCK)
500 {
501 my_errno=EACCES; /* Not allowed to change */
502 DBUG_RETURN(-1); /* when have read_lock() */
503 }
504 DBUG_RETURN(0);
505 } /* _mi_readinfo */
506
507
508 /*
509 Every isam-function that uppdates the isam-database MUST end with this
510 request
511 */
512
_mi_writeinfo(register MI_INFO * info,uint operation)513 int _mi_writeinfo(register MI_INFO *info, uint operation)
514 {
515 int error,olderror;
516 MYISAM_SHARE *share=info->s;
517 DBUG_ENTER("_mi_writeinfo");
518 DBUG_PRINT("info",("operation: %u tot_locks: %u", operation,
519 share->tot_locks));
520
521 error=0;
522 if (share->tot_locks == 0)
523 {
524 olderror=my_errno; /* Remember last error */
525 if (operation)
526 { /* Two threads can't be here */
527 share->state.process= share->last_process= share->this_process;
528 share->state.unique= info->last_unique= info->this_unique;
529 share->state.update_count= info->last_loop= ++info->this_loop;
530 if ((error=mi_state_info_write(share->kfile, &share->state, 1)))
531 olderror=my_errno;
532 #ifdef _WIN32
533 if (myisam_flush)
534 {
535 if (share->file_map)
536 my_msync(info->dfile, share->file_map, share->mmaped_length, MS_SYNC);
537 mysql_file_sync(share->kfile, 0);
538 mysql_file_sync(info->dfile, 0);
539 }
540 #endif
541 }
542 if (!(operation & WRITEINFO_NO_UNLOCK) &&
543 my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
544 MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
545 DBUG_RETURN(1);
546 my_errno=olderror;
547 }
548 else if (operation)
549 share->changed= 1; /* Mark keyfile changed */
550 DBUG_RETURN(error);
551 } /* _mi_writeinfo */
552
553
554 /* Test if someone has changed the database */
555 /* (Should be called after readinfo) */
556
_mi_test_if_changed(register MI_INFO * info)557 int _mi_test_if_changed(register MI_INFO *info)
558 {
559 MYISAM_SHARE *share=info->s;
560 if (share->state.process != share->last_process ||
561 share->state.unique != info->last_unique ||
562 share->state.update_count != info->last_loop)
563 { /* Keyfile has changed */
564 DBUG_PRINT("info",("index file changed"));
565 if (share->state.process != share->this_process)
566 (void) flush_key_blocks(share->key_cache, share->kfile,
567 &share->dirty_part_map, FLUSH_RELEASE);
568 share->last_process=share->state.process;
569 info->last_unique= share->state.unique;
570 info->last_loop= share->state.update_count;
571 info->update|= HA_STATE_WRITTEN; /* Must use file on next */
572 info->data_changed= 1; /* For mi_is_changed */
573 return 1;
574 }
575 return (!(info->update & HA_STATE_AKTIV) ||
576 (info->update & (HA_STATE_WRITTEN | HA_STATE_DELETED |
577 HA_STATE_KEY_CHANGED)));
578 } /* _mi_test_if_changed */
579
580
581 /*
582 Put a mark in the .MYI file that someone is updating the table
583
584
585 DOCUMENTATION
586
587 state.open_count in the .MYI file is used the following way:
588 - For the first change of the .MYI file in this process open_count is
589 incremented by mi_mark_file_change(). (We have a write lock on the file
590 when this happens)
591 - In mi_close() it's decremented by _mi_decrement_open_count() if it
592 was incremented in the same process.
593
594 This mean that if we are the only process using the file, the open_count
595 tells us if the MYISAM file wasn't properly closed. (This is true if
596 my_disable_locking is set).
597 */
598
599
_mi_mark_file_changed(MI_INFO * info)600 int _mi_mark_file_changed(MI_INFO *info)
601 {
602 uchar buff[3];
603 register MYISAM_SHARE *share=info->s;
604 DBUG_ENTER("_mi_mark_file_changed");
605
606 if (!(share->state.changed & STATE_CHANGED) || ! share->global_changed)
607 {
608 share->state.changed|=(STATE_CHANGED | STATE_NOT_ANALYZED |
609 STATE_NOT_OPTIMIZED_KEYS);
610 if (!share->global_changed)
611 {
612 share->global_changed=1;
613 share->state.open_count++;
614 }
615 if (!share->temporary)
616 {
617 mi_int2store(buff,share->state.open_count);
618 buff[2]=1; /* Mark that it's changed */
619 DBUG_RETURN((int)mysql_file_pwrite(share->kfile, buff, sizeof(buff),
620 sizeof(share->state.header),
621 MYF(MY_NABP)));
622 }
623 }
624 DBUG_RETURN(0);
625 }
626
627
628 /*
629 This is only called by close or by extra(HA_FLUSH) if the OS has the pwrite()
630 call. In these context the following code should be safe!
631 */
632
_mi_decrement_open_count(MI_INFO * info)633 int _mi_decrement_open_count(MI_INFO *info)
634 {
635 uchar buff[2];
636 register MYISAM_SHARE *share=info->s;
637 int lock_error=0,write_error=0;
638 if (share->global_changed)
639 {
640 uint old_lock=info->lock_type;
641 share->global_changed=0;
642 lock_error= my_disable_locking ? 0 : mi_lock_database(info,F_WRLCK);
643 /* Its not fatal even if we couldn't get the lock ! */
644 if (share->state.open_count > 0)
645 {
646 share->state.open_count--;
647 mi_int2store(buff,share->state.open_count);
648 write_error= (mysql_file_pwrite(share->kfile, buff, sizeof(buff),
649 sizeof(share->state.header),
650 MYF(MY_NABP)) != 0);
651 }
652 if (!lock_error && !my_disable_locking)
653 lock_error=mi_lock_database(info,old_lock);
654 }
655 return MY_TEST(lock_error || write_error);
656 }
657
658
_mi_report_crashed_ignore(MI_INFO * file,const char * message,const char * sfile,uint sline)659 void _mi_report_crashed_ignore(MI_INFO *file __attribute__((unused)),
660 const char *message __attribute__((unused)),
661 const char *sfile __attribute__((unused)),
662 uint sline __attribute__((unused)))
663 {
664 }
665