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