]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/chmem.c
Merge branch 'maybe-for-v2.32' of https://github.com/rudimeier/util-linux
[thirdparty/util-linux.git] / sys-utils / chmem.c
1 /*
2 * chmem - Memory configuration tool
3 *
4 * Copyright IBM Corp. 2016
5 *
6 * This program 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 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it would 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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <getopt.h>
24 #include <assert.h>
25 #include <dirent.h>
26
27 #include "c.h"
28 #include "nls.h"
29 #include "path.h"
30 #include "strutils.h"
31 #include "strv.h"
32 #include "optutils.h"
33 #include "closestream.h"
34 #include "xalloc.h"
35
36 /* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */
37 #define CHMEM_EXIT_SOMEOK 64
38
39 #define _PATH_SYS_MEMORY "/sys/devices/system/memory"
40 #define _PATH_SYS_MEMORY_BLOCK_SIZE _PATH_SYS_MEMORY "/block_size_bytes"
41
42 struct chmem_desc {
43 struct dirent **dirs;
44 int ndirs;
45 uint64_t block_size;
46 uint64_t start;
47 uint64_t end;
48 uint64_t size;
49 unsigned int use_blocks : 1;
50 unsigned int is_size : 1;
51 unsigned int verbose : 1;
52 unsigned int have_zones : 1;
53 };
54
55 enum {
56 CMD_MEMORY_ENABLE = 0,
57 CMD_MEMORY_DISABLE,
58 CMD_NONE
59 };
60
61 enum zone_id {
62 ZONE_DMA = 0,
63 ZONE_DMA32,
64 ZONE_NORMAL,
65 ZONE_HIGHMEM,
66 ZONE_MOVABLE,
67 ZONE_DEVICE,
68 };
69
70 static char *zone_names[] = {
71 [ZONE_DMA] = "DMA",
72 [ZONE_DMA32] = "DMA32",
73 [ZONE_NORMAL] = "Normal",
74 [ZONE_HIGHMEM] = "Highmem",
75 [ZONE_MOVABLE] = "Movable",
76 [ZONE_DEVICE] = "Device",
77 };
78
79 /*
80 * name must be null-terminated
81 */
82 static int zone_name_to_id(const char *name)
83 {
84 size_t i;
85
86 for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
87 if (!strcasecmp(name, zone_names[i]))
88 return i;
89 }
90 return -1;
91 }
92
93 static void idxtostr(struct chmem_desc *desc, uint64_t idx, char *buf, size_t bufsz)
94 {
95 uint64_t start, end;
96
97 start = idx * desc->block_size;
98 end = start + desc->block_size - 1;
99 snprintf(buf, bufsz,
100 _("Memory Block %"PRIu64" (0x%016"PRIx64"-0x%016"PRIx64")"),
101 idx, start, end);
102 }
103
104 static int chmem_size(struct chmem_desc *desc, int enable, int zone_id)
105 {
106 char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
107 uint64_t size, index;
108 const char *zn;
109 int i, rc;
110
111 size = desc->size;
112 onoff = enable ? "online" : "offline";
113 i = enable ? 0 : desc->ndirs - 1;
114
115 if (enable && zone_id >= 0) {
116 if (zone_id == ZONE_MOVABLE)
117 onoff = "online_movable";
118 else
119 onoff = "online_kernel";
120 }
121
122 for (; i >= 0 && i < desc->ndirs && size; i += enable ? 1 : -1) {
123 name = desc->dirs[i]->d_name;
124 index = strtou64_or_err(name + 6, _("Failed to parse index"));
125 path_read_str(line, sizeof(line), _PATH_SYS_MEMORY "/%s/state", name);
126 if (strncmp(onoff, line, 6) == 0)
127 continue;
128
129 if (desc->have_zones) {
130 path_read_str(line, sizeof(line),
131 _PATH_SYS_MEMORY "/%s/valid_zones", name);
132 if (zone_id >= 0) {
133 zn = zone_names[zone_id];
134 if (enable && !strcasestr(line, zn))
135 continue;
136 if (!enable && strncasecmp(line, zn, strlen(zn)))
137 continue;
138 } else if (enable) {
139 /* By default, use zone Movable for online, if valid */
140 if (strcasestr(line, zone_names[ZONE_MOVABLE]))
141 onoff = "online_movable";
142 else
143 onoff = "online";
144 }
145 }
146
147 idxtostr(desc, index, str, sizeof(str));
148 rc = path_write_str(onoff, _PATH_SYS_MEMORY"/%s/state", name);
149 if (rc == -1 && desc->verbose) {
150 if (enable)
151 fprintf(stdout, _("%s enable failed\n"), str);
152 else
153 fprintf(stdout, _("%s disable failed\n"), str);
154 } else if (rc == 0 && desc->verbose) {
155 if (enable)
156 fprintf(stdout, _("%s enabled\n"), str);
157 else
158 fprintf(stdout, _("%s disabled\n"), str);
159 }
160 if (rc == 0)
161 size--;
162 }
163 if (size) {
164 uint64_t bytes;
165 char *sizestr;
166
167 bytes = (desc->size - size) * desc->block_size;
168 sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, bytes);
169 if (enable)
170 warnx(_("Could only enable %s of memory"), sizestr);
171 else
172 warnx(_("Could only disable %s of memory"), sizestr);
173 free(sizestr);
174 }
175 return size == 0 ? 0 : size == desc->size ? -1 : 1;
176 }
177
178 static int chmem_range(struct chmem_desc *desc, int enable, int zone_id)
179 {
180 char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
181 uint64_t index, todo;
182 const char *zn;
183 int i, rc;
184
185 todo = desc->end - desc->start + 1;
186 onoff = enable ? "online" : "offline";
187
188 if (enable && zone_id >= 0) {
189 if (zone_id == ZONE_MOVABLE)
190 onoff = "online_movable";
191 else
192 onoff = "online_kernel";
193 }
194
195 for (i = 0; i < desc->ndirs; i++) {
196 name = desc->dirs[i]->d_name;
197 index = strtou64_or_err(name + 6, _("Failed to parse index"));
198 if (index < desc->start)
199 continue;
200 if (index > desc->end)
201 break;
202 idxtostr(desc, index, str, sizeof(str));
203 path_read_str(line, sizeof(line), _PATH_SYS_MEMORY "/%s/state", name);
204 if (strncmp(onoff, line, 6) == 0) {
205 if (desc->verbose && enable)
206 fprintf(stdout, _("%s already enabled\n"), str);
207 else if (desc->verbose && !enable)
208 fprintf(stdout, _("%s already disabled\n"), str);
209 todo--;
210 continue;
211 }
212
213 if (desc->have_zones) {
214 path_read_str(line, sizeof(line),
215 _PATH_SYS_MEMORY "/%s/valid_zones", name);
216 if (zone_id >= 0) {
217 zn = zone_names[zone_id];
218 if (enable && !strcasestr(line, zn)) {
219 warnx(_("%s enable failed: Zone mismatch"), str);
220 continue;
221 }
222 if (!enable && strncasecmp(line, zn, strlen(zn))) {
223 warnx(_("%s disable failed: Zone mismatch"), str);
224 continue;
225 }
226 } else if (enable) {
227 /* By default, use zone Movable for online, if valid */
228 if (strcasestr(line, zone_names[ZONE_MOVABLE]))
229 onoff = "online_movable";
230 else
231 onoff = "online";
232 }
233 }
234
235 rc = path_write_str(onoff, _PATH_SYS_MEMORY"/%s/state", name);
236 if (rc == -1) {
237 if (enable)
238 warn(_("%s enable failed"), str);
239 else
240 warn(_("%s disable failed"), str);
241 } else if (desc->verbose) {
242 if (enable)
243 fprintf(stdout, _("%s enabled\n"), str);
244 else
245 fprintf(stdout, _("%s disabled\n"), str);
246 }
247 if (rc == 0)
248 todo--;
249 }
250 return todo == 0 ? 0 : todo == desc->end - desc->start + 1 ? -1 : 1;
251 }
252
253 static int filter(const struct dirent *de)
254 {
255 if (strncmp("memory", de->d_name, 6))
256 return 0;
257 return isdigit_string(de->d_name + 6);
258 }
259
260 static void read_info(struct chmem_desc *desc)
261 {
262 char line[BUFSIZ];
263
264 desc->ndirs = scandir(_PATH_SYS_MEMORY, &desc->dirs, filter, versionsort);
265 if (desc->ndirs <= 0)
266 err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY);
267 path_read_str(line, sizeof(line), _PATH_SYS_MEMORY_BLOCK_SIZE);
268 desc->block_size = strtoumax(line, NULL, 16);
269 }
270
271 static void parse_single_param(struct chmem_desc *desc, char *str)
272 {
273 if (desc->use_blocks) {
274 desc->start = strtou64_or_err(str, _("Failed to parse block number"));
275 desc->end = desc->start;
276 return;
277 }
278 desc->is_size = 1;
279 desc->size = strtosize_or_err(str, _("Failed to parse size"));
280 if (isdigit(str[strlen(str) - 1]))
281 desc->size *= 1024*1024;
282 if (desc->size % desc->block_size) {
283 errx(EXIT_FAILURE, _("Size must be aligned to memory block size (%s)"),
284 size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
285 }
286 desc->size /= desc->block_size;
287 }
288
289 static void parse_range_param(struct chmem_desc *desc, char *start, char *end)
290 {
291 if (desc->use_blocks) {
292 desc->start = strtou64_or_err(start, _("Failed to parse start"));
293 desc->end = strtou64_or_err(end, _("Failed to parse end"));
294 return;
295 }
296 if (strlen(start) < 2 || start[1] != 'x')
297 errx(EXIT_FAILURE, _("Invalid start address format: %s"), start);
298 if (strlen(end) < 2 || end[1] != 'x')
299 errx(EXIT_FAILURE, _("Invalid end address format: %s"), end);
300 desc->start = strtox64_or_err(start, _("Failed to parse start address"));
301 desc->end = strtox64_or_err(end, _("Failed to parse end address"));
302 if (desc->start % desc->block_size || (desc->end + 1) % desc->block_size) {
303 errx(EXIT_FAILURE,
304 _("Start address and (end address + 1) must be aligned to "
305 "memory block size (%s)"),
306 size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
307 }
308 desc->start /= desc->block_size;
309 desc->end /= desc->block_size;
310 }
311
312 static void parse_parameter(struct chmem_desc *desc, char *param)
313 {
314 char **split;
315
316 split = strv_split(param, "-");
317 if (strv_length(split) > 2)
318 errx(EXIT_FAILURE, _("Invalid parameter: %s"), param);
319 if (strv_length(split) == 1)
320 parse_single_param(desc, split[0]);
321 else
322 parse_range_param(desc, split[0], split[1]);
323 strv_free(split);
324 if (desc->start > desc->end)
325 errx(EXIT_FAILURE, _("Invalid range: %s"), param);
326 }
327
328 static void __attribute__((__noreturn__)) usage(void)
329 {
330 FILE *out = stdout;
331 size_t i;
332
333 fputs(USAGE_HEADER, out);
334 fprintf(out, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name);
335
336 fputs(USAGE_SEPARATOR, out);
337 fputs(_("Set a particular size or range of memory online or offline.\n"), out);
338
339 fputs(USAGE_OPTIONS, out);
340 fputs(_(" -e, --enable enable memory\n"), out);
341 fputs(_(" -d, --disable disable memory\n"), out);
342 fputs(_(" -b, --blocks use memory blocks\n"), out);
343 fputs(_(" -z, --zone <name> select memory zone (see below)\n"), out);
344 fputs(_(" -v, --verbose verbose output\n"), out);
345 printf(USAGE_HELP_OPTIONS(20));
346
347 fputs(_("\nSupported zones:\n"), out);
348 for (i = 0; i < ARRAY_SIZE(zone_names); i++)
349 fprintf(out, " %s\n", zone_names[i]);
350
351 printf(USAGE_MAN_TAIL("chmem(8)"));
352
353 exit(EXIT_SUCCESS);
354 }
355
356 int main(int argc, char **argv)
357 {
358 struct chmem_desc _desc = { }, *desc = &_desc;
359 int cmd = CMD_NONE, zone_id = -1;
360 char *zone = NULL;
361 int c, rc;
362
363 static const struct option longopts[] = {
364 {"block", no_argument, NULL, 'b'},
365 {"disable", no_argument, NULL, 'd'},
366 {"enable", no_argument, NULL, 'e'},
367 {"help", no_argument, NULL, 'h'},
368 {"verbose", no_argument, NULL, 'v'},
369 {"version", no_argument, NULL, 'V'},
370 {"zone", required_argument, NULL, 'z'},
371 {NULL, 0, NULL, 0}
372 };
373
374 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
375 { 'd','e' },
376 { 0 }
377 };
378 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
379
380 setlocale(LC_ALL, "");
381 bindtextdomain(PACKAGE, LOCALEDIR);
382 textdomain(PACKAGE);
383 atexit(close_stdout);
384
385 read_info(desc);
386
387 while ((c = getopt_long(argc, argv, "bdehvVz:", longopts, NULL)) != -1) {
388
389 err_exclusive_options(c, longopts, excl, excl_st);
390
391 switch (c) {
392 case 'd':
393 cmd = CMD_MEMORY_DISABLE;
394 break;
395 case 'e':
396 cmd = CMD_MEMORY_ENABLE;
397 break;
398 case 'b':
399 desc->use_blocks = 1;
400 break;
401 case 'h':
402 usage();
403 break;
404 case 'v':
405 desc->verbose = 1;
406 break;
407 case 'V':
408 printf(UTIL_LINUX_VERSION);
409 return EXIT_SUCCESS;
410 case 'z':
411 zone = xstrdup(optarg);
412 break;
413 default:
414 errtryhelp(EXIT_FAILURE);
415 }
416 }
417
418 if ((argc == 1) || (argc != optind + 1) || (cmd == CMD_NONE)) {
419 warnx(_("bad usage"));
420 errtryhelp(EXIT_FAILURE);
421 }
422
423 parse_parameter(desc, argv[optind]);
424
425 /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
426 if (path_exist(_PATH_SYS_MEMORY "/memory0/valid_zones"))
427 desc->have_zones = 1;
428 else if (zone)
429 warnx(_("zone ignored, no valid_zones sysfs attribute present"));
430
431 if (zone && desc->have_zones) {
432 zone_id = zone_name_to_id(zone);
433 if (zone_id == -1) {
434 warnx(_("unknown memory zone: %s"), zone);
435 errtryhelp(EXIT_FAILURE);
436 }
437 }
438
439 if (desc->is_size)
440 rc = chmem_size(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
441 else
442 rc = chmem_range(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
443
444 return rc == 0 ? EXIT_SUCCESS :
445 rc < 0 ? EXIT_FAILURE : CHMEM_EXIT_SOMEOK;
446 }