List:Commits« Previous MessageNext Message »
From:Rafal Somla Date:January 21 2009 2:11pm
Subject:bzr commit into mysql-6.0-backup branch (Rafal.Somla:2753) Bug#40975
View as plain text  
#At file:///ext/mysql/bzr/backup/bug40975/

 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.
added:
  mysql-test/suite/backup/r/backup_stream_pipe.result
  mysql-test/suite/backup/t/backup_stream_pipe.test
modified:
  sql/backup/stream.cc
  sql/backup/stream_v1_transport.c

per-file messages:
  mysql-test/suite/backup/t/backup_stream_pipe.test
    A test repeating test scenario described in bug report.
  sql/backup/stream.cc
    Add error injection code which simulates reading from a pipe.
  sql/backup/stream_v1_transport.c
    Remove errornous code.
=== added file 'mysql-test/suite/backup/r/backup_stream_pipe.result'
--- a/mysql-test/suite/backup/r/backup_stream_pipe.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/r/backup_stream_pipe.result	2009-01-21 14:10:58 +0000
@@ -0,0 +1,38 @@
+# Initial clean-up.
+DROP DATABASE IF EXISTS db1;
+# Prepare objects to backup.
+CREATE DATABASE db1;
+USE db1;
+CREATE TABLE t1(a int, b blob);
+# Fill t1 with approx 80k of data.
+SELECT a, length(b) FROM t1;
+a	length(b)
+10	8192
+9	8192
+8	8192
+7	8192
+6	8192
+5	8192
+4	8192
+3	8192
+2	8192
+1	8192
+CHECK TABLE t1;
+Table	Op	Msg_type	Msg_text
+db1.t1	check	status	OK
+# Backup the table.
+BACKUP DATABASE db1 TO 'db1.bkp';
+backup_id
+#
+SET DEBUG='d,backup_read_simulate_pipe';
+# Restore the table.
+RESTORE FROM 'db1.bkp' OVERWRITE;
+backup_id
+#
+SET DEBUG='d,-';
+# Check for errors.
+CHECK TABLE db1.t1;
+Table	Op	Msg_type	Msg_text
+db1.t1	check	status	OK
+# Final clean-up.
+DROP DATABASE db1;

=== added file 'mysql-test/suite/backup/t/backup_stream_pipe.test'
--- a/mysql-test/suite/backup/t/backup_stream_pipe.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/t/backup_stream_pipe.test	2009-01-21 14:10:58 +0000
@@ -0,0 +1,81 @@
+#
+# Test for problem reported in BUG#40975. 
+#
+# Due to errors in stream library code, RESTORE was confused when reading 
+# image from a pipe in a situation when read call returns less bytes than 
+# requested. This manifested in getting corrupted table after restore. In 
+# this test we simulate this situation using error injection code. We check 
+# that table is no longer corrupted.
+#
+# To have a similar setup as the one presented in the bug report (the word.bak 
+# database) we create a table with enough data to span several blocks in the 
+# backup image.
+# 
+--source include/not_embedded.inc
+--source include/have_debug.inc
+
+let $bdir=`SELECT @@backupdir`;
+
+--echo # Initial clean-up.
+
+--disable_warnings
+DROP DATABASE IF EXISTS db1;
+--error 0,1
+--remove_file $bdir/db1.bkp
+--enable_warnings
+
+--echo # Prepare objects to backup.
+
+CREATE DATABASE db1;
+USE db1;
+
+CREATE TABLE t1(a int, b blob);
+
+# Single block in backup stream is 16k by default.
+# We need table data to span several such blocks.
+# We will fill t1 with 10 rows, each approx 8k long.
+
+# Create $data string of length 8k
+
+let $data=12345678;
+let $n=10;
+while ($n)
+{
+  let $data=$data$data;
+  dec $n;
+}
+
+--echo # Fill t1 with approx 80k of data.
+
+--disable_query_log
+let $n=10;
+while ($n)
+{
+  eval INSERT INTO t1 VALUES ($n,'$data');
+  dec $n;
+}
+--enable_query_log
+SELECT a, length(b) FROM t1;
+CHECK TABLE t1;
+
+--echo # Backup the table.
+--replace_column 1 #
+BACKUP DATABASE db1 TO 'db1.bkp';
+
+# Enable simulation of reading from a pipe.
+SET DEBUG='d,backup_read_simulate_pipe';
+
+--echo # Restore the table.
+--replace_column 1 #
+RESTORE FROM 'db1.bkp' OVERWRITE;
+
+SET DEBUG='d,-';
+
+--echo # Check for errors.
+
+CHECK TABLE db1.t1;
+
+--echo # Final clean-up.
+
+DROP DATABASE db1;
+--remove_file $bdir/db1.bkp

=== modified file 'sql/backup/stream.cc'
--- a/sql/backup/stream.cc	2008-12-18 21:46:36 +0000
+++ b/sql/backup/stream.cc	2009-01-21 14:10:58 +0000
@@ -165,6 +165,13 @@ extern "C" int stream_read(void *instanc
   else
 #endif
   {
+    DBUG_EXECUTE_IF("backup_read_simulate_pipe",{
+      /*
+        Simulate reading from a pipe, when less bytes is read than actually
+        requested.
+      */
+      if (howmuch > 1024) howmuch= 1024;
+    });
     howmuch= my_read(fd, buf->begin, howmuch, MYF(0));
   }
 

=== modified file 'sql/backup/stream_v1_transport.c'
--- a/sql/backup/stream_v1_transport.c	2009-01-20 11:36:32 +0000
+++ b/sql/backup/stream_v1_transport.c	2009-01-21 14:10:58 +0000
@@ -1574,7 +1574,6 @@ int bstream_read_part(backup_stream *s, 
     s->buf.begin += data->begin - saved.begin;
     s->buf.pos= s->buf.begin;
 
-    data->begin= data->end;
     data->end= saved.end;
   }
 

Thread
bzr commit into mysql-6.0-backup branch (Rafal.Somla:2753) Bug#40975Rafal Somla21 Jan
  • Re: bzr commit into mysql-6.0-backup branch (Rafal.Somla:2753)Bug#40975Jørgen Løland22 Jan
    • Re: bzr commit into mysql-6.0-backup branch (Rafal.Somla:2753)Bug#40975Chuck Bell22 Jan
    • Re: bzr commit into mysql-6.0-backup branch (Rafal.Somla:2753)Bug#40975Rafal Somla9 Feb