]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/chmem.c
tests: (getopt) remove unwanted paths from error output
[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
41 struct chmem_desc {
42 struct path_cxt *sysmem; /* _PATH_SYS_MEMORY handler */
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
126 if (ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/state", name) > 0
127 && strncmp(onoff, line, 6) == 0)
128 continue;
129
130 if (desc->have_zones) {
131 ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%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 = ul_path_writef_string(desc->sysmem, onoff, "%s/state", name);
149 if (rc != 0 && 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 if (ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/state", name) > 0
204 && 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 ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/valid_zones", name);
215 if (zone_id >= 0) {
216 zn = zone_names[zone_id];
217 if (enable && !strcasestr(line, zn)) {
218 warnx(_("%s enable failed: Zone mismatch"), str);
219 continue;
220 }
221 if (!enable && strncasecmp(line, zn, strlen(zn))) {
222 warnx(_("%s disable failed: Zone mismatch"), str);
223 continue;
224 }
225 } else if (enable) {
226 /* By default, use zone Movable for online, if valid */
227 if (strcasestr(line, zone_names[ZONE_MOVABLE]))
228 onoff = "online_movable";
229 else
230 onoff = "online";
231 }
232 }
233
234 rc = ul_path_writef_string(desc->sysmem, onoff, "%s/state", name);
235 if (rc != 0) {
236 if (enable)
237 warn(_("%s enable failed"), str);
238 else
239 warn(_("%s disable failed"), str);
240 } else if (desc->verbose) {
241 if (enable)
242 fprintf(stdout, _("%s enabled\n"), str);
243 else
244 fprintf(stdout, _("%s disabled\n"), str);
245 }
246 if (rc == 0)
247 todo--;
248 }
249 return todo == 0 ? 0 : todo == desc->end - desc->start + 1 ? -1 : 1;
250 }
251
252 static int filter(const struct dirent *de)
253 {
254 if (strncmp("memory", de->d_name, 6))
255 return 0;
256 return isdigit_string(de->d_name + 6);
257 }
258
259 static void read_info(struct chmem_desc *desc)
260 {
261 char line[128];
262
263 desc->ndirs = scandir(_PATH_SYS_MEMORY, &desc->dirs, filter, versionsort);
264 if (desc->ndirs <= 0)
265 err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY);
266 ul_path_read_buffer(desc->sysmem, line, sizeof(line), "block_size_bytes");
267 desc->block_size = strtoumax(line, NULL, 16);
268 }
269
270 static void parse_single_param(struct chmem_desc *desc, char *str)
271 {
272 if (desc->use_blocks) {
273 desc->start = strtou64_or_err(str, _("Failed to parse block number"));
274 desc->end = desc->start;
275 return;
276 }
277 desc->is_size = 1;
278 desc->size = strtosize_or_err(str, _("Failed to parse size"));
279 if (isdigit(str[strlen(str) - 1]))
280 desc->size *= 1024*1024;
281 if (desc->size % desc->block_size) {
282 errx(EXIT_FAILURE, _("Size must be aligned to memory block size (%s)"),
283 size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
284 }
285 desc->size /= desc->block_size;
286 }
287
288 static void parse_range_param(struct chmem_desc *desc, char *start, char *end)
289 {
290 if (desc->use_blocks) {
291 desc->start = strtou64_or_err(start, _("Failed to parse start"));
292 desc->end = strtou64_or_err(end, _("Failed to parse end"));
293 return;
294 }
295 if (strlen(start) < 2 || start[1] != 'x')
296 errx(EXIT_FAILURE, _("Invalid start address format: %s"), start);
297 if (strlen(end) < 2 || end[1] != 'x')
298 errx(EXIT_FAILURE, _("Invalid end address format: %s"), end);
299 desc->start = strtox64_or_err(start, _("Failed to parse start address"));
300 desc->end = strtox64_or_err(end, _("Failed to parse end address"));
301 if (desc->start % desc->block_size || (desc->end + 1) % desc->block_size) {
302 errx(EXIT_FAILURE,
303 _("Start address and (end address + 1) must be aligned to "
304 "memory block size (%s)"),
305 size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
306 }
307 desc->start /= desc->block_size;
308 desc->end /= desc->block_size;
309 }
310
311 static void parse_parameter(struct chmem_desc *desc, char *param)
312 {
313 char **split;
314
315 split = strv_split(param, "-");
316 if (strv_length(split) > 2)
317 errx(EXIT_FAILURE, _("Invalid parameter: %s"), param);
318 if (strv_length(split) == 1)
319 parse_single_param(desc, split[0]);
320 else
321 parse_range_param(desc, split[0], split[1]);
322 strv_free(split);
323 if (desc->start > desc->end)
324 errx(EXIT_FAILURE, _("Invalid range: %s"), param);
325 }
326
327 static void __attribute__((__noreturn__)) usage(void)
328 {
329 FILE *out = stdout;
330 size_t i;
331
332 fputs(USAGE_HEADER, out);
333 fprintf(out, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name);
334
335 fputs(USAGE_SEPARATOR, out);
336 fputs(_("Set a particular size or range of memory online or offline.\n"), out);
337
338 fputs(USAGE_OPTIONS, out);
339 fputs(_(" -e, --enable enable memory\n"), out);
340 fputs(_(" -d, --disable disable memory\n"), out);
341 fputs(_(" -b, --blocks use memory blocks\n"), out);
342 fputs(_(" -z, --zone <name> select memory zone (see below)\n"), out);
343 fputs(_(" -v, --verbose verbose output\n"), out);
344 printf(USAGE_HELP_OPTIONS(20));
345
346 fputs(_("\nSupported zones:\n"), out);
347 for (i = 0; i < ARRAY_SIZE(zone_names); i++)
348 fprintf(out, " %s\n", zone_names[i]);
349
350 printf(USAGE_MAN_TAIL("chmem(8)"));
351
352 exit(EXIT_SUCCESS);
353 }
354
355 int main(int argc, char **argv)
356 {
357 struct chmem_desc _desc = { 0 }, *desc = &_desc;
358 int cmd = CMD_NONE, zone_id = -1;
359 char *zone = NULL;
360 int c, rc;
361
362 static const struct option longopts[] = {
363 {"block", no_argument, NULL, 'b'},
364 {"disable", no_argument, NULL, 'd'},
365 {"enable", no_argument, NULL, 'e'},
366 {"help", no_argument, NULL, 'h'},
367 {"verbose", no_argument, NULL, 'v'},
368 {"version", no_argument, NULL, 'V'},
369 {"zone", required_argument, NULL, 'z'},
370 {NULL, 0, NULL, 0}
371 };
372
373 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
374 { 'd','e' },
375 { 0 }
376 };
377 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
378
379 setlocale(LC_ALL, "");
380 bindtextdomain(PACKAGE, LOCALEDIR);
381 textdomain(PACKAGE);
382 close_stdout_atexit();
383
384 ul_path_init_debug();
385 desc->sysmem = ul_new_path(_PATH_SYS_MEMORY);
386 if (!desc->sysmem)
387 err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_MEMORY);
388
389 read_info(desc);
390
391 while ((c = getopt_long(argc, argv, "bdehvVz:", longopts, NULL)) != -1) {
392
393 err_exclusive_options(c, longopts, excl, excl_st);
394
395 switch (c) {
396 case 'd':
397 cmd = CMD_MEMORY_DISABLE;
398 break;
399 case 'e':
400 cmd = CMD_MEMORY_ENABLE;
401 break;
402 case 'b':
403 desc->use_blocks = 1;
404 break;
405 case 'v':
406 desc->verbose = 1;
407 break;
408 case 'z':
409 zone = xstrdup(optarg);
410 break;
411
412 case 'h':
413 usage();
414 case 'V':
415 print_version(EXIT_SUCCESS);
416 default:
417 errtryhelp(EXIT_FAILURE);
418 }
419 }
420
421 if ((argc == 1) || (argc != optind + 1) || (cmd == CMD_NONE)) {
422 warnx(_("bad usage"));
423 errtryhelp(EXIT_FAILURE);
424 }
425
426 parse_parameter(desc, argv[optind]);
427
428
429 /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
430 if (ul_path_access(desc->sysmem, F_OK, "memory0/valid_zones") == 0)
431 desc->have_zones = 1;
432 else if (zone)
433 warnx(_("zone ignored, no valid_zones sysfs attribute present"));
434
435 if (zone && desc->have_zones) {
436 zone_id = zone_name_to_id(zone);
437 if (zone_id == -1) {
438 warnx(_("unknown memory zone: %s"), zone);
439 errtryhelp(EXIT_FAILURE);
440 }
441 }
442
443 if (desc->is_size)
444 rc = chmem_size(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
445 else
446 rc = chmem_range(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
447
448 ul_unref_path(desc->sysmem);
449
450 return rc == 0 ? EXIT_SUCCESS :
451 rc < 0 ? EXIT_FAILURE : CHMEM_EXIT_SOMEOK;
452 }