Commit e1f6da82 authored by Michael Widenius's avatar Michael Widenius

Fixed bug lp:917689 "Archive table corruption crashing MariaDB signal 11"

Added 'from_end' as extra parameter to Field::unpack() to detect wrong from data.
Change ha_archive::unpack_row() to detect wrong field lengths.
Replication code changed to detect wrong field information in events.


mysql-test/r/archive.result:
  dded test case for lp:917689
sql/field.cc:
  Added 'from_end' as extra parameter to Field::unpack() to detect wrong from data.
  Removed not used 'unpack_key' functions.
sql/field.h:
  Added 'from_end' as extra parameter to Field::unpack() to detect wrong from data.
  Removed not used 'unpack_key' functions.
  Removed some not needed unpack() functions.
sql/filesort.cc:
  Added buffer end parameter to unpack_addon_fields()
sql/log_event.h:
  Added end of buffer argument to unpack_row()
sql/log_event_old.cc:
  Added end of buffer argument to unpack_row()
sql/log_event_old.h:
  Added end of buffer argument to unpack_row()
sql/records.cc:
  Added buffer end parameter to unpack_addon_fields()
sql/rpl_record.cc:
  Added end of buffer argument to unpack_row()
  Added detection of wrong field information in events
sql/rpl_record.h:
  Added end of buffer argument to unpack_row()
sql/rpl_record_old.cc:
  Added end of buffer argument to unpack_row()
  Added detection of wrong field information in events
sql/rpl_record_old.h:
  Added end of buffer argument to unpack_row()
sql/table.h:
  Added buffer end parameter to unpack()
storage/archive/ha_archive.cc:
  Change ha_archive::unpack_row() to detect wrong field lengths.
  This fixes lp:917689
parent b02f8819
...@@ -12801,3 +12801,14 @@ OPTIMIZE TABLE t1; ...@@ -12801,3 +12801,14 @@ OPTIMIZE TABLE t1;
Table Op Msg_type Msg_text Table Op Msg_type Msg_text
test.t1 optimize status OK test.t1 optimize status OK
DROP TABLE t1; DROP TABLE t1;
#
# BUG#917689 Using wrong archive table causes crash
#
create table t1 (a int, b char(50)) engine=archive;
select * from t1;
ERROR HY000: Table 't1' is marked as crashed and should be repaired
show warnings;
Level Code Message
Error 127 Got error 127 when reading table `test`.`t1`
Error 1194 Table 't1' is marked as crashed and should be repaired
drop table t1;
...@@ -1727,3 +1727,14 @@ CHECKSUM TABLE t1 EXTENDED; ...@@ -1727,3 +1727,14 @@ CHECKSUM TABLE t1 EXTENDED;
FLUSH TABLE t1; FLUSH TABLE t1;
OPTIMIZE TABLE t1; OPTIMIZE TABLE t1;
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # BUG#917689 Using wrong archive table causes crash
--echo #
create table t1 (a int, b char(50)) engine=archive;
--remove_file $MYSQLD_DATADIR/test/t1.ARZ
copy_file std_data/t917689.ARZ $MYSQLD_DATADIR/test/t1.ARZ;
--error 1194
select * from t1;
show warnings;
drop table t1;
...@@ -194,12 +194,15 @@ begin ...@@ -194,12 +194,15 @@ begin
insert into test.t1 values (x, z); insert into test.t1 values (x, z);
end| end|
let $start_value= `SELECT @@max_join_size`|
call mixset("mixset", 19)| call mixset("mixset", 19)|
show variables like 'max_join_size'| show variables like 'max_join_size'|
select id,data,@z from t1| select id,data,@z from t1|
delete from t1| delete from t1|
drop procedure mixset| drop procedure mixset|
--disable_query_log
eval SET @@max_join_size= $start_value|
--enable_query_log
# Multiple CALL statements, one with OUT parameter. # Multiple CALL statements, one with OUT parameter.
--disable_warnings --disable_warnings
......
...@@ -1472,11 +1472,13 @@ Field::pack(uchar *to, const uchar *from, uint max_length) ...@@ -1472,11 +1472,13 @@ Field::pack(uchar *to, const uchar *from, uint max_length)
data data
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
@return 0 if wrong data
*/ */
const uchar * const uchar *
Field::unpack(uchar* to, const uchar *from, uint param_data) Field::unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data)
{ {
uint length=pack_length(); uint length=pack_length(), len;
int from_type= 0; int from_type= 0;
/* /*
If from length is > 255, it has encoded data in the upper bits. Need If from length is > 255, it has encoded data in the upper bits. Need
...@@ -1492,14 +1494,19 @@ Field::unpack(uchar* to, const uchar *from, uint param_data) ...@@ -1492,14 +1494,19 @@ Field::unpack(uchar* to, const uchar *from, uint param_data)
(length == param_data) || (length == param_data) ||
(from_type != real_type())) (from_type != real_type()))
{ {
if (from + length > from_end)
return 0; // Error in data
memcpy(to, from, length); memcpy(to, from, length);
return from+length; return from+length;
} }
uint len= (param_data && (param_data < length)) ? len= (param_data && (param_data < length)) ? param_data : length;
param_data : length;
memcpy(to, from, param_data > length ? length : len); if (from + len > from_end)
return 0; // Error in data
memcpy(to, from, len);
return from+len; return from+len;
} }
...@@ -2916,10 +2923,11 @@ uint Field_new_decimal::is_equal(Create_field *new_field) ...@@ -2916,10 +2923,11 @@ uint Field_new_decimal::is_equal(Create_field *new_field)
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
*/ */
const uchar * const uchar *
Field_new_decimal::unpack(uchar* to, const uchar *from, uint param_data) Field_new_decimal::unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data)
{ {
if (param_data == 0) if (param_data == 0)
return Field::unpack(to, from, param_data); return Field::unpack(to, from, from_end, param_data);
uint from_precision= (param_data & 0xff00) >> 8U; uint from_precision= (param_data & 0xff00) >> 8U;
uint from_decimal= param_data & 0x00ff; uint from_decimal= param_data & 0x00ff;
...@@ -2949,7 +2957,11 @@ Field_new_decimal::unpack(uchar* to, const uchar *from, uint param_data) ...@@ -2949,7 +2957,11 @@ Field_new_decimal::unpack(uchar* to, const uchar *from, uint param_data)
decimal2bin(&dec_val, to, precision, decimals()); decimal2bin(&dec_val, to, precision, decimals());
} }
else else
{
if (from + len > from_end)
return 0; // Wrong data
memcpy(to, from, len); // Sizes are the same, just copy the data. memcpy(to, from, len); // Sizes are the same, just copy the data.
}
return from+len; return from+len;
} }
...@@ -6542,7 +6554,8 @@ uchar *Field_string::pack(uchar *to, const uchar *from, uint max_length) ...@@ -6542,7 +6554,8 @@ uchar *Field_string::pack(uchar *to, const uchar *from, uint max_length)
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
*/ */
const uchar * const uchar *
Field_string::unpack(uchar *to, const uchar *from, uint param_data) Field_string::unpack(uchar *to, const uchar *from, const uchar *from_end,
uint param_data)
{ {
uint from_length, length; uint from_length, length;
...@@ -6564,11 +6577,19 @@ Field_string::unpack(uchar *to, const uchar *from, uint param_data) ...@@ -6564,11 +6577,19 @@ Field_string::unpack(uchar *to, const uchar *from, uint param_data)
*/ */
if (from_length > 255) if (from_length > 255)
{ {
if (from + 2 > from_end)
return 0;
length= uint2korr(from); length= uint2korr(from);
from+= 2; from+= 2;
} }
else else
{
if (from + 1 > from_end)
return 0;
length= (uint) *from++; length= (uint) *from++;
}
if (from + length > from_end || length > field_length)
return 0;
memcpy(to, from, length); memcpy(to, from, length);
// Pad the string with the pad character of the fields charset // Pad the string with the pad character of the fields charset
...@@ -7060,6 +7081,7 @@ Field_varstring::pack_key(uchar *to, const uchar *key, uint max_length) ...@@ -7060,6 +7081,7 @@ Field_varstring::pack_key(uchar *to, const uchar *key, uint max_length)
Pointer to end of 'key' (To the next key part if multi-segment key) Pointer to end of 'key' (To the next key part if multi-segment key)
*/ */
#ifdef NOT_USED
const uchar * const uchar *
Field_varstring::unpack_key(uchar *to, const uchar *key, uint max_length) Field_varstring::unpack_key(uchar *to, const uchar *key, uint max_length)
{ {
...@@ -7076,6 +7098,7 @@ Field_varstring::unpack_key(uchar *to, const uchar *key, uint max_length) ...@@ -7076,6 +7098,7 @@ Field_varstring::unpack_key(uchar *to, const uchar *key, uint max_length)
memcpy(ptr + length_bytes, key, length); memcpy(ptr + length_bytes, key, length);
return key + length; return key + length;
} }
#endif
/** /**
Create a packed key that will be used for storage in the index tree. Create a packed key that will be used for storage in the index tree.
...@@ -7120,11 +7143,16 @@ uchar * Field_varstring::pack_key_from_key_image(uchar *to, const uchar *from, ...@@ -7120,11 +7143,16 @@ uchar * Field_varstring::pack_key_from_key_image(uchar *to, const uchar *from,
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
*/ */
const uchar * const uchar *
Field_varstring::unpack(uchar *to, const uchar *from, uint param_data) Field_varstring::unpack(uchar *to, const uchar *from, const uchar *from_end,
uint param_data)
{ {
uint length; uint length;
uint l_bytes= (param_data && (param_data < field_length)) ? uint l_bytes= (param_data && (param_data < field_length)) ?
(param_data <= 255) ? 1 : 2 : length_bytes; (param_data <= 255) ? 1 : 2 : length_bytes;
if (from + l_bytes > from_end)
return 0; // Error in data
if (l_bytes == 1) if (l_bytes == 1)
{ {
to[0]= *from++; to[0]= *from++;
...@@ -7139,7 +7167,11 @@ Field_varstring::unpack(uchar *to, const uchar *from, uint param_data) ...@@ -7139,7 +7167,11 @@ Field_varstring::unpack(uchar *to, const uchar *from, uint param_data)
to[1]= *from++; to[1]= *from++;
} }
if (length) if (length)
{
if (from + length > from_end || length > field_length)
return 0; // Error in data
memcpy(to+ length_bytes, from, length); memcpy(to+ length_bytes, from, length);
}
return from+length; return from+length;
} }
...@@ -7771,16 +7803,21 @@ uchar *Field_blob::pack(uchar *to, const uchar *from, uint max_length) ...@@ -7771,16 +7803,21 @@ uchar *Field_blob::pack(uchar *to, const uchar *from, uint max_length)
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
*/ */
const uchar *Field_blob::unpack(uchar *to, const uchar *from, uint param_data) const uchar *Field_blob::unpack(uchar *to, const uchar *from,
const uchar *from_end, uint param_data)
{ {
DBUG_ENTER("Field_blob::unpack"); DBUG_ENTER("Field_blob::unpack");
DBUG_PRINT("enter", ("to: 0x%lx; from: 0x%lx; param_data: %u", DBUG_PRINT("enter", ("to: 0x%lx; from: 0x%lx; param_data: %u",
(ulong) to, (ulong) from, param_data)); (ulong) to, (ulong) from, param_data));
uint const master_packlength= uint const master_packlength=
param_data > 0 ? param_data & 0xFF : packlength; param_data > 0 ? param_data & 0xFF : packlength;
if (from + master_packlength > from_end)
DBUG_RETURN(0); // Error in data
uint32 const length= get_length(from, master_packlength); uint32 const length= get_length(from, master_packlength);
DBUG_DUMP("packed", from, length + master_packlength); DBUG_DUMP("packed", from, length + master_packlength);
bitmap_set_bit(table->write_set, field_index); bitmap_set_bit(table->write_set, field_index);
if (from + master_packlength + length > from_end)
DBUG_RETURN(0);
store(reinterpret_cast<const char*>(from) + master_packlength, store(reinterpret_cast<const char*>(from) + master_packlength,
length, field_charset); length, field_charset);
DBUG_DUMP("record", to, table->s->reclength); DBUG_DUMP("record", to, table->s->reclength);
...@@ -7878,6 +7915,7 @@ Field_blob::pack_key(uchar *to, const uchar *from, uint max_length) ...@@ -7878,6 +7915,7 @@ Field_blob::pack_key(uchar *to, const uchar *from, uint max_length)
Pointer into 'from' past the last byte copied from packed key. Pointer into 'from' past the last byte copied from packed key.
*/ */
#ifdef NOT_USED
const uchar * const uchar *
Field_blob::unpack_key(uchar *to, const uchar *from, uint max_length) Field_blob::unpack_key(uchar *to, const uchar *from, uint max_length)
{ {
...@@ -7898,7 +7936,7 @@ Field_blob::unpack_key(uchar *to, const uchar *from, uint max_length) ...@@ -7898,7 +7936,7 @@ Field_blob::unpack_key(uchar *to, const uchar *from, uint max_length)
/* point to first byte of next field in 'from' */ /* point to first byte of next field in 'from' */
return from + length; return from + length;
} }
#endif
/** Create a packed key that will be used for storage from a MySQL key. */ /** Create a packed key that will be used for storage from a MySQL key. */
...@@ -8940,7 +8978,8 @@ Field_bit::pack(uchar *to, const uchar *from, uint max_length) ...@@ -8940,7 +8978,8 @@ Field_bit::pack(uchar *to, const uchar *from, uint max_length)
@return New pointer into memory based on from + length of the data @return New pointer into memory based on from + length of the data
*/ */
const uchar * const uchar *
Field_bit::unpack(uchar *to, const uchar *from, uint param_data) Field_bit::unpack(uchar *to, const uchar *from, const uchar *from_end,
uint param_data)
{ {
uint const from_len= (param_data >> 8U) & 0x00ff; uint const from_len= (param_data >> 8U) & 0x00ff;
uint const from_bit_len= param_data & 0x00ff; uint const from_bit_len= param_data & 0x00ff;
...@@ -8951,6 +8990,9 @@ Field_bit::unpack(uchar *to, const uchar *from, uint param_data) ...@@ -8951,6 +8990,9 @@ Field_bit::unpack(uchar *to, const uchar *from, uint param_data)
if (param_data == 0 || if (param_data == 0 ||
((from_bit_len == bit_len) && (from_len == bytes_in_rec))) ((from_bit_len == bit_len) && (from_len == bytes_in_rec)))
{ {
if (from + bytes_in_rec + test(bit_len) > from_end)
return 0; // Error in data
if (bit_len > 0) if (bit_len > 0)
{ {
/* /*
...@@ -8975,10 +9017,16 @@ Field_bit::unpack(uchar *to, const uchar *from, uint param_data) ...@@ -8975,10 +9017,16 @@ Field_bit::unpack(uchar *to, const uchar *from, uint param_data)
Lastly the odd bits need to be masked out if the bytes_in_rec > 0. Lastly the odd bits need to be masked out if the bytes_in_rec > 0.
Otherwise stray bits can cause spurious values. Otherwise stray bits can cause spurious values.
*/ */
uint len= from_len + ((from_bit_len > 0) ? 1 : 0);
uint new_len= (field_length + 7) / 8; uint new_len= (field_length + 7) / 8;
if (from + len > from_end || new_len < len)
return 0; // Error in data
char *value= (char *)my_alloca(new_len); char *value= (char *)my_alloca(new_len);
bzero(value, new_len); bzero(value, new_len);
uint len= from_len + ((from_bit_len > 0) ? 1 : 0);
memcpy(value + (new_len - len), from, len); memcpy(value + (new_len - len), from, len);
/* /*
Mask out the unused bits in the partial byte. Mask out the unused bits in the partial byte.
......
...@@ -483,17 +483,8 @@ class Field ...@@ -483,17 +483,8 @@ class Field
DBUG_RETURN(result); DBUG_RETURN(result);
} }
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar* to, const uchar *from,
/** const uchar *from_end, uint param_data=0);
@overload Field::unpack(uchar*, const uchar*, uint, bool)
*/
const uchar *unpack(uchar* to, const uchar *from)
{
DBUG_ENTER("Field::unpack");
const uchar *result= unpack(to, from, 0);
DBUG_RETURN(result);
}
virtual uchar *pack_key(uchar* to, const uchar *from, uint max_length) virtual uchar *pack_key(uchar* to, const uchar *from, uint max_length)
{ {
return pack(to, from, max_length); return pack(to, from, max_length);
...@@ -502,10 +493,12 @@ class Field ...@@ -502,10 +493,12 @@ class Field
{ {
return pack(to, from, max_length); return pack(to, from, max_length);
} }
#ifdef NOT_USED
virtual const uchar *unpack_key(uchar* to, const uchar *from, uint max_length) virtual const uchar *unpack_key(uchar* to, const uchar *from, uint max_length)
{ {
return unpack(to, from, max_length); return unpack(to, from, from + max_length+2, max_length);
} }
#endif
virtual uint packed_col_length(const uchar *to, uint length) virtual uint packed_col_length(const uchar *to, uint length)
{ return length;} { return length;}
virtual uint max_packed_col_length(uint max_length) virtual uint max_packed_col_length(uint max_length)
...@@ -786,10 +779,6 @@ class Field_decimal :public Field_real { ...@@ -786,10 +779,6 @@ class Field_decimal :public Field_real {
void overflow(bool negative); void overflow(bool negative);
bool zero_pack() const { return 0; } bool zero_pack() const { return 0; }
void sql_type(String &str) const; void sql_type(String &str) const;
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data)
{
return Field::unpack(to, from, param_data);
}
virtual uchar *pack(uchar* to, const uchar *from, uint max_length) virtual uchar *pack(uchar* to, const uchar *from, uint max_length)
{ {
return Field::pack(to, from, max_length); return Field::pack(to, from, max_length);
...@@ -845,7 +834,7 @@ class Field_new_decimal :public Field_num { ...@@ -845,7 +834,7 @@ class Field_new_decimal :public Field_num {
int compatible_field_size(uint field_metadata, int compatible_field_size(uint field_metadata,
const Relay_log_info *rli, uint16 mflags); const Relay_log_info *rli, uint16 mflags);
uint is_equal(Create_field *new_field); uint is_equal(Create_field *new_field);
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data);
static Field *create_from_item (Item *); static Field *create_from_item (Item *);
}; };
...@@ -883,8 +872,11 @@ class Field_tiny :public Field_num { ...@@ -883,8 +872,11 @@ class Field_tiny :public Field_num {
return to + 1; return to + 1;
} }
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data) virtual const uchar *unpack(uchar* to, const uchar *from,
const uchar *from_end, uint param_data)
{ {
if (from == from_end)
return 0;
*to= *from; *to= *from;
return from + 1; return from + 1;
} }
...@@ -928,15 +920,19 @@ class Field_short :public Field_num { ...@@ -928,15 +920,19 @@ class Field_short :public Field_num {
int16 val; int16 val;
val = sint2korr(from); val = sint2korr(from);
int2store(to, val); int2store(to, val);
return to + sizeof(val); return to + 2;
} }
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data) virtual const uchar *unpack(uchar* to, const uchar *from,
const uchar *from_end, uint param_data)
{ {
int16 val; int16 val;
if (from +2 > from_end)
return 0;
val = sint2korr(from); val = sint2korr(from);
int2store(to, val); int2store(to, val);
return from + sizeof(val); return from + 2;
} }
}; };
...@@ -971,11 +967,6 @@ class Field_medium :public Field_num { ...@@ -971,11 +967,6 @@ class Field_medium :public Field_num {
{ {
return Field::pack(to, from, max_length); return Field::pack(to, from, max_length);
} }
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data)
{
return Field::unpack(to, from, param_data);
}
}; };
...@@ -1016,8 +1007,11 @@ class Field_long :public Field_num { ...@@ -1016,8 +1007,11 @@ class Field_long :public Field_num {
return pack_int32(to, from); return pack_int32(to, from);
} }
virtual const uchar *unpack(uchar* to, const uchar *from, virtual const uchar *unpack(uchar* to, const uchar *from,
const uchar *from_end,
uint param_data __attribute__((unused))) uint param_data __attribute__((unused)))
{ {
if (from + 4 > from_end)
return 0;
return unpack_int32(to, from); return unpack_int32(to, from);
} }
}; };
...@@ -1064,9 +1058,11 @@ class Field_longlong :public Field_num { ...@@ -1064,9 +1058,11 @@ class Field_longlong :public Field_num {
{ {
return pack_int64(to, from); return pack_int64(to, from);
} }
virtual const uchar *unpack(uchar* to, const uchar *from, const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data __attribute__((unused))) uint param_data __attribute__((unused)))
{ {
if (from + 8 > from_end)
return 0;
return unpack_int64(to, from); return unpack_int64(to, from);
} }
}; };
...@@ -1230,9 +1226,11 @@ class Field_timestamp :public Field_str { ...@@ -1230,9 +1226,11 @@ class Field_timestamp :public Field_str {
{ {
return pack_int32(to, from); return pack_int32(to, from);
} }
const uchar *unpack(uchar* to, const uchar *from, const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data __attribute__((unused))) uint param_data __attribute__((unused)))
{ {
if (from + 4 > from_end)
return 0;
return unpack_int32(to, from); return unpack_int32(to, from);
} }
}; };
...@@ -1269,8 +1267,9 @@ class Field_timestamp_hires :public Field_timestamp { ...@@ -1269,8 +1267,9 @@ class Field_timestamp_hires :public Field_timestamp {
uint32 pack_length() const; uint32 pack_length() const;
uchar *pack(uchar *to, const uchar *from, uint max_length) uchar *pack(uchar *to, const uchar *from, uint max_length)
{ return Field::pack(to, from, max_length); } { return Field::pack(to, from, max_length); }
const uchar *unpack(uchar* to, const uchar *from, uint param_data) const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
{ return Field::unpack(to, from, param_data); } uint param_data)
{ return Field::unpack(to, from, from_end, param_data); }
uint size_of() const { return sizeof(*this); } uint size_of() const { return sizeof(*this); }
bool eq_def(Field *field) bool eq_def(Field *field)
{ return Field_str::eq_def(field) && dec == field->decimals(); } { return Field_str::eq_def(field) && dec == field->decimals(); }
...@@ -1353,9 +1352,11 @@ class Field_date :public Field_temporal { ...@@ -1353,9 +1352,11 @@ class Field_date :public Field_temporal {
{ {
return pack_int32(to, from); return pack_int32(to, from);
} }
const uchar *unpack(uchar* to, const uchar *from, const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data __attribute__((unused))) uint param_data __attribute__((unused)))
{ {
if (from + 4 > from_end)
return 0;
return unpack_int32(to, from); return unpack_int32(to, from);
} }
}; };
...@@ -1482,9 +1483,11 @@ class Field_datetime :public Field_temporal { ...@@ -1482,9 +1483,11 @@ class Field_datetime :public Field_temporal {
{ {
return pack_int64(to, from); return pack_int64(to, from);
} }
const uchar *unpack(uchar* to, const uchar *from, const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
uint param_data __attribute__((unused))) uint param_data __attribute__((unused)))
{ {
if (from + 8 > from_end)
return 0;
return unpack_int64(to, from); return unpack_int64(to, from);
} }
}; };
...@@ -1520,8 +1523,9 @@ class Field_datetime_hires :public Field_datetime { ...@@ -1520,8 +1523,9 @@ class Field_datetime_hires :public Field_datetime {
bool get_date(MYSQL_TIME *ltime,uint fuzzydate); bool get_date(MYSQL_TIME *ltime,uint fuzzydate);
uchar *pack(uchar *to, const uchar *from, uint max_length) uchar *pack(uchar *to, const uchar *from, uint max_length)
{ return Field::pack(to, from, max_length); } { return Field::pack(to, from, max_length); }
const uchar *unpack(uchar* to, const uchar *from, uint param_data) const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end,
{ return Field::unpack(to, from, param_data); } uint param_data)
{ return Field::unpack(to, from, from_end, param_data); }
uint size_of() const { return sizeof(*this); } uint size_of() const { return sizeof(*this); }
}; };
...@@ -1612,7 +1616,8 @@ class Field_string :public Field_longstr { ...@@ -1612,7 +1616,8 @@ class Field_string :public Field_longstr {
void sql_type(String &str) const; void sql_type(String &str) const;
virtual uchar *pack(uchar *to, const uchar *from, virtual uchar *pack(uchar *to, const uchar *from,
uint max_length); uint max_length);
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar* to, const uchar *from,
const uchar *from_end,uint param_data);
uint pack_length_from_metadata(uint field_metadata) uint pack_length_from_metadata(uint field_metadata)
{ {
DBUG_PRINT("debug", ("field_metadata: 0x%04x", field_metadata)); DBUG_PRINT("debug", ("field_metadata: 0x%04x", field_metadata));
...@@ -1700,8 +1705,11 @@ class Field_varstring :public Field_longstr { ...@@ -1700,8 +1705,11 @@ class Field_varstring :public Field_longstr {
virtual uchar *pack(uchar *to, const uchar *from, uint max_length); virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
uchar *pack_key(uchar *to, const uchar *from, uint max_length); uchar *pack_key(uchar *to, const uchar *from, uint max_length);
uchar *pack_key_from_key_image(uchar* to, const uchar *from, uint max_length); uchar *pack_key_from_key_image(uchar* to, const uchar *from, uint max_length);
virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar* to, const uchar *from,
const uchar *from_end, uint param_data);
#ifdef NOT_USED
const uchar *unpack_key(uchar* to, const uchar *from, uint max_length); const uchar *unpack_key(uchar* to, const uchar *from, uint max_length);
#endif
int pack_cmp(const uchar *a, const uchar *b, uint key_length, int pack_cmp(const uchar *a, const uchar *b, uint key_length,
bool insert_or_update); bool insert_or_update);
int pack_cmp(const uchar *b, uint key_length,bool insert_or_update); int pack_cmp(const uchar *b, uint key_length,bool insert_or_update);
...@@ -1873,8 +1881,11 @@ class Field_blob :public Field_longstr { ...@@ -1873,8 +1881,11 @@ class Field_blob :public Field_longstr {
virtual uchar *pack(uchar *to, const uchar *from, uint max_length); virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
uchar *pack_key(uchar *to, const uchar *from, uint max_length); uchar *pack_key(uchar *to, const uchar *from, uint max_length);
uchar *pack_key_from_key_image(uchar* to, const uchar *from, uint max_length); uchar *pack_key_from_key_image(uchar* to, const uchar *from, uint max_length);
virtual const uchar *unpack(uchar *to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar *to, const uchar *from,
const uchar *from_end, uint param_data);
#ifdef NOT_USED
const uchar *unpack_key(uchar* to, const uchar *from, uint max_length); const uchar *unpack_key(uchar* to, const uchar *from, uint max_length);
#endif
int pack_cmp(const uchar *a, const uchar *b, uint key_length, int pack_cmp(const uchar *a, const uchar *b, uint key_length,
bool insert_or_update); bool insert_or_update);
int pack_cmp(const uchar *b, uint key_length,bool insert_or_update); int pack_cmp(const uchar *b, uint key_length,bool insert_or_update);
...@@ -2077,7 +2088,8 @@ class Field_bit :public Field { ...@@ -2077,7 +2088,8 @@ class Field_bit :public Field {
const Relay_log_info *rli, uint16 mflags); const Relay_log_info *rli, uint16 mflags);
void sql_type(String &str) const; void sql_type(String &str) const;
virtual uchar *pack(uchar *to, const uchar *from, uint max_length); virtual uchar *pack(uchar *to, const uchar *from, uint max_length);
virtual const uchar *unpack(uchar *to, const uchar *from, uint param_data); virtual const uchar *unpack(uchar *to, const uchar *from,
const uchar *from_end, uint param_data);
virtual void set_default(); virtual void set_default();
Field *new_key_field(MEM_ROOT *root, struct st_table *new_table, Field *new_key_field(MEM_ROOT *root, struct st_table *new_table,
......
...@@ -60,7 +60,7 @@ static uint sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length, ...@@ -60,7 +60,7 @@ static uint sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length,
static SORT_ADDON_FIELD *get_addon_fields(THD *thd, Field **ptabfield, static SORT_ADDON_FIELD *get_addon_fields(THD *thd, Field **ptabfield,
uint sortlength, uint *plength); uint sortlength, uint *plength);
static void unpack_addon_fields(struct st_sort_addon_field *addon_field, static void unpack_addon_fields(struct st_sort_addon_field *addon_field,
uchar *buff); uchar *buff, uchar *buff_end);
/** /**
Sort a table. Sort a table.
Creates a set of pointers that can be used to read the rows Creates a set of pointers that can be used to read the rows
...@@ -1732,7 +1732,8 @@ get_addon_fields(THD *thd, Field **ptabfield, uint sortlength, uint *plength) ...@@ -1732,7 +1732,8 @@ get_addon_fields(THD *thd, Field **ptabfield, uint sortlength, uint *plength)
*/ */
static void static void
unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff) unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff,
uchar *buff_end)
{ {
Field *field; Field *field;
SORT_ADDON_FIELD *addonf= addon_field; SORT_ADDON_FIELD *addonf= addon_field;
...@@ -1745,7 +1746,7 @@ unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff) ...@@ -1745,7 +1746,7 @@ unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff)
continue; continue;
} }
field->set_notnull(); field->set_notnull();
field->unpack(field->ptr, buff + addonf->offset); field->unpack(field->ptr, buff + addonf->offset, buff_end, 0);
} }
} }
......
...@@ -3752,7 +3752,8 @@ class Rows_log_event : public Log_event ...@@ -3752,7 +3752,8 @@ class Rows_log_event : public Log_event
DBUG_ASSERT(m_table); DBUG_ASSERT(m_table);
ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT); ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT);
int const result= ::unpack_row(rli, m_table, m_width, m_curr_row, &m_cols, int const result= ::unpack_row(rli, m_table, m_width, m_curr_row,
m_rows_end, &m_cols,
&m_curr_row_end, &m_master_reclength); &m_curr_row_end, &m_master_reclength);
if (m_curr_row_end > m_rows_end) if (m_curr_row_end > m_rows_end)
my_error(ER_SLAVE_CORRUPT_EVENT, MYF(0)); my_error(ER_SLAVE_CORRUPT_EVENT, MYF(0));
......
...@@ -998,7 +998,8 @@ Write_rows_log_event_old::do_prepare_row(THD *thd_arg, ...@@ -998,7 +998,8 @@ Write_rows_log_event_old::do_prepare_row(THD *thd_arg,
int error; int error;
error= unpack_row_old(const_cast<Relay_log_info*>(rli), error= unpack_row_old(const_cast<Relay_log_info*>(rli),
table, m_width, table->record[0], table, m_width, table->record[0],
row_start, &m_cols, row_end, &m_master_reclength, row_start, m_rows_end,
&m_cols, row_end, &m_master_reclength,
table->write_set, PRE_GA_WRITE_ROWS_EVENT); table->write_set, PRE_GA_WRITE_ROWS_EVENT);
bitmap_copy(table->read_set, table->write_set); bitmap_copy(table->read_set, table->write_set);
return error; return error;
...@@ -1085,7 +1086,8 @@ Delete_rows_log_event_old::do_prepare_row(THD *thd_arg, ...@@ -1085,7 +1086,8 @@ Delete_rows_log_event_old::do_prepare_row(THD *thd_arg,
error= unpack_row_old(const_cast<Relay_log_info*>(rli), error= unpack_row_old(const_cast<Relay_log_info*>(rli),
table, m_width, table->record[0], table, m_width, table->record[0],
row_start, &m_cols, row_end, &m_master_reclength, row_start, m_rows_end,
&m_cols, row_end, &m_master_reclength,
table->read_set, PRE_GA_DELETE_ROWS_EVENT); table->read_set, PRE_GA_DELETE_ROWS_EVENT);
/* /*
If we will access rows using the random access method, m_key will If we will access rows using the random access method, m_key will
...@@ -1184,13 +1186,15 @@ int Update_rows_log_event_old::do_prepare_row(THD *thd_arg, ...@@ -1184,13 +1186,15 @@ int Update_rows_log_event_old::do_prepare_row(THD *thd_arg,
/* record[0] is the before image for the update */ /* record[0] is the before image for the update */
error= unpack_row_old(const_cast<Relay_log_info*>(rli), error= unpack_row_old(const_cast<Relay_log_info*>(rli),
table, m_width, table->record[0], table, m_width, table->record[0],
row_start, &m_cols, row_end, &m_master_reclength, row_start, m_rows_end,
&m_cols, row_end, &m_master_reclength,
table->read_set, PRE_GA_UPDATE_ROWS_EVENT); table->read_set, PRE_GA_UPDATE_ROWS_EVENT);
row_start = *row_end; row_start = *row_end;
/* m_after_image is the after image for the update */ /* m_after_image is the after image for the update */
error= unpack_row_old(const_cast<Relay_log_info*>(rli), error= unpack_row_old(const_cast<Relay_log_info*>(rli),
table, m_width, m_after_image, table, m_width, m_after_image,
row_start, &m_cols, row_end, &m_master_reclength, row_start, m_rows_end,
&m_cols, row_end, &m_master_reclength,
table->write_set, PRE_GA_UPDATE_ROWS_EVENT); table->write_set, PRE_GA_UPDATE_ROWS_EVENT);
DBUG_DUMP("record[0]", table->record[0], table->s->reclength); DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
......
...@@ -203,7 +203,8 @@ class Old_rows_log_event : public Log_event ...@@ -203,7 +203,8 @@ class Old_rows_log_event : public Log_event
{ {
DBUG_ASSERT(m_table); DBUG_ASSERT(m_table);
ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT); ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT);
int const result= ::unpack_row(rli, m_table, m_width, m_curr_row, &m_cols, int const result= ::unpack_row(rli, m_table, m_width, m_curr_row,
m_rows_end, &m_cols,
&m_curr_row_end, &m_master_reclength); &m_curr_row_end, &m_master_reclength);
ASSERT_OR_RETURN_ERROR(m_curr_row_end <= m_rows_end, HA_ERR_CORRUPT_EVENT); ASSERT_OR_RETURN_ERROR(m_curr_row_end <= m_rows_end, HA_ERR_CORRUPT_EVENT);
return result; return result;
......
...@@ -447,7 +447,8 @@ static int rr_unpack_from_tempfile(READ_RECORD *info) ...@@ -447,7 +447,8 @@ static int rr_unpack_from_tempfile(READ_RECORD *info)
if (my_b_read(info->io_cache, info->rec_buf, info->ref_length)) if (my_b_read(info->io_cache, info->rec_buf, info->ref_length))
return -1; return -1;
TABLE *table= info->table; TABLE *table= info->table;
(*table->sort.unpack)(table->sort.addon_field, info->rec_buf); (*table->sort.unpack)(table->sort.addon_field, info->rec_buf,
info->rec_buf + info->ref_length);
return 0; return 0;
} }
...@@ -498,7 +499,8 @@ static int rr_unpack_from_buffer(READ_RECORD *info) ...@@ -498,7 +499,8 @@ static int rr_unpack_from_buffer(READ_RECORD *info)
if (info->cache_pos == info->cache_end) if (info->cache_pos == info->cache_end)
return -1; /* End of buffer */ return -1; /* End of buffer */
TABLE *table= info->table; TABLE *table= info->table;
(*table->sort.unpack)(table->sort.addon_field, info->cache_pos); (*table->sort.unpack)(table->sort.addon_field, info->cache_pos,
info->cache_end);
info->cache_pos+= info->ref_length; info->cache_pos+= info->ref_length;
return 0; return 0;
......
...@@ -176,13 +176,15 @@ pack_row(TABLE *table, MY_BITMAP const* cols, ...@@ -176,13 +176,15 @@ pack_row(TABLE *table, MY_BITMAP const* cols,
@retval ER_NO_DEFAULT_FOR_FIELD @retval ER_NO_DEFAULT_FOR_FIELD
Returned if one of the fields existing on the slave but not on the Returned if one of the fields existing on the slave but not on the
master does not have a default value (and isn't nullable) master does not have a default value (and isn't nullable)
@retval ER_SLAVE_CORRUPT_EVENT
Found error when trying to unpack fields.
*/ */
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
int int
unpack_row(Relay_log_info const *rli, unpack_row(Relay_log_info const *rli,
TABLE *table, uint const colcnt, TABLE *table, uint const colcnt,
uchar const *const row_data, MY_BITMAP const *cols, uchar const *const row_data, uchar const *const row_buffer_end,
MY_BITMAP const *cols,
uchar const **const row_end, ulong *const master_reclength) uchar const **const row_end, ulong *const master_reclength)
{ {
DBUG_ENTER("unpack_row"); DBUG_ENTER("unpack_row");
...@@ -224,9 +226,6 @@ unpack_row(Relay_log_info const *rli, ...@@ -224,9 +226,6 @@ unpack_row(Relay_log_info const *rli,
DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set
/* Field...::unpack() cannot return 0 */
DBUG_ASSERT(pack_ptr != NULL);
if (null_bits & null_mask) if (null_bits & null_mask)
{ {
if (f->maybe_null()) if (f->maybe_null())
...@@ -272,14 +271,21 @@ unpack_row(Relay_log_info const *rli, ...@@ -272,14 +271,21 @@ unpack_row(Relay_log_info const *rli,
#ifndef DBUG_OFF #ifndef DBUG_OFF
uchar const *const old_pack_ptr= pack_ptr; uchar const *const old_pack_ptr= pack_ptr;
#endif #endif
pack_ptr= f->unpack(f->ptr, pack_ptr, metadata); pack_ptr= f->unpack(f->ptr, pack_ptr, row_buffer_end, metadata);
DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;" DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;"
" pack_ptr: 0x%lx; pack_ptr': 0x%lx; bytes: %d", " pack_ptr: 0x%lx; pack_ptr': 0x%lx; bytes: %d",
f->field_name, metadata, f->field_name, metadata,
(ulong) old_pack_ptr, (ulong) pack_ptr, (ulong) old_pack_ptr, (ulong) pack_ptr,
(int) (pack_ptr - old_pack_ptr))); (int) (pack_ptr - old_pack_ptr)));
if (!pack_ptr)
{
rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT,
"Could not read field `%s` of table `%s`.`%s`",
f->field_name, table->s->db.str,
table->s->table_name.str);
DBUG_RETURN(ER_SLAVE_CORRUPT_EVENT);
}
} }
null_mask <<= 1; null_mask <<= 1;
} }
i++; i++;
......
...@@ -29,7 +29,8 @@ size_t pack_row(TABLE* table, MY_BITMAP const* cols, ...@@ -29,7 +29,8 @@ size_t pack_row(TABLE* table, MY_BITMAP const* cols,
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
int unpack_row(Relay_log_info const *rli, int unpack_row(Relay_log_info const *rli,
TABLE *table, uint const colcnt, TABLE *table, uint const colcnt,
uchar const *const row_data, MY_BITMAP const *cols, uchar const *const row_data, uchar const *row_buffer_end,
MY_BITMAP const *cols,
uchar const **const row_end, ulong *const master_reclength); uchar const **const row_end, ulong *const master_reclength);
// Fill table's record[0] with default values. // Fill table's record[0] with default values.
......
...@@ -82,12 +82,15 @@ pack_row_old(TABLE *table, MY_BITMAP const* cols, ...@@ -82,12 +82,15 @@ pack_row_old(TABLE *table, MY_BITMAP const* cols,
ER_NO_DEFAULT_FOR_FIELD ER_NO_DEFAULT_FOR_FIELD
Returned if one of the fields existing on the slave but not on Returned if one of the fields existing on the slave but not on
the master does not have a default value (and isn't nullable) the master does not have a default value (and isn't nullable)
ER_SLAVE_CORRUPT_EVENT
Wrong data for field found.
*/ */
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
int int
unpack_row_old(Relay_log_info *rli, unpack_row_old(Relay_log_info *rli,
TABLE *table, uint const colcnt, uchar *record, TABLE *table, uint const colcnt, uchar *record,
uchar const *row, MY_BITMAP const *cols, uchar const *row, const uchar *row_buffer_end,
MY_BITMAP const *cols,
uchar const **row_end, ulong *master_reclength, uchar const **row_end, ulong *master_reclength,
MY_BITMAP* const rw_set, Log_event_type const event_type) MY_BITMAP* const rw_set, Log_event_type const event_type)
{ {
...@@ -133,10 +136,16 @@ unpack_row_old(Relay_log_info *rli, ...@@ -133,10 +136,16 @@ unpack_row_old(Relay_log_info *rli,
if (bitmap_is_set(cols, field_ptr - begin_ptr)) if (bitmap_is_set(cols, field_ptr - begin_ptr))
{ {
f->move_field_offset(offset); f->move_field_offset(offset);
ptr= f->unpack(f->ptr, ptr); ptr= f->unpack(f->ptr, ptr, row_buffer_end, 0);
f->move_field_offset(-offset); f->move_field_offset(-offset);
/* Field...::unpack() cannot return 0 */ if (!ptr)
DBUG_ASSERT(ptr != NULL); {
rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT,
"Could not read field `%s` of table `%s`.`%s`",
f->field_name, table->s->db.str,
table->s->table_name.str);
return(ER_SLAVE_CORRUPT_EVENT);
}
} }
else else
bitmap_clear_bit(rw_set, field_ptr - begin_ptr); bitmap_clear_bit(rw_set, field_ptr - begin_ptr);
......
...@@ -23,7 +23,8 @@ size_t pack_row_old(TABLE *table, MY_BITMAP const* cols, ...@@ -23,7 +23,8 @@ size_t pack_row_old(TABLE *table, MY_BITMAP const* cols,
#ifdef HAVE_REPLICATION #ifdef HAVE_REPLICATION
int unpack_row_old(Relay_log_info *rli, int unpack_row_old(Relay_log_info *rli,
TABLE *table, uint const colcnt, uchar *record, TABLE *table, uint const colcnt, uchar *record,
uchar const *row, MY_BITMAP const *cols, uchar const *row, uchar const *row_buffer_end,
MY_BITMAP const *cols,
uchar const **row_end, ulong *master_reclength, uchar const **row_end, ulong *master_reclength,
MY_BITMAP* const rw_set, MY_BITMAP* const rw_set,
Log_event_type const event_type); Log_event_type const event_type);
......
...@@ -15763,8 +15763,13 @@ int report_error(TABLE *table, int error) ...@@ -15763,8 +15763,13 @@ int report_error(TABLE *table, int error)
print them to the .err log print them to the .err log
*/ */
if (error != HA_ERR_LOCK_DEADLOCK && error != HA_ERR_LOCK_WAIT_TIMEOUT) if (error != HA_ERR_LOCK_DEADLOCK && error != HA_ERR_LOCK_WAIT_TIMEOUT)
{
push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_ERROR, error,
"Got error %d when reading table `%s`.`%s`",
error, table->s->db.str, table->s->table_name.str);
sql_print_error("Got error %d when reading table '%s'", sql_print_error("Got error %d when reading table '%s'",
error, table->s->path.str); error, table->s->path.str);
}
table->file->print_error(error,MYF(0)); table->file->print_error(error,MYF(0));
return 1; return 1;
} }
......
...@@ -166,7 +166,7 @@ typedef struct st_filesort_info ...@@ -166,7 +166,7 @@ typedef struct st_filesort_info
uchar *addon_buf; /* Pointer to a buffer if sorted with fields */ uchar *addon_buf; /* Pointer to a buffer if sorted with fields */
size_t addon_length; /* Length of the buffer */ size_t addon_length; /* Length of the buffer */
struct st_sort_addon_field *addon_field; /* Pointer to the fields info */ struct st_sort_addon_field *addon_field; /* Pointer to the fields info */
void (*unpack)(struct st_sort_addon_field *, uchar *); /* To unpack back */ void (*unpack)(struct st_sort_addon_field *, uchar *, uchar *); /* To unpack back */
uchar *record_pointers; /* If sorted in memory */ uchar *record_pointers; /* If sorted in memory */
ha_rows found_records; /* How many records in sort */ ha_rows found_records; /* How many records in sort */
} FILESORT_INFO; } FILESORT_INFO;
......
...@@ -1120,20 +1120,26 @@ int ha_archive::unpack_row(azio_stream *file_to_read, uchar *record) ...@@ -1120,20 +1120,26 @@ int ha_archive::unpack_row(azio_stream *file_to_read, uchar *record)
if (read != row_len || error) if (read != row_len || error)
{ {
DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); DBUG_RETURN(error ? HA_ERR_CRASHED_ON_USAGE : HA_ERR_WRONG_IN_RECORD);
} }
/* Copy null bits */ /* Copy null bits */
const uchar *ptr= record_buffer->buffer; const uchar *ptr= record_buffer->buffer, *end= ptr+ row_len;
memcpy(record, ptr, table->s->null_bytes); memcpy(record, ptr, table->s->null_bytes);
ptr+= table->s->null_bytes; ptr+= table->s->null_bytes;
if (ptr > end)
DBUG_RETURN(HA_ERR_WRONG_IN_RECORD);
for (Field **field=table->field ; *field ; field++) for (Field **field=table->field ; *field ; field++)
{ {
if (!((*field)->is_null_in_record(record))) if (!((*field)->is_null_in_record(record)))
{ {
ptr= (*field)->unpack(record + (*field)->offset(table->record[0]), ptr); if (!(ptr= (*field)->unpack(record + (*field)->offset(table->record[0]),
ptr, end)))
DBUG_RETURN(HA_ERR_WRONG_IN_RECORD);
} }
} }
if (ptr != end)
DBUG_RETURN(HA_ERR_WRONG_IN_RECORD);
DBUG_RETURN(0); DBUG_RETURN(0);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment