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