|
| 1 | +/* |
| 2 | + * memsize.c - Iterate through GRUB memory map to get the total amount of |
| 3 | + * available RAM. The memory map does NOT reflect the total physical |
| 4 | + * amount, so use with care. |
| 5 | + * It can export the value to the environment, to make great use of tests. |
| 6 | + */ |
| 7 | +/* |
| 8 | + * GRUB -- GRand Unified Bootloader |
| 9 | + * Copyright (C) 2003,2007 Free Software Foundation, Inc. |
| 10 | + * Copyright (C) 2003 NIIBE Yutaka <[email protected]> |
| 11 | + * |
| 12 | + * GRUB is free software: you can redistribute it and/or modify |
| 13 | + * it under the terms of the GNU General Public License as published by |
| 14 | + * the Free Software Foundation, either version 3 of the License, or |
| 15 | + * (at your option) any later version. |
| 16 | + * |
| 17 | + * GRUB is distributed in the hope that it will be useful, |
| 18 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 20 | + * GNU General Public License for more details. |
| 21 | + * |
| 22 | + * You should have received a copy of the GNU General Public License |
| 23 | + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| 24 | + */ |
| 25 | + |
| 26 | +#include <grub/types.h> |
| 27 | +#include <grub/misc.h> |
| 28 | +#include <grub/memory.h> |
| 29 | +#include <grub/err.h> |
| 30 | +#include <grub/env.h> |
| 31 | +#include <grub/dl.h> |
| 32 | +#include <grub/extcmd.h> |
| 33 | +#include <grub/i18n.h> |
| 34 | + |
| 35 | +GRUB_MOD_LICENSE ("GPLv3+"); |
| 36 | + |
| 37 | +const char *default_varname = "memsize"; |
| 38 | +grub_extcmd_t cmd; |
| 39 | +grub_uint64_t grub_avail_mem_bytes = 0; |
| 40 | + |
| 41 | +static const struct grub_arg_option grub_options_memsize[] = |
| 42 | + { |
| 43 | + {"byte", 'b', GRUB_ARG_OPTION_OPTIONAL, |
| 44 | + N_("Print and export the vaule as number of bytes."), 0, 0}, |
| 45 | + {"kibi", 'K', GRUB_ARG_OPTION_OPTIONAL, |
| 46 | + N_("Print and export the value as kibibytes."), 0, 0}, |
| 47 | + {"mebi", 'M', GRUB_ARG_OPTION_OPTIONAL, |
| 48 | + N_("Print and export the value as mebibytes."), 0, 0}, |
| 49 | + {"gibi", 'G', GRUB_ARG_OPTION_OPTIONAL, |
| 50 | + N_("Print and export the value as gibibytes."), 0, 0}, |
| 51 | + {"quiet", 'q', GRUB_ARG_OPTION_OPTIONAL, |
| 52 | + N_("Do not print anyting, just set a variable quietly."), 0, 0}, |
| 53 | + {"set", 's', GRUB_ARG_OPTION_OPTIONAL, |
| 54 | + N_("Set a variable to the vaule as the unit specified."), |
| 55 | + N_("VARNAME"), ARG_TYPE_STRING}, |
| 56 | + {0, 0, 0, 0, 0, 0}, |
| 57 | +}; |
| 58 | + |
| 59 | +/* Corresponding strings of the units */ |
| 60 | +const char* unit_strs[] = { |
| 61 | + "B", "KiB", "MiB", "GiB", |
| 62 | +}; |
| 63 | + |
| 64 | +/* Corresponding vaules to convert */ |
| 65 | +const int conversions[] = { |
| 66 | + 0, 10, 20, 30 |
| 67 | +}; |
| 68 | + |
| 69 | +enum grub_options_memsize |
| 70 | + { |
| 71 | + UNIT_BYTES, |
| 72 | + UNIT_BIN_KIB, |
| 73 | + UNIT_BIN_MIB, |
| 74 | + UNIT_BIN_GIB, |
| 75 | + FLAG_QUIET, |
| 76 | + HAS_CUSTOM_VARIABLE, |
| 77 | +}; |
| 78 | + |
| 79 | +#ifdef GRUB_MACHINE_EMU |
| 80 | +__attribute__ ((unused)) |
| 81 | +#endif |
| 82 | +static int |
| 83 | +memsize_hook (grub_uint64_t addr __attribute__ ((unused)), |
| 84 | + grub_uint64_t size, grub_memory_type_t type, void *data) |
| 85 | +{ |
| 86 | + /* |
| 87 | + * Iterate through GRUB memory map. |
| 88 | + * FIXME: Which type of memory region should we consider to add up? |
| 89 | + */ |
| 90 | + grub_uint64_t *total = (grub_uint64_t *) data; |
| 91 | + if (type == GRUB_MEMORY_AVAILABLE) { |
| 92 | + *total += size; |
| 93 | + } |
| 94 | + return GRUB_ERR_NONE; |
| 95 | +} |
| 96 | + |
| 97 | +static grub_err_t |
| 98 | +grub_cmd_memsize (grub_extcmd_context_t ctxt, |
| 99 | + int argc __attribute__ ((unused)), |
| 100 | + char **args __attribute__ ((unused))) |
| 101 | +{ |
| 102 | + /* Parsed argument list. */ |
| 103 | + struct grub_arg_list *state = ctxt->state; |
| 104 | + /* Length of the variable name. */ |
| 105 | + grub_size_t len = 0; |
| 106 | + /* Buffer of variable name. */ |
| 107 | + char buf[64] = {0}; |
| 108 | + /* Requested unit to convert. */ |
| 109 | + enum grub_options_memsize unit = UNIT_BIN_MIB; |
| 110 | + enum grub_options_memsize opt = UNIT_BYTES; |
| 111 | + /* Flag to make sure only one unit is specified. */ |
| 112 | + int unit_switch_is_set = 0; |
| 113 | + /* Total amount of available RAM. */ |
| 114 | + grub_uint64_t avail_mem = 0; |
| 115 | + /* The converted value. */ |
| 116 | + grub_uint64_t converted_avail_mem = 0; |
| 117 | + char *converted_avail_mem_str = 0; |
| 118 | + /* Record errors during grub_machine_mmap_iterate. */ |
| 119 | +#ifdef GRUB_MACHINE_EMU |
| 120 | + __attribute__ ((unused)) |
| 121 | +#endif |
| 122 | + grub_err_t err; |
| 123 | + /* Sanity checks */ |
| 124 | + /* Only one unit switch is allowed. */ |
| 125 | + for (; opt < FLAG_QUIET; opt++) |
| 126 | + { |
| 127 | + if (state[opt].set != 0) |
| 128 | + { |
| 129 | + if (unit_switch_is_set != 0) |
| 130 | + return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| 131 | + N_("Only one unit switch is allowed")); |
| 132 | + else |
| 133 | + { |
| 134 | + unit = opt; |
| 135 | + unit_switch_is_set = 1; |
| 136 | + } |
| 137 | + } |
| 138 | + } |
| 139 | + /* Check if the specified variable name is valid. */ |
| 140 | + if (state[HAS_CUSTOM_VARIABLE].set != 0) |
| 141 | + { |
| 142 | + char *str = state[HAS_CUSTOM_VARIABLE].arg; |
| 143 | + len = grub_strlen (str); |
| 144 | + if (len > 63) { |
| 145 | + return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| 146 | + N_("Variable names should not exceed 63 characters")); |
| 147 | + } else if (len == 0) { |
| 148 | + return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| 149 | + N_("Variable name expected but not specified")); |
| 150 | + } |
| 151 | + /* [0-9a-zA-Z_] for variable names */ |
| 152 | + for (char *c = str; c < str + len; c++) |
| 153 | + { |
| 154 | + if ( !(*c >= '0' && *c <= '9') |
| 155 | + && !(*c >= 'a' && *c <= 'z') |
| 156 | + && !(*c >= 'A' && *c <= 'Z') |
| 157 | + && !(*c == '_')) |
| 158 | + return grub_error(GRUB_ERR_BAD_ARGUMENT, |
| 159 | + N_("Invalid variable name")); |
| 160 | + } |
| 161 | + grub_strncpy (buf, str, len); |
| 162 | + } |
| 163 | + else |
| 164 | + { |
| 165 | + len = grub_strlen (default_varname); |
| 166 | + grub_strncpy (buf, default_varname, len); |
| 167 | + } |
| 168 | + /* Iterate through GRUB's memory map. */ |
| 169 | +#ifndef GRUB_MACHINE_EMU |
| 170 | + err = grub_machine_mmap_iterate (memsize_hook, (void *) &avail_mem); |
| 171 | + if (err != GRUB_ERR_NONE) |
| 172 | + return err; |
| 173 | +#endif |
| 174 | + /* |
| 175 | + * Convert the vaule to the specified unit, and export it to the |
| 176 | + * environment. |
| 177 | + * Can not use division, which might requires libgcc! |
| 178 | + */ |
| 179 | + converted_avail_mem = avail_mem >> conversions[unit]; |
| 180 | + /* |
| 181 | + * Unfortunately the test command can only perform comparisons across |
| 182 | + * signed ints, or the test command will fail. We need to check this in |
| 183 | + * order to make it usable to test command. |
| 184 | + */ |
| 185 | + if (converted_avail_mem > GRUB_INT_MAX) |
| 186 | + { |
| 187 | + return grub_error (GRUB_ERR_BAD_NUMBER, |
| 188 | + N_("Value is too large (%" |
| 189 | + PRIuGRUB_UINT64_T " > %" |
| 190 | + PRIdGRUB_INT32_T ")"), |
| 191 | + converted_avail_mem, |
| 192 | + GRUB_INT_MAX); |
| 193 | + } |
| 194 | + if (state[FLAG_QUIET].set == 0) |
| 195 | + { |
| 196 | + grub_printf (N_("The amount of available RAM is %" |
| 197 | + PRIuGRUB_UINT64_T " %s.\n"), |
| 198 | + converted_avail_mem, unit_strs[unit]); |
| 199 | + } |
| 200 | + converted_avail_mem_str = |
| 201 | + grub_xasprintf ("%" PRIuGRUB_UINT64_T, |
| 202 | + converted_avail_mem); |
| 203 | + if (!converted_avail_mem_str) |
| 204 | + return grub_error (GRUB_ERR_OUT_OF_MEMORY, |
| 205 | + N_("Can't allocate space for value string")); |
| 206 | + |
| 207 | + grub_env_set (buf, converted_avail_mem_str); |
| 208 | + grub_env_export (buf); |
| 209 | + |
| 210 | + return GRUB_ERR_NONE; |
| 211 | +} |
| 212 | + |
| 213 | +GRUB_MOD_INIT(memsize) |
| 214 | +{ |
| 215 | + cmd = |
| 216 | + grub_register_extcmd ("memsize", |
| 217 | + grub_cmd_memsize, |
| 218 | + GRUB_COMMAND_FLAG_EXTCMD, |
| 219 | + N_("[-b|-K|-M|-G] [-q] [--set VARNAME]"), |
| 220 | + N_("Get the amount of system RAM available to GRUB, " |
| 221 | + "and export the integer value to the environment. " |
| 222 | + "The vaule will be printed out by default. " |
| 223 | + "-q disables the output. " |
| 224 | + "The default variable name is `memsize'. " |
| 225 | + "The unit can be b for bytes," |
| 226 | + "or K, M, G for binary units. The default unit is MiB."), |
| 227 | + grub_options_memsize); |
| 228 | +} |
| 229 | + |
| 230 | +GRUB_MOD_FINI(memsize) |
| 231 | +{ |
| 232 | + grub_unregister_extcmd (cmd); |
| 233 | +} |
0 commit comments