1/*
2 * memtoy.c -- toy/tool for investigating Linux [Numa] VM behavior
3 */
4/*
5 *  Copyright (c) 2005 Hewlett-Packard, Inc
6 *  All rights reserved.
7 */
8
9/*
10 *  This program is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License as published by
12 *  the Free Software Foundation; either version 2 of the License, or
13 *  (at your option) any later version.
14 *
15 *  This program is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with this program; if not, write to the Free Software
22 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
23 */
24
25#include <stdio.h>
26
27#include "config.h"
28#include "tst_res_flags.h"
29#if HAVE_NUMA_H
30#include <numa.h>
31#endif
32
33#ifdef HAVE_NUMA_V2
34
35#include <sys/types.h>
36#include <sys/time.h>
37#include <sys/mman.h>
38#include <libgen.h>
39#include <errno.h>
40#include <numa.h>
41#include <signal.h>
42#include <stdarg.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include "memtoy.h"
47
48/*
49 * global context
50 */
51glctx_t glctx;			/* global context */
52
53/*
54 * command line options:
55 *
56 *  -v          = verbose
57 *  -V          = display version
58 *  -h|x	= display help.
59 */
60#define OPTIONS	"Vhvx"
61
62/*
63 * usage/help message
64 */
65char *USAGE = "\nUsage:  %s [-v] [-V] [-{h|x}]\n\n\
66Where:\n\
67\t-v            enable verbosity\n\
68\t-V            display version info\n\
69\t-h|x          show this usage/help message\n\
70\n\
71More info - TODO\n\
72";
73
74/*
75 * die() - emit error message and exit w/ specified return code.
76 *	   if exit_code < 0, save current errno, and fetch associated
77 *	   error string.  Print error string after app error message.
78 *	   Then exit with abs(exit_code).
79 */
80void die(int exit_code, char *format, ...)
81{
82	va_list ap;
83	char *errstr;
84	int saverrno;
85
86	va_start(ap, format);
87
88	if (exit_code < 0) {
89		saverrno = errno;
90		errstr = strerror(errno);
91	}
92
93	(void)vfprintf(stderr, format, ap);
94	va_end(ap);
95
96	if (exit_code < 0)
97		fprintf(stderr, "Error = (%d) %s\n", saverrno, errstr);
98
99	exit(abs(exit_code));
100}
101
102void usage(char *mesg)
103{
104	if (mesg != NULL) {
105		fprintf(stderr, "%s\n", mesg);
106	}
107	fprintf(stderr, USAGE, glctx.program_name);
108	exit(1);
109}
110
111#ifdef _DEBUG
112/*
113 * This function is a wrapper around "fprintf(stderr, ...)" so that we
114 * can use the DPRINTF(<flag>, (<[f]printf arguments>)) macro for debug
115 * prints.  See the definition of DPRINTF in XXX.h
116 */
117int _dvprintf(char *format, ...)
118{
119	va_list ap;
120	int retval;
121
122	va_start(ap, format);
123
124	retval = vfprintf(stderr, format, ap);
125
126	va_end(ap);
127
128	fflush(stderr);
129	return (retval);
130}
131#endif
132
133void vprint(char *format, ...)
134{
135	va_list ap;
136	glctx_t *gcp = &glctx;
137
138	va_start(ap, format);
139
140	if (!is_option(VERBOSE))
141		goto out;
142
143	(void)vfprintf(stderr, format, ap);
144	fflush(stderr);
145
146out:
147	va_end(ap);
148	return;
149
150}
151
152/*
153 * =========================================================================
154 */
155static int signals_to_handle[] = {
156	SIGINT, SIGQUIT, SIGSEGV, SIGBUS,
157	SIGUSR1, SIGUSR2, 0
158};
159
160static char *sig_names[] = {
161	"SIGINT", "SIGQUIT", "SIGSEGV", "SIGBUS",
162	"SIGUSR1", "SIGUSR2", "unknown", 0
163};
164
165/*
166 * signal_handler()
167 *
168 * save siginfo and name in global context
169 */
170void signal_handler(int sig, siginfo_t * info, void *vcontext)
171{
172	glctx_t *gcp = &glctx;
173	int isig = 0, *sigp = signals_to_handle;
174	static siginfo_t infocopy;
175
176	/*
177	 * static copy of signal info.
178	 * Note, additional signals, before use, can overwrite
179	 */
180	infocopy = *info;
181	gcp->siginfo = &infocopy;
182
183	while (*sigp) {
184		if (*sigp == sig)
185			break;
186		++isig;
187		++sigp;
188	}
189	gcp->signame = sig_names[isig];
190
191	vprint("signal hander entered for sig %s\n", gcp->signame);
192
193	switch (sig) {
194	case SIGSEGV:
195	case SIGBUS:
196		if (gcp->sigjmp) {
197			gcp->sigjmp = false;
198			siglongjmp(gcp->sigjmp_env, 1);
199		}
200
201		die(8, "\n%s:  signal %s, but siglongjmp not armed\n",
202		    gcp->program_name, gcp->signame);
203		break;
204
205	case SIGINT:
206	case SIGQUIT:
207		break;
208
209	default:
210		die(8, "\n%s:  Unexpected signal:  %d\n",
211		    gcp->program_name, sig);
212		break;
213	}
214}
215
216/*
217 * set_signals()
218 *
219 * Setup signal dispositions to catch selected signals
220 */
221void set_signals()
222{
223	glctx_t *gcp = &glctx;
224	int *sigp = signals_to_handle;
225	char **namep = sig_names;
226
227	struct sigaction act = {
228		.sa_sigaction = signal_handler,
229		.sa_flags = SA_SIGINFO
230	};
231
232	(void)sigfillset(&(act.sa_mask));
233
234	while (*sigp) {
235		char *sig_name = *(namep++);
236		int sig = *(sigp++);
237
238		if (0 != sigaction(sig, &act, NULL)) {
239			die(-1, "%s: Failed to set sigaction for %s\n",
240			    gcp->program_name, sig_name);
241		} else
242#if 0
243			vprint("%s: established handler for %s\n",
244			       gcp->program_name, sig_name)
245#endif
246			    ;
247	}
248
249	return;
250}
251
252void reset_signal(void)
253{
254//TODO:  free siginfo if/when malloc'd
255	glctx.siginfo = NULL;
256	glctx.sigjmp = false;
257}
258
259void wait_for_signal(const char *mesg)
260{
261	printf("%s ... ", mesg);
262	fflush(stdout);
263	pause();
264	vprint("%s: wakened by signal %s\n", __FUNCTION__, glctx.signame);
265	reset_signal();
266	printf("\n");
267	fflush(stdout);
268}
269
270void show_siginfo()
271{
272	glctx_t *gcp = &glctx;
273	siginfo_t *info = gcp->siginfo;
274	void *badaddr = info->si_addr;
275	char *sigcode;
276
277	switch (info->si_signo) {
278	case SIGSEGV:
279		switch (info->si_code) {
280		case SEGV_MAPERR:
281			sigcode = "address not mapped";
282			break;
283
284		case SEGV_ACCERR:
285			sigcode = "invalid access error";
286			break;
287
288		default:
289			sigcode = "unknown";
290			break;
291		}
292		break;
293
294	case SIGBUS:
295		switch (info->si_code) {
296		case BUS_ADRALN:
297			sigcode = "invalid address alignment";
298			break;
299
300		case BUS_ADRERR:
301			sigcode = "non-existent physical address";
302			break;
303
304		default:
305			sigcode = "unknown";
306			break;
307		}
308		break;
309
310	default:
311		/*
312		 * ignore SIGINT/SIGQUIT
313		 */
314		return;
315	}
316
317	printf("Signal %s @ 0x%lx - %s\n", gcp->signame, badaddr, sigcode);
318
319}
320
321/*
322 * =========================================================================
323 */
324
325void touch_memory(bool rw, unsigned long *memp, size_t memlen)
326{
327	glctx_t *gcp = &glctx;
328
329	unsigned long *memend, *pp, sink;
330	unsigned long longs_in_page = gcp->pagesize / sizeof(unsigned long);
331
332	memend = memp + memlen / sizeof(unsigned long);
333	vprint("!!!%s from 0x%lx thru 0x%lx\n",
334	       rw ? "Writing" : "Reading", memp, memend);
335
336	for (pp = memp; pp < memend; pp += longs_in_page) {
337		// vprint("%s:  touching 0x%lx\n", __FUNCTION__, pp);
338		if (!sigsetjmp(gcp->sigjmp_env, true)) {
339			gcp->sigjmp = true;
340
341			/*
342			 *  Mah-ahm!  He's touching me!
343			 */
344			if (rw)
345				*pp = (unsigned long)pp;
346			else
347				sink = *pp;
348
349			gcp->sigjmp = false;
350		} else {
351			show_siginfo();
352			reset_signal();
353			break;
354		}
355
356		/*
357		 * Any [handled] signal breaks the loop
358		 */
359		if (gcp->siginfo != NULL) {
360			reset_signal();
361			break;
362		}
363	}
364}
365
366/*
367 * =========================================================================
368 */
369
370void init_glctx(glctx_t * gcp)
371{
372
373	bzero(gcp, sizeof(glctx_t));
374
375	gcp->pagesize = (size_t) sysconf(_SC_PAGESIZE);
376
377	if (numa_available() >= 0) {
378		gcp->numa_max_node = numa_max_node();
379	} else
380		gcp->numa_max_node = -1;
381
382	segment_init(gcp);
383
384	if (isatty(fileno(stdin)))
385		set_option(INTERACTIVE);
386
387}
388
389/*
390 * cleanup() - at exit cleanup routine
391 */
392static void cleanup()
393{
394	glctx_t *gcp = &glctx;
395
396	segment_cleanup(gcp);
397}				/* cleanup() */
398
399int parse_command_line_args(int argc, char *argv[])
400{
401	extern int optind;
402	extern char *optarg;
403
404	glctx_t *gcp = &glctx;
405	int argval;
406	int error = 0;
407
408	char c;
409
410	gcp->program_name = basename(argv[0]);
411
412	/*
413	 * process command line options.
414	 */
415	while ((c = getopt(argc, argv, OPTIONS)) != (char)EOF) {
416		char *next;
417
418		switch (c) {
419
420		case 'v':
421			set_option(VERBOSE);
422			break;
423
424		case 'h':
425		case 'x':
426			usage(NULL);
427
428			break;
429
430		case 'V':
431			printf("memtoy " MEMTOY_VERSION " built "
432			       __DATE__ " @ " __TIME__ "\n");
433			exit(0);
434			break;
435
436#ifdef _DEBUG
437		case '0':
438			argval = strtoul(optarg, &next, 0);
439			if (*next != '\0') {
440				fprintf(stderr,
441					"-D <debug-mask> must be unsigned hex/decimal integer\n");
442				++error;
443			} else
444				gcp->debug = argval;
445			break;
446#endif
447
448		default:
449			error = 1;
450			break;
451		}
452	}
453done:
454
455	return (error);
456}
457
458int main(int argc, char *argv[])
459{
460	glctx_t *gcp = &glctx;
461	bool user_is_super;
462	int error;
463
464	init_glctx(gcp);
465	if (!is_option(INTERACTIVE))
466		setbuf(stdout, NULL);
467
468	/*
469	 * Register cleanup handler
470	 */
471	if (atexit(cleanup) != 0) {
472		die(-1, "%s:  atexit(cleanup) registration failed\n", argv[0]);
473	}
474
475	user_is_super = (geteuid() == 0);
476
477	error = parse_command_line_args(argc, argv);
478
479	if (error /* || argc==1 */ ) {
480		usage(NULL);
481
482	}
483
484	/*
485	 * actual program logic starts here
486	 */
487	printf("memtoy pid:  %d\n", getpid());
488	vprint("%s:  pagesize = %d\n", gcp->program_name, gcp->pagesize);
489	if (gcp->numa_max_node >= 0)
490		vprint("%s:  NUMA available - max node: %d\n",
491		       gcp->program_name, gcp->numa_max_node);
492
493	set_signals();
494
495	process_commands();
496
497	return 0;
498
499}
500#else
501int main(void)
502{
503	fprintf(stderr, "test requires libnuma >= 2 and it's development packages\n");
504	return TCONF;
505}
506#endif
507