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