Home | History | Annotate | Download | only in memory_hotplug
      1 /*
      2  * memtoy:  commands.c - command line interface
      3  *
      4  * A brute force/ad hoc command interpreter:
      5  * + parse commands [interactive or batch]
      6  * + convert/validate arguments
      7  * + some general/administrative commands herein
      8  * + actual segment management routines in segment.c
      9  */
     10 /*
     11  *  Copyright (c) 2005 Hewlett-Packard, Inc
     12  *  All rights reserved.
     13  */
     14 
     15 /*
     16  *  This program is free software; you can redistribute it and/or modify
     17  *  it under the terms of the GNU General Public License as published by
     18  *  the Free Software Foundation; either version 2 of the License, or
     19  *  (at your option) any later version.
     20  *
     21  *  This program is distributed in the hope that it will be useful,
     22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     24  *  GNU General Public License for more details.
     25  *
     26  *  You should have received a copy of the GNU General Public License
     27  *  along with this program; if not, write to the Free Software
     28  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
     29  */
     30 
     31 #include "config.h"
     32 
     33 #ifdef HAVE_NUMA_V2
     34 #include <sys/types.h>
     35 #include <sys/time.h>
     36 #include <sys/mman.h>
     37 #include <ctype.h>
     38 #include <errno.h>
     39 #include <numa.h>
     40 #include <numaif.h>
     41 #include <stdarg.h>
     42 #include <stdlib.h>
     43 #include <stdio.h>
     44 #include <string.h>
     45 #include <unistd.h>
     46 #include <sys/syscall.h>
     47 
     48 #include "memtoy.h"
     49 #include "test.h"
     50 
     51 #define CMD_SUCCESS 0
     52 #define CMD_ERROR   1
     53 
     54 #ifndef __NR_migrate_pages
     55 #define __NR_migrate_pages 0
     56 #endif
     57 
     58 #ifndef MPOL_MF_WAIT
     59 #define MPOL_MF_WAIT    (1<<2)	/* Wait for existing pages to migrate */
     60 #endif
     61 
     62 static inline int nodemask_isset(nodemask_t * mask, int node)
     63 {
     64 	if ((unsigned)node >= NUMA_NUM_NODES)
     65 		return 0;
     66 	if (mask->n[node / (8 * sizeof(unsigned long))] &
     67 	    (1UL << (node % (8 * sizeof(unsigned long)))))
     68 		return 1;
     69 	return 0;
     70 }
     71 
     72 static inline void nodemask_set(nodemask_t * mask, int node)
     73 {
     74 	mask->n[node / (8 * sizeof(unsigned long))] |=
     75 	    (1UL << (node % (8 * sizeof(unsigned long))));
     76 }
     77 
     78 static char *whitespace = " \t";
     79 
     80 /*
     81  * =========================================================================
     82  */
     83 static int help_me(char *);	/* forward reference */
     84 
     85 /*
     86  * required_arg -- check for a required argument; issue message if not there
     87  *
     88  * return true if arg [something] exists; else return false
     89  */
     90 static bool required_arg(char *arg, char *arg_name)
     91 {
     92 	glctx_t *gcp = &glctx;
     93 
     94 	if (*arg != '\0')
     95 		return true;
     96 
     97 	fprintf(stderr, "%s:  command '%s' missing required argument: %s\n\n",
     98 		gcp->program_name, gcp->cmd_name, arg_name);
     99 	help_me(gcp->cmd_name);
    100 
    101 	return false;
    102 }
    103 
    104 /*
    105  *  size_kmgp() -- convert ascii arg to numeric and scale as requested
    106  */
    107 #define KILO_SHIFT 10
    108 static size_t size_kmgp(char *arg)
    109 {
    110 	size_t argval;
    111 	char *next;
    112 
    113 	argval = strtoul(arg, &next, 0);
    114 	if (*next == '\0')
    115 		return argval;
    116 
    117 	switch (tolower(*next)) {
    118 	case 'p':		/* pages */
    119 		argval *= glctx.pagesize;
    120 		break;
    121 
    122 	case 'k':
    123 		argval <<= KILO_SHIFT;
    124 		break;
    125 
    126 	case 'm':
    127 		argval <<= KILO_SHIFT * 2;
    128 		break;
    129 
    130 	case 'g':
    131 		argval <<= KILO_SHIFT * 3;
    132 		break;
    133 
    134 	default:
    135 		return BOGUS_SIZE;	/* bogus chars after number */
    136 	}
    137 
    138 	return argval;
    139 }
    140 
    141 static size_t get_scaled_value(char *args, char *what)
    142 {
    143 	glctx_t *gcp = &glctx;
    144 	size_t size = size_kmgp(args);
    145 
    146 	if (size == BOGUS_SIZE) {
    147 		fprintf(stderr, "%s:  segment %s must be numeric value"
    148 			" followed by optional k, m, g or p [pages] scale factor.\n",
    149 			gcp->program_name, what);
    150 	}
    151 
    152 	return size;
    153 }
    154 
    155 static int get_range(char *args, range_t * range, char **nextarg)
    156 {
    157 
    158 	if (isdigit(*args)) {
    159 		char *nextarg;
    160 
    161 		args = strtok_r(args, whitespace, &nextarg);
    162 		range->offset = get_scaled_value(args, "offset");
    163 		if (range->offset == BOGUS_SIZE)
    164 			return CMD_ERROR;
    165 		args = nextarg + strspn(nextarg, whitespace);
    166 
    167 		/*
    168 		 * <length> ... only if offset specified
    169 		 */
    170 		if (*args != '\0') {
    171 			args = strtok_r(args, whitespace, &nextarg);
    172 			if (*args != '*') {
    173 				range->length =
    174 				    get_scaled_value(args, "length");
    175 				if (range->length == BOGUS_SIZE)
    176 					return CMD_ERROR;
    177 			} else
    178 				range->length = 0;	/* map to end of file */
    179 			args = nextarg + strspn(nextarg, whitespace);
    180 		}
    181 	}
    182 
    183 	*nextarg = args;
    184 	return CMD_SUCCESS;
    185 }
    186 
    187 static int get_shared(char *args)
    188 {
    189 	glctx_t *gcp = &glctx;
    190 	int segflag = MAP_PRIVATE;
    191 
    192 	if (!strcmp(args, "shared"))
    193 		segflag = MAP_SHARED;
    194 	else if (*args != '\0' && strcmp(args, "private")) {
    195 		fprintf(stderr, "%s:  anon seg access type must be one of:  "
    196 			"'private' or 'shared'\n", gcp->program_name);
    197 		return -1;
    198 	}
    199 	return segflag;
    200 }
    201 
    202 /*
    203  * get_access() - check args for 'read'\'write'
    204  * return:
    205  *	1 = read
    206  *	2 = write
    207  *	0 = neither [error]
    208  */
    209 static int get_access(char *args)
    210 {
    211 	glctx_t *gcp = &glctx;
    212 	int axcs = 1;
    213 	int len = strlen(args);
    214 
    215 	if (tolower(*args) == 'w')
    216 		axcs = 2;
    217 	else if (len != 0 && tolower(*args) != 'r') {
    218 		fprintf(stderr,
    219 			"%s:  segment access must be 'r[ead]' or 'w[rite]'\n",
    220 			gcp->program_name);
    221 		return 0;
    222 	}
    223 
    224 	return axcs;
    225 }
    226 
    227 static bool numa_supported(void)
    228 {
    229 	glctx_t *gcp = &glctx;
    230 
    231 	if (gcp->numa_max_node <= 0) {
    232 		fprintf(stderr, "%s:  no NUMA support on this platform\n",
    233 			gcp->program_name);
    234 		return false;
    235 	}
    236 	return true;
    237 }
    238 
    239 static struct policies {
    240 	char *pol_name;
    241 	int pol_flag;
    242 } policies[] = {
    243 	{
    244 	"default", MPOL_DEFAULT}, {
    245 	"preferred", MPOL_PREFERRED}, {
    246 	"bind", MPOL_BIND}, {
    247 	"interleaved", MPOL_INTERLEAVE}, {
    248 	NULL, -1}
    249 };
    250 
    251 /*
    252  * get_mbind_policy() - parse <policy> argument to mbind command
    253  *
    254  * format:  <mpol>[+<flags>]
    255  * <mpol> is one of the policies[] above.
    256  * '+<flags>' = modifiers to mbind() call.  parsed by get_mbind_flags()
    257  */
    258 static int get_mbind_policy(char *args, char **nextarg)
    259 {
    260 	glctx_t *gcp = &glctx;
    261 	struct policies *polp;
    262 	char *pol;
    263 
    264 	pol = args;
    265 	args += strcspn(args, " 	+");
    266 
    267 	for (polp = policies; polp->pol_name != NULL; ++polp) {
    268 		size_t plen = args - pol;
    269 
    270 		if (strncmp(pol, polp->pol_name, plen))
    271 			continue;
    272 
    273 		*nextarg = args;
    274 		return polp->pol_flag;
    275 	}
    276 
    277 	fprintf(stderr, "%s:  unrecognized policy %s\n",
    278 		gcp->program_name, pol);
    279 	return CMD_ERROR;
    280 }
    281 
    282 /*
    283  * get_mbind_flags() - parse mbind(2) modifier flags
    284  *
    285  * format: +move[+wait]
    286  * 'move' specifies that currently allocated pages should be migrated.
    287  *        => MPOL_MF_MOVE
    288  * 'wait' [only if 'move' specified] specifies that mbind(2) should not
    289  *        return until all pages that can be migrated have been.
    290  *        => MPOL_MF_WAIT
    291  *
    292  * returns flags on success; -1 on error
    293  */
    294 static int get_mbind_flags(char *args, char **nextarg)
    295 {
    296 	glctx_t *gcp = &glctx;
    297 	char *arg;
    298 	int flags = 0;
    299 
    300 	arg = args;
    301 	args += strcspn(args, " 	+");
    302 
    303 	if (strncmp(arg, "move", args - arg))
    304 		goto flags_err;
    305 
    306 	flags = MPOL_MF_MOVE;
    307 
    308 	if (*args == '+') {
    309 		++args;
    310 		if (*args == '\0') {
    311 			fprintf(stderr, "%s:  expected 'wait' after '+'\n",
    312 				gcp->program_name);
    313 			return -1;
    314 		}
    315 		arg = strtok_r(args, "  ", &args);
    316 		if (strncmp(arg, "wait", strlen(arg)))
    317 			goto flags_err;
    318 
    319 		flags |= MPOL_MF_WAIT;
    320 	}
    321 
    322 	*nextarg = args;
    323 	return flags;
    324 
    325 flags_err:
    326 	fprintf(stderr, "%s: unrecognized mbind flag: %s\n",
    327 		gcp->program_name, arg);
    328 	return -1;
    329 
    330 }
    331 
    332 /*
    333  * get_nodemask() -- get nodemask from comma-separated list of node ids.
    334  *
    335  * N.B., caller must free returned nodemask
    336  */
    337 static nodemask_t *get_nodemask(char *args)
    338 {
    339 	glctx_t *gcp = &glctx;
    340 	nodemask_t *nmp = (nodemask_t *) calloc(1, sizeof(nodemask_t));
    341 	char *next;
    342 	int node;
    343 	while (*args != '\0') {
    344 		if (!isdigit(*args)) {
    345 			fprintf(stderr, "%s:  expected digit for <node/list>\n",
    346 				gcp->program_name);
    347 			goto out_err;
    348 		}
    349 
    350 		node = strtoul(args, &next, 10);
    351 
    352 		if (node > gcp->numa_max_node) {
    353 			fprintf(stderr, "%s:  node ids must be <= %d\n",
    354 				gcp->program_name, gcp->numa_max_node);
    355 			goto out_err;
    356 		}
    357 
    358 		nodemask_set(nmp, node);
    359 
    360 		if (*next == '\0')
    361 			return nmp;
    362 		if (*next != ',') {
    363 			break;
    364 		}
    365 		args = next + 1;
    366 	}
    367 
    368 out_err:
    369 	free(nmp);
    370 	return NULL;
    371 }
    372 
    373 /*
    374  * get_arg_nodeid_list() -- get list [array] of node ids from comma-separated list.
    375  *
    376  * on success, returns count of id's in list; on error -1
    377  */
    378 static int get_arg_nodeid_list(char *args, unsigned int *list)
    379 {
    380 	glctx_t *gcp;
    381 	char *next;
    382 	nodemask_t my_allowed_nodes;
    383 	int node, count = 0;
    384 
    385 	gcp = &glctx;
    386 	my_allowed_nodes = numa_get_membind_compat();
    387 	while (*args != '\0') {
    388 		if (!isdigit(*args)) {
    389 			fprintf(stderr, "%s:  expected digit for <node/list>\n",
    390 				gcp->program_name);
    391 			return -1;
    392 		}
    393 
    394 		node = strtoul(args, &next, 10);
    395 
    396 		if (node > gcp->numa_max_node) {
    397 			fprintf(stderr, "%s:  node ids must be <= %d\n",
    398 				gcp->program_name, gcp->numa_max_node);
    399 			return -1;
    400 		}
    401 
    402 		if (!nodemask_isset(&my_allowed_nodes, node)) {
    403 			fprintf(stderr,
    404 				"%s:  node %d is not in my allowed node mask\n",
    405 				gcp->program_name, node);
    406 			return -1;
    407 		}
    408 
    409 		*(list + count++) = node;
    410 
    411 		if (*next == '\0')
    412 			return count;
    413 		if (*next != ',') {
    414 			break;
    415 		}
    416 
    417 		if (count >= gcp->numa_max_node) {
    418 			fprintf(stderr, "%s:  too many node ids in list\n",
    419 				gcp->program_name);
    420 		}
    421 		args = next + 1;
    422 	}
    423 
    424 	return -1;
    425 }
    426 
    427 /*
    428  * get_current_nodeid_list() - fill arg array with nodes from
    429  * current thread's allowed node mask.  return # of nodes in
    430  * mask.
    431  */
    432 static int get_current_nodeid_list(unsigned int *fromids)
    433 {
    434 	/*
    435 	 * FIXME (garrcoop): gcp is uninitialized and shortly hereafter used in
    436 	 * an initialization statement..... UHHHHHHH... test writer fail?
    437 	 */
    438 	glctx_t *gcp;
    439 	nodemask_t my_allowed_nodes;
    440 	int nr_nodes = 0, max_node = gcp->numa_max_node;
    441 	int node;
    442 
    443 	gcp = &glctx;
    444 	my_allowed_nodes = numa_get_membind_compat();
    445 	for (node = 0; node <= max_node; ++node) {
    446 		if (nodemask_isset(&my_allowed_nodes, node))
    447 			*(fromids + nr_nodes++) = node;
    448 	}
    449 
    450 	/*
    451 	 * shouldn't happen, but let 'em know if it does
    452 	 */
    453 	if (nr_nodes == 0)
    454 		fprintf(stderr, "%s:  my allowed node mask is empty !!???\n",
    455 			gcp->program_name);
    456 	return nr_nodes;
    457 }
    458 
    459 /*
    460  * NOTE (garrcoop): Get rid of an -Wunused warning. This wasn't deleted because
    461  * I don't know what the original intent was for this code.
    462  */
    463 #if 0
    464 static void not_implemented()
    465 {
    466 	glctx_t *gcp = &glctx;
    467 
    468 	fprintf(stderr, "%s:  %s not implemented yet\n",
    469 		gcp->program_name, gcp->cmd_name);
    470 }
    471 #endif
    472 
    473 /*
    474  * =========================================================================
    475  */
    476 static int quit(char *args)
    477 {
    478 	exit(0);		/* let cleanup() do its thing */
    479 }
    480 
    481 static int show_pid(char *args)
    482 {
    483 	glctx_t *gcp = &glctx;
    484 
    485 	printf("%s:  pid = %d\n", gcp->program_name, getpid());
    486 
    487 	return CMD_SUCCESS;
    488 }
    489 
    490 static int pause_me(char *args)
    491 {
    492 	// glctx_t *gcp = &glctx;
    493 
    494 	pause();
    495 	reset_signal();
    496 
    497 	return CMD_SUCCESS;
    498 }
    499 
    500 static char *numa_header = "  Node  Total Mem[MB]  Free Mem[MB]\n";
    501 static int numa_info(char *args)
    502 {
    503 	glctx_t *gcp = &glctx;
    504 	unsigned int *nodeids;
    505 	int nr_nodes, i;
    506 	bool do_header = true;
    507 
    508 	if (!numa_supported())
    509 		return CMD_ERROR;
    510 
    511 	nodeids = calloc(gcp->numa_max_node, sizeof(*nodeids));
    512 	nr_nodes = get_current_nodeid_list(nodeids);
    513 	if (nr_nodes < 0)
    514 		return CMD_ERROR;
    515 
    516 	for (i = 0; i < nr_nodes; ++i) {
    517 		int node = nodeids[i];
    518 		long node_size, node_free;
    519 
    520 		node_size = numa_node_size(node, &node_free);
    521 		if (node_size < 0) {
    522 			fprintf(stderr,
    523 				"%s:  numa_node_size() failed for node %d\n",
    524 				gcp->program_name, node);
    525 			return CMD_ERROR;
    526 		}
    527 
    528 		if (do_header) {
    529 			do_header = false;
    530 			puts(numa_header);
    531 		}
    532 		printf("  %3d  %9ld      %8ld\n", node,
    533 		       node_size / (1024 * 1024), node_free / (1024 * 1024));
    534 	}
    535 
    536 	return CMD_SUCCESS;
    537 }
    538 
    539 /*
    540  * migrate <to-node-id[s]> [<from-node-id[s]>]
    541  *
    542  * Node id[s] - single node id or comma-separated list
    543  * <to-node-id[s]> - 1-for-1 with <from-node-id[s]>, OR
    544  * if <from-node-id[s]> omitted, <to-node-id[s]> must be
    545  * a single node id.
    546  */
    547 static int migrate_process(char *args)
    548 {
    549 	glctx_t *gcp = &glctx;
    550 	unsigned int *fromids, *toids;
    551 	char *idlist, *nextarg;
    552 	struct timeval t_start, t_end;
    553 	int nr_to, nr_from;
    554 	int nr_migrated;
    555 	int ret = CMD_ERROR;
    556 
    557 	if (!numa_supported())
    558 		return CMD_ERROR;
    559 
    560 	toids = calloc(gcp->numa_max_node, sizeof(*toids));
    561 	fromids = calloc(gcp->numa_max_node, sizeof(*fromids));
    562 
    563 	/*
    564 	 * <to-node-id[s]>
    565 	 */
    566 	if (!required_arg(args, "<to-node-id[s]>"))
    567 		return CMD_ERROR;
    568 	idlist = strtok_r(args, whitespace, &nextarg);
    569 	nr_to = get_arg_nodeid_list(idlist, toids);
    570 	if (nr_to <= 0)
    571 		goto out_free;
    572 	args = nextarg + strspn(nextarg, whitespace);
    573 
    574 	if (*args != '\0') {
    575 		/*
    576 		 * apparently, <from-node-id[s]> present
    577 		 */
    578 		idlist = strtok_r(args, whitespace, &nextarg);
    579 		nr_from = get_arg_nodeid_list(idlist, fromids);
    580 		if (nr_from <= 0)
    581 			goto out_free;
    582 		if (nr_from != nr_to) {
    583 			fprintf(stderr,
    584 				"%s:  # of 'from' ids must = # of 'to' ids\n",
    585 				gcp->program_name);
    586 			goto out_free;
    587 		}
    588 	} else {
    589 		int i;
    590 
    591 		/*
    592 		 * no <from-node-id[s]>, nr_to must == 1,
    593 		 * get fromids from memory policy.
    594 		 */
    595 		if (nr_to > 1) {
    596 			fprintf(stderr, "%s:  # to ids must = 1"
    597 				" when no 'from' ids specified\n",
    598 				gcp->program_name);
    599 			goto out_free;
    600 		}
    601 		nr_from = get_current_nodeid_list(fromids);
    602 		if (nr_from <= 0)
    603 			goto out_free;
    604 
    605 		/*
    606 		 * remove 'to' node from 'from' list.  to and from
    607 		 * lists can't intersect.
    608 		 */
    609 		for (i = nr_from - 1; i >= 0; --i) {
    610 			if (*toids == *(fromids + i)) {
    611 				while (i <= nr_from) {
    612 					*(fromids + i) = *(fromids + i + 1);
    613 					++i;
    614 				}
    615 				--nr_from;
    616 				break;
    617 			}
    618 		}
    619 
    620 		/*
    621 		 * fill out nr_from toids with the single 'to' node
    622 		 */
    623 		for (; nr_to < nr_from; ++nr_to)
    624 			*(toids + nr_to) = *toids;	/* toids[0] */
    625 	}
    626 
    627 	gettimeofday(&t_start, NULL);
    628 	nr_migrated =
    629 	    syscall(__NR_migrate_pages, getpid(), nr_from, fromids, toids);
    630 	if (nr_migrated < 0) {
    631 		int err = errno;
    632 		fprintf(stderr, "%s: migrate_pages failed - %s\n",
    633 			gcp->program_name, strerror(err));
    634 		goto out_free;
    635 	}
    636 	gettimeofday(&t_end, NULL);
    637 	printf("%s:  migrated %d pages in %6.3fsecs\n",
    638 	       gcp->program_name, nr_migrated,
    639 	       (float)(tv_diff_usec(&t_start, &t_end)) / 1000000.0);
    640 	ret = CMD_SUCCESS;
    641 
    642 out_free:
    643 	free(toids);
    644 	free(fromids);
    645 	return ret;
    646 }
    647 
    648 static int show_seg(char *args)
    649 {
    650 	glctx_t *gcp = &glctx;
    651 
    652 	char *segname = NULL, *nextarg;
    653 
    654 	args += strspn(args, whitespace);
    655 	if (*args != '\0')
    656 		segname = strtok_r(args, whitespace, &nextarg);
    657 
    658 	if (!segment_show(segname))
    659 		return CMD_ERROR;
    660 
    661 	return CMD_SUCCESS;
    662 }
    663 
    664 /*
    665  * anon_seg:  <seg-name> <size>[kmgp] [private|shared]
    666  */
    667 static int anon_seg(char *args)
    668 {
    669 	glctx_t *gcp = &glctx;
    670 
    671 	char *segname, *nextarg;
    672 	range_t range = { 0L, 0L };
    673 	int segflag = 0;
    674 
    675 	args += strspn(args, whitespace);
    676 
    677 	if (!required_arg(args, "<seg-name>"))
    678 		return CMD_ERROR;
    679 	segname = strtok_r(args, whitespace, &nextarg);
    680 	args = nextarg + strspn(nextarg, whitespace);
    681 
    682 	if (!required_arg(args, "<size>"))
    683 		return CMD_ERROR;
    684 	args = strtok_r(args, whitespace, &nextarg);
    685 	range.length = get_scaled_value(args, "size");
    686 	if (range.length == BOGUS_SIZE)
    687 		return CMD_ERROR;
    688 	args = nextarg + strspn(nextarg, whitespace);
    689 
    690 	if (*args != '\0') {
    691 		segflag = get_shared(args);
    692 		if (segflag == -1)
    693 			return CMD_ERROR;
    694 	}
    695 
    696 	if (!segment_register(SEGT_ANON, segname, &range, segflag))
    697 		return CMD_ERROR;
    698 
    699 	return CMD_SUCCESS;
    700 }
    701 
    702 /*
    703  * file_seg:  <path-name> [<offset>[kmgp] <length>[kmgp]  [private|shared]]
    704  */
    705 static int file_seg(char *args)
    706 {
    707 	glctx_t *gcp = &glctx;
    708 
    709 	char *pathname, *nextarg;
    710 	range_t range = { 0L, 0L };
    711 	int segflag = MAP_PRIVATE;
    712 
    713 	args += strspn(args, whitespace);
    714 
    715 	if (!required_arg(args, "<path-name>"))
    716 		return CMD_ERROR;
    717 	pathname = strtok_r(args, whitespace, &nextarg);
    718 	args = nextarg + strspn(nextarg, whitespace);
    719 
    720 	/*
    721 	 * offset, length are optional
    722 	 */
    723 	if (get_range(args, &range, &nextarg) == CMD_ERROR)
    724 		return CMD_ERROR;
    725 	args = nextarg;
    726 
    727 	if (*args != '\0') {
    728 		segflag = get_shared(args);
    729 		if (segflag == -1)
    730 			return CMD_ERROR;
    731 	}
    732 
    733 	if (!segment_register(SEGT_FILE, pathname, &range, segflag))
    734 		return CMD_ERROR;
    735 
    736 	return CMD_SUCCESS;
    737 }
    738 
    739 /*
    740  * remove_seg:  <seg-name> [<seg-name> ...]
    741  */
    742 static int remove_seg(char *args)
    743 {
    744 	glctx_t *gcp = &glctx;
    745 
    746 	args += strspn(args, whitespace);
    747 	if (!required_arg(args, "<seg-name>"))
    748 		return CMD_ERROR;
    749 
    750 	while (*args != '\0') {
    751 		char *segname, *nextarg;
    752 
    753 		segname = strtok_r(args, whitespace, &nextarg);
    754 		args = nextarg + strspn(nextarg, whitespace);
    755 
    756 		segment_remove(segname);
    757 	}
    758 	return 0;
    759 }
    760 
    761 /*
    762  * touch_seg:  <seg-name> [<offset> <length>] [read|write]
    763  */
    764 static int touch_seg(char *args)
    765 {
    766 	glctx_t *gcp = &glctx;
    767 
    768 	char *segname, *nextarg;
    769 	range_t range = { 0L, 0L };
    770 	int axcs;
    771 
    772 	args += strspn(args, whitespace);
    773 	if (!required_arg(args, "<seg-name>"))
    774 		return CMD_ERROR;
    775 	segname = strtok_r(args, whitespace, &nextarg);
    776 	args = nextarg + strspn(nextarg, whitespace);
    777 
    778 	/*
    779 	 * offset, length are optional
    780 	 */
    781 	if (get_range(args, &range, &nextarg) == CMD_ERROR)
    782 		return CMD_ERROR;
    783 	args = nextarg;
    784 
    785 	axcs = get_access(args);
    786 	if (axcs == 0)
    787 		return CMD_ERROR;
    788 
    789 	if (!segment_touch(segname, &range, axcs - 1))
    790 		return CMD_ERROR;
    791 
    792 	return CMD_SUCCESS;
    793 }
    794 
    795 /*
    796  * unmap <seg-name> - unmap specified segment, but remember name/size/...
    797  */
    798 static int unmap_seg(char *args)
    799 {
    800 	glctx_t *gcp = &glctx;
    801 	char *segname, *nextarg;
    802 
    803 	args += strspn(args, whitespace);
    804 	if (!required_arg(args, "<seg-name>"))
    805 		return CMD_ERROR;
    806 	segname = strtok_r(args, whitespace, &nextarg);
    807 	args = nextarg + strspn(nextarg, whitespace);
    808 
    809 	if (!segment_unmap(segname))
    810 		return CMD_ERROR;
    811 
    812 	return CMD_SUCCESS;
    813 }
    814 
    815 /*
    816  * map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>]
    817  */
    818 static int map_seg(char *args)
    819 {
    820 	glctx_t *gcp = &glctx;
    821 
    822 	char *segname, *nextarg;
    823 	range_t range = { 0L, 0L };
    824 	range_t *rangep = NULL;
    825 	int segflag = MAP_PRIVATE;
    826 
    827 	args += strspn(args, whitespace);
    828 	if (!required_arg(args, "<seg-name>"))
    829 		return CMD_ERROR;
    830 	segname = strtok_r(args, whitespace, &nextarg);
    831 	args = nextarg + strspn(nextarg, whitespace);
    832 
    833 	/*
    834 	 * offset, length are optional
    835 	 */
    836 	if (get_range(args, &range, &nextarg) == CMD_ERROR)
    837 		return CMD_ERROR;
    838 	if (args != nextarg) {
    839 		rangep = &range;	/* override any registered range */
    840 		args = nextarg;
    841 	}
    842 
    843 	if (*args != '\0') {
    844 		segflag = get_shared(args);
    845 		if (segflag == -1)
    846 			return CMD_ERROR;
    847 	}
    848 
    849 	if (!segment_map(segname, rangep, segflag))
    850 		return CMD_ERROR;
    851 
    852 	return CMD_SUCCESS;
    853 }
    854 
    855 /*
    856  * mbind <seg-name> [<offset>[kmgp] <length>[kmgp]] <policy> <node-list>
    857  */
    858 static int mbind_seg(char *args)
    859 {
    860 	glctx_t *gcp = &glctx;
    861 
    862 	char *segname, *nextarg;
    863 	range_t range = { 0L, 0L };
    864 	nodemask_t *nodemask = NULL;
    865 	int policy, flags = 0;
    866 	int ret;
    867 
    868 	if (!numa_supported())
    869 		return CMD_ERROR;
    870 
    871 	args += strspn(args, whitespace);
    872 	if (!required_arg(args, "<seg-name>"))
    873 		return CMD_ERROR;
    874 	segname = strtok_r(args, whitespace, &nextarg);
    875 	args = nextarg + strspn(nextarg, whitespace);
    876 
    877 	/*
    878 	 * offset, length are optional
    879 	 */
    880 	if (get_range(args, &range, &nextarg) == CMD_ERROR)
    881 		return CMD_ERROR;
    882 	args = nextarg;
    883 
    884 	if (!required_arg(args, "<policy>"))
    885 		return CMD_ERROR;
    886 	policy = get_mbind_policy(args, &nextarg);
    887 	if (policy < 0)
    888 		return CMD_ERROR;
    889 
    890 	args = nextarg + strspn(nextarg, whitespace);
    891 	if (*args == '+') {
    892 		flags = get_mbind_flags(++args, &nextarg);
    893 		if (flags == -1)
    894 			return CMD_ERROR;
    895 	}
    896 	args = nextarg + strspn(nextarg, whitespace);
    897 
    898 	if (policy != MPOL_DEFAULT) {
    899 		if (!required_arg(args, "<node/list>"))
    900 			return CMD_ERROR;
    901 		nodemask = get_nodemask(args);
    902 		if (nodemask == NULL)
    903 			return CMD_ERROR;
    904 	}
    905 
    906 	ret = CMD_SUCCESS;
    907 #if 1				// for testing
    908 	if (!segment_mbind(segname, &range, policy, nodemask, flags))
    909 		ret = CMD_ERROR;
    910 #endif
    911 
    912 	if (nodemask != NULL)
    913 		free(nodemask);
    914 	return ret;
    915 }
    916 
    917 /*
    918  *  shmem_seg - create [shmget] and register a SysV shared memory segment
    919  *              of specified size
    920  */
    921 static int shmem_seg(char *args)
    922 {
    923 	glctx_t *gcp = &glctx;
    924 
    925 	char *segname, *nextarg;
    926 	range_t range = { 0L, 0L };
    927 
    928 	args += strspn(args, whitespace);
    929 
    930 	if (!required_arg(args, "<seg-name>"))
    931 		return CMD_ERROR;
    932 	segname = strtok_r(args, whitespace, &nextarg);
    933 	args = nextarg + strspn(nextarg, whitespace);
    934 
    935 	if (!required_arg(args, "<size>"))
    936 		return CMD_ERROR;
    937 	args = strtok_r(args, whitespace, &nextarg);
    938 	range.length = get_scaled_value(args, "size");
    939 	if (range.length == BOGUS_SIZE)
    940 		return CMD_ERROR;
    941 	args = nextarg + strspn(nextarg, whitespace);
    942 
    943 	if (!segment_register(SEGT_SHM, segname, &range, MAP_SHARED))
    944 		return CMD_ERROR;
    945 
    946 	return CMD_SUCCESS;
    947 }
    948 
    949 /*
    950  * where <seg-name> [<offset>[kmgp] <length>[kmgp]]  - show node location
    951  * of specified range of segment.
    952  *
    953  * NOTE: if neither <offset> nor <length> specified, <offset> defaults
    954  * to 0 [start of segment], as usual, and length defaults to 64 pages
    955  * rather than the entire segment.  Suitable for a "quick look" at where
    956  * segment resides.
    957  */
    958 static int where_seg(char *args)
    959 {
    960 	glctx_t *gcp = &glctx;
    961 
    962 	char *segname, *nextarg;
    963 	range_t range = { 0L, 0L };
    964 	int ret;
    965 
    966 	if (!numa_supported())
    967 		return CMD_ERROR;
    968 
    969 	args += strspn(args, whitespace);
    970 	if (!required_arg(args, "<seg-name>"))
    971 		return CMD_ERROR;
    972 	segname = strtok_r(args, whitespace, &nextarg);
    973 	args = nextarg + strspn(nextarg, whitespace);
    974 
    975 	/*
    976 	 * offset, length are optional
    977 	 */
    978 	if (get_range(args, &range, &nextarg) == CMD_ERROR)
    979 		return CMD_ERROR;
    980 	if (args == nextarg)
    981 		range.length = 64 * gcp->pagesize;	/* default length */
    982 
    983 	if (!segment_location(segname, &range))
    984 		return CMD_ERROR;
    985 
    986 	return CMD_SUCCESS;
    987 }
    988 
    989 #if 0
    990 static int command(char *args)
    991 {
    992 	glctx_t *gcp = &glctx;
    993 
    994 	return CMD_SUCCESS;
    995 }
    996 
    997 #endif
    998 /*
    999  * =========================================================================
   1000  */
   1001 typedef int (*cmd_func_t) (char *);
   1002 
   1003 struct command {
   1004 	char *cmd_name;
   1005 	cmd_func_t cmd_func;	/* */
   1006 	char *cmd_help;
   1007 
   1008 } cmd_table[] = {
   1009 	{
   1010 	.cmd_name = "quit",.cmd_func = quit,.cmd_help =
   1011 		    "quit           - just what you think\n"
   1012 		    "\tEOF on stdin has the same effect\n"}, {
   1013 	.cmd_name = "help",.cmd_func = help_me,.cmd_help =
   1014 		    "help           - show this help\n"
   1015 		    "help <command> - display help for just <command>\n"}, {
   1016 	.cmd_name = "pid",.cmd_func = show_pid,.cmd_help =
   1017 		    "pid            - show process id of this session\n"}, {
   1018 	.cmd_name = "pause",.cmd_func = pause_me,.cmd_help =
   1019 		    "pause          - pause program until signal"
   1020 		    " -- e.g., INT, USR1\n"}, {
   1021 	.cmd_name = "numa",.cmd_func = numa_info,.cmd_help =
   1022 		    "numa          - display numa info as seen by this program.\n"
   1023 		    "\tshows nodes from which program may allocate memory\n"
   1024 		    "\twith total and free memory.\n"}, {
   1025 	.cmd_name = "migrate",.cmd_func = migrate_process,.cmd_help =
   1026 		    "migrate <to-node-id[s]> [<from-node-id[s]>] - \n"
   1027 		    "\tmigrate this process' memory from <from-node-id[s]>\n"
   1028 		    "\tto <to-node-id[s]>.  Specify multiple node ids as a\n"
   1029 		    "\tcomma-separated list. TODO - more info\n"}, {
   1030 	.cmd_name = "show",.cmd_func = show_seg,.cmd_help =
   1031 		    "show [<name>]  - show info for segment[s]; default all\n"},
   1032 	{
   1033 	.cmd_name = "anon",.cmd_func = anon_seg,.cmd_help =
   1034 		    "anon <seg-name> <seg-size>[k|m|g|p] [<seg-share>] -\n"
   1035 		    "\tdefine a MAP_ANONYMOUS segment of specified size\n"
   1036 		    "\t<seg-share> := private|shared - default = private\n"}, {
   1037 	.cmd_name = "file",.cmd_func = file_seg,.cmd_help =
   1038 		    "file <pathname> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] -\n"
   1039 		    "\tdefine a mapped file segment of specified length starting at the\n"
   1040 		    "\tspecified offset into the file.  <offset> and <length> may be\n"
   1041 		    "\tomitted and specified on the map command.\n"
   1042 		    "\t<seg-share> := private|shared - default = private\n"}, {
   1043 	.cmd_name = "shm",.cmd_func = shmem_seg,.cmd_help =
   1044 		    "shm <seg-name> <seg-size>[k|m|g|p] - \n"
   1045 		    "\tdefine a shared memory segment of specified size.\n"
   1046 		    "\tYou may need to increase limits [/proc/sys/kernel/shmmax].\n"
   1047 		    "\tUse map/unmap to attach/detach\n"}, {
   1048 	.cmd_name = "remove",.cmd_func = remove_seg,.cmd_help =
   1049 		    "remove <seg-name> [<seg-name> ...] - remove the named segment[s]\n"},
   1050 	{
   1051 	.cmd_name = "map",.cmd_func = map_seg,.cmd_help =
   1052 		    "map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] - \n"
   1053 		    "\tmmap()/shmat() a previously defined, currently unmapped() segment.\n"
   1054 		    "\t<offset> and <length> apply only to mapped files.\n"
   1055 		    "\tUse <length> of '*' or '0' to map to the end of the file.\n"},
   1056 	{
   1057 	.cmd_name = "unmap",.cmd_func = unmap_seg,.cmd_help =
   1058 		    "unmap <seg-name> - unmap specified segment, but remember name/size/...\n"},
   1059 	{
   1060 	.cmd_name = "touch",.cmd_func = touch_seg,.cmd_help =
   1061 		    "touch <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [read|write] - \n"
   1062 		    "\tread [default] or write the named segment from <offset> through\n"
   1063 		    "\t<offset>+<length>.  If <offset> and <length> omitted, touches all\n"
   1064 		    "\t of mapped segment.\n"}, {
   1065 	.cmd_name = "mbind",.cmd_func = mbind_seg,.cmd_help =
   1066 		    "mbind <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]]\n"
   1067 		    "      <policy>[+move[+wait]] [<node/list>] - \n"
   1068 		    "\tset the numa policy for the specified range of the name segment\n"
   1069 		    "\tto policy --  one of {default, bind, preferred, interleaved}.\n"
   1070 		    "\t<node/list> specifies a node id or a comma separated list of\n"
   1071 		    "\tnode ids.  <node> is ignored for 'default' policy, and only\n"
   1072 		    "\tthe first node is used for 'preferred' policy.\n"
   1073 		    "\t'+move' specifies that currently allocated pages be prepared\n"
   1074 		    "\t        for migration on next touch\n"
   1075 		    "\t'+wait' [valid only with +move] specifies that pages mbind()\n"
   1076 		    "          touch the pages and wait for migration before returning.\n"},
   1077 	{
   1078 	.cmd_name = "where",.cmd_func = where_seg,.cmd_help =
   1079 		    "where <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] - \n"
   1080 		    "\tshow the node location of pages in the specified range\n"
   1081 		    "\tof the specified segment.  <offset> defaults to start of\n"
   1082 		    "\tsegment; <length> defaults to 64 pages.\n"},
   1083 #if 0				/* template for new commands */
   1084 	{
   1085 	.cmd_name = "",.cmd_func =,.cmd_help =},
   1086 #endif
   1087 	{
   1088 	.cmd_name = NULL}
   1089 };
   1090 
   1091 static int help_me(char *args)
   1092 {
   1093 	struct command *cmdp = cmd_table;
   1094 	char *cmd, *nextarg;
   1095 	int cmdlen;
   1096 	bool match = false;
   1097 
   1098 	args += strspn(args, whitespace);
   1099 	if (*args != '\0') {
   1100 		cmd = strtok_r(args, whitespace, &nextarg);
   1101 		cmdlen = strlen(cmd);
   1102 	} else {
   1103 		cmd = NULL;
   1104 		cmdlen = 0;
   1105 	}
   1106 
   1107 	for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) {
   1108 		if (cmd == NULL || !strncmp(cmd, cmdp->cmd_name, cmdlen)) {
   1109 			printf("%s\n", cmdp->cmd_help);
   1110 			match = true;
   1111 		}
   1112 	}
   1113 
   1114 	if (!match) {
   1115 		printf("unrecognized command:  %s\n", cmd);
   1116 		printf("\tuse 'help' for a complete list of commands\n");
   1117 		return CMD_ERROR;
   1118 	}
   1119 
   1120 	return CMD_SUCCESS;
   1121 }
   1122 
   1123 /*
   1124  * =========================================================================
   1125  */
   1126 #define CMDBUFSZ 256
   1127 
   1128 static bool unique_abbrev(char *cmd, size_t clen, struct command *cmdp)
   1129 {
   1130 	for (; cmdp->cmd_name != NULL; ++cmdp) {
   1131 		if (!strncmp(cmd, cmdp->cmd_name, clen))
   1132 			return false;	/* match: not unique */
   1133 	}
   1134 	return true;
   1135 }
   1136 
   1137 static int parse_command(char *cmdline)
   1138 {
   1139 	glctx_t *gcp = &glctx;
   1140 	char *cmd, *args;
   1141 	struct command *cmdp;
   1142 
   1143 	cmdline += strspn(cmdline, whitespace);	/* possibly redundant */
   1144 
   1145 	cmd = strtok_r(cmdline, whitespace, &args);
   1146 
   1147 	for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) {
   1148 		size_t clen = strlen(cmd);
   1149 		int ret;
   1150 
   1151 		if (strncmp(cmd, cmdp->cmd_name, clen))
   1152 			continue;
   1153 		if (!unique_abbrev(cmd, clen, cmdp + 1)) {
   1154 			fprintf(stderr, "%s:  ambiguous command:  %s\n",
   1155 				gcp->program_name, cmd);
   1156 			return CMD_ERROR;
   1157 		}
   1158 		gcp->cmd_name = cmdp->cmd_name;
   1159 		ret = cmdp->cmd_func(args);
   1160 		gcp->cmd_name = NULL;
   1161 		return ret;
   1162 	}
   1163 
   1164 	fprintf(stderr, "%s:  unrecognized command %s\n", __FUNCTION__, cmd);
   1165 	return CMD_ERROR;
   1166 }
   1167 
   1168 void process_commands()
   1169 {
   1170 	glctx_t *gcp = &glctx;
   1171 
   1172 	char cmdbuf[CMDBUFSZ];
   1173 
   1174 	do {
   1175 		char *cmdline;
   1176 		size_t cmdlen;
   1177 
   1178 		if (is_option(INTERACTIVE))
   1179 			printf("%s>", gcp->program_name);
   1180 
   1181 		cmdline = fgets(cmdbuf, CMDBUFSZ, stdin);
   1182 		if (cmdline == NULL) {
   1183 			printf("%s\n",
   1184 			       is_option(INTERACTIVE) ? "" : "EOF on stdin");
   1185 			exit(0);	/* EOF */
   1186 		}
   1187 		if (cmdline[0] == '\n')
   1188 			continue;
   1189 
   1190 		/*
   1191 		 * trim trailing newline, if any
   1192 		 */
   1193 		cmdlen = strlen(cmdline);
   1194 		if (cmdline[cmdlen - 1] == '\n')
   1195 			cmdline[--cmdlen] = '\0';
   1196 
   1197 		cmdline += strspn(cmdline, whitespace);
   1198 		cmdlen -= (cmdline - cmdbuf);
   1199 
   1200 		if (cmdlen == 0) {
   1201 			//TODO:  interactive help?
   1202 			continue;	/* ignore blank lines */
   1203 		}
   1204 
   1205 		if (*cmdline == '#')
   1206 			continue;	/* comments */
   1207 
   1208 		/*
   1209 		 * trim trailing whitespace for ease of parsing
   1210 		 */
   1211 		while (strchr(whitespace, cmdline[cmdlen - 1]))
   1212 			cmdline[--cmdlen] = '\0';
   1213 
   1214 		if (cmdlen == 0)
   1215 			continue;
   1216 
   1217 		/*
   1218 		 * reset signals just before parsing a command.
   1219 		 * non-interactive:  exit on SIGQUIT
   1220 		 */
   1221 		if (signalled(gcp)) {
   1222 			if (!is_option(INTERACTIVE) &&
   1223 			    gcp->siginfo->si_signo == SIGQUIT)
   1224 				exit(0);
   1225 			reset_signal();
   1226 		}
   1227 
   1228 		/*
   1229 		 * non-interactive:  errors are fatal
   1230 		 */
   1231 		if (!is_option(INTERACTIVE)) {
   1232 			vprint("%s>%s\n", gcp->program_name, cmdline);
   1233 			if (parse_command(cmdline) == CMD_ERROR) {
   1234 				fprintf(stderr, "%s:  command error\n",
   1235 					gcp->program_name);
   1236 				exit(4);
   1237 			}
   1238 		} else
   1239 			parse_command(cmdline);
   1240 
   1241 	} while (1);
   1242 }
   1243 #endif
   1244