1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 *  Copyright (c) International Business Machines Corp., 2001
4 *  Copyright (c) Linux Test Project., 2019
5 *
6 *  DESCRIPTION:
7 *
8 *  mtest01 mallocs memory <chunksize> at a time until malloc fails.
9 *
10 *  Parent process starts several child processes (each child process is
11 *  tasked with allocating some amount of memory), it waits until all child
12 *  processes send SIGRTMIN signal and resumes all children by sending the
13 *  SIGCONT signal.
14 *
15 *  Child process allocates certain amount of memory and fills it with some
16 *  data (the '-w' option) so the pages are actually allocated when the desired
17 *  amount of memory is allocated then it sends SIGRTMIN signal to the parent
18 *  process, it pauses itself by raise SIGSTOP until get parent SIGCONT signal
19 *  to continue and exit.
20 */
21
22#include <sys/types.h>
23#include <sys/sysinfo.h>
24#include <sys/wait.h>
25#include <limits.h>
26#include <signal.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <unistd.h>
30
31#include "lapi/abisize.h"
32#include "tst_test.h"
33
34#define FIVE_HUNDRED_MB         (500ULL*1024*1024)
35
36#if defined(__s390__) || defined(__s390x__)
37#define ALLOC_THRESHOLD		FIVE_HUNDRED_MB
38#elif defined(TST_ABI32)
39#define ALLOC_THRESHOLD		(2*FIVE_HUNDRED_MB)
40#elif defined(TST_ABI64)
41#define ALLOC_THRESHOLD		(6*FIVE_HUNDRED_MB)
42#endif
43
44static pid_t *pid_list;
45static sig_atomic_t children_done;
46static int max_pids;
47static unsigned long long alloc_maxbytes;
48
49static int chunksize = 1024*1024;
50static int maxpercent = 20;
51static long maxbytes = 0;
52static char *dowrite;
53static char *verbose;
54
55static char *opt_chunksize, *opt_maxbytes, *opt_maxpercent;
56
57static void parse_mtest_options(char *str_chunksize, int *chunksize,
58		char *str_maxbytes, long *maxbytes,
59		char *str_maxpercent, int *maxpercent)
60{
61	if (str_chunksize)
62		if (tst_parse_int(str_chunksize, chunksize, 1, INT_MAX))
63			tst_brk(TBROK, "Invalid chunksize '%s'", str_chunksize);
64
65	if (str_maxbytes) {
66		if (tst_parse_long(str_maxbytes, maxbytes, 1, LONG_MAX)) {
67			tst_brk(TBROK, "Invalid maxbytes '%s'", str_maxbytes);
68		} else if (str_maxpercent) {
69			tst_brk(TBROK, "ERROR: -b option cannot be used with -p "
70					"option at the same time");
71		}
72		alloc_maxbytes = (unsigned long long)maxbytes;
73	}
74
75	if (str_maxpercent) {
76		if (tst_parse_int(str_maxpercent, maxpercent, 1, 99)) {
77			tst_brk(TBROK, "Invalid maxpercent '%s'", str_maxpercent);
78		} else if (str_maxbytes) {
79			tst_brk(TBROK, "ERROR: -p option cannot be used with -b "
80					"option at the same time");
81		}
82	}
83}
84
85static void handler(int sig LTP_ATTRIBUTE_UNUSED)
86{
87        children_done++;
88}
89
90static void do_write_mem(char *mem, int chunksize)
91{
92	int i, pagesz = getpagesize();
93
94	for (i = 0; i < chunksize; i += pagesz)
95		*(mem + i) = 'a';
96}
97
98static void setup(void)
99{
100	struct sysinfo sstats;
101	unsigned long long total_free;
102
103	struct sigaction act;
104	act.sa_handler = handler;
105	act.sa_flags = 0;
106	sigemptyset(&act.sa_mask);
107	sigaction(SIGRTMIN, &act, 0);
108
109	parse_mtest_options(opt_chunksize, &chunksize,
110			opt_maxbytes, &maxbytes,
111			opt_maxpercent, &maxpercent);
112	sysinfo(&sstats);
113	total_free = sstats.freeram;
114
115	max_pids = total_free * sstats.mem_unit
116		/ (unsigned long)ALLOC_THRESHOLD + 10;
117	pid_list = SAFE_MALLOC(max_pids * sizeof(pid_t));
118
119	if (!alloc_maxbytes) {
120		/* set alloc_maxbytes to the extra amount we want to allocate */
121		alloc_maxbytes = ((float)maxpercent / 100.00)
122			* (sstats.mem_unit * total_free);
123		tst_res(TINFO, "Filling up %d%% of free ram which is %llu kbytes",
124			 maxpercent, alloc_maxbytes / 1024);
125	}
126}
127
128static void cleanup(void)
129{
130	if(pid_list)
131		free(pid_list);
132}
133
134static void child_loop_alloc(unsigned long long alloc_bytes)
135{
136	unsigned long bytecount = 0;
137	char *mem;
138	int runtime_rem;
139
140	tst_res(TINFO, "... child %d starting", getpid());
141
142	while (1) {
143		mem = SAFE_MALLOC(chunksize);
144		if (dowrite)
145			do_write_mem(mem, chunksize);
146
147		if (verbose)
148			tst_res(TINFO,
149				"child %d allocated %lu bytes chunksize is %d",
150				getpid(), bytecount, chunksize);
151		bytecount += chunksize;
152		if (bytecount >= alloc_bytes)
153			break;
154	}
155
156	runtime_rem = tst_remaining_runtime();
157
158	if (dowrite)
159		tst_res(TINFO, "... [t=%d] %lu bytes allocated and used in child %d",
160				runtime_rem, bytecount, getpid());
161	else
162		tst_res(TINFO, "... [t=%d] %lu bytes allocated only in child %d",
163				runtime_rem, bytecount, getpid());
164
165	kill(getppid(), SIGRTMIN);
166	raise(SIGSTOP);
167	exit(0);
168}
169
170static void mem_test(void)
171{
172	pid_t pid;
173	int i = 0, pid_cntr = 0;
174	unsigned long long alloc_bytes = alloc_maxbytes;
175	const char *write_msg = "";
176
177	if (dowrite)
178		write_msg = "(and written to) ";
179
180	/* to make mtest01 support -i N */
181	children_done = 0;
182
183	do {
184		pid = SAFE_FORK();
185		if (pid == 0) {
186			alloc_bytes = MIN(ALLOC_THRESHOLD, alloc_bytes);
187			child_loop_alloc(alloc_bytes);
188		}
189
190		pid_list[pid_cntr++] = pid;
191
192		if (alloc_bytes <= ALLOC_THRESHOLD)
193			break;
194
195		alloc_bytes -= ALLOC_THRESHOLD;
196	} while (pid_cntr < max_pids);
197
198	/* wait in the loop for all children finish allocating */
199	while (children_done < pid_cntr) {
200		if (!tst_remaining_runtime()) {
201			tst_res(TWARN,
202				"the remaininig time is not enough for testing");
203			break;
204		}
205
206		usleep(100000);
207	}
208
209	if (children_done < pid_cntr) {
210		tst_res(TFAIL, "kbytes allocated %sless than expected %llu",
211				write_msg, alloc_maxbytes / 1024);
212
213		for (i = 0; i < pid_cntr; i++)
214			kill(pid_list[i], SIGKILL);
215
216		return;
217	}
218
219	tst_res(TPASS, "%llu kbytes allocated %s",
220			alloc_maxbytes / 1024, write_msg);
221
222	for (i = 0; i < pid_cntr; i++) {
223		TST_PROCESS_STATE_WAIT(pid_list[i], 'T', 0);
224		kill(pid_list[i], SIGCONT);
225	}
226}
227
228static struct tst_test test = {
229	.forks_child = 1,
230	.options = (struct tst_option[]) {
231		{"c:", &opt_chunksize,	"Size of chunk in bytes to malloc on each pass"},
232		{"b:", &opt_maxbytes,	"Maximum number of bytes to allocate before stopping"},
233		{"p:", &opt_maxpercent, "Percent of total memory used at which the program stops"},
234		{"w",  &dowrite,   	"Write to the memory after allocating"},
235		{"v",  &verbose,     	"Verbose"},
236		{}
237	},
238	.max_runtime = 300,
239	.setup = setup,
240	.cleanup = cleanup,
241	.test_all = mem_test,
242};
243