grub-devel.gnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 2/2] Add a module for retrieving SMBIOS information
@ 2015-08-16  2:27 David Michael
  2015-09-19  6:52 ` Andrei Borzenkov
  0 siblings, 1 reply; 2+ messages in thread
From: David Michael @ 2015-08-16  2:27 UTC (permalink / raw
  To: grub-devel; +Cc: arvidjaar, phcoder

The following are two use cases from Rajat Jain <rajatjain@juniper.net>:

1) We have a board that boots Linux and this board itself can be plugged into one of different chassis types. We need to pass different parameters to the kernel based on the "CHASSIS_TYPE" information that is passed by the bios in the DMI / SMBIOS tables.

2) We may have a USB stick that can go into multiple boards, and the exact kernel to be loaded depends on the machine information (PRODUCT_NAME etc) passed via the DMI.
---

Changes since v3:

* Added handlers for each --get option that specify their data type's
  length and a formatting function to hopefully make additions easier.

* Added a check that returned string values are terminated properly.

* Made the table-crawling loop exit on an end-of-table structure.

* Copyright assignment was handled.

To address points suggested by Vladimir Serbinenko:

* Moved the EPS-location functions to standalone files to be shared by
  different modules a la ACPI.

* Removed the byte-ordering functions in favor of unaligned functions.

* Removed the hex dump.  Actually, removed the whole structure printing
  function since hex dump was the core of it.  (Using a --get option is
  required now.)

* Made the table-crawling loop abort on invalid structure lengths.

* Switched some return statements to GRUB_ERR_IO.

To address points suggested by Andrei Borzenkov:

* Added support for the SMBIOS v3.0.0 (64-bit) specification.  Note that
  when both v2 and v3 are found, v3 is always used since the
  specification requires the v3 table to be a superset of the v2 table.

* Added an abstraction layer to hide the v2/v3 EPS differences.

* Removed the environment variable export.

* Added an option to get a UUID.

Issues not addressed:

* This patch doesn't add any command options to fetch fields by readable
  names.  I'm not against having the option (aside from the reasons
  Andrei mentioned), but I would like to keep the ability to specify
  structure types and offsets.  Limiting access to known names would
  make it impossible to use the non-standard custom/OEM structures.

* The UUID string representation won't always match dmidecode.  UUIDs
  from v2.6 and newer are required by the spec to use little-endian
  encoding for the first three fields, but it is unspecified before
  that.  In dmidecode, all UUIDs from v2.5 and older are assumed to be
  big-endian.  This patch just uses little-endian.  Do we want to make
  the same assumption to match dmidecode output on old systems?

 docs/grub.texi                       |  69 +++++++
 grub-core/Makefile.core.def          |  15 ++
 grub-core/commands/efi/smbios.c      |  59 ++++++
 grub-core/commands/i386/pc/smbios.c  |  50 +++++
 grub-core/commands/smbios.c          | 372 +++++++++++++++++++++++++++++++++++
 grub-core/efiemu/i386/pc/cfgtables.c |  44 ++---
 include/grub/smbios.h                |  68 +++++++
 7 files changed, 648 insertions(+), 29 deletions(-)
 create mode 100644 grub-core/commands/efi/smbios.c
 create mode 100644 grub-core/commands/i386/pc/smbios.c
 create mode 100644 grub-core/commands/smbios.c
 create mode 100644 include/grub/smbios.h

diff --git a/docs/grub.texi b/docs/grub.texi
index b9f41a7..aa9d73d 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -3830,6 +3830,7 @@ you forget a command, you can run the command @command{help}
 * sha256sum::                   Compute or check SHA256 hash
 * sha512sum::                   Compute or check SHA512 hash
 * sleep::                       Wait for a specified number of seconds
+* smbios::                      Retrieve SMBIOS information
 * source::                      Read a configuration file in same context
 * test::                        Check file types and compare values
 * true::                        Do nothing, successfully
@@ -4944,6 +4945,74 @@ if timeout was interrupted by @key{ESC}.
 @end deffn
 
 
+@node smbios
+@subsection smbios
+
+@deffn Command smbios @
+ [@option{--type} @var{type}] @
+ [@option{--handle} @var{handle}] @
+ [@option{--match} @var{match}] @
+ (@option{--get-byte} | @option{--get-word} | @option{--get-dword} | @
+  @option{--get-qword} | @option{--get-string} | @option{--get-uuid}) @
+ @var{offset} @
+ [@option{--set} @var{variable}]
+Retrieve SMBIOS information.
+
+The @command{smbios} command returns the value of a field in an SMBIOS
+structure.  The following options determine which structure to select.
+
+@itemize @bullet
+@item
+Specifying @option{--type} will select structures with a matching
+@var{type}.  The type can be any integer from 0 to 255.
+@item
+Specifying @option{--handle} will select structures with a matching
+@var{handle}.  The handle can be any integer from 0 to 65535.
+@item
+Specifying @option{--match} will select structure number @var{match} in the
+filtered list of structures; e.g. @code{smbios --type 4 --match 2} will select
+the second Process Information (Type 4) structure.  The list is always ordered
+the same as the hardware's SMBIOS table.  The match number must be a positive
+integer.  If unspecified, the first matching structure will be selected.
+@end itemize
+
+The remaining options determine which field in the selected SMBIOS structure to
+return.  Only one of these options may be specified at a time.
+
+@itemize @bullet
+@item
+When given @option{--get-byte}, return the value of the byte
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-word}, return the value of the word (two bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-dword}, return the value of the dword (four bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-qword}, return the value of the qword (eight bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-string}, return the string with its index found
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-uuid}, return the value of the UUID (sixteen bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@end itemize
+
+The default action is to print the value of the requested field to the console,
+but a variable name can be specified with @option{--set} to store the value
+instead of printing it.
+
+For example, this will store and then display the system manufacturer's name.
+
+@example
+smbios --type 1 --get-string 4 --set system_manufacturer
+echo $system_manufacturer
+@end example
+@end deffn
+
+
 @node source
 @subsection source
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 9764cd2..bb04be6 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1009,6 +1009,21 @@ module = {
 };
 
 module = {
+  name = smbios;
+
+  common = commands/smbios.c;
+  efi = commands/efi/smbios.c;
+  i386_pc = commands/i386/pc/smbios.c;
+  i386_coreboot = commands/i386/pc/smbios.c;
+  i386_multiboot = commands/i386/pc/smbios.c;
+
+  enable = efi;
+  enable = i386_pc;
+  enable = i386_coreboot;
+  enable = i386_multiboot;
+};
+
+module = {
   name = suspend;
   ieee1275 = commands/ieee1275/suspend.c;
   enable = i386_ieee1275;
diff --git a/grub-core/commands/efi/smbios.c b/grub-core/commands/efi/smbios.c
new file mode 100644
index 0000000..9277022
--- /dev/null
+++ b/grub-core/commands/efi/smbios.c
@@ -0,0 +1,59 @@
+/* smbios.c - get smbios tables. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2015  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/smbios.h>
+#include <grub/misc.h>
+#include <grub/efi/efi.h>
+#include <grub/efi/api.h>
+
+struct grub_smbios_eps *
+grub_machine_smbios_get_eps (void)
+{
+  unsigned i;
+  static grub_efi_packed_guid_t smbios_guid = GRUB_EFI_SMBIOS_TABLE_GUID;
+
+  for (i = 0; i < grub_efi_system_table->num_table_entries; i++)
+    {
+      grub_efi_packed_guid_t *guid =
+	&grub_efi_system_table->configuration_table[i].vendor_guid;
+
+      if (! grub_memcmp (guid, &smbios_guid, sizeof (grub_efi_packed_guid_t)))
+	return (struct grub_smbios_eps *)
+	  grub_efi_system_table->configuration_table[i].vendor_table;
+    }
+  return 0;
+}
+
+struct grub_smbios_eps3 *
+grub_machine_smbios_get_eps3 (void)
+{
+  unsigned i;
+  static grub_efi_packed_guid_t smbios3_guid = GRUB_EFI_SMBIOS3_TABLE_GUID;
+
+  for (i = 0; i < grub_efi_system_table->num_table_entries; i++)
+    {
+      grub_efi_packed_guid_t *guid =
+	&grub_efi_system_table->configuration_table[i].vendor_guid;
+
+      if (! grub_memcmp (guid, &smbios3_guid, sizeof (grub_efi_packed_guid_t)))
+	return (struct grub_smbios_eps3 *)
+	  grub_efi_system_table->configuration_table[i].vendor_table;
+    }
+  return 0;
+}
diff --git a/grub-core/commands/i386/pc/smbios.c b/grub-core/commands/i386/pc/smbios.c
new file mode 100644
index 0000000..3bdf3d7
--- /dev/null
+++ b/grub-core/commands/i386/pc/smbios.c
@@ -0,0 +1,50 @@
+/* smbios.c - get smbios tables. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2015  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/acpi.h>
+#include <grub/smbios.h>
+#include <grub/misc.h>
+
+struct grub_smbios_eps *
+grub_machine_smbios_get_eps (void)
+{
+  grub_uint8_t *ptr;
+
+  grub_dprintf ("smbios", "Looking for SMBIOS EPS. Scanning BIOS\n");
+  for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000;
+       ptr += 16)
+    if (grub_memcmp (ptr, "_SM_", 4) == 0
+	&& grub_byte_checksum (ptr, sizeof (struct grub_smbios_eps)) == 0)
+      return (struct grub_smbios_eps *) ptr;
+  return 0;
+}
+
+struct grub_smbios_eps3 *
+grub_machine_smbios_get_eps3 (void)
+{
+  grub_uint8_t *ptr;
+
+  grub_dprintf ("smbios", "Looking for SMBIOS3 EPS. Scanning BIOS\n");
+  for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000;
+       ptr += 16)
+    if (grub_memcmp (ptr, "_SM3_", 5) == 0
+	&& grub_byte_checksum (ptr, sizeof (struct grub_smbios_eps3)) == 0)
+      return (struct grub_smbios_eps3 *) ptr;
+  return 0;
+}
diff --git a/grub-core/commands/smbios.c b/grub-core/commands/smbios.c
new file mode 100644
index 0000000..8218631
--- /dev/null
+++ b/grub-core/commands/smbios.c
@@ -0,0 +1,372 @@
+/* smbios.c - retrieve smbios information. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013,2014,2015  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/env.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/smbios.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+/* Locate the SMBIOS entry point structure depending on the hardware. */
+struct grub_smbios_eps *
+grub_smbios_get_eps (void)
+{
+  static struct grub_smbios_eps *eps = NULL;
+  if (eps != NULL)
+    return eps;
+  eps = grub_machine_smbios_get_eps ();
+  return eps;
+}
+
+/* Locate the SMBIOS3 entry point structure depending on the hardware. */
+struct grub_smbios_eps3 *
+grub_smbios_get_eps3 (void)
+{
+  static struct grub_smbios_eps3 *eps = NULL;
+  if (eps != NULL)
+    return eps;
+  eps = grub_machine_smbios_get_eps3 ();
+  return eps;
+}
+
+/* Abstract useful values found in either the SMBIOS3 or SMBIOS EPS. */
+static struct {
+  grub_addr_t start;
+  grub_addr_t end;
+  grub_uint16_t structures;
+} table_desc = {0, 0, 0};
+
+
+/*
+ * These functions convert values from the various SMBIOS structure field types
+ * into a string formatted to be returned to the user.  They expect that the
+ * structure and offset were already validated.  The given buffer stores the
+ * newly formatted string if needed.  When the requested data is successfully
+ * retrieved and formatted, the pointer to the string is returned; otherwise,
+ * NULL is returned on failure.
+ */
+
+static const char *
+grub_smbios_format_byte (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_snprintf (buffer, size, "%u", structure[offset]);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_word (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint16_t value = grub_get_unaligned16 (structure + offset);
+  grub_snprintf (buffer, size, "%u", value);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_dword (char *buffer, grub_size_t size,
+                          const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint32_t value = grub_get_unaligned32 (structure + offset);
+  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT32_T, value);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_qword (char *buffer, grub_size_t size,
+                          const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint64_t value = grub_get_unaligned64 (structure + offset);
+  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT64_T, value);
+  return (const char *)buffer;
+}
+
+/* The matching string pointer is returned directly to avoid extra copying. */
+static const char *
+grub_smbios_get_string (char *buffer __attribute__ ((unused)),
+                        grub_size_t size __attribute__ ((unused)),
+                        const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  const grub_uint8_t *ptr = structure + structure[1];
+  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
+  const grub_uint8_t referenced_string_number = structure[offset];
+  grub_uint8_t i;
+
+  /* A string referenced with zero is interpreted as unset. */
+  if (referenced_string_number == 0)
+    return NULL;
+
+  /* Search the string set. */
+  for (i = 1; *ptr != 0 && ptr < table_end; i++)
+    if (i == referenced_string_number)
+      {
+        const char *str = (const char *)ptr;
+        while (*ptr++ != 0)
+          if (ptr >= table_end)
+            return NULL; /* The string isn't terminated. */
+        return str;
+      }
+    else
+      while (*ptr++ != 0 && ptr < table_end);
+
+  /* The string number is greater than the number of strings in the set. */
+  return NULL;
+}
+
+static const char *
+grub_smbios_format_uuid (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  const grub_uint8_t *f = structure + offset; /* little-endian fields */
+  const grub_uint8_t *g = f + 8; /* byte-by-byte fields */
+  grub_snprintf (buffer, size,
+                 "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
+                 "%02x%02x-%02x%02x%02x%02x%02x%02x",
+                 f[3], f[2], f[1], f[0], f[5], f[4], f[7], f[6],
+                 g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7]);
+  return (const char *)buffer;
+}
+
+
+/* List the field formatting functions and the number of bytes they need. */
+#define MAXIMUM_FORMAT_LENGTH (sizeof ("ffffffff-ffff-ffff-ffff-ffffffffffff"))
+static const struct {
+  const char *(*format) (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset);
+  grub_uint8_t field_length;
+} field_extractors[] = {
+  {grub_smbios_format_byte, 1},
+  {grub_smbios_format_word, 2},
+  {grub_smbios_format_dword, 4},
+  {grub_smbios_format_qword, 8},
+  {grub_smbios_get_string, 1},
+  {grub_smbios_format_uuid, 16}
+};
+
+/* List command options, with structure field getters ordered as above. */
+#define FIRST_GETTER_OPT (3)
+#define SETTER_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors))
+static const struct grub_arg_option options[] = {
+  {"type",       't', 0, N_("Match entries with the given type."),
+                         N_("type"), ARG_TYPE_INT},
+  {"handle",     'h', 0, N_("Match entries with the given handle."),
+                         N_("handle"), ARG_TYPE_INT},
+  {"match",      'm', 0, N_("Select a structure when several match."),
+                         N_("match"), ARG_TYPE_INT},
+  {"get-byte",   'b', 0, N_("Get the byte's value at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"get-word",   'w', 0, N_("Get two bytes' value at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"get-dword",  'd', 0, N_("Get four bytes' value at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"get-qword",  'q', 0, N_("Get eight bytes' value at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"get-string", 's', 0, N_("Get the string specified at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"get-uuid",   'u', 0, N_("Get the UUID's value at the given offset."),
+                         N_("offset"), ARG_TYPE_INT},
+  {"set",       '\0', 0, N_("Store the value in the given variable name."),
+                         N_("variable"), ARG_TYPE_STRING},
+  {0, 0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Return a matching SMBIOS structure.
+ *
+ * This method can use up to three criteria for selecting a structure:
+ *   - The "type" field                  (use -1 to ignore)
+ *   - The "handle" field                (use -1 to ignore)
+ *   - Which to return if several match  (use  0 to ignore)
+ *
+ * The return value is a pointer to the first matching structure.  If no
+ * structures match the given parameters, NULL is returned.
+ */
+static const grub_uint8_t *
+grub_smbios_match_structure (const grub_int16_t type,
+                             const grub_int32_t handle,
+                             const grub_uint16_t match)
+{
+  const grub_uint8_t *ptr = (const grub_uint8_t *)table_desc.start;
+  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
+  grub_uint16_t structures = table_desc.structures;
+  grub_uint16_t structure_count = 0;
+  grub_uint16_t matches = 0;
+
+  while (ptr < table_end
+         && ptr[1] >= 4 /* Valid structures include the 4-byte header. */
+         && (structure_count++ < structures || structures == 0))
+    {
+      grub_uint16_t structure_handle = grub_get_unaligned16 (ptr + 2);
+      grub_uint8_t structure_type = ptr[0];
+
+      if ((handle < 0 || handle == structure_handle)
+          && (type < 0 || type == structure_type)
+          && (match == 0 || match == ++matches))
+        return ptr;
+
+      else
+        {
+          ptr += ptr[1];
+          while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end);
+        }
+
+      if (structure_type == GRUB_SMBIOS_TYPE_END_OF_TABLE)
+        break;
+    }
+
+  return NULL;
+}
+
+
+static grub_err_t
+grub_cmd_smbios (grub_extcmd_context_t ctxt,
+                 int argc __attribute__ ((unused)),
+                 char **argv __attribute__ ((unused)))
+{
+  struct grub_arg_list *state = ctxt->state;
+
+  grub_int16_t type = -1;
+  grub_int32_t handle = -1;
+  grub_uint16_t match = 0;
+  grub_uint8_t offset = 0;
+
+  const grub_uint8_t *structure;
+  const char *value;
+  char buffer[MAXIMUM_FORMAT_LENGTH];
+  grub_int32_t option;
+  grub_int8_t field_type = -1;
+  grub_uint8_t i;
+
+  if (table_desc.start == 0)
+    return grub_error (GRUB_ERR_IO,
+                       N_("the SMBIOS entry point structure was not found"));
+
+  /* Read the given filtering options. */
+  if (state[0].set)
+    {
+      option = grub_strtol (state[0].arg, NULL, 0);
+      if (option < 0 || option > 255)
+        return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                           N_("the type must be between 0 and 255"));
+      type = (grub_int16_t)option;
+    }
+  if (state[1].set)
+    {
+      option = grub_strtol (state[1].arg, NULL, 0);
+      if (option < 0 || option > 65535)
+        return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                           N_("the handle must be between 0 and 65535"));
+      handle = (grub_int32_t)option;
+    }
+  if (state[2].set)
+    {
+      option = grub_strtol (state[2].arg, NULL, 0);
+      if (option <= 0)
+        return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                           N_("the match must be a positive integer"));
+      match = (grub_uint16_t)option;
+    }
+
+  /* Determine the data type of the structure field to retrieve. */
+  for (i = 0; i < ARRAY_SIZE(field_extractors); i++)
+    if (state[FIRST_GETTER_OPT + i].set)
+      {
+        if (field_type >= 0)
+          return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                             N_("only one --get option is usable at a time"));
+        field_type = i;
+      }
+
+  /* Require a choice of a structure field to return. */
+  if (field_type < 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       N_("one of the --get options is required"));
+
+  /* Locate a matching SMBIOS structure. */
+  structure = grub_smbios_match_structure (type, handle, match);
+  if (structure == NULL)
+    return grub_error (GRUB_ERR_IO,
+                       N_("no structure matched the given options"));
+
+  /* Ensure the requested byte offset is inside the structure. */
+  option = grub_strtol (state[FIRST_GETTER_OPT + field_type].arg, NULL, 0);
+  if (option < 0 || option >= structure[1])
+    return grub_error (GRUB_ERR_OUT_OF_RANGE,
+                       N_("the given offset is outside the structure"));
+
+  /* Ensure the requested data type at the offset is inside the structure. */
+  offset = (grub_uint8_t)option;
+  if (offset + field_extractors[field_type].field_length > structure[1])
+    return grub_error (GRUB_ERR_OUT_OF_RANGE,
+                       N_("the field ends outside the structure"));
+
+  /* Format the requested structure field into a readable string. */
+  value = field_extractors[field_type].format (buffer, sizeof (buffer),
+                                               structure, offset);
+  if (value == NULL)
+    return grub_error (GRUB_ERR_IO,
+                       N_("failed to retrieve the structure field"));
+
+  /* Store or print the formatted value. */
+  if (state[SETTER_OPT].set)
+    grub_env_set (state[SETTER_OPT].arg, value);
+  else
+    grub_printf ("%s\n", value);
+
+  return GRUB_ERR_NONE;
+}
+
+
+static grub_extcmd_t cmd;
+
+GRUB_MOD_INIT(smbios)
+{
+  struct grub_smbios_eps3 *eps3;
+  struct grub_smbios_eps *eps;
+
+  if ((eps3 = grub_smbios_get_eps3 ()))
+    {
+      table_desc.start = (grub_addr_t)eps3->table_address;
+      table_desc.end = table_desc.start + eps3->maximum_table_length;
+      table_desc.structures = 0; /* SMBIOS3 drops the structure count. */
+    }
+  else if ((eps = grub_smbios_get_eps ()))
+    {
+      table_desc.start = (grub_addr_t)eps->intermediate.table_address;
+      table_desc.end = table_desc.start + eps->intermediate.table_length;
+      table_desc.structures = eps->intermediate.structures;
+    }
+
+  cmd = grub_register_extcmd ("smbios", grub_cmd_smbios, 0,
+                              N_("[-t type] [-h handle] [-m match] "
+                                 "(-b|-w|-d|-q|-s|-u) offset "
+                                 "[--set variable]"),
+                              N_("Retrieve SMBIOS information."), options);
+}
+
+GRUB_MOD_FINI(smbios)
+{
+  grub_unregister_extcmd (cmd);
+}
diff --git a/grub-core/efiemu/i386/pc/cfgtables.c b/grub-core/efiemu/i386/pc/cfgtables.c
index ff4e10e..1d88331 100644
--- a/grub-core/efiemu/i386/pc/cfgtables.c
+++ b/grub-core/efiemu/i386/pc/cfgtables.c
@@ -22,18 +22,17 @@
 #include <grub/misc.h>
 #include <grub/mm.h>
 #include <grub/acpi.h>
+#include <grub/smbios.h>
 
 grub_err_t
 grub_machine_efiemu_init_tables (void)
 {
-  grub_uint8_t *ptr;
   void *table;
   grub_err_t err;
   grub_efi_guid_t smbios = GRUB_EFI_SMBIOS_TABLE_GUID;
   grub_efi_guid_t smbios3 = GRUB_EFI_SMBIOS3_TABLE_GUID;
   grub_efi_guid_t acpi20 = GRUB_EFI_ACPI_20_TABLE_GUID;
   grub_efi_guid_t acpi = GRUB_EFI_ACPI_TABLE_GUID;
-  grub_uint8_t found_smbios = 0;
 
   err = grub_efiemu_unregister_configuration_table (smbios);
   if (err)
@@ -48,6 +47,20 @@ grub_machine_efiemu_init_tables (void)
   if (err)
     return err;
 
+  table = grub_smbios_get_eps ();
+  if (table)
+    {
+      err = grub_efiemu_register_configuration_table (smbios, 0, 0, table);
+      if (err)
+	return err;
+    }
+  table = grub_smbios_get_eps3 ();
+  if (table)
+    {
+      err = grub_efiemu_register_configuration_table (smbios3, 0, 0, table);
+      if (err)
+	return err;
+    }
   table = grub_acpi_get_rsdpv1 ();
   if (table)
     {
@@ -63,32 +76,5 @@ grub_machine_efiemu_init_tables (void)
 	return err;
     }
 
-  for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000;
-       ptr += 16)
-    if (found_smbios != 2
-	&& grub_memcmp (ptr, "_SM_", 4) == 0
-	&& grub_byte_checksum (ptr, *(ptr + 5)) == 0)
-      {
-	grub_dprintf ("efiemu", "Registering SMBIOS\n");
-	err = grub_efiemu_register_configuration_table (smbios, 0, 0, ptr);
-	if (err)
-	  return err;
-	if (found_smbios == 3)
-	  break;
-	found_smbios = 2;
-      }
-    else if (found_smbios != 3
-	     && grub_memcmp (ptr, "_SM3_", 5) == 0
-	     && grub_byte_checksum (ptr, *(ptr + 6)) == 0)
-      {
-	grub_dprintf ("efiemu", "Registering SMBIOS3\n");
-	err = grub_efiemu_register_configuration_table (smbios3, 0, 0, ptr);
-	if (err)
-	  return err;
-	if (found_smbios == 2)
-	  break;
-	found_smbios = 3;
-      }
-
   return GRUB_ERR_NONE;
 }
diff --git a/include/grub/smbios.h b/include/grub/smbios.h
new file mode 100644
index 0000000..4b82fa8
--- /dev/null
+++ b/include/grub/smbios.h
@@ -0,0 +1,68 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2015  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_SMBIOS_HEADER
+#define GRUB_SMBIOS_HEADER	1
+
+#include <grub/types.h>
+
+#define GRUB_SMBIOS_TYPE_END_OF_TABLE ((grub_uint8_t)127)
+
+struct grub_smbios_ieps
+{
+  grub_uint8_t anchor[5]; /* "_DMI_" */
+  grub_uint8_t checksum;
+  grub_uint16_t table_length;
+  grub_uint32_t table_address;
+  grub_uint16_t structures;
+  grub_uint8_t revision;
+} GRUB_PACKED;
+
+struct grub_smbios_eps
+{
+  grub_uint8_t anchor[4]; /* "_SM_" */
+  grub_uint8_t checksum;
+  grub_uint8_t length; /* 0x1f */
+  grub_uint8_t version_major;
+  grub_uint8_t version_minor;
+  grub_uint16_t maximum_structure_size;
+  grub_uint8_t revision;
+  grub_uint8_t formatted[5];
+  struct grub_smbios_ieps intermediate;
+} GRUB_PACKED;
+
+struct grub_smbios_eps3
+{
+  grub_uint8_t anchor[5]; /* "_SM3_" */
+  grub_uint8_t checksum;
+  grub_uint8_t length; /* 0x18 */
+  grub_uint8_t version_major;
+  grub_uint8_t version_minor;
+  grub_uint8_t docrev;
+  grub_uint8_t revision;
+  grub_uint8_t reserved;
+  grub_uint32_t maximum_table_length;
+  grub_uint64_t table_address;
+} GRUB_PACKED;
+
+struct grub_smbios_eps *grub_smbios_get_eps (void);
+struct grub_smbios_eps3 *grub_smbios_get_eps3 (void);
+struct grub_smbios_eps *grub_machine_smbios_get_eps (void);
+struct grub_smbios_eps3 *grub_machine_smbios_get_eps3 (void);
+
+#endif /* ! GRUB_SMBIOS_HEADER */
-- 
2.1.0



^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH v4 2/2] Add a module for retrieving SMBIOS information
  2015-08-16  2:27 [PATCH v4 2/2] Add a module for retrieving SMBIOS information David Michael
@ 2015-09-19  6:52 ` Andrei Borzenkov
  0 siblings, 0 replies; 2+ messages in thread
From: Andrei Borzenkov @ 2015-09-19  6:52 UTC (permalink / raw
  To: David Michael, grub-devel; +Cc: phcoder

16.08.2015 05:27, David Michael пишет:
> +
> +@itemize @bullet
> +@item
> +When given @option{--get-byte}, return the value of the byte
> +at @var{offset} bytes into the selected SMBIOS structure.
> +@item
> +When given @option{--get-word}, return the value of the word (two bytes)
> +at @var{offset} bytes into the selected SMBIOS structure.
> +@item
> +When given @option{--get-dword}, return the value of the dword (four bytes)
> +at @var{offset} bytes into the selected SMBIOS structure.
> +@item
> +When given @option{--get-qword}, return the value of the qword (eight bytes)
> +at @var{offset} bytes into the selected SMBIOS structure.

It probably should be more specific that value is returned as unsigned 
integer. Note that test command evaluates integers as simple int so it 
may not be able to compare qword as integers (I assume int is 32 bit on 
all supported platforms).

> +@item
> +When given @option{--get-string}, return the string with its index found
> +at @var{offset} bytes into the selected SMBIOS structure.
> +@item
> +When given @option{--get-uuid}, return the value of the UUID (sixteen bytes)
> +at @var{offset} bytes into the selected SMBIOS structure.

Also format of UUID needs clarification. We had customer call because 
BIOS and monitoring software did not agree on byte order in UUID.

...

> diff --git a/grub-core/commands/smbios.c b/grub-core/commands/smbios.c
> new file mode 100644
> index 0000000..8218631
> --- /dev/null
> +++ b/grub-core/commands/smbios.c
> @@ -0,0 +1,372 @@
> +/* smbios.c - retrieve smbios information. */
> +/*
> + *  GRUB  --  GRand Unified Bootloader
> + *  Copyright (C) 2013,2014,2015  Free Software Foundation, Inc.at

That's new file, how comes it has copyright in the past?

> + *
> + *  GRUB is free software: you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation, either version 3 of the License, or
> + *  (at your option) any later version.
> + *
> + *  GRUB is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <grub/dl.h>
> +#include <grub/env.h>
> +#include <grub/extcmd.h>
> +#include <grub/i18n.h>
> +#include <grub/misc.h>
> +#include <grub/mm.h>
> +#include <grub/smbios.h>
> +
> +GRUB_MOD_LICENSE ("GPLv3+");
> +
> +
> +/* Locate the SMBIOS entry point structure depending on the hardware. */
> +struct grub_smbios_eps *
> +grub_smbios_get_eps (void)
> +{
> +  static struct grub_smbios_eps *eps = NULL;
> +  if (eps != NULL)
> +    return eps;
> +  eps = grub_machine_smbios_get_eps ();
> +  return eps;
> +}
> +
> +/* Locate the SMBIOS3 entry point structure depending on the hardware. */
> +struct grub_smbios_eps3 *
> +grub_smbios_get_eps3 (void)
> +{
> +  static struct grub_smbios_eps3 *eps = NULL;
> +  if (eps != NULL)
> +    return eps;
> +  eps = grub_machine_smbios_get_eps3 ();
> +  return eps;
> +}
> +
> +/* Abstract useful values found in either the SMBIOS3 or SMBIOS EPS. */
> +static struct {
> +  grub_addr_t start;
> +  grub_addr_t end;
> +  grub_uint16_t structures;
> +} table_desc = {0, 0, 0};
> +
> +
> +/*
> + * These functions convert values from the various SMBIOS structure field types
> + * into a string formatted to be returned to the user.  They expect that the
> + * structure and offset were already validated.  The given buffer stores the
> + * newly formatted string if needed.  When the requested data is successfully
> + * retrieved and formatted, the pointer to the string is returned; otherwise,
> + * NULL is returned on failure.
> + */
> +
> +static const char *
> +grub_smbios_format_byte (char *buffer, grub_size_t size,
> +                         const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  grub_snprintf (buffer, size, "%u", structure[offset]);
> +  return (const char *)buffer;
> +}
> +
> +static const char *
> +grub_smbios_format_word (char *buffer, grub_size_t size,
> +                         const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  grub_uint16_t value = grub_get_unaligned16 (structure + offset);
> +  grub_snprintf (buffer, size, "%u", value);
> +  return (const char *)buffer;
> +}
> +
> +static const char *
> +grub_smbios_format_dword (char *buffer, grub_size_t size,
> +                          const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  grub_uint32_t value = grub_get_unaligned32 (structure + offset);
> +  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT32_T, value);
> +  return (const char *)buffer;
> +}
> +
> +static const char *
> +grub_smbios_format_qword (char *buffer, grub_size_t size,
> +                          const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  grub_uint64_t value = grub_get_unaligned64 (structure + offset);
> +  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT64_T, value);
> +  return (const char *)buffer;
> +}
> +
> +/* The matching string pointer is returned directly to avoid extra copying. */
> +static const char *
> +grub_smbios_get_string (char *buffer __attribute__ ((unused)),
> +                        grub_size_t size __attribute__ ((unused)),
> +                        const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  const grub_uint8_t *ptr = structure + structure[1];
> +  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
> +  const grub_uint8_t referenced_string_number = structure[offset];
> +  grub_uint8_t i;
> +
> +  /* A string referenced with zero is interpreted as unset. */
> +  if (referenced_string_number == 0)
> +    return NULL;
> +
> +  /* Search the string set. */
> +  for (i = 1; *ptr != 0 && ptr < table_end; i++)
> +    if (i == referenced_string_number)
> +      {
> +        const char *str = (const char *)ptr;
> +        while (*ptr++ != 0)
> +          if (ptr >= table_end)
> +            return NULL; /* The string isn't terminated. */
> +        return str;
> +      }
> +    else
> +      while (*ptr++ != 0 && ptr < table_end);
> +
> +  /* The string number is greater than the number of strings in the set. */
> +  return NULL;
> +}
> +
> +static const char *
> +grub_smbios_format_uuid (char *buffer, grub_size_t size,
> +                         const grub_uint8_t *structure, grub_uint8_t offset)
> +{
> +  const grub_uint8_t *f = structure + offset; /* little-endian fields */
> +  const grub_uint8_t *g = f + 8; /* byte-by-byte fields */
> +  grub_snprintf (buffer, size,
> +                 "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
> +                 "%02x%02x-%02x%02x%02x%02x%02x%02x",
> +                 f[3], f[2], f[1], f[0], f[5], f[4], f[7], f[6],
> +                 g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7]);
> +  return (const char *)buffer;
> +}
> +
> +
> +/* List the field formatting functions and the number of bytes they need. */
> +#define MAXIMUM_FORMAT_LENGTH (sizeof ("ffffffff-ffff-ffff-ffff-ffffffffffff"))
> +static const struct {
> +  const char *(*format) (char *buffer, grub_size_t size,
> +                         const grub_uint8_t *structure, grub_uint8_t offset);
> +  grub_uint8_t field_length;
> +} field_extractors[] = {
> +  {grub_smbios_format_byte, 1},
> +  {grub_smbios_format_word, 2},
> +  {grub_smbios_format_dword, 4},
> +  {grub_smbios_format_qword, 8},
> +  {grub_smbios_get_string, 1},
> +  {grub_smbios_format_uuid, 16}
> +};
> +
> +/* List command options, with structure field getters ordered as above. */
> +#define FIRST_GETTER_OPT (3)
> +#define SETTER_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors))
> +static const struct grub_arg_option options[] = {
> +  {"type",       't', 0, N_("Match entries with the given type."),
> +                         N_("type"), ARG_TYPE_INT},
> +  {"handle",     'h', 0, N_("Match entries with the given handle."),
> +                         N_("handle"), ARG_TYPE_INT},
> +  {"match",      'm', 0, N_("Select a structure when several match."),
> +                         N_("match"), ARG_TYPE_INT},
> +  {"get-byte",   'b', 0, N_("Get the byte's value at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"get-word",   'w', 0, N_("Get two bytes' value at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"get-dword",  'd', 0, N_("Get four bytes' value at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"get-qword",  'q', 0, N_("Get eight bytes' value at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"get-string", 's', 0, N_("Get the string specified at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"get-uuid",   'u', 0, N_("Get the UUID's value at the given offset."),
> +                         N_("offset"), ARG_TYPE_INT},
> +  {"set",       '\0', 0, N_("Store the value in the given variable name."),
> +                         N_("variable"), ARG_TYPE_STRING},
> +  {0, 0, 0, 0, 0, 0}
> +};
> +
> +
> +/*
> + * Return a matching SMBIOS structure.
> + *
> + * This method can use up to three criteria for selecting a structure:
> + *   - The "type" field                  (use -1 to ignore)
> + *   - The "handle" field                (use -1 to ignore)
> + *   - Which to return if several match  (use  0 to ignore)
> + *
> + * The return value is a pointer to the first matching structure.  If no
> + * structures match the given parameters, NULL is returned.
> + */
> +static const grub_uint8_t *
> +grub_smbios_match_structure (const grub_int16_t type,
> +                             const grub_int32_t handle,
> +                             const grub_uint16_t match)
> +{
> +  const grub_uint8_t *ptr = (const grub_uint8_t *)table_desc.start;
> +  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
> +  grub_uint16_t structures = table_desc.structures;
> +  grub_uint16_t structure_count = 0;
> +  grub_uint16_t matches = 0;
> +
> +  while (ptr < table_end
> +         && ptr[1] >= 4 /* Valid structures include the 4-byte header. */
> +         && (structure_count++ < structures || structures == 0))
> +    {
> +      grub_uint16_t structure_handle = grub_get_unaligned16 (ptr + 2);
> +      grub_uint8_t structure_type = ptr[0];
> +
> +      if ((handle < 0 || handle == structure_handle)
> +          && (type < 0 || type == structure_type)
> +          && (match == 0 || match == ++matches))
> +        return ptr;
> +
> +      else
> +        {
> +          ptr += ptr[1];
> +          while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end);
> +        }
> +
> +      if (structure_type == GRUB_SMBIOS_TYPE_END_OF_TABLE)
> +        break;
> +    }
> +
> +  return NULL;
> +}
> +
> +
> +static grub_err_t
> +grub_cmd_smbios (grub_extcmd_context_t ctxt,
> +                 int argc __attribute__ ((unused)),
> +                 char **argv __attribute__ ((unused)))
> +{
> +  struct grub_arg_list *state = ctxt->state;
> +
> +  grub_int16_t type = -1;
> +  grub_int32_t handle = -1;
> +  grub_uint16_t match = 0;
> +  grub_uint8_t offset = 0;
> +
> +  const grub_uint8_t *structure;
> +  const char *value;
> +  char buffer[MAXIMUM_FORMAT_LENGTH];
> +  grub_int32_t option;
> +  grub_int8_t field_type = -1;
> +  grub_uint8_t i;
> +
> +  if (table_desc.start == 0)
> +    return grub_error (GRUB_ERR_IO,
> +                       N_("the SMBIOS entry point structure was not found"));
> +
> +  /* Read the given filtering options. */
> +  if (state[0].set)
> +    {
> +      option = grub_strtol (state[0].arg, NULL, 0);
> +      if (option < 0 || option > 255)
> +        return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                           N_("the type must be between 0 and 255"));
> +      type = (grub_int16_t)option;
> +    }
> +  if (state[1].set)
> +    {
> +      option = grub_strtol (state[1].arg, NULL, 0);
> +      if (option < 0 || option > 65535)
> +        return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                           N_("the handle must be between 0 and 65535"));
> +      handle = (grub_int32_t)option;
> +    }
> +  if (state[2].set)
> +    {
> +      option = grub_strtol (state[2].arg, NULL, 0);
> +      if (option <= 0)
> +        return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                           N_("the match must be a positive integer"));
> +      match = (grub_uint16_t)option;
> +    }
> +
> +  /* Determine the data type of the structure field to retrieve. */
> +  for (i = 0; i < ARRAY_SIZE(field_extractors); i++)
> +    if (state[FIRST_GETTER_OPT + i].set)
> +      {
> +        if (field_type >= 0)
> +          return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                             N_("only one --get option is usable at a time"));
> +        field_type = i;
> +      }
> +
> +  /* Require a choice of a structure field to return. */
> +  if (field_type < 0)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT,
> +                       N_("one of the --get options is required"));
> +
> +  /* Locate a matching SMBIOS structure. */
> +  structure = grub_smbios_match_structure (type, handle, match);
> +  if (structure == NULL)
> +    return grub_error (GRUB_ERR_IO,
> +                       N_("no structure matched the given options"));
> +
> +  /* Ensure the requested byte offset is inside the structure. */
> +  option = grub_strtol (state[FIRST_GETTER_OPT + field_type].arg, NULL, 0);
> +  if (option < 0 || option >= structure[1])
> +    return grub_error (GRUB_ERR_OUT_OF_RANGE,
> +                       N_("the given offset is outside the structure"));
> +
> +  /* Ensure the requested data type at the offset is inside the structure. */
> +  offset = (grub_uint8_t)option;
> +  if (offset + field_extractors[field_type].field_length > structure[1])
> +    return grub_error (GRUB_ERR_OUT_OF_RANGE,
> +                       N_("the field ends outside the structure"));
> +
> +  /* Format the requested structure field into a readable string. */
> +  value = field_extractors[field_type].format (buffer, sizeof (buffer),
> +                                               structure, offset);

Do you really need to pass buffer as argument? It is static anyway.

Otherwise I'm fine with it (without running through actual compile) but 
I suspect this will require contributor agreement and that's what only 
Vladimir can decide.


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2015-09-19  6:53 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-08-16  2:27 [PATCH v4 2/2] Add a module for retrieving SMBIOS information David Michael
2015-09-19  6:52 ` Andrei Borzenkov

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 read-only IMAP folder(s) and NNTP newsgroup(s).