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