Bug #40975 | Database restored through named pipes brings up a corrupt database. | ||
---|---|---|---|
Submitted: | 24 Nov 2008 12:31 | Modified: | 8 Apr 2009 0:21 |
Reporter: | Priyesh Narayanan | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: Backup | Severity: | S3 (Non-critical) |
Version: | 6.0.7-alpha-debug | OS: | Any |
Assigned to: | Rafal Somla | CPU Architecture: | Any |
[24 Nov 2008 12:31]
Priyesh Narayanan
[26 Nov 2008 15:07]
MySQL Verification Team
Repeated with 6.0.7 release: mysql> backup database world to '/tmp/bak_world'; +-----------+ | backup_id | +-----------+ | 1 | +-----------+ 1 row in set (0.05 sec) mysql> restore from '/tmp/bakPipe'; +-----------+ | backup_id | +-----------+ | 2 | +-----------+ 1 row in set (2.54 sec) mysql> select backup_state, operation, error_num, num_objects, total_bytes from -> mysql.online_backup where backup_id=1; +--------------+-----------+-----------+-------------+-------------+ | backup_state | operation | error_num | num_objects | total_bytes | +--------------+-----------+-----------+-------------+-------------+ | complete | backup | 0 | 3 | 445734 | +--------------+-----------+-----------+-------------+-------------+ 1 row in set (0.00 sec) mysql> select backup_state, operation, error_num, num_objects, total_bytes from mysql.online_backup where backup_id=2; +--------------+-----------+-----------+-------------+-------------+ | backup_state | operation | error_num | num_objects | total_bytes | +--------------+-----------+-----------+-------------+-------------+ | complete | restore | 0 | 3 | 957679 | +--------------+-----------+-----------+-------------+-------------+ 1 row in set (0.00 sec) mysql> show variables like "%version%"; +-------------------------+------------------------------+ | Variable_name | Value | +-------------------------+------------------------------+ | protocol_version | 10 | | version | 6.0.7-alpha | | version_comment | MySQL Community Server (GPL) | | version_compile_machine | x86_64 | | version_compile_os | unknown-linux-gnu | +-------------------------+------------------------------+ 5 rows in set (0.01 sec)
[26 Nov 2008 15:18]
MySQL Verification Team
Thank you for the bug report. mysql> check table City; +------------+-------+----------+---------------------------------------------------------------+ | Table | Op | Msg_type | Msg_text | +------------+-------+----------+---------------------------------------------------------------+ | world.City | check | warning | Size of indexfile is: 92161 Should be: 43008 | | world.City | check | warning | Size of datafile is: 580496 Should be: 273293 | | world.City | check | error | Found key at page 7168 that points to record outside datafile | | world.City | check | error | Corrupt | +------------+-------+----------+---------------------------------------------------------------+ 4 rows in set (0.00 sec)
[28 Nov 2008 14:42]
Jørgen Løland
I can easily repeat on 6.0.7, but not on current development branch. I'll retry with the binaries once 6.0.8 has been released. Output: ------- (...) backup database world to 'wrld.bup'; backup_id 270 restore from '../../mypipe' overwrite; backup_id 271 select backup_id, backup_state, operation, total_bytes from mysql.backup_history; backup_id backup_state operation total_bytes 270 complete backup 445734 271 complete restore 445734 check table City; Table Op Msg_type Msg_text world.City check status OK
[17 Dec 2008 11:41]
Jørgen Løland
I just repeated this bug on development branch.
[18 Dec 2008 8:22]
Jørgen Løland
Bug seems to only appear when the MyISAM driver is used. Repeating the script 10 times for each of InnoDB and Falcon (snapshot driver) and memory (default driver) does not result in corruption. As a comparison, repeating the script for MyISAM results in corruption approximately every second try.
[18 Jan 2009 12:43]
Rafal Somla
Working hypothesis. When reading trough a pipe, the backup stream library gets confused and does not detect end of a table data chunk. This is most probably the bstream_rd_part() function in stream_v1_transport.cc which should return BSTREAM_EOC when a complete chunk has been read. The problem can be seen by inserting code into bstream_rd_data_chunk() function in stream_v1.c at this place: /* Read bytes until current buffer is full or end of chunk is reached. */ while (ret == BSTREAM_OK && (to_read.end > to_read.begin)) ret= bstream_read_part(s,&to_read,*envelope); + if (to_read.begin > envelope->begin + <biggest data chunk size>) + <set breakpoint here> Then one can see that the code is trying to read more data that there is in the biggest chunk. The biggest chunk size can be determined using Ingo's mysqlbackup client (WL#4630 - not pushed at the time of writing this).
[21 Jan 2009 14:11]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/63725 2753 Rafal Somla 2009-01-21 Bug #40975 - Database restored through named pipes brings up a corrupt database. The problem was buried deep inside the backup stream library code. Inside bstream_read_part() function (in backup/stream_v1_transpor.c) there is a branch which reads bytes directly from an underlying stream to the provided input buffer (the old code): else { /* read directly into data area */ ASSERT(s->buf.header > s->buf.pos); howmuch= data->end - data->begin; if ((s->buf.pos + howmuch) > s->buf.header) howmuch= s->buf.header - s->buf.pos; saved= *data; data->end= data->begin + howmuch; ret= as_read(&s->stream, data, buf); if (ret == BSTREAM_ERROR) return SERROR(s); if (ret == BSTREAM_EOS) s->state= EOS; s->buf.begin += data->begin - saved.begin; s->buf.pos= s->buf.begin; data->begin= data->end; data->end= saved.end; } After determining how much data can be read (howmuch variable) the data blob describes the memory area where the data should be stored. as_read() function reads bytes from the underlying stream to the area described by data and updates data blob to describe the space which was *not* filled with bytes: |- the original data blob | | | ---[***************[---------]------ | | | |- bytes read --|- data --| from stream blob after the operation In the case depicted above, when not all the available area was filled with bytes from the stream and data blob is non-empty, the assignment data->begin= data->end incorrectly modifies data->begin pointer which in this situation is different from data->end. This causes further confusion in the internal buffering code. The assignment is completely wrong and serves no purpose here - the patch removes it. This problem was not detected before due to the fact that in the usual situation the whole available area is filled with data from the stream so that after as_read() data->begin == data->end. In this situation the assignment has no effect. However, when reading from a pipe, not always enough bytes is available in the stream to fill the buffer and above situation can happen. In the test we trigger the errornous situation using error injection code.
[22 Jan 2009 10:04]
Jørgen Løland
Good to push
[22 Jan 2009 17:34]
Chuck Bell
Approved
[9 Feb 2009 9:07]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/65571 2762 Rafal Somla 2009-02-09 Bug #40975 - Database restored through named pipes brings up a corrupt database. The problem was buried deep inside the backup stream library code. Inside bstream_read_part() function (in backup/stream_v1_transpor.c) there is a branch which reads bytes directly from an underlying stream to the provided input buffer (the old code): else { /* read directly into data area */ ASSERT(s->buf.header > s->buf.pos); howmuch= data->end - data->begin; if ((s->buf.pos + howmuch) > s->buf.header) howmuch= s->buf.header - s->buf.pos; saved= *data; data->end= data->begin + howmuch; ret= as_read(&s->stream, data, buf); if (ret == BSTREAM_ERROR) return SERROR(s); if (ret == BSTREAM_EOS) s->state= EOS; s->buf.begin += data->begin - saved.begin; s->buf.pos= s->buf.begin; data->begin= data->end; data->end= saved.end; } After determining how much data can be read (howmuch variable) the data blob describes the memory area where the data should be stored. as_read() function reads bytes from the underlying stream to the area described by data and updates data blob to describe the space which was *not* filled with bytes: |- the original data blob | | | ---[***************[---------]------ | | | |- bytes read --|- data --| from stream blob after the operation In the case depicted above, when not all the available area was filled with bytes from the stream and data blob is non-empty, the assignment data->begin= data->end incorrectly modifies data->begin pointer which in this situation is different from data->end. This causes further confusion in the internal buffering code. The assignment is completely wrong and serves no purpose here - the patch removes it. This problem was not detected before due to the fact that in the usual situation the whole available area is filled with data from the stream so that after as_read() data->begin == data->end. In this situation the assignment has no effect. However, when reading from a pipe, not always enough bytes is available in the stream to fill the buffer and above situation can happen. In the test we trigger the errornous situation using error injection code.
[11 Feb 2009 9:26]
Rafal Somla
Pushed to mysql-6.0-backup tree on Feb 9.
[26 Mar 2009 12:35]
Bugs System
Pushed into 6.0.11-alpha (revid:alik@sun.com-20090326121822-pt84kzxxayzho4mn) (version source revid:rafal.somla@sun.com-20090302164601-znhm4tadplfi2iqu) (merge vers: 6.0.11-alpha) (pib:6)
[8 Apr 2009 0:21]
Paul DuBois
Noted in 6.0.11 changelog. Using RESTORE to restore a database through a named pipe resulted in corrupt data.