smbios.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /* smbios.c - retrieve smbios information. */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2019 Free Software Foundation, Inc.
  5. *
  6. * GRUB is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * GRUB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <grub/dl.h>
  20. #include <grub/env.h>
  21. #include <grub/extcmd.h>
  22. #include <grub/i18n.h>
  23. #include <grub/misc.h>
  24. #include <grub/mm.h>
  25. #include <grub/smbios.h>
  26. GRUB_MOD_LICENSE ("GPLv3+");
  27. /* Abstract useful values found in either the SMBIOS3 or SMBIOS EPS. */
  28. static struct {
  29. grub_addr_t start;
  30. grub_addr_t end;
  31. grub_uint16_t structures;
  32. } table_desc;
  33. static grub_extcmd_t cmd;
  34. /* Locate the SMBIOS entry point structure depending on the hardware. */
  35. struct grub_smbios_eps *
  36. grub_smbios_get_eps (void)
  37. {
  38. static struct grub_smbios_eps *eps = NULL;
  39. if (eps != NULL)
  40. return eps;
  41. eps = grub_machine_smbios_get_eps ();
  42. return eps;
  43. }
  44. /* Locate the SMBIOS3 entry point structure depending on the hardware. */
  45. static struct grub_smbios_eps3 *
  46. grub_smbios_get_eps3 (void)
  47. {
  48. static struct grub_smbios_eps3 *eps = NULL;
  49. if (eps != NULL)
  50. return eps;
  51. eps = grub_machine_smbios_get_eps3 ();
  52. return eps;
  53. }
  54. static char *
  55. linux_string (const char *value)
  56. {
  57. char *out = grub_malloc( grub_strlen (value) + 1);
  58. const char *src = value;
  59. char *dst = out;
  60. for (; *src; src++)
  61. if (*src > ' ' && *src < 127 && *src != ':')
  62. *dst++ = *src;
  63. *dst = 0;
  64. return out;
  65. }
  66. /*
  67. * These functions convert values from the various SMBIOS structure field types
  68. * into a string formatted to be returned to the user. They expect that the
  69. * structure and offset were already validated. When the requested data is
  70. * successfully retrieved and formatted, the pointer to the string is returned;
  71. * otherwise, NULL is returned on failure. Don't free the result.
  72. */
  73. static const char *
  74. grub_smbios_format_byte (const grub_uint8_t *structure, grub_uint8_t offset)
  75. {
  76. static char buffer[sizeof ("255")];
  77. grub_snprintf (buffer, sizeof (buffer), "%u", structure[offset]);
  78. return (const char *)buffer;
  79. }
  80. static const char *
  81. grub_smbios_format_word (const grub_uint8_t *structure, grub_uint8_t offset)
  82. {
  83. static char buffer[sizeof ("65535")];
  84. grub_uint16_t value = grub_get_unaligned16 (structure + offset);
  85. grub_snprintf (buffer, sizeof (buffer), "%u", value);
  86. return (const char *)buffer;
  87. }
  88. static const char *
  89. grub_smbios_format_dword (const grub_uint8_t *structure, grub_uint8_t offset)
  90. {
  91. static char buffer[sizeof ("4294967295")];
  92. grub_uint32_t value = grub_get_unaligned32 (structure + offset);
  93. grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT32_T, value);
  94. return (const char *)buffer;
  95. }
  96. static const char *
  97. grub_smbios_format_qword (const grub_uint8_t *structure, grub_uint8_t offset)
  98. {
  99. static char buffer[sizeof ("18446744073709551615")];
  100. grub_uint64_t value = grub_get_unaligned64 (structure + offset);
  101. grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT64_T, value);
  102. return (const char *)buffer;
  103. }
  104. static const char *
  105. grub_smbios_get_string (const grub_uint8_t *structure, grub_uint8_t offset)
  106. {
  107. const grub_uint8_t *ptr = structure + structure[1];
  108. const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
  109. const grub_uint8_t referenced_string_number = structure[offset];
  110. grub_uint8_t i;
  111. /* A string referenced with zero is interpreted as unset. */
  112. if (referenced_string_number == 0)
  113. return NULL;
  114. /* Search the string set. */
  115. for (i = 1; *ptr != 0 && ptr < table_end; i++)
  116. if (i == referenced_string_number)
  117. {
  118. const char *str = (const char *)ptr;
  119. while (*ptr++ != 0)
  120. if (ptr >= table_end)
  121. return NULL; /* The string isn't terminated. */
  122. return str;
  123. }
  124. else
  125. while (*ptr++ != 0 && ptr < table_end);
  126. /* The string number is greater than the number of strings in the set. */
  127. return NULL;
  128. }
  129. static const char *
  130. grub_smbios_format_uuid (const grub_uint8_t *structure, grub_uint8_t offset)
  131. {
  132. static char buffer[sizeof ("ffffffff-ffff-ffff-ffff-ffffffffffff")];
  133. const grub_uint8_t *f = structure + offset; /* little-endian fields */
  134. const grub_uint8_t *g = f + 8; /* byte-by-byte fields */
  135. grub_snprintf (buffer, sizeof (buffer),
  136. "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
  137. "%02x%02x-%02x%02x%02x%02x%02x%02x",
  138. f[3], f[2], f[1], f[0], f[5], f[4], f[7], f[6],
  139. g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7]);
  140. return (const char *)buffer;
  141. }
  142. /* List the field formatting functions and the number of bytes they need. */
  143. static const struct {
  144. const char *(*format) (const grub_uint8_t *structure, grub_uint8_t offset);
  145. grub_uint8_t field_length;
  146. } field_extractors[] = {
  147. {grub_smbios_format_byte, 1},
  148. {grub_smbios_format_word, 2},
  149. {grub_smbios_format_dword, 4},
  150. {grub_smbios_format_qword, 8},
  151. {grub_smbios_get_string, 1},
  152. {grub_smbios_format_uuid, 16}
  153. };
  154. /* List command options, with structure field getters ordered as above. */
  155. #define FIRST_GETTER_OPT (3)
  156. #define SETTER_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors))
  157. #define LINUX_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors) + 1)
  158. static const struct grub_arg_option options[] = {
  159. {"type", 't', 0, N_("Match structures with the given type."),
  160. N_("type"), ARG_TYPE_INT},
  161. {"handle", 'h', 0, N_("Match structures with the given handle."),
  162. N_("handle"), ARG_TYPE_INT},
  163. {"match", 'm', 0, N_("Select a structure when several match."),
  164. N_("match"), ARG_TYPE_INT},
  165. {"get-byte", 'b', 0, N_("Get the byte's value at the given offset."),
  166. N_("offset"), ARG_TYPE_INT},
  167. {"get-word", 'w', 0, N_("Get two bytes' value at the given offset."),
  168. N_("offset"), ARG_TYPE_INT},
  169. {"get-dword", 'd', 0, N_("Get four bytes' value at the given offset."),
  170. N_("offset"), ARG_TYPE_INT},
  171. {"get-qword", 'q', 0, N_("Get eight bytes' value at the given offset."),
  172. N_("offset"), ARG_TYPE_INT},
  173. {"get-string", 's', 0, N_("Get the string specified at the given offset."),
  174. N_("offset"), ARG_TYPE_INT},
  175. {"get-uuid", 'u', 0, N_("Get the UUID's value at the given offset."),
  176. N_("offset"), ARG_TYPE_INT},
  177. {"set", '\0', 0, N_("Store the value in the given variable name."),
  178. N_("variable"), ARG_TYPE_STRING},
  179. {"linux", '\0', 0, N_("Filter the result like linux does."),
  180. N_("variable"), ARG_TYPE_NONE},
  181. {0, 0, 0, 0, 0, 0}
  182. };
  183. /*
  184. * Return a matching SMBIOS structure.
  185. *
  186. * This method can use up to three criteria for selecting a structure:
  187. * - The "type" field (use -1 to ignore)
  188. * - The "handle" field (use -1 to ignore)
  189. * - Which to return if several match (use 0 to ignore)
  190. *
  191. * The return value is a pointer to the first matching structure. If no
  192. * structures match the given parameters, NULL is returned.
  193. */
  194. static const grub_uint8_t *
  195. grub_smbios_match_structure (const grub_int16_t type,
  196. const grub_int32_t handle,
  197. const grub_uint16_t match)
  198. {
  199. const grub_uint8_t *ptr = (const grub_uint8_t *)table_desc.start;
  200. const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
  201. grub_uint16_t structures = table_desc.structures;
  202. grub_uint16_t structure_count = 0;
  203. grub_uint16_t matches = 0;
  204. while (ptr < table_end
  205. && ptr[1] >= 4 /* Valid structures include the 4-byte header. */
  206. && (structure_count++ < structures || structures == 0))
  207. {
  208. grub_uint16_t structure_handle = grub_get_unaligned16 (ptr + 2);
  209. grub_uint8_t structure_type = ptr[0];
  210. if ((handle < 0 || handle == structure_handle)
  211. && (type < 0 || type == structure_type)
  212. && (match == 0 || match == ++matches))
  213. return ptr;
  214. else
  215. {
  216. ptr += ptr[1];
  217. while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end);
  218. }
  219. if (structure_type == GRUB_SMBIOS_TYPE_END_OF_TABLE)
  220. break;
  221. }
  222. return NULL;
  223. }
  224. static grub_err_t
  225. grub_cmd_smbios (grub_extcmd_context_t ctxt,
  226. int argc __attribute__ ((unused)),
  227. char **argv __attribute__ ((unused)))
  228. {
  229. struct grub_arg_list *state = ctxt->state;
  230. grub_int16_t type = -1;
  231. grub_int32_t handle = -1;
  232. grub_uint16_t match = 0;
  233. grub_uint8_t offset = 0;
  234. const grub_uint8_t *structure;
  235. const char *value;
  236. char *modified_value = NULL;
  237. grub_int32_t option;
  238. grub_int8_t field_type = -1;
  239. grub_uint8_t i;
  240. if (table_desc.start == 0)
  241. return grub_error (GRUB_ERR_IO,
  242. N_("the SMBIOS entry point structure was not found"));
  243. /* Read the given filtering options. */
  244. if (state[0].set)
  245. {
  246. option = grub_strtol (state[0].arg, NULL, 0);
  247. if (option < 0 || option > 255)
  248. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  249. N_("the type must be between 0 and 255"));
  250. type = (grub_int16_t)option;
  251. }
  252. if (state[1].set)
  253. {
  254. option = grub_strtol (state[1].arg, NULL, 0);
  255. if (option < 0 || option > 65535)
  256. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  257. N_("the handle must be between 0 and 65535"));
  258. handle = (grub_int32_t)option;
  259. }
  260. if (state[2].set)
  261. {
  262. option = grub_strtol (state[2].arg, NULL, 0);
  263. if (option <= 0 || option > 65535)
  264. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  265. N_("the match must be a positive integer"));
  266. match = (grub_uint16_t)option;
  267. }
  268. /* Determine the data type of the structure field to retrieve. */
  269. for (i = 0; i < ARRAY_SIZE(field_extractors); i++)
  270. if (state[FIRST_GETTER_OPT + i].set)
  271. {
  272. if (field_type >= 0)
  273. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  274. N_("only one --get option is usable at a time"));
  275. field_type = i;
  276. }
  277. /* Require a choice of a structure field to return. */
  278. if (field_type < 0)
  279. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  280. N_("one of the --get options is required"));
  281. /* Locate a matching SMBIOS structure. */
  282. structure = grub_smbios_match_structure (type, handle, match);
  283. if (structure == NULL)
  284. return grub_error (GRUB_ERR_IO,
  285. N_("no structure matched the given options"));
  286. /* Ensure the requested byte offset is inside the structure. */
  287. option = grub_strtol (state[FIRST_GETTER_OPT + field_type].arg, NULL, 0);
  288. if (option < 0 || option >= structure[1])
  289. return grub_error (GRUB_ERR_OUT_OF_RANGE,
  290. N_("the given offset is outside the structure"));
  291. /* Ensure the requested data type at the offset is inside the structure. */
  292. offset = (grub_uint8_t)option;
  293. if (offset + field_extractors[field_type].field_length > structure[1])
  294. return grub_error (GRUB_ERR_OUT_OF_RANGE,
  295. N_("the field ends outside the structure"));
  296. /* Format the requested structure field into a readable string. */
  297. value = field_extractors[field_type].format (structure, offset);
  298. if (value == NULL)
  299. return grub_error (GRUB_ERR_IO,
  300. N_("failed to retrieve the structure field"));
  301. if (state[LINUX_OPT].set)
  302. value = modified_value = linux_string (value);
  303. /* Store or print the formatted value. */
  304. if (state[SETTER_OPT].set)
  305. grub_env_set (state[SETTER_OPT].arg, value);
  306. else
  307. grub_printf ("%s\n", value);
  308. grub_free(modified_value);
  309. return GRUB_ERR_NONE;
  310. }
  311. GRUB_MOD_INIT(smbios)
  312. {
  313. struct grub_smbios_eps3 *eps3;
  314. struct grub_smbios_eps *eps;
  315. if ((eps3 = grub_smbios_get_eps3 ()))
  316. {
  317. table_desc.start = (grub_addr_t)eps3->table_address;
  318. table_desc.end = table_desc.start + eps3->maximum_table_length;
  319. table_desc.structures = 0; /* SMBIOS3 drops the structure count. */
  320. }
  321. else if ((eps = grub_smbios_get_eps ()))
  322. {
  323. table_desc.start = (grub_addr_t)eps->intermediate.table_address;
  324. table_desc.end = table_desc.start + eps->intermediate.table_length;
  325. table_desc.structures = eps->intermediate.structures;
  326. }
  327. cmd = grub_register_extcmd ("smbios", grub_cmd_smbios, 0,
  328. N_("[-t type] [-h handle] [-m match] "
  329. "(-b|-w|-d|-q|-s|-u) offset "
  330. "[--set variable]"),
  331. N_("Retrieve SMBIOS information."), options);
  332. }
  333. GRUB_MOD_FINI(smbios)
  334. {
  335. grub_unregister_extcmd (cmd);
  336. }