From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 331A21FF139 for ; Tue, 10 Feb 2026 16:06:42 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id D8A3D6661; Tue, 10 Feb 2026 16:07:26 +0100 (CET) From: Robert Obkircher To: pbs-devel@lists.proxmox.com Subject: [PATCH v6 proxmox-backup 12/18] client: don't poll terminated source in FixedChunkStream Date: Tue, 10 Feb 2026 16:06:28 +0100 Message-ID: <20260210150642.469670-13-r.obkircher@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260210150642.469670-1-r.obkircher@proxmox.com> References: <20260210150642.469670-1-r.obkircher@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1770735959436 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.057 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: J35CUMJVBHD6KSCGPNHUWCKHBNB2AL6S X-Message-ID-Hash: J35CUMJVBHD6KSCGPNHUWCKHBNB2AL6S X-MailFrom: r.obkircher@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Fix incorrect chunking when the input file is a pts character device (stdin in an interactive terminal). Previously, the stream could produce multiple smaller chunks from interactive input, and a single Ctrl+D was not enough to terminate it. This was because a None from the source resulted in Some partial chunk, but the next time the input was polled again and it suddenly had more input. The documentation of Stream::poll_next clearly states that calling it on a terminated stream may panic, block forever, or cause other kinds of problems, so this should be fixed even it it appeared to work with normal files. Using the fuse method or adding a FusedStream trait bound might be nicer that seemed like a larger change compared to the boolean. Signed-off-by: Robert Obkircher --- pbs-client/src/chunk_stream.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pbs-client/src/chunk_stream.rs b/pbs-client/src/chunk_stream.rs index e3f0980c..94a45ac7 100644 --- a/pbs-client/src/chunk_stream.rs +++ b/pbs-client/src/chunk_stream.rs @@ -187,6 +187,7 @@ pub struct FixedChunkStream { input: S, chunk_size: usize, buffer: BytesMut, + done: bool, } impl FixedChunkStream { @@ -195,6 +196,7 @@ impl FixedChunkStream { input, chunk_size, buffer: BytesMut::new(), + done: false, } } } @@ -213,6 +215,9 @@ where cx: &mut Context, ) -> Poll>> { let this = self.get_mut(); + if this.done { + return Poll::Ready(None); + } loop { if this.buffer.len() >= this.chunk_size { return Poll::Ready(Some(Ok(this.buffer.split_to(this.chunk_size)))); @@ -223,6 +228,9 @@ where return Poll::Ready(Some(Err(err))); } None => { + // Must not call input.try_poll_next again! + this.done = true; + // last chunk can have any size if !this.buffer.is_empty() { return Poll::Ready(Some(Ok(this.buffer.split()))); -- 2.47.3