@@ -17,6 +17,7 @@ package wal
1717import (
1818 "bytes"
1919 "fmt"
20+ "github.com/stretchr/testify/require"
2021 "io"
2122 "io/ioutil"
2223 "math"
@@ -1155,3 +1156,79 @@ func TestValidSnapshotEntriesAfterPurgeWal(t *testing.T) {
11551156 t .Fatal (err )
11561157 }
11571158}
1159+
1160+ func TestLastRecordLengthExceedFileEnd (t * testing.T ) {
1161+ /* The data below was generated by code something like below. The length
1162+ * of the last record was intentionally changed to 1000 in order to make
1163+ * sure it exceeds the end of the file.
1164+ *
1165+ * for i := 0; i < 3; i++ {
1166+ * es := []raftpb.Entry{{Index: uint64(i + 1), Data: []byte(fmt.Sprintf("waldata%d", i+1))}}
1167+ * if err = w.Save(raftpb.HardState{}, es); err != nil {
1168+ * t.Fatal(err)
1169+ * }
1170+ * }
1171+ * ......
1172+ * var sb strings.Builder
1173+ * for _, ch := range buf {
1174+ * sb.WriteString(fmt.Sprintf("\\x%02x", ch))
1175+ * }
1176+ */
1177+ // Generate WAL file
1178+ t .Log ("Generate a WAL file with the last record's length modified." )
1179+ data := []byte ("\x04 \x00 \x00 \x00 \x00 \x00 \x00 \x84 \x08 \x04 \x10 \x00 \x00 " +
1180+ "\x00 \x00 \x00 \x04 \x00 \x00 \x00 \x00 \x00 \x00 \x84 \x08 \x01 \x10 \x00 \x00 " +
1181+ "\x00 \x00 \x00 \x0e \x00 \x00 \x00 \x00 \x00 \x00 \x82 \x08 \x05 \x10 \xa0 \xb3 " +
1182+ "\x9b \x8f \x08 \x1a \x04 \x08 \x00 \x10 \x00 \x00 \x00 \x1a \x00 \x00 \x00 \x00 " +
1183+ "\x00 \x00 \x86 \x08 \x02 \x10 \xba \x8b \xdc \x85 \x0f \x1a \x10 \x08 \x00 \x10 " +
1184+ "\x00 \x18 \x01 \x22 \x08 \x77 \x61 \x6c \x64 \x61 \x74 \x61 \x31 \x00 \x00 \x00 " +
1185+ "\x00 \x00 \x00 \x1a \x00 \x00 \x00 \x00 \x00 \x00 \x86 \x08 \x02 \x10 \xa1 \xe8 " +
1186+ "\xff \x9c \x02 \x1a \x10 \x08 \x00 \x10 \x00 \x18 \x02 \x22 \x08 \x77 \x61 \x6c " +
1187+ "\x64 \x61 \x74 \x61 \x32 \x00 \x00 \x00 \x00 \x00 \x00 \xe8 \x03 \x00 \x00 \x00 " +
1188+ "\x00 \x00 \x86 \x08 \x02 \x10 \xa1 \x9c \xa1 \xaa \x04 \x1a \x10 \x08 \x00 \x10 " +
1189+ "\x00 \x18 \x03 \x22 \x08 \x77 \x61 \x6c \x64 \x61 \x74 \x61 \x33 \x00 \x00 \x00 " +
1190+ "\x00 \x00 \x00 " )
1191+
1192+ buf := bytes .NewBuffer (data )
1193+ f , err := createFileWithData (t , buf )
1194+ fileName := f .Name ()
1195+ require .NoError (t , err )
1196+ t .Logf ("fileName: %v" , fileName )
1197+
1198+ // Verify low-level decoder directly
1199+ t .Log ("Verify all records can be parsed correctly." )
1200+ rec := & walpb.Record {}
1201+ decoder := newDecoder (fileutil .NewFileReader (f ))
1202+ for {
1203+ if err = decoder .decode (rec ); err != nil {
1204+ require .ErrorIs (t , err , io .ErrUnexpectedEOF )
1205+ break
1206+ }
1207+ if rec .Type == entryType {
1208+ e := mustUnmarshalEntry (rec .Data )
1209+ t .Logf ("Validating normal entry: %v" , e )
1210+ recData := fmt .Sprintf ("waldata%d" , e .Index )
1211+ require .Equal (t , raftpb .EntryNormal , e .Type )
1212+ require .Equal (t , recData , string (e .Data ))
1213+ }
1214+ rec = & walpb.Record {}
1215+ }
1216+ require .NoError (t , f .Close ())
1217+
1218+ // Verify w.ReadAll() returns io.ErrUnexpectedEOF in the error chain.
1219+ t .Log ("Verify the w.ReadAll returns io.ErrUnexpectedEOF in the error chain" )
1220+ newFileName := filepath .Join (filepath .Dir (fileName ), "0000000000000000-0000000000000000.wal" )
1221+ require .NoError (t , os .Rename (fileName , newFileName ))
1222+
1223+ w , err := Open (zaptest .NewLogger (t ), filepath .Dir (fileName ), walpb.Snapshot {
1224+ Index : 0 ,
1225+ Term : 0 ,
1226+ })
1227+ require .NoError (t , err )
1228+ defer w .Close ()
1229+
1230+ _ , _ , _ , err = w .ReadAll ()
1231+ // Note: The wal file will be repaired automatically in production
1232+ // environment, but only once.
1233+ require .ErrorIs (t , err , io .ErrUnexpectedEOF )
1234+ }
0 commit comments