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