public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: Ruiyu Ni <ruiyu.ni@intel.com>
To: edk2-devel@lists.01.org
Cc: Chen A Chen <chen.a.chen@intel.com>,
	Michael D Kinney <michael.d.kinney@intel.com>,
	Jaben Carsey <jaben.carsey@intel.com>,
	Jeff Fan <jeff.fan@intel.com>
Subject: [PATCH] ShellPkg/setvar: Support data format in Shell 2.2 spec
Date: Wed, 29 Mar 2017 11:22:38 +0800	[thread overview]
Message-ID: <20170329032238.255628-1-ruiyu.ni@intel.com> (raw)

From: Chen A Chen <chen.a.chen@intel.com>

Shell 2.2 spec defines =0x/=0X, =H/=h, =S, =L and =P for
hex number, hex array, ascii string, unicode string and
device path data.
The patch adds such support.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Chen A Chen <chen.a.chen@intel.com>
Cc: Ruiyu Ni <ruiyu.ni@intel.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Jaben Carsey <jaben.carsey@intel.com>
Cc: Jeff Fan <jeff.fan@intel.com>
---
 .../Library/UefiShellDebug1CommandsLib/SetVar.c    | 423 +++++++++++++++------
 1 file changed, 297 insertions(+), 126 deletions(-)

diff --git a/ShellPkg/Library/UefiShellDebug1CommandsLib/SetVar.c b/ShellPkg/Library/UefiShellDebug1CommandsLib/SetVar.c
index c59032a..fc76b58 100644
--- a/ShellPkg/Library/UefiShellDebug1CommandsLib/SetVar.c
+++ b/ShellPkg/Library/UefiShellDebug1CommandsLib/SetVar.c
@@ -1,4 +1,4 @@
-/** @file
+/** @file
   Main file for SetVar shell Debug1 function.
 
   (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
@@ -23,6 +23,21 @@ STATIC CONST SHELL_PARAM_ITEM ParamList[] = {
   {NULL, TypeMax}
   };
 
+typedef enum {
+  DataTypeHexNumber   = 0,
+  DataTypeHexArray    = 1,
+  DataTypeAscii       = 2,
+  DataTypeUnicode     = 3,
+  DataTypeDevicePath  = 4,
+  DataTypeUnKnow      = 5
+} DATA_TYPE;
+
+typedef union {
+  UINT8     HexNumber8;
+  UINT16    HexNumber16;
+  UINT32    HexNumber32;
+  UINT64    HexNumber64;
+} HEX_NUMBER;
 
 /**
   Check if the input is a (potentially empty) string of hexadecimal nibbles.
@@ -50,6 +65,270 @@ IsStringOfHexNibbles (
   return TRUE;
 }
 
+/**
+  Function to check the TYPE of Data.
+
+  @param[in]    Data          The Data to be check.
+
+  @retval       DATA_TYPE     The TYPE of Data.
+**/
+DATA_TYPE
+TestDataType (
+  IN CONST CHAR16  *Data
+  )
+{
+  if (Data[0] == L'0' && (Data[1] == L'x' || Data[1] == L'X')) {
+    if (IsStringOfHexNibbles (Data+2) && StrLen (Data + 2) <= 16) {
+      return DataTypeHexNumber;
+    } else {
+      return DataTypeUnKnow;
+    }
+  } else if (Data[0] == L'H') {
+    if (IsStringOfHexNibbles (Data + 1) && StrLen (Data + 1) % 2 == 0) {
+      return DataTypeHexArray;
+    } else {
+      return DataTypeUnKnow;
+    }
+  } else if (Data[0] == L'S') {
+    return DataTypeAscii;
+  } else if (Data[0] == L'L') {
+    return DataTypeUnicode;
+  } else if (Data[0] == L'P' || StrnCmp (Data, L"--", 2) == 0) {
+    return DataTypeDevicePath;
+  }
+
+  if (IsStringOfHexNibbles (Data) && StrLen (Data) % 2 == 0) {
+    return DataTypeHexArray;
+  }
+
+  return DataTypeAscii;
+}
+
+/**
+  Function to parse the Data by the type of Data, and save in the Buffer.
+
+  @param[in]      Data                A pointer to a buffer to be parsed.
+  @param[out]     Buffer              A pointer to a buffer to hold the return data.
+  @param[in,out]  BufferSize          On input, indicates the size of Buffer in bytes.
+                                      On output,indicates the size of data return in Buffer.
+                                      Or the size in bytes of the buffer needed to obtain.
+
+  @retval   EFI_INVALID_PARAMETER     The Buffer or BufferSize is NULL.
+  @retval   EFI_BUFFER_TOO_SMALL      The Buffer is too small to hold the data.
+  @retval   EFI_OUT_OF_RESOURCES      A memory allcation failed.
+  @retval   EFI_SUCCESS               The Data parsed successful and save in the Buffer.
+**/
+EFI_STATUS
+ParseParameterData (
+  IN CONST CHAR16   *Data,
+  OUT VOID          *Buffer,
+  IN OUT UINTN      *BufferSize
+  )
+{
+  UINT64                    HexNumber;
+  UINTN                     HexNumberLen;
+  UINTN                     Size;
+  CHAR8                     *AsciiBuffer;
+  DATA_TYPE                 DataType;
+  EFI_DEVICE_PATH_PROTOCOL  *DevPath;
+  EFI_STATUS                Status;
+
+  HexNumber                 = 0;
+  HexNumberLen              = 0;
+  Size                      = 0;
+  AsciiBuffer               = NULL;
+  DevPath                   = NULL;
+  Status                    = EFI_SUCCESS;
+
+  if (Data == NULL || BufferSize == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  DataType = TestDataType (Data);
+  if (DataType == DataTypeHexNumber) {
+    //
+    // hex number
+    //
+    StrHexToUint64S (Data + 2, NULL, &HexNumber);
+    HexNumberLen = StrLen (Data + 2);
+    if (HexNumberLen >= 1 && HexNumberLen <= 2) {
+      Size = 1;
+    } else if (HexNumberLen >= 3 && HexNumberLen <= 4) {
+      Size = 2;
+    } else if (HexNumberLen >= 5 && HexNumberLen <= 8) {
+      Size = 4;
+    } else if (HexNumberLen >= 9 && HexNumberLen <= 16) {
+      Size = 8;
+    }
+    if (Buffer != NULL && *BufferSize >= Size) {
+      CopyMem(Buffer, (VOID *)&HexNumber, Size);
+    } else {
+      Status = EFI_BUFFER_TOO_SMALL;
+    }
+    *BufferSize = Size;
+  } else if (DataType == DataTypeHexArray) {
+    //
+    // hex array
+    //
+    if (*Data == L'H') {
+      Data = Data + 1;
+    }
+
+    Size = StrLen (Data) / 2;
+    if (Buffer != NULL && *BufferSize >= Size) {
+      StrHexToBytes(Data, StrLen  (Data), (UINT8 *)Buffer, Size);
+    } else {
+      Status = EFI_BUFFER_TOO_SMALL;
+    }
+    *BufferSize = Size;
+  } else if (DataType == DataTypeAscii) {
+    //
+    // ascii text
+    //
+    if (*Data == L'S') {
+      Data = Data + 1;
+    }
+    AsciiBuffer = AllocateZeroPool (StrSize (Data) / 2);
+    if (AsciiBuffer == NULL) {
+      Status = EFI_OUT_OF_RESOURCES;
+    } else {
+      AsciiSPrint (AsciiBuffer, StrSize (Data) / 2, "%s", (CHAR8 *)Data);
+
+      Size = StrSize (Data) / 2 - 1;
+      if (Buffer != NULL && *BufferSize >= Size) {
+        CopyMem (Buffer, AsciiBuffer, Size);
+      } else {
+        Status = EFI_BUFFER_TOO_SMALL;
+      }
+      *BufferSize = Size;
+    }
+    SHELL_FREE_NON_NULL (AsciiBuffer);
+  } else if (DataType == DataTypeUnicode) {
+    //
+    // unicode text
+    //
+    if (*Data == L'L') {
+      Data = Data + 1;
+    }
+    Size = StrSize (Data) - sizeof (CHAR16);
+    if (Buffer != NULL && *BufferSize >= Size) {
+      CopyMem (Buffer, Data, Size);
+    } else {
+      Status = EFI_BUFFER_TOO_SMALL;
+    }
+    *BufferSize = Size;
+  } else if (DataType == DataTypeDevicePath) {
+    if (*Data == L'P') {
+      Data = Data + 1;
+    } else if (StrnCmp (Data, L"--", 2) == 0) {
+      Data = Data + 2;
+    }
+    DevPath = ConvertTextToDevicePath (Data);
+    if (DevPath == NULL) {
+      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_DPFT), gShellDebug1HiiHandle, L"setvar");
+      Status = EFI_INVALID_PARAMETER;
+    } else {
+      Size = GetDevicePathSize (DevPath);
+      if (Buffer != NULL && *BufferSize >= Size) {
+        CopyMem (Buffer, DevPath, Size);
+      } else {
+        Status = EFI_BUFFER_TOO_SMALL;
+      }
+      *BufferSize = Size;
+    }
+    SHELL_FREE_NON_NULL (DevPath);
+  } else {
+    Status = EFI_INVALID_PARAMETER;
+  }
+
+  return Status;
+}
+
+/**
+  Function to get each data from parameters.
+
+  @param[in]    Pacakge               The package of checked values.
+  @param[out]   Buffer                A pointer to a buffer to hold the return data.
+  @param[out]   BufferSize            Indicates the size of data in bytes return in Buffer.
+
+  @retval   EFI_INVALID_PARAMETER     Buffer or BufferSize is NULL.
+  @retval   EFI_OUT_OF_RESOURCES      A memory allcation failed.
+  @retval   EFI_SUCCESS               Get each parameter data was successful.
+**/
+EFI_STATUS
+GetVariableDataFromParameter (
+  IN CONST LIST_ENTRY *Package,
+  OUT UINT8           **Buffer,
+  OUT UINTN           *BufferSize
+  )
+{
+  CONST CHAR16    *TempData;
+  UINTN           Index;
+  UINTN           TotalSize;
+  UINTN           Size;
+  UINT8           *BufferWalker;
+  EFI_STATUS      Status;
+
+  TotalSize       = 0;
+  Size            = 0;
+  Status          = EFI_SUCCESS;
+
+  if (BufferSize == NULL || Buffer == NULL || ShellCommandLineGetCount (Package) < 3) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  for (Index = 2; Index < ShellCommandLineGetCount (Package); Index++) {
+    TempData = ShellCommandLineGetRawValue (Package, Index);
+
+    if (TempData[0] != L'=') {
+      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellDebug1HiiHandle, L"setvar", TempData);
+      return EFI_INVALID_PARAMETER;
+    }
+
+    TempData = TempData + 1;
+    Size = 0;
+    Status = ParseParameterData (TempData, NULL, &Size);
+    if (EFI_ERROR (Status)) {
+      if (Status == EFI_BUFFER_TOO_SMALL) {
+        //
+        // We expect return EFI_BUFFER_TOO_SMALL when pass 'NULL' as second parameter to the function ParseParameterData.
+        //
+        TotalSize += Size;
+      } else {
+        if (Status == EFI_INVALID_PARAMETER) {
+          ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellDebug1HiiHandle, L"setvar", TempData);
+        } else if (Status == EFI_NOT_FOUND) {
+          ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_DPFT), gShellDebug1HiiHandle, L"setvar");
+        }
+        return Status;
+      }
+    }
+  }
+
+  *BufferSize = TotalSize;
+  *Buffer = AllocateZeroPool (TotalSize);
+
+  if (*Buffer == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+  } else {
+    BufferWalker = *Buffer;
+    for (Index = 2; Index < ShellCommandLineGetCount (Package); Index++) {
+      TempData = ShellCommandLineGetRawValue (Package, Index);
+      TempData = TempData + 1;
+
+      Size = TotalSize;
+      Status = ParseParameterData (TempData, (VOID *)BufferWalker, &Size);
+      if (!EFI_ERROR (Status)) {
+        BufferWalker = BufferWalker + Size;
+        TotalSize = TotalSize - Size;
+      } else {
+        return Status;
+      }
+    }
+  }
+
+  return EFI_SUCCESS;
+}
 
 /**
   Function for 'setvar' command.
@@ -70,21 +349,18 @@ ShellCommandRunSetVar (
   CHAR16              *ProblemParam;
   SHELL_STATUS        ShellStatus;
   CONST CHAR16        *VariableName;
-  CONST CHAR16        *Data;
   EFI_GUID            Guid;
   CONST CHAR16        *StringGuid;
   UINT32              Attributes;
   VOID                *Buffer;
   UINTN               Size;
   UINTN               LoopVar;
-  EFI_DEVICE_PATH_PROTOCOL           *DevPath;
 
   ShellStatus         = SHELL_SUCCESS;
   Status              = EFI_SUCCESS;
   Buffer              = NULL;
   Size                = 0;
   Attributes          = 0;
-  DevPath             = NULL;
 
   //
   // initialize the shell lib (we must be in non-auto-init...)
@@ -111,12 +387,8 @@ ShellCommandRunSetVar (
     if (ShellCommandLineGetCount(Package) < 2) {
       ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellDebug1HiiHandle, L"setvar");  
       ShellStatus = SHELL_INVALID_PARAMETER;
-    } else if (ShellCommandLineGetCount(Package) > 3) {
-      ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellDebug1HiiHandle, L"setvar");  
-      ShellStatus = SHELL_INVALID_PARAMETER;
     } else {
       VariableName  = ShellCommandLineGetRawValue(Package, 1);
-      Data          = ShellCommandLineGetRawValue(Package, 2);
       if (!ShellCommandLineGetFlag(Package, L"-guid")){
         CopyGuid(&Guid, &gEfiGlobalVariableGuid);
       } else {
@@ -127,54 +399,35 @@ ShellCommandRunSetVar (
           ShellStatus = SHELL_INVALID_PARAMETER;
         }
       }
-      if (Data == NULL || Data[0] !=  L'=') {
+
+      if (ShellCommandLineGetCount(Package) == 2) {
         //
-        // Display what's there
+        // Display
         //
         Status = gRT->GetVariable((CHAR16*)VariableName, &Guid, &Attributes, &Size, Buffer);
         if (Status == EFI_BUFFER_TOO_SMALL) {
           Buffer = AllocateZeroPool(Size);
           Status = gRT->GetVariable((CHAR16*)VariableName, &Guid, &Attributes, &Size, Buffer);
         }
-        if (!EFI_ERROR(Status)&& Buffer != NULL) {
-          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_PRINT), gShellDebug1HiiHandle, &Guid, VariableName, Size);
-          for (LoopVar = 0 ; LoopVar < Size ; LoopVar++) {
+        if (!EFI_ERROR(Status) && Buffer != NULL) {
+          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN(STR_SETVAR_PRINT), gShellDebug1HiiHandle, &Guid, VariableName, Size);
+          for (LoopVar = 0; LoopVar < Size; LoopVar++) {
             ShellPrintEx(-1, -1, L"%02x ", ((UINT8*)Buffer)[LoopVar]);
           }
           ShellPrintEx(-1, -1, L"\r\n");
         } else {
-          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_GET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
-          ShellStatus = SHELL_ACCESS_DENIED;
-        }
-      } else if (StrCmp(Data, L"=") == 0) {
-        //
-        // Delete what's there!
-        //
-        Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, 0, NULL);
-        if (EFI_ERROR(Status)) {
-          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
+          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_GET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);
           ShellStatus = SHELL_ACCESS_DENIED;
-        } else {
-          ASSERT(ShellStatus == SHELL_SUCCESS);
         }
       } else {
         //
-        // Change what's there or create a new one.
-        //
-
-        ASSERT(Data[0] == L'=');
-        Data++;
-        ASSERT(Data[0] != L'\0');
-
-        //
-        // Determine if the variable exists and get the attributes
+        // Create, Delete or Modify.
         //
         Status = gRT->GetVariable((CHAR16*)VariableName, &Guid, &Attributes, &Size, Buffer);
         if (Status == EFI_BUFFER_TOO_SMALL) {
           Buffer = AllocateZeroPool(Size);
           Status = gRT->GetVariable((CHAR16*)VariableName, &Guid, &Attributes, &Size, Buffer);
         }
-
         if (EFI_ERROR(Status) || Buffer == NULL) {
           //
           // Creating a new variable.  determine attributes from command line.
@@ -193,94 +446,16 @@ ShellCommandRunSetVar (
         }
         SHELL_FREE_NON_NULL(Buffer);
 
-        //
-        // What type is the new data.
-        //
-        if (IsStringOfHexNibbles(Data)) {
-          if (StrLen(Data) % 2 != 0) {
-            ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellDebug1HiiHandle, L"setvar", Data);  
-            ShellStatus = SHELL_INVALID_PARAMETER;
-          } else {
-            //
-            // arbitrary buffer
-            //
-            Buffer = AllocateZeroPool((StrLen(Data) / 2));
-            if (Buffer == NULL) {
-              Status = EFI_OUT_OF_RESOURCES;
-            } else {
-              StrHexToBytes (Data, StrLen (Data), Buffer, StrLen (Data) / 2);
-              Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, StrLen(Data) / 2, Buffer);
-            }
-            if (EFI_ERROR(Status)) {
-              ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
-              ShellStatus = SHELL_ACCESS_DENIED;
-            } else {
-              ASSERT(ShellStatus == SHELL_SUCCESS);
-            }
-          }
-        } else if (StrnCmp(Data, L"\"", 1) == 0) {
-          //
-          // ascii text
-          //
-          Data++;
-          Buffer = AllocateZeroPool(StrSize(Data) / 2);
-          if (Buffer == NULL) {
-            Status = EFI_OUT_OF_RESOURCES;
-          } else {
-            AsciiSPrint(Buffer, StrSize(Data) / 2, "%s", Data);
-            ((CHAR8*)Buffer)[AsciiStrLen(Buffer)-1] = CHAR_NULL;
-            Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, AsciiStrSize(Buffer)-sizeof(CHAR8), Buffer);
-          }
-          if (EFI_ERROR(Status)) {
-            ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
-            ShellStatus = SHELL_ACCESS_DENIED;
-          } else {
-            ASSERT(ShellStatus == SHELL_SUCCESS);
-          }
-        } else if (StrnCmp(Data, L"L\"", 2) == 0) {
-          //
-          // ucs2 text
-          //
-          Data++;
-          Data++;
-          Buffer = AllocateZeroPool(StrSize(Data));
-          if (Buffer == NULL) {
-            ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_OUT_MEM), gShellDebug1HiiHandle, L"setvar");  
-            ShellStatus = SHELL_OUT_OF_RESOURCES;
-          } else {
-            UnicodeSPrint(Buffer, StrSize(Data), L"%s", Data);
-            ((CHAR16*)Buffer)[StrLen(Buffer)-1] = CHAR_NULL;
-
-            Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, StrSize(Buffer)-sizeof(CHAR16), Buffer);
-            if (EFI_ERROR(Status)) {
-              ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
-              ShellStatus = SHELL_ACCESS_DENIED;
-            } else {
-              ASSERT(ShellStatus == SHELL_SUCCESS);
-            }
-          }
-        } else if (StrnCmp(Data, L"--", 2) == 0) {
-          //
-          // device path in text format
-          //
-          Data++;
-          Data++;
-          DevPath = ConvertTextToDevicePath(Data);
-          if (DevPath == NULL) {
-            ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_DPFT), gShellDebug1HiiHandle, L"setvar");  
-            ShellStatus = SHELL_INVALID_PARAMETER;
-          } else {
-            Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, GetDevicePathSize(DevPath), DevPath);
-            if (EFI_ERROR(Status)) {
-              ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);  
-              ShellStatus = SHELL_ACCESS_DENIED;
-            } else {
-              ASSERT(ShellStatus == SHELL_SUCCESS);
-            }
-          }
+        Size = 0;
+        Status = GetVariableDataFromParameter(Package, (UINT8 **)&Buffer, &Size);
+        if (!EFI_ERROR(Status)) {
+          Status = gRT->SetVariable((CHAR16*)VariableName, &Guid, Attributes, Size, Buffer);
+        }
+        if (EFI_ERROR(Status)) {
+          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN(STR_SETVAR_ERROR_SET), gShellDebug1HiiHandle, L"setvar", &Guid, VariableName);
+          ShellStatus = SHELL_ACCESS_DENIED;
         } else {
-          ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellDebug1HiiHandle, L"setvar", Data);  
-          ShellStatus = SHELL_INVALID_PARAMETER;
+          ASSERT(ShellStatus == SHELL_SUCCESS);
         }
       }
     }
@@ -291,9 +466,5 @@ ShellCommandRunSetVar (
     FreePool(Buffer);
   }
 
-  if (DevPath != NULL) {
-    FreePool(DevPath);
-  }
-
   return (ShellStatus);
 }
-- 
2.9.0.windows.1



                 reply	other threads:[~2017-03-29  3:22 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20170329032238.255628-1-ruiyu.ni@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