Fix detection of truncated zstd-compressed backups

Started by Chao Li17 days ago21 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253344
psql -h localhost -U postgres

Built from patchset v20 (message #20), August 23, 2026 at 05:00 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253344_20 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253344_20 && git checkout t253344_20

Patchset v20 (message #20) is on t253344_20

Jump to latest
#1Chao Li
li.evan.chao@gmail.com

Hi,

One of our users reported this issue to me on PG18. pg_verifybackup reported success for a zstd-compressed backup, but zstdfailed to decompress it. After debugging with the user’s data, I found that the backup was truncated, but pg_verifybackup does not verify that the final zstd frame completed. I also tested current master, where the problem still exists.

I created a simple repro. First, use the following commands to create a fake backup folder and truncate one byte from the compressed tar file. The final zstd command will fail:
```
workdir=$(mktemp -d /tmp/zstd-trunc.XXXXXX)
mkdir "$workdir/truncated"
dd if=/dev/zero of="$workdir/base.tar" bs=1024 count=2
zstd -q -f "$workdir/base.tar" -o "$workdir/base.tar.zst"
size=$(stat -f %z "$workdir/base.tar.zst")
dd if="$workdir/base.tar.zst" of="$workdir/truncated/base.tar.zst" bs=1 count=$((size - 1))

manifest_prefix=$'{"PostgreSQL-Backup-Manifest-Version": 1,\n "Files": [],\n "WAL-Ranges": [],\n'
printf '%s' "$manifest_prefix" > "$workdir/manifest-prefix"
manifest_checksum=$(shasum -a 256 "$workdir/manifest-prefix" | awk '{print $1}')
printf '%s"Manifest-Checksum": "%s"}\n' "$manifest_prefix" "$manifest_checksum" > $workdir/truncated/backup_manifest

zstd -t $workdir/truncated/base.tar.zst
```

However, pg_verifybackup doesn't report an error for the truncated backup:
```
% pg_verifybackup -n -F t -s /tmp/zstd-trunc.YHQ9P4/truncated
backup successfully verified
```

Looking at this code in astreamer_zstd_decompressor_content()
```
ret = ZSTD_decompressStream(mystreamer->dctx,
&mystreamer->zstd_outBuf, &inBuf);

if (ZSTD_isError(ret))
pg_fatal("could not decompress data: %s",
ZSTD_getErrorName(ret));
```

While decompressing the truncated tar file, ZSTD_decompressStream() returns 1. The doc for ZSTD_decompressStream() at [1]https://github.com/facebook/zstd/blob/v1.5.7/lib/zstd.h#L887-L905 says that a return > 0 means that there is still decoding or flushing to do before the current frame is complete. In this case, it may indicate an incomplete frame.

So, this patch records the return value in astreamer_zstd_frame and checks it in astreamer_zstd_decompressor_finalize(). See the attached patch for details.

I also added a loop to call ZSTD_decompressStream() again in astreamer_zstd_decompressor_finalize(), because the doc [1]https://github.com/facebook/zstd/blob/v1.5.7/lib/zstd.h#L887-L905 says that, when output.pos == output.size and the return value is greater than 0, the caller must call ZSTD_decompressStream() again to flush the remaining buffered output.

With the fix, now pg_verifybackup reports a failure:
```
% pg_verifybackup -n -F t -s /tmp/zstd-trunc.YHQ9P4/truncated
pg_verifybackup: error: could not decompress data: compressed stream is incomplete
```

BTW, I would also like to bump my previous patch [2]/messages/by-id/3B062561-C39C-4367-AB1E-F4C27BC6F736@gmail.com. After changing fe_utils/astreamer_zstd.c, pg_verifybackup is not rebuilt automatically, which is inconvenient. I felt the pain again while working on this patch.

[1]: https://github.com/facebook/zstd/blob/v1.5.7/lib/zstd.h#L887-L905
[2]: /messages/by-id/3B062561-C39C-4367-AB1E-F4C27BC6F736@gmail.com

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_1
v1-0001-Fix-detection-of-truncated-zstd-compressed-backup.patchapplication/octet-stream; name=v1-0001-Fix-detection-of-truncated-zstd-compressed-backup.patch; x-unix-mode=0644Download+38-1
#2Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Chao Li (#1)
Re: Fix detection of truncated zstd-compressed backups

Hello!

Isn't this a generic problem, also present in gzip/lz4 compressed backups?

#3Chao Li
li.evan.chao@gmail.com
In reply to: Zsolt Parragi (#2)
Re: Fix detection of truncated zstd-compressed backups

On Aug 8, 2026, at 03:42, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

Hello!

Isn't this a generic problem, also present in gzip/lz4 compressed backups?

Maybe, but I haven’t checked them yet. I will do that next week.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#4Chao Li
li.evan.chao@gmail.com
In reply to: Chao Li (#3)
Re: Fix detection of truncated zstd-compressed backups

On Aug 8, 2026, at 08:00, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 8, 2026, at 03:42, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

Hello!

Isn't this a generic problem, also present in gzip/lz4 compressed backups?

Maybe, but I haven’t checked them yet. I will do that next week.

I just verified gzip has the same problem using a similar script:
```
workdir=$(mktemp -d /tmp/gzip-trunc.XXXXXX)
mkdir "$workdir/truncated"
dd if=/dev/zero of="$workdir/base.tar" bs=1024 count=2
gzip -c "$workdir/base.tar" > "$workdir/base.tar.gz"
size=$(stat -f %z "$workdir/base.tar.gz")
dd if="$workdir/base.tar.gz" of="$workdir/truncated/base.tar.gz" bs=1 count=$((size - 1))

manifest_prefix=$'{"PostgreSQL-Backup-Manifest-Version": 1,\n "Files": [],\n "WAL-Ranges": [],\n'
printf '%s' "$manifest_prefix" > "$workdir/manifest-prefix"
manifest_checksum=$(shasum -a 256 "$workdir/manifest-prefix" | awk '{print $1}')
printf '%s"Manifest-Checksum": "%s"}\n' "$manifest_prefix" "$manifest_checksum" > "$workdir/truncated/backup_manifest"

gzip -t "$workdir/truncated/base.tar.gz"
pg_verifybackup -F t -s "$workdir/truncated"
```

gzip fails, while pg_verifybackup succeeds.

Looking at the code, astreamer_gzip_decompressor_content() stops processing when it has consumed the input buffer. However, the inflate() doc, see [2] and [3], says that only Z_STREAM_END indicates that decompression has completed. My debugging also shows that, when reading the truncated gzip file, inflate() returns Z_OK, while it returns Z_STREAM_END for a valid gzip file. Therefore, this patch records whether inflate() has returned Z_STREAM_END and checks that state in astreamer_gzip_decompressor_finalize().

Unlike zstd, we don't need to call inflate() again in astreamer_gzip_decompressor_finalize(). Once all input has been consumed without Z_STREAM_END, calling inflate() with no input cannot complete the stream.

See attached 0002 for the fix of gzip streamer. I will check the lz4 streamer next.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_4
v2-0001-Fix-detection-of-truncated-zstd-compressed-backup.patchapplication/octet-stream; name=v2-0001-Fix-detection-of-truncated-zstd-compressed-backup.patch; x-unix-mode=0644Download+38-1
v2-0002-Fix-detection-of-truncated-gzip-compressed-backup.patchapplication/octet-stream; name=v2-0002-Fix-detection-of-truncated-gzip-compressed-backup.patch; x-unix-mode=0644Download+6-1
#5Chao Li
li.evan.chao@gmail.com
In reply to: Chao Li (#4)
Re: Fix detection of truncated zstd-compressed backups

On Aug 10, 2026, at 14:45, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 8, 2026, at 08:00, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 8, 2026, at 03:42, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

Hello!

Isn't this a generic problem, also present in gzip/lz4 compressed backups?

Maybe, but I haven’t checked them yet. I will do that next week.

I just verified gzip has the same problem using a similar script:
```
workdir=$(mktemp -d /tmp/gzip-trunc.XXXXXX)
mkdir "$workdir/truncated"
dd if=/dev/zero of="$workdir/base.tar" bs=1024 count=2
gzip -c "$workdir/base.tar" > "$workdir/base.tar.gz"
size=$(stat -f %z "$workdir/base.tar.gz")
dd if="$workdir/base.tar.gz" of="$workdir/truncated/base.tar.gz" bs=1 count=$((size - 1))

manifest_prefix=$'{"PostgreSQL-Backup-Manifest-Version": 1,\n "Files": [],\n "WAL-Ranges": [],\n'
printf '%s' "$manifest_prefix" > "$workdir/manifest-prefix"
manifest_checksum=$(shasum -a 256 "$workdir/manifest-prefix" | awk '{print $1}')
printf '%s"Manifest-Checksum": "%s"}\n' "$manifest_prefix" "$manifest_checksum" > "$workdir/truncated/backup_manifest"

gzip -t "$workdir/truncated/base.tar.gz"
pg_verifybackup -F t -s "$workdir/truncated"
```

gzip fails, while pg_verifybackup succeeds.

Looking at the code, astreamer_gzip_decompressor_content() stops processing when it has consumed the input buffer. However, the inflate() doc, see [2] and [3], says that only Z_STREAM_END indicates that decompression has completed. My debugging also shows that, when reading the truncated gzip file, inflate() returns Z_OK, while it returns Z_STREAM_END for a valid gzip file. Therefore, this patch records whether inflate() has returned Z_STREAM_END and checks that state in astreamer_gzip_decompressor_finalize().

Unlike zstd, we don't need to call inflate() again in astreamer_gzip_decompressor_finalize(). Once all input has been consumed without Z_STREAM_END, calling inflate() with no input cannot complete the stream.

See attached 0002 for the fix of gzip streamer. I will check the lz4 streamer next.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

<v2-0001-Fix-detection-of-truncated-zstd-compressed-backup.patch><v2-0002-Fix-detection-of-truncated-gzip-compressed-backup.patch>

Sorry, forgot to add the reference links:

[2]: https://zlib.net/zlib_how.html
[3]: https://zlib.net/manual.html

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#6Chao Li
li.evan.chao@gmail.com
In reply to: Chao Li (#4)
Re: Fix detection of truncated zstd-compressed backups

On Aug 10, 2026, at 14:45, Chao Li <li.evan.chao@gmail.com> wrote:

See attached 0002 for the fix of gzip streamer. I will check the lz4 streamer next.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

<v2-0001-Fix-detection-of-truncated-zstd-compressed-backup.patch><v2-0002-Fix-detection-of-truncated-gzip-compressed-backup.patch>

Confirmed that lz4 also has the same problem. See the similar repro script:
```
workdir=$(mktemp -d /tmp/lz4-trunc.XXXXXX)
mkdir "$workdir/truncated"
dd if=/dev/zero of="$workdir/base.tar" bs=1024 count=2
lz4 -q -f "$workdir/base.tar" "$workdir/base.tar.lz4"
size=$(stat -f %z "$workdir/base.tar.lz4")
dd if="$workdir/base.tar.lz4" of="$workdir/truncated/base.tar.lz4" bs=1 count=$((size - 1))

manifest_prefix=$'{"PostgreSQL-Backup-Manifest-Version": 1,\n "Files": [],\n "WAL-Ranges": [],\n'
printf '%s' "$manifest_prefix" > "$workdir/manifest-prefix"
manifest_checksum=$(shasum -a 256 "$workdir/manifest-prefix" | awk '{print $1}')
printf '%s"Manifest-Checksum": "%s"}\n' "$manifest_prefix" "$manifest_checksum" > "$workdir/truncated/backup_manifest"

lz4 -t "$workdir/truncated/base.tar.lz4"
pg_verifybackup -F t -s "$workdir/truncated"
```

lz4 fails to decompress the truncated tar file, but pg_verifybackup succeeds.

The doc for LZ4F_decompress() [4]https://github.com/lz4/lz4/blob/dev/lib/lz4frame.h#L470-L500 says that a return value >0 is a hint about how many source bytes are needed next, 0 means that the frame is complete, and an error return is identified with LZ4F_isError().

So, as in 0001, we can record the return value of LZ4F_decompress() in astreamer_lz4_frame and check it in astreamer_lz4_decompressor_finalize(). Unlike zstd, we don't need to call LZ4F_decompress() again because it has no documented case where a positive return value with a full output buffer requires an empty-input call to flush internally buffered output.

[4]: https://github.com/lz4/lz4/blob/dev/lib/lz4frame.h#L470-L500

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_6
v3-0001-Fix-detection-of-truncated-zstd-compressed-backup.patchapplication/octet-stream; name=v3-0001-Fix-detection-of-truncated-zstd-compressed-backup.patch; x-unix-mode=0644Download+38-1
v3-0002-Fix-detection-of-truncated-gzip-compressed-backup.patchapplication/octet-stream; name=v3-0002-Fix-detection-of-truncated-gzip-compressed-backup.patch; x-unix-mode=0644Download+6-1
v3-0003-Fix-detection-of-truncated-LZ4-compressed-backups.patchapplication/octet-stream; name=v3-0003-Fix-detection-of-truncated-LZ4-compressed-backups.patch; x-unix-mode=0644Download+9-1
#7Daniel Gustafsson
daniel@yesql.se
In reply to: Chao Li (#6)
Re: Fix detection of truncated zstd-compressed backups

On 10 Aug 2026, at 10:11, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 10, 2026, at 14:45, Chao Li <li.evan.chao@gmail.com> wrote:

See attached 0002 for the fix of gzip streamer. I will check the lz4 streamer next.

Confirmed that lz4 also has the same problem. See the similar repro script:

Thanks for this patchset, I think this is something we should fix. I took the
liberty to squash the patchset into a single patch to start preparing it for
the final shape, as well as adding a testcase to verify this.

+	/* Reject empty input, which does not contain a complete zstd frame. */
+	streamer->decompression_ret = 1;

I am not a huge fan of this, we claim that we save the return value but then we
assign a value which hasn't yet been returned as a sentinel. Given that the
return is a size_t we also can't really invent a sentinel. Since we don't
actually use the returned value for anything but "done or not-done", so I
propose something like the attached which interprets the value and stores a
named state. What are your thoughts on this?

--
Daniel Gustafsson

Attachments:

t253344_7
v4-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v4-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+93-2
#8Chao Li
li.evan.chao@gmail.com
In reply to: Daniel Gustafsson (#7)
Re: Fix detection of truncated zstd-compressed backups

On Aug 11, 2026, at 00:28, Daniel Gustafsson <daniel@yesql.se> wrote:

On 10 Aug 2026, at 10:11, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 10, 2026, at 14:45, Chao Li <li.evan.chao@gmail.com> wrote:

See attached 0002 for the fix of gzip streamer. I will check the lz4 streamer next.

Confirmed that lz4 also has the same problem. See the similar repro script:

Thanks for this patchset, I think this is something we should fix. I took the
liberty to squash the patchset into a single patch to start preparing it for
the final shape, as well as adding a testcase to verify this.

Thank for taking care of this patch.

+ /* Reject empty input, which does not contain a complete zstd frame. */
+ streamer->decompression_ret = 1;

I am not a huge fan of this, we claim that we save the return value but then we
assign a value which hasn't yet been returned as a sentinel. Given that the
return is a size_t we also can't really invent a sentinel. Since we don't
actually use the returned value for anything but "done or not-done", so I
propose something like the attached which interprets the value and stores a
named state. What are your thoughts on this?

I agree with the direction. The new proposal can distinguish an empty stream from a truncated stream.

<v4-0001-Fix-detection-of-truncated-compressed-backups.patch>

A few comments with v4:

1 - ztsd
```
+	if (mystreamer->state != STREAM_FINISHED)
+		pg_fatal("could not decompress data: compressed stream is incomplete");
+	else if (unlikely(mystreamer->state == STREAM_NEW))
+		pg_fatal("could not decompress data: compressed stream is empty");
```

This is a small logic error. As STREAM_NEW!=STREAM_FINISHED already, the “else if” is unreachable. We should check if (unlikely(mystreamer->state == STREAM_NEW) first.

2 - ztsd
```
+		/* The stream is only done when ZSTD_decompressStream returns 0 */
+		if (ret)
+			mystreamer->state = STREAM_HAS_DATA;
+		else
+			mystreamer->state = STREAM_FINISHED;
```

When ret == 0, that only means the current frame is complete, so the comment “the stream is only done” sounds too strong, I would change to “The frame is only done when …”.

3 - ztsd and lz4
```
+typedef enum
+{
+	STREAM_NEW,
+	STREAM_HAS_DATA,
+	STREAM_FINISHED,
+}			pg_stream_state;
```

Similar to comment 2, return == 0 means the current is complete and >0 means the current frame is incomplete, thus I would rename STREAM_HAS_DATA to FRAME_HAS_DATA, and STREAM_FINISHED to FRAME_FINISHED.

I addressed all the 3 comments in v5.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_8
v5-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v5-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+94-2
#9Osama Abdul Qader
osamaabdulqader.cs@gmail.com
In reply to: Chao Li (#8)
Re: Fix detection of truncated zstd-compressed backups

Hi Chao,

I noticed your commit f80cb3ae0737 ("Fix detection of truncated
zstd-compressed backups"), which addresses the truncated ZSTD backup issue
I had reported.

I added a regression test to src/bin/pg_verifybackup/t/008_untar.pl. The
test creates a server-side ZSTD backup, verifies the intact backup,
truncates base.tar.zst by one byte, and then verifies that pg_verifybackup
rejects the truncated backup.

The test passes with your fix:
make check -C src/bin/pg_verifybackup TESTS=t/008_untar

Result: PASS.

I noticed that your commit changes astreamer_zstd.c but does not add a
regression test to 008_untar.pl. Would this test be useful to include with
the fix?
Thanks,
Osama Abdul Qader

On Tue, Aug 11, 2026 at 2:55 AM Chao Li <li.evan.chao@gmail.com> wrote:

Show quoted text

On Aug 11, 2026, at 00:28, Daniel Gustafsson <daniel@yesql.se> wrote:

On 10 Aug 2026, at 10:11, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 10, 2026, at 14:45, Chao Li <li.evan.chao@gmail.com> wrote:

See attached 0002 for the fix of gzip streamer. I will check the lz4

streamer next.

Confirmed that lz4 also has the same problem. See the similar repro

script:

Thanks for this patchset, I think this is something we should fix. I

took the

liberty to squash the patchset into a single patch to start preparing it

for

the final shape, as well as adding a testcase to verify this.

Thank for taking care of this patch.

+ /* Reject empty input, which does not contain a complete zstd frame. */
+ streamer->decompression_ret = 1;

I am not a huge fan of this, we claim that we save the return value but

then we

assign a value which hasn't yet been returned as a sentinel. Given that

the

return is a size_t we also can't really invent a sentinel. Since we

don't

actually use the returned value for anything but "done or not-done", so I
propose something like the attached which interprets the value and

stores a

named state. What are your thoughts on this?

I agree with the direction. The new proposal can distinguish an empty
stream from a truncated stream.

<v4-0001-Fix-detection-of-truncated-compressed-backups.patch>

A few comments with v4:

1 - ztsd
```
+       if (mystreamer->state != STREAM_FINISHED)
+               pg_fatal("could not decompress data: compressed stream is
incomplete");
+       else if (unlikely(mystreamer->state == STREAM_NEW))
+               pg_fatal("could not decompress data: compressed stream is
empty");
```

This is a small logic error. As STREAM_NEW!=STREAM_FINISHED already, the
“else if” is unreachable. We should check if (unlikely(mystreamer->state ==
STREAM_NEW) first.

2 - ztsd
```
+               /* The stream is only done when ZSTD_decompressStream
returns 0 */
+               if (ret)
+                       mystreamer->state = STREAM_HAS_DATA;
+               else
+                       mystreamer->state = STREAM_FINISHED;
```

When ret == 0, that only means the current frame is complete, so the
comment “the stream is only done” sounds too strong, I would change to “The
frame is only done when …”.

3 - ztsd and lz4
```
+typedef enum
+{
+       STREAM_NEW,
+       STREAM_HAS_DATA,
+       STREAM_FINISHED,
+}                      pg_stream_state;
```

Similar to comment 2, return == 0 means the current is complete and >0
means the current frame is incomplete, thus I would rename STREAM_HAS_DATA
to FRAME_HAS_DATA, and STREAM_FINISHED to FRAME_FINISHED.

I addressed all the 3 comments in v5.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_9
zstd-truncated-backup-regression-test.patchapplication/x-patch; name=zstd-truncated-backup-regression-test.patchDownload+12-1
#10Chao Li
li.evan.chao@gmail.com
In reply to: Osama Abdul Qader (#9)
Re: Fix detection of truncated zstd-compressed backups

On Aug 11, 2026, at 15:40, Osama Abdul Qader <osamaabdulqader.cs@gmail.com> wrote:

Hi Chao,

I noticed your commit f80cb3ae0737 ("Fix detection of truncated zstd-compressed backups"), which addresses the truncated ZSTD backup issue I had reported.

I added a regression test to src/bin/pg_verifybackup/t/008_untar.pl. The test creates a server-side ZSTD backup, verifies the intact backup, truncates base.tar.zst by one byte, and then verifies that pg_verifybackup rejects the truncated backup.

The test passes with your fix:
make check -C src/bin/pg_verifybackup TESTS=t/008_untar

Result: PASS.

I noticed that your commit changes astreamer_zstd.c but does not add a regression test to 008_untar.pl. Would this test be useful to include with the fix?
Thanks,
Osama Abdul Qader

Hi Osama,

Thanks for your review and for adding the test. I think the test Daniel added to 010_client_untar.pl covers all three astreamer types.

Your test covers server-side compression, but from the astreamer’s perspective, I don't think there is much difference between client side and server side compression. Therefore, I am not sure that an additional server side test is needed. Let’s see what Daniel thinks.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#11Osama Abdul Qader
osamaabdulqader.cs@gmail.com
In reply to: Chao Li (#10)
Re: Fix detection of truncated zstd-compressed backups

Hi Chao,

Thanks for the feedback, That's make sense to me, I'll wait for Mr.
Daniel's thought on whether the existing 010_client_untar.pl coverage is
sufficient.

Thanks,
Osama Abdul Qader

On Tue, Aug 11, 2026 at 1:28 PM Chao Li <li.evan.chao@gmail.com> wrote:

Show quoted text

On Aug 11, 2026, at 15:40, Osama Abdul Qader <

osamaabdulqader.cs@gmail.com> wrote:

Hi Chao,

I noticed your commit f80cb3ae0737 ("Fix detection of truncated

zstd-compressed backups"), which addresses the truncated ZSTD backup issue
I had reported.

I added a regression test to src/bin/pg_verifybackup/t/008_untar.pl.

The test creates a server-side ZSTD backup, verifies the intact backup,
truncates base.tar.zst by one byte, and then verifies that pg_verifybackup
rejects the truncated backup.

The test passes with your fix:
make check -C src/bin/pg_verifybackup TESTS=t/008_untar

Result: PASS.

I noticed that your commit changes astreamer_zstd.c but does not add a

regression test to 008_untar.pl. Would this test be useful to include
with the fix?

Thanks,
Osama Abdul Qader

Hi Osama,

Thanks for your review and for adding the test. I think the test Daniel
added to 010_client_untar.pl covers all three astreamer types.

Your test covers server-side compression, but from the astreamer’s
perspective, I don't think there is much difference between client side and
server side compression. Therefore, I am not sure that an additional server
side test is needed. Let’s see what Daniel thinks.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#12Chao Li
li.evan.chao@gmail.com
In reply to: Osama Abdul Qader (#11)
Re: Fix detection of truncated zstd-compressed backups

On Aug 11, 2026, at 16:32, Osama Abdul Qader <osamaabdulqader.cs@gmail.com> wrote:

Hi Chao,

Thanks for the feedback, That's make sense to me, I'll wait for Mr. Daniel's thought on whether the existing 010_client_untar.pl coverage is sufficient.

Thanks,
Osama Abdul Qader

On Tue, Aug 11, 2026 at 1:28 PM Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 11, 2026, at 15:40, Osama Abdul Qader <osamaabdulqader.cs@gmail.com> wrote:

Hi Chao,

I noticed your commit f80cb3ae0737 ("Fix detection of truncated zstd-compressed backups"), which addresses the truncated ZSTD backup issue I had reported.

I added a regression test to src/bin/pg_verifybackup/t/008_untar.pl. The test creates a server-side ZSTD backup, verifies the intact backup, truncates base.tar.zst by one byte, and then verifies that pg_verifybackup rejects the truncated backup.

The test passes with your fix:
make check -C src/bin/pg_verifybackup TESTS=t/008_untar

Result: PASS.

I noticed that your commit changes astreamer_zstd.c but does not add a regression test to 008_untar.pl. Would this test be useful to include with the fix?
Thanks,
Osama Abdul Qader

Hi Osama,

Thanks for your review and for adding the test. I think the test Daniel added to 010_client_untar.pl covers all three astreamer types.

Your test covers server-side compression, but from the astreamer’s perspective, I don't think there is much difference between client side and server side compression. Therefore, I am not sure that an additional server side test is needed. Let’s see what Daniel thinks.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Hi, Osama,

FYI, when you want to attach a patch file, please include “nocfbot” in the file name. Otherwise, CommitFest will pick up your patch file and incorrectly ask for a rebase. See [1]https://wiki.postgresql.org/wiki/Cfbot#When_are_patches_tested?.

Resending v5 to recover the CF entry.

[1]: https://wiki.postgresql.org/wiki/Cfbot#When_are_patches_tested?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_12
v5-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v5-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+94-2
#13Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Chao Li (#12)
Re: Fix detection of truncated zstd-compressed backups

I have two minor comment for v5, otherwise it looks good to me:

+typedef enum
+{
+	STREAM_NEW,
+	STREAM_HAS_DATA,
+	STREAM_FINISHED,
+}			pg_stream_state;
+

This is duplicated in two files, wouldn't be astreamer.h a better place for it?

+	if (!mystreamer->stream_finished)
+		pg_fatal("could not decompress data: compressed stream is incomplete");
+

The other two checks distinguish empty from incomplete, is this difference intended?

#14Chao Li
li.evan.chao@gmail.com
In reply to: Zsolt Parragi (#13)
Re: Fix detection of truncated zstd-compressed backups

On Aug 13, 2026, at 05:56, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

I have two minor comment for v5, otherwise it looks good to me:

Hi Zsolt,

Thanks a lot for reviewing.

+typedef enum
+{
+ STREAM_NEW,
+ STREAM_HAS_DATA,
+ STREAM_FINISHED,
+} pg_stream_state;
+

This is duplicated in two files, wouldn't be astreamer.h a better place for it?

I think this makes sense. Plus your second comment, this enum can be shared by all 3 astreamers. I moved it to astreamer.h and renamed it to astreamer_decompression_state.

+ if (!mystreamer->stream_finished)
+ pg_fatal("could not decompress data: compressed stream is incomplete");
+

The other two checks distinguish empty from incomplete, is this
difference intended?

I do not think that is intentional. My initial implementation did not check for an empty stream, and gzip uses a different pattern from zstd and LZ4, which is probably why Daniel initially added the empty-stream check only to zstd and lz4.

I have now added the same check to gzip so that all three astreamers are consistent.

PFA v6: addressed Zsolt’s comments.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_14
v6-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v6-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+93-2
#15Japin Li
japinli@hotmail.com
In reply to: Chao Li (#14)
Re: Fix detection of truncated zstd-compressed backups

References: <5962B878-C43D-4EBC-9E95-1F945CE5E586@gmail.com>
<CAN4CZFNuV2NKXbPTGkN8chga01sVGpUcNinM-HpdyFtH0eJR0w@mail.gmail.com>
<06FBF640-1A1D-48B0-BBEA-F4EBB4017309@gmail.com>
<91EEEF17-A331-4CEA-ABEA-E63C4FBDB173@gmail.com>
<2E9971CA-062B-4EC0-8858-9979C73A3888@gmail.com>
<CF427D27-3AF6-4C0E-ABAE-9FF7DED20FCD@yesql.se>
<1381EA72-8932-4734-9F7B-8315208EDB96@gmail.com>
<CAC+8b5imyLMnif=JBYnDK9H6xLhAgSP_Yw2WtU-vTOb34iqW5w@mail.gmail.com>
<6D85CC75-CB35-45E3-ADBB-8287DF6BB429@gmail.com>
<CAC+8b5itHRfCcgSt4WKCxkcOZ5+DvmQR2mavj1gU6YhBBpvZrQ@mail.gmail.com>
<6A9A9FC1-B59E-4454-8B95-570054038EFE@gmail.com>
<CAN4CZFM_RaAxiRcf0JmhmNqUac+9-3KYGVu+Uvc43VxH7bibaw@mail.gmail.com>
<3664E105-4512-4BD2-99A7-0F5C25EBB173@gmail.com>
User-Agent: mu4e 1.14.1; emacs 30.2
Hi Chao,

Thanks for updating the patch.

Date: Thu, 13 Aug 2026 11:32:59 +0800

On Thu, 13 Aug 2026 at 09:47, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 13, 2026, at 05:56, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

I have two minor comment for v5, otherwise it looks good to me:

Hi Zsolt,

Thanks a lot for reviewing.

+typedef enum
+{
+ STREAM_NEW,
+ STREAM_HAS_DATA,
+ STREAM_FINISHED,
+} pg_stream_state;
+

This is duplicated in two files, wouldn't be astreamer.h a better place for it?

I think this makes sense. Plus your second comment, this enum can be shared by all 3 astreamers. I moved it to astreamer.h and renamed it to astreamer_decompression_state.

The astreamer_decompression_state should be added to pgindent's typedefs.list.

+ if (!mystreamer->stream_finished)
+ pg_fatal("could not decompress data: compressed stream is incomplete");
+

The other two checks distinguish empty from incomplete, is this
difference intended?

I do not think that is intentional. My initial implementation did not
check for an empty stream, and gzip uses a different pattern from zstd
and LZ4, which is probably why Daniel initially added the empty-stream
check only to zstd and lz4.

I have now added the same check to gzip so that all three astreamers are consistent.

PFA v6: addressed Zsolt’s comments.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

#16Chao Li
li.evan.chao@gmail.com
In reply to: Japin Li (#15)
Re: Fix detection of truncated zstd-compressed backups

On Aug 13, 2026, at 11:39, Japin Li <japinli@hotmail.com> wrote:

Hi Chao,

Thanks for updating the patch.

Hi Jipan, thanks a lot for reviewing.

Date: Thu, 13 Aug 2026 11:32:59 +0800

On Thu, 13 Aug 2026 at 09:47, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 13, 2026, at 05:56, Zsolt Parragi <zsolt.parragi@percona.com> wrote:

I have two minor comment for v5, otherwise it looks good to me:

Hi Zsolt,

Thanks a lot for reviewing.

+typedef enum
+{
+ STREAM_NEW,
+ STREAM_HAS_DATA,
+ STREAM_FINISHED,
+} pg_stream_state;
+

This is duplicated in two files, wouldn't be astreamer.h a better place for it?

I think this makes sense. Plus your second comment, this enum can be shared by all 3 astreamers. I moved it to astreamer.h and renamed it to astreamer_decompression_state.

The astreamer_decompression_state should be added to pgindent's typedefs.list.

Yes, I missed that part.

PFA v7: addressed Jipan’s comment.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_16
v7-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v7-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+94-2
#17Osama Abdul Qader
osamaabdulqader.cs@gmail.com
In reply to: Chao Li (#16)
Re: Fix detection of truncated zstd-compressed backups

Hi Chao,

Regarding the nocfbot, I get you, and sure in my future patches, I'll make
sure to use nocfbot in my patches, Thanks for letting me know about that
and I regret for the inconvenience that occurs.

With best regards
Osama Abdul Qader.

On Thu, 13 Aug, 2026, 10:42 am Chao Li, <li.evan.chao@gmail.com> wrote:

Show quoted text

On Aug 13, 2026, at 11:39, Japin Li <japinli@hotmail.com> wrote:

Hi Chao,

Thanks for updating the patch.

Hi Jipan, thanks a lot for reviewing.

Date: Thu, 13 Aug 2026 11:32:59 +0800

On Thu, 13 Aug 2026 at 09:47, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 13, 2026, at 05:56, Zsolt Parragi <zsolt.parragi@percona.com>

wrote:

I have two minor comment for v5, otherwise it looks good to me:

Hi Zsolt,

Thanks a lot for reviewing.

+typedef enum
+{
+ STREAM_NEW,
+ STREAM_HAS_DATA,
+ STREAM_FINISHED,
+} pg_stream_state;
+

This is duplicated in two files, wouldn't be astreamer.h a better

place for it?

I think this makes sense. Plus your second comment, this enum can be

shared by all 3 astreamers. I moved it to astreamer.h and renamed it to
astreamer_decompression_state.

The astreamer_decompression_state should be added to pgindent's

typedefs.list.

Yes, I missed that part.

PFA v7: addressed Jipan’s comment.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#18Daniel Gustafsson
daniel@yesql.se
In reply to: Chao Li (#16)
Re: Fix detection of truncated zstd-compressed backups

On 13 Aug 2026, at 07:11, Chao Li <li.evan.chao@gmail.com> wrote:

PFA v7: addressed Jipan’s comment.

Sorry for being slow on this, things are quite busy but I hope to have a review
soon. While poking at this I realized that our compression code in pg_dump
likely has the same issue. I hacked up a quick PoC diff (attached) but it's
untested (can one actually test the data-in-zstd-internal-buffers case at all?)
and mainly a sketch. If you want to pick it up and rework into this patchset
to tackle it treewide then that would be fantastic.

--
Daniel Gustafsson

Attachments:

t253344_18
pg_dump.diff.txttext/plain; name=pg_dump.diff.txt; x-unix-mode=0644Download+21-0
#19Chao Li
li.evan.chao@gmail.com
In reply to: Daniel Gustafsson (#18)
Re: Fix detection of truncated zstd-compressed backups

On Aug 14, 2026, at 04:57, Daniel Gustafsson <daniel@yesql.se> wrote:

On 13 Aug 2026, at 07:11, Chao Li <li.evan.chao@gmail.com> wrote:

PFA v7: addressed Jipan’s comment.

Sorry for being slow on this, things are quite busy but I hope to have a review
soon. While poking at this I realized that our compression code in pg_dump
likely has the same issue. I hacked up a quick PoC diff (attached) but it's
untested (can one actually test the data-in-zstd-internal-buffers case at all?)
and mainly a sketch. If you want to pick it up and rework into this patchset
to tackle it treewide then that would be fantastic.

--
Daniel Gustafsson

<pg_dump.diff.txt>

I can work on this today.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#20Chao Li
li.evan.chao@gmail.com
In reply to: Chao Li (#19)
Re: Fix detection of truncated zstd-compressed backups

On Aug 14, 2026, at 06:59, Chao Li <li.evan.chao@gmail.com> wrote:

On Aug 14, 2026, at 04:57, Daniel Gustafsson <daniel@yesql.se> wrote:

On 13 Aug 2026, at 07:11, Chao Li <li.evan.chao@gmail.com> wrote:

PFA v7: addressed Jipan’s comment.

Sorry for being slow on this, things are quite busy but I hope to have a review
soon. While poking at this I realized that our compression code in pg_dump
likely has the same issue. I hacked up a quick PoC diff (attached) but it's
untested (can one actually test the data-in-zstd-internal-buffers case at all?)
and mainly a sketch. If you want to pick it up and rework into this patchset
to tackle it treewide then that would be fantastic.

--
Daniel Gustafsson

<pg_dump.diff.txt>

I can work on this today.

I just checked pg_dump/pg_restore. The problem exists only with zstd and lz4, gzip doesn't have the problem.

Daniel’s PoC covers the custom-archive-format path, but not the directory-format path.

For the directory-format path, we can reproduce the problem by simply truncating one byte from a compressed data file. For the custom-archive path, reproducing the problem is less straightforward because the compressed data is stored inside length-prefixed archive blocks. Simply truncating the file can make archive parsing fail before the decompressor sees the truncated frame. I created a repro script, see the attached shell script.

Before the fix, the output contains:
```
custom zstd: exit status 0
directory zstd: exit status 0
custom lz4: exit status 0
directory lz4: exit status 0
pg_restore: error: could not uncompress data: (null)
custom gzip: exit status 1
pg_restore: error: could not close data file "/tmp/pgdump-trunc.1rxbf0/gzip-dir-bad/3931.dat": Undefined error: 0
directory gzip: exit status 1
```

This shows that gzip reports failure, while zstd and lz4 silently accept the truncated dump files.

After the fix, zstd and lz4 report failures as well:
```
pg_restore: error: could not decompress data: compressed stream is incomplete
custom zstd: exit status 1
pg_restore: error: could not decompress data: compressed stream is incomplete
directory zstd: exit status 1
pg_restore: error: could not decompress data: compressed stream is incomplete
custom lz4: exit status 1
pg_restore: error: could not read from input file: Input/output error
directory lz4: exit status 1
pg_restore: error: could not uncompress data: (null)
custom gzip: exit status 1
pg_restore: error: could not close data file "/tmp/pgdump-trunc.XjUiJ4/gzip-dir-bad/3931.dat": Undefined error: 0
directory gzip: exit status 1
```

While testing, I also found a small issue in LZ4Stream_read_internal(). Its error branches call pg_log_error() and then return -1, but callers immediately call pg_fatal() when the return value <0. This results in duplicate error messages. So, I removed those pg_log_error() calls.

See 0002 for the fix. I added tests only for zstd and lz4, since gzip is not changed.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

t253344_20
nocfbot_test_pgdump_comp.shapplication/octet-stream; name=nocfbot_test_pgdump_comp.sh; x-unix-mode=0755Download
v8-0001-Fix-detection-of-truncated-compressed-backups.patchapplication/octet-stream; name=v8-0001-Fix-detection-of-truncated-compressed-backups.patch; x-unix-mode=0644Download+94-2
v8-0002-Fix-detection-of-truncated-zstd-and-LZ4-dump-data.patchapplication/octet-stream; name=v8-0002-Fix-detection-of-truncated-zstd-and-LZ4-dump-data.patch; x-unix-mode=0644Download+187-9
#21Osama Abdul Qader
osamaabdulqader.cs@gmail.com
In reply to: Chao Li (#20)