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