From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Cc: Xiaoyao Li <xiaoyao.li@intel.com>, David Hildenbrand <david@redhat.com>
Subject: [PULL 49/63] RAMBlock: Add support of KVM private guest memfd
Date: Tue, 23 Apr 2024 17:09:37 +0200 [thread overview]
Message-ID: <20240423150951.41600-50-pbonzini@redhat.com> (raw)
In-Reply-To: <20240423150951.41600-1-pbonzini@redhat.com>
From: Xiaoyao Li <xiaoyao.li@intel.com>
Add KVM guest_memfd support to RAMBlock so both normal hva based memory
and kvm guest memfd based private memory can be associated in one RAMBlock.
Introduce new flag RAM_GUEST_MEMFD. When it's set, it calls KVM ioctl to
create private guest_memfd during RAMBlock setup.
Allocating a new RAM_GUEST_MEMFD flag to instruct the setup of guest memfd
is more flexible and extensible than simply relying on the VM type because
in the future we may have the case that not all the memory of a VM need
guest memfd. As a benefit, it also avoid getting MachineState in memory
subsystem.
Note, RAM_GUEST_MEMFD is supposed to be set for memory backends of
confidential guests, such as TDX VM. How and when to set it for memory
backends will be implemented in the following patches.
Introduce memory_region_has_guest_memfd() to query if the MemoryRegion has
KVM guest_memfd allocated.
Signed-off-by: Xiaoyao Li <xiaoyao.li@intel.com>
Reviewed-by: David Hildenbrand <david@redhat.com>
Message-ID: <20240320083945.991426-7-michael.roth@amd.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/exec/memory.h | 20 +++++++++++++++++---
include/exec/ram_addr.h | 2 +-
include/exec/ramblock.h | 1 +
include/sysemu/kvm.h | 2 ++
accel/kvm/kvm-all.c | 28 ++++++++++++++++++++++++++++
accel/stubs/kvm-stub.c | 5 +++++
system/memory.c | 5 +++++
system/physmem.c | 34 +++++++++++++++++++++++++++++++---
8 files changed, 90 insertions(+), 7 deletions(-)
diff --git a/include/exec/memory.h b/include/exec/memory.h
index 8626a355b31..679a8476852 100644
--- a/include/exec/memory.h
+++ b/include/exec/memory.h
@@ -243,6 +243,9 @@ typedef struct IOMMUTLBEvent {
/* RAM FD is opened read-only */
#define RAM_READONLY_FD (1 << 11)
+/* RAM can be private that has kvm guest memfd backend */
+#define RAM_GUEST_MEMFD (1 << 12)
+
static inline void iommu_notifier_init(IOMMUNotifier *n, IOMMUNotify fn,
IOMMUNotifierFlag flags,
hwaddr start, hwaddr end,
@@ -1307,7 +1310,8 @@ bool memory_region_init_ram_nomigrate(MemoryRegion *mr,
* @name: Region name, becomes part of RAMBlock name used in migration stream
* must be unique within any device
* @size: size of the region.
- * @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_NORESERVE.
+ * @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_NORESERVE,
+ * RAM_GUEST_MEMFD.
* @errp: pointer to Error*, to store an error if it happens.
*
* Note that this function does not do anything to cause the data in the
@@ -1369,7 +1373,7 @@ bool memory_region_init_resizeable_ram(MemoryRegion *mr,
* (getpagesize()) will be used.
* @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_PMEM,
* RAM_NORESERVE, RAM_PROTECTED, RAM_NAMED_FILE, RAM_READONLY,
- * RAM_READONLY_FD
+ * RAM_READONLY_FD, RAM_GUEST_MEMFD
* @path: the path in which to allocate the RAM.
* @offset: offset within the file referenced by path
* @errp: pointer to Error*, to store an error if it happens.
@@ -1399,7 +1403,7 @@ bool memory_region_init_ram_from_file(MemoryRegion *mr,
* @size: size of the region.
* @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_PMEM,
* RAM_NORESERVE, RAM_PROTECTED, RAM_NAMED_FILE, RAM_READONLY,
- * RAM_READONLY_FD
+ * RAM_READONLY_FD, RAM_GUEST_MEMFD
* @fd: the fd to mmap.
* @offset: offset within the file referenced by fd
* @errp: pointer to Error*, to store an error if it happens.
@@ -1722,6 +1726,16 @@ static inline bool memory_region_is_romd(MemoryRegion *mr)
*/
bool memory_region_is_protected(MemoryRegion *mr);
+/**
+ * memory_region_has_guest_memfd: check whether a memory region has guest_memfd
+ * associated
+ *
+ * Returns %true if a memory region's ram_block has valid guest_memfd assigned.
+ *
+ * @mr: the memory region being queried
+ */
+bool memory_region_has_guest_memfd(MemoryRegion *mr);
+
/**
* memory_region_get_iommu: check whether a memory region is an iommu
*
diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h
index de45ba7bc96..07c8f863750 100644
--- a/include/exec/ram_addr.h
+++ b/include/exec/ram_addr.h
@@ -110,7 +110,7 @@ long qemu_maxrampagesize(void);
* @mr: the memory region where the ram block is
* @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_PMEM,
* RAM_NORESERVE, RAM_PROTECTED, RAM_NAMED_FILE, RAM_READONLY,
- * RAM_READONLY_FD
+ * RAM_READONLY_FD, RAM_GUEST_MEMFD
* @mem_path or @fd: specify the backing file or device
* @offset: Offset into target file
* @errp: pointer to Error*, to store an error if it happens
diff --git a/include/exec/ramblock.h b/include/exec/ramblock.h
index 848915ea5bf..459c8917de2 100644
--- a/include/exec/ramblock.h
+++ b/include/exec/ramblock.h
@@ -41,6 +41,7 @@ struct RAMBlock {
QLIST_HEAD(, RAMBlockNotifier) ramblock_notifiers;
int fd;
uint64_t fd_offset;
+ int guest_memfd;
size_t page_size;
/* dirty bitmap used during migration */
unsigned long *bmap;
diff --git a/include/sysemu/kvm.h b/include/sysemu/kvm.h
index 594ae9b4605..217f3fe17ba 100644
--- a/include/sysemu/kvm.h
+++ b/include/sysemu/kvm.h
@@ -537,6 +537,8 @@ void kvm_mark_guest_state_protected(void);
*/
bool kvm_hwpoisoned_mem(void);
+int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp);
+
int kvm_set_memory_attributes_private(hwaddr start, uint64_t size);
int kvm_set_memory_attributes_shared(hwaddr start, uint64_t size);
diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index db0b1a16edd..1b7bbd838c4 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -92,6 +92,7 @@ static bool kvm_has_guest_debug;
static int kvm_sstep_flags;
static bool kvm_immediate_exit;
static uint64_t kvm_supported_memory_attributes;
+static bool kvm_guest_memfd_supported;
static hwaddr kvm_max_slot_size = ~0;
static const KVMCapabilityInfo kvm_required_capabilites[] = {
@@ -2419,6 +2420,11 @@ static int kvm_init(MachineState *ms)
}
kvm_supported_memory_attributes = kvm_check_extension(s, KVM_CAP_MEMORY_ATTRIBUTES);
+ kvm_guest_memfd_supported =
+ kvm_check_extension(s, KVM_CAP_GUEST_MEMFD) &&
+ kvm_check_extension(s, KVM_CAP_USER_MEMORY2) &&
+ (kvm_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE);
+
kvm_immediate_exit = kvm_check_extension(s, KVM_CAP_IMMEDIATE_EXIT);
s->nr_slots = kvm_check_extension(s, KVM_CAP_NR_MEMSLOTS);
@@ -4138,3 +4144,25 @@ void kvm_mark_guest_state_protected(void)
{
kvm_state->guest_state_protected = true;
}
+
+int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
+{
+ int fd;
+ struct kvm_create_guest_memfd guest_memfd = {
+ .size = size,
+ .flags = flags,
+ };
+
+ if (!kvm_guest_memfd_supported) {
+ error_setg(errp, "KVM does not support guest_memfd");
+ return -1;
+ }
+
+ fd = kvm_vm_ioctl(kvm_state, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
+ if (fd < 0) {
+ error_setg_errno(errp, errno, "Error creating KVM guest_memfd");
+ return -1;
+ }
+
+ return fd;
+}
diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c
index ca381728840..8e0eb22e61c 100644
--- a/accel/stubs/kvm-stub.c
+++ b/accel/stubs/kvm-stub.c
@@ -129,3 +129,8 @@ bool kvm_hwpoisoned_mem(void)
{
return false;
}
+
+int kvm_create_guest_memfd(uint64_t size, uint64_t flags, Error **errp)
+{
+ return -ENOSYS;
+}
diff --git a/system/memory.c b/system/memory.c
index a229a79988f..c756950c0c0 100644
--- a/system/memory.c
+++ b/system/memory.c
@@ -1850,6 +1850,11 @@ bool memory_region_is_protected(MemoryRegion *mr)
return mr->ram && (mr->ram_block->flags & RAM_PROTECTED);
}
+bool memory_region_has_guest_memfd(MemoryRegion *mr)
+{
+ return mr->ram_block && mr->ram_block->guest_memfd >= 0;
+}
+
uint8_t memory_region_get_dirty_log_mask(MemoryRegion *mr)
{
uint8_t mask = mr->dirty_log_mask;
diff --git a/system/physmem.c b/system/physmem.c
index a4fe3d2bf89..f5dfa20e57e 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -1808,6 +1808,7 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
const bool shared = qemu_ram_is_shared(new_block);
RAMBlock *block;
RAMBlock *last_block = NULL;
+ bool free_on_error = false;
ram_addr_t old_ram_size, new_ram_size;
Error *err = NULL;
@@ -1837,6 +1838,19 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
return;
}
memory_try_enable_merging(new_block->host, new_block->max_length);
+ free_on_error = true;
+ }
+ }
+
+ if (new_block->flags & RAM_GUEST_MEMFD) {
+ assert(kvm_enabled());
+ assert(new_block->guest_memfd < 0);
+
+ new_block->guest_memfd = kvm_create_guest_memfd(new_block->max_length,
+ 0, errp);
+ if (new_block->guest_memfd < 0) {
+ qemu_mutex_unlock_ramlist();
+ goto out_free;
}
}
@@ -1888,6 +1902,13 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
ram_block_notify_add(new_block->host, new_block->used_length,
new_block->max_length);
}
+ return;
+
+out_free:
+ if (free_on_error) {
+ qemu_anon_ram_free(new_block->host, new_block->max_length);
+ new_block->host = NULL;
+ }
}
#ifdef CONFIG_POSIX
@@ -1902,7 +1923,7 @@ RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr,
/* Just support these ram flags by now. */
assert((ram_flags & ~(RAM_SHARED | RAM_PMEM | RAM_NORESERVE |
RAM_PROTECTED | RAM_NAMED_FILE | RAM_READONLY |
- RAM_READONLY_FD)) == 0);
+ RAM_READONLY_FD | RAM_GUEST_MEMFD)) == 0);
if (xen_enabled()) {
error_setg(errp, "-mem-path not supported with Xen");
@@ -1939,6 +1960,7 @@ RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr,
new_block->used_length = size;
new_block->max_length = size;
new_block->flags = ram_flags;
+ new_block->guest_memfd = -1;
new_block->host = file_ram_alloc(new_block, size, fd, !file_size, offset,
errp);
if (!new_block->host) {
@@ -2018,7 +2040,7 @@ RAMBlock *qemu_ram_alloc_internal(ram_addr_t size, ram_addr_t max_size,
int align;
assert((ram_flags & ~(RAM_SHARED | RAM_RESIZEABLE | RAM_PREALLOC |
- RAM_NORESERVE)) == 0);
+ RAM_NORESERVE | RAM_GUEST_MEMFD)) == 0);
assert(!host ^ (ram_flags & RAM_PREALLOC));
align = qemu_real_host_page_size();
@@ -2033,6 +2055,7 @@ RAMBlock *qemu_ram_alloc_internal(ram_addr_t size, ram_addr_t max_size,
new_block->max_length = max_size;
assert(max_size >= size);
new_block->fd = -1;
+ new_block->guest_memfd = -1;
new_block->page_size = qemu_real_host_page_size();
new_block->host = host;
new_block->flags = ram_flags;
@@ -2055,7 +2078,7 @@ RAMBlock *qemu_ram_alloc_from_ptr(ram_addr_t size, void *host,
RAMBlock *qemu_ram_alloc(ram_addr_t size, uint32_t ram_flags,
MemoryRegion *mr, Error **errp)
{
- assert((ram_flags & ~(RAM_SHARED | RAM_NORESERVE)) == 0);
+ assert((ram_flags & ~(RAM_SHARED | RAM_NORESERVE | RAM_GUEST_MEMFD)) == 0);
return qemu_ram_alloc_internal(size, size, NULL, NULL, ram_flags, mr, errp);
}
@@ -2083,6 +2106,11 @@ static void reclaim_ramblock(RAMBlock *block)
} else {
qemu_anon_ram_free(block->host, block->max_length);
}
+
+ if (block->guest_memfd >= 0) {
+ close(block->guest_memfd);
+ }
+
g_free(block);
}
--
2.44.0
next prev parent reply other threads:[~2024-04-23 15:17 UTC|newest]
Thread overview: 83+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-23 15:08 [PULL 00/63] First batch of i386 and build system patch for QEMU 9.1 Paolo Bonzini
2024-04-23 15:08 ` [PULL 01/63] meson: do not link pixman automatically into all targets Paolo Bonzini
2024-04-23 15:08 ` [PULL 02/63] tests: only build plugins if TCG is enabled Paolo Bonzini
2024-04-23 15:08 ` [PULL 03/63] ebpf: Restrict to system emulation Paolo Bonzini
2024-04-23 15:08 ` [PULL 04/63] tests/unit: match some unit tests to corresponding feature switches Paolo Bonzini
2024-04-23 15:08 ` [PULL 05/63] yank: only build if needed Paolo Bonzini
2024-04-23 15:08 ` [PULL 06/63] util/qemu-config: Extract QMP commands to qemu-config-qmp.c Paolo Bonzini
2024-04-23 15:08 ` [PULL 07/63] hw/core: Move system emulation files to system_ss Paolo Bonzini
2024-04-23 15:08 ` [PULL 08/63] hw: Include minimal source set in user emulation build Paolo Bonzini
2024-04-23 15:08 ` [PULL 09/63] stubs: remove obsolete stubs Paolo Bonzini
2024-04-23 15:08 ` [PULL 10/63] hw/usb: move stubs out of stubs/ Paolo Bonzini
2024-04-23 15:08 ` [PULL 11/63] hw/virtio: " Paolo Bonzini
2024-08-03 2:29 ` Michael Tokarev
2024-09-05 16:27 ` Paolo Bonzini
2024-09-06 7:08 ` Michael Tokarev
2024-09-06 7:30 ` Paolo Bonzini
2024-04-23 15:09 ` [PULL 12/63] semihosting: " Paolo Bonzini
2024-04-23 15:09 ` [PULL 13/63] ramfb: " Paolo Bonzini
2024-04-23 15:09 ` [PULL 14/63] memory-device: " Paolo Bonzini
2024-04-23 15:09 ` [PULL 15/63] colo: " Paolo Bonzini
2024-04-23 15:09 ` [PULL 16/63] stubs: split record/replay stubs further Paolo Bonzini
2024-04-23 15:09 ` [PULL 17/63] stubs: include stubs only if needed Paolo Bonzini
2024-06-04 10:07 ` Daniel P. Berrangé
2024-06-05 14:46 ` Zhao Liu
2024-04-23 15:09 ` [PULL 18/63] stubs: move monitor_fdsets_cleanup with other fdset stubs Paolo Bonzini
2024-04-23 15:09 ` [PULL 19/63] vga: optimize computation of dirty memory region Paolo Bonzini
2024-04-23 15:09 ` [PULL 20/63] vga: move dirty memory region code together Paolo Bonzini
2024-04-23 15:09 ` [PULL 21/63] kvm: use configs/ definition to conditionalize debug support Paolo Bonzini
2024-04-23 15:09 ` [PULL 22/63] hw: Add compat machines for 9.1 Paolo Bonzini
2024-04-23 15:09 ` [PULL 23/63] target/i386: add guest-phys-bits cpu property Paolo Bonzini
2024-04-23 15:09 ` [PULL 24/63] kvm: add support for guest physical bits Paolo Bonzini
2024-04-23 15:09 ` [PULL 25/63] i386/kvm: Move architectural CPUID leaf generation to separate helper Paolo Bonzini
2024-04-23 15:29 ` Xiaoyao Li
2024-04-23 15:09 ` [PULL 26/63] target/i386: Introduce Icelake-Server-v7 to enable TSX Paolo Bonzini
2024-04-23 15:09 ` [PULL 27/63] target/i386: Add new CPU model SierraForest Paolo Bonzini
2024-04-23 15:09 ` [PULL 28/63] target/i386: Export RFDS bit to guests Paolo Bonzini
2024-04-23 15:09 ` [PULL 29/63] pci-host/q35: Move PAM initialization above SMRAM initialization Paolo Bonzini
2025-08-12 14:20 ` Michael Tokarev
2025-08-12 14:40 ` Michael Tokarev
2024-04-23 15:09 ` [PULL 30/63] q35: Introduce smm_ranges property for q35-pci-host Paolo Bonzini
2025-08-12 14:45 ` Philippe Mathieu-Daudé
2025-08-12 15:27 ` Michael Tokarev
2025-09-17 14:23 ` Michael Tokarev
2025-09-18 9:20 ` Gerd Hoffmann
2025-09-18 9:27 ` Michael Tokarev
2025-09-18 10:25 ` Gerd Hoffmann
2024-04-23 15:09 ` [PULL 31/63] hw/i386/acpi: Set PCAT_COMPAT bit only when pic is not disabled Paolo Bonzini
2024-04-23 15:09 ` [PULL 32/63] confidential guest support: Add kvm_init() and kvm_reset() in class Paolo Bonzini
2024-04-23 15:09 ` [PULL 33/63] i386/sev: Switch to use confidential_guest_kvm_init() Paolo Bonzini
2024-04-23 15:09 ` [PULL 34/63] ppc/pef: switch to use confidential_guest_kvm_init/reset() Paolo Bonzini
2024-04-23 15:09 ` [PULL 35/63] s390: Switch to use confidential_guest_kvm_init() Paolo Bonzini
2024-04-23 15:09 ` [PULL 36/63] scripts/update-linux-headers: Add setup_data.h to import list Paolo Bonzini
2024-04-23 15:09 ` [PULL 37/63] scripts/update-linux-headers: Add bits.h to file imports Paolo Bonzini
2024-04-23 15:09 ` [PULL 38/63] linux-headers: update to current kvm/next Paolo Bonzini
2024-04-23 15:09 ` [PULL 39/63] runstate: skip initial CPU reset if reset is not actually possible Paolo Bonzini
2024-04-23 15:09 ` [PULL 40/63] KVM: track whether guest state is encrypted Paolo Bonzini
2024-04-23 15:09 ` [PULL 41/63] KVM: remove kvm_arch_cpu_check_are_resettable Paolo Bonzini
2024-04-23 15:09 ` [PULL 42/63] target/i386: introduce x86-confidential-guest Paolo Bonzini
2024-04-23 15:09 ` [PULL 43/63] target/i386: Implement mc->kvm_type() to get VM type Paolo Bonzini
2024-04-24 8:36 ` Xiaoyao Li
2024-04-23 15:09 ` [PULL 44/63] target/i386: SEV: use KVM_SEV_INIT2 if possible Paolo Bonzini
2024-04-23 15:09 ` [PULL 45/63] i386/sev: Add 'legacy-vm-type' parameter for SEV guest objects Paolo Bonzini
2024-04-23 15:09 ` [PULL 46/63] hw/i386/sev: Use legacy SEV VM types for older machine types Paolo Bonzini
2024-04-23 15:09 ` [PULL 47/63] trace/kvm: Split address space and slot id in trace_kvm_set_user_memory() Paolo Bonzini
2024-04-23 15:09 ` [PULL 48/63] kvm: Introduce support for memory_attributes Paolo Bonzini
2024-04-23 15:09 ` Paolo Bonzini [this message]
2024-04-23 15:09 ` [PULL 50/63] kvm: Enable KVM_SET_USER_MEMORY_REGION2 for memslot Paolo Bonzini
2024-04-23 15:09 ` [PULL 51/63] kvm/memory: Make memory type private by default if it has guest memfd backend Paolo Bonzini
2024-04-23 15:09 ` [PULL 52/63] HostMem: Add mechanism to opt in kvm guest memfd via MachineState Paolo Bonzini
2024-04-23 15:09 ` [PULL 53/63] RAMBlock: make guest_memfd require uncoordinated discard Paolo Bonzini
2024-04-23 15:09 ` [PULL 54/63] physmem: Introduce ram_block_discard_guest_memfd_range() Paolo Bonzini
2024-04-23 15:09 ` [PULL 55/63] kvm: handle KVM_EXIT_MEMORY_FAULT Paolo Bonzini
2024-04-26 13:40 ` Peter Maydell
2024-04-30 19:06 ` Paolo Bonzini
2024-04-23 15:09 ` [PULL 56/63] kvm/tdx: Don't complain when converting vMMIO region to shared Paolo Bonzini
2024-04-23 15:09 ` [PULL 57/63] kvm/tdx: Ignore memory conversion to shared of unassigned region Paolo Bonzini
2024-04-23 15:09 ` [PULL 58/63] target/i386/host-cpu: Consolidate the use of warn_report_once() Paolo Bonzini
2024-04-23 15:09 ` [PULL 59/63] target/i386/cpu: " Paolo Bonzini
2024-04-23 15:09 ` [PULL 60/63] target/i386/cpu: Merge the warning and error messages for AMD HT check Paolo Bonzini
2024-04-23 15:09 ` [PULL 61/63] accel/tcg/icount-common: Consolidate the use of warn_report_once() Paolo Bonzini
2024-04-23 15:09 ` [PULL 62/63] pythondeps.toml: warn about updates needed to docs/requirements.txt Paolo Bonzini
2024-04-23 15:09 ` [PULL 63/63] target/i386/translate.c: always write 32-bits for SGDT and SIDT Paolo Bonzini
2024-04-24 4:26 ` [PULL 00/63] First batch of i386 and build system patch for QEMU 9.1 Richard Henderson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240423150951.41600-50-pbonzini@redhat.com \
--to=pbonzini@redhat.com \
--cc=david@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=xiaoyao.li@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).