From: Dandan Bi <dandan.bi@intel.com>
To: edk2-devel@lists.01.org
Cc: Eric Dong <eric.dong@intel.com>, Liming Gao <liming.gao@intel.com>
Subject: [RFC V2 4/5] MdeModulePkg/HiiDatabase: Handle questions with Bit VarStore
Date: Sat, 27 May 2017 16:34:19 +0800 [thread overview]
Message-ID: <1495874060-331108-5-git-send-email-dandan.bi@intel.com> (raw)
In-Reply-To: <1495874060-331108-1-git-send-email-dandan.bi@intel.com>
For oneof/numeric/checkbox, their storage may be bit field.
When generating <ConfigAltResp> string to get default value
for these questions, we need to parse the Ifr data to get
the bit Varstore info,and then generating the correct
<ConfigAltResp> string.
Cc: Eric Dong <eric.dong@intel.com>
Cc: Liming Gao <liming.gao@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Dandan Bi <dandan.bi@intel.com>
---
.../Universal/HiiDatabaseDxe/ConfigRouting.c | 194 +++++++++++++++++++--
.../Universal/HiiDatabaseDxe/HiiDatabase.h | 4 +
.../Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf | 1 +
3 files changed, 186 insertions(+), 13 deletions(-)
diff --git a/MdeModulePkg/Universal/HiiDatabaseDxe/ConfigRouting.c b/MdeModulePkg/Universal/HiiDatabaseDxe/ConfigRouting.c
index c9ff1cf..bf8296e 100644
--- a/MdeModulePkg/Universal/HiiDatabaseDxe/ConfigRouting.c
+++ b/MdeModulePkg/Universal/HiiDatabaseDxe/ConfigRouting.c
@@ -13,10 +13,11 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "HiiDatabase.h"
extern HII_DATABASE_PRIVATE_DATA mPrivate;
+BOOLEAN mQuestionReferBitVar = FALSE;
/**
Calculate the number of Unicode characters of the incoming Configuration string,
not including NULL terminator.
@@ -1223,19 +1224,19 @@ InsertBlockData (
// Insert block data in its Offset and Width order.
//
for (Link = BlockLink->ForwardLink; Link != BlockLink; Link = Link->ForwardLink) {
BlockArray = BASE_CR (Link, IFR_BLOCK_DATA, Entry);
if (BlockArray->Offset == BlockSingleData->Offset) {
- if (BlockArray->Width > BlockSingleData->Width) {
+ if ((BlockArray->Width > BlockSingleData->Width) || (BlockSingleData->IsBitVar && BlockArray->Width == BlockSingleData->Width)) {
//
// Insert this block data in the front of block array
//
InsertTailList (Link, &BlockSingleData->Entry);
return;
}
- if (BlockArray->Width == BlockSingleData->Width) {
+ if ((!BlockSingleData->IsBitVar) && BlockArray->Width == BlockSingleData->Width) {
//
// The same block array has been added.
//
if (BlockSingleData != BlockArray) {
FreePool (BlockSingleData);
@@ -1991,20 +1992,25 @@ IsThisOpcodeRequired (
IN IFR_BLOCK_DATA *RequestBlockArray,
IN EFI_HII_HANDLE HiiHandle,
IN OUT IFR_VARSTORAGE_DATA *VarStorageData,
IN EFI_IFR_OP_HEADER *IfrOpHdr,
IN UINT16 VarWidth,
- OUT IFR_BLOCK_DATA **ReturnData
+ OUT IFR_BLOCK_DATA **ReturnData,
+ IN BOOLEAN IsBitVar
)
{
IFR_BLOCK_DATA *BlockData;
UINT16 VarOffset;
EFI_STRING_ID NameId;
EFI_IFR_QUESTION_HEADER *IfrQuestionHdr;
+ UINT16 BitOffset;
+ UINT16 BitWidth;
+ UINT16 BitVarWidth;
NameId = 0;
VarOffset = 0;
+ BitVarWidth = 0;
IfrQuestionHdr = (EFI_IFR_QUESTION_HEADER *)((CHAR8 *) IfrOpHdr + sizeof (EFI_IFR_OP_HEADER));
if (VarStorageData->Type == EFI_HII_VARSTORE_NAME_VALUE) {
NameId = IfrQuestionHdr->VarStoreInfo.VarName;
@@ -2016,11 +2022,19 @@ IsThisOpcodeRequired (
// This question is not in the requested string. Skip it.
//
return EFI_NOT_FOUND;
}
} else {
- VarOffset = IfrQuestionHdr->VarStoreInfo.VarOffset;
+ if (IsBitVar) {
+ BitOffset = IfrQuestionHdr->VarStoreInfo.VarOffset;
+ VarOffset = BitOffset / 8;
+ BitVarWidth = VarWidth;
+ BitWidth = BitOffset - (VarOffset * 8) + BitVarWidth;
+ VarWidth = (BitWidth % 8 == 0 ? BitWidth / 8: BitWidth / 8 + 1);
+ } else {
+ VarOffset = IfrQuestionHdr->VarStoreInfo.VarOffset;
+ }
//
// Check whether this question is in requested block array.
//
if (!BlockArrayCheck (RequestBlockArray, VarOffset, VarWidth, FALSE, HiiHandle)) {
@@ -2051,10 +2065,13 @@ IsThisOpcodeRequired (
BlockData->Width = VarWidth;
BlockData->QuestionId = IfrQuestionHdr->QuestionId;
BlockData->OpCode = IfrOpHdr->OpCode;
BlockData->Scope = IfrOpHdr->Scope;
+ BlockData->IsBitVar = IsBitVar;
+ BlockData->BitOffset = IfrQuestionHdr->VarStoreInfo.VarOffset;
+ BlockData->BitWidth = BitVarWidth;
InitializeListHead (&BlockData->DefaultValueEntry);
//
// Add Block Data into VarStorageData BlockEntry
//
InsertBlockData (&VarStorageData->BlockEntry, &BlockData);
@@ -2124,10 +2141,11 @@ ParseIfrData (
EFI_HII_PACKAGE_HEADER *PackageHeader;
EFI_VARSTORE_ID VarStoreId;
UINT16 SmallestDefaultId;
BOOLEAN SmallestIdFromFlag;
BOOLEAN FromOtherDefaultOpcode;
+ BOOLEAN IsBitVar;
Status = EFI_SUCCESS;
BlockData = NULL;
DefaultDataPtr = NULL;
FirstOneOfOption = FALSE;
@@ -2135,10 +2153,11 @@ ParseIfrData (
FirstOrderedList = FALSE;
VarStoreName = NULL;
ZeroMem (&DefaultData, sizeof (IFR_DEFAULT_DATA));
SmallestDefaultId = 0xFFFF;
FromOtherDefaultOpcode = FALSE;
+ IsBitVar = FALSE;
//
// Go through the form package to parse OpCode one by one.
//
PackageOffset = sizeof (EFI_HII_PACKAGE_HEADER);
@@ -2309,11 +2328,11 @@ ParseIfrData (
//
if (BlockData != NULL){
BlockData = NULL;
}
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2341,20 +2360,28 @@ ParseIfrData (
//
IfrOneOf = (EFI_IFR_ONE_OF *) IfrOpHdr;
if (IfrOneOf->Question.VarStoreId != VarStoreId) {
break;
}
- VarWidth = (UINT16) (1 << (IfrOneOf->Flags & EFI_IFR_NUMERIC_SIZE));
+
+ if (mQuestionReferBitVar) {
+ mQuestionReferBitVar = FALSE;
+ VarWidth = IfrOneOf->Flags & EFI_IFR_NUMERIC_SIZE_BIT ;
+ IsBitVar = TRUE;
+ } else {
+ VarWidth = (UINT16) (1 << (IfrOneOf->Flags & EFI_IFR_NUMERIC_SIZE));
+ IsBitVar = FALSE;
+ }
//
// The BlockData may allocate by other opcode,need to clean.
//
if (BlockData != NULL){
BlockData = NULL;
}
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, IsBitVar);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2439,11 +2466,11 @@ ParseIfrData (
//
if (BlockData != NULL){
BlockData = NULL;
}
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2475,20 +2502,29 @@ ParseIfrData (
//
IfrCheckBox = (EFI_IFR_CHECKBOX *) IfrOpHdr;
if (IfrCheckBox->Question.VarStoreId != VarStoreId) {
break;
}
+
VarWidth = (UINT16) sizeof (BOOLEAN);
//
// The BlockData may allocate by other opcode,need to clean.
//
if (BlockData != NULL){
BlockData = NULL;
}
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ if (mQuestionReferBitVar) {
+ mQuestionReferBitVar = FALSE;
+ VarWidth = 1;
+ IsBitVar = TRUE;
+ } else {
+ IsBitVar = FALSE;
+ }
+
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, IsBitVar);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2612,11 +2648,11 @@ ParseIfrData (
if (BlockData != NULL){
BlockData = NULL;
}
VarWidth = (UINT16) sizeof (EFI_HII_DATE);
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2654,11 +2690,11 @@ ParseIfrData (
if (BlockData != NULL){
BlockData = NULL;
}
VarWidth = (UINT16) sizeof (EFI_HII_TIME);
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2696,11 +2732,11 @@ ParseIfrData (
if (BlockData != NULL){
BlockData = NULL;
}
VarWidth = (UINT16) (IfrString->MaxSize * sizeof (UINT16));
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2738,11 +2774,11 @@ ParseIfrData (
if (BlockData != NULL){
BlockData = NULL;
}
VarWidth = (UINT16) (IfrPassword->MaxSize * sizeof (UINT16));
- Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData);
+ Status = IsThisOpcodeRequired(RequestBlockArray, HiiHandle, VarStorageData, IfrOpHdr, VarWidth, &BlockData, FALSE);
if (EFI_ERROR (Status)) {
if (Status == EFI_NOT_FOUND){
//
//The opcode is not required,exit and parse other opcode.
//
@@ -2985,13 +3021,19 @@ ParseIfrData (
//
SmallestDefaultId = 0xFFFF;
FromOtherDefaultOpcode = FALSE;
}
}
+ mQuestionReferBitVar = FALSE;
break;
+ case EFI_IFR_GUID_OP:
+ if (CompareGuid ((EFI_GUID *)((UINT8*)IfrOpHdr + sizeof (EFI_IFR_OP_HEADER)), &gEfiIfrBitvarstoreGuid)) {
+ mQuestionReferBitVar = TRUE;
+ }
+
default:
if (BlockData != NULL) {
if (BlockData->Scope > 0) {
BlockData->Scope = (UINT8) (BlockData->Scope + IfrOpHdr->Scope);
}
@@ -3567,10 +3609,134 @@ GetStorageWidth (
return StorageWidth;
}
/**
+For some question (oneof/numeric/checkbox),their storage may be bit filed,
+their block data may have same OFFSET and WIDTH, this function merge the
+same block data to one.
+
+@param BlockLink The Link of the block data.
+
+**/
+VOID
+UpdateBlockDataArray(
+ IN LIST_ENTRY *BlockLink
+)
+{
+ LIST_ENTRY *Link;
+ LIST_ENTRY *TempLink;
+ LIST_ENTRY *ListEntry;
+ LIST_ENTRY *NextListEntry;
+ LIST_ENTRY *LinkDefault;
+ LIST_ENTRY *NextLinkDefault;
+ IFR_BLOCK_DATA *BlockData;
+ IFR_BLOCK_DATA *NextBlockeData;
+ IFR_DEFAULT_DATA *DefaultValueData;
+ IFR_DEFAULT_DATA *NextDefaultValueData;
+ UINT32 Value;
+ UINT32 Mask;
+ UINT32 PreBits;
+ UINT32 *DefaultValue;
+ UINT8 *BufferValue;
+ BOOLEAN BlockDataChanged;
+ BOOLEAN DefaultIdChanged;
+ BOOLEAN NextBlockChanged;
+
+ Value = 0;
+ Link = BlockLink->ForwardLink;
+ BlockDataChanged = TRUE;
+ DefaultIdChanged = FALSE;
+ NextBlockChanged = FALSE;
+
+ while (Link != BlockLink) {
+ BlockData = BASE_CR (Link, IFR_BLOCK_DATA, Entry);
+ TempLink = Link->ForwardLink;
+ if (!BlockData ->IsBitVar) {
+ Link = Link->ForwardLink;
+ continue;
+ }
+
+ while (TempLink != Link) {
+ NextBlockeData = BASE_CR (TempLink, IFR_BLOCK_DATA, Entry);
+ TempLink = TempLink->ForwardLink;
+ if (BlockData->Offset != NextBlockeData->Offset || BlockData->Width != NextBlockeData->Width) {
+ continue;
+ }
+ //
+ // They are the same blocks, merge them to one, and update the default value.
+ //
+ ListEntry = &BlockData->DefaultValueEntry;
+ for (LinkDefault = ListEntry->ForwardLink; LinkDefault != ListEntry; LinkDefault = LinkDefault->ForwardLink) {
+ DefaultValueData = BASE_CR (LinkDefault, IFR_DEFAULT_DATA, Entry);
+ NextListEntry = &NextBlockeData->DefaultValueEntry;
+ for (NextLinkDefault = NextListEntry->ForwardLink; NextLinkDefault != NextListEntry; NextLinkDefault = NextLinkDefault->ForwardLink) {
+ NextDefaultValueData = BASE_CR (NextLinkDefault, IFR_DEFAULT_DATA, Entry);
+ if (DefaultValueData->DefaultId != NextDefaultValueData->DefaultId) {
+ continue;
+ }
+ //
+ // The default value with same default id.
+ //
+ if (BlockDataChanged) {
+ Mask = (1 << BlockData->BitWidth) - 1;
+ PreBits = BlockData->BitOffset - BlockData->Offset * 8;
+ DefaultValue = (UINT32*)&(DefaultValueData->Value);
+ *DefaultValue <<= PreBits;
+ Mask <<= PreBits;
+ Value = (Value & (~Mask)) | *DefaultValue;
+ BlockDataChanged = FALSE;
+ } else if (!BlockDataChanged && !NextBlockChanged && DefaultIdChanged){
+ Mask = (1 << BlockData->BitWidth) - 1;
+ PreBits = BlockData->BitOffset - BlockData->Offset * 8;
+ DefaultValue = (UINT32*)&(DefaultValueData->Value);
+ *DefaultValue <<= PreBits;
+ Mask <<= PreBits;
+ Value = (Value & (~Mask)) | *DefaultValue;
+ DefaultIdChanged = FALSE;
+ } else if (!BlockDataChanged && NextBlockChanged) {
+ BufferValue = (UINT8*)&(DefaultValueData->Value);
+ Value = (UINTN)(*BufferValue);
+ }
+
+ Mask = (1 << NextBlockeData->BitWidth) - 1;
+ PreBits = NextBlockeData->BitOffset - NextBlockeData->Offset * 8;
+ DefaultValue = (UINT32*) & (NextDefaultValueData->Value);
+ *DefaultValue <<= PreBits;
+ Mask <<= PreBits;
+ Value = (Value & (~Mask)) | *DefaultValue;
+
+ CopyMem (&DefaultValueData->Value, &Value, sizeof (EFI_IFR_TYPE_VALUE));
+ }
+ DefaultIdChanged = TRUE;
+ }
+ RemoveEntryList (&NextBlockeData->Entry);
+ if (NextBlockeData->Name != NULL) {
+ FreePool (NextBlockeData->Name);
+ }
+ //
+ // Free default value link array
+ //
+ while (!IsListEmpty (&NextBlockeData->DefaultValueEntry)) {
+ DefaultValueData = BASE_CR (NextBlockeData->DefaultValueEntry.ForwardLink, IFR_DEFAULT_DATA, Entry);
+ RemoveEntryList (&DefaultValueData->Entry);
+ FreePool (DefaultValueData);
+ }
+ FreePool (NextBlockeData);
+
+ Link->ForwardLink = TempLink;
+ NextBlockChanged = TRUE;
+ }
+
+ Link = Link->ForwardLink;
+ BlockDataChanged = TRUE;
+ NextBlockChanged = FALSE;
+ DefaultIdChanged = FALSE;
+ }
+}
+
+/**
Generate ConfigAltResp string base on the varstore info.
@param HiiHandle Hii Handle for this hii package.
@param ConfigHdr The config header for this varstore.
@param VarStorageData The varstore info.
@@ -3610,10 +3776,12 @@ GenerateAltConfigResp (
//
// Add length for <ConfigHdr> + '\0'
//
Length = StrLen (ConfigHdr) + 1;
+ UpdateBlockDataArray (&VarStorageData->BlockEntry);
+
for (Link = DefaultIdArray->Entry.ForwardLink; Link != &DefaultIdArray->Entry; Link = Link->ForwardLink) {
DefaultId = BASE_CR (Link, IFR_DEFAULT_DATA, Entry);
//
// Add length for "&<ConfigHdr>&ALTCFG=XXXX"
// |1| StrLen (ConfigHdr) | 8 | 4 |
diff --git a/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabase.h b/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabase.h
index e6760c3..d083641 100644
--- a/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabase.h
+++ b/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabase.h
@@ -29,10 +29,11 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#include <Protocol/HiiConfigKeyword.h>
#include <Protocol/SimpleTextOut.h>
#include <Guid/HiiKeyBoardLayout.h>
#include <Guid/GlobalVariable.h>
+#include <Guid/MdeModuleHii.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
@@ -75,15 +76,18 @@ typedef struct {
typedef struct {
LIST_ENTRY Entry; // Link to Block array
UINT16 Offset;
UINT16 Width;
+ UINT16 BitOffset;
+ UINT16 BitWidth;
EFI_QUESTION_ID QuestionId;
UINT8 OpCode;
UINT8 Scope;
LIST_ENTRY DefaultValueEntry; // Link to its default value array
CHAR16 *Name;
+ BOOLEAN IsBitVar;
} IFR_BLOCK_DATA;
//
// Get default value from IFR data.
//
diff --git a/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf b/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf
index 6bb1d03..af798b9 100644
--- a/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf
+++ b/MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf
@@ -88,10 +88,11 @@
## CONSUMES ## Event
## PRODUCES ## Event
gEfiHiiKeyBoardLayoutGuid
gEfiHiiImageDecoderNameJpegGuid |gEfiMdeModulePkgTokenSpaceGuid.PcdSupportHiiImageProtocol ## SOMETIMES_CONSUMES ## GUID
gEfiHiiImageDecoderNamePngGuid |gEfiMdeModulePkgTokenSpaceGuid.PcdSupportHiiImageProtocol ## SOMETIMES_CONSUMES ## GUID
+ gEfiIfrBitvarstoreGuid ## SOMETIMES_CONSUMES ## GUID
[Depex]
TRUE
[UserExtensions.TianoCore."ExtraFiles"]
--
1.9.5.msysgit.1
next prev parent reply other threads:[~2017-05-27 8:34 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-05-27 8:34 [RFC V2 0/5] Support Bit fields in EFI/Buffer VarStore Dandan Bi
2017-05-27 8:34 ` [RFC V2 1/5] BaseTool/VfrCompiler: " Dandan Bi
2017-05-27 8:34 ` [RFC V2 2/5] MdeModulePkg: Add guid/flags to implement BitFiled support Dandan Bi
2017-05-27 8:34 ` [RFC V2 3/5] MdeModulePkg/SetupBrowser: Handle questions with Bit VarStore Dandan Bi
2017-05-27 8:34 ` Dandan Bi [this message]
2017-05-27 8:34 ` [RFC V2 5/5] MdeModulePkg/DriverSample: Add sample questions with bit VarStore Dandan Bi
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-list 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=1495874060-331108-5-git-send-email-dandan.bi@intel.com \
--to=devel@edk2.groups.io \
/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