]>
Commit | Line | Data |
---|---|---|
cad2d1ac | 1 | /* |
9abd5e4b KZ |
2 | * SPDX-License-Identifier: GPL-2.0-or-later |
3 | * | |
cad2d1ac HC |
4 | * lsmem - Show memory configuration |
5 | * | |
6 | * Copyright IBM Corp. 2016 | |
a5c4535b | 7 | * Copyright (C) 2016 Karel Zak <kzak@redhat.com> |
cad2d1ac HC |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
cad2d1ac | 13 | */ |
cad2d1ac HC |
14 | #include <c.h> |
15 | #include <nls.h> | |
16 | #include <path.h> | |
17 | #include <strutils.h> | |
18 | #include <closestream.h> | |
19 | #include <xalloc.h> | |
20 | #include <getopt.h> | |
21 | #include <stdio.h> | |
22 | #include <stdlib.h> | |
23 | #include <dirent.h> | |
cad2d1ac HC |
24 | #include <fcntl.h> |
25 | #include <inttypes.h> | |
26 | #include <assert.h> | |
27 | #include <optutils.h> | |
28 | #include <libsmartcols.h> | |
29 | ||
30 | #define _PATH_SYS_MEMORY "/sys/devices/system/memory" | |
cad2d1ac HC |
31 | |
32 | #define MEMORY_STATE_ONLINE 0 | |
33 | #define MEMORY_STATE_OFFLINE 1 | |
34 | #define MEMORY_STATE_GOING_OFFLINE 2 | |
35 | #define MEMORY_STATE_UNKNOWN 3 | |
36 | ||
60a7e9e9 GS |
37 | enum zone_id { |
38 | ZONE_DMA = 0, | |
39 | ZONE_DMA32, | |
40 | ZONE_NORMAL, | |
41 | ZONE_HIGHMEM, | |
42 | ZONE_MOVABLE, | |
43 | ZONE_DEVICE, | |
44 | ZONE_NONE, | |
45 | ZONE_UNKNOWN, | |
46 | MAX_NR_ZONES, | |
47 | }; | |
48 | ||
cad2d1ac HC |
49 | struct memory_block { |
50 | uint64_t index; | |
51 | uint64_t count; | |
52 | int state; | |
53 | int node; | |
60a7e9e9 GS |
54 | int nr_zones; |
55 | int zones[MAX_NR_ZONES]; | |
cad2d1ac HC |
56 | unsigned int removable:1; |
57 | }; | |
58 | ||
0cf0710a | 59 | struct lsmem { |
e4319a10 | 60 | struct path_cxt *sysmem; /* _PATH_SYS_MEMORY directory handler */ |
cad2d1ac HC |
61 | struct dirent **dirs; |
62 | int ndirs; | |
63 | struct memory_block *blocks; | |
64 | int nblocks; | |
cad2d1ac HC |
65 | uint64_t block_size; |
66 | uint64_t mem_online; | |
67 | uint64_t mem_offline; | |
cad2d1ac | 68 | |
0a1ed6df KZ |
69 | struct libscols_table *table; |
70 | unsigned int have_nodes : 1, | |
71 | raw : 1, | |
72 | export : 1, | |
73 | json : 1, | |
74 | noheadings : 1, | |
75 | summary : 1, | |
76 | list_all : 1, | |
77 | bytes : 1, | |
c5332fbb | 78 | want_summary : 1, |
60a7e9e9 | 79 | want_table : 1, |
d12e945d KZ |
80 | split_by_node : 1, |
81 | split_by_state : 1, | |
82 | split_by_removable : 1, | |
83 | split_by_zones : 1, | |
60a7e9e9 | 84 | have_zones : 1; |
cad2d1ac HC |
85 | }; |
86 | ||
d12e945d | 87 | |
cad2d1ac HC |
88 | enum { |
89 | COL_RANGE, | |
90 | COL_SIZE, | |
91 | COL_STATE, | |
92 | COL_REMOVABLE, | |
93 | COL_BLOCK, | |
94 | COL_NODE, | |
60a7e9e9 GS |
95 | COL_ZONES, |
96 | }; | |
97 | ||
98 | static char *zone_names[] = { | |
99 | [ZONE_DMA] = "DMA", | |
100 | [ZONE_DMA32] = "DMA32", | |
101 | [ZONE_NORMAL] = "Normal", | |
102 | [ZONE_HIGHMEM] = "Highmem", | |
103 | [ZONE_MOVABLE] = "Movable", | |
104 | [ZONE_DEVICE] = "Device", | |
105 | [ZONE_NONE] = "None", /* block contains more than one zone, can't be offlined */ | |
106 | [ZONE_UNKNOWN] = "Unknown", | |
cad2d1ac HC |
107 | }; |
108 | ||
0a1ed6df KZ |
109 | /* column names */ |
110 | struct coldesc { | |
111 | const char *name; /* header */ | |
112 | double whint; /* width hint (N < 1 is in percent of termwidth) */ | |
113 | int flags; /* SCOLS_FL_* */ | |
114 | const char *help; | |
cad2d1ac HC |
115 | }; |
116 | ||
0a1ed6df KZ |
117 | /* columns descriptions */ |
118 | static struct coldesc coldescs[] = { | |
4775cc69 KZ |
119 | [COL_RANGE] = { "RANGE", 0, 0, N_("start and end address of the memory range")}, |
120 | [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the memory range")}, | |
4c0e1eaf | 121 | [COL_STATE] = { "STATE", 0, SCOLS_FL_RIGHT, N_("online status of the memory range")}, |
0a1ed6df | 122 | [COL_REMOVABLE] = { "REMOVABLE", 0, SCOLS_FL_RIGHT, N_("memory is removable")}, |
4775cc69 KZ |
123 | [COL_BLOCK] = { "BLOCK", 0, SCOLS_FL_RIGHT, N_("memory block number or blocks range")}, |
124 | [COL_NODE] = { "NODE", 0, SCOLS_FL_RIGHT, N_("numa node of memory")}, | |
60a7e9e9 | 125 | [COL_ZONES] = { "ZONES", 0, SCOLS_FL_RIGHT, N_("valid zones for the memory range")}, |
cad2d1ac HC |
126 | }; |
127 | ||
0a1ed6df KZ |
128 | /* columns[] array specifies all currently wanted output column. The columns |
129 | * are defined by coldescs[] array and you can specify (on command line) each | |
130 | * column twice. That's enough, dynamically allocated array of the columns is | |
131 | * unnecessary overkill and over-engineering in this case */ | |
132 | static int columns[ARRAY_SIZE(coldescs) * 2]; | |
133 | static size_t ncolumns; | |
134 | ||
135 | static inline size_t err_columns_index(size_t arysz, size_t idx) | |
136 | { | |
137 | if (idx >= arysz) | |
138 | errx(EXIT_FAILURE, _("too many columns specified, " | |
139 | "the limit is %zu columns"), | |
140 | arysz - 1); | |
141 | return idx; | |
142 | } | |
143 | ||
60a7e9e9 GS |
144 | /* |
145 | * name must be null-terminated | |
146 | */ | |
147 | static int zone_name_to_id(const char *name) | |
148 | { | |
149 | size_t i; | |
150 | ||
151 | for (i = 0; i < ARRAY_SIZE(zone_names); i++) { | |
152 | if (!strcasecmp(name, zone_names[i])) | |
153 | return i; | |
154 | } | |
155 | return ZONE_UNKNOWN; | |
156 | } | |
157 | ||
0a1ed6df KZ |
158 | #define add_column(ary, n, id) \ |
159 | ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id)) | |
160 | ||
cad2d1ac HC |
161 | static int column_name_to_id(const char *name, size_t namesz) |
162 | { | |
163 | size_t i; | |
164 | ||
165 | for (i = 0; i < ARRAY_SIZE(coldescs); i++) { | |
166 | const char *cn = coldescs[i].name; | |
167 | ||
168 | if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
169 | return i; | |
170 | } | |
171 | warnx(_("unknown column: %s"), name); | |
172 | return -1; | |
173 | } | |
174 | ||
0a1ed6df | 175 | static inline int get_column_id(int num) |
cad2d1ac | 176 | { |
0a1ed6df KZ |
177 | assert(num >= 0); |
178 | assert((size_t) num < ncolumns); | |
179 | assert(columns[num] < (int) ARRAY_SIZE(coldescs)); | |
180 | ||
181 | return columns[num]; | |
cad2d1ac HC |
182 | } |
183 | ||
0a1ed6df | 184 | static inline struct coldesc *get_column_desc(int num) |
cad2d1ac | 185 | { |
0a1ed6df | 186 | return &coldescs[ get_column_id(num) ]; |
cad2d1ac HC |
187 | } |
188 | ||
d12e945d | 189 | static inline void reset_split_policy(struct lsmem *l, int enable) |
cad2d1ac | 190 | { |
d12e945d KZ |
191 | l->split_by_state = enable; |
192 | l->split_by_node = enable; | |
193 | l->split_by_removable = enable; | |
194 | l->split_by_zones = enable; | |
cad2d1ac HC |
195 | } |
196 | ||
96cbe362 KZ |
197 | static void set_split_policy(struct lsmem *l, int cols[], size_t ncols) |
198 | { | |
199 | size_t i; | |
200 | ||
201 | reset_split_policy(l, 0); | |
202 | ||
203 | for (i = 0; i < ncols; i++) { | |
204 | switch (cols[i]) { | |
205 | case COL_STATE: | |
206 | l->split_by_state = 1; | |
207 | break; | |
208 | case COL_NODE: | |
209 | l->split_by_node = 1; | |
210 | break; | |
211 | case COL_REMOVABLE: | |
212 | l->split_by_removable = 1; | |
213 | break; | |
214 | case COL_ZONES: | |
215 | l->split_by_zones = 1; | |
216 | break; | |
217 | default: | |
218 | break; | |
219 | } | |
220 | } | |
221 | } | |
222 | ||
0cf0710a | 223 | static void add_scols_line(struct lsmem *lsmem, struct memory_block *blk) |
cad2d1ac | 224 | { |
0a1ed6df KZ |
225 | size_t i; |
226 | struct libscols_line *line; | |
cad2d1ac | 227 | |
0cf0710a | 228 | line = scols_table_new_line(lsmem->table, NULL); |
0a1ed6df KZ |
229 | if (!line) |
230 | err_oom(); | |
cad2d1ac | 231 | |
0a1ed6df KZ |
232 | for (i = 0; i < ncolumns; i++) { |
233 | char *str = NULL; | |
cad2d1ac | 234 | |
0a1ed6df KZ |
235 | switch (get_column_id(i)) { |
236 | case COL_RANGE: | |
237 | { | |
0cf0710a KZ |
238 | uint64_t start = blk->index * lsmem->block_size; |
239 | uint64_t size = blk->count * lsmem->block_size; | |
0a1ed6df KZ |
240 | xasprintf(&str, "0x%016"PRIx64"-0x%016"PRIx64, start, start + size - 1); |
241 | break; | |
242 | } | |
243 | case COL_SIZE: | |
0cf0710a KZ |
244 | if (lsmem->bytes) |
245 | xasprintf(&str, "%"PRId64, (uint64_t) blk->count * lsmem->block_size); | |
0a1ed6df KZ |
246 | else |
247 | str = size_to_human_string(SIZE_SUFFIX_1LETTER, | |
0cf0710a | 248 | (uint64_t) blk->count * lsmem->block_size); |
0a1ed6df KZ |
249 | break; |
250 | case COL_STATE: | |
251 | str = xstrdup( | |
252 | blk->state == MEMORY_STATE_ONLINE ? _("online") : | |
253 | blk->state == MEMORY_STATE_OFFLINE ? _("offline") : | |
254 | blk->state == MEMORY_STATE_GOING_OFFLINE ? _("on->off") : | |
255 | "?"); | |
256 | break; | |
257 | case COL_REMOVABLE: | |
258 | if (blk->state == MEMORY_STATE_ONLINE) | |
259 | str = xstrdup(blk->removable ? _("yes") : _("no")); | |
260 | break; | |
261 | case COL_BLOCK: | |
262 | if (blk->count == 1) | |
263 | xasprintf(&str, "%"PRId64, blk->index); | |
264 | else | |
265 | xasprintf(&str, "%"PRId64"-%"PRId64, | |
266 | blk->index, blk->index + blk->count - 1); | |
267 | break; | |
268 | case COL_NODE: | |
0cf0710a | 269 | if (lsmem->have_nodes) |
0a1ed6df KZ |
270 | xasprintf(&str, "%d", blk->node); |
271 | break; | |
60a7e9e9 GS |
272 | case COL_ZONES: |
273 | if (lsmem->have_zones) { | |
274 | char valid_zones[BUFSIZ]; | |
275 | int j, zone_id; | |
276 | ||
277 | valid_zones[0] = '\0'; | |
278 | for (j = 0; j < blk->nr_zones; j++) { | |
279 | zone_id = blk->zones[j]; | |
280 | if (strlen(valid_zones) + | |
281 | strlen(zone_names[zone_id]) > BUFSIZ - 2) | |
282 | break; | |
283 | strcat(valid_zones, zone_names[zone_id]); | |
284 | if (j + 1 < blk->nr_zones) | |
285 | strcat(valid_zones, "/"); | |
286 | } | |
287 | str = xstrdup(valid_zones); | |
92368ce1 | 288 | } |
60a7e9e9 | 289 | break; |
cad2d1ac | 290 | } |
0a1ed6df KZ |
291 | |
292 | if (str && scols_line_refer_data(line, i, str) != 0) | |
293 | err_oom(); | |
cad2d1ac | 294 | } |
cad2d1ac HC |
295 | } |
296 | ||
0cf0710a | 297 | static void fill_scols_table(struct lsmem *lsmem) |
0a1ed6df KZ |
298 | { |
299 | int i; | |
300 | ||
0cf0710a KZ |
301 | for (i = 0; i < lsmem->nblocks; i++) |
302 | add_scols_line(lsmem, &lsmem->blocks[i]); | |
0a1ed6df KZ |
303 | } |
304 | ||
0cf0710a | 305 | static void print_summary(struct lsmem *lsmem) |
cad2d1ac | 306 | { |
c5332fbb KZ |
307 | if (lsmem->bytes) { |
308 | printf("%-23s %15"PRId64"\n",_("Memory block size:"), lsmem->block_size); | |
309 | printf("%-23s %15"PRId64"\n",_("Total online memory:"), lsmem->mem_online); | |
310 | printf("%-23s %15"PRId64"\n",_("Total offline memory:"), lsmem->mem_offline); | |
311 | } else { | |
8e7a0c16 KZ |
312 | char *p; |
313 | ||
314 | if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->block_size))) | |
315 | printf("%-23s %5s\n",_("Memory block size:"), p); | |
316 | free(p); | |
317 | ||
318 | if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_online))) | |
319 | printf("%-23s %5s\n",_("Total online memory:"), p); | |
320 | free(p); | |
321 | ||
322 | if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_offline))) | |
323 | printf("%-23s %5s\n",_("Total offline memory:"), p); | |
324 | free(p); | |
c5332fbb | 325 | } |
cad2d1ac HC |
326 | } |
327 | ||
e4319a10 | 328 | static int memory_block_get_node(struct lsmem *lsmem, char *name) |
cad2d1ac HC |
329 | { |
330 | struct dirent *de; | |
cad2d1ac HC |
331 | DIR *dir; |
332 | int node; | |
333 | ||
e4319a10 KZ |
334 | dir = ul_path_opendir(lsmem->sysmem, name); |
335 | if (!dir) | |
336 | err(EXIT_FAILURE, _("Failed to open %s"), name); | |
81435af3 | 337 | |
cad2d1ac HC |
338 | node = -1; |
339 | while ((de = readdir(dir)) != NULL) { | |
ad296391 | 340 | if (strncmp("node", de->d_name, 4) != 0) |
cad2d1ac HC |
341 | continue; |
342 | if (!isdigit_string(de->d_name + 4)) | |
343 | continue; | |
fe4e122a | 344 | errno = 0; |
cad2d1ac | 345 | node = strtol(de->d_name + 4, NULL, 10); |
fe4e122a KZ |
346 | if (errno) |
347 | continue; | |
51a90159 | 348 | break; |
cad2d1ac HC |
349 | } |
350 | closedir(dir); | |
351 | return node; | |
352 | } | |
353 | ||
fe4e122a | 354 | static int memory_block_read_attrs(struct lsmem *lsmem, char *name, |
cad2d1ac HC |
355 | struct memory_block *blk) |
356 | { | |
e4319a10 | 357 | char *line = NULL; |
fe4e122a | 358 | int i, x = 0, rc = 0; |
e4319a10 KZ |
359 | |
360 | memset(blk, 0, sizeof(*blk)); | |
cad2d1ac | 361 | |
fe4e122a | 362 | errno = 0; |
cad2d1ac | 363 | blk->count = 1; |
cad2d1ac | 364 | blk->state = MEMORY_STATE_UNKNOWN; |
e4319a10 | 365 | blk->index = strtoumax(name + 6, NULL, 10); /* get <num> of "memory<num>" */ |
9c41d270 | 366 | |
fe4e122a KZ |
367 | if (errno) |
368 | rc = -errno; | |
369 | ||
e4319a10 KZ |
370 | if (ul_path_readf_s32(lsmem->sysmem, &x, "%s/removable", name) == 0) |
371 | blk->removable = x == 1; | |
372 | ||
4397707e | 373 | if (ul_path_readf_string(lsmem->sysmem, &line, "%s/state", name) > 0 && line) { |
e4319a10 KZ |
374 | if (strcmp(line, "offline") == 0) |
375 | blk->state = MEMORY_STATE_OFFLINE; | |
376 | else if (strcmp(line, "online") == 0) | |
377 | blk->state = MEMORY_STATE_ONLINE; | |
378 | else if (strcmp(line, "going-offline") == 0) | |
379 | blk->state = MEMORY_STATE_GOING_OFFLINE; | |
380 | free(line); | |
381 | } | |
9c41d270 | 382 | |
0cf0710a | 383 | if (lsmem->have_nodes) |
e4319a10 | 384 | blk->node = memory_block_get_node(lsmem, name); |
60a7e9e9 GS |
385 | |
386 | blk->nr_zones = 0; | |
4397707e KZ |
387 | if (lsmem->have_zones |
388 | && ul_path_readf_string(lsmem->sysmem, &line, "%s/valid_zones", name) > 0 | |
389 | && line) { | |
e4319a10 KZ |
390 | |
391 | char *token = strtok(line, " "); | |
392 | ||
393 | for (i = 0; token && i < MAX_NR_ZONES; i++) { | |
60a7e9e9 GS |
394 | blk->zones[i] = zone_name_to_id(token); |
395 | blk->nr_zones++; | |
396 | token = strtok(NULL, " "); | |
397 | } | |
e4319a10 | 398 | free(line); |
60a7e9e9 | 399 | } |
fe4e122a KZ |
400 | |
401 | return rc; | |
cad2d1ac HC |
402 | } |
403 | ||
0cf0710a | 404 | static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk) |
cad2d1ac HC |
405 | { |
406 | struct memory_block *curr; | |
60a7e9e9 | 407 | int i; |
cad2d1ac | 408 | |
0cf0710a | 409 | if (!lsmem->nblocks) |
cad2d1ac | 410 | return 0; |
0cf0710a KZ |
411 | curr = &lsmem->blocks[lsmem->nblocks - 1]; |
412 | if (lsmem->list_all) | |
cad2d1ac HC |
413 | return 0; |
414 | if (curr->index + curr->count != blk->index) | |
415 | return 0; | |
d12e945d | 416 | if (lsmem->split_by_state && curr->state != blk->state) |
cad2d1ac | 417 | return 0; |
d12e945d | 418 | if (lsmem->split_by_removable && curr->removable != blk->removable) |
cad2d1ac | 419 | return 0; |
d12e945d | 420 | if (lsmem->split_by_node && lsmem->have_nodes) { |
cad2d1ac HC |
421 | if (curr->node != blk->node) |
422 | return 0; | |
423 | } | |
d12e945d | 424 | if (lsmem->split_by_zones && lsmem->have_zones) { |
60a7e9e9 GS |
425 | if (curr->nr_zones != blk->nr_zones) |
426 | return 0; | |
427 | for (i = 0; i < curr->nr_zones; i++) { | |
428 | if (curr->zones[i] == ZONE_UNKNOWN || | |
429 | curr->zones[i] != blk->zones[i]) | |
430 | return 0; | |
431 | } | |
432 | } | |
cad2d1ac HC |
433 | return 1; |
434 | } | |
435 | ||
bf1d0a4e KZ |
436 | static void free_info(struct lsmem *lsmem) |
437 | { | |
438 | int i; | |
439 | ||
440 | if (!lsmem) | |
441 | return; | |
442 | free(lsmem->blocks); | |
443 | for (i = 0; i < lsmem->ndirs; i++) | |
444 | free(lsmem->dirs[i]); | |
445 | free(lsmem->dirs); | |
446 | } | |
447 | ||
0cf0710a | 448 | static void read_info(struct lsmem *lsmem) |
cad2d1ac | 449 | { |
cad2d1ac | 450 | struct memory_block blk; |
e4319a10 | 451 | char buf[128]; |
cad2d1ac HC |
452 | int i; |
453 | ||
e4319a10 KZ |
454 | if (ul_path_read_buffer(lsmem->sysmem, buf, sizeof(buf), "block_size_bytes") <= 0) |
455 | err(EXIT_FAILURE, _("failed to read memory block size")); | |
fe4e122a KZ |
456 | |
457 | errno = 0; | |
e4319a10 | 458 | lsmem->block_size = strtoumax(buf, NULL, 16); |
fe4e122a KZ |
459 | if (errno) |
460 | err(EXIT_FAILURE, _("failed to read memory block size")); | |
cad2d1ac | 461 | |
0cf0710a KZ |
462 | for (i = 0; i < lsmem->ndirs; i++) { |
463 | memory_block_read_attrs(lsmem, lsmem->dirs[i]->d_name, &blk); | |
49834246 GS |
464 | if (blk.state == MEMORY_STATE_ONLINE) |
465 | lsmem->mem_online += lsmem->block_size; | |
466 | else | |
467 | lsmem->mem_offline += lsmem->block_size; | |
0cf0710a KZ |
468 | if (is_mergeable(lsmem, &blk)) { |
469 | lsmem->blocks[lsmem->nblocks - 1].count++; | |
cad2d1ac HC |
470 | continue; |
471 | } | |
0cf0710a | 472 | lsmem->nblocks++; |
64d6d400 | 473 | lsmem->blocks = xreallocarray(lsmem->blocks, lsmem->nblocks, sizeof(blk)); |
0cf0710a | 474 | *&lsmem->blocks[lsmem->nblocks - 1] = blk; |
cad2d1ac | 475 | } |
cad2d1ac HC |
476 | } |
477 | ||
478 | static int memory_block_filter(const struct dirent *de) | |
479 | { | |
ad296391 | 480 | if (strncmp("memory", de->d_name, 6) != 0) |
cad2d1ac HC |
481 | return 0; |
482 | return isdigit_string(de->d_name + 6); | |
483 | } | |
484 | ||
0cf0710a | 485 | static void read_basic_info(struct lsmem *lsmem) |
cad2d1ac | 486 | { |
e4319a10 | 487 | char dir[PATH_MAX]; |
cad2d1ac | 488 | |
e4319a10 | 489 | if (ul_path_access(lsmem->sysmem, F_OK, "block_size_bytes") != 0) |
cad2d1ac HC |
490 | errx(EXIT_FAILURE, _("This system does not support memory blocks")); |
491 | ||
e4319a10 | 492 | ul_path_get_abspath(lsmem->sysmem, dir, sizeof(dir), NULL); |
81435af3 | 493 | |
0cf0710a | 494 | lsmem->ndirs = scandir(dir, &lsmem->dirs, memory_block_filter, versionsort); |
0cf0710a | 495 | if (lsmem->ndirs <= 0) |
e4319a10 | 496 | err(EXIT_FAILURE, _("Failed to read %s"), dir); |
cad2d1ac | 497 | |
e4319a10 | 498 | if (memory_block_get_node(lsmem, lsmem->dirs[0]->d_name) != -1) |
0cf0710a | 499 | lsmem->have_nodes = 1; |
60a7e9e9 | 500 | |
e4319a10 KZ |
501 | /* The valid_zones sysmem attribute was introduced with kernel 3.18 */ |
502 | if (ul_path_access(lsmem->sysmem, F_OK, "memory0/valid_zones") == 0) | |
60a7e9e9 | 503 | lsmem->have_zones = 1; |
cad2d1ac HC |
504 | } |
505 | ||
6e1eda6f | 506 | static void __attribute__((__noreturn__)) usage(void) |
cad2d1ac | 507 | { |
6e1eda6f | 508 | FILE *out = stdout; |
9c41d270 | 509 | size_t i; |
cad2d1ac HC |
510 | |
511 | fputs(USAGE_HEADER, out); | |
512 | fprintf(out, _(" %s [options]\n"), program_invocation_short_name); | |
513 | ||
514 | fputs(USAGE_SEPARATOR, out); | |
515 | fputs(_("List the ranges of available memory with their online status.\n"), out); | |
516 | ||
517 | fputs(USAGE_OPTIONS, out); | |
0a1ed6df KZ |
518 | fputs(_(" -J, --json use JSON output format\n"), out); |
519 | fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); | |
0508f347 | 520 | fputs(_(" -a, --all list each individual memory block\n"), out); |
0a1ed6df KZ |
521 | fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); |
522 | fputs(_(" -n, --noheadings don't print headings\n"), out); | |
523 | fputs(_(" -o, --output <list> output columns\n"), out); | |
fcd4bbff | 524 | fputs(_(" --output-all output all columns\n"), out); |
0a1ed6df | 525 | fputs(_(" -r, --raw use raw output format\n"), out); |
d12e945d | 526 | fputs(_(" -S, --split <list> split ranges by specified columns\n"), out); |
0a1ed6df | 527 | fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out); |
c5332fbb | 528 | fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out); |
0a1ed6df | 529 | |
cad2d1ac | 530 | fputs(USAGE_SEPARATOR, out); |
bad4c729 | 531 | fprintf(out, USAGE_HELP_OPTIONS(22)); |
cad2d1ac | 532 | |
6e2d5a44 | 533 | fputs(USAGE_COLUMNS, out); |
cad2d1ac | 534 | for (i = 0; i < ARRAY_SIZE(coldescs); i++) |
6e2d5a44 | 535 | fprintf(out, " %10s %s\n", coldescs[i].name, _(coldescs[i].help)); |
cad2d1ac | 536 | |
bad4c729 | 537 | fprintf(out, USAGE_MAN_TAIL("lsmem(1)")); |
cad2d1ac | 538 | |
2c308875 | 539 | exit(EXIT_SUCCESS); |
cad2d1ac HC |
540 | } |
541 | ||
542 | int main(int argc, char **argv) | |
543 | { | |
c5332fbb KZ |
544 | struct lsmem _lsmem = { |
545 | .want_table = 1, | |
546 | .want_summary = 1 | |
547 | }, *lsmem = &_lsmem; | |
548 | ||
e4319a10 | 549 | const char *outarg = NULL, *splitarg = NULL, *prefix = NULL; |
cad2d1ac | 550 | int c; |
0a1ed6df | 551 | size_t i; |
cad2d1ac | 552 | |
c5332fbb | 553 | enum { |
fcd4bbff SK |
554 | LSMEM_OPT_SUMARRY = CHAR_MAX + 1, |
555 | OPT_OUTPUT_ALL | |
c5332fbb KZ |
556 | }; |
557 | ||
cad2d1ac HC |
558 | static const struct option longopts[] = { |
559 | {"all", no_argument, NULL, 'a'}, | |
0a1ed6df | 560 | {"bytes", no_argument, NULL, 'b'}, |
cad2d1ac | 561 | {"help", no_argument, NULL, 'h'}, |
0a1ed6df KZ |
562 | {"json", no_argument, NULL, 'J'}, |
563 | {"noheadings", no_argument, NULL, 'n'}, | |
564 | {"output", required_argument, NULL, 'o'}, | |
fcd4bbff | 565 | {"output-all", no_argument, NULL, OPT_OUTPUT_ALL}, |
0a1ed6df KZ |
566 | {"pairs", no_argument, NULL, 'P'}, |
567 | {"raw", no_argument, NULL, 'r'}, | |
cad2d1ac | 568 | {"sysroot", required_argument, NULL, 's'}, |
d12e945d | 569 | {"split", required_argument, NULL, 'S'}, |
cad2d1ac | 570 | {"version", no_argument, NULL, 'V'}, |
c5332fbb | 571 | {"summary", optional_argument, NULL, LSMEM_OPT_SUMARRY }, |
cad2d1ac HC |
572 | {NULL, 0, NULL, 0} |
573 | }; | |
574 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ | |
0a1ed6df | 575 | { 'J', 'P', 'r' }, |
d12e945d | 576 | { 'S', 'a' }, |
cad2d1ac HC |
577 | { 0 } |
578 | }; | |
579 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
580 | ||
581 | setlocale(LC_ALL, ""); | |
582 | bindtextdomain(PACKAGE, LOCALEDIR); | |
583 | textdomain(PACKAGE); | |
2c308875 | 584 | close_stdout_atexit(); |
cad2d1ac | 585 | |
d12e945d | 586 | while ((c = getopt_long(argc, argv, "abhJno:PrS:s:V", longopts, NULL)) != -1) { |
cad2d1ac HC |
587 | |
588 | err_exclusive_options(c, longopts, excl, excl_st); | |
589 | ||
590 | switch (c) { | |
591 | case 'a': | |
0cf0710a | 592 | lsmem->list_all = 1; |
0a1ed6df KZ |
593 | break; |
594 | case 'b': | |
0cf0710a | 595 | lsmem->bytes = 1; |
cad2d1ac | 596 | break; |
0a1ed6df | 597 | case 'J': |
0cf0710a | 598 | lsmem->json = 1; |
019ad6c1 | 599 | lsmem->want_summary = 0; |
0a1ed6df KZ |
600 | break; |
601 | case 'n': | |
0cf0710a | 602 | lsmem->noheadings = 1; |
0a1ed6df KZ |
603 | break; |
604 | case 'o': | |
605 | outarg = optarg; | |
606 | break; | |
fcd4bbff SK |
607 | case OPT_OUTPUT_ALL: |
608 | for (ncolumns = 0; (size_t)ncolumns < ARRAY_SIZE(coldescs); ncolumns++) | |
609 | columns[ncolumns] = ncolumns; | |
610 | break; | |
0a1ed6df | 611 | case 'P': |
0cf0710a | 612 | lsmem->export = 1; |
019ad6c1 | 613 | lsmem->want_summary = 0; |
0a1ed6df KZ |
614 | break; |
615 | case 'r': | |
0cf0710a | 616 | lsmem->raw = 1; |
019ad6c1 | 617 | lsmem->want_summary = 0; |
cad2d1ac HC |
618 | break; |
619 | case 's': | |
e4319a10 | 620 | prefix = optarg; |
cad2d1ac | 621 | break; |
d12e945d KZ |
622 | case 'S': |
623 | splitarg = optarg; | |
624 | break; | |
c5332fbb KZ |
625 | case LSMEM_OPT_SUMARRY: |
626 | if (optarg) { | |
627 | if (strcmp(optarg, "never") == 0) | |
628 | lsmem->want_summary = 0; | |
629 | else if (strcmp(optarg, "only") == 0) | |
630 | lsmem->want_table = 0; | |
631 | else if (strcmp(optarg, "always") == 0) | |
632 | lsmem->want_summary = 1; | |
633 | else | |
634 | errx(EXIT_FAILURE, _("unsupported --summary argument")); | |
635 | } else | |
636 | lsmem->want_table = 0; | |
637 | break; | |
2c308875 KZ |
638 | |
639 | case 'h': | |
640 | usage(); | |
641 | case 'V': | |
642 | print_version(EXIT_SUCCESS); | |
cad2d1ac | 643 | default: |
677ec86c | 644 | errtryhelp(EXIT_FAILURE); |
cad2d1ac HC |
645 | } |
646 | } | |
647 | ||
6e1eda6f RM |
648 | if (argc != optind) { |
649 | warnx(_("bad usage")); | |
650 | errtryhelp(EXIT_FAILURE); | |
651 | } | |
cad2d1ac | 652 | |
d4625442 KZ |
653 | if (lsmem->want_table + lsmem->want_summary == 0) |
654 | errx(EXIT_FAILURE, _("options --{raw,json,pairs} and --summary=only are mutually exclusive")); | |
655 | ||
e4319a10 KZ |
656 | ul_path_init_debug(); |
657 | ||
658 | lsmem->sysmem = ul_new_path(_PATH_SYS_MEMORY); | |
659 | if (!lsmem->sysmem) | |
660 | err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_MEMORY); | |
661 | if (prefix && ul_path_set_prefix(lsmem->sysmem, prefix) != 0) | |
662 | err(EXIT_FAILURE, _("invalid argument to --sysroot")); | |
c02980d6 KZ |
663 | if (!ul_path_is_accessible(lsmem->sysmem)) |
664 | err(EXIT_FAILURE, _("cannot open %s"), _PATH_SYS_MEMORY); | |
e4319a10 | 665 | |
d4625442 KZ |
666 | /* Shortcut to avoid scols machinery on --summary=only */ |
667 | if (lsmem->want_table == 0 && lsmem->want_summary) { | |
668 | read_basic_info(lsmem); | |
669 | read_info(lsmem); | |
670 | print_summary(lsmem); | |
671 | return EXIT_SUCCESS; | |
672 | } | |
673 | ||
0a1ed6df KZ |
674 | /* |
675 | * Default columns | |
676 | */ | |
cad2d1ac | 677 | if (!ncolumns) { |
0a1ed6df KZ |
678 | add_column(columns, ncolumns++, COL_RANGE); |
679 | add_column(columns, ncolumns++, COL_SIZE); | |
680 | add_column(columns, ncolumns++, COL_STATE); | |
681 | add_column(columns, ncolumns++, COL_REMOVABLE); | |
682 | add_column(columns, ncolumns++, COL_BLOCK); | |
cad2d1ac HC |
683 | } |
684 | ||
0a1ed6df KZ |
685 | if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), |
686 | &ncolumns, column_name_to_id) < 0) | |
687 | return EXIT_FAILURE; | |
688 | ||
689 | /* | |
690 | * Initialize output | |
691 | */ | |
692 | scols_init_debug(0); | |
693 | ||
0cf0710a | 694 | if (!(lsmem->table = scols_new_table())) |
0a1ed6df | 695 | errx(EXIT_FAILURE, _("failed to initialize output table")); |
0cf0710a KZ |
696 | scols_table_enable_raw(lsmem->table, lsmem->raw); |
697 | scols_table_enable_export(lsmem->table, lsmem->export); | |
698 | scols_table_enable_json(lsmem->table, lsmem->json); | |
699 | scols_table_enable_noheadings(lsmem->table, lsmem->noheadings); | |
0a1ed6df | 700 | |
0cf0710a KZ |
701 | if (lsmem->json) |
702 | scols_table_set_name(lsmem->table, "memory"); | |
cad2d1ac | 703 | |
0a1ed6df KZ |
704 | for (i = 0; i < ncolumns; i++) { |
705 | struct coldesc *ci = get_column_desc(i); | |
704f9ba6 KZ |
706 | struct libscols_column *cl; |
707 | ||
708 | cl = scols_table_new_column(lsmem->table, ci->name, ci->whint, ci->flags); | |
709 | if (!cl) | |
0a1ed6df | 710 | err(EXIT_FAILURE, _("Failed to initialize output column")); |
704f9ba6 KZ |
711 | |
712 | if (lsmem->json) { | |
713 | int id = get_column_id(i); | |
714 | ||
715 | switch (id) { | |
716 | case COL_SIZE: | |
717 | if (!lsmem->bytes) | |
718 | break; | |
719 | /* fallthrough */ | |
720 | case COL_NODE: | |
721 | scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); | |
722 | break; | |
723 | case COL_REMOVABLE: | |
724 | scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); | |
725 | break; | |
726 | } | |
727 | } | |
cad2d1ac | 728 | } |
0a1ed6df | 729 | |
d12e945d KZ |
730 | if (splitarg) { |
731 | int split[ARRAY_SIZE(coldescs)] = { 0 }; | |
732 | static size_t nsplits = 0; | |
733 | ||
d12e945d KZ |
734 | if (strcasecmp(splitarg, "none") == 0) |
735 | ; | |
736 | else if (string_add_to_idarray(splitarg, split, ARRAY_SIZE(split), | |
737 | &nsplits, column_name_to_id) < 0) | |
738 | return EXIT_FAILURE; | |
739 | ||
96cbe362 KZ |
740 | set_split_policy(lsmem, split, nsplits); |
741 | ||
d12e945d | 742 | } else |
96cbe362 KZ |
743 | /* follow output columns */ |
744 | set_split_policy(lsmem, columns, ncolumns); | |
0a1ed6df KZ |
745 | |
746 | /* | |
747 | * Read data and print output | |
748 | */ | |
0cf0710a KZ |
749 | read_basic_info(lsmem); |
750 | read_info(lsmem); | |
0a1ed6df | 751 | |
c5332fbb KZ |
752 | if (lsmem->want_table) { |
753 | fill_scols_table(lsmem); | |
754 | scols_print_table(lsmem->table); | |
755 | ||
756 | if (lsmem->want_summary) | |
757 | fputc('\n', stdout); | |
758 | } | |
0a1ed6df | 759 | |
c5332fbb KZ |
760 | if (lsmem->want_summary) |
761 | print_summary(lsmem); | |
0a1ed6df | 762 | |
0cf0710a | 763 | scols_unref_table(lsmem->table); |
e4319a10 | 764 | ul_unref_path(lsmem->sysmem); |
bf1d0a4e | 765 | free_info(lsmem); |
cad2d1ac HC |
766 | return 0; |
767 | } |