ature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Boris Burkov [ Upstream commit 38e818718c5e04961eea0fa8feff3f100ce40408 ] >>From the memory-barriers.txt document regarding memory barrier ordering guarantees: (*) These guarantees do not apply to bitfields, because compilers often generate code to modify these using non-atomic read-modify-write sequences. Do not attempt to use bitfields to synchronize parallel algorithms. (*) Even in cases where bitfields are protected by locks, all fields in a given bitfield must be protected by one lock. If two fields in a given bitfield are protected by different locks, the compiler's non-atomic read-modify-write sequences can cause an update to one field to corrupt the value of an adjacent field. btrfs_space_info has a bitfield sharing an underlying word consisting of the fields full, chunk_alloc, and flush: struct btrfs_space_info { struct btrfs_fs_info * fs_info; /* 0 8 */ struct btrfs_space_info * parent; /* 8 8 */ ... int clamp; /* 172 4 */ unsigned int full:1; /* 176: 0 4 */ unsigned int chunk_alloc:1; /* 176: 1 4 */ unsigned int flush:1; /* 176: 2 4 */ ... Therefore, to be safe from parallel read-modify-writes losing a write to one of the bitfield members protected by a lock, all writes to all the bitfields must use the lock. They almost universally do, except for btrfs_clear_space_info_full() which iterates over the space_infos and writes out found->full = 0 without a lock. Imagine that we have one thread completing a transaction in which we finished deleting a block_group and are thus calling btrfs_clear_space_info_full() while simultaneously the data reclaim ticket infrastructure is running do_async_reclaim_data_space(): T1 T2 btrfs_commit_transaction btrfs_clear_space_info_full data_sinfo->full = 0 READ: full:0, chunk_alloc:0, flush:1 do_async_reclaim_data_space(data_sinfo) spin_lock(&space_info->lock); if(list_empty(tickets)) space_info->flush = 0; READ: full: 0, chunk_alloc:0, flush:1 MOD/WRITE: full: 0, chunk_alloc:0, flush:0 spin_unlock(&space_info->lock); return; MOD/WRITE: full:0, chunk_alloc:0, flush:1 and now data_sinfo->flush is 1 but the reclaim worker has exited. This breaks the invariant that flush is 0 iff there is no work queued or running. Once this invariant is violated, future allocations that go into __reserve_bytes() will add tickets to space_info->tickets but will see space_info->flush is set to 1 and not queue the work. After this, they will block forever on the resulting ticket, as it is now impossible to kick the worker again. I also confirmed by looking at the assembly of the affected kernel that it is doing RMW operations. For example, to set the flush (3rd) bit to 0, the assembly is: andb $0xfb,0x60(%rbx) and similarly for setting the full (1st) bit to 0: andb $0xfe,-0x20(%rax) So I think this is really a bug on practical systems. I have observed a number of systems in this exact state, but am currently unable to reproduce it. Rather than leaving this footgun lying around for the future, take advantage of the fact that there is room in the struct anyway, and that it is already quite large and simply change the three bitfield members to bools. This avoids writes to space_info->full having any effect on writes to space_info->flush, regardless of locking. Fixes: 957780eb2788 ("Btrfs: introduce ticketed enospc infrastructure") Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Reviewed-by: David Sterba Signed-off-by: David Sterba [ The context change is due to the commit cc0517fe779f ("btrfs: tweak extent/chunk allocation for space_info sub-space") in v6.16 which is irrelevant to the logic of this patch. ] Signed-off-by: Rahul Sharma Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/block-group.c | 6 +++--- fs/btrfs/space-info.c | 22 +++++++++++----------- fs/btrfs/space-info.h | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -4195,7 +4195,7 @@ int btrfs_chunk_alloc(struct btrfs_trans mutex_unlock(&fs_info->chunk_mutex); } else { /* Proceed with allocation */ - space_info->chunk_alloc = 1; + space_info->chunk_alloc = true; wait_for_alloc = false; spin_unlock(&space_info->lock); } @@ -4244,7 +4244,7 @@ int btrfs_chunk_alloc(struct btrfs_trans spin_lock(&space_info->lock); if (ret < 0) { if (ret == -ENOSPC) - space_info->full = 1; + space_info->full = true; else goto out; } else { @@ -4254,7 +4254,7 @@ int btrfs_chunk_alloc(struct btrfs_trans space_info->force_alloc = CHUNK_ALLOC_NO_FORCE; out: - space_info->chunk_alloc = 0; + space_info->chunk_alloc = false; spin_unlock(&space_info->lock); mutex_unlock(&fs_info->chunk_mutex); --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -183,7 +183,7 @@ void btrfs_clear_space_info_full(struct struct btrfs_space_info *found; list_for_each_entry(found, head, list) - found->full = 0; + found->full = false; } /* @@ -364,7 +364,7 @@ void btrfs_add_bg_to_space_info(struct b found->bytes_readonly += block_group->bytes_super; btrfs_space_info_update_bytes_zone_unusable(info, found, block_group->zone_unusable); if (block_group->length > 0) - found->full = 0; + found->full = false; btrfs_try_granting_tickets(info, found); spin_unlock(&found->lock); @@ -1140,7 +1140,7 @@ static void btrfs_async_reclaim_metadata spin_lock(&space_info->lock); to_reclaim = btrfs_calc_reclaim_metadata_size(fs_info, space_info); if (!to_reclaim) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1152,7 +1152,7 @@ static void btrfs_async_reclaim_metadata flush_space(fs_info, space_info, to_reclaim, flush_state, false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1195,7 +1195,7 @@ static void btrfs_async_reclaim_metadata flush_state = FLUSH_DELAYED_ITEMS_NR; commit_cycles--; } else { - space_info->flush = 0; + space_info->flush = false; } } else { flush_state = FLUSH_DELAYED_ITEMS_NR; @@ -1357,7 +1357,7 @@ static void btrfs_async_reclaim_data_spa spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1368,7 +1368,7 @@ static void btrfs_async_reclaim_data_spa flush_space(fs_info, space_info, U64_MAX, ALLOC_CHUNK_FORCE, false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1385,7 +1385,7 @@ static void btrfs_async_reclaim_data_spa data_flush_states[flush_state], false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1402,7 +1402,7 @@ static void btrfs_async_reclaim_data_spa if (maybe_fail_all_tickets(fs_info, space_info)) flush_state = 0; else - space_info->flush = 0; + space_info->flush = false; } else { flush_state = 0; } @@ -1418,7 +1418,7 @@ static void btrfs_async_reclaim_data_spa aborted_fs: maybe_fail_all_tickets(fs_info, space_info); - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); } @@ -1787,7 +1787,7 @@ static int __reserve_bytes(struct btrfs_ */ maybe_clamp_preempt(fs_info, space_info); - space_info->flush = 1; + space_info->flush = true; trace_btrfs_trigger_flush(fs_info, space_info->flags, orig_bytes, flush, --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -136,11 +136,11 @@ struct btrfs_space_info { flushing. The value is >> clamp, so turns out to be a 2^clamp divisor. */ - unsigned int full:1; /* indicates that we cannot allocate any more + bool full; /* indicates that we cannot allocate any more chunks for this space */ - unsigned int chunk_alloc:1; /* set if we are allocating a chunk */ + bool chunk_alloc; /* set if we are allocating a chunk */ - unsigned int flush:1; /* set if we are trying to make space */ + bool flush; /* set if we are trying to make space */ unsigned int force_alloc; /* set if we need to force a chunk alloc for this space */ From - Wed Jan 28 16:27:21 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id GJHoLOI4emnsTjwAYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:14 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:14 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8Nu-00000000V3Y-1g1Q for hi@josie.lol; Wed, 28 Jan 2026 16:27:14 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 131A830ACED6 for ; Wed, 28 Jan 2026 15:49:43 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4653A2DF155; Wed, 28 Jan 2026 15:49:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="CkzV7JKc" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 22B1A27FD4A; Wed, 28 Jan 2026 15:49:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615378; cv=none; b=k3vLTqtTDyshCL2iTO/5I8fhsgi25i0MxuqcF5KjG6DugnOgogZTyPNpr3REOoniEJTBjngU1+nB/hQwgL81bITR9t3erQ9eQUf1E37Jt/DE//djCCUs6UWXh/MkZ5UEa5FDlMZnD+muEN7t4OqheaBAsnl7nVWvBhxnxv53eoQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615378; c=relaxed/simple; bh=8VF/3oDm5oCyfIcKNvV1ojQk2hLqqnygG7FZDMQuR7g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Uj3yPB3/bOLbUQEJa1dHXXijYNjqKtr/fHb25LJT8Z5q5eaQQ92Km7DhO/Obvnuz5gEmMxlVYX5WV4wTrPO7ZEbczMURZzFSZNaa+Ud7xI7Lo33pXEwoTO2BAtvgEc4CbtmXbE41MurayTaZjruyYDL6pVh6NMt0SIN32DB4uHA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=CkzV7JKc; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id 68681C4CEF1; Wed, 28 Jan 2026 15:49:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615377; bh=8VF/3oDm5oCyfIcKNvV1ojQk2hLqqnygG7FZDMQuR7g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CkzV7JKcgloiFepX4BZLlR2DE1ZAirJ6P+Q5Hey2kOzDh/HmXIL7Wxy1mhhVb/fVs fk8bMibNQfWJpdv0TxMq4mRk9ElrIf7ykqKOXSh0DDVt+jZ2wJVaoUNAtpMzSXQyct eGhC0iSWHdGh6kPJoZjYgsUihuBrLQrtUd0vogzs= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, "kernel-dev@igalia.com, Heitor Alves de Siqueira" , Stefano Garzarella , Will Deacon , "Michael S. Tsirkin" , Heitor Alves de Siqueira Subject: [PATCH 6.12 162/169] vsock/virtio: Move length check to callers of virtio_vsock_skb_rx_put() Date: Wed, 28 Jan 2026 16:24:05 +0100 Message-ID: <20260128145339.850587479@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Will Deacon [Upstream commit 87dbae5e36613a6020f3d64a2eaeac0a1e0e6dc6] virtio_vsock_skb_rx_put() only calls skb_put() if the length in the packet header is not zero even though skb_put() handles this case gracefully. Remove the functionally redundant check from virtio_vsock_skb_rx_put() and, on the assumption that this is a worthwhile optimisation for handling credit messages, augment the existing length checks in virtio_transport_rx_work() to elide the call for zero-length payloads. Since the callers all have the length, extend virtio_vsock_skb_rx_put() to take it as an additional parameter rather than fish it back out of the packet header. Note that the vhost code already has similar logic in vhost_vsock_alloc_skb(). Reviewed-by: Stefano Garzarella Signed-off-by: Will Deacon Message-Id: <20250717090116.11987-4-will@kernel.org> Signed-off-by: Michael S. Tsirkin Signed-off-by: Heitor Alves de Siqueira Signed-off-by: Greg Kroah-Hartman --- drivers/vhost/vsock.c | 2 +- include/linux/virtio_vsock.h | 9 ++------- net/vmw_vsock/virtio_transport.c | 4 +++- 3 files changed, 6 insertions(+), 9 deletions(-) --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -376,7 +376,7 @@ vhost_vsock_alloc_skb(struct vhost_virtq return NULL; } - virtio_vsock_skb_rx_put(skb); + virtio_vsock_skb_rx_put(skb, payload_len); nbytes = copy_from_iter(skb->data, payload_len, &iov_iter); if (nbytes != payload_len) { --- a/include/linux/virtio_vsock.h +++ b/include/linux/virtio_vsock.h @@ -47,14 +47,9 @@ static inline void virtio_vsock_skb_clea VIRTIO_VSOCK_SKB_CB(skb)->tap_delivered = false; } -static inline void virtio_vsock_skb_rx_put(struct sk_buff *skb) +static inline void virtio_vsock_skb_rx_put(struct sk_buff *skb, u32 len) { - u32 len; - - len = le32_to_cpu(virtio_vsock_hdr(skb)->len); - - if (len > 0) - skb_put(skb, len); + skb_put(skb, len); } static inline struct sk_buff *virtio_vsock_alloc_skb(unsigned int size, gfp_t mask) --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -656,7 +656,9 @@ static void virtio_transport_rx_work(str continue; } - virtio_vsock_skb_rx_put(skb); + if (payload_len) + virtio_vsock_skb_rx_put(skb, payload_len); + virtio_transport_deliver_tap_pkt(skb); virtio_transport_recv_pkt(&virtio_transport, skb); } From - Wed Jan 28 16:27:24 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id 4A8qJ+c4emkzPDoAYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:19 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:19 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8Nz-00000000VEX-1TTY for hi@josie.lol; Wed, 28 Jan 2026 16:27:19 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 003703148352 for ; Wed, 28 Jan 2026 15:49:47 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id A2D042F261C; Wed, 28 Jan 2026 15:49:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="eSxXbn4Q" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E2EA2E2DF4; Wed, 28 Jan 2026 15:49:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615387; cv=none; b=AE/knwm1Z4ajyHhOnPC8gLduWHQ0CrbMZdQTULzH4T1vI05tB1pTY4Zkx0TWKPb1uxtHJg9sDNPdtYMIz4j/4de7dNIFDiXHrSPD2tXCYxFk7Wx2g/mXMz9p/yKZBc5jve62WJvUYPF5gdbIZVF2EFhquaJKaw7Xo/wpu7DERTw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615387; c=relaxed/simple; bh=nRxiH1KHX2rVne2eu3eqt42nboHcfVsp7Qk5O+67pok=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=H8javzfZFp6ujM8SsGTQ1tLBzv6o4KRWJ4OllUaw7dBV3T3gVf9ihrs+87JYwqaH5ku0elbRG8+1V59eGiV5Tog2VuoFa7hHiRHnY1DiD1qad3vfb36k6j4mHv+7EQhZZLys9sjsCUws9sdc3Nf7tVBfGhl5t5X/7I52J7riC5E= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=eSxXbn4Q; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id E943FC4CEF1; Wed, 28 Jan 2026 15:49:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615387; bh=nRxiH1KHX2rVne2eu3eqt42nboHcfVsp7Qk5O+67pok=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eSxXbn4QR5EGBA2MOY9QdJOCnL7fHsv85eq3zzRmbpfFk5yCJNoILMm3vsOxH5f0H x3ZCmRcQF3dpuWNtJUvUvEn35GivTVsfs0qTTb1kObI5BRw6b0jdmouLmHJBNRqQlw blfafsQ0IQHgyzH/uoRQqF6dW/UtJgCSWQwHn3UM= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, "kernel-dev@igalia.com, Heitor Alves de Siqueira" , Stefano Garzarella , Will Deacon , "Michael S. Tsirkin" , Heitor Alves de Siqueira Subject: [PATCH 6.12 165/169] vsock/virtio: Rename virtio_vsock_skb_rx_put() Date: Wed, 28 Jan 2026 16:24:08 +0100 Message-ID: <20260128145339.963422213@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Will Deacon [Upstream commit 8ca76151d2c8219edea82f1925a2a25907ff6a9d] In preparation for using virtio_vsock_skb_rx_put() when populating SKBs on the vsock TX path, rename virtio_vsock_skb_rx_put() to virtio_vsock_skb_put(). No functional change. Reviewed-by: Stefano Garzarella Signed-off-by: Will Deacon Message-Id: <20250717090116.11987-9-will@kernel.org> Signed-off-by: Michael S. Tsirkin Signed-off-by: Heitor Alves de Siqueira Signed-off-by: Greg Kroah-Hartman --- drivers/vhost/vsock.c | 2 +- include/linux/virtio_vsock.h | 2 +- net/vmw_vsock/virtio_transport.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -377,7 +377,7 @@ vhost_vsock_alloc_skb(struct vhost_virtq return NULL; } - virtio_vsock_skb_rx_put(skb, payload_len); + virtio_vsock_skb_put(skb, payload_len); nbytes = copy_from_iter(skb->data, payload_len, &iov_iter); if (nbytes != payload_len) { --- a/include/linux/virtio_vsock.h +++ b/include/linux/virtio_vsock.h @@ -47,7 +47,7 @@ static inline void virtio_vsock_skb_clea VIRTIO_VSOCK_SKB_CB(skb)->tap_delivered = false; } -static inline void virtio_vsock_skb_rx_put(struct sk_buff *skb, u32 len) +static inline void virtio_vsock_skb_put(struct sk_buff *skb, u32 len) { skb_put(skb, len); } --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -657,7 +657,7 @@ static void virtio_transport_rx_work(str } if (payload_len) - virtio_vsock_skb_rx_put(skb, payload_len); + virtio_vsock_skb_put(skb, payload_len); virtio_transport_deliver_tap_pkt(skb); virtio_transport_recv_pkt(&virtio_transport, skb); From - Wed Jan 28 16:27:28 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id ePVXOuo4emmBgz4AYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:22 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:23 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8O2-00000000VLL-2ers for hi@josie.lol; Wed, 28 Jan 2026 16:27:22 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 7B0173148ADE for ; Wed, 28 Jan 2026 15:49:51 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 2A87A2E2DF4; Wed, 28 Jan 2026 15:49:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="2pqgo6d3" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 075182DF155; Wed, 28 Jan 2026 15:49:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615391; cv=none; b=VCuKkG6cBGFYvisBAruO9eD0/3W6XgYFFKOHMoQl+TJO4LRfj48nVguds864kO5qnoNFAiVplaLRRGX++HhjD6fS/Oo8B3bOMOiy2gHTtZPztSdP6DI63NJRutptSG6Yp7Tc7qThQzlGtfawU05ZcQh2dhdNMJ/X1sHFsBQIyCw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615391; c=relaxed/simple; bh=tj6bcOrchwMiXJqJq7Pm+J4lJRM17a+NC7ijqZ0rlzI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=brikFm42/OygLS1FsygPVj4HsGH/xEw/bZ6dTIYE6yJeRIl3Ak4+H6agYvOUNNvUrv5NU6D8m9srcYQ9AsEvkNv75bojjc7DLwOrmiy0ULcEjOe6TCAIqSaYHi3fxRMSDQEL8MeGiM85s++cHPK9BSAU1z+Zx3UIMf/1CeGeOFM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=2pqgo6d3; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id 5639AC4CEF1; Wed, 28 Jan 2026 15:49:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615390; bh=tj6bcOrchwMiXJqJq7Pm+J4lJRM17a+NC7ijqZ0rlzI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=2pqgo6d3su2jt4OXppriopJTAimz37Zj3qkm4trjb/zpLbrHjk6Ciziwm9t4WygMu y4w8qeC/RmtiVL70nXXmJkHgvKOHV1jgF8ctPs8JQS3I/p3hPbX+e+diJ2khxubM0j 0FWMvcg6Dcam5e5IAWWYKzYg5Da1EEE6TxUG2Sks= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, "kernel-dev@igalia.com, Heitor Alves de Siqueira" , Stefano Garzarella , Will Deacon , "Michael S. Tsirkin" , Heitor Alves de Siqueira Subject: [PATCH 6.12 166/169] vhost/vsock: Allocate nonlinear SKBs for handling large receive buffers Date: Wed, 28 Jan 2026 16:24:09 +0100 Message-ID: <20260128145340.000408047@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Will Deacon [Upstream commit ab9aa2f3afc2713c14f6c4c6b90c9a0933b837f1] When receiving a packet from a guest, vhost_vsock_handle_tx_kick() calls vhost_vsock_alloc_linear_skb() to allocate and fill an SKB with the receive data. Unfortunately, these are always linear allocations and can therefore result in significant pressure on kmalloc() considering that the maximum packet size (VIRTIO_VSOCK_MAX_PKT_BUF_SIZE + VIRTIO_VSOCK_SKB_HEADROOM) is a little over 64KiB, resulting in a 128KiB allocation for each packet. Rework the vsock SKB allocation so that, for sizes with page order greater than PAGE_ALLOC_COSTLY_ORDER, a nonlinear SKB is allocated instead with the packet header in the SKB and the receive data in the fragments. Finally, add a debug warning if virtio_vsock_skb_rx_put() is ever called on an SKB with a non-zero length, as this would be destructive for the nonlinear case. Reviewed-by: Stefano Garzarella Signed-off-by: Will Deacon Message-Id: <20250717090116.11987-8-will@kernel.org> Signed-off-by: Michael S. Tsirkin Signed-off-by: Heitor Alves de Siqueira Signed-off-by: Greg Kroah-Hartman --- drivers/vhost/vsock.c | 8 +++----- include/linux/virtio_vsock.h | 32 +++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -350,7 +350,7 @@ vhost_vsock_alloc_skb(struct vhost_virtq return NULL; /* len contains both payload and hdr */ - skb = virtio_vsock_alloc_linear_skb(len, GFP_KERNEL); + skb = virtio_vsock_alloc_skb(len, GFP_KERNEL); if (!skb) return NULL; @@ -379,10 +379,8 @@ vhost_vsock_alloc_skb(struct vhost_virtq virtio_vsock_skb_put(skb, payload_len); - nbytes = copy_from_iter(skb->data, payload_len, &iov_iter); - if (nbytes != payload_len) { - vq_err(vq, "Expected %zu byte payload, got %zu bytes\n", - payload_len, nbytes); + if (skb_copy_datagram_from_iter(skb, 0, &iov_iter, payload_len)) { + vq_err(vq, "Failed to copy %zu byte payload\n", payload_len); kfree_skb(skb); return NULL; } --- a/include/linux/virtio_vsock.h +++ b/include/linux/virtio_vsock.h @@ -49,22 +49,48 @@ static inline void virtio_vsock_skb_clea static inline void virtio_vsock_skb_put(struct sk_buff *skb, u32 len) { - skb_put(skb, len); + DEBUG_NET_WARN_ON_ONCE(skb->len); + + if (skb_is_nonlinear(skb)) + skb->len = len; + else + skb_put(skb, len); } static inline struct sk_buff * -virtio_vsock_alloc_linear_skb(unsigned int size, gfp_t mask) +__virtio_vsock_alloc_skb_with_frags(unsigned int header_len, + unsigned int data_len, + gfp_t mask) { struct sk_buff *skb; + int err; - skb = alloc_skb(size, mask); + skb = alloc_skb_with_frags(header_len, data_len, + PAGE_ALLOC_COSTLY_ORDER, &err, mask); if (!skb) return NULL; skb_reserve(skb, VIRTIO_VSOCK_SKB_HEADROOM); + skb->data_len = data_len; return skb; } +static inline struct sk_buff * +virtio_vsock_alloc_linear_skb(unsigned int size, gfp_t mask) +{ + return __virtio_vsock_alloc_skb_with_frags(size, 0, mask); +} + +static inline struct sk_buff *virtio_vsock_alloc_skb(unsigned int size, gfp_t mask) +{ + if (size <= SKB_WITH_OVERHEAD(PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER)) + return virtio_vsock_alloc_linear_skb(size, mask); + + size -= VIRTIO_VSOCK_SKB_HEADROOM; + return __virtio_vsock_alloc_skb_with_frags(VIRTIO_VSOCK_SKB_HEADROOM, + size, mask); +} + static inline void virtio_vsock_skb_queue_head(struct sk_buff_head *list, struct sk_buff *skb) { From - Wed Jan 28 16:27:33 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id yD3iHPA4emmC+DsAYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:28 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:28 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8O8-00000000VWq-0ZNo for hi@josie.lol; Wed, 28 Jan 2026 16:27:28 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id AF1C1314F839 for ; Wed, 28 Jan 2026 15:49:57 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 5947E2E2DF4; Wed, 28 Jan 2026 15:49:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="HzMOLDWp" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 36E1C2DF138; Wed, 28 Jan 2026 15:49:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615397; cv=none; b=BarBlKLQC4oXdBHC4MQ0MLI4Pt1XuDrMsFRvzsVq1ZSvado14Grc8iy4+qp9fwkvH20BiC07k8UKB8PI/ypM3HG7dkOrDb+G/bAG6P9QwEnm6vguyU6bFxMz229LSBhz3BYmBBRn3cNZDWT0lAnz4xPadRqgRHC6dYA5vNNfpZg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615397; c=relaxed/simple; bh=tFGlRD6WbcddGBWcrbA9QufwiuxnE+YBunthxwIDuyk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cxum2srIMiKbfQI4xSMoiheo2I5+pLCBBX+Cb+rjToxOyfx0FaOBqwOTlh/RvYqfyHxABdE3STKqEFWUZYnHS4wHd0esXNYR46Nc5jWQcIorTpRFfD66TxfIEYJ+sr3o0XYEv2r+VOX5s3yzhmEBnLo22BNYg5+Rq111F6+ce4s= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=HzMOLDWp; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id A3CBEC4CEF1; Wed, 28 Jan 2026 15:49:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615397; bh=tFGlRD6WbcddGBWcrbA9QufwiuxnE+YBunthxwIDuyk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HzMOLDWpt8KD1pLlsyVJRSjHDzT8Jkt2+hKE1sOqFpqrZU9ZnmEr9ndU3hRdKzENV S2gp3DRkN5sio7WcH3bHLD1kRC3GMOkwwXw+fuUU117XlmVLMVqzNd8TJy2V3FOXWk oQ6wOaOpMCWfCMNeJNYCxor1OTPeSucZqUqb+yKY= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Christian Brauner , Alexander Viro , Will Deacon , "Michael S. Tsirkin" , Stefan Hajnoczi , Jakub Kicinski , Heitor Alves de Siqueira Subject: [PATCH 6.12 168/169] net: Introduce skb_copy_datagram_from_iter_full() Date: Wed, 28 Jan 2026 16:24:11 +0100 Message-ID: <20260128145340.074993193@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: msgid.link] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Will Deacon [Upstream commit b08a784a5d1495c42ff9b0c70887d49211cddfe0] In a similar manner to copy_from_iter()/copy_from_iter_full(), introduce skb_copy_datagram_from_iter_full() which reverts the iterator to its initial state when returning an error. A subsequent fix for a vsock regression will make use of this new function. Cc: Christian Brauner Cc: Alexander Viro Signed-off-by: Will Deacon Acked-by: Michael S. Tsirkin Reviewed-by: Stefan Hajnoczi Link: https://patch.msgid.link/20250818180355.29275-2-will@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Heitor Alves de Siqueira Signed-off-by: Greg Kroah-Hartman --- include/linux/skbuff.h | 2 ++ net/core/datagram.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+) --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4117,6 +4117,8 @@ int skb_copy_and_hash_datagram_iter(cons struct ahash_request *hash); int skb_copy_datagram_from_iter(struct sk_buff *skb, int offset, struct iov_iter *from, int len); +int skb_copy_datagram_from_iter_full(struct sk_buff *skb, int offset, + struct iov_iter *from, int len); int zerocopy_sg_from_iter(struct sk_buff *skb, struct iov_iter *frm); void skb_free_datagram(struct sock *sk, struct sk_buff *skb); int skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags); --- a/net/core/datagram.c +++ b/net/core/datagram.c @@ -621,6 +621,20 @@ fault: } EXPORT_SYMBOL(skb_copy_datagram_from_iter); +int skb_copy_datagram_from_iter_full(struct sk_buff *skb, int offset, + struct iov_iter *from, int len) +{ + struct iov_iter_state state; + int ret; + + iov_iter_save_state(from, &state); + ret = skb_copy_datagram_from_iter(skb, offset, from, len); + if (ret) + iov_iter_restore(from, &state); + return ret; +} +EXPORT_SYMBOL(skb_copy_datagram_from_iter_full); + int zerocopy_fill_skb_from_iter(struct sk_buff *skb, struct iov_iter *from, size_t length) { From - Wed Jan 28 16:27:34 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id wKliM/A4emk6GT8AYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:28 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:29 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8O8-00000000VXb-2RiR for hi@josie.lol; Wed, 28 Jan 2026 16:27:28 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 8F47031186AF for ; Wed, 28 Jan 2026 15:49:55 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C74532F261C; Wed, 28 Jan 2026 15:49:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="z7Ep+jN3" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 942052EA172; Wed, 28 Jan 2026 15:49:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615394; cv=none; b=b5pejiaX/r39gQO8c23eDW7E4WTyeSlWz9pxO3bf5vUqBttLLdzW6teKLHl26SBMYvXikoxvOMqYeEz1OpnYsXSLxsI2Bn+aoPdwkDKDx/u+TvDsrFFuaDvWhNzeCkrx4n7xAb6iu0CkH2wp0PdZ8oj/SspVoUbjw3Ivgp55MLc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615394; c=relaxed/simple; bh=Q20CDpNz9Xb73sC5b3s2ZBo/pLfeW/qrS61y4XUJnHE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=atcYixRJhm4xrhI4HxFC6NOJX23KWq5CaSFZNPG5zBtvBcJ/1djO8uLHusH91LKjR+P9hKCA4rljLj83rvY2FhV8IBbLk6EjASbBtw8SpnLOf7AItyXCoEfhVOrzpqKfwFN30WJSwFAay4ax7aK4b8fzwaxvK0GCMAha1PphjHs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=z7Ep+jN3; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id AEC5FC4CEF7; Wed, 28 Jan 2026 15:49:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615394; bh=Q20CDpNz9Xb73sC5b3s2ZBo/pLfeW/qrS61y4XUJnHE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=z7Ep+jN3GcMm6YOtGNHw2uOipjyXBSPbhDICYut+kq9vVJWnqmDIGBchk8yt9JJdQ G8Ss4YQNeonqPqReTZsWp9hfeY/aiprQeH1F3Hn5Pq0c2210zaDRxNjvISnL1d/ynW JUrvgfO6yL+PUsyY1EXipNPq9I0LmBoBCCvKfmU0= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, "kernel-dev@igalia.com, Heitor Alves de Siqueira" , Stefano Garzarella , Will Deacon , "Michael S. Tsirkin" , Heitor Alves de Siqueira Subject: [PATCH 6.12 167/169] vsock/virtio: Allocate nonlinear SKBs for handling large transmit buffers Date: Wed, 28 Jan 2026 16:24:10 +0100 Message-ID: <20260128145340.037790800@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Will Deacon [Upstream commit 6693731487a8145a9b039bc983d77edc47693855] When transmitting a vsock packet, virtio_transport_send_pkt_info() calls virtio_transport_alloc_linear_skb() to allocate and fill SKBs with the transmit data. Unfortunately, these are always linear allocations and can therefore result in significant pressure on kmalloc() considering that the maximum packet size (VIRTIO_VSOCK_MAX_PKT_BUF_SIZE + VIRTIO_VSOCK_SKB_HEADROOM) is a little over 64KiB, resulting in a 128KiB allocation for each packet. Rework the vsock SKB allocation so that, for sizes with page order greater than PAGE_ALLOC_COSTLY_ORDER, a nonlinear SKB is allocated instead with the packet header in the SKB and the transmit data in the fragments. Note that this affects both the vhost and virtio transports. Reviewed-by: Stefano Garzarella Signed-off-by: Will Deacon Message-Id: <20250717090116.11987-10-will@kernel.org> Signed-off-by: Michael S. Tsirkin Signed-off-by: Heitor Alves de Siqueira Signed-off-by: Greg Kroah-Hartman --- net/vmw_vsock/virtio_transport_common.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -111,7 +111,8 @@ static int virtio_transport_fill_skb(str &info->msg->msg_iter, len); - return memcpy_from_msg(skb_put(skb, len), info->msg, len); + virtio_vsock_skb_put(skb, len); + return skb_copy_datagram_from_iter(skb, 0, &info->msg->msg_iter, len); } static void virtio_transport_init_hdr(struct sk_buff *skb, @@ -263,7 +264,7 @@ static struct sk_buff *virtio_transport_ if (!zcopy) skb_len += payload_len; - skb = virtio_vsock_alloc_linear_skb(skb_len, GFP_KERNEL); + skb = virtio_vsock_alloc_skb(skb_len, GFP_KERNEL); if (!skb) return NULL; From - Wed Jan 28 16:27:41 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id UO5UF/g4emk6GT8AYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:36 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:36 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8OG-00000000VlE-0WNA for hi@josie.lol; Wed, 28 Jan 2026 16:27:36 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id B781D31345CC for ; Wed, 28 Jan 2026 15:50:01 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3D0BA2E2DF4; Wed, 28 Jan 2026 15:50:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="KVt0AOGl" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1A1CC2DF138; Wed, 28 Jan 2026 15:50:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615401; cv=none; b=URqmfRs89Ypm+SnpcoB7jDci8eztWSHxCikM5E3DDl2D6CSXxKKwIsrfBJy8OH26hmn6OY2WVa/aeHGtYIEF5MDywEWFCbN+uVc28V46G5zsDannWN/0t4lBAFd29nJojUrFuevEKHDGYJptbiEdF5cU8v6e3Q/y/djL9uKxpag= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615401; c=relaxed/simple; bh=AYZUS5vQOrSM7zEsodlzPCC+Q4Q/8BL4NRNoval4g3U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=i9yDUXC+JlwBNu4A1HV426bAx3oF9qO8fJHjJSv3dmQHJ5sFoEXt/sHfhDs+Bo4RNhqeFeKyDuIQK+Rg9fBmc7A1zky+imsBRggVxD9EZP0++IDuIlOt3NqeCIZg7J6Wz4QgRW1QrrT0H/K3YBF2qcS2wjrbqBR7IEIVLM7CGCE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=KVt0AOGl; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0D802C4CEF1; Wed, 28 Jan 2026 15:49:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615400; bh=AYZUS5vQOrSM7zEsodlzPCC+Q4Q/8BL4NRNoval4g3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KVt0AOGl10vqQAXdPVYKZl3HbUbQzUBjaYMjb5NJeNvzEd+R0ouNE4gDO+VVM+OEP KUSGwRffL3v+ADRq5BlkO3XkdhPt/e0hsDmGnM4+Gu4DZd64/LA0cCwqsGltZvz/v4 UEX9Y7PmaMp5AO6W4LYdTqxEbExSah4mA/iyybwQ= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Dragan Simic , Geraldo Nascimento , Shawn Lin , Heiko Stuebner , Sasha Levin Subject: [PATCH 6.12 151/169] arm64: dts: rockchip: remove redundant max-link-speed from nanopi-r4s Date: Wed, 28 Jan 2026 16:23:54 +0100 Message-ID: <20260128145339.439136743@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: msgid.link] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Geraldo Nascimento [ Upstream commit ce652c98a7bfa0b7c675ef5cd85c44c186db96af ] This is already the default in rk3399-base.dtsi, remove redundant declaration from rk3399-nanopi-r4s.dtsi. Fixes: db792e9adbf8 ("rockchip: rk3399: Add support for FriendlyARM NanoPi R4S") Cc: stable@vger.kernel.org Reported-by: Dragan Simic Reviewed-by: Dragan Simic Signed-off-by: Geraldo Nascimento Acked-by: Shawn Lin Link: https://patch.msgid.link/6694456a735844177c897581f785cc00c064c7d1.1763415706.git.geraldogabriel@gmail.com Signed-off-by: Heiko Stuebner [ adapted file path from rk3399-nanopi-r4s.dtsi to rk3399-nanopi-r4s.dts ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dts | 1 - 1 file changed, 1 deletion(-) --- a/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dts +++ b/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dts @@ -73,7 +73,6 @@ }; &pcie0 { - max-link-speed = <1>; num-lanes = <1>; vpcie3v3-supply = <&vcc3v3_sys>; }; From - Wed Jan 28 16:27:50 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id +P+WKgI5emk6GT8AYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:46 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:46 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8OQ-00000000W45-1bDV for hi@josie.lol; Wed, 28 Jan 2026 16:27:46 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id A261430E12B6 for ; Wed, 28 Jan 2026 15:50:08 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id E18872E2DF4; Wed, 28 Jan 2026 15:50:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="zm1oUnal" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AEE4C288C22; Wed, 28 Jan 2026 15:50:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615407; cv=none; b=FXiCP35ULuMXIYv3PoBhNh9VElmxgFL/HCyWbTkhx6vedhdDRtrHsilv48xk9NSbGLdHlJ1m1aNrC8R+XW2NQo14/uXIzuFo7IdehgJYSBJMPmjqgZ65J585L+PLonl9RL3F6GhncJaa87TZ2bpJYkat79x+icfvrwVUOBhtmJY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615407; c=relaxed/simple; bh=XP7iCW/fFtecCpQAoo011EIJc0uVNDNz5KgGXu2SuZo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Y946f0m8/ps3jmbcvRGEcX+tkuLVarrqkSZxFxaIo046gElc5LDbD0TREmz3sTZiAQ2BS6/VJNGS3qkLg1oQx46Zxs1nw32TDHIihUk9koZwDgT4yk14+T/ZvyQ4G1CDpMPOgsR4hzvYlPhTEKx01vtYxm4nO+MttCoTFcAk7HU= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=zm1oUnal; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id E00BBC4CEF1; Wed, 28 Jan 2026 15:50:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615407; bh=XP7iCW/fFtecCpQAoo011EIJc0uVNDNz5KgGXu2SuZo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=zm1oUnalVOY7P1PIgQ0wCJEd1J49Mxmr/gxRErnsPl721N/LM7VVcXSqeeeenoFE5 XornW4Sa/DqWHhVWgmioTlEBIFtwlxwLsG5LkWKyUWHGvhjVtZYPmVI3D+ogVPoPdm 2Y8u1+gyCXIo0TKWefFTlSXceM1iwpA88sh8bg8s= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Andy Shevchenko , =?UTF-8?q?Nuno=20S=C3=A1?= , Jonathan Cameron , Sasha Levin Subject: [PATCH 6.12 152/169] iio: core: add missing mutex_destroy in iio_dev_release() Date: Wed, 28 Jan 2026 16:23:55 +0100 Message-ID: <20260128145339.475910119@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Andy Shevchenko [ Upstream commit f5d203467a31798191365efeb16cd619d2c8f23a ] Add missing mutex_destroy() call in iio_dev_release() to properly clean up the mutex initialized in iio_device_alloc(). Ensure proper resource cleanup and follows kernel practices. Found by code review. While at it, create a lockdep key before mutex initialisation. This will help with converting it to the better API in the future. Fixes: 847ec80bbaa7 ("Staging: IIO: core support for device registration and management") Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron Stable-dep-of: 9910159f0659 ("iio: core: add separate lockdep class for info_exist_lock") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/iio/industrialio-core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1628,6 +1628,9 @@ static void iio_dev_release(struct devic iio_device_detach_buffers(indio_dev); + mutex_destroy(&iio_dev_opaque->info_exist_lock); + mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1672,8 +1675,7 @@ struct iio_dev *iio_device_alloc(struct indio_dev->dev.type = &iio_device_type; indio_dev->dev.bus = &iio_bus_type; device_initialize(&indio_dev->dev); - mutex_init(&iio_dev_opaque->mlock); - mutex_init(&iio_dev_opaque->info_exist_lock); + INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); @@ -1696,6 +1698,9 @@ struct iio_dev *iio_device_alloc(struct lockdep_register_key(&iio_dev_opaque->mlock_key); lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); + mutex_init(&iio_dev_opaque->mlock); + mutex_init(&iio_dev_opaque->info_exist_lock); + return indio_dev; } EXPORT_SYMBOL(iio_device_alloc); From - Wed Jan 28 16:27:56 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id 8G4tHgc5emk6GT8AYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:51 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:51 +0000 Received: from sea.lore.kernel.org ([172.234.253.10]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8OV-00000000WG2-0ZWz for hi@josie.lol; Wed, 28 Jan 2026 16:27:51 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 6308D3155671 for ; Wed, 28 Jan 2026 15:50:15 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id AFF412E2DF4; Wed, 28 Jan 2026 15:50:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="UCDtFaQn" X-Original-To: stable@vger.kernel.org Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8ABC0288C22; Wed, 28 Jan 2026 15:50:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615414; cv=none; b=awVh4ZxG8RDY/6xFWyUvdhpNI1HsO4SKKMHK8pKlTZ46IpeBNZVBMi6fWHtn7qjw0XwjP+2wkQXAeN2KkuH+cYcD3yUOBXP9jpNeWMkr6AHz4Q9Lz7+bfJmCjBtxuNS20UEKb8wUfh34aA90oS9sRQypbXp/GkGGMq9UOjfp3OY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769615414; c=relaxed/simple; bh=FS6H52q/AClE2NIoiREK1rbyGc9rHD/rG72BR2zVb9Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FRa2QzceAjuVk7TiMtdB2u44aarNUdcM1XXn8OXaeRZs6sb6quG5wCEMpcyINUal2eTrbQJoHhjEyUssmEeqqtHqzAwflAUTvMTxPxJDSiprOBAS4j3p/cD20yKnXkPJKDAZtQpjob6wRPKb87DpVtzyScS6NWo4xe7wcL890MY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=UCDtFaQn; arc=none smtp.client-ip=10.30.226.201 Received: by smtp.kernel.org (Postfix) with ESMTPSA id 01485C4CEF1; Wed, 28 Jan 2026 15:50:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1769615414; bh=FS6H52q/AClE2NIoiREK1rbyGc9rHD/rG72BR2zVb9Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UCDtFaQn6FnarPxyDh2E41chsIxb6vwpQqqTHpS3hexS47RntOhmow3JXSLcIb1kk 2hrR/gKNjlqJFAs7rxeCOeSDz0w3I2rxKnmvnxJUo0432BcoVsysvg1d8Cw0ikPcRf F9A/CKnvY+buN11fs6HuVbH/8Ln5/iV1MMeMqbpQ= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Rasmus Villemoes , Peter Rosin , Jonathan Cameron , Sasha Levin Subject: [PATCH 6.12 154/169] iio: core: add separate lockdep class for info_exist_lock Date: Wed, 28 Jan 2026 16:23:57 +0100 Message-ID: <20260128145339.551456129@linuxfoundation.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260128145334.006287341@linuxfoundation.org> References: <20260128145334.006287341@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-DKIM: signer='linuxfoundation.org' status='pass' reason='' DKIMCheck: Server passes DKIM test, 0 Spam score X-Spam-Score: 0.4 (/) X-Spam-Report: Spam detection software, running on the system "witcher.mxrouting.net", has performed the tests listed below against this email. Information: https://mxroutedocs.com/directadmin/spamfilters/ --- Content analysis details: (0.4 points) --- pts rule name description ---- ---------------------- ----------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: linuxfoundation.org] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [172.234.253.10 listed in list.dnswl.org] 1.5 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager -0.0 DKIMWL_WL_HIGH DKIMwl.org - High trust sender SpamTally: Final spam score: 4 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Rasmus Villemoes [ Upstream commit 9910159f06590c17df4fbddedaabb4c0201cc4cb ] When one iio device is a consumer of another, it is possible that the ->info_exist_lock of both ends up being taken when reading the value of the consumer device. Since they currently belong to the same lockdep class (being initialized in a single location with mutex_init()), that results in a lockdep warning CPU0 ---- lock(&iio_dev_opaque->info_exist_lock); lock(&iio_dev_opaque->info_exist_lock); *** DEADLOCK *** May be due to missing lock nesting notation 4 locks held by sensors/414: #0: c31fd6dc (&p->lock){+.+.}-{3:3}, at: seq_read_iter+0x44/0x4e4 #1: c4f5a1c4 (&of->mutex){+.+.}-{3:3}, at: kernfs_seq_start+0x1c/0xac #2: c2827548 (kn->active#34){.+.+}-{0:0}, at: kernfs_seq_start+0x30/0xac #3: c1dd2b68 (&iio_dev_opaque->info_exist_lock){+.+.}-{3:3}, at: iio_read_channel_processed_scale+0x24/0xd8 stack backtrace: CPU: 0 UID: 0 PID: 414 Comm: sensors Not tainted 6.17.11 #5 NONE Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x44/0x60 dump_stack_lvl from print_deadlock_bug+0x2b8/0x334 print_deadlock_bug from __lock_acquire+0x13a4/0x2ab0 __lock_acquire from lock_acquire+0xd0/0x2c0 lock_acquire from __mutex_lock+0xa0/0xe8c __mutex_lock from mutex_lock_nested+0x1c/0x24 mutex_lock_nested from iio_read_channel_raw+0x20/0x6c iio_read_channel_raw from rescale_read_raw+0x128/0x1c4 rescale_read_raw from iio_channel_read+0xe4/0xf4 iio_channel_read from iio_read_channel_processed_scale+0x6c/0xd8 iio_read_channel_processed_scale from iio_hwmon_read_val+0x68/0xbc iio_hwmon_read_val from dev_attr_show+0x18/0x48 dev_attr_show from sysfs_kf_seq_show+0x80/0x110 sysfs_kf_seq_show from seq_read_iter+0xdc/0x4e4 seq_read_iter from vfs_read+0x238/0x2e4 vfs_read from ksys_read+0x6c/0xec ksys_read from ret_fast_syscall+0x0/0x1c Just as the mlock_key already has its own lockdep class, add a lock_class_key for the info_exist mutex. Note that this has in theory been a problem since before IIO first left staging, but it only occurs when a chain of consumers is in use and that is not often done. Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Rasmus Villemoes Reviewed-by: Peter Rosin Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/iio/industrialio-core.c | 4 +++- include/linux/iio/iio-opaque.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1631,6 +1631,7 @@ static void iio_dev_release(struct devic mutex_destroy(&iio_dev_opaque->info_exist_lock); mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->info_exist_key); lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1696,9 +1697,10 @@ struct iio_dev *iio_device_alloc(struct INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); lockdep_register_key(&iio_dev_opaque->mlock_key); + lockdep_register_key(&iio_dev_opaque->info_exist_key); mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); - mutex_init(&iio_dev_opaque->info_exist_lock); + mutex_init_with_key(&iio_dev_opaque->info_exist_lock, &iio_dev_opaque->info_exist_key); return indio_dev; } --- a/include/linux/iio/iio-opaque.h +++ b/include/linux/iio/iio-opaque.h @@ -14,6 +14,7 @@ * @mlock: lock used to prevent simultaneous device state changes * @mlock_key: lockdep class for iio_dev lock * @info_exist_lock: lock to prevent use during removal + * @info_exist_key: lockdep class for info_exist lock * @trig_readonly: mark the current trigger immutable * @event_interface: event chrdevs associated with interrupt lines * @attached_buffers: array of buffers statically attached by the driver @@ -47,6 +48,7 @@ struct iio_dev_opaque { struct mutex mlock; struct lock_class_key mlock_key; struct mutex info_exist_lock; + struct lock_class_key info_exist_key; bool trig_readonly; struct iio_event_interface *event_interface; struct iio_buffer **attached_buffers; From - Wed Jan 28 16:28:01 2026 X-Mozilla-Status: 0001 X-Mozilla-Status2: 00000000 Return-Path: Delivered-To: hi@josie.lol Received: from witcher.mxrouting.net by witcher.mxrouting.net with LMTP id QN1iGwg5emkzPDoAYBR5ng (envelope-from ) for ; Wed, 28 Jan 2026 16:27:52 +0000 Return-path: Envelope-to: hi@josie.lol Delivery-date: Wed, 28 Jan 2026 16:27:52 +0000 Received: from sin.lore.kernel.org ([104.64.211.4]) by witcher.mxrouting.net with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98) (envelope-from ) id 1vl8OV-00000000WBn-2qMt for hi@josie.lol; Wed, 28 Jan 2026 16:27:52 +0000 Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sin.lore.kernel.org (Postfix) with ESMTP id 0848B31190DC for ; Wed, 28 Jan 2026 15:55:37 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kern/*! This crate provides a cross platform abstraction for writing colored text to a terminal. Colors are written using either ANSI escape sequences or by communicating with a Windows console. Much of this API was motivated by use inside command line applications, where colors or styles can be configured by the end user and/or the environment. This crate also provides platform independent support for writing colored text to an in memory buffer. While this is easy to do with ANSI escape sequences (because they are in the buffer themselves), it is trickier to do with the Windows console API, which requires synchronous communication. In ANSI mode, this crate also provides support for writing hyperlinks. # Organization The `WriteColor` trait extends the `io::Write` trait with methods for setting colors or resetting them. `StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are analogous to `std::io::Stdout` and `std::io::StdoutLock`, or `std::io::Stderr` and `std::io::StderrLock`. `Buffer` is an in memory buffer that supports colored text. In a parallel program, each thread might write to its own buffer. A buffer can be printed to using a `BufferWriter`. The advantage of this design is that each thread can work in parallel on a buffer without having to synchronize access to global resources such as the Windows console. Moreover, this design also prevents interleaving of buffer output. `Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of `io::Write`. These types are useful when you know exactly what you need. An analogous type for the Windows console is not provided since it cannot exist. # Example: using `StandardStream` The `StandardStream` type in this crate works similarly to `std::io::Stdout`, except it is augmented with methods for coloring by the `WriteColor` trait. For example, to write some green text: ```rust,no_run # fn test() -> Result<(), Box<::std::error::Error>> { use std::io::Write; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; let mut stdout = StandardStream::stdout(ColorChoice::Always); stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; writeln!(&mut stdout, "green text!")?; # Ok(()) } ``` Note that any text written to the terminal now will be colored green when using ANSI escape sequences, even if it is written via stderr, and even if stderr had previously been set to `Color::Red`. Users will need to manage any color changes themselves by calling [`WriteColor::set_color`](trait.WriteColor.html#tymethod.set_color), and this may include calling [`WriteColor::reset`](trait.WriteColor.html#tymethod.reset) before the program exits to a shell. # Example: using `BufferWriter` A `BufferWriter` can create buffers and write buffers to stdout or stderr. It does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer` implements `io::Write` and `io::WriteColor`. This example shows how to print some green text to stderr. ```rust,no_run # fn test() -> Result<(), Box<::std::error::Error>> { use std::io::Write; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; let mut bufwtr = BufferWriter::stderr(ColorChoice::Always); let mut buffer = bufwtr.buffer(); buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; writeln!(&mut buffer, "green text!")?; bufwtr.print(&buffer)?; # Ok(()) } ``` # Detecting presence of a terminal In many scenarios when using color, one often wants to enable colors automatically when writing to a terminal and disable colors automatically when writing to anything else. The typical way to achieve this in Unix environments is via libc's [`isatty`](https://man7.org/linux/man-pages/man3/isatty.3.html) function. Unfortunately, this notoriously does not work well in Windows environments. To work around that, the recommended solution is to use the standard library's [`IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html) trait. It goes out of its way to get it as right as possible in Windows environments. For example, in a command line application that exposes a `--color` flag, your logic for how to enable colors might look like this: ```ignore use std::io::IsTerminal; use termcolor::{ColorChoice, StandardStream}; let preference = argv.get_flag("color").unwrap_or("auto"); let mut choice = preference.parse::()?; if choice == ColorChoice::Auto && !std::io::stdin().is_terminal() { choice = ColorChoice::Never; } let stdout = StandardStream::stdout(choice); // ... write to stdout ``` Currently, `termcolor` does not provide anything to do this for you. */ #![deny(missing_debug_implementations, missing_docs)] // #[cfg(doctest)] // use doc_comment::doctest; // #[cfg(doctest)] // doctest!("../README.md"); use std::env; use std::error; use std::fmt; use std::io::{self, Write}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(windows)] use std::sync::{Mutex, MutexGuard}; #[cfg(windows)] use winapi_util::console as wincon; /// This trait describes the behavior of writers that support colored output. pub trait WriteColor: io::Write { /// Returns true if and only if the underlying writer supports colors. fn supports_color(&self) -> bool; /// Set the color settings of the writer. /// /// Subsequent writes to this writer will use these settings until either /// `reset` is called or new color settings are set. /// /// If there was a problem setting the color settings, then an error is /// returned. fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()>; /// Reset the current color settings to their original settings. /// /// If there was a problem resetting the color settings, then an error is /// returned. /// /// Note that this does not reset hyperlinks. Those need to be /// reset on their own, e.g., by calling `set_hyperlink` with /// [`HyperlinkSpec::none`]. fn reset(&mut self) -> io::Result<()>; /// Returns true if and only if the underlying writer must synchronously /// interact with an end user's device in order to control colors. By /// default, this always returns `false`. /// /// In practice, this should return `true` if the underlying writer is /// manipulating colors using the Windows console APIs. /// /// This is useful for writing generic code (such as a buffered writer) /// that can perform certain optimizations when the underlying writer /// doesn't rely on synchronous APIs. For example, ANSI escape sequences /// can be passed through to the end user's device as is. fn is_synchronous(&self) -> bool { false } /// Set the current hyperlink of the writer. /// /// The typical way to use this is to first call it with a /// [`HyperlinkSpec::open`] to write the actual URI to a tty that supports /// [OSC-8]. At this point, the caller can now write the label for the /// hyperlink. This may include coloring or other styles. Once the caller /// has finished writing the label, one should call this method again with /// [`HyperlinkSpec::close`]. /// /// If there was a problem setting the hyperlink, then an error is /// returned. /// /// This defaults to doing nothing. /// /// [OSC8]: https://github.com/Alhadis/OSC8-Adoption/ fn set_hyperlink(&mut self, _link: &HyperlinkSpec) -> io::Result<()> { Ok(()) } /// Returns true if and only if the underlying writer supports hyperlinks. /// /// This can be used to avoid generating hyperlink URIs unnecessarily. /// /// This defaults to `false`. fn supports_hyperlinks(&self) -> bool { false } } impl<'a, T: ?Sized + WriteColor> WriteColor for &'a mut T { fn supports_color(&self) -> bool { (&**self).supports_color() } fn supports_hyperlinks(&self) -> bool { (&**self).supports_hyperlinks() } fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { (&mut **self).set_color(spec) } fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { (&mut **self).set_hyperlink(link) } fn reset(&mut self) -> io::Result<()> { (&mut **self).reset() } fn is_synchronous(&self) -> bool { (&**self).is_synchronous() } } impl WriteColor for Box { fn supports_color(&self) -> bool { (&**self).supports_color() } fn supports_hyperlinks(&self) -> bool { (&**self).supports_hyperlinks() } fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { (&mut **self).set_color(spec) } fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { (&mut **self).set_hyperlink(link) } fn reset(&mut self) -> io::Result<()> { (&mut **self).reset() } fn is_synchronous(&self) -> bool { (&**self).is_synchronous() } } /// ColorChoice represents the color preferences of an end user. /// /// The `Default` implementation for this type will select `Auto`, which tries /// to do the right thing based on the current environment. /// /// The `FromStr` implementation for this type converts a lowercase kebab-case /// string of the variant name to the corresponding variant. Any other string /// results in an error. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ColorChoice { /// Try very hard to emit colors. This includes emitting ANSI colors /// on Windows if the console API is unavailable. Always, /// AlwaysAnsi is like Always, except it never tries to use anything other /// than emitting ANSI color codes. AlwaysAnsi, /// Try to use colors, but don't force the issue. If the console isn't /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for /// example, then don't use colors. Auto, /// Never emit colors. Never, } /// The default is `Auto`. impl Default for ColorChoice { fn default() -> ColorChoice { ColorChoice::Auto } } impl FromStr for ColorChoice { type Err = ColorChoiceParseError; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "always" => Ok(ColorChoice::Always), "always-ansi" => Ok(ColorChoice::AlwaysAnsi), "never" => Ok(ColorChoice::Never), "auto" => Ok(ColorChoice::Auto), unknown => Err(ColorChoiceParseError { unknown_choice: unknown.to_string(), }), } } } impl ColorChoice { /// Returns true if we should attempt to write colored output. fn should_attempt_color(&self) -> bool { match *self { ColorChoice::Always => true, ColorChoice::AlwaysAnsi => true, ColorChoice::Never => false, ColorChoice::Auto => self.env_allows_color(), } } #[cfg(not(windows))] fn env_allows_color(&self) -> bool { match env::var_os("TERM") { // If TERM isn't set, then we are in a weird environment that // probably doesn't support colors. None => return false, Some(k) => { if k == "dumb" { return false; } } } // If TERM != dumb, then the only way we don't allow colors at this // point is if NO_COLOR is set. if env::var_os("NO_COLOR").is_some() { return false; } true } #[cfg(windows)] fn env_allows_color(&self) -> bool { // On Windows, if TERM isn't set, then we shouldn't automatically // assume that colors aren't allowed. This is unlike Unix environments // where TERM is more rigorously set. if let Some(k) = env::var_os("TERM") { if k == "dumb" { return false; } } // If TERM != dumb, then the only way we don't allow colors at this // point is if NO_COLOR is set. if env::var_os("NO_COLOR").is_some() { return false; } true } /// Returns true if this choice should forcefully use ANSI color codes. /// /// It's possible that ANSI is still the correct choice even if this /// returns false. #[cfg(windows)] fn should_ansi(&self) -> bool { match *self { ColorChoice::Always => false, ColorChoice::AlwaysAnsi => true, ColorChoice::Never => false, ColorChoice::Auto => { match env::var("TERM") { Err(_) => false, // cygwin doesn't seem to support ANSI escape sequences // and instead has its own variety. However, the Windows // console API may be available. Ok(k) => k != "dumb" && k != "cygwin", } } } } } /// An error that occurs when parsing a `ColorChoice` fails. #[derive(Clone, Debug)] pub struct ColorChoiceParseError { unknown_choice: String, } impl std::error::Error for ColorChoiceParseError {} impl fmt::Display for ColorChoiceParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "unrecognized color choice '{}': valid choices are: \ always, always-ansi, never, auto", self.unknown_choice, ) } } /// `std::io` implements `Stdout` and `Stderr` (and their `Lock` variants) as /// separate types, which makes it difficult to abstract over them. We use /// some simple internal enum types to work around this. enum StandardStreamType { Stdout, Stderr, StdoutBuffered, StderrBuffered, } #[derive(Debug)] enum IoStandardStream { Stdout(io::Stdout), Stderr(io::Stderr), StdoutBuffered(io::BufWriter), StderrBuffered(io::BufWriter), } impl IoStandardStream { fn new(sty: StandardStreamType) -> IoStandardStream { match sty { StandardStreamType::Stdout => { IoStandardStream::Stdout(io::stdout()) } StandardStreamType::Stderr => { IoStandardStream::Stderr(io::stderr()) } StandardStreamType::StdoutBuffered => { let wtr = io::BufWriter::new(io::stdout()); IoStandardStream::StdoutBuffered(wtr) } StandardStreamType::StderrBuffered => { let wtr = io::BufWriter::new(io::stderr()); IoStandardStream::StderrBuffered(wtr) } } } fn lock(&self) -> IoStandardStreamLock<'_> { match *self { IoStandardStream::Stdout(ref s) => { IoStandardStreamLock::StdoutLock(s.lock()) } IoStandardStream::Stderr(ref s) => { IoStandardStreamLock::StderrLock(s.lock()) } IoStandardStream::StdoutBuffered(_) | IoStandardStream::StderrBuffered(_) => { // We don't permit this case to ever occur in the public API, // so it's OK to panic. panic!("cannot lock a buffered standard stream") } } } } impl io::Write for IoStandardStream { #[inline(always)] fn write(&mut self, b: &[u8]) -> io::Result { match *self { IoStandardStream::Stdout(ref mut s) => s.write(b), IoStandardStream::Stderr(ref mut s) => s.write(b), IoStandardStream::StdoutBuffered(ref mut s) => s.write(b), IoStandardStream::StderrBuffered(ref mut s) => s.write(b), } } #[inline(always)] fn flush(&mut self) -> io::Result<()> { match *self { IoStandardStream::Stdout(ref mut s) => s.flush(), IoStandardStream::Stderr(ref mut s) => s.flush(), IoStandardStream::StdoutBuffered(ref mut s) => s.flush(), IoStandardStream::StderrBuffered(ref mut s) => s.flush(), } } } // Same rigmarole for the locked variants of the standard streams. #[derive(Debug)] enum IoStandardStreamLock<'a> { StdoutLock(io::StdoutLock<'a>), StderrLock(io::StderrLock<'a>), } impl<'a> io::Write for IoStandardStreamLock<'a> { #[inline(always)] fn write(&mut self, b: &[u8]) -> io::Result { match *self { IoStandardStreamLock::StdoutLock(ref mut s) => s.write(b), IoStandardStreamLock::StderrLock(ref mut s) => s.write(b), } } #[inline(always)] fn flush(&mut self) -> io::Result<()> { match *self { IoStandardStreamLock::StdoutLock(ref mut s) => s.flush(), IoStandardStreamLock::StderrLock(ref mut s) => s.flush(), } } } /// Satisfies `io::Write` and `WriteColor`, and supports optional coloring /// to either of the standard output streams, stdout and stderr. #[derive(Debug)] pub struct StandardStream { wtr: LossyStandardStream>, } /// `StandardStreamLock` is a locked reference to a `StandardStream`. /// /// This implements the `io::Write` and `WriteColor` traits, and is constructed /// via the `Write::lock` method. /// /// The lifetime `'a` refers to the lifetime of the corresponding /// `StandardStream`. #[derive(Debug)] pub struct StandardStreamLock<'a> { wtr: LossyStandardStream>>, } /// Like `StandardStream`, but does buffered writing. #[derive(Debug)] pub struct BufferedStandardStream { wtr: LossyStandardStream>, } /// WriterInner is a (limited) generic representation of a writer. It is /// limited because W should only ever be stdout/stderr on Windows. #[derive(Debug)] enum WriterInner { NoColor(NoColor), Ansi(Ansi), #[cfg(windows)] Windows { wtr: W, console: Mutex, }, } /// WriterInnerLock is a (limited) generic representation of a writer. It is /// limited because W should only ever be stdout/stderr on Windows. #[derive(Debug)] enum WriterInnerLock<'a, W> { NoColor(NoColor), Ansi(Ansi), /// What a gross hack. On Windows, we need to specify a lifetime for the /// console when in a locked state, but obviously don't need to do that /// on Unix, which makes the `'a` unused. To satisfy the compiler, we need /// a PhantomData. #[allow(dead_code)] Unreachable(::std::marker::PhantomData<&'a ()>), #[cfg(windows)] Windows { wtr: W, console: MutexGuard<'a, wincon::Console>, }, } impl StandardStream { /// Create a new `StandardStream` with the given color preferences that /// writes to standard output. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing via /// the `WriteColor` trait. pub fn stdout(choice: ColorChoice) -> StandardStream { let wtr = WriterInner::create(StandardStreamType::Stdout, choice); StandardStream { wtr: LossyStandardStream::new(wtr) } } /// Create a new `StandardStream` with the given color preferences that /// writes to standard error. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing via /// the `WriteColor` trait. pub fn stderr(choice: ColorChoice) -> StandardStream { let wtr = WriterInner::create(StandardStreamType::Stderr, choice); StandardStream { wtr: LossyStandardStream::new(wtr) } } /// Lock the underlying writer. /// /// The lock guard returned also satisfies `io::Write` and /// `WriteColor`. /// /// This method is **not reentrant**. It may panic if `lock` is called /// while a `StandardStreamLock` is still alive. pub fn lock(&self) -> StandardStreamLock<'_> { StandardStreamLock::from_stream(self) } } impl<'a> StandardStreamLock<'a> { #[cfg(not(windows))] fn from_stream(stream: &StandardStream) -> StandardStreamLock<'_> { let locked = match *stream.wtr.get_ref() { WriterInner::NoColor(ref w) => { WriterInnerLock::NoColor(NoColor(w.0.lock())) } WriterInner::Ansi(ref w) => { WriterInnerLock::Ansi(Ansi(w.0.lock())) } }; StandardStreamLock { wtr: stream.wtr.wrap(locked) } } #[cfg(windows)] fn from_stream(stream: &StandardStream) -> StandardStreamLock { let locked = match *stream.wtr.get_ref() { WriterInner::NoColor(ref w) => { WriterInnerLock::NoColor(NoColor(w.0.lock())) } WriterInner::Ansi(ref w) => { WriterInnerLock::Ansi(Ansi(w.0.lock())) } #[cfg(windows)] WriterInner::Windows { ref wtr, ref console } => { WriterInnerLock::Windows { wtr: wtr.lock(), console: console.lock().unwrap(), } } }; StandardStreamLock { wtr: stream.wtr.wrap(locked) } } } impl BufferedStandardStream { /// Create a new `BufferedStandardStream` with the given color preferences /// that writes to standard output via a buffered writer. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing via /// the `WriteColor` trait. pub fn stdout(choice: ColorChoice) -> BufferedStandardStream { let wtr = WriterInner::create(StandardStreamType::StdoutBuffered, choice); BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } } /// Create a new `BufferedStandardStream` with the given color preferences /// that writes to standard error via a buffered writer. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing via /// the `WriteColor` trait. pub fn stderr(choice: ColorChoice) -> BufferedStandardStream { let wtr = WriterInner::create(StandardStreamType::StderrBuffered, choice); BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } } } impl WriterInner { /// Create a new inner writer for a standard stream with the given color /// preferences. #[cfg(not(windows))] fn create( sty: StandardStreamType, choice: ColorChoice, ) -> WriterInner { if choice.should_attempt_color() { WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) } else { WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) } } /// Create a new inner writer for a standard stream with the given color /// preferences. /// /// If coloring is desired and a Windows console could not be found, then /// ANSI escape sequences are used instead. #[cfg(windows)] fn create( sty: StandardStreamType, choice: ColorChoice, ) -> WriterInner { let mut con = match sty { StandardStreamType::Stdout => wincon::Console::stdout(), StandardStreamType::Stderr => wincon::Console::stderr(), StandardStreamType::StdoutBuffered => wincon::Console::stdout(), StandardStreamType::StderrBuffered => wincon::Console::stderr(), }; let is_console_virtual = con .as_mut() .map(|con| con.set_virtual_terminal_processing(true).is_ok()) .unwrap_or(false); if choice.should_attempt_color() { if choice.should_ansi() || is_console_virtual { WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) } else if let Ok(console) = con { WriterInner::Windows { wtr: IoStandardStream::new(sty), console: Mutex::new(console), } } else { WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) } } else { WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) } } } impl io::Write for StandardStream { #[inline] fn write(&mut self, b: &[u8]) -> io::Result { self.wtr.write(b) } #[inline] fn flush(&mut self) -> io::Result<()> { self.wtr.flush() } } impl WriteColor for StandardStream { #[inline] fn supports_color(&self) -> bool { self.wtr.supports_color() } #[inline] fn supports_hyperlinks(&self) -> bool { self.wtr.supports_hyperlinks() } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { self.wtr.set_color(spec) } #[inline] fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { self.wtr.set_hyperlink(link) } #[inline] fn reset(&mut self) -> io::Result<()> { self.wtr.reset() } #[inline] fn is_synchronous(&self) -> bool { self.wtr.is_synchronous() } } impl<'a> io::Write for StandardStreamLock<'a> { #[inline] fn write(&mut self, b: &[u8]) -> io::Result { self.wtr.write(b) } #[inline] fn flush(&mut self) -> io::Result<()> { self.wtr.flush() } } impl<'a> WriteColor for StandardStreamLock<'a> { #[inline] fn supports_color(&self) -> bool { self.wtr.supports_color() } #[inline] fn supports_hyperlinks(&self) -> bool { self.wtr.supports_hyperlinks() } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { self.wtr.set_color(spec) } #[inline] fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { self.wtr.set_hyperlink(link) } #[inline] fn reset(&mut self) -> io::Result<()> { self.wtr.reset() } #[inline] fn is_synchronous(&self) -> bool { self.wtr.is_synchronous() } } impl io::Write for BufferedStandardStream { #[inline] fn write(&mut self, b: &[u8]) -> io::Result { self.wtr.write(b) } #[inline] fn flush(&mut self) -> io::Result<()> { self.wtr.flush() } } impl WriteColor for BufferedStandardStream { #[inline] fn supports_color(&self) -> bool { self.wtr.supports_color() } #[inline] fn supports_hyperlinks(&self) -> bool { self.wtr.supports_hyperlinks() } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { if self.is_synchronous() { self.wtr.flush()?; } self.wtr.set_color(spec) } #[inline] fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { if self.is_synchronous() { self.wtr.flush()?; } self.wtr.set_hyperlink(link) } #[inline] fn reset(&mut self) -> io::Result<()> { self.wtr.reset() } #[inline] fn is_synchronous(&self) -> bool { self.wtr.is_synchronous() } } impl io::Write for WriterInner { #[inline(always)] fn write(&mut self, buf: &[u8]) -> io::Result { match *self { WriterInner::NoColor(ref mut wtr) => wtr.write(buf), WriterInner::Ansi(ref mut wtr) => wtr.write(buf), #[cfg(windows)] WriterInner::Windows { ref mut wtr, .. } => wtr.write(buf), } } #[inline(always)] fn flush(&mut self) -> io::Result<()> { match *self { WriterInner::NoColor(ref mut wtr) => wtr.flush(), WriterInner::Ansi(ref mut wtr) => wtr.flush(), #[cfg(windows)] WriterInner::Windows { ref mut wtr, .. } => wtr.flush(), } } } impl WriteColor for WriterInner { fn supports_color(&self) -> bool { match *self { WriterInner::NoColor(_) => false, WriterInner::Ansi(_) => true, #[cfg(windows)] WriterInner::Windows { .. } => true, } } fn supports_hyperlinks(&self) -> bool { match *self { WriterInner::NoColor(_) => false, WriterInner::Ansi(_) => true, #[cfg(windows)] WriterInner::Windows { .. } => false, } } fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { match *self { WriterInner::NoColor(ref mut wtr) => wtr.set_color(spec), WriterInner::Ansi(ref mut wtr) => wtr.set_color(spec), #[cfg(windows)] WriterInner::Windows { ref mut wtr, ref console } => { wtr.flush()?; let mut console = console.lock().unwrap(); spec.write_console(&mut *console) } } } fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { match *self { WriterInner::NoColor(ref mut wtr) => wtr.set_hyperlink(link), WriterInner::Ansi(ref mut wtr) => wtr.set_hyperlink(link), #[cfg(windows)] WriterInner::Windows { .. } => Ok(()), } } fn reset(&mut self) -> io::Result<()> { match *self { WriterInner::NoColor(ref mut wtr) => wtr.reset(), WriterInner::Ansi(ref mut wtr) => wtr.reset(), #[cfg(windows)] WriterInner::Windows { ref mut wtr, ref mut console } => { wtr.flush()?; console.lock().unwrap().reset()?; Ok(()) } } } fn is_synchronous(&self) -> bool { match *self { WriterInner::NoColor(_) => false, WriterInner::Ansi(_) => false, #[cfg(windows)] WriterInner::Windows { .. } => true, } } } impl<'a, W: io::Write> io::Write for WriterInnerLock<'a, W> { fn write(&mut self, buf: &[u8]) -> io::Result { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(ref mut wtr) => wtr.write(buf), WriterInnerLock::Ansi(ref mut wtr) => wtr.write(buf), #[cfg(windows)] WriterInnerLock::Windows { ref mut wtr, .. } => wtr.write(buf), } } fn flush(&mut self) -> io::Result<()> { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(ref mut wtr) => wtr.flush(), WriterInnerLock::Ansi(ref mut wtr) => wtr.flush(), #[cfg(windows)] WriterInnerLock::Windows { ref mut wtr, .. } => wtr.flush(), } } } impl<'a, W: io::Write> WriteColor for WriterInnerLock<'a, W> { fn supports_color(&self) -> bool { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(_) => false, WriterInnerLock::Ansi(_) => true, #[cfg(windows)] WriterInnerLock::Windows { .. } => true, } } fn supports_hyperlinks(&self) -> bool { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(_) => false, WriterInnerLock::Ansi(_) => true, #[cfg(windows)] WriterInnerLock::Windows { .. } => false, } } fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(ref mut wtr) => wtr.set_color(spec), WriterInnerLock::Ansi(ref mut wtr) => wtr.set_color(spec), #[cfg(windows)] WriterInnerLock::Windows { ref mut wtr, ref mut console } => { wtr.flush()?; spec.write_console(console) } } } fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(ref mut wtr) => wtr.set_hyperlink(link), WriterInnerLock::Ansi(ref mut wtr) => wtr.set_hyperlink(link), #[cfg(windows)] WriterInnerLock::Windows { .. } => Ok(()), } } fn reset(&mut self) -> io::Result<()> { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(ref mut wtr) => wtr.reset(), WriterInnerLock::Ansi(ref mut wtr) => wtr.reset(), #[cfg(windows)] WriterInnerLock::Windows { ref mut wtr, ref mut console } => { wtr.flush()?; console.reset()?; Ok(()) } } } fn is_synchronous(&self) -> bool { match *self { WriterInnerLock::Unreachable(_) => unreachable!(), WriterInnerLock::NoColor(_) => false, WriterInnerLock::Ansi(_) => false, #[cfg(windows)] WriterInnerLock::Windows { .. } => true, } } } /// Writes colored buffers to stdout or stderr. /// /// Writable buffers can be obtained by calling `buffer` on a `BufferWriter`. /// /// This writer works with terminals that support ANSI escape sequences or /// with a Windows console. /// /// It is intended for a `BufferWriter` to be put in an `Arc` and written to /// from multiple threads simultaneously. #[derive(Debug)] pub struct BufferWriter { stream: LossyStandardStream, printed: AtomicBool, separator: Option>, color_choice: ColorChoice, #[cfg(windows)] console: Option>, } impl BufferWriter { /// Create a new `BufferWriter` that writes to a standard stream with the /// given color preferences. /// /// The specific color/style settings can be configured when writing to /// the buffers themselves. #[cfg(not(windows))] fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { BufferWriter { stream: LossyStandardStream::new(IoStandardStream::new(sty)), printed: AtomicBool::new(false), separator: None, color_choice: choice, } } /// Create a new `BufferWriter` that writes to a standard stream with the /// given color preferences. /// /// If coloring is desired and a Windows console could not be found, then /// ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing to /// the buffers themselves. #[cfg(windows)] fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { let mut con = match sty { StandardStreamType::Stdout => wincon::Console::stdout(), StandardStreamType::Stderr => wincon::Console::stderr(), StandardStreamType::StdoutBuffered => wincon::Console::stdout(), StandardStreamType::StderrBuffered => wincon::Console::stderr(), } .ok(); let is_console_virtual = con .as_mut() .map(|con| con.set_virtual_terminal_processing(true).is_ok()) .unwrap_or(false); // If we can enable ANSI on Windows, then we don't need the console // anymore. if is_console_virtual { con = None; } let stream = LossyStandardStream::new(IoStandardStream::new(sty)); BufferWriter { stream, printed: AtomicBool::new(false), separator: None, color_choice: choice, console: con.map(Mutex::new), } } /// Create a new `BufferWriter` that writes to stdout with the given /// color preferences. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing to /// the buffers themselves. pub fn stdout(choice: ColorChoice) -> BufferWriter { BufferWriter::create(StandardStreamType::Stdout, choice) } /// Create a new `BufferWriter` that writes to stderr with the given /// color preferences. /// /// On Windows, if coloring is desired and a Windows console could not be /// found, then ANSI escape sequences are used instead. /// /// The specific color/style settings can be configured when writing to /// the buffers themselves. pub fn stderr(choice: ColorChoice) -> BufferWriter { BufferWriter::create(StandardStreamType::Stderr, choice) } /// If set, the separator given is printed between buffers. By default, no /// separator is printed. /// /// The default value is `None`. pub fn separator(&mut self, sep: Option>) { self.separator = sep; } /// Creates a new `Buffer` with the current color preferences. /// /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can /// be printed using the `print` method. #[cfg(not(windows))] pub fn buffer(&self) -> Buffer { Buffer::new(self.color_choice) } /// Creates a new `Buffer` with the current color preferences. /// /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can /// be printed using the `print` method. #[cfg(windows)] pub fn buffer(&self) -> Buffer { Buffer::new(self.color_choice, self.console.is_some()) } /// Prints the contents of the given buffer. /// /// It is safe to call this from multiple threads simultaneously. In /// particular, all buffers are written atomically. No interleaving will /// occur. pub fn print(&self, buf: &Buffer) -> io::Result<()> { if buf.is_empty() { return Ok(()); } let mut stream = self.stream.wrap(self.stream.get_ref().lock()); if let Some(ref sep) = self.separator { if self.printed.load(Ordering::Relaxed) { stream.write_all(sep)?; stream.write_all(b"\n")?; } } match buf.0 { BufferInner::NoColor(ref b) => stream.write_all(&b.0)?, BufferInner::Ansi(ref b) => stream.write_all(&b.0)?, #[cfg(windows)] BufferInner::Windows(ref b) => { // We guarantee by construction that we have a console here. // Namely, a BufferWriter is the only way to produce a Buffer. let console_mutex = self .console .as_ref() .expect("got Windows buffer but have no Console"); let mut console = console_mutex.lock().unwrap(); b.print(&mut *console, &mut stream)?; } } self.printed.store(true, Ordering::Relaxed); Ok(()) } } /// Write colored text to memory. /// /// `Buffer` is a platform independent abstraction for printing colored text to /// an in memory buffer. When the buffer is printed using a `BufferWriter`, the /// color information will be applied to the output device (a tty on Unix and a /// console on Windows). /// /// A `Buffer` is typically created by calling the `BufferWriter.buffer` /// method, which will take color preferences and the environment into /// account. However, buffers can also be manually created using `no_color`, /// `ansi` or `console` (on Windows). #[derive(Clone, Debug)] pub struct Buffer(BufferInner); /// BufferInner is an enumeration of different buffer types. #[derive(Clone, Debug)] enum BufferInner { /// No coloring information should be applied. This ignores all coloring /// directives. NoColor(NoColor>), /// Apply coloring using ANSI escape sequences embedded into the buffer. Ansi(Ansi>), /// Apply coloring using the Windows console APIs. This buffer saves /// color information in memory and only interacts with the console when /// the buffer is printed. #[cfg(windows)] Windows(WindowsBuffer), } impl Buffer { /// Create a new buffer with the given color settings. #[cfg(not(windows))] fn new(choice: ColorChoice) -> Buffer { if choice.should_attempt_color() { Buffer::ansi() } else { Buffer::no_color() } } /// Create a new buffer with the given color settings. /// /// On Windows, one can elect to create a buffer capable of being written /// to a console. Only enable it if a console is available. /// /// If coloring is desired and `console` is false, then ANSI escape /// sequences are used instead. #[cfg(windows)] fn new(choice: ColorChoice, console: bool) -> Buffer { if choice.should_attempt_color() { if !console || choice.should_ansi() { Buffer::ansi() } else { Buffer::console() } } else { Buffer::no_color() } } /// Create a buffer that drops all color information. pub fn no_color() -> Buffer { Buffer(BufferInner::NoColor(NoColor(vec![]))) } /// Create a buffer that uses ANSI escape sequences. pub fn ansi() -> Buffer { Buffer(BufferInner::Ansi(Ansi(vec![]))) } /// Create a buffer that can be written to a Windows console. #[cfg(windows)] pub fn console() -> Buffer { Buffer(BufferInner::Windows(WindowsBuffer::new())) } /// Returns true if and only if this buffer is empty. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns the length of this buffer in bytes. pub fn len(&self) -> usize { match self.0 { BufferInner::NoColor(ref b) => b.0.len(), BufferInner::Ansi(ref b) => b.0.len(), #[cfg(windows)] BufferInner::Windows(ref b) => b.buf.len(), } } /// Clears this buffer. pub fn clear(&mut self) { match self.0 { BufferInner::NoColor(ref mut b) => b.0.clear(), BufferInner::Ansi(ref mut b) => b.0.clear(), #[cfg(windows)] BufferInner::Windows(ref mut b) => b.clear(), } } /// Consume this buffer and return the underlying raw data. /// /// On Windows, this unrecoverably drops all color information associated /// with the buffer. pub fn into_inner(self) -> Vec { match self.0 { BufferInner::NoColor(b) => b.0, BufferInner::Ansi(b) => b.0, #[cfg(windows)] BufferInner::Windows(b) => b.buf, } } /// Return the underlying data of the buffer. pub fn as_slice(&self) -> &[u8] { match self.0 { BufferInner::NoColor(ref b) => &b.0, BufferInner::Ansi(ref b) => &b.0, #[cfg(windows)] BufferInner::Windows(ref b) => &b.buf, } } /// Return the underlying data of the buffer as a mutable slice. pub fn as_mut_slice(&mut self) -> &mut [u8] { match self.0 { BufferInner::NoColor(ref mut b) => &mut b.0, BufferInner::Ansi(ref mut b) => &mut b.0, #[cfg(windows)] BufferInner::Windows(ref mut b) => &mut b.buf, } } } impl io::Write for Buffer { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { match self.0 { BufferInner::NoColor(ref mut w) => w.write(buf), BufferInner::Ansi(ref mut w) => w.write(buf), #[cfg(windows)] BufferInner::Windows(ref mut w) => w.write(buf), } } #[inline] fn flush(&mut self) -> io::Result<()> { match self.0 { BufferInner::NoColor(ref mut w) => w.flush(), BufferInner::Ansi(ref mut w) => w.flush(), #[cfg(windows)] BufferInner::Windows(ref mut w) => w.flush(), } } } impl WriteColor for Buffer { #[inline] fn supports_color(&self) -> bool { match self.0 { BufferInner::NoColor(_) => false, BufferInner::Ansi(_) => true, #[cfg(windows)] BufferInner::Windows(_) => true, } } #[inline] fn supports_hyperlinks(&self) -> bool { match self.0 { BufferInner::NoColor(_) => false, BufferInner::Ansi(_) => true, #[cfg(windows)] BufferInner::Windows(_) => false, } } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { match self.0 { BufferInner::NoColor(ref mut w) => w.set_color(spec), BufferInner::Ansi(ref mut w) => w.set_color(spec), #[cfg(windows)] BufferInner::Windows(ref mut w) => w.set_color(spec), } } #[inline] fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { match self.0 { BufferInner::NoColor(ref mut w) => w.set_hyperlink(link), BufferInner::Ansi(ref mut w) => w.set_hyperlink(link), #[cfg(windows)] BufferInner::Windows(ref mut w) => w.set_hyperlink(link), } } #[inline] fn reset(&mut self) -> io::Result<()> { match self.0 { BufferInner::NoColor(ref mut w) => w.reset(), BufferInner::Ansi(ref mut w) => w.reset(), #[cfg(windows)] BufferInner::Windows(ref mut w) => w.reset(), } } #[inline] fn is_synchronous(&self) -> bool { false } } /// Satisfies `WriteColor` but ignores all color options. #[derive(Clone, Debug)] pub struct NoColor(W); impl NoColor { /// Create a new writer that satisfies `WriteColor` but drops all color /// information. pub fn new(wtr: W) -> NoColor { NoColor(wtr) } /// Consume this `NoColor` value and return the inner writer. pub fn into_inner(self) -> W { self.0 } /// Return a reference to the inner writer. pub fn get_ref(&self) -> &W { &self.0 } /// Return a mutable reference to the inner writer. pub fn get_mut(&mut self) -> &mut W { &mut self.0 } } impl io::Write for NoColor { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } #[inline] fn flush(&mut self) -> io::Result<()> { self.0.flush() } } impl WriteColor for NoColor { #[inline] fn supports_color(&self) -> bool { false } #[inline] fn supports_hyperlinks(&self) -> bool { false } #[inline] fn set_color(&mut self, _: &ColorSpec) -> io::Result<()> { Ok(()) } #[inline] fn set_hyperlink(&mut self, _: &HyperlinkSpec) -> io::Result<()> { Ok(()) } #[inline] fn reset(&mut self) -> io::Result<()> { Ok(()) } #[inline] fn is_synchronous(&self) -> bool { false } } /// Satisfies `WriteColor` using standard ANSI escape sequences. #[derive(Clone, Debug)] pub struct Ansi(W); impl Ansi { /// Create a new writer that satisfies `WriteColor` using standard ANSI /// escape sequences. pub fn new(wtr: W) -> Ansi { Ansi(wtr) } /// Consume this `Ansi` value and return the inner writer. pub fn into_inner(self) -> W { self.0 } /// Return a reference to the inner writer. pub fn get_ref(&self) -> &W { &self.0 } /// Return a mutable reference to the inner writer. pub fn get_mut(&mut self) -> &mut W { &mut self.0 } } impl io::Write for Ansi { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } // Adding this method here is not required because it has a default impl, // but it seems to provide a perf improvement in some cases when using // a `BufWriter` with lots of writes. // // See https://github.com/BurntSushi/termcolor/pull/56 for more details // and a minimized example. #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.0.write_all(buf) } #[inline] fn flush(&mut self) -> io::Result<()> { self.0.flush() } } impl WriteColor for Ansi { #[inline] fn supports_color(&self) -> bool { true } #[inline] fn supports_hyperlinks(&self) -> bool { true } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { if spec.reset { self.reset()?; } if spec.bold { self.write_str("\x1B[1m")?; } if spec.dimmed { self.write_str("\x1B[2m")?; } if spec.italic { self.write_str("\x1B[3m")?; } if spec.underline { self.write_str("\x1B[4m")?; } if spec.strikethrough { self.write_str("\x1B[9m")?; } if let Some(ref c) = spec.fg_color { self.write_color(true, c, spec.intense)?; } if let Some(ref c) = spec.bg_color { self.write_color(false, c, spec.intense)?; } Ok(()) } #[inline] fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { self.write_str("\x1B]8;;")?; if let Some(uri) = link.uri() { self.write_all(uri)?; } self.write_str("\x1B\\") } #[inline] fn reset(&mut self) -> io::Result<()> { self.write_str("\x1B[0m") } #[inline] fn is_synchronous(&self) -> bool { false } } impl Ansi { fn write_str(&mut self, s: &str) -> io::Result<()> { self.write_all(s.as_bytes()) } fn write_color( &mut self, fg: bool, c: &Color, intense: bool, ) -> io::Result<()> { macro_rules! write_intense { ($clr:expr) => { if fg { self.write_str(concat!("\x1B[38;5;", $clr, "m")) } else { self.write_str(concat!("\x1B[48;5;", $clr, "m")) } }; } macro_rules! write_normal { ($clr:expr) => { if fg { self.write_str(concat!("\x1B[3", $clr, "m")) } else { self.write_str(concat!("\x1B[4", $clr, "m")) } }; } macro_rules! write_var_ansi_code { ($pre:expr, $($code:expr),+) => {{ // The loop generates at worst a literal of the form // '255,255,255m' which is 12-bytes. // The largest `pre` expression we currently use is 7 bytes. // This gives us the maximum of 19-bytes for our work buffer. let pre_len = $pre.len(); assert!(pre_len <= 7); let mut fmt = [0u8; 19]; fmt[..pre_len].copy_from_slice($pre); let mut i = pre_len - 1; $( let c1: u8 = ($code / 100) % 10; let c2: u8 = ($code / 10) % 10; let c3: u8 = $code % 10; let mut printed = false; if c1 != 0 { printed = true; i += 1; fmt[i] = b'0' + c1; } if c2 != 0 || printed { i += 1; fmt[i] = b'0' + c2; } // If we received a zero value we must still print a value. i += 1; fmt[i] = b'0' + c3; i += 1; fmt[i] = b';'; )+ fmt[i] = b'm'; self.write_all(&fmt[0..i+1]) }} } macro_rules! write_custom { ($ansi256:expr) => { if fg { write_var_ansi_code!(b"\x1B[38;5;", $ansi256) } else { write_var_ansi_code!(b"\x1B[48;5;", $ansi256) } }; ($r:expr, $g:expr, $b:expr) => {{ if fg { write_var_ansi_code!(b"\x1B[38;2;", $r, $g, $b) } else { write_var_ansi_code!(b"\x1B[48;2;", $r, $g, $b) } }}; } if intense { match *c { Color::Black => write_intense!("8"), Color::Blue => write_intense!("12"), Color::Green => write_intense!("10"), Color::Red => write_intense!("9"), Color::Cyan => write_intense!("14"), Color::Magenta => write_intense!("13"), Color::Yellow => write_intense!("11"), Color::White => write_intense!("15"), Color::Ansi256(c) => write_custom!(c), Color::Rgb(r, g, b) => write_custom!(r, g, b), Color::__Nonexhaustive => unreachable!(), } } else { match *c { Color::Black => write_normal!("0"), Color::Blue => write_normal!("4"), Color::Green => write_normal!("2"), Color::Red => write_normal!("1"), Color::Cyan => write_normal!("6"), Color::Magenta => write_normal!("5"), Color::Yellow => write_normal!("3"), Color::White => write_normal!("7"), Color::Ansi256(c) => write_custom!(c), Color::Rgb(r, g, b) => write_custom!(r, g, b), Color::__Nonexhaustive => unreachable!(), } } } } impl WriteColor for io::Sink { fn supports_color(&self) -> bool { false } fn supports_hyperlinks(&self) -> bool { false } fn set_color(&mut self, _: &ColorSpec) -> io::Result<()> { Ok(()) } fn set_hyperlink(&mut self, _: &HyperlinkSpec) -> io::Result<()> { Ok(()) } fn reset(&mut self) -> io::Result<()> { Ok(()) } } /// An in-memory buffer that provides Windows console coloring. /// /// This doesn't actually communicate with the Windows console. Instead, it /// acts like a normal buffer but also saves the color information associated /// with positions in the buffer. It is only when the buffer is written to the /// console that coloring is actually applied. /// /// This is roughly isomorphic to the ANSI based approach (i.e., /// `Ansi>`), except with ANSI, the color information is embedded /// directly into the buffer. /// /// Note that there is no way to write something generic like /// `WindowsConsole` since coloring on Windows is tied /// specifically to the console APIs, and therefore can't work on arbitrary /// writers. #[cfg(windows)] #[derive(Clone, Debug)] struct WindowsBuffer { /// The actual content that should be printed. buf: Vec, /// A sequence of position oriented color specifications. Namely, each /// element is a position and a color spec, where the color spec should /// be applied at the position inside of `buf`. /// /// A missing color spec implies the underlying console should be reset. colors: Vec<(usize, Option)>, } #[cfg(windows)] impl WindowsBuffer { /// Create a new empty buffer for Windows console coloring. fn new() -> WindowsBuffer { WindowsBuffer { buf: vec![], colors: vec![] } } /// Push the given color specification into this buffer. /// /// This has the effect of setting the given color information at the /// current position in the buffer. fn push(&mut self, spec: Option) { let pos = self.buf.len(); self.colors.push((pos, spec)); } /// Print the contents to the given stream handle, and use the console /// for coloring. fn print( &self, console: &mut wincon::Console, stream: &mut LossyStandardStream, ) -> io::Result<()> { let mut last = 0; for &(pos, ref spec) in &self.colors { stream.write_all(&self.buf[last..pos])?; stream.flush()?; last = pos; match *spec { None => console.reset()?, Some(ref spec) => spec.write_console(console)?, } } stream.write_all(&self.buf[last..])?; stream.flush() } /// Clear the buffer. fn clear(&mut self) { self.buf.clear(); self.colors.clear(); } } #[cfg(windows)] impl io::Write for WindowsBuffer { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.extend_from_slice(buf); Ok(buf.len()) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } } #[cfg(windows)] impl WriteColor for WindowsBuffer { #[inline] fn supports_color(&self) -> bool { true } #[inline] fn supports_hyperlinks(&self) -> bool { false } #[inline] fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { self.push(Some(spec.clone())); Ok(()) } #[inline] fn set_hyperlink(&mut self, _: &HyperlinkSpec) -> io::Result<()> { Ok(()) } #[inline] fn reset(&mut self) -> io::Result<()> { self.push(None); Ok(()) } #[inline] fn is_synchronous(&self) -> bool { false } } /// A color specification. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ColorSpec { fg_color: Option, bg_color: Option, bold: bool, intense: bool, underline: bool, dimmed: bool, italic: bool, reset: bool, strikethrough: bool, } impl Default for ColorSpec { fn default() -> ColorSpec { ColorSpec { fg_color: None, bg_color: None, bold: false, intense: false, underline: false, dimmed: false, italic: false, reset: true, strikethrough: false, } } } impl ColorSpec { /// Create a new color specification that has no colors or styles. pub fn new() -> ColorSpec { ColorSpec::default() } /// Get the foreground color. pub fn fg(&self) -> Option<&Color> { self.fg_color.as_ref() } /// Set the foreground color. pub fn set_fg(&mut self, color: Option) -> &mut ColorSpec { self.fg_color = color; self } /// Get the background color. pub fn bg(&self) -> Option<&Color> { self.bg_color.as_ref() } /// Set the background color. pub fn set_bg(&mut self, color: Option) -> &mut ColorSpec { self.bg_color = color; self } /// Get whether this is bold or not. /// /// Note that the bold setting has no effect in a Windows console. pub fn bold(&self) -> bool { self.bold } /// Set whether the text is bolded or not. /// /// Note that the bold setting has no effect in a Windows console. pub fn set_bold(&mut self, yes: bool) -> &mut ColorSpec { self.bold = yes; self } /// Get whether this is dimmed or not. /// /// Note that the dimmed setting has no effect in a Windows console. pub fn dimmed(&self) -> bool { self.dimmed } /// Set whether the text is dimmed or not. /// /// Note that the dimmed setting has no effect in a Windows console. pub fn set_dimmed(&mut self, yes: bool) -> &mut ColorSpec { self.dimmed = yes; self } /// Get whether this is italic or not. /// /// Note that the italic setting has no effect in a Windows console. pub fn italic(&self) -> bool { self.italic } /// Set whether the text is italicized or not. /// /// Note that the italic setting has no effect in a Windows console. pub fn set_italic(&mut self, yes: bool) -> &mut ColorSpec { self.italic = yes; self } /// Get whether this is underline or not. /// /// Note that the underline setting has no effect in a Windows console. pub fn underline(&self) -> bool { self.underline } /// Set whether the text is underlined or not. /// /// Note that the underline setting has no effect in a Windows console. pub fn set_underline(&mut self, yes: bool) -> &mut ColorSpec { self.underline = yes; self } /// Get whether this is strikethrough or not. /// /// Note that the strikethrough setting has no effect in a Windows console. pub fn strikethrough(&self) -> bool { self.strikethrough } /// Set whether the text is strikethrough or not. /// /// Note that the strikethrough setting has no effect in a Windows console. pub fn set_strikethrough(&mut self, yes: bool) -> &mut ColorSpec { self.strikethrough = yes; self } /// Get whether reset is enabled or not. /// /// reset is enabled by default. When disabled and using ANSI escape /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s /// settings are applied. /// /// Note that the reset setting has no effect in a Windows console. pub fn reset(&self) -> bool { self.reset } /// Set whether to reset the terminal whenever color settings are applied. /// /// reset is enabled by default. When disabled and using ANSI escape /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s /// settings are applied. /// /// Typically this is useful if callers have a requirement to more /// scrupulously manage the exact sequence of escape codes that are emitted /// when using ANSI for colors. /// /// Note that the reset setting has no effect in a Windows console. pub fn set_reset(&mut self, yes: bool) -> &mut ColorSpec { self.reset = yes; self } /// Get whether this is intense or not. /// /// On Unix-like systems, this will output the ANSI escape sequence /// that will print a high-intensity version of the color /// specified. /// /// On Windows systems, this will output the ANSI escape sequence /// that will print a brighter version of the color specified. pub fn intense(&self) -> bool { self.intense } /// Set whether the text is intense or not. /// /// On Unix-like systems, this will output the ANSI escape sequence /// that will print a high-intensity version of the color /// specified. /// /// On Windows systems, this will output the ANSI escape sequence /// that will print a brighter version of the color specified. pub fn set_intense(&mut self, yes: bool) -> &mut ColorSpec { self.intense = yes; self } /// Returns true if this color specification has no colors or styles. pub fn is_none(&self) -> bool { self.fg_color.is_none() && self.bg_color.is_none() && !self.bold && !self.underline && !self.dimmed && !self.italic && !self.intense && !self.strikethrough } /// Clears this color specification so that it has no color/style settings. pub fn clear(&mut self) { self.fg_color = None; self.bg_color = None; self.bold = false; self.underline = false; self.intense = false; self.dimmed = false; self.italic = false; self.strikethrough = false; } /// Writes this color spec to the given Windows console. #[cfg(windows)] fn write_console(&self, console: &mut wincon::Console) -> io::Result<()> { let fg_color = self.fg_color.and_then(|c| c.to_windows(self.intense)); if let Some((intense, color)) = fg_color { console.fg(intense, color)?; } let bg_color = self.bg_color.and_then(|c| c.to_windows(self.intense)); if let Some((intense, color)) = bg_color { console.bg(intense, color)?; } Ok(()) } } /// The set of available colors for the terminal foreground/background. /// /// The `Ansi256` and `Rgb` colors will only output the correct codes when /// paired with the `Ansi` `WriteColor` implementation. /// /// The `Ansi256` and `Rgb` color types are not supported when writing colors /// on Windows using the console. If they are used on Windows, then they are /// silently ignored and no colors will be emitted. /// /// This set may expand over time. /// /// This type has a `FromStr` impl that can parse colors from their human /// readable form. The format is as follows: /// /// 1. Any of the explicitly listed colors in English. They are matched /// case insensitively. /// 2. A single 8-bit integer, in either decimal or hexadecimal format. /// 3. A triple of 8-bit integers separated by a comma, where each integer is /// in decimal or hexadecimal format. /// /// Hexadecimal numbers are written with a `0x` prefix. #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Color { Black, Blue, Green, Red, Cyan, Magenta, Yellow, White, Ansi256(u8), Rgb(u8, u8, u8), #[doc(hidden)] __Nonexhaustive, } impl Color { /// Translate this color to a wincon::Color. #[cfg(windows)] fn to_windows( self, intense: bool, ) -> Option<(wincon::Intense, wincon::Color)> { use wincon::Intense::{No, Yes}; let color = match self { Color::Black => wincon::Color::Black, Color::Blue => wincon::Color::Blue, Color::Green => wincon::Color::Green, Color::Red => wincon::Color::Red, Color::Cyan => wincon::Color::Cyan, Color::Magenta => wincon::Color::Magenta, Color::Yellow => wincon::Color::Yellow, Color::White => wincon::Color::White, Color::Ansi256(0) => return Some((No, wincon::Color::Black)), Color::Ansi256(1) => return Some((No, wincon::Color::Red)), Color::Ansi256(2) => return Some((No, wincon::Color::Green)), Color::Ansi256(3) => return Some((No, wincon::Color::Yellow)), Color::Ansi256(4) => return Some((No, wincon::Color::Blue)), Color::Ansi256(5) => return Some((No, wincon::Color::Magenta)), Color::Ansi256(6) => return Some((No, wincon::Color::Cyan)), Color::Ansi256(7) => return Some((No, wincon::Color::White)), Color::Ansi256(8) => return Some((Yes, wincon::Color::Black)), Color::Ansi256(9) => return Some((Yes, wincon::Color::Red)), Color::Ansi256(10) => return Some((Yes, wincon::Color::Green)), Color::Ansi256(11) => return Some((Yes, wincon::Color::Yellow)), Color::Ansi256(12) => return Some((Yes, wincon::Color::Blue)), Color::Ansi256(13) => return Some((Yes, wincon::Color::Magenta)), Color::Ansi256(14) => return Some((Yes, wincon::Color::Cyan)), Color::Ansi256(15) => return Some((Yes, wincon::Color::White)), Color::Ansi256(_) => return None, Color::Rgb(_, _, _) => return None, Color::__Nonexhaustive => unreachable!(), }; let intense = if intense { Yes } else { No }; Some((intense, color)) } /// Parses a numeric color string, either ANSI or RGB. fn from_str_numeric(s: &str) -> Result { // The "ansi256" format is a single number (decimal or hex) // corresponding to one of 256 colors. // // The "rgb" format is a triple of numbers (decimal or hex) delimited // by a comma corresponding to one of 256^3 colors. fn parse_number(s: &str) -> Option { use std::u8; if s.starts_with("0x") { u8::from_str_radix(&s[2..], 16).ok() } else { u8::from_str_radix(s, 10).ok() } } let codes: Vec<&str> = s.split(',').collect(); if codes.len() == 1 { if let Some(n) = parse_number(&codes[0]) { Ok(Color::Ansi256(n)) } else { if s.chars().all(|c| c.is_digit(16)) { Err(ParseColorError { kind: ParseColorErrorKind::InvalidAnsi256, given: s.to_string(), }) } else { Err(ParseColorError { kind: ParseColorErrorKind::InvalidName, given: s.to_string(), }) } } } else if codes.len() == 3 { let mut v = vec![]; for code in codes { let n = parse_number(code).ok_or_else(|| ParseColorError { kind: ParseColorErrorKind::InvalidRgb, given: s.to_string(), })?; v.push(n); } Ok(Color::Rgb(v[0], v[1], v[2])) } else { Err(if s.contains(",") { ParseColorError { kind: ParseColorErrorKind::InvalidRgb, given: s.to_string(), } } else { ParseColorError { kind: ParseColorErrorKind::InvalidName, given: s.to_string(), } }) } } } /// An error from parsing an invalid color specification. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParseColorError { kind: ParseColorErrorKind, given: String, } #[derive(Clone, Debug, Eq, PartialEq)] enum ParseColorErrorKind { InvalidName, InvalidAnsi256, InvalidRgb, } impl ParseColorError { /// Return the string that couldn't be parsed as a valid color. pub fn invalid(&self) -> &str { &self.given } } impl error::Error for ParseColorError { fn description(&self) -> &str { use self::ParseColorErrorKind::*; match self.kind { InvalidName => "unrecognized color name", InvalidAnsi256 => "invalid ansi256 color number", InvalidRgb => "invalid RGB color triple", } } } impl fmt::Display for ParseColorError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::ParseColorErrorKind::*; match self.kind { InvalidName => write!( f, "unrecognized color name '{}'. Choose from: \ black, blue, green, red, cyan, magenta, yellow, \ white", self.given ), InvalidAnsi256 => write!( f, "unrecognized ansi256 color number, \ should be '[0-255]' (or a hex number), but is '{}'", self.given ), InvalidRgb => write!( f, "unrecognized RGB color triple, \ should be '[0-255],[0-255],[0-255]' (or a hex \ triple), but is '{}'", self.given ), } } } impl FromStr for Color { type Err = ParseColorError; fn from_str(s: &str) -> Result { match &*s.to_lowercase() { "black" => Ok(Color::Black), "blue" => Ok(Color::Blue), "green" => Ok(Color::Green), "red" => Ok(Color::Red), "cyan" => Ok(Color::Cyan), "magenta" => Ok(Color::Magenta), "yellow" => Ok(Color::Yellow), "white" => Ok(Color::White), _ => Color::from_str_numeric(s), } } } /// A hyperlink specification. #[derive(Clone, Debug)] pub struct HyperlinkSpec<'a> { uri: Option<&'a [u8]>, } impl<'a> HyperlinkSpec<'a> { /// Creates a new hyperlink specification. pub fn open(uri: &'a [u8]) -> HyperlinkSpec<'a> { HyperlinkSpec { uri: Some(uri) } } /// Creates a hyperlink specification representing no hyperlink. pub fn close() -> HyperlinkSpec<'a> { HyperlinkSpec { uri: None } } /// Returns the URI of the hyperlink if one is attached to this spec. pub fn uri(&self) -> Option<&'a [u8]> { self.uri } } #[derive(Debug)] struct LossyStandardStream { wtr: W, #[cfg(windows)] is_console: bool, } impl LossyStandardStream { #[cfg(not(windows))] fn new(wtr: W) -> LossyStandardStream { LossyStandardStream { wtr } } #[cfg(windows)] fn new(wtr: W) -> LossyStandardStream { let is_console = wincon::Console::stdout().is_ok() || wincon::Console::stderr().is_ok(); LossyStandardStream { wtr, is_console } } #[cfg(not(windows))] fn wrap(&self, wtr: Q) -> LossyStandardStream { LossyStandardStream::new(wtr) } #[cfg(windows)] fn wrap(&self, wtr: Q) -> LossyStandardStream { LossyStandardStream { wtr, is_console: self.is_console } } fn get_ref(&self) -> &W { &self.wtr } } impl WriteColor for LossyStandardStream { fn supports_color(&self) -> bool { self.wtr.supports_color() } fn supports_hyperlinks(&self) -> bool { self.wtr.supports_hyperlinks() } fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { self.wtr.set_color(spec) } fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> { self.wtr.set_hyperlink(link) } fn reset(&mut self) -> io::Result<()> { self.wtr.reset() } fn is_synchronous(&self) -> bool { self.wtr.is_synchronous() } } impl io::Write for LossyStandardStream { #[cfg(not(windows))] fn write(&mut self, buf: &[u8]) -> io::Result { self.wtr.write(buf) } #[cfg(windows)] fn write(&mut self, buf: &[u8]) -> io::Result { if self.is_console { write_lossy_utf8(&mut self.wtr, buf) } else { self.wtr.write(buf) } } fn flush(&mut self) -> io::Result<()> { self.wtr.flush() } } #[cfg(windows)] fn write_lossy_utf8(mut w: W, buf: &[u8]) -> io::Result { match ::std::str::from_utf8(buf) { Ok(s) => w.write(s.as_bytes()), Err(ref e) if e.valid_up_to() == 0 => { w.write(b"\xEF\xBF\xBD")?; Ok(1) } Err(e) => w.write(&buf[..e.valid_up_to()]), } } #[cfg(test)] mod tests { use super::{ Ansi, Color, ColorSpec, HyperlinkSpec, ParseColorError, ParseColorErrorKind, StandardStream, WriteColor, }; fn assert_is_send() {} #[test] fn standard_stream_is_send() { assert_is_send::(); } #[test] fn test_simple_parse_ok() { let color = "green".parse::(); assert_eq!(color, Ok(Color::Green)); } #[test] fn test_256_parse_ok() { let color = "7".parse::(); assert_eq!(color, Ok(Color::Ansi256(7))); let color = "32".parse::(); assert_eq!(color, Ok(Color::Ansi256(32))); let color = "0xFF".parse::(); assert_eq!(color, Ok(Color::Ansi256(0xFF))); } #[test] fn test_256_parse_err_out_of_range() { let color = "256".parse::(); assert_eq!( color, Err(ParseColorError { kind: ParseColorErrorKind::InvalidAnsi256, given: "256".to_string(), }) ); } #[test] fn test_rgb_parse_ok() { let color = "0,0,0".parse::(); assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); let color = "0,128,255".parse::(); assert_eq!(color, Ok(Color::Rgb(0, 128, 255))); let color = "0x0,0x0,0x0".parse::(); assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); let color = "0x33,0x66,0xFF".parse::(); assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF))); } #[test] fn test_rgb_parse_err_out_of_range() { let color = "0,0,256".parse::(); assert_eq!( color, Err(ParseColorError { kind: ParseColorErrorKind::InvalidRgb, given: "0,0,256".to_string(), }) ); } #[test] fn test_rgb_parse_err_bad_format() { let color = "0,0".parse::(); assert_eq!( color, Err(ParseColorError { kind: ParseColorErrorKind::InvalidRgb, given: "0,0".to_string(), }) ); let color = "not_a_color".parse::(); assert_eq!( color, Err(ParseColorError { kind: ParseColorErrorKind::InvalidName, given: "not_a_color".to_string(), }) ); } #[test] fn test_var_ansi_write_rgb() { let mut buf = Ansi::new(vec![]); let _ = buf.write_color(true, &Color::Rgb(254, 253, 255), false); assert_eq!(buf.0, b"\x1B[38;2;254;253;255m"); } #[test] fn test_reset() { let spec = ColorSpec::new(); let mut buf = Ansi::new(vec![]); buf.set_color(&spec).unwrap(); assert_eq!(buf.0, b"\x1B[0m"); } #[test] fn test_no_reset() { let mut spec = ColorSpec::new(); spec.set_reset(false); let mut buf = Ansi::new(vec![]); buf.set_color(&spec).unwrap(); assert_eq!(buf.0, b""); } #[test] fn test_var_ansi_write_256() { let mut buf = Ansi::new(vec![]); let _ = buf.write_color(false, &Color::Ansi256(7), false); assert_eq!(buf.0, b"\x1B[48;5;7m"); let mut buf = Ansi::new(vec![]); let _ = buf.write_color(false, &Color::Ansi256(208), false); assert_eq!(buf.0, b"\x1B[48;5;208m"); } fn all_attributes() -> Vec { let mut result = vec![]; for fg in vec![None, Some(Color::Red)] { for bg in vec![None, Some(Color::Red)] { for bold in vec![false, true] { for underline in vec![false, true] { for intense in vec![false, true] { for italic in vec![false, true] { for strikethrough in vec![false, true] { for dimmed in vec![false, true] { let mut color = ColorSpec::new(); color.set_fg(fg); color.set_bg(bg); color.set_bold(bold); color.set_underline(underline); color.set_intense(intense); color.set_italic(italic); color.set_dimmed(dimmed); color.set_strikethrough(strikethrough); result.push(color); } } } } } } } } result } #[test] fn test_is_none() { for (i, color) in all_attributes().iter().enumerate() { assert_eq!( i == 0, color.is_none(), "{:?} => {}", color, color.is_none() ) } } #[test] fn test_clear() { for color in all_attributes() { let mut color1 = color.clone(); color1.clear(); assert!(color1.is_none(), "{:?} => {:?}", color, color1); } } #[test] fn test_ansi_hyperlink() { let mut buf = Ansi::new(vec![]); buf.set_hyperlink(&HyperlinkSpec::open(b"https://example.com")) .unwrap(); buf.write_str("label").unwrap(); buf.set_hyperlink(&HyperlinkSpec::close()).unwrap(); assert_eq!( buf.0, b"\x1B]8;;https://example.com\x1B\\label\x1B]8;;\x1B\\".to_vec() ); } }