1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) International Business Machines  Corp., 2001
4 * Copyright (C) 2020 Cyril Hrubis <chrubis@suse.cz>
5 */
6
7/*\
8 * [Description]
9 *
10 * Verify that shmctl() IPC_STAT and SHM_STAT reports correct data.
11 *
12 * The shm_nattach is excercised by:
13 *
14 * - forking() children that attach and detach SHM
15 * - attaching the SHM before fork and letting the children detach it
16 *
17 * We check that the number shm_nattach is correct after each step we do.
18 */
19
20#define _GNU_SOURCE
21#include <stdlib.h>
22#include "tst_test.h"
23#include "tst_safe_sysv_ipc.h"
24#include "tst_clocks.h"
25#include "libnewipc.h"
26
27#define NCHILD 20
28
29static pid_t children[NCHILD];
30
31static int shm_id;
32static int shm_idx;
33static time_t ctime_min, ctime_max;
34
35static void *addr;
36
37static void attach_child(void)
38{
39	pause();
40
41	addr = SAFE_SHMAT(shm_id, NULL, 0);
42
43	pause();
44
45	SAFE_SHMDT(addr);
46
47	pause();
48
49	exit(0);
50}
51
52static void detach_child(void)
53{
54	pause();
55
56	SAFE_SHMDT(addr);
57
58	pause();
59
60	exit(0);
61}
62
63static void fork_children(void (*child_func)(void))
64{
65	unsigned int i;
66
67	for (i = 0; i < NCHILD; i++) {
68		pid_t pid = SAFE_FORK();
69
70		if (!pid)
71			child_func();
72
73		children[i] = pid;
74	}
75}
76
77static void wait_for_children(void)
78{
79	unsigned int i;
80
81	for (i = 0; i < NCHILD; i++)
82		TST_PROCESS_STATE_WAIT(children[i], 'S', 0);
83}
84
85static void signal_children(void)
86{
87	unsigned int i;
88
89	for (i = 0; i < NCHILD; i++)
90		SAFE_KILL(children[i], SIGUSR1);
91}
92
93static void reap_children(void)
94{
95	unsigned int i;
96
97	for (i = 0; i < NCHILD; i++)
98		SAFE_WAITPID(children[i], NULL, 0);
99}
100
101static void check_nattch(int exp_nattch, const char *msg)
102{
103	struct shmid_ds ds1;
104	struct shmid_ds ds2;
105
106	SAFE_SHMCTL(shm_id, IPC_STAT, &ds1);
107	SAFE_SHMCTL(shm_idx, SHM_STAT, &ds2);
108
109	if (ds1.shm_nattch != ds2.shm_nattch) {
110		tst_res(TFAIL, "IPC_STAT nattch=%li SHM_STAT nattch=%li",
111			(long)ds1.shm_nattch, (long)ds2.shm_nattch);
112		return;
113	}
114
115	if ((int)ds1.shm_nattch == exp_nattch) {
116		tst_res(TPASS, "%s shm_nattch=%i", msg, exp_nattch);
117		return;
118	}
119
120	tst_res(TFAIL, "%s shm_nattcg=%li expected %i",
121		msg, (long)ds1.shm_nattch, exp_nattch);
122}
123
124static void verify_shmstat_attach(void)
125{
126	fork_children(attach_child);
127	wait_for_children();
128
129	check_nattch(0, "before child shmat()");
130
131	signal_children();
132	wait_for_children();
133
134	check_nattch(NCHILD, "after child shmat()");
135
136	signal_children();
137	wait_for_children();
138
139	check_nattch(0, "after child shmdt()");
140
141	signal_children();
142	reap_children();
143}
144
145static void verify_shmstat_inherit(void)
146{
147	addr = SAFE_SHMAT(shm_id, NULL, 0);
148
149	fork_children(detach_child);
150	wait_for_children();
151
152	check_nattch(NCHILD+1, "inherited after fork()");
153
154	signal_children();
155	wait_for_children();
156
157	check_nattch(1, "after child shmdt()");
158
159	SAFE_SHMDT(addr);
160
161	check_nattch(0, "after parent shmdt()");
162
163	signal_children();
164	reap_children();
165}
166
167static void check_ds(struct shmid_ds *ds, const char *desc)
168{
169	pid_t pid = getpid();
170
171	if (ds->shm_segsz != SHM_SIZE) {
172		tst_res(TFAIL, "%s: shm_segsz=%zu, expected %i",
173			desc, ds->shm_segsz, SHM_SIZE);
174	} else {
175		tst_res(TPASS, "%s: shm_segsz=%i", desc, SHM_SIZE);
176	}
177
178	if (ds->shm_cpid != pid) {
179		tst_res(TFAIL, "%s: shm_cpid=%i, expected %i",
180			desc, ds->shm_cpid, pid);
181	} else {
182		tst_res(TPASS, "%s: shm_cpid=%i", desc, pid);
183	}
184
185	if (ds->shm_ctime < ctime_min || ds->shm_ctime > ctime_max) {
186		tst_res(TFAIL, "%s: shm_ctime=%li, expected <%li,%li>",
187			desc, ds->shm_ctime, ctime_min, ctime_max);
188	} else {
189		tst_res(TPASS, "%s: shm_ctime=%li in range <%li,%li>",
190			desc, ds->shm_ctime, ctime_min, ctime_max);
191	}
192}
193
194static void shmstat_basic_check(void)
195{
196	struct shmid_ds ds;
197
198	memset(&ds, 0, sizeof(ds));
199	SAFE_SHMCTL(shm_id, IPC_STAT, &ds);
200
201	check_ds(&ds, "IPC_STAT");
202
203	memset(&ds, 0, sizeof(ds));
204	SAFE_SHMCTL(shm_idx, SHM_STAT, &ds);
205
206	check_ds(&ds, "SHM_STAT");
207}
208
209static struct tcase {
210	void (*func)(void);
211	const char *desc;
212} tcases[] = {
213	{shmstat_basic_check, "Basic checks"},
214	{verify_shmstat_attach, "Children attach SHM"},
215	{verify_shmstat_inherit, "Chidlren inherit SHM"},
216};
217
218static void verify_shmstat(unsigned int n)
219{
220	tst_res(TINFO, "%s", tcases[n].desc);
221	tcases[n].func();
222}
223
224static void dummy_sighandler(int sig)
225{
226	(void)sig;
227}
228
229static int get_shm_idx_from_id(int shm_id)
230{
231	struct shm_info dummy;
232	struct shmid_ds dummy_ds;
233	int max_idx, i;
234
235	max_idx = SAFE_SHMCTL(shm_id, SHM_INFO, (void *)&dummy);
236
237	for (i = 0; i <= max_idx; i++) {
238		if (shmctl(i, SHM_STAT, &dummy_ds) == shm_id)
239			return i;
240	}
241
242	return -1;
243}
244
245static void setup(void)
246{
247	ctime_min = tst_get_fs_timestamp();
248	shm_id = SAFE_SHMGET(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | SHM_RW);
249	ctime_max = tst_get_fs_timestamp();
250
251	shm_idx = get_shm_idx_from_id(shm_id);
252
253	if (shm_idx < 0)
254		tst_brk(TBROK, "Failed to get shm_id to idx mapping");
255
256	tst_res(TINFO, "shm_id=%i maps to kernel index=%i", shm_id, shm_idx);
257
258	SAFE_SIGNAL(SIGUSR1, dummy_sighandler);
259}
260
261static void cleanup(void)
262{
263	if (shm_id >= 0)
264		SAFE_SHMCTL(shm_id, IPC_RMID, NULL);
265}
266
267static struct tst_test test = {
268	.setup = setup,
269	.cleanup = cleanup,
270	.forks_child = 1,
271	.test = verify_shmstat,
272	.tcnt = ARRAY_SIZE(tcases),
273};
274