Repository: redis/redis

Problem statement:
[BUG] redis-check-aof fails when aof file is ok
**Describe the bug**

`redis-check-aof` fails but aof file is valid.

Running `redis-check-aof` from version 7.2.1 on an aof file produced by a 6.2.5 redis-server results in this error: 
```
++ redis-check-aof --fix /redis/redis_config_7/appendonly.aof

Start checking Multi Part AOF
*** FATAL AOF MANIFEST FILE ERROR ***

Reading the manifest file, at line 1

# >>> 'REDIS0009�	redis-ver6.2.5�'

# Invalid AOF manifest file format
```

Running `redis-check-aof` from version 6.2.5 on the same file is successful:

```
The AOF appears to start with an RDB preamble.
Checking the RDB preamble to start:
[offset 0] Checking RDB file ../appendonly.aof
[offset 26] AUX FIELD redis-ver = '6.2.5'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1703584773'
[offset 67] AUX FIELD used-mem = '15533832'
[offset 83] AUX FIELD aof-preamble = '1'
[offset 85] Selecting DB ID 0
[offset 1181521] Checksum OK
[offset 1181521] \o/ RDB looks OK! \o/
[info] 23 keys read
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
AOF analyzed: size=208098914, ok_up_to=208098914, ok_up_to_line=10206, diff=0
AOF is valid
```

Also, running a redis-server (7.2.1) with the same aof file is also successful, and information is loaded.
```
2100634:C 15 Jan 2024 10:48:48.631 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2100634:C 15 Jan 2024 10:48:48.631 * Redis version=7.2.1, bits=64, commit=bf577adf, modified=1, pid=2100634, just started
...
2100634:M 15 Jan 2024 10:48:48.675 * Successfully migrated an old-style AOF file (appendonly.aof) into the AOF directory (appendonlydir).
...
2100634:M 15 Jan 2024 10:48:48.675 * Reading RDB preamble from AOF file...
2100634:M 15 Jan 2024 10:48:48.675 * Loading RDB produced by version 6.2.5
```

**To reproduce**

I did not find a minimal way to reproduce. We run 7.2.1 `redis-check-aof`  with 6.2.5 aof files all the time, and this only reproduces now and then. 
I didn't yet manage to understand how these files are different than others. 
I can not share the aof file as it might contain sensitive information.
Did not find any errors in the `redis-server` that wrote the `aof` file.



**Expected behavior**

`redis-check-aof` from 6.2.5 and 7.2.1 should have the same output

**Additional information**

- Compiling and running on ubuntu 20


header of the aof file: 
```
hexdump appendonly.aof -n 200
0000000 4552 4944 3053 3030 fa39 7209 6465 7369
0000010 762d 7265 3605 322e 352e 0afa 6572 6964
0000020 2d73 6962 7374 40c0 05fa 7463 6d69 c265
0000030 a405 658a 08fa 7375 6465 6d2d 6d65 08c2
0000040 ed07 fa00 610c 666f 702d 6572 6d61 6c62
0000050 c065 fe01 fb00 0017 0f00 7270 7669 7461
0000060 5f65 6576 7372 6f69 c06e 0005 660a 7269
0000070 7473 625f 6f6f c074 0001 7405 5f78 6469
0000080 02c0 0e04 6761 6e65 3a74 6f68 6b6f 5f73
0000090 6264 0d1a 6277 665f 5f65 6761 6e65 5f74
00000a0 c330 0080 6200 808d 0300 24bf 7b0f 6822
00000b0 6f6f 736b 645f 2262 7b3a 5f22 4048 0305
00000c0 6f4e 656e 0740 3105
```






Hints:
can you share your AOF file (on private)? i am not able to reproduce it.

7.2.1
```
./src/redis-check-aof  --fix appendonly-6.2.5.aof 
Start checking Old-Style AOF
[offset 0] Checking RDB file appendonly-6.2.5.aof
[offset 26] AUX FIELD redis-ver = '6.2.5'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1705342822'
[offset 67] AUX FIELD used-mem = '798672'
[offset 83] AUX FIELD aof-preamble = '1'
[offset 85] Selecting DB ID 0
[offset 108] Checksum OK
[offset 108] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
AOF analyzed: filename=appendonly-6.2.5.aof, size=108, ok_up_to=108, ok_up_to_line=1, diff=0
AOF appendonly-6.2.5.aof is valid
```

6.2.5
```
./src/redis-check-aof --fix appendonly-6.2.5.aof 
The AOF appears to start with an RDB preamble.
Checking the RDB preamble to start:
[offset 0] Checking RDB file --fix
[offset 26] AUX FIELD redis-ver = '6.2.5'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1705342822'
[offset 67] AUX FIELD used-mem = '798672'
[offset 83] AUX FIELD aof-preamble = '1'
[offset 85] Selecting DB ID 0
[offset 108] Checksum OK
[offset 108] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
AOF analyzed: size=108, ok_up_to=108, ok_up_to_line=1, diff=0
AOF is valid
```
@enjoy-binbin Thanks for the quick answer.
Unfortunately I can not share the aof file as it contains confidential information. I'll try and create another sample that I can share. 
Is it possible to locate just the manifest part of this file? Seems like it's at the top of the file.


ok, i think i know the reason, the data in the aof file contains some data, which is identified as manifest. i am looking at it.

Patch:
diff --git a/src/aof.c b/src/aof.c
index cb9d899bbce..9a9bcf34ea1 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -117,7 +117,9 @@ aofInfo *aofInfoDup(aofInfo *orig) {
     return ai;
 }
 
-/* Format aofInfo as a string and it will be a line in the manifest. */
+/* Format aofInfo as a string and it will be a line in the manifest.
+ *
+ * When update this format, make sure to update redis-check-aof as well. */
 sds aofInfoFormat(sds buf, aofInfo *ai) {
     sds filename_repr = NULL;
 
diff --git a/src/redis-check-aof.c b/src/redis-check-aof.c
index 9c2eb5eef70..0cc9c7b3fa8 100644
--- a/src/redis-check-aof.c
+++ b/src/redis-check-aof.c
@@ -234,6 +234,7 @@ int checkSingleAof(char *aof_filename, char *aof_filepath, int last_file, int fi
     struct redis_stat sb;
     if (redis_fstat(fileno(fp),&sb) == -1) {
         printf("Cannot stat file: %s, aborting...\n", aof_filename);
+        fclose(fp);
         exit(1);
     }
 
@@ -345,6 +346,7 @@ int fileIsRDB(char *filepath) {
     struct redis_stat sb;
     if (redis_fstat(fileno(fp), &sb) == -1) {
         printf("Cannot stat file: %s\n", filepath);
+        fclose(fp);
         exit(1);
     }
 
@@ -381,6 +383,7 @@ int fileIsManifest(char *filepath) {
     struct redis_stat sb;
     if (redis_fstat(fileno(fp), &sb) == -1) {
         printf("Cannot stat file: %s\n", filepath);
+        fclose(fp);
         exit(1);
     }
 
@@ -397,15 +400,20 @@ int fileIsManifest(char *filepath) {
                 break;
             } else {
                 printf("Cannot read file: %s\n", filepath);
+                fclose(fp);
                 exit(1);
             }
         }
 
-        /* Skip comments lines */
+        /* We will skip comments lines.
+         * At present, the manifest format is fixed, see aofInfoFormat.
+         * We will break directly as long as it encounters other items. */
         if (buf[0] == '#') {
             continue;
         } else if (!memcmp(buf, "file", strlen("file"))) {
             is_manifest = 1;
+        } else {
+            break;
         }
     }
 


Test patch:
diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl
index 0050ef5772f..137b2d86339 100644
--- a/tests/integration/aof.tcl
+++ b/tests/integration/aof.tcl
@@ -481,6 +481,18 @@ tags {"aof external:skip"} {
         assert_match "*Start checking Old-Style AOF*is valid*" $result
     }
 
+    test {Test redis-check-aof for old style resp AOF - has data in the same format as manifest} {
+        create_aof $aof_dirpath $aof_file {
+            append_to_aof [formatCommand set file file]
+            append_to_aof [formatCommand set "file appendonly.aof.2.base.rdb seq 2 type b" "file appendonly.aof.2.base.rdb seq 2 type b"]
+        }
+
+        catch {
+            exec src/redis-check-aof $aof_file
+        } result
+        assert_match "*Start checking Old-Style AOF*is valid*" $result
+    }
+
     test {Test redis-check-aof for old style rdb-preamble AOF} {
         catch {
             exec src/redis-check-aof tests/assets/rdb-preamble.aof
@@ -529,6 +541,19 @@ tags {"aof external:skip"} {
         assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RDB format)*DB preamble is OK, proceeding with AOF tail*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result
     }
 
+    test {Test redis-check-aof for Multi Part AOF contains a format error} {
+        create_aof_manifest $aof_dirpath $aof_manifest_file {
+            append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
+            append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
+            append_to_manifest "!!!\n"
+        }
+
+        catch {
+            exec src/redis-check-aof $aof_manifest_file
+        } result
+        assert_match "*Invalid AOF manifest file format*" $result
+    }
+
     test {Test redis-check-aof only truncates the last file for Multi Part AOF in fix mode} {
         create_aof $aof_dirpath $aof_base_file {
             append_to_aof [formatCommand set foo hello]