List:Commits« Previous MessageNext Message »
From:Rafal Somla Date:June 30 2009 8:48pm
Subject:bzr push into mysql-6.0-backup branch (Rafal.Somla:2838 to 2839)
WL#4765
View as plain text  
 2839 Rafal Somla	2009-06-30
      WL#4765 - Coverage test of backup stream library.
      
      This patch adds unittest/backup/ subdirectory containing unit tests
      for backup stream library, aimed at getting full code coverage.
      Currently, together with the backup test suite, these tests cover 
      97% of backup library code 
      (files sql/backup/{stream_v1.c,stream_v1_transport.c}).
      
      These tests link against libbackupstream library residing in sql/backup/. 
      To build them, they must be compiled after the server. This patch changes 
      the building order accordingly (i.e., unittest/ will be built after sql/).
      
      The patch also adds invocation of these unit tests for pushbuild testing. 
      However, I'm not able to test it without actually pushing this change.
      
      
      HOW TO CHECK COVERAGE
      ---------------------
      
      1. Build tree with gcov configuration. I use BUILD/compile-pentium-gcov.
      2. Clean coverage data: in mysql-test/ dir: mysql-test-lcov.pl -p
      3. Run backup test suite: mtr --suite=backup
      4. Run backup unit tests: in unittest/ dir: perl unit.pl run backup
      5. Generate coverage report: in mysql-test/ dir: mysql-test-lcov.pl
      6. Load mysql-test/mysql-test-lcov/index.html into your browser.
      
      Alternative to 5,6 is as follows:
      - cd sql/backup
      - run: gcov stream_v1*
      - look at stream_v1*.gcov files.
      
      Note: the lcov and gcov tools do not understand purecov annotations. On
      the WL page you can find attached scripts for processing .gcov files to
      hide annotated lines.
     @ Makefile.am
        Change order in which unittests are built - build them after the server, 
        so that libraries such as libbstream can be used in them.
     @ configure.in
        Add unittest/backup/Makefile.
     @ mysql-test/collections/mysql-6.0-backup.push
        I think this can make PB2 run the unit tests on every push. But it must 
        be tested and it can be tested only by pushing this patch.
     @ unittest/Makefile.am
        Add backup subdirectory to unittest/.
     @ unittest/backup/bstr_callback_errors-t.c
        Test which triggers errors in bstream callback functions.
     @ unittest/backup/bstr_data_chunks-t.c
        Test writing table data chunks of various sizes.
     @ unittest/backup/bstr_eos-t.c
        Test which simulates early end of stream when reading from backup image.
     @ unittest/backup/bstr_extra-t.c
        Various tests to cover more exotic code branches.
     @ unittest/backup/bstr_extra1-t.c
        Test errors when reading basic datatypes (only the ones which are not 
        already covered elsewhere)
     @ unittest/backup/bstr_io_errors-t.c
        Test triggering errors in low-level I/O functions.
     @ unittest/backup/bstr_random_chunks-t.c
        Test writing and reading a pseudo-random sequence of data chunks.
     @ unittest/backup/catalog.c
        Callback functions implementing backup catalogue.
     @ unittest/backup/no_catalog.c
        Trivial implementation of catalogue callbacks for tests which do not
        need a catalogue.
     @ unittest/backup/stream_test.h
        Common header for unit tests which want to test backup stream library.
     @ unittest/backup/test_main.c
        Main function for unit tests.
     @ unittest/backup/test_stream.c
        Implementation of low-level I/O operations for backup stream used
        in tests.

    added:
      unittest/backup/
      unittest/backup/Makefile.am
      unittest/backup/bstr_callback_errors-t.c
      unittest/backup/bstr_data_chunks-t.c
      unittest/backup/bstr_eos-t.c
      unittest/backup/bstr_extra-t.c
      unittest/backup/bstr_extra1-t.c
      unittest/backup/bstr_io_errors-t.c
      unittest/backup/bstr_random_chunks-t.c
      unittest/backup/catalog.c
      unittest/backup/catalog.h
      unittest/backup/no_catalog.c
      unittest/backup/stream_test.h
      unittest/backup/test_main.c
      unittest/backup/test_stream.c
    modified:
      Makefile.am
      configure.in
      mysql-test/collections/mysql-6.0-backup.push
      sql/backup/stream_v1.c
      sql/backup/stream_v1_transport.c
      unittest/Makefile.am
      unittest/README.txt
 2838 Chuck Bell	2009-06-30 [merge]
      merge with merge tree

    modified:
      mysql-test/collections/default.experimental
      mysql-test/lib/mtr_cases.pm
      mysql-test/r/information_schema.result
      mysql-test/t/disabled.def
=== modified file 'Makefile.am'
--- a/Makefile.am	2009-06-08 14:58:33 +0000
+++ b/Makefile.am	2009-06-30 20:37:35 +0000
@@ -26,8 +26,8 @@ EXTRA_DIST =		INSTALL-SOURCE INSTALL-WIN
 SUBDIRS =		. include @docs_dirs@ @zlib_dir@ \
 			@readline_topdir@ sql-common scripts \
 			@pstack_dir@ libservices \
-			@sql_union_dirs@ unittest storage plugin \
-			@sql_server@ @man_dirs@ tests \
+			@sql_union_dirs@ storage plugin \
+			@sql_server@ unittest @man_dirs@ tests \
 			netware @libmysqld_dirs@ \
 			mysql-test support-files sql-bench \
 			win

=== modified file 'configure.in'
--- a/configure.in	2009-06-17 07:30:19 +0000
+++ b/configure.in	2009-06-30 20:37:35 +0000
@@ -2945,7 +2945,7 @@ fi
 
 AC_CONFIG_FILES(Makefile extra/Makefile mysys/Makefile mysys/tests/Makefile dnl
  unittest/Makefile unittest/mytap/Makefile unittest/mytap/t/Makefile dnl
- unittest/mysys/Makefile unittest/examples/Makefile dnl
+ unittest/mysys/Makefile unittest/examples/Makefile unittest/backup/Makefile dnl
  strings/Makefile regex/Makefile storage/Makefile dnl
  man/Makefile BUILD/Makefile vio/Makefile dnl
  libmysql/Makefile libmysql_r/Makefile client/Makefile dnl

=== modified file 'mysql-test/collections/mysql-6.0-backup.push'
--- a/mysql-test/collections/mysql-6.0-backup.push	2009-04-28 06:29:39 +0000
+++ b/mysql-test/collections/mysql-6.0-backup.push	2009-06-30 20:37:35 +0000
@@ -5,3 +5,4 @@ perl mysql-test-run.pl --comment=rpl_bin
 perl mysql-test-run.pl --comment=funcs_1                                                                                                        --suite=funcs_1                                                           --experimental=collections/default.experimental --timer --force 
 perl mysql-test-run.pl --comment=ps_stm_threadpool --ps-protocol  --mysqld=--binlog-format=statement --mysqld=--thread-handling=pool-of-threads --suite=main,backup,backup_engines,backup_ptr,binlog,federated,rpl,maria  --experimental=collections/default.experimental --timer --force 
 perl mysql-test-run.pl --comment=falcon                                                                                                         --suite=falcon                                                            --experimental=collections/default.experimental --timer --force 
+perl ../unittest/unit.pl run ../unittest/backup

=== modified file 'sql/backup/stream_v1.c'
--- a/sql/backup/stream_v1.c	2009-06-26 06:29:50 +0000
+++ b/sql/backup/stream_v1.c	2009-06-30 20:37:35 +0000
@@ -209,6 +209,8 @@ int bstream_rd_preamble(backup_stream *s
     return BSTREAM_ERROR;
   CHECK_RD_OK(bstream_next_chunk(s));
 
+  /* In this version, we always write summary at the end of the stream. */ 
+  /* purecov: begin inspected */
   if (hdr->flags & BSTREAM_FLAG_INLINE_SUMMARY)
   {
     CHECK_RD_RES(bstream_rd_summary(s,hdr));
@@ -216,6 +218,7 @@ int bstream_rd_preamble(backup_stream *s
       return BSTREAM_ERROR;
     CHECK_RD_OK(bstream_next_chunk(s));
   }
+  /* purecov: end inspected */
 
   CHECK_RD_RES(bstream_rd_catalogue(s,hdr));
   if (ret == BSTREAM_EOS)
@@ -734,7 +737,7 @@ int bstream_wr_catalogue(backup_stream *
 
   /* Free iterator if not already done */
   if (it)
-    bcat_iterator_free(cat,it);
+    bcat_iterator_free(cat,it);         /* purecov: inspected */
 
   return ret;
 }
@@ -849,6 +852,13 @@ int bstream_rd_catalogue(backup_stream *
 
     CHECK_RD_RES(bstream_rd_byte(s,&flags));
 
+    /*
+      In current version we never store extra data in database catalogue entry. 
+      Flags field is always 0x00 (see bstream_wr_catalogue(), code which 
+      writes list of databases).
+     */ 
+
+    /* purecov: begin inspected */
     if (flags & BSTREAM_FLAG_HAS_EXTRA_DATA)
     {
       if (ret != BSTREAM_OK)
@@ -857,6 +867,7 @@ int bstream_rd_catalogue(backup_stream *
       CHECK_RD_OK(bstream_rd_int2(s,&len));
       CHECK_RD_RES(bstream_skip(s,len));
     }
+    /* purecov: end inspected */
 
   } while (ret == BSTREAM_OK);
 
@@ -890,7 +901,7 @@ int bstream_rd_catalogue(backup_stream *
 
   /* Free iterator if not already done */
   if (iter)
-    bcat_iterator_free(cat,iter);
+    bcat_iterator_free(cat,iter);       /* purecov: inspected */
 
   return ret;
 }
@@ -1329,7 +1340,7 @@ wr_error:
 
   /* Free iterators if not already done */
   if (titer)
-    bcat_iterator_free(cat,titer);
+    bcat_iterator_free(cat,titer);      /* purecov: inspected */
 
   if (iter) 
     bcat_iterator_free(cat,iter);
@@ -1411,7 +1422,7 @@ int bstream_rd_meta_data(backup_stream *
 
   /* Free iterator if not already done */
   if (iter) 
-    bcat_iterator_free(cat,iter);
+    bcat_iterator_free(cat,iter);       /* purecov: inspected */
 
   return ret;
 }

=== modified file 'sql/backup/stream_v1_transport.c'
--- a/sql/backup/stream_v1_transport.c	2009-06-26 06:29:50 +0000
+++ b/sql/backup/stream_v1_transport.c	2009-06-30 20:37:35 +0000
@@ -333,7 +333,7 @@ int as_write_all(struct st_abstract_stre
   return ret;
 }
 
-
+/* purecov: begin deadcode */
 /** Fill blob with bytes from stream */
 int as_read_all(struct st_abstract_stream *s, bstream_blob b, bstream_blob env)
 {
@@ -344,7 +344,7 @@ int as_read_all(struct st_abstract_strea
 
   return ret;
 }
-
+/* purecov: end */
 
 /*************************************************************************
  OUTPUT BUFFER
@@ -1214,6 +1214,7 @@ int bstream_write_part(backup_stream *s,
   return BSTREAM_OK;
 }
 
+/* purecov: begin deadcode */
 /**
   Write bytes to backup stream.
 
@@ -1231,6 +1232,7 @@ int bstream_write(backup_stream *s, bstr
 
   return bstream_write_part(s,b,buf);
 }
+/* purecov: end */
 
 /**
   Write complete blob to backup stream.
@@ -1689,7 +1691,7 @@ int bstream_read_part(backup_stream *s, 
   return check_end(s);
 }
 
-
+/* purecov: begin deadcode */
 /**
   Read bytes from backup stream.
 
@@ -1709,7 +1711,7 @@ int bstream_read(backup_stream *s, bstre
 {
   return bstream_read_part(s,b,*b);
 }
-
+/* purecov: end */
 
 /**
   Read complete blob to backup stream.
@@ -1828,6 +1830,7 @@ int bstream_next_chunk(backup_stream *s)
   return check_end(s);
 }
 
+/* purecov: begin deadcode */
 /**
   Skip given amount of bytes in the stream.
 
@@ -1842,3 +1845,4 @@ int bstream_skip(backup_stream *s, unsig
 
   return BSTREAM_OK;
 }
+/* purecov: end deadcode */

=== modified file 'unittest/Makefile.am'
--- a/unittest/Makefile.am	2009-01-07 10:58:33 +0000
+++ b/unittest/Makefile.am	2009-06-30 20:37:35 +0000
@@ -13,12 +13,13 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-SUBDIRS      = mytap mysys examples
+SUBDIRS      = mytap mysys examples backup
 
 EXTRA_DIST = unit.pl
 CLEANFILES = unit
 
-unittests = mytap mysys @mysql_se_unittest_dirs@  @mysql_pg_unittest_dirs@ ../dbug
+unittests  = mytap mysys backup 
+unittests += @mysql_se_unittest_dirs@  @mysql_pg_unittest_dirs@ ../dbug
 
 test:
 	perl unit.pl run $(unittests)

=== modified file 'unittest/README.txt'
--- a/unittest/README.txt	2006-10-20 20:56:03 +0000
+++ b/unittest/README.txt	2009-06-30 20:37:35 +0000
@@ -6,9 +6,11 @@ This is the current structure of the uni
 will be added over time.
 
 mytap                 Source for the MyTAP library
+
 mysys                 Tests for mysys components
   bitmap-t.c          Unit test for MY_BITMAP
   base64-t.c          Unit test for base64 encoding functions
+
 examples              Example unit tests.
   core-t.c            Example of raising a signal in the middle of the test
 		      THIS TEST WILL STOP ALL FURTHER TESTING!
@@ -18,6 +20,17 @@ examples              Example unit tests
   todo-t.c            Example where test contain test points that are TODO
   no_plan-t.c         Example of a test with no plan (avoid this)
 
+backup                Tests for mysql backup system
+  bstr_callback_errors-t Bstream library test: errors in callback functions
+  bstr_data_chunks-t  Bstream library test: write/read data chunks of various 
+                      sizes
+  bstr_eos-t          Bstream library test: insert EOS markers into the stream
+  bstr_extra-t        Bstream library test: misc tests
+  bstr_extra1-t       Bstream library test: more misc tests
+  bstr_io_errors-t    Bstream library test: errors in low-level I/O functions
+  bstr_random_chunks-t Bstream library test: write/read pseudo-random sequence
+                      of data chunks
+    
 
 Executing unit tests
 --------------------

=== added directory 'unittest/backup'
=== added file 'unittest/backup/Makefile.am'
--- a/unittest/backup/Makefile.am	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/Makefile.am	2009-06-30 20:37:35 +0000
@@ -0,0 +1,32 @@
+AM_CPPFLAGS= -I$(top_srcdir)/sql/backup -I$(top_srcdir)/unittest/mytap
+
+LDADD= libstreamtest.la
+
+STREAMTESTS =  bstr_callback_errors-t bstr_io_errors-t bstr_eos-t
+STREAMTESTS += bstr_data_chunks-t bstr_random_chunks-t
+STREAMTESTS += bstr_extra-t bstr_extra1-t
+
+noinst_PROGRAMS =   $(STREAMTESTS)
+
+bstr_callback_errors_t_SOURCES = bstr_callback_errors-t.c catalog.c
+bstr_data_chunks_t_SOURCES     = bstr_data_chunks-t.c     no_catalog.c
+bstr_random_chunks_t_SOURCES   = bstr_random_chunks-t.c   no_catalog.c
+bstr_io_errors_t_SOURCES       = bstr_io_errors-t.c       catalog.c
+bstr_eos_t_SOURCES             = bstr_eos-t.c             catalog.c
+bstr_extra_t_SOURCES           = bstr_extra-t.c           catalog.c
+bstr_extra1_t_SOURCES          = bstr_extra1-t.c          catalog.c
+
+#
+# Library with infrastructure for unit tests 
+#
+
+noinst_LTLIBRARIES = libstreamtest.la
+libstreamtest_la_LDFLAGS = -static
+libstreamtest_la_SOURCES = test_stream.c test_main.c
+libstreamtest_la_LIBADD = $(top_builddir)/sql/backup/libbackupstream.la \
+                          -lmytap -lmysys -ldbug -lmystrings
+libstreamtest_la_LDFLAGS+= \
+         -L$(top_builddir)/mysys \
+         -L$(top_builddir)/dbug \
+         -L$(top_builddir)/strings \
+         -L$(top_builddir)/unittest/mytap

=== added file 'unittest/backup/bstr_callback_errors-t.c'
--- a/unittest/backup/bstr_callback_errors-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_callback_errors-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,337 @@
+/** @file
+
+  Trigger errors in callback functions which are called from bstream
+  library when writing/reading backup image.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+image_header  img_header;
+
+typedef enum {WR,RD} op_type;
+
+/*
+  These helper functions look at callback counters and arrange for a specified
+  callback function to report error when called for the 1-st, 2-nd, ... time.
+  This happens during writing or reading of backup image.
+*/
+
+static int fail_iterator(op_type, item_type);
+static int fail_db_iterator(op_type, uint);
+static int fail_get_meta(item_type type);
+static int fail_item(item_type type);
+static int fail_catalog();
+static int fail_alloc();
+
+void run_test()
+{
+  uint i;
+  uint planned;
+
+  init_catalog(&img_header);
+
+  diag("Testing callback failures during writing of backup image");
+
+  planned= 1 + global_count + 4 + db_count
+           + 1 + global_count + perdb_count + 1;
+
+  /*
+    Do a clean write of backup image to fill callback_counts structure holding
+    counters for callback functions.
+  */
+  reset_counts();
+  ok(!(write_image(&img_header, "test_image.bst") || check_iterators()),
+     "Counting callbacks");
+
+  /* Failures in get_iterator */
+
+  /* iterate over all global object types present in the catalogue */
+  for (i=0; i < global_count; ++i)
+    ok(fail_iterator(WR, objects_global[i].type),
+       "Errors in bcat_iterator_get() callback (for global objects of type %d)",
+       objects_global[i].type);
+
+  /* cover remaining iterator types */
+  ok(fail_iterator(WR, BSTREAM_IT_DB),
+     "Errors in bcat_iterator_get() callback (for databases)");
+  ok(fail_iterator(WR, BSTREAM_IT_GLOBAL),
+     "Errors in bcat_iterator_get() callback (for all global objects)");
+  ok(fail_iterator(WR, BSTREAM_IT_PERDB),
+     "Errors in bcat_iterator_get() callback (for per-database objects)");
+  ok(fail_iterator(WR, BSTREAM_IT_PERTABLE),
+     "Errors in bcat_iterator_get() callback (for per-table objects)");
+
+  /* test failures in database iterators (for each database in the catalogue) */
+  for (i=0; i < db_count; ++i)
+    ok(fail_db_iterator(WR, i),
+       "Errors in bcat_db_iterator_get() callback (for database #%d)",i);
+
+  /* Failures in get_item_create_query/data */
+
+  ok(fail_get_meta(BSTREAM_IT_DB),
+     "Errors in bcat_get_item_create_query/data() callback (for databases)");
+
+  /* iterate over all global object types present in the catalogue */
+  for(i=0; i < global_count; ++i)
+    ok(fail_get_meta(objects_global[i].type),
+       "Errors in bcat_get_item_create_query/data() callback"
+       " (for objects of type %d)", objects_global[i].type);
+
+  /* iterate over all per-database object types present in the catalogue */
+  for(i=0; i < perdb_count; ++i)
+    ok(fail_get_meta(objects_perdb[i].type),
+       "Errors in bcat_get_item_create_query/data() callback"
+       " (for objects of type %d)", objects_perdb[i].type);
+
+  /* test errors in memory allocation */
+  ok(fail_alloc(WR), "Errors in memory allocation");
+
+  diag("Testing callback failures during reading of backup image");
+
+  plan(planned+= 3 + global_count + perdb_count + 2);
+
+  /* Do a clean read of the backup image to set the callback counters. */
+  reset_counts();
+  ok(!(read_image(&img_header, "test_image.bst")
+       || check_iterators()),
+     "Counting callbacks");
+
+  /*
+    The only iterator used when reading backup image is the database iterator.
+  */
+  ok(fail_iterator(RD, BSTREAM_IT_DB),
+     "Errors in bcat_iterator_get() callback (for databases)");
+
+  /* Failures in bcat_add/create_item */
+
+  ok(fail_item(BSTREAM_IT_DB),
+     "Errors in bcat_add/create_item() callback (for databases)");
+
+  for(i=0; i < global_count; ++i)
+    ok(fail_item(objects_global[i].type),
+       "Errors in bcat_add/create_item() callbacks"
+       " (for objects of type %d)", objects_global[i].type);
+
+  for(i=0; i < perdb_count; ++i)
+    ok(fail_item(objects_perdb[i].type),
+       "Errors in bcat_add/create_item() callbacks"
+       " (for objects of type %d)", objects_perdb[i].type);
+
+  /* test failures in misc catalogue managing callbacks */
+  ok(fail_catalog(), "Errors in bcat_reset/close() callbacks");
+
+  /* test errors in memory allocation */
+  ok(fail_alloc(RD), "Errors in memory allocation");
+}
+
+
+/**************************************************************************
+
+  Implementation of helper functions.
+
+**************************************************************************/
+
+int fail_callback(op_type, int*, uint);
+
+/**
+  Read or write image triggering errors in bcat_iterator_get().
+
+  @param[IN] rd_or_wr   tells whether image should be written or read; if
+                        reading is specified, then "test_image.bst" backup
+                        image should be created earlier.
+  @param[IN] type       specifies which iterator should fail.
+
+  @returns True if test went as expected, that is, read/write operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_iterator(op_type rd_or_wr, item_type type)
+{
+  int *ctr;
+
+  DBUG_ASSERT(type < 128);
+
+  ctr= &(callback_counts.iterator_get[type]);
+  return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/**
+  Read or write image triggering errors in bcat_db_itearator_get().
+
+  @param[IN] rd_or_wr   tells whether image should be written or read; if
+                        reading is specified, then "test_image.bst" backup
+                        image should be created earlier.
+  @param[IN] db_num     specifies which databse's iterator should fail.
+
+  @returns True if test went as expected, that is, read/write operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_db_iterator(op_type rd_or_wr, uint db_num)
+{
+  int *ctr;
+
+  DBUG_ASSERT(db_num < db_count);
+
+  ctr= &(callback_counts.db_iterator_get[db_num]);
+  return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/**
+  Write image triggering errors in bcat_get_item_create_query/data().
+
+  @param[IN] type  The type of item for which callback should fail.
+
+  @returns True if test went as expected, that is, write operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_get_meta(item_type type)
+{
+  DBUG_ASSERT(type < 128);
+  int ret1, ret2;
+  int *ctr;
+
+  ctr= &(callback_counts.get_serialization[type]);
+  ret1= fail_callback(WR, ctr, *ctr);
+
+  ctr= &(callback_counts.get_metadata[type]);
+  ret2= fail_callback(WR, ctr, *ctr);
+
+  return ret1 || ret2;
+}
+
+
+/**
+  Read image triggering errors in bcat_add/create_item().
+
+  @param[IN] type  The type of item for which callback should fail.
+
+  @returns True if test went as expected, that is, read operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_item(item_type type)
+{
+  DBUG_ASSERT(type < 128);
+  int ret1, ret2;
+  int *ctr;
+
+  ctr= &(callback_counts.add_item[type]);
+  ret1= fail_callback(RD, ctr, *ctr);
+
+  ctr= &(callback_counts.create_item[type]);
+  ret2= fail_callback(RD, ctr, *ctr);
+
+  return ret1 || ret2;
+}
+
+
+/**
+  Read image triggering errors in bcat_reset/close().
+
+  @returns True if test went as expected, that is, read operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_catalog()
+{
+  int ret1, ret2;
+  int *ctr;
+
+  ctr= &(callback_counts.cat_reset);
+  ret1= fail_callback(RD, ctr, *ctr);
+
+  ctr= &(callback_counts.cat_close);
+  ret2= fail_callback(RD, ctr, *ctr);
+
+  return ret1 || ret2;
+}
+
+/**
+  Read/write image triggering memory allocation errors.
+
+  @returns True if test went as expected, that is, read/write operations
+  were aborted with error due to error in the callback call.
+
+  @see @c fail_callback().
+*/
+
+static
+int fail_alloc(op_type rd_or_wr)
+{
+  int *ctr= &(callback_counts.alloc);
+
+  return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/*
+  Read or write backup image triggering errors in specified callback function.
+
+  This function writes or reads backup image several times, each time arranging
+  for a specified callback to report error. The error will be reported at n-th
+  call to the callback function, where n ranges from @a howmuch to @a 1.
+
+  Triggering of error is arranged by assigning negative value to the 
+  corresponding callback counter (see documentation of @c callback_counts in
+  catalog.h). 
+
+  @param[IN] rd_or_wr   tells whether image should be written or read; if
+                        reading is specified, then "test_image.bst" backup
+                        image should be created earlier
+  @param[IN] cntr       pointer to the counter in callback_counts structure,
+                        corresponding to the callback which should be tested
+  @param[IN] howmuch    specifies how many calls to the callback function will
+                        be tested
+
+  @returns True if the test went as expected, that is, write/read operation
+  returned error each time it was performed with failing callback function.
+*/
+
+int fail_callback(op_type rd_or_wr, int *cntr, uint howmuch)
+{
+  struct st_callback_counts saved_counts= callback_counts;
+  int ret= 1, ret1;
+
+  if (!howmuch)
+  {
+    diag("No callbacks recorded");
+    return 0;
+  }
+
+  for(; howmuch; --howmuch)
+  {
+    reset_counts();
+    *cntr= -howmuch;
+
+    switch (rd_or_wr) {
+    case WR: ret1= write_image(&img_header, "fail_callback.bst"); break;
+    case RD: ret1= read_image(&img_header, "test_image.bst"); break;
+    }
+    ret= ret && ret1 && !check_iterators();
+  }
+
+  callback_counts= saved_counts;
+  return ret;
+}

=== added file 'unittest/backup/bstr_data_chunks-t.c'
--- a/unittest/backup/bstr_data_chunks-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_data_chunks-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,368 @@
+/** @file
+
+  Write to stream table data chunks of various sizes relative to the
+  stream block size. Repeat it for different block sizes.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+
+/**
+  @brief How many stream blocks to write.
+
+  When writing to stream, enough data chunks will be written to cover this
+  many stream blocks. However, no more than MAX_STREAM_LEN bytes will be
+  written.
+*/
+
+#define BLOCK_COUNT     20
+
+/**
+  @brief Limit on the size of backup stream created in this test.
+*/
+
+#define MAX_STREAM_LEN  (5*1024*1024)
+
+static int chunks(size_t block_size, size_t chunk_size);
+static int write_data_chunk(backup_stream *stream, size_t chunk_size);
+static int read_data_chunk(backup_stream *stream, size_t *data_size);
+
+/**
+  Helper function which runs @c chunks() test via @c ok() call with
+  appropriate message.
+*/
+
+void ok_chunks(size_t block_size, size_t chunk_size)
+{
+  ok(!chunks(block_size, chunk_size),
+     "Chunks of size %lu over blocks of size %lu",
+     (long unsigned)chunk_size, (long unsigned)block_size);
+}
+
+
+void run_test()
+{
+  size_t block_size[] = { 20, 1024, 4*1024, 16*1024, 1024*1024, 0 };
+  size_t bsize;
+  uint i;
+
+  plan(5*6);
+
+  for(i=0; block_size[i]; ++i)
+  {
+    bsize= block_size[i];
+
+    ok_chunks(bsize, bsize/10 + 1);
+    ok_chunks(bsize, (5*bsize)/7 + 1);
+    ok_chunks(bsize, bsize );
+    ok_chunks(bsize, (7*bsize)/5 + 1);
+    ok_chunks(bsize, 3*bsize + bsize/3);
+    ok_chunks(bsize, 10*bsize);
+  }
+}
+
+
+/**
+  Counter for table data chunks.
+
+  The counter counts how many table data chunks have been
+  written/read by write/read_data_chunk() functions. It should be
+  set to 0 before writing/reading starts.
+*/
+
+unsigned long int chunk_count=0;
+byte *data_buffer= NULL;        /**< buffer for storing chunk contents */
+
+
+/**
+  Write chunks of size @c chunk_size to stream using blocks of
+  size @block_size. Then read them back and check data integrity.
+*/
+
+int chunks(size_t block_size, size_t chunk_size)
+{
+  int  ret= 1;
+  int  alloc;
+  test_backup_stream stream;
+  size_t data_len;
+  byte   *ptr;
+
+
+  stream_block_size= block_size;
+
+  /*
+    Table data chunk header takes at least 5 bytes. We also need
+    at least 1 byte of data. Thus chunk_size can't be less than 6.
+   */
+
+  if (chunk_size < 6)
+    chunk_size= 6;
+
+  /* allocate buffer for data */
+
+  byte *mem= data_buffer= (byte*)malloc(chunk_size);
+  if (!mem)
+  {
+    diag("Failed to allocate buffer for chunk data");
+    goto end;
+  }
+
+  alloc= 10;
+
+  /* Fill buffer with data */
+
+  memset(data_buffer, 0xF5, chunk_size);
+
+  /* open stream */
+
+  if (backup_stream_open_wr(&stream, "data_chunks.bst"))
+  {
+    diag("Failed to open stream for writing");
+    goto end;
+  }
+
+  alloc= 20;
+
+  /*
+    Write enough chunks to output BLOCK_COUNT stream blocks, but no more
+    than MAX_STREAM_LEN bytes.
+  */
+  for(chunk_count=0; chunk_count*chunk_size <= BLOCK_COUNT*block_size
+                     && chunk_count*chunk_size < MAX_STREAM_LEN; )
+    if (write_data_chunk((backup_stream*)&stream, chunk_size))
+      goto end;
+
+  backup_stream_close(&stream);
+
+  alloc= 10;
+
+  /* now start reading */
+
+  if (backup_stream_open_rd(&stream, "data_chunks.bst"))
+  {
+    diag("Failed to open stream for reading");
+    goto end;
+  }
+
+  alloc= 20;
+
+  /*
+    Check that block size, which is detected when the stream is opened for
+    reading, is as we specified when writing the stream.
+  */
+  if (stream.base.block_size != block_size)
+  {
+    diag("Incorrect block size %lu instead of %lu.",
+         stream.base.block_size, (long unsigned)block_size);
+    goto end;
+  }
+
+  chunk_count= 0;
+
+  /* read chunks from the stream */
+
+  do 
+  {
+    ret= read_data_chunk((backup_stream*)&stream, &data_len);
+
+    if (ret == BSTREAM_ERROR)
+      goto end;
+
+    /* check that all bytes read are correct */
+
+    for (ptr= data_buffer; ptr < data_buffer + data_len; ++ptr)
+      if (*ptr != 0xF5)
+      {
+        diag("Data of chunk #%lu of length %lu"
+             " corrupted at byte %d which is 0x%X.",
+             chunk_count, (long unsigned)data_len, ptr-data_buffer, *ptr);
+        ret= 1;
+        goto end;
+      };
+
+  } while (ret == BSTREAM_OK);
+
+  ret= 0;
+
+ end:
+
+  /* 
+    In a moment we are going to free the buffer memory - avoid the dangling 
+    pointer. 
+  */
+  data_buffer= NULL;
+
+  if (alloc >= 20) backup_stream_close(&stream);
+  if (alloc >= 10) free(mem);
+
+  return ret;
+}
+
+
+/**
+  Write a table data chunk of the given size.
+
+  Data to be written is taken from the global buffer @c data_buffer which
+  should be allocated and of appropriate size. Size of the data
+  payload is calculated so that the complete chunk (data payload
+  + headers) has size @c chunk_size.
+
+  Apart from the data payload, the following information will be
+  stored in the table data chunk header, for debugging purposes.
+
+  table number     - size of the data payload,
+  snapshot number  - consecutive chunk number modulo 128,
+  flags            - 0x0 (not used)
+
+  @returns 0 on success.
+*/
+
+int write_data_chunk(backup_stream *stream, size_t chunk_size)
+{
+  struct st_bstream_data_chunk dchunk;
+  size_t data_size= chunk_size;
+
+  memset(&dchunk, 0, sizeof(dchunk));
+
+  ++chunk_count;
+
+  dchunk.table_num=  data_size;
+  dchunk.snap_num=   chunk_count & 0x7F;
+  dchunk.data.begin= data_buffer;
+  dchunk.data.end=   data_buffer+data_size;
+
+  if (BSTREAM_OK != bstream_wr_data_chunk(stream, &dchunk))
+  {
+    diag("Error when writing data chunk #%lu", chunk_count);
+    return 1;
+  }
+
+  return 0;
+}
+
+
+int read_data_chunk1(backup_stream *stream, size_t *len);
+int read_data_chunk2(backup_stream *stream, size_t *len);
+
+/**
+  Read next table data chunk.
+
+  Read either a complete chunk using @c read_data_chunk1() function
+  or just an initial fragment woith @c read_data_chunk2() function.
+
+  After reading the chunk, global pointer @c data_buffer is set to point
+  at the data payload. The size of the data is stored in @c data_size
+  which should not be NULL.
+
+  @retval BSTREAM_OK  Data chunk has been read and there are more in the stream.
+  @retval BSTREAM_EOS Data chunk has been read and end of stream has been 
+                      reached.
+  @retcal BSTREAM_ERROR Error when reading data chunk.
+*/
+
+int read_data_chunk(backup_stream *stream, size_t *len)
+{
+  struct st_bstream_data_chunk dchunk;
+  memset(&dchunk, 0, sizeof(dchunk));
+
+  ++chunk_count;
+
+  /* every now and then read data chunk using different logic */
+  if (0 == chunk_count % 7)
+    return read_data_chunk2(stream, len);  /* read only fragment of the chunk */
+  else
+    return read_data_chunk1(stream, len);  /* read complete chunk */
+}
+
+
+/**
+  Read complete table data chunk.
+
+  Read chunk using @c bstream_rd_data_chunk() function. If the size of the
+  chunk stored as table number does not equal the actual size then error is
+  reported.
+
+  @retval BSTREAM_OK  Data chunk has been read and there are more in the stream.
+  @retval BSTREAM_EOS Data chunk has been read and end of stream has been 
+                      reached.
+  @retcal BSTREAM_ERROR Error when reading data chunk.
+*/
+
+int read_data_chunk1(backup_stream *stream, size_t *len)
+{
+  struct st_bstream_data_chunk dchunk;
+  int ret;
+  size_t data_size;
+
+  DBUG_ASSERT(len);
+
+  memset(&dchunk, 0, sizeof(dchunk));
+
+  ret= bstream_rd_data_chunk(stream, &dchunk);
+
+  if (ret == BSTREAM_ERROR)
+  {
+    diag("Error reading data chunk #%lu.", chunk_count);
+    return BSTREAM_ERROR;
+  }
+
+  if (ret == BSTREAM_EOC)
+  {
+    diag("Invalid chunk encountered (#%lu).", chunk_count);
+    return BSTREAM_ERROR;
+  }
+
+  data_size= dchunk.data.end - dchunk.data.begin;
+
+  if (dchunk.table_num != data_size)
+  {
+    diag("Invalid data size in chunk #%lu (%u): %lu instead of %lu",
+         chunk_count, dchunk.snap_num, dchunk.table_num,
+         (long unsigned)data_size);
+    return BSTREAM_ERROR;
+  }
+
+  data_buffer= dchunk.data.begin;
+  *len= data_size;
+
+  return ret;
+}
+
+
+int bstream_read_part(backup_stream *s, blob *data, blob buf);
+
+/**
+  Read first 1024 bytes of a table data chunk and skip the rest.
+*/
+
+int read_data_chunk2(backup_stream *stream, size_t *len)
+{
+  short unsigned int       snap_num, flags;
+  unsigned int             seq_num;
+  long unsigned int        table_num;
+  byte    buf[1024];
+  blob    to_read, envelope;
+  int ret= BSTREAM_OK;
+
+  /* read table data chunk header */
+  bstream_rd_byte(stream, &snap_num);
+  bstream_rd_int2(stream, &seq_num);
+  bstream_rd_byte(stream, &flags);
+  bstream_rd_num(stream, &table_num);
+
+  to_read.begin= buf;
+  to_read.end=   buf+1024;
+  envelope= to_read;
+  /*
+    Read bytes until current buffer is full or end of chunk is reached.
+   */
+  while ((ret == BSTREAM_OK) && (to_read.begin < to_read.end))
+    ret= bstream_read_part(stream,&to_read,envelope);
+
+  data_buffer= buf;
+  *len= to_read.begin - buf;
+
+  /* Move to next chunk */
+  return ret==BSTREAM_ERROR ? BSTREAM_ERROR : bstream_next_chunk(stream);
+}

=== added file 'unittest/backup/bstr_eos-t.c'
--- a/unittest/backup/bstr_eos-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_eos-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,422 @@
+/** @file
+
+  Prematurely report end of stream when reading backup image. EOS is
+  reported after reading given amount of stream chunks.
+
+  Test uses in-memory stream. When backup image is written, it is stored
+  in a memory buffer. Then the image is analysed to find chunk boundaries.
+  Finally, image is read from memory several times. Each time read is
+  interrupted by EOS signal at different stages.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+#define FR_EOC    0x80  /**< bits for EOC fragment */
+#define FR_EOS    0xC0  /**< bits for EOS fragment */
+#define FR_MORE   0x00  /**< bits for MORE fragment */
+#define FR_LAST   0x40  /**< bits for LAST fragment */
+
+static int mem_write_image(image_header *hdr);
+static int mem_read_image(image_header *hdr);
+static int mem_read_lowlevel();
+static void get_chunk_boundaries();
+
+#define BSIZE   1024    /**< Size of stram i/o block */
+
+/**
+  @brief Buffer for in-memory stream.
+
+  We will store only the first block of the image, hence only that much
+  space is needed.
+*/
+
+byte image[BSIZE];
+byte *stream_pos= image;  /**< current position in the buffer */
+byte *stop_at= NULL;      /**< position at which reading will be interrupted */
+int  early_eos= BSTREAM_EOS; /**< value to return when reding is interrupted */
+
+/**
+  @brief Array of pointers to chunk boundaries.
+
+  This is filled by @c get_chunk_boundaries().
+*/
+
+byte *chunk[128];
+
+/**
+  @brief The number of chunks found in the image.
+
+  This is set by @c get_chunk_boundaries().
+*/
+
+uint chunk_count= 0;
+
+
+void run_test()
+{
+  image_header  hdr;
+  int  i;
+  byte header;
+
+  init_catalog(&hdr);
+
+  ok(!mem_write_image(&hdr), "Writing backup image");
+
+  get_chunk_boundaries();
+
+  plan(1 + 2*chunk_count + 3*(chunk_count-3));
+
+  /*
+    In what follows we will read the image interrupting low-level read at each
+    chunk boundary. First we instruct low-level read to report ERROR at
+    interruption. Then, it will report EOS.
+  */
+
+  early_eos= BSTREAM_ERROR;
+
+  for (i= chunk_count-1; i >= 0; --i)
+  {
+    stop_at= chunk[i];
+    ok(BSTREAM_ERROR == mem_read_image(&hdr),
+       "ERROR at chunk %d when reading image", i);
+  }
+
+  /*
+    The low-level read subroutine does not interpret the image. It merely
+    scans all data chunks.
+  */
+  for (i= chunk_count-1; i >= 0; --i)
+  {
+    stop_at= chunk[i];
+    ok(BSTREAM_ERROR == mem_read_lowlevel(&hdr),
+       "ERROR at chunk %d when reading chunks", i);
+  }
+
+  early_eos= BSTREAM_EOS;
+
+  /*
+    There are "chunks" at the end of the stream which function as end
+    markers. The reading logic works fine if these are missing
+    due to early EOS. Hence we don't expect bstream library to report
+    errors for the few last chunks and we skip them.
+   */
+
+  for (i= chunk_count-4; i >= 0; --i)
+  {
+    stop_at= chunk[i];
+    ok(BSTREAM_ERROR == mem_read_image(&hdr),
+       "EOS at chunk %d when reading image", i);
+  }
+
+  /*
+    In the next loop we store EOS marker at the end of each chunk, so that the
+    end of stream is detected by bstream logic before it is reported by
+    low-level I/O.
+  */
+
+  for (i= chunk_count-4; i >= 0; --i)
+  {
+    header= *chunk[i];
+    *chunk[i]= FR_EOS;
+    stop_at= chunk[i]+1;
+    ok(BSTREAM_ERROR == mem_read_image(&hdr),
+       "EOS marker at chunk %d when reading image", i);
+    *chunk[i]= header;
+  }
+
+  for (i= chunk_count-4; i >= 0; --i)
+  {
+    header= *chunk[i];
+    *chunk[i]= FR_EOS;
+    stop_at= chunk[i]+1;
+    ok(BSTREAM_EOS == mem_read_lowlevel(&hdr),
+       "EOS marker at chunk %d when reading chunks", i);
+    *chunk[i]= header;
+  }
+}
+
+
+/*********************************************************************
+
+  Helper functions for reading/writing backup image.
+
+*********************************************************************/
+
+static int mem_write(void*, blob*, blob);
+static int mem_read(void*, blob*, blob);
+static int mem_forward(void*, unsigned long int*);
+
+static int mem_write_image(image_header *hdr)
+{
+  backup_stream stream;
+  int ret;
+
+  stream.stream.write= mem_write;
+  stream.stream.read= mem_read;
+  stream.stream.forward= mem_forward;
+  stream_pos= image;
+
+  reset_counts();
+
+  if (bstream_open_wr(&stream, 1024, 0))
+    return OPEN_ERROR;
+
+  ret= bstream_wr_preamble(&stream, hdr);
+  bstream_close(&stream);
+
+  return check_iterators() ? BSTREAM_ERROR : ret;
+}
+
+
+static int mem_read_image(image_header *hdr)
+{
+  backup_stream stream;
+  int ret;
+
+  stream.stream.write= mem_write;
+  stream.stream.read= mem_read;
+  stream.stream.forward= mem_forward;
+  stream_pos= image;
+
+  reset_counts();
+
+  if (bstream_open_rd(&stream, 0))
+    return OPEN_ERROR;
+
+  ret= bstream_rd_preamble(&stream, hdr);
+  bstream_close(&stream);
+
+  return check_iterators() ? BSTREAM_ERROR : ret;
+}
+
+
+static int mem_read_lowlevel()
+{
+  backup_stream stream;
+  unsigned short int x;
+  int ret;
+
+  stream.stream.write= mem_write;
+  stream.stream.read= mem_read;
+  stream.stream.forward= mem_forward;
+  stream_pos= image;
+
+  if (bstream_open_rd(&stream, 0))
+    return OPEN_ERROR;
+
+  do {
+    ret= bstream_rd_byte(&stream, &x);
+    ret= bstream_next_chunk(&stream);
+  } while (ret == BSTREAM_OK);
+
+  bstream_close(&stream);
+
+  return ret;
+}
+
+
+/*********************************************************************
+
+  Implementation of in-memory stream.
+
+*********************************************************************/
+
+static
+int mem_write(void *stream __attribute__ ((__unused__)),
+              bstream_blob *buf,
+              bstream_blob envelope __attribute__ ((__unused__)))
+{
+  int ret= BSTREAM_OK;
+
+  DBUG_ASSERT(buf);
+
+  if (!buf->begin || buf->begin == buf->end)
+    return BSTREAM_OK;
+
+  DBUG_ASSERT(buf->end);
+
+  size_t howmuch = buf->end - buf->begin;
+  if (stream_pos + howmuch >= image + 1024)
+  {
+    howmuch= image + 1024 - stream_pos;
+    ret= BSTREAM_EOS;
+  }
+
+  memmove(stream_pos, buf->begin, howmuch);
+
+  stream_pos+= howmuch;
+  buf->begin+= howmuch;
+
+  return ret;
+}
+
+
+static
+int mem_read(void *stream __attribute__ ((__unused__)),
+             bstream_blob *buf,
+             bstream_blob envelope __attribute__ ((__unused__)))
+{
+  size_t howmuch;
+  int ret= BSTREAM_OK;
+
+  DBUG_ASSERT(buf);
+
+  if (!buf->begin || buf->begin == buf->end)
+    return BSTREAM_OK;
+
+  DBUG_ASSERT(buf->end);
+
+  if (stream_pos == stop_at)
+    return early_eos;
+
+  howmuch= buf->end - buf->begin;
+  if (stream_pos + howmuch >= image + 1024)
+  {
+    howmuch= image + 1024 - stream_pos;
+    ret= BSTREAM_EOS;
+  }
+  if (stop_at && stream_pos + howmuch > stop_at)
+  {
+    howmuch= stop_at - stream_pos;
+    ret= BSTREAM_EOS;
+  }
+
+  memmove(buf->begin, stream_pos, howmuch);
+
+  stream_pos += howmuch;
+  buf->begin += howmuch;
+
+  return ret;
+}
+
+
+static
+int mem_forward(void *stream __attribute__ ((__unused__)),
+                unsigned long int *offset)
+{
+  int ret= BSTREAM_OK;
+  size_t howmuch;
+
+  DBUG_ASSERT(offset);
+
+  if (*offset == 0)
+    return BSTREAM_OK;
+
+  howmuch= *offset;
+  if (stream_pos + howmuch >= image + 1024)
+  {
+    howmuch= image + 1024 - stream_pos;
+    ret= BSTREAM_EOS;
+  }
+
+  stream_pos+= howmuch;
+  *offset-= howmuch;
+
+  return ret;
+}
+
+
+/*********************************************************************
+
+  Code for analysing chunk boundaries (taken from stream_v1_transport.c).
+
+*********************************************************************/
+
+#define FR_TYPE_MASK  0xC0  /**< type bits for mask */
+#define FR_LEN_MASK   0x3F  /**< type length for mask */
+
+/** biggest size of small fragment */
+#define FR_SMALL_MAX  ((size_t)FR_LEN_MASK)
+#define FR_BIG        0x80        /**< type bits for big fragment */
+#define FR_HUGE       0xC0        /**< type bits for huge fragment */
+#define FR_BIG_SHIFT  6           /**< value shift for big fragment */
+#define FR_HUGE_SHIFT 12          /**< value shift for huge fragment */
+/** header for the biggest possible chunk */
+#define FR_HUGE_MAX_HDR (FR_HUGE|FR_LEN_MASK)
+/** size of the biggest possible chunk */
+#define FR_HUGE_MAX_LEN ((size_t)FR_LEN_MASK << FR_HUGE_SHIFT)
+
+
+static
+int read_fragment_header(byte **header)
+{
+  byte hdr= *((*header)++);
+  size_t len= hdr & FR_LEN_MASK; /* 6 bit value stored in the header */
+  int last;
+
+  if (hdr == FR_EOS)
+    return BSTREAM_EOS;
+
+  if (hdr == FR_EOC)
+    return BSTREAM_EOC;
+
+  last= hdr & FR_LAST ? FR_LAST : FR_MORE; /* is it last fragment of a chunk? */
+
+  /*
+    If len == 0 then we have a fragment which extends to the end of a block,
+    thus we restore *header to signal that fact to the caller.
+   */
+  if (len == 0)
+  {
+    (*header)--;
+    return last;
+  }
+
+  /*
+    If the highest bit is set, we are looking at a big or huge fragment. Such
+    fragment is never the last fragment of a chunk.
+   */
+  if (hdr & 0x80)
+  {
+    last= FR_MORE;
+
+    if ((hdr & FR_TYPE_MASK) == FR_BIG)
+      len <<= FR_BIG_SHIFT;
+
+    if ((hdr & FR_TYPE_MASK) == FR_HUGE)
+      len <<= FR_HUGE_SHIFT;
+  }
+
+  /* move *header pointer to the first byte after the fragment */
+  *header += len;
+
+  return last;
+}
+
+
+void get_chunk_boundaries()
+{
+  byte *fhead= image+5;
+  byte *saved_fhead;
+
+  chunk_count=0;
+
+  chunk[chunk_count++]= fhead;
+
+  while (fhead < image + 1024 && chunk_count < 127)
+  {
+    saved_fhead= fhead;
+    switch (read_fragment_header(&fhead)) {
+
+    case BSTREAM_EOC:
+    case FR_LAST:
+      if (fhead > saved_fhead)
+        chunk[chunk_count++]= fhead;
+      else
+        fhead = image + 1024;
+      break;
+
+    case BSTREAM_EOS:
+      chunk[chunk_count++]= saved_fhead;
+      fhead= image + 1024;
+
+    case FR_MORE: break;
+
+    }
+  }
+
+  chunk[chunk_count]= NULL;
+}

=== added file 'unittest/backup/bstr_extra-t.c'
--- a/unittest/backup/bstr_extra-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_extra-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,114 @@
+/** @file
+
+  Execute some "border cases" to exercise bstream library code. This includes
+  incomplete backup image preamble, incorrect snapshot/item types and
+  writing/reading image with no objects.
+*/
+#include <tap.h>
+#include "stream_test.h"
+#include "catalog.h"
+
+/* Import some low-level functions from bstream library */
+int bstream_wr_header(backup_stream*, struct st_bstream_image_header*);
+int bstream_wr_catalogue(backup_stream*, struct st_bstream_image_header*);
+int bstream_end_chunk(backup_stream*);
+
+image_header  img_header;
+
+
+void run_test()
+{
+  test_backup_stream stream;
+  int ret1, ret2;
+
+  plan(5);
+
+  init_catalog(&img_header);
+
+  /* write/read only image header */
+
+  reset_counts();
+  if (backup_stream_open_wr(&stream, "extra.bst"))
+  {
+    diag("Failed to open stream for writing");
+    return;
+  };
+  ret1= bstream_wr_header((backup_stream*)&stream, &img_header);
+  backup_stream_close(&stream);
+  ret1= ret1 || check_iterators();
+
+  reset_counts();
+  if (backup_stream_open_rd(&stream, "extra.bst"))
+  {
+    diag("Failed to open stream for reading");
+    return;
+  };
+  ret2= bstream_rd_preamble((backup_stream*)&stream, &img_header);
+  backup_stream_close(&stream);
+  ret1= ret1 || check_iterators();
+
+  ok(!ret1 && ret2, "Incomplete preamble: only header");
+
+  /* write/read preamble with metadata section missing */
+
+  reset_counts();
+  if (backup_stream_open_wr(&stream, "extra.bst"))
+  {
+    diag("Failed to open stream for writing");
+    return;
+  };
+  ret1= bstream_wr_header((backup_stream*)&stream, &img_header);
+  ret1= ret1 || bstream_end_chunk((backup_stream*)&stream);
+  ret1= ret1 || bstream_wr_catalogue((backup_stream*)&stream, &img_header);
+  backup_stream_close(&stream);
+  ret1= ret1 || check_iterators();
+
+  reset_counts();
+  if (backup_stream_open_rd(&stream, "extra.bst"))
+  {
+    diag("Failed to open stream for reading");
+    return;
+  };
+  ret2= bstream_rd_preamble((backup_stream*)&stream, &img_header);
+  backup_stream_close(&stream);
+  ret1= ret1 || check_iterators();
+
+  ok(!ret1 && ret2, "Incomplete preamble: no metadata");
+
+
+  /* write/read snapshot of unknown type */
+
+  snapshots[0]= 333;      /* unknown snapshot type */
+  init_catalog(&img_header);
+
+  reset_counts();
+  ret1= write_image(&img_header, "unknown_snapshot.bst") || check_iterators();
+  reset_counts();
+  ret2= read_image(&img_header, "unknown_snapshot.bst");
+  ret1= ret1 || check_iterators();
+
+  ok(!ret1 && ret2, "Image with unknown snapshot type");
+
+  /* try writing image with unknown item type (it should fail) */
+
+  objects_perdb[0].type= 333;
+  init_catalog(&img_header);
+
+  reset_counts();
+  ret1= write_image(&img_header, "unknown_item.bst");
+  ret2= check_iterators();
+
+  ok(ret1 && !ret2, "Writing image with unknown item type");
+
+  /* write/read empty backup image (also no databases) */
+  opt_empty_catalog= 1;
+  img_header.snap_count= 0;
+
+  reset_counts();
+  ret1= write_image(&img_header, "empty.bst") || check_iterators();
+  reset_counts();
+  ret2= read_image(&img_header, "empty.bst") || check_iterators();
+
+  ok(!ret1 && !ret2, "Empty image");
+
+}

=== added file 'unittest/backup/bstr_extra1-t.c'
--- a/unittest/backup/bstr_extra1-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_extra1-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,160 @@
+/* @file
+
+  Provoke errors when reading basic data types, so that error handling branch
+  in the code is executed. This is done only for datatypes for which error
+  branches are not covered in other tests.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+
+/* import some low-level functions from bstream library */
+
+int  bstream_rd_item_type(backup_stream*, item_type*);
+int  bstream_write_blob(backup_stream*, blob);
+int  bstream_end_chunk(backup_stream*);
+void bstream_free(byte*);
+
+/**
+  Dummy low-level read function which always returns error.
+*/
+
+int dummy_read(void *stream, blob *buf, blob envelope);
+
+void run_test()
+{
+  test_backup_stream stream;
+  backup_stream *bstr= (backup_stream*)&stream;
+
+  /*
+    Note: buffer size must be odd as only then expected error branch is taken.
+  */
+  byte buf[127];
+
+  blob data;
+  unsigned int num;
+  long unsigned int lnum;
+  int ret= BSTREAM_OK;
+
+  /* fill buffer with 0x01 bytes */
+  memset(buf, 0x01, sizeof(buf));
+  data.begin= buf;
+  data.end=   buf + sizeof(buf);
+
+  stream_block_size= 256;
+
+  plan(3 + 3 + 3);
+
+  /* write 0x01 bytes to stream */
+
+  ret= backup_stream_open_wr(&stream, "extra1.bst");
+  if (ret)
+  {
+    diag("Failed to open stream for writing");
+    return;
+  }
+
+  ret= bstream_write_blob(bstr, data);
+  ok(BSTREAM_OK == ret, "Writing bytes to stream");
+
+  /*
+    execute flush twice to enter branch where flush is made with empty output
+    buffer
+  */
+  ret= bstream_flush(bstr);
+  ret= ret || bstream_flush(bstr);
+  ok(!ret, "Flushing flushed stream");
+
+  backup_stream_close(&stream);
+
+  /*
+     call close on already closed stream to cover corresponding branch in the
+     code
+  */
+  ret= bstream_close(bstr);
+  ok(BSTREAM_OK == ret, "Closing already closed stream");
+
+  ret= backup_stream_open_rd(&stream, "extra1.bst");
+  if (ret)
+  {
+    diag("Failed to open stream for reading");
+    return;
+  }
+
+  /*
+    Call writing functions on stream opened for reading.
+   */
+  ret= bstream_write_blob(bstr, data);
+  ok(BSTREAM_ERROR == ret, "Writing bytes to stream opened for reading");
+  ret= bstream_end_chunk(bstr);
+  ok(BSTREAM_ERROR == ret, "Closing chunk in stream opened for reading");
+  ret= bstream_flush(bstr);
+  ok(BSTREAM_ERROR == ret, "Flushing stream opened for reading");
+
+  /*
+    After setting read function to dummy_read, error will be hit by bstream
+    library next time it tries to reload input buffer.
+  */
+  stream.base.stream.read= dummy_read;
+
+  /*
+   In what follows, various basic types of data are read from the stream. This
+   is done in a loop, so that eventually the bstream input buffer will be
+   reloaded and error will be hit.
+  */
+
+  do
+  {
+    ret= bstream_rd_string(bstr, &data);
+    if (!ret)
+      bstream_free(data.begin);
+  } while (ret == BSTREAM_OK);
+
+  backup_stream_close(&stream);
+  ok(BSTREAM_ERROR == ret, "Error when reading strings");
+
+  ret= backup_stream_open_rd(&stream, "extra1.bst");
+  if (ret)
+  {
+    diag("Failed to open stream for reading");
+    return;
+  }
+  stream.base.stream.read= dummy_read;
+
+  do
+  {
+    ret= bstream_rd_int4(bstr, &lnum);
+  } while (ret == BSTREAM_OK);
+
+  backup_stream_close(&stream);
+  ok(BSTREAM_ERROR == ret, "Error when reading int4 numbers");
+
+  ret= backup_stream_open_rd(&stream, "extra1.bst");
+  if (ret)
+  {
+    diag("Failed to open stream for reading");
+    return;
+  }
+  stream.base.stream.read= dummy_read;
+
+  do
+  {
+    ret= bstream_rd_int2(bstr, &num);
+  } while (ret == BSTREAM_OK);
+
+  bstream_next_chunk(bstr);
+  bstream_next_chunk(bstr);
+
+  backup_stream_close(&stream);
+  ok(BSTREAM_ERROR == ret, "Error when reading int2 numbers");
+
+}
+
+
+int dummy_read(void *stream  __attribute__ ((__unused__)),
+               blob *buf     __attribute__ ((__unused__)),
+               blob envelope __attribute__ ((__unused__)))
+{
+  return BSTREAM_ERROR;
+}

=== added file 'unittest/backup/bstr_io_errors-t.c'
--- a/unittest/backup/bstr_io_errors-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_io_errors-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,75 @@
+/** @file
+
+  Write and read backup image triggering errors during low-level write/read
+  calls.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+image_header  img_header;
+
+
+void run_test()
+{
+  int i;
+  int  can_pass;
+  uint planned;
+
+  init_catalog(&img_header);
+
+  diag("Testing writing of backup image");
+
+  reset_io_counts();
+  ok(!write_image(&img_header, "io_errors.bst") && !check_iterators(),
+     "Counting i/o write calls");
+
+  diag("Made %lu write calls", io_counts.write);
+  diag("Wrote %lu bytes", (long unsigned)io_counts.write_bytes);
+
+  planned= 1 + 2*io_counts.write;
+
+  /*
+    Errors during last 2 write calls (which happen during closing of a stream)
+    are not reported by bstream library
+  */
+  can_pass= 2;
+
+  for(i= io_counts.write; i > 0 ; --i)
+  {
+    reset_io_counts();
+    io_counts.write= -i;
+    ok((write_image(&img_header, "write_fail.bst") && !check_iterators())
+       || can_pass, "Failing %d-th write call", i);
+
+    reset_io_counts();
+    ok((read_image(&img_header, "write_fail.bst") && !check_iterators())
+       || can_pass, "Reading resulting image");
+  
+    if (can_pass)
+      --can_pass;
+  }
+
+  diag("Testing reading of backup image");
+
+  reset_io_counts();
+  ok(!read_image(&img_header, "io_errors.bst") && !check_iterators(),
+     "Counting i/o read calls");
+
+  diag("Made %lu read calls", io_counts.read);
+  diag("Read %lu bytes", (long unsigned)io_counts.read_bytes);
+
+  plan(planned+= 1 + io_counts.read);
+
+  for(i= io_counts.read; i > 0 ; --i)
+  {
+    reset_io_counts();
+    io_counts.read= -i;
+    ok(read_image(&img_header, "io_errors.bst") && !check_iterators(),
+       "Failing %d-th read call", i);
+  }
+
+}
+

=== added file 'unittest/backup/bstr_random_chunks-t.c'
--- a/unittest/backup/bstr_random_chunks-t.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_random_chunks-t.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,244 @@
+/* @file
+
+  In order to excersise bstream library code, read and write a pseudo-random
+  sequence of chunks of varied sizes.
+*/
+#include <tap.h>
+#include "stream_test.h"
+
+/* import some low-level function from bstream library */
+
+int bstream_flush(backup_stream *s);
+int bstream_end_chunk(backup_stream *s);
+int bstream_write_blob(backup_stream *s, bstream_blob buf);
+int bstream_wr_int4(backup_stream *s, unsigned long int x);
+int bstream_read_part(backup_stream *s, bstream_blob *data, bstream_blob buf);
+
+/** Determines how big image will be created */
+#define DATA_AMOUNT 10*1024*1024
+/** Stream block size to use */
+#define BSIZE       4*1024
+
+/**
+  List of chunk sizes which will be used in the pseudo-random sequence.
+*/
+size_t chunk_size[] = { 10,
+                        BSIZE/10,
+                        (5*BSIZE)/7,
+                        (7*BSIZE)/5,
+                        500*1024 };
+#define CSIZE_NUM   5   /**< Size of @c chunk_size[] array */
+
+
+static uint hash(uint mod, size_t key);
+static int write_chunk(backup_stream *stream, size_t chunk_size);
+static int read_chunk(backup_stream *stream, size_t *len);
+/**
+  Chunk counter increased by @c write/read_chunk() functions.
+*/
+static unsigned long int chunk_count=0;
+
+void run_test()
+{
+  test_backup_stream stream;
+  size_t byte_count;
+  size_t csize;
+  int    ret;
+
+  stream_block_size= BSIZE;
+
+  diag("Writing");
+
+  ret= backup_stream_open_wr(&stream, "random.bst");
+  if (ret)
+  {
+    diag("Failed to open stream for writing");
+    return;
+  }
+
+  chunk_count= 0;
+
+  for(byte_count=0 ; byte_count < DATA_AMOUNT ; )
+  {
+    csize= chunk_size[hash(CSIZE_NUM, chunk_count)];
+
+    ok(!write_chunk((backup_stream*)&stream, csize),
+       "Wrote chunk #%lu of size %lu bytes", chunk_count, (long unsigned)csize);
+
+    /*
+      Flush stream every now and then to exercise more code in bstream library.
+     */
+    if (chunk_count % 7 == 0)
+      bstream_flush((backup_stream*)&stream);
+
+    byte_count+= csize;
+  }
+
+  backup_stream_close(&stream);
+
+  diag("Reading");
+
+  plan(2*chunk_count + 2);
+
+  ret= backup_stream_open_rd(&stream, "random.bst"); 
+  if (ret)
+  {
+    diag("Failed to open stream for reading");
+    return;
+  }
+
+  chunk_count= 0;
+
+  do {
+
+    ret= read_chunk((backup_stream*)&stream, &byte_count);
+    ok(BSTREAM_ERROR != ret,
+       "Reading chunk #%lu of size %lu bytes",
+       chunk_count, (long unsigned)byte_count);
+
+    if (ret == BSTREAM_ERROR)
+      goto end;
+
+  } while (ret == BSTREAM_OK);
+
+  /* try to read yet another chunk to hit EOS */
+
+  ret= read_chunk((backup_stream*)&stream, &byte_count);
+  ok(BSTREAM_ERROR == ret,"Reading another chunk after EOS was hit");
+
+  ret= bstream_next_chunk((backup_stream*)&stream);
+  ok(BSTREAM_EOS == ret,"Moving to next chunk after EOS was hit");
+
+ end:
+
+  backup_stream_close(&stream);
+}
+
+/**
+  Hash function returning number in given range.
+
+  @param[IN] mod   function will return number in range 0 .. (mod-1)
+  @param[IN] key   function argument
+
+  @note Function uses sdbm algorithm from http://www.cse.yorku.ca/~oz/hash.html
+*/
+uint hash(uint mod, size_t key)
+{
+  static size_t hash= 33;
+  hash= key + (hash << 6) + (hash << 16) - hash;
+  return hash % mod;
+}
+
+/**
+  Write a chunk of given size to backup stream.
+
+  First 4 bytes of the chunk contain its size. The remaining bytes are filled
+  with 0xF5 byte.
+
+  @returns Non-zero value in case of error, 0 otherwise.
+*/
+int write_chunk(backup_stream *stream, size_t chunk_size)
+{
+  byte buf[1024];
+  blob to_write;
+  int ret;
+
+  memset(buf, 0xF5, 1024);
+  ++chunk_count;
+
+  bstream_wr_int4(stream, chunk_size);
+  chunk_size-= 4;
+
+  while (chunk_size)
+  {
+    to_write.begin= buf;
+    to_write.end= buf + chunk_size;
+
+    if (to_write.end > to_write.begin + 1024)
+      to_write.end= to_write.begin + 1024;
+
+    chunk_size-= (to_write.end - to_write.begin);
+
+    ret= bstream_write_blob(stream, to_write);
+
+    if (BSTREAM_OK != ret)
+    {
+      diag("Error when writing chunk #%lu", chunk_count);
+      return ret;
+    }
+  }
+
+  return bstream_end_chunk(stream);
+}
+
+/**
+  Read chunk written by @c write_chunk.
+
+  Reads the chunk, verifies data integrity and its length.
+
+  @param[OUT] len  set to the number of bytes found in the chunk.
+
+  @retval BSTREAM_OK    Chunk read successfuly and integrity tests passed.
+  @retval BSTREAM_EOS   End of stream has been encountered.
+  @retval BSTREAM_ERROR Error detected.
+*/
+int read_chunk(backup_stream *stream, size_t *len)
+{
+  unsigned long int chunk_size;
+  byte buf[1024];
+  byte *ptr;
+  bstream_blob to_read, envelope;
+  int ret= BSTREAM_OK;
+
+  DBUG_ASSERT(len);
+
+  ++chunk_count;
+
+  ret= bstream_rd_int4(stream, &chunk_size);
+  if (ret != BSTREAM_OK)
+    return ret;
+
+  *len= chunk_size;
+  chunk_size-= 4;
+
+  while (chunk_size && ret == BSTREAM_OK)
+  {
+    to_read.begin= buf;
+    to_read.end=   buf + chunk_size;
+
+    if (to_read.end > to_read.begin + 1024)
+      to_read.end= to_read.begin + 1024;
+
+    envelope= to_read;
+    ret= bstream_read_part(stream, &to_read, envelope);
+
+    if (ret == BSTREAM_ERROR)
+    {
+      diag("Error reading data in chunk #%lu.", chunk_count);
+      return BSTREAM_ERROR;
+    }
+
+    /* check that all bytes are correct */
+
+    for (ptr= buf; ptr < to_read.begin; ++ptr)
+      if (0xF5 != *ptr)
+      {
+        diag("Incorrect byte read in chunk #%lu", chunk_count);
+        return BSTREAM_ERROR;
+      }
+
+    chunk_size-= to_read.begin - buf;
+  }
+
+  (*len)-= chunk_size;
+  ret= bstream_next_chunk(stream);
+
+  if (chunk_size)
+  {
+    diag("Not enough bytes found in chunk #%lu: %lu are missing",
+         chunk_count, chunk_size);
+    return BSTREAM_ERROR;
+  }
+
+  return ret;
+}

=== added file 'unittest/backup/catalog.c'
--- a/unittest/backup/catalog.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/catalog.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,694 @@
+/** @file
+
+  Define backup/restore catalogue callbacks to be used for testing backup
+  stream library.
+
+  The contents of the catalogue is determined by arrays @c snapshots[],
+  @c objects_global[], @c objects_perdb[], @c objects_pertable[]. Additionally,
+  the catalogue will contain DB_COUNT databases named db1, ..., dbN and
+  TABLE_COUNT tables named t1, ..., tM. Tables are distributed among available
+  databases and snapshots.
+
+  For each object in the catalogue, the serialization string and extra metadata
+  string have form "serialization of <object name>" and
+  "metadata for <object name>", respectively.
+
+  Calls to all callback functions are counted. There are mechanisms for
+  reporting error from each callback function after a given number of calls.
+*/
+
+#include <my_global.h>
+#include <m_string.h>
+#include <stream_v1_services.h>
+
+#include "catalog.h"
+
+/**
+  List of table data snapshots reported in the catalogue.
+*/
+snapshot_type snapshots[] = {BI_NATIVE, BI_DEFAULT, BI_CS, BI_NODATA};
+const uint snap_count= sizeof(snapshots)/sizeof(snapshot_type);
+
+/**
+  List of global objects other than databases.
+
+  Databases are declared separately with DB_COUNT constant.
+*/
+item_info objects_global[]=
+{
+  {BSTREAM_IT_CHARSET,    {(byte*)"charset", NULL},   0},
+  {BSTREAM_IT_USER,       {(byte*)"user", NULL},      0},
+  {BSTREAM_IT_TABLESPACE, {(byte*)"tablespace",NULL}, 0},
+};
+const uint global_count= sizeof(objects_global)/sizeof(item_info);
+
+/*
+  List of per-database objects other than tables.
+
+  Tables are declared separately with TABLE_COUNT constant. These objects
+  are reported inside each database.
+*/
+item_info objects_perdb[]=
+{
+  {BSTREAM_IT_VIEW,       {(byte*)"view",NULL},       0},
+  {BSTREAM_IT_SPROC,      {(byte*)"procedure",NULL},  1},
+  {BSTREAM_IT_SFUNC,      {(byte*)"function",NULL},   2},
+  {BSTREAM_IT_EVENT,      {(byte*)"event",NULL},      3},
+  {BSTREAM_IT_PRIVILEGE,  {(byte*)"privilege",NULL},  4}
+};
+const uint perdb_count= sizeof(objects_perdb)/sizeof(item_info);
+
+/*
+  List of per-table objects.
+
+  These objects are distributed among all the tables present in the catalogue.
+*/
+item_info objects_pertable[]=
+{
+  {BSTREAM_IT_TRIGGER,    {(byte*)"trigger",NULL},    0}
+};
+const uint pertable_count= sizeof(objects_pertable)/sizeof(item_info);
+
+/**
+  Defines number N of databases present in the catalogue.
+
+  The databases are called db1, ... , dbN.
+*/
+#define DB_COUNT        1
+
+/**
+  Defines number M of tables present in the catalogue.
+
+  The tables are called t1, ... , tM. They are evenly distributed among the
+  databases. That is, table t1 belongs to db1, ... tN belongs to dbN, tN+1
+  belongs to db1 and so on.
+*/
+#define TABLE_COUNT     4
+
+table_info tables[TABLE_COUNT];
+db_info    dbs[DB_COUNT];
+
+const uint db_count= DB_COUNT;
+const uint table_count= TABLE_COUNT;
+
+/**
+  Structure containing bstream callback counters.
+
+  Whenever one of the low-level I/O functions is called, the corresponding
+  counter will be increased.
+
+  Setting counter to a negative value will cause the corresponding function
+  to report ERROR or EOS when counter reaches the value of 0.
+*/
+
+struct st_callback_counts callback_counts;
+
+
+/**
+  Initialize data structures implementing the catalogue.
+
+  This function must be called before any callback is used.
+*/
+void init_catalog(image_header *hdr)
+{
+  uint i;
+  char *name;
+  blob *engine_name;
+  static char db_name[DB_COUNT][10];
+  static char table_name[TABLE_COUNT][10];
+
+  bzero(hdr, sizeof(image_header));
+
+  hdr->version= 1;
+  hdr->snap_count= snap_count;
+  hdr->flags= BSTREAM_FLAG_BINLOG;
+
+  /* Fill snapshot information */
+
+  for(i=0; i < snap_count; ++i)
+  {
+    hdr->snapshot[i].type= snapshots[i];
+    if (snapshots[i] == BI_NATIVE)
+    {
+      engine_name=  &(hdr->snapshot[i].engine.name);
+      engine_name->begin= (byte*)"Storage_engine";
+      engine_name->end=   engine_name->begin + 14;
+    }
+  }
+
+  /* Initialize database items */
+
+  for (i=0; i < db_count; ++i)
+  {
+    name= db_name[i];
+
+    snprintf(name, 10, "db%d", i+1);
+
+    dbs[i].base.type= BSTREAM_IT_DB;
+    dbs[i].base.pos= i;
+    dbs[i].base.name.begin= (byte*)name;
+    dbs[i].base.name.end=   (byte*)name + strlen(name);
+  }
+
+  /* Initialize other global items */
+
+  for (i=0; i < global_count; ++i)
+  {
+    name= (char*)objects_global[i].name.begin;
+    objects_global[i].name.end= (byte*)name + strlen(name);
+  }
+
+  /* Initialize table items */
+
+  for (i=0; i < table_count; ++i)
+  {
+    name= table_name[i];
+
+    snprintf(name, 10, "t%d", i+1);
+
+    tables[i].base.base.type= BSTREAM_IT_TABLE;
+    tables[i].base.base.pos= i;
+    tables[i].base.base.name.begin= (byte*)name;
+    tables[i].base.base.name.end= (byte*)name + strlen(name);
+    tables[i].base.db=  &dbs[i % db_count];
+    tables[i].snap_num= i % snap_count;
+    ++(hdr->snapshot[i % snap_count].table_count);
+  }
+
+  /* Initialize other per-db items */
+
+  for (i=0; i < perdb_count; ++i)
+  {
+    name= (char*)objects_perdb[i].name.begin;
+    objects_perdb[i].name.end= (byte*)name + strlen(name);
+  }
+
+  /* Initialize other per-table items */
+
+  for (i=0; i < pertable_count; ++i)
+  {
+    name= (char*)objects_pertable[i].name.begin;
+    objects_pertable[i].name.end= (byte*)name + strlen(name);
+  }
+
+}
+
+/** Reset all callback counters. */
+void reset_counts()
+{
+   bzero(&callback_counts, sizeof(callback_counts));
+}
+
+/**
+  Check that each iterator_get() call was matched with iterator_free().
+
+  @return 0 if call counts match, non-zero otherwise.
+*/
+int check_iterators()
+{
+  uint i;
+
+  for(i=0; i<128; ++i)
+    if (callback_counts.iterator_get[i] > 0
+        && callback_counts.iterator_get[i] 
+           != callback_counts.iterator_free[i])
+      return 1;
+
+  for(i=0; i<128; ++i)
+    if (callback_counts.db_iterator_get[i] > 0
+        && callback_counts.db_iterator_get[i] 
+           != callback_counts.db_iterator_free[i])
+      return 1;
+
+  return 0;
+}
+
+/**********************************************************************
+
+  Functions for iterating over contents of the catalogue.
+
+
+  Backup stream library asks for different kinds of iterators:
+
+  a) iterator over a given type of global objects (BSTREAM_IT_CHARSET,
+     BSTRERAM_IT_USER, etc).
+  b) iterator determining order in which global objects' metadata will be
+     stored (BSTREAM_IT_GLOBAL).
+  c) iterator determining order in which per-database objects' metadata will
+     be stored (BSTREAM_IT_PERDB).
+  d) iterator determining order in which per-table objects' metadata will be
+     stored (BSTREAM_IT_PERTABLE).
+
+  The iterators are represented by pointers to the corresponding @c it_*
+  counter. In case of iterators a) and b) they are both represented by a pointer
+  to @c it_global. However, for iterator a), the value of @c it_global
+  is positive and indicates the type of the object over which we should iterate.
+  For iterator b) the value if @c it_global is negative.
+
+  Note: Since always only one instance of an iterator is used, we do not bother
+  to dynamically allocate/free iterators. Instead, we always use a single, 
+  static instance of each iterator represented by an appropriate pointer to one
+  of the @c it_* variables.
+
+*********************************************************************/
+
+
+int  it_global;
+int  it_db;
+int  it_perdb;
+int  it_pertable;
+
+/** Iterate over databases */
+item_info* db_iterator_next()
+{
+ DBUG_ASSERT(it_db >= 0);
+
+ if ((unsigned)it_db >= db_count)
+   return NULL;
+
+ return (item_info*)&dbs[it_db++];
+}
+
+/** 
+  Iterate over global objects of type @c it_global.
+
+  This function is used for iterator represented by @c it_global when its
+  value is positive. The value of @c it_global indicates the type of global
+  objects over which we shuld iterate. A separate counter @c it_db is used to
+  keep track of iterator's position.
+*/
+item_info* other_iterator_next()
+{
+  uint pos;
+  item_info *item= NULL;
+
+  DBUG_ASSERT(it_global >= 0);
+
+  /* there are no more global objects to look at - return NULL */
+  if ((unsigned)it_db >= global_count)
+    return NULL;
+
+  /* 
+    Search objects_global[] array for next global objects whose type equals
+    it_global.
+  */
+  for(pos=it_db++; pos < global_count; ++pos)
+    if (objects_global[pos].type == (item_type)it_global)
+      item= &objects_global[pos];
+
+  if (!item)
+    return NULL;
+
+  /*
+    For objects of type CHARSET and USER bstream library expects only the name
+    of the object. For other objects we should return a pointer to item_info
+    structure describing it.
+  */ 
+  switch (item->type) {
+  case BSTREAM_IT_CHARSET:
+  case BSTREAM_IT_USER: return (item_info*)&(item->name);
+  default: return item;
+  }
+}
+
+/**
+  Iterate over all global objecs.
+
+  This function is used for iterator represented by @c it_global when its
+  value is negative. In that case we should iterate over all global objects 
+  from the catalogue. We start with databases which are enumerated using 
+  @c it_db counter. Then we enumerate other global objects from 
+  @c objects_global[] array using @c it_global to track iterator's position.
+*/
+item_info* global_iterator_next()
+{
+  unsigned pos= it_db;
+
+  DBUG_ASSERT(it_global < 0);
+
+  /*
+    Return next database if not all of them enumerated yet.
+  */ 
+  if (pos < db_count)
+  {
+    ++it_db;
+    return (item_info*)&dbs[pos];
+  }
+
+  /*
+    Otherwise iterate over all global objects from objects_global[] array
+    using the value of it_global for keeping the current position within
+    the array. Since we want it_global to be negative, we use convention that
+    values -1,-2,-3,... correspond to positions 0,1,2,...
+  */ 
+  pos= -it_global-1;
+
+  /*
+    Return NULL if all global objects have been already enumerated.
+  */ 
+  if (pos >= global_count)
+    return NULL;
+
+  --it_global;
+
+  return &objects_global[pos];
+}
+
+/**
+  Iterate over all objects belonging to a given database.
+
+  First all tables belonging to that database are returned, then other
+  per-database objects as defined by @c objects_perdb[] array. Counter
+  @c it_perdb indicates current postition of the iterator. First table_count
+  values correspond to the tables while the following values indicate position
+  within @c objects_perdb[] array.
+*/
+dbitem_info* perdb_iterator_next(struct st_bstream_db_info *db)
+{
+  uint pos;
+  static dbitem_info item_buf;
+
+  DBUG_ASSERT(it_perdb >= 0);
+
+  /* Find next table beloging to this database */
+
+  for (; (unsigned)it_perdb < table_count; ++it_perdb)
+    if (tables[it_db].base.db == db)
+      return (dbitem_info*)&(tables[it_perdb++]);
+
+  /* Otherwise return next non-table object */
+  pos= it_perdb - table_count;
+
+  if (pos >= perdb_count)
+    return NULL;
+
+  ++it_perdb;
+
+  item_buf.db= db;
+  item_buf.base= objects_perdb[pos];
+
+  return &item_buf;
+}
+
+/**
+  Iterate over all per-table objects (in all tables).
+
+  Per-table object descriptions are stored in @c objects_pertable[] array.
+  Counter @c it_pertable is used to keep position within that table.
+*/
+titem_info* pertable_iterator_next()
+{
+ static titem_info item;
+
+ DBUG_ASSERT(it_pertable >= 0);
+
+ if ((unsigned)it_pertable >= pertable_count)
+   return NULL;
+
+ item.base= objects_pertable[it_pertable++];
+ item.table= &tables[it_pertable % table_count];
+
+ return &item;
+}
+
+
+/**********************************************************************
+
+  Implementation of catalogue iterator callbacks.
+
+*********************************************************************/
+
+/**
+  Return pointer to given iterator instance.
+
+  The type of the iterator is indicated by @a type parameter:
+  
+  BSTREAM_IT_CHARSET, BSTREAM_IT_USER, etc - an iterator over a given type
+     of global objects.
+
+  BSTREAM_IT_GLOBAL - an iterator determining order in which global objects' 
+     metadata will be stored.
+
+  BSTREAM_IT_PERDB - an iterator determining order in which per-database 
+     objects' metadata will be stored.
+
+  BSTREAM_IT_PERTABLE - an iterator determining order in which per-table
+     objects' metadata will be stored.
+
+  @return Pointer to the @c it_* counter representing given iterator or NULL
+  in case of error.
+*/
+void* bcat_iterator_get(struct st_bstream_image_header
+                        *catalogue  __attribute__ ((__unused__)),
+                        unsigned int type)
+{
+  STEP_AND_CHECK_COUNT(callback_counts.iterator_get[type], return NULL);
+
+  switch (type) {
+
+  case BSTREAM_IT_GLOBAL:
+    it_db= 0;
+    it_global= -1;
+    return &it_global;
+
+  case BSTREAM_IT_PERDB:
+    it_db= 0;
+    it_perdb= 0;
+    return &it_perdb;
+
+  case BSTREAM_IT_DB:
+    it_db= 0;
+    return &it_db;
+
+  case BSTREAM_IT_PERTABLE:
+    it_pertable= 0;
+    return &it_pertable;
+
+  default:
+    it_global= type;
+    it_db= 0;
+    return &it_global;
+  }
+}
+
+/**
+  Free resources used by iterator.
+
+  Since we do not allocate any resources, here we only step the appropriate
+  callback counter. Later these conuters can be used to verify that the number
+  of @c bcat_iterator_free() calls matches that of @c bcat_iterator_get(). This
+  is done in @check_iterators() helper function.
+*/
+void  bcat_iterator_free(struct st_bstream_image_header
+                         *catalogue  __attribute__ ((__unused__)),
+                         void *iter)
+{
+  uint type;
+
+  if (iter == &it_global)
+    type= it_global < 0 ? BSTREAM_IT_GLOBAL : it_global;
+  else if (iter == &it_perdb)
+    type= BSTREAM_IT_PERDB;
+  else if (iter == &it_db)
+    type= BSTREAM_IT_DB;
+  else if (iter == &it_pertable)
+    type= BSTREAM_IT_PERTABLE;
+
+  ++(callback_counts.iterator_free[type]);
+}
+
+int opt_empty_catalog= 0;
+
+/**
+  Return next object from a given iterator.
+
+  Call appropriate @c *_iterator_next() function to return next object pointed
+  by the iterator.
+*/
+struct st_bstream_item_info*
+bcat_iterator_next(struct st_bstream_image_header
+                   *catalogue __attribute__ ((__unused__)), void *iter)
+{
+  dbitem_info *info;
+
+  if (opt_empty_catalog)
+     return NULL;
+
+  if (iter == &it_global && it_global < 0)
+    return global_iterator_next();
+
+  if (iter == &it_global)
+    return other_iterator_next();
+
+  if (iter == &it_db)
+    return db_iterator_next();
+
+  if (iter == &it_pertable)
+    return (item_info*)pertable_iterator_next();
+
+  /*
+    If iter == &it_perdb we want to enumerate all per-database objects. We
+    iterate over all databases using it_db counter and for each database use
+    perdb_iterator_next() until it returns NULL indicating that there are
+    no more objects in that database.
+  */
+
+  if (iter == &it_perdb)
+  {
+    DBUG_ASSERT(it_db >= 0);
+
+    if ((unsigned)it_db >= db_count)
+      return NULL;
+
+    info= perdb_iterator_next(&dbs[it_db]);
+
+    if (!info)
+    {
+      ++it_db;
+      it_perdb=0;
+      return bcat_iterator_next(NULL, iter);
+    }
+
+    return (item_info*)info;
+  }
+
+  return NULL;
+}
+
+/**
+  Return iterator enumerating all objects belonging to a given database.
+
+  It is not important what pointer is returned as long as it is not NULL.
+  The enumeration is always performed by @c perdb_iterator_next() function.
+*/
+void* bcat_db_iterator_get(struct st_bstream_image_header
+                           *catalogue  __attribute__ ((__unused__)),
+                           struct st_bstream_db_info *db)
+{
+  STEP_AND_CHECK_COUNT(callback_counts.db_iterator_get[db->base.pos],
+                       return NULL);
+  it_perdb=0;
+  return &it_perdb;
+}
+
+/** Return next object from database iterator */
+struct st_bstream_dbitem_info*
+bcat_db_iterator_next(struct st_bstream_image_header
+                      *catalogue  __attribute__ ((__unused__)),
+                      struct st_bstream_db_info *db,
+                      void *iter  __attribute__ ((__unused__)))
+{
+  return perdb_iterator_next(db);
+}
+
+/** Free database iterator resources */
+void  bcat_db_iterator_free(struct st_bstream_image_header
+                            *catalogue  __attribute__ ((__unused__)),
+                            struct st_bstream_db_info *db,
+                            void *iter  __attribute__ ((__unused__)))
+{
+  ++(callback_counts.db_iterator_free[db->base.pos]);
+}
+
+/**********************************************************************
+
+  Functions for getting object's metadata.
+
+*********************************************************************/
+
+int bcat_get_item_create_query(struct st_bstream_image_header
+                               *catalogue  __attribute__ ((__unused__)),
+                               struct st_bstream_item_info *item,
+                               bstream_blob *stmt)
+{
+  static char buf[]= "serialization of xxxxxxxxxxxxxxxx";
+  int len;
+
+  STEP_AND_CHECK_COUNT(callback_counts.get_serialization[item->type], 
+                       return BSTREAM_ERROR);
+
+  len= snprintf(buf+17, 16, "%s", item->name.begin);
+
+  stmt->begin= (byte*) buf;
+  stmt->end= stmt->begin + 17 + len;
+
+  return BSTREAM_OK;
+}
+
+int bcat_get_item_create_data(struct st_bstream_image_header
+                              *catalogue  __attribute__ ((__unused__)),
+                              struct st_bstream_item_info *item,
+                              bstream_blob *data)
+{
+  static char buf[]= "metadata for xxxxxxxxxxxxxxxx";
+  int len;
+
+  STEP_AND_CHECK_COUNT(callback_counts.get_metadata[item->type], 
+                       return BSTREAM_ERROR);
+
+  len= snprintf(buf+13, 16, "%s", item->name.begin);
+
+  data->begin= (byte*) buf;
+  data->end= data->begin + 13 + len;
+
+  return BSTREAM_OK;
+}
+
+/*********************************************************************
+
+  Other catalogue callback functions.
+
+*********************************************************************/
+
+int bcat_create_item(struct st_bstream_image_header
+                     *catalogue  __attribute__ ((__unused__)),
+                     struct st_bstream_item_info *item,
+                     bstream_blob create_stmt  __attribute__ ((__unused__)),
+                     bstream_blob other_meta_data  __attribute__ ((__unused__)))
+{
+  STEP_AND_CHECK_COUNT(callback_counts.create_item[item->type], 
+                       return BSTREAM_ERROR);
+  return BSTREAM_OK;
+}
+
+int bcat_reset(struct st_bstream_image_header
+               *catalogue __attribute__ ((__unused__)))
+{
+  STEP_AND_CHECK_COUNT(callback_counts.cat_reset, return BSTREAM_ERROR);
+  return BSTREAM_OK;
+}
+
+int bcat_close(struct st_bstream_image_header
+               *catalogue  __attribute__ ((__unused__)))
+{
+  STEP_AND_CHECK_COUNT(callback_counts.cat_close, return BSTREAM_ERROR);
+  return BSTREAM_OK;
+}
+
+int bcat_add_item(struct st_bstream_image_header
+                  *catalogue  __attribute__ ((__unused__)),
+                  struct st_bstream_item_info *item)
+{
+  STEP_AND_CHECK_COUNT(callback_counts.add_item[item->type], 
+                       return BSTREAM_ERROR);
+  return BSTREAM_OK;
+}
+
+
+/*********************************************************************
+
+  Memory allocation callbacks.
+
+*********************************************************************/
+
+byte* bstream_alloc(unsigned long int size)
+{
+  STEP_AND_CHECK_COUNT(callback_counts.alloc, return NULL);
+  return malloc(size);
+}
+
+void bstream_free(byte *ptr)
+{
+  ++callback_counts.free;
+  free(ptr);
+}

=== added file 'unittest/backup/catalog.h'
--- a/unittest/backup/catalog.h	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/catalog.h	2009-06-30 20:37:35 +0000
@@ -0,0 +1,59 @@
+#ifndef CATALOG_H_
+#define CATALOG_H_
+
+#include "stream_test.h"
+
+extern const uint snap_count;
+extern const uint global_count;
+extern const uint db_count;
+extern const uint table_count;
+extern const uint perdb_count;
+extern const uint pertable_count;
+
+extern snapshot_type snapshots[];
+extern item_info     objects_global[];
+extern db_info       dbs[];
+extern table_info    tables[];
+extern item_info     objects_perdb[];
+extern item_info     objects_pertable[];
+
+/** 
+  If non zero, makes the catalogue empty - no objects are reported by the 
+  iterators. 
+*/
+extern int opt_empty_catalog;
+
+/**
+  Structure containing bstream callback counters.
+
+  Whenever one of the callback functions is called, the corresponding
+  counter will be increased.
+
+  Setting counter to a negative value will cause the corresponding function
+  report ERROR when counter reaches value of 0.
+*/
+extern struct st_callback_counts {
+  int iterator_get[128];
+  int iterator_free[128];
+  int db_iterator_get[128];
+  int db_iterator_free[128];
+  int get_serialization[128];
+  int get_metadata[128];
+  int add_item[128];
+  int create_item[128];
+  int cat_reset;
+  int cat_close;
+  int alloc;
+  int free;
+} callback_counts;
+
+/**
+  Increase counter C, execute X if counter reaches value of 0.
+*/
+#define STEP_AND_CHECK_COUNT(C,X)   if (0 == ++(C)) X
+
+void init_catalog(image_header*);
+void reset_counts();
+int  check_iterators();
+
+#endif /*CATALOG_H_*/

=== added file 'unittest/backup/no_catalog.c'
--- a/unittest/backup/no_catalog.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/no_catalog.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,96 @@
+/** @file
+
+  A trivial implementation of catalogue callbacks required by bstream library.
+*/
+
+#define BSTREAM_USE_MALLOC
+#include <stream_v1_services.h>
+
+
+int bcat_reset(struct st_bstream_image_header
+               *catalogue __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_close(struct st_bstream_image_header
+               *catalogue __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_add_item(struct st_bstream_image_header
+                  *catalogue __attribute__ ((__unused__)),
+                  struct st_bstream_item_info
+                  *item __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+void* bcat_iterator_get(struct st_bstream_image_header
+                        *catalogue __attribute__ ((__unused__)),
+                        unsigned int type __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+struct st_bstream_item_info*
+bcat_iterator_next(struct st_bstream_image_header
+                   *catalogue __attribute__ ((__unused__)),
+                   void *iter __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+void  bcat_iterator_free(struct st_bstream_image_header
+                         *catalogue __attribute__ ((__unused__)),
+                         void *iter __attribute__ ((__unused__)))
+{}
+
+
+void* bcat_db_iterator_get(struct st_bstream_image_header
+                           *catalogue __attribute__ ((__unused__)),
+                           struct st_bstream_db_info
+                           *db __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+/** Return next item from database items iterator */
+
+struct st_bstream_dbitem_info*
+bcat_db_iterator_next(struct st_bstream_image_header
+                      *catalogue __attribute__ ((__unused__)),
+                      struct st_bstream_db_info
+                      *db __attribute__ ((__unused__)),
+                      void *iter __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+/** Free database items iterator resources */
+
+void  bcat_db_iterator_free(struct st_bstream_image_header
+                            *catalogue __attribute__ ((__unused__)),
+                            struct st_bstream_db_info
+                            *db __attribute__ ((__unused__)),
+                            void *iter __attribute__ ((__unused__)))
+{}
+
+
+int bcat_get_item_create_query(struct st_bstream_image_header
+                               *catalogue __attribute__ ((__unused__)),
+                               struct st_bstream_item_info
+                               *item __attribute__ ((__unused__)),
+                               bstream_blob *stmt __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_get_item_create_data(struct st_bstream_image_header
+                              *catalogue __attribute__ ((__unused__)),
+                              struct st_bstream_item_info
+                              *item __attribute__ ((__unused__)),
+                              bstream_blob *data __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_create_item(struct st_bstream_image_header
+                     *catalogue __attribute__ ((__unused__)),
+                     struct st_bstream_item_info
+                     *item __attribute__ ((__unused__)),
+                     bstream_blob create_stmt __attribute__ ((__unused__)),
+                     bstream_blob other_meta_data __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }

=== added file 'unittest/backup/stream_test.h'
--- a/unittest/backup/stream_test.h	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/stream_test.h	2009-06-30 20:37:35 +0000
@@ -0,0 +1,49 @@
+#ifndef STREAM_TEST_H_
+#define STREAM_TEST_H_
+
+#include <m_string.h>
+#include <stream_v1.h>
+
+typedef bstream_byte byte;
+typedef bstream_blob blob;
+
+typedef enum   enum_bstream_snapshot_type  snapshot_type;
+typedef enum   enum_bstream_item_type      item_type;
+typedef struct st_bstream_image_header     image_header;
+typedef struct st_bstream_item_info        item_info;
+typedef struct st_bstream_db_info          db_info;
+typedef struct st_bstream_dbitem_info      dbitem_info;
+typedef struct st_bstream_table_info       table_info;
+typedef struct st_bstream_titem_info       titem_info;
+typedef struct st_bstream_data_chunk       table_data_chunk;
+
+extern
+struct st_io_counts
+{
+  long    read;
+  long    write;
+  size_t  read_bytes;
+  size_t  write_bytes;
+} io_counts;
+
+void reset_io_counts();
+
+typedef struct
+{
+  backup_stream base;
+  int   m_fd;
+} test_backup_stream;
+
+extern size_t stream_block_size;
+
+int backup_stream_open_rd(test_backup_stream *stream, const char *path);
+int backup_stream_open_wr(test_backup_stream *stream, const char *path);
+int backup_stream_close(test_backup_stream *stream);
+
+#define OPEN_ERROR      (BSTREAM_ERROR+10)
+#define CLOSE_ERROR     (BSTREAM_ERROR+11)
+
+int write_image(image_header *hdr, const char *path);
+int read_image(image_header *hdr, const char *path);
+
+#endif /*STREAM_TEST_H_*/

=== added file 'unittest/backup/test_main.c'
--- a/unittest/backup/test_main.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/test_main.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,23 @@
+/** @file
+
+  Implements main() function for unit tests. The tests should define
+  their functionality in run_test() function.
+*/
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <tap.h>
+
+extern void run_test();
+void (*test_exit_hook)() = NULL;
+
+int main()
+{
+  my_init();
+  run_test();
+
+  if (test_exit_hook)
+    test_exit_hook();
+
+  return exit_status();
+}

=== added file 'unittest/backup/test_stream.c'
--- a/unittest/backup/test_stream.c	1970-01-01 00:00:00 +0000
+++ b/unittest/backup/test_stream.c	2009-06-30 20:37:35 +0000
@@ -0,0 +1,330 @@
+/** @file
+
+  Implementation of low-level I/O file stream for testing backup stream
+  library.
+*/
+
+#include <my_global.h>
+#include <my_sys.h>
+
+#include "stream_test.h"
+
+/** I/O block size to be used when creating new stream. */
+
+size_t stream_block_size= 1024;
+
+static int stream_write(void *stream, bstream_blob *buf, bstream_blob envelope);
+static int stream_read(void *stream, bstream_blob *buf, bstream_blob);
+static int stream_forward(void *stream, unsigned long int *offset);
+
+/**
+  Structure containing I/O callback counters.
+
+  Whenever one of the low-level I/O functions is called, the corresponding
+  counter will be increased.
+
+  Setting counter to a negative value will cause the corresponding function
+  to report ERROR or EOS when counter reaches the value of 0.
+*/
+struct st_io_counts io_counts;
+
+static const char* files_to_delete[128];
+static unsigned int files_to_delete_count= 0;
+
+extern void (*test_exit_hook)();
+
+
+void delete_files_at_exit()
+{
+  unsigned i;
+
+  for (i=0; i < files_to_delete_count && i < 128; ++i)
+  {
+    DBUG_ASSERT(files_to_delete[i]);
+    my_delete(files_to_delete[i], MYF(0));
+  }
+}
+
+
+/** Reset all callback counters */
+
+void reset_io_counts()
+{
+  bzero(&io_counts, sizeof(io_counts));
+}
+
+
+/**
+  Open test stream for writing.
+
+  Data will be written to @a path file. If that file already exists, it will be
+  overwritten. The stream will use I/O blocks of size specified by the current
+  value of @c stream_block_size.
+*/
+
+int backup_stream_open_wr(test_backup_stream *stream, const char *path)
+{
+  DBUG_ASSERT(stream);
+
+  /* open file for writing */
+  int fd= my_open(path, O_WRONLY|O_CREAT|O_TRUNC, MYF(0));
+  if (fd < 0)
+    return BSTREAM_ERROR;
+
+  /* Register file path for deletion at the end of the test */
+  files_to_delete[files_to_delete_count++]= path;
+  test_exit_hook= delete_files_at_exit;
+
+  stream->m_fd= fd;
+  stream->base.stream.write= stream_write;
+  stream->base.stream.forward= stream_forward;
+
+  return bstream_open_wr(&(stream->base), stream_block_size, 0);
+}
+
+
+/**
+  Open test stream for reading.
+
+  The stream will get bytes from @a path file.
+*/
+
+int backup_stream_open_rd(test_backup_stream *stream, const char *path)
+{
+  DBUG_ASSERT(stream);
+
+  /* open file for reading */
+  int fd= my_open(path, O_RDONLY, MYF(0));
+  if (fd < 0)
+    return BSTREAM_ERROR;
+
+  stream->m_fd= fd;
+  stream->base.stream.read=  stream_read;
+  stream->base.stream.forward= stream_forward;
+
+  return bstream_open_rd(&(stream->base), 0);
+}
+
+
+/** Close a test stream. */
+
+int backup_stream_close(test_backup_stream *stream)
+{
+  DBUG_ASSERT(stream);
+  int fd= stream->m_fd;
+
+  (void)bstream_close(&(stream->base));
+
+  /* close the stream */
+  if (fd >= 0)
+    my_close(fd, MYF(0));
+
+  return BSTREAM_OK;
+}
+
+
+/**
+  Helper function which writes a backup image to given file.
+
+  The caller of this function should implement the @c bcat_ callbacks which
+  will determine the set of objects to be included in the image and their
+  metadata. The image will contain no table data chunks, only preamble and
+  summary section.
+
+  @returns 0 on success, non-zero otherwise.
+*/
+
+int write_image(image_header *hdr, const char *path)
+{
+  test_backup_stream stream;
+  int ret1, ret2;
+
+  ret1= backup_stream_open_wr(&stream, path);
+  if (ret1)
+    return OPEN_ERROR;
+
+  ret1= bstream_wr_preamble((backup_stream*)&stream, hdr);
+  ret2= bstream_wr_summary((backup_stream*)&stream, hdr);
+
+  backup_stream_close(&stream);
+
+  return ret1 || ret2;
+}
+
+
+/**
+  Helper fuction which reads backup image from a file.
+
+  Any table data chunks present in the image are ignored.
+
+  @returns 0 on success, non-zero otherwise.
+*/
+
+int read_image(image_header *hdr, const char *path)
+{
+  test_backup_stream stream;
+  table_data_chunk   dchunk;
+  int ret1, ret2;
+
+  ret1= backup_stream_open_rd(&stream, path);
+  if (ret1)
+    return OPEN_ERROR;
+
+  ret1= bstream_rd_preamble((backup_stream*)&stream, hdr);
+
+  if (ret1 == BSTREAM_ERROR)
+    goto end;
+
+  /* Read and skip table data chunks */
+  do {
+    ret2= bstream_rd_data_chunk((backup_stream*)&stream, &dchunk);
+  } while (ret2 == BSTREAM_OK);
+
+  /*
+    Now, there should be the summary block at the end of the image.
+  */
+  if (ret2 != BSTREAM_EOC)
+    goto end;
+
+  /* Read the summary block and check that stream ends after it */
+
+  ret2= bstream_rd_summary((backup_stream*)&stream, hdr);
+  ret2= (ret2 != BSTREAM_EOS) ? BSTREAM_ERROR : BSTREAM_OK;
+
+end:
+
+  backup_stream_close(&stream);
+
+  return ret1 || ret2;
+}
+
+
+/*******************************************************************
+
+  Implementation of low-level I/O operations.
+
+*******************************************************************/
+
+
+/** Low-level write function */
+
+static
+int stream_write(void *stream, bstream_blob *buf,
+                 bstream_blob envelope __attribute__ ((__unused__)))
+{
+  int fd;
+  int res;
+  size_t howmuch;
+
+  DBUG_ASSERT(stream);
+  DBUG_ASSERT(buf);
+
+  test_backup_stream *s= stream;
+
+  fd= s->m_fd;
+
+  if (fd < 0)
+    return BSTREAM_ERROR;
+
+  if (!buf->begin || buf->begin == buf->end)
+    return BSTREAM_OK;
+
+  DBUG_ASSERT(buf->end);
+
+  howmuch = buf->end - buf->begin;
+  res= my_write(fd, buf->begin, howmuch,
+                MYF(MY_NABP) /* error if not all bytes written */ );
+
+  if (++io_counts.write == 0)
+    goto error;
+
+  if (res)
+    goto error;
+
+  buf->begin= buf->end;
+  io_counts.write_bytes += howmuch;
+
+  return BSTREAM_OK;
+
+ error:
+ 
+   my_close(fd, MYF(0));
+   s->m_fd= -1;
+   return BSTREAM_ERROR;   
+}
+
+
+/** Low-level read function */
+static
+int stream_read(void *stream, bstream_blob *buf,
+                bstream_blob envelope  __attribute__ ((__unused__)))
+{
+  int fd;
+  size_t howmuch;
+
+  DBUG_ASSERT(stream);
+  DBUG_ASSERT(buf);
+
+  test_backup_stream *s= stream;
+
+  fd= s->m_fd;
+
+  DBUG_ASSERT(fd >= 0);
+
+  if (!buf->begin || buf->begin == buf->end)
+    return BSTREAM_OK;
+
+  DBUG_ASSERT(buf->end);
+
+  howmuch= buf->end - buf->begin;
+  howmuch= my_read(fd, buf->begin, howmuch, MYF(0));
+
+  if (0 == ++io_counts.read)
+    goto error;
+
+  if (howmuch == (size_t) -1)
+    goto error;
+
+  if (howmuch == 0)
+    return BSTREAM_EOS;
+
+  buf->begin += howmuch;
+
+  io_counts.read_bytes += howmuch;
+
+  return BSTREAM_OK;
+
+ error:
+ 
+   my_close(fd, MYF(0));
+   s->m_fd= -1;
+   return BSTREAM_ERROR;   
+}
+
+
+/** Low-level forward function */
+static
+int stream_forward(void *stream, unsigned long int *offset)
+{
+  int fd;
+  size_t howmuch;
+
+  DBUG_ASSERT(stream);
+  DBUG_ASSERT(offset);
+
+  test_backup_stream *s= stream;
+
+  fd= s->m_fd;
+
+  DBUG_ASSERT(fd >= 0);
+
+  if (*offset == 0)
+    return BSTREAM_OK;
+
+  howmuch= *offset;
+
+  lseek(fd, howmuch, SEEK_CUR);
+  *offset= 0;
+
+  return BSTREAM_OK;
+}


Attachment: [text/bzr-bundle] bzr/rafal.somla@sun.com-20090630203735-e5k93j1z6m0sb69x.bundle
Thread
bzr push into mysql-6.0-backup branch (Rafal.Somla:2838 to 2839)WL#4765Rafal Somla30 Jun