xref: /minix/minix/servers/ipc/shm.c (revision 08cbf5a0)
1 #include "inc.h"
2 
3 /* Private shm_perm.mode flags, synchronized with NetBSD kernel values */
4 #define SHM_ALLOC	0x0800	/* slot is in use (SHMSEG_ALLOCATED) */
5 
6 struct shm_struct {
7 	struct shmid_ds shmid_ds;
8 	vir_bytes page;
9 	phys_bytes vm_id;
10 };
11 static struct shm_struct shm_list[SHMMNI];
12 static unsigned int shm_list_nr = 0; /* highest in-use slot number plus one */
13 
14 static struct shm_struct *
15 shm_find_key(key_t key)
16 {
17 	unsigned int i;
18 
19 	if (key == IPC_PRIVATE)
20 		return NULL;
21 
22 	for (i = 0; i < shm_list_nr; i++) {
23 		if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
24 			continue;
25 		if (shm_list[i].shmid_ds.shm_perm._key == key)
26 			return &shm_list[i];
27 	}
28 
29 	return NULL;
30 }
31 
32 static struct shm_struct *
33 shm_find_id(int id)
34 {
35 	struct shm_struct *shm;
36 	unsigned int i;
37 
38 	i = IPCID_TO_IX(id);
39 	if (i >= shm_list_nr)
40 		return NULL;
41 
42 	shm = &shm_list[i];
43 	if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
44 		return NULL;
45 	if (shm->shmid_ds.shm_perm._seq != IPCID_TO_SEQ(id))
46 		return NULL;
47 	return shm;
48 }
49 
50 int
51 do_shmget(message * m)
52 {
53 	struct shm_struct *shm;
54 	unsigned int i, seq;
55 	key_t key;
56 	size_t size, old_size;
57 	int flag;
58 	void *page;
59 
60 	key = m->m_lc_ipc_shmget.key;
61 	old_size = size = m->m_lc_ipc_shmget.size;
62 	flag = m->m_lc_ipc_shmget.flag;
63 
64 	if ((shm = shm_find_key(key)) != NULL) {
65 		if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, flag))
66 			return EACCES;
67 		if ((flag & IPC_CREAT) && (flag & IPC_EXCL))
68 			return EEXIST;
69 		if (size && shm->shmid_ds.shm_segsz < size)
70 			return EINVAL;
71 		i = shm - shm_list;
72 	} else { /* no key found */
73 		if (!(flag & IPC_CREAT))
74 			return ENOENT;
75 		if (size <= 0)
76 			return EINVAL;
77 		size = roundup(size, PAGE_SIZE);
78 		if (size <= 0)
79 			return EINVAL;
80 
81 		/* Find a free entry. */
82 		for (i = 0; i < __arraycount(shm_list); i++)
83 			if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
84 				break;
85 		if (i == __arraycount(shm_list))
86 			return ENOSPC;
87 
88 		/*
89 		 * Allocate memory to share.  For now, we store the page
90 		 * reference as a numerical value so as to avoid issues with
91 		 * live update.  TODO: a proper solution.
92 		 */
93 		page = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
94 		if (page == MAP_FAILED)
95 			return ENOMEM;
96 		memset(page, 0, size);
97 
98 		/* Initialize the entry. */
99 		shm = &shm_list[i];
100 		seq = shm->shmid_ds.shm_perm._seq;
101 		memset(shm, 0, sizeof(*shm));
102 
103 		shm->shmid_ds.shm_perm._key = key;
104 		shm->shmid_ds.shm_perm.cuid =
105 			shm->shmid_ds.shm_perm.uid = getnuid(m->m_source);
106 		shm->shmid_ds.shm_perm.cgid =
107 			shm->shmid_ds.shm_perm.gid = getngid(m->m_source);
108 		shm->shmid_ds.shm_perm.mode = SHM_ALLOC | (flag & ACCESSPERMS);
109 		shm->shmid_ds.shm_perm._seq = (seq + 1) & 0x7fff;
110 		shm->shmid_ds.shm_segsz = old_size;
111 		shm->shmid_ds.shm_atime = 0;
112 		shm->shmid_ds.shm_dtime = 0;
113 		shm->shmid_ds.shm_ctime = clock_time(NULL);
114 		shm->shmid_ds.shm_cpid = getnpid(m->m_source);
115 		shm->shmid_ds.shm_lpid = 0;
116 		shm->shmid_ds.shm_nattch = 0;
117 		shm->page = (vir_bytes)page;
118 		shm->vm_id = vm_getphys(sef_self(), page);
119 
120 		assert(i <= shm_list_nr);
121 		if (i == shm_list_nr)
122 			shm_list_nr++;
123 	}
124 
125 	m->m_lc_ipc_shmget.retid = IXSEQ_TO_IPCID(i, shm->shmid_ds.shm_perm);
126 	return OK;
127 }
128 
129 int
130 do_shmat(message * m)
131 {
132 	int id, flag, mask;
133 	vir_bytes addr;
134 	void *ret;
135 	struct shm_struct *shm;
136 
137 	id = m->m_lc_ipc_shmat.id;
138 	addr = (vir_bytes)m->m_lc_ipc_shmat.addr;
139 	flag = m->m_lc_ipc_shmat.flag;
140 
141 	if (addr % PAGE_SIZE) {
142 		if (flag & SHM_RND)
143 			addr -= addr % PAGE_SIZE;
144 		else
145 			return EINVAL;
146 	}
147 
148 	if ((shm = shm_find_id(id)) == NULL)
149 		return EINVAL;
150 
151 	mask = 0;
152 	if (flag & SHM_RDONLY)
153 		mask = IPC_R;
154 	else
155 		mask = IPC_R | IPC_W;
156 	if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, mask))
157 		return EACCES;
158 
159 	ret = vm_remap(m->m_source, sef_self(), (void *)addr,
160 	    (void *)shm->page, shm->shmid_ds.shm_segsz);
161 	if (ret == MAP_FAILED)
162 		return ENOMEM;
163 
164 	shm->shmid_ds.shm_atime = clock_time(NULL);
165 	shm->shmid_ds.shm_lpid = getnpid(m->m_source);
166 	/* nattch is updated lazily */
167 
168 	m->m_lc_ipc_shmat.retaddr = ret;
169 	return OK;
170 }
171 
172 void
173 update_refcount_and_destroy(void)
174 {
175 	u8_t rc;
176 	unsigned int i;
177 
178 	for (i = 0; i < shm_list_nr; i++) {
179 		if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
180 			continue;
181 
182 		rc = vm_getrefcount(sef_self(), (void *)shm_list[i].page);
183 		if (rc == (u8_t)-1) {
184 			printf("IPC: can't find physical region.\n");
185 			continue;
186 		}
187 		shm_list[i].shmid_ds.shm_nattch = rc - 1;
188 
189 		if (shm_list[i].shmid_ds.shm_nattch == 0 &&
190 		    (shm_list[i].shmid_ds.shm_perm.mode & SHM_DEST)) {
191 			munmap((void *)shm_list[i].page,
192 			    roundup(shm_list[i].shmid_ds.shm_segsz,
193 			    PAGE_SIZE));
194 			/* Mark the entry as free. */
195 			shm_list[i].shmid_ds.shm_perm.mode &= ~SHM_ALLOC;
196 		}
197 	}
198 
199 	/*
200 	 * Now that we may have removed an arbitrary set of slots, ensure that
201 	 * shm_list_nr again equals the highest in-use slot number plus one.
202 	 */
203 	while (shm_list_nr > 0 &&
204 	    !(shm_list[shm_list_nr - 1].shmid_ds.shm_perm.mode & SHM_ALLOC))
205 		shm_list_nr--;
206 }
207 
208 int
209 do_shmdt(message * m)
210 {
211 	struct shm_struct *shm;
212 	vir_bytes addr;
213 	phys_bytes vm_id;
214 	unsigned int i;
215 
216 	addr = (vir_bytes)m->m_lc_ipc_shmdt.addr;
217 
218 	if ((vm_id = vm_getphys(m->m_source, (void *)addr)) == 0)
219 		return EINVAL;
220 
221 	for (i = 0; i < shm_list_nr; i++) {
222 		shm = &shm_list[i];
223 
224 		if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
225 			continue;
226 
227 		if (shm->vm_id == vm_id) {
228 			shm->shmid_ds.shm_atime = clock_time(NULL);
229 			shm->shmid_ds.shm_lpid = getnpid(m->m_source);
230 			/* nattch is updated lazily */
231 
232 			vm_unmap(m->m_source, (void *)addr);
233 			break;
234 		}
235 	}
236 	if (i == shm_list_nr)
237 		printf("IPC: do_shmdt: ID %lu not found\n", vm_id);
238 
239 	update_refcount_and_destroy();
240 
241 	return OK;
242 }
243 
244 /*
245  * Fill a shminfo structure with actual information.
246  */
247 static void
248 fill_shminfo(struct shminfo * sinfo)
249 {
250 
251 	memset(sinfo, 0, sizeof(*sinfo));
252 
253 	sinfo->shmmax = (unsigned long)-1;
254 	sinfo->shmmin = 1;
255 	sinfo->shmmni = __arraycount(shm_list);
256 	sinfo->shmseg = (unsigned long)-1;
257 	sinfo->shmall = (unsigned long)-1;
258 }
259 
260 int
261 do_shmctl(message * m)
262 {
263 	struct shmid_ds tmp_ds;
264 	struct shm_struct *shm;
265 	struct shminfo sinfo;
266 	struct shm_info s_info;
267 	vir_bytes buf;
268 	unsigned int i;
269 	uid_t uid;
270 	int r, id, cmd;
271 
272 	id = m->m_lc_ipc_shmctl.id;
273 	cmd = m->m_lc_ipc_shmctl.cmd;
274 	buf = (vir_bytes)m->m_lc_ipc_shmctl.buf;
275 
276 	/*
277 	 * For stat calls, sure that all information is up-to-date.  Since this
278 	 * may free the slot, do this before mapping from ID to slot below.
279 	 */
280 	if (cmd == IPC_STAT || cmd == SHM_STAT)
281 		update_refcount_and_destroy();
282 
283 	switch (cmd) {
284 	case IPC_INFO:
285 	case SHM_INFO:
286 		shm = NULL;
287 		break;
288 	case SHM_STAT:
289 		if (id < 0 || (unsigned int)id >= shm_list_nr)
290 			return EINVAL;
291 		shm = &shm_list[id];
292 		if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
293 			return EINVAL;
294 		break;
295 	default:
296 		if ((shm = shm_find_id(id)) == NULL)
297 			return EINVAL;
298 		break;
299 	}
300 
301 	switch (cmd) {
302 	case IPC_STAT:
303 	case SHM_STAT:
304 		/* Check whether the caller has read permission. */
305 		if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, IPC_R))
306 			return EACCES;
307 		if ((r = sys_datacopy(SELF, (vir_bytes)&shm->shmid_ds,
308 		    m->m_source, buf, sizeof(shm->shmid_ds))) != OK)
309 			return r;
310 		if (cmd == SHM_STAT)
311 			m->m_lc_ipc_shmctl.ret =
312 			    IXSEQ_TO_IPCID(id, shm->shmid_ds.shm_perm);
313 		break;
314 	case IPC_SET:
315 		uid = getnuid(m->m_source);
316 		if (uid != shm->shmid_ds.shm_perm.cuid &&
317 		    uid != shm->shmid_ds.shm_perm.uid && uid != 0)
318 			return EPERM;
319 		if ((r = sys_datacopy(m->m_source, buf, SELF,
320 		    (vir_bytes)&tmp_ds, sizeof(tmp_ds))) != OK)
321 			return r;
322 		shm->shmid_ds.shm_perm.uid = tmp_ds.shm_perm.uid;
323 		shm->shmid_ds.shm_perm.gid = tmp_ds.shm_perm.gid;
324 		shm->shmid_ds.shm_perm.mode &= ~ACCESSPERMS;
325 		shm->shmid_ds.shm_perm.mode |=
326 		    tmp_ds.shm_perm.mode & ACCESSPERMS;
327 		shm->shmid_ds.shm_ctime = clock_time(NULL);
328 		break;
329 	case IPC_RMID:
330 		uid = getnuid(m->m_source);
331 		if (uid != shm->shmid_ds.shm_perm.cuid &&
332 		    uid != shm->shmid_ds.shm_perm.uid && uid != 0)
333 			return EPERM;
334 		shm->shmid_ds.shm_perm.mode |= SHM_DEST;
335 		/* Destroy if possible. */
336 		update_refcount_and_destroy();
337 		break;
338 	case IPC_INFO:
339 		fill_shminfo(&sinfo);
340 		if ((r = sys_datacopy(SELF, (vir_bytes)&sinfo, m->m_source,
341 		    buf, sizeof(sinfo))) != OK)
342 			return r;
343 		if (shm_list_nr > 0)
344 			m->m_lc_ipc_shmctl.ret = shm_list_nr - 1;
345 		else
346 			m->m_lc_ipc_shmctl.ret = 0;
347 		break;
348 	case SHM_INFO:
349 		memset(&s_info, 0, sizeof(s_info));
350 		s_info.used_ids = shm_list_nr;
351 		s_info.shm_tot = 0;
352 		for (i = 0; i < shm_list_nr; i++)
353 			s_info.shm_tot +=
354 			    shm_list[i].shmid_ds.shm_segsz / PAGE_SIZE;
355 		s_info.shm_rss = s_info.shm_tot;
356 		s_info.shm_swp = 0;
357 		s_info.swap_attempts = 0;
358 		s_info.swap_successes = 0;
359 		if ((r = sys_datacopy(SELF, (vir_bytes)&s_info, m->m_source,
360 		    buf, sizeof(s_info))) != OK)
361 			return r;
362 		if (shm_list_nr > 0)
363 			m->m_lc_ipc_shmctl.ret = shm_list_nr - 1;
364 		else
365 			m->m_lc_ipc_shmctl.ret = 0;
366 		break;
367 	default:
368 		return EINVAL;
369 	}
370 	return OK;
371 }
372 
373 /*
374  * Return shared memory information for a remote MIB call on the sysvipc_info
375  * node in the kern.ipc subtree.  The particular semantics of this call are
376  * tightly coupled to the implementation of the ipcs(1) userland utility.
377  */
378 ssize_t
379 get_shm_mib_info(struct rmib_oldp * oldp)
380 {
381 	struct shm_sysctl_info shmsi;
382 	struct shmid_ds *shmds;
383 	unsigned int i;
384 	ssize_t r, off;
385 
386 	off = 0;
387 
388 	fill_shminfo(&shmsi.shminfo);
389 
390 	/*
391 	 * As a hackish exception, the requested size may imply that just
392 	 * general information is to be returned, without throwing an ENOMEM
393 	 * error because there is no space for full output.
394 	 */
395 	if (rmib_getoldlen(oldp) == sizeof(shmsi.shminfo))
396 		return rmib_copyout(oldp, 0, &shmsi.shminfo,
397 		    sizeof(shmsi.shminfo));
398 
399 	/*
400 	 * ipcs(1) blindly expects the returned array to be of size
401 	 * shminfo.shmmni, using the SHMSEG_ALLOCATED (aka SHM_ALLOC) mode flag
402 	 * to see whether each entry is valid.  If we return a smaller size,
403 	 * ipcs(1) will access arbitrary memory.
404 	 */
405 	assert(shmsi.shminfo.shmmni > 0);
406 
407 	if (oldp == NULL)
408 		return sizeof(shmsi) + sizeof(shmsi.shmids[0]) *
409 		    (shmsi.shminfo.shmmni - 1);
410 
411 	/*
412 	 * Copy out entries one by one.  For the first entry, copy out the
413 	 * entire "shmsi" structure.  For subsequent entries, reuse the single
414 	 * embedded 'shmids' element of "shmsi" and copy out only that element.
415 	 */
416 	for (i = 0; i < shmsi.shminfo.shmmni; i++) {
417 		shmds = &shm_list[i].shmid_ds;
418 
419 		memset(&shmsi.shmids[0], 0, sizeof(shmsi.shmids[0]));
420 		if (i < shm_list_nr && (shmds->shm_perm.mode & SHM_ALLOC)) {
421 			prepare_mib_perm(&shmsi.shmids[0].shm_perm,
422 			    &shmds->shm_perm);
423 			shmsi.shmids[0].shm_segsz = shmds->shm_segsz;
424 			shmsi.shmids[0].shm_lpid = shmds->shm_lpid;
425 			shmsi.shmids[0].shm_cpid = shmds->shm_cpid;
426 			shmsi.shmids[0].shm_atime = shmds->shm_atime;
427 			shmsi.shmids[0].shm_dtime = shmds->shm_dtime;
428 			shmsi.shmids[0].shm_ctime = shmds->shm_ctime;
429 			shmsi.shmids[0].shm_nattch = shmds->shm_nattch;
430 		}
431 
432 		if (off == 0)
433 			r = rmib_copyout(oldp, off, &shmsi, sizeof(shmsi));
434 		else
435 			r = rmib_copyout(oldp, off, &shmsi.shmids[0],
436 			    sizeof(shmsi.shmids[0]));
437 
438 		if (r < 0)
439 			return r;
440 		off += r;
441 	}
442 
443 	return off;
444 }
445 
446 #if 0
447 static void
448 list_shm_ds(void)
449 {
450 	unsigned int i;
451 
452 	printf("key\tid\tpage\n");
453 	for (i = 0; i < shm_list_nr; i++) {
454 		if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
455 			continue;
456 		printf("%ld\t%d\t%lx\n",
457 		    shm_list[i].shmid_ds.shm_perm._key,
458 		    IXSEQ_TO_IPCID(i, shm_list[i].shmid_ds.shm_perm),
459 		    shm_list[i].page);
460 	}
461 }
462 #endif
463 
464 int
465 is_shm_nil(void)
466 {
467 
468 	return (shm_list_nr == 0);
469 }
470