1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) International Business Machines  Corp., 2004
4 * Copyright (c) Linux Test Project, 2004-2020
5 */
6
7/*
8 * DESCRIPTION
9 *	hugeshmctl01 - test the IPC_STAT, IPC_SET and IPC_RMID commands as
10 *		   they are used with shmctl()
11 *
12 * ALGORITHM
13 *	loop if that option was specified
14 *	create a large shared memory segment with read and write permission
15 *	set up any test case specific conditions
16 *	call shmctl() using the TEST macro
17 *	check the return code
18 *	  if failure, issue a FAIL message.
19 *	otherwise,
20 *	  if doing functionality testing
21 *		call the correct test function
22 *		if the conditions are correct,
23 *			issue a PASS message
24 *		otherwise
25 *			issue a FAIL message
26 *	  otherwise
27 *	    issue a PASS message
28 *	call cleanup
29 *
30 * HISTORY
31 *	03/2001 - Written by Wayne Boyer
32 *	04/2004 - Updated by Robbie Williamson
33 */
34
35#include <limits.h>
36#include "hugetlb.h"
37
38#define N_ATTACH	4U
39#define NEWMODE		0066
40
41static size_t shm_size;
42static int shm_id_1 = -1;
43static struct shmid_ds buf;
44static time_t save_time;
45static void *attach_to_parent;
46
47static void stat_setup_1(void);
48static void stat_cleanup(void);
49static void stat_setup_2(void);
50static void set_setup(void);
51static void func_stat(void);
52static void func_set(void);
53static void func_rmid(void);
54static void *set_shmat(void);
55
56static struct tcase {
57	int cmd;
58	void (*func_test)(void);
59	void (*func_setup)(void);
60} tcases[] = {
61	{IPC_STAT, func_stat, stat_setup_1},
62	{IPC_STAT, func_stat, stat_setup_2},
63	{IPC_SET,  func_set,  set_setup},
64	{IPC_RMID, func_rmid, NULL}
65};
66
67static void test_hugeshmctl(unsigned int i)
68{
69	/*
70	 * Create a shared memory segment with read and write
71	 * permissions.  Do this here instead of in setup()
72	 * so that looping (-i) will work correctly.
73	 */
74	if (i == 0)
75		shm_id_1 = shmget(shmkey, shm_size,
76			SHM_HUGETLB | IPC_CREAT | IPC_EXCL | SHM_RW);
77	if (shm_id_1 == -1)
78		tst_brk(TBROK | TERRNO, "shmget #main");
79
80	if (tcases[i].func_setup != NULL)
81		(*tcases[i].func_setup) ();
82
83	if (shmctl(shm_id_1, tcases[i].cmd, &buf) == -1) {
84		tst_res(TFAIL | TERRNO, "shmctl #main");
85		return;
86	}
87	(*tcases[i].func_test)();
88}
89
90/*
91 * set_shmat() - Attach the shared memory and return the pointer.
92 */
93static void *set_shmat(void)
94{
95	void *rval;
96
97	rval = shmat(shm_id_1, 0, 0);
98	if (rval == (void *)-1)
99		tst_brk(TBROK | TERRNO, "set shmat");
100
101	return rval;
102}
103
104/*
105 * stat_setup_2() - Set up for the IPC_STAT command with shmctl().
106 *                Attach the shared memory to parent process and
107 *                some children will inherit the shared memory.
108 */
109static void stat_setup_2(void)
110{
111	if (!attach_to_parent)
112		attach_to_parent = set_shmat();
113	stat_setup_1();
114}
115
116/*
117 * stat_setup_1() - Set up for the IPC_STAT command with shmctl().
118 *                some children will inherit or attatch the shared memory.
119 *                It deponds on whther we attach the shared memory
120 *                to parent process.
121 */
122static void stat_setup_1(void)
123{
124	unsigned int i;
125	void *test;
126	pid_t pid;
127
128	for (i = 0; i < N_ATTACH; i++) {
129		switch (pid = SAFE_FORK()) {
130		case 0:
131			test = (attach_to_parent == NULL) ? set_shmat() : attach_to_parent;
132			/* do an assignement for fun */
133			*(int *)test = i;
134
135			TST_CHECKPOINT_WAKE(0);
136
137			TST_CHECKPOINT_WAIT(1);
138
139			/* now we're back - detach the memory and exit */
140			if (shmdt(test) == -1)
141				tst_brk(TBROK | TERRNO,
142					 "shmdt in this function broke");
143
144			exit(0);
145		default:
146			TST_CHECKPOINT_WAIT(0);
147		}
148	}
149}
150
151
152/*
153 * func_stat() - check the functionality of the IPC_STAT command with shmctl()
154 *		 by looking at the pid of the creator, the segement size,
155 *		 the number of attaches and the mode.
156 */
157static void func_stat(void)
158{
159	pid_t pid;
160	unsigned int num;
161
162	/* check perm, pid, nattach and size */
163	pid = getpid();
164
165	if (buf.shm_cpid != pid) {
166		tst_res(TFAIL, "creator pid is incorrect");
167		goto fail;
168	}
169
170	if (buf.shm_segsz != shm_size) {
171		tst_res(TFAIL, "segment size is incorrect");
172		goto fail;
173	}
174
175	/*
176	 * The first case, only the children attach the memory, so
177	 * the attaches equal N_ATTACH. The second case, the parent
178	 * attaches the memory and the children inherit that memory
179	 * so the attaches equal N_ATTACH + 1.
180	 */
181	num = (attach_to_parent == NULL) ? 0 : 1;
182	if (buf.shm_nattch != N_ATTACH + num) {
183		tst_res(TFAIL, "# of attaches is incorrect - %lu",
184			 (unsigned long)buf.shm_nattch);
185		goto fail;
186	}
187
188	/* use MODE_MASK to make sure we are comparing the last 9 bits */
189	if ((buf.shm_perm.mode & MODE_MASK) != ((SHM_RW) & MODE_MASK)) {
190		tst_res(TFAIL, "segment mode is incorrect");
191		goto fail;
192	}
193
194	tst_res(TPASS, "pid, size, # of attaches and mode are correct "
195		 "- pass #%d", num);
196
197fail:
198	stat_cleanup();
199
200	/* save the change time for use in the next test */
201	save_time = buf.shm_ctime;
202}
203
204/*
205 * stat_cleanup() - signal the children to clean up after themselves and
206 *		    have the parent make dessert, er, um, make that remove
207 *		    the shared memory that is no longer needed.
208 */
209static void stat_cleanup(void)
210{
211	unsigned int i;
212	int status;
213
214	/* wake up the childern so they can detach the memory and exit */
215	TST_CHECKPOINT_WAKE2(1, N_ATTACH);
216
217	for (i = 0; i < N_ATTACH; i++)
218		SAFE_WAIT(&status);
219
220	/* remove the parent's shared memory if we set*/
221	if (attach_to_parent) {
222		if (shmdt(attach_to_parent) == -1)
223			tst_res(TFAIL | TERRNO,
224				"shmdt in this function failed");
225		attach_to_parent = NULL;
226	}
227}
228
229/*
230 * set_setup() - set up for the IPC_SET command with shmctl()
231 */
232static void set_setup(void)
233{
234	/* set up a new mode for the shared memory segment */
235	buf.shm_perm.mode = SHM_RW | NEWMODE;
236
237	/* sleep for one second to get a different shm_ctime value */
238	sleep(1);
239}
240
241/*
242 * func_set() - check the functionality of the IPC_SET command with shmctl()
243 */
244static void func_set(void)
245{
246	/* first stat the shared memory to get the new data */
247	if (shmctl(shm_id_1, IPC_STAT, &buf) == -1) {
248		tst_res(TFAIL | TERRNO, "shmctl in this function failed");
249		return;
250	}
251
252	if ((buf.shm_perm.mode & MODE_MASK) != ((SHM_RW | NEWMODE) & MODE_MASK)) {
253		tst_res(TFAIL, "new mode is incorrect");
254		return;
255	}
256
257	if (save_time >= buf.shm_ctime) {
258		tst_res(TFAIL, "change time is incorrect");
259		return;
260	}
261
262	tst_res(TPASS, "new mode and change time are correct");
263}
264
265/*
266 * func_rmid() - check the functionality of the IPC_RMID command with shmctl()
267 */
268static void func_rmid(void)
269{
270	/* Do another shmctl() - we should get EINVAL */
271	if (shmctl(shm_id_1, IPC_STAT, &buf) != -1)
272		tst_brk(TBROK, "shmctl in this function "
273			 "succeeded unexpectedly");
274	if (errno != EINVAL)
275		tst_res(TFAIL | TERRNO, "shmctl in this function failed "
276			 "unexpectedly - expect errno=EINVAL, got");
277	else
278		tst_res(TPASS, "shmctl in this function failed as expected, "
279			 "shared memory appears to be removed");
280	shm_id_1 = -1;
281}
282
283static void setup(void)
284{
285	long hpage_size;
286
287	if (tst_hugepages == 0)
288		tst_brk(TCONF, "No enough hugepages for testing.");
289
290	hpage_size = SAFE_READ_MEMINFO("Hugepagesize:") * 1024;
291
292	shm_size = hpage_size * tst_hugepages / 2;
293	update_shm_size(&shm_size);
294	shmkey = getipckey();
295}
296
297static void cleanup(void)
298{
299	rm_shm(shm_id_1);
300}
301
302static struct tst_test test = {
303	.tcnt = ARRAY_SIZE(tcases),
304	.needs_root = 1,
305	.forks_child = 1,
306	.options = (struct tst_option[]) {
307		{"s:", &nr_opt, "Set the number of the been allocated hugepages"},
308		{}
309	},
310	.setup = setup,
311	.cleanup = cleanup,
312	.test = test_hugeshmctl,
313	.needs_checkpoints = 1,
314	.hugepages = {128, TST_REQUEST},
315};
316