blob: 7dd29f405e410153b190dfed642939925bda3335 [file] [log] [blame]
Davin Pottse5ef45b2019-02-01 22:52:23 -06001/*
2posixshmem - A Python module for accessing POSIX 1003.1b-1993 shared memory.
3
4Copyright (c) 2012, Philip Semanchuk
5Copyright (c) 2018, 2019, Davin Potts
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are met:
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 * Neither the name of posixshmem nor the names of its contributors may
16 be used to endorse or promote products derived from this software
17 without specific prior written permission.
18
19THIS SOFTWARE IS PROVIDED BY ITS CONTRIBUTORS ''AS IS'' AND ANY
20EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22DISCLAIMED. IN NO EVENT SHALL Philip Semanchuk BE LIABLE FOR ANY
23DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29*/
30
31#define PY_SSIZE_T_CLEAN
32
33#include <Python.h>
34#include "structmember.h"
35
36#include <time.h>
37#include <sys/time.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <stdio.h>
41
42// For shared memory stuff
43#include <sys/stat.h>
44#include <sys/mman.h>
45
46/* SEM_FAILED is defined as an int in Apple's headers, and this makes the
47compiler complain when I compare it to a pointer. Python faced the same
48problem (issue 9586) and I copied their solution here.
49ref: http://bugs.python.org/issue9586
50
51Note that in /Developer/SDKs/MacOSX10.4u.sdk/usr/include/sys/semaphore.h,
52SEM_FAILED is #defined as -1 and that's apparently the definition used by
53Python when building. In /usr/include/sys/semaphore.h, it's defined
54as ((sem_t *)-1).
55*/
56#ifdef __APPLE__
57 #undef SEM_FAILED
58 #define SEM_FAILED ((sem_t *)-1)
59#endif
60
61/* POSIX says that a mode_t "shall be an integer type". To avoid the need
62for a specific get_mode function for each type, I'll just stuff the mode into
63a long and mention it in the Xxx_members list for each type.
64ref: http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/types.h.html
65*/
66
67typedef struct {
68 PyObject_HEAD
69 char *name;
70 long mode;
71 int fd;
72} SharedMemory;
73
74
75// FreeBSD (and perhaps other BSDs) limit names to 14 characters. In the
76// code below, strings of this length are allocated on the stack, so
77// increase this gently or change that code to use malloc().
78#define MAX_SAFE_NAME_LENGTH 14
79
80
81/* Struct to contain an IPC object name which can be None */
82typedef struct {
83 int is_none;
84 char *name;
85} NoneableName;
86
87
88/*
89 Exceptions for this module
90*/
91
92static PyObject *pBaseException;
93static PyObject *pPermissionsException;
94static PyObject *pExistentialException;
95
96
97#ifdef POSIX_IPC_DEBUG
98#define DPRINTF(fmt, args...) fprintf(stderr, "+++ " fmt, ## args)
99#else
100#define DPRINTF(fmt, args...)
101#endif
102
103static char *
104bytes_to_c_string(PyObject* o, int lock) {
105/* Convert a bytes object to a char *. Optionally lock the buffer if it is a
106 bytes array.
107 This code swiped directly from Python 3.1's posixmodule.c by Philip S.
108 The name there is bytes2str().
109*/
110 if (PyBytes_Check(o))
111 return PyBytes_AsString(o);
112 else if (PyByteArray_Check(o)) {
113 if (lock && PyObject_GetBuffer(o, NULL, 0) < 0)
114 /* On a bytearray, this should not fail. */
115 PyErr_BadInternalCall();
116 return PyByteArray_AsString(o);
117 } else {
118 /* The FS converter should have verified that this
119 is either bytes or bytearray. */
120 Py_FatalError("bad object passed to bytes2str");
121 /* not reached. */
122 return "";
123 }
124}
125
126static void
127release_bytes(PyObject* o)
128 /* Release the lock, decref the object.
129 This code swiped directly from Python 3.1's posixmodule.c by Philip S.
130 */
131{
132 if (PyByteArray_Check(o))
133 o->ob_type->tp_as_buffer->bf_releasebuffer(NULL, 0);
134 Py_DECREF(o);
135}
136
137
138static int
139random_in_range(int min, int max) {
140 // returns a random int N such that min <= N <= max
141 int diff = (max - min) + 1;
142
143 // ref: http://www.c-faq.com/lib/randrange.html
144 return ((int)((double)rand() / ((double)RAND_MAX + 1) * diff)) + min;
145}
146
147
148static
149int create_random_name(char *name) {
150 // The random name is always lowercase so that this code will work
151 // on case-insensitive file systems. It always starts with a forward
152 // slash.
153 int length;
154 char *alphabet = "abcdefghijklmnopqrstuvwxyz";
155 int i;
156
157 // Generate a random length for the name. I subtract 1 from the
158 // MAX_SAFE_NAME_LENGTH in order to allow for the name's leading "/".
159 length = random_in_range(6, MAX_SAFE_NAME_LENGTH - 1);
160
161 name[0] = '/';
162 name[length] = '\0';
163 i = length;
164 while (--i)
165 name[i] = alphabet[random_in_range(0, 25)];
166
167 return length;
168}
169
170
171static int
172convert_name_param(PyObject *py_name_param, void *checked_name) {
173 /* Verifies that the py_name_param is either None or a string.
174 If it's a string, checked_name->name points to a PyMalloc-ed buffer
175 holding a NULL-terminated C version of the string when this function
176 concludes. The caller is responsible for releasing the buffer.
177 */
178 int rc = 0;
179 NoneableName *p_name = (NoneableName *)checked_name;
180 PyObject *py_name_as_bytes = NULL;
181 char *p_name_as_c_string = NULL;
182
183 DPRINTF("inside convert_name_param\n");
184 DPRINTF("PyBytes_Check() = %d \n", PyBytes_Check(py_name_param));
185 DPRINTF("PyString_Check() = %d \n", PyString_Check(py_name_param));
186 DPRINTF("PyUnicode_Check() = %d \n", PyUnicode_Check(py_name_param));
187
188 p_name->is_none = 0;
189
190 // The name can be None or a Python string
191 if (py_name_param == Py_None) {
192 DPRINTF("name is None\n");
193 rc = 1;
194 p_name->is_none = 1;
195 }
196 else if (PyUnicode_Check(py_name_param) || PyBytes_Check(py_name_param)) {
197 DPRINTF("name is Unicode or bytes\n");
198 // The caller passed me a Unicode string or a byte array; I need a
199 // char *. Getting from one to the other takes a couple steps.
200
201 if (PyUnicode_Check(py_name_param)) {
202 DPRINTF("name is Unicode\n");
203 // PyUnicode_FSConverter() converts the Unicode object into a
204 // bytes or a bytearray object. (Why can't it be one or the other?)
205 PyUnicode_FSConverter(py_name_param, &py_name_as_bytes);
206 }
207 else {
208 DPRINTF("name is bytes\n");
209 // Make a copy of the name param.
210 py_name_as_bytes = PyBytes_FromObject(py_name_param);
211 }
212
213 // bytes_to_c_string() returns a pointer to the buffer.
214 p_name_as_c_string = bytes_to_c_string(py_name_as_bytes, 0);
215
216 // PyMalloc memory and copy the user-supplied name to it.
217 p_name->name = (char *)PyMem_Malloc(strlen(p_name_as_c_string) + 1);
218 if (p_name->name) {
219 rc = 1;
220 strcpy(p_name->name, p_name_as_c_string);
221 }
222 else
223 PyErr_SetString(PyExc_MemoryError, "Out of memory");
224
225 // The bytes version of the name isn't useful to me, and per the
226 // documentation for PyUnicode_FSConverter(), I am responsible for
227 // releasing it when I'm done.
228 release_bytes(py_name_as_bytes);
229 }
230 else
231 PyErr_SetString(PyExc_TypeError, "Name must be None or a string");
232
233 return rc;
234}
235
236
237
238/* ===== Begin Shared Memory implementation functions ===== */
239
240static PyObject *
241shm_str(SharedMemory *self) {
242 return PyUnicode_FromString(self->name ? self->name : "(no name)");
243}
244
245static PyObject *
246shm_repr(SharedMemory *self) {
247 char mode[32];
248
249 sprintf(mode, "0%o", (int)(self->mode));
250
251 return PyUnicode_FromFormat("_posixshmem.SharedMemory(\"%s\", mode=%s)",
252 self->name, mode);
253}
254
255static PyObject *
256my_shm_unlink(const char *name) {
257 DPRINTF("unlinking shm name %s\n", name);
258 if (-1 == shm_unlink(name)) {
259 switch (errno) {
260 case EACCES:
261 PyErr_SetString(pPermissionsException, "Permission denied");
262 break;
263
264 case ENOENT:
265 PyErr_SetString(pExistentialException,
266 "No shared memory exists with the specified name");
267 break;
268
269 case ENAMETOOLONG:
270 PyErr_SetString(PyExc_ValueError, "The name is too long");
271 break;
272
273 default:
274 PyErr_SetFromErrno(PyExc_OSError);
275 break;
276 }
277
278 goto error_return;
279 }
280
281 Py_RETURN_NONE;
282
283 error_return:
284 return NULL;
285}
286
287
288static PyObject *
289SharedMemory_new(PyTypeObject *type, PyObject *args, PyObject *kwlist) {
290 SharedMemory *self;
291
292 self = (SharedMemory *)type->tp_alloc(type, 0);
293
294 return (PyObject *)self;
295}
296
297
298static int
299SharedMemory_init(SharedMemory *self, PyObject *args, PyObject *keywords) {
300 NoneableName name;
301 char temp_name[MAX_SAFE_NAME_LENGTH + 1];
302 unsigned int flags = 0;
303 unsigned long size = 0;
304 int read_only = 0;
305 static char *keyword_list[ ] = {"name", "flags", "mode", "size", "read_only", NULL};
306
307 // First things first -- initialize the self struct.
308 self->name = NULL;
309 self->fd = 0;
310 self->mode = 0600;
311
312 if (!PyArg_ParseTupleAndKeywords(args, keywords, "O&|Iiki", keyword_list,
313 &convert_name_param, &name, &flags,
314 &(self->mode), &size, &read_only))
315 goto error_return;
316
317 if ( !(flags & O_CREAT) && (flags & O_EXCL) ) {
318 PyErr_SetString(PyExc_ValueError,
319 "O_EXCL must be combined with O_CREAT");
320 goto error_return;
321 }
322
323 if (name.is_none && ((flags & O_EXCL) != O_EXCL)) {
324 PyErr_SetString(PyExc_ValueError,
325 "Name can only be None if O_EXCL is set");
326 goto error_return;
327 }
328
329 flags |= (read_only ? O_RDONLY : O_RDWR);
330
331 if (name.is_none) {
332 // (name == None) ==> generate a name for the caller
333 do {
334 errno = 0;
335 create_random_name(temp_name);
336
337 DPRINTF("calling shm_open, name=%s, flags=0x%x, mode=0%o\n",
338 temp_name, flags, (int)self->mode);
339 self->fd = shm_open(temp_name, flags, (mode_t)self->mode);
340
341 } while ( (-1 == self->fd) && (EEXIST == errno) );
342
343 // PyMalloc memory and copy the randomly-generated name to it.
344 self->name = (char *)PyMem_Malloc(strlen(temp_name) + 1);
345 if (self->name)
346 strcpy(self->name, temp_name);
347 else {
348 PyErr_SetString(PyExc_MemoryError, "Out of memory");
349 goto error_return;
350 }
351 }
352 else {
353 // (name != None) ==> use name supplied by the caller. It was
354 // already converted to C by convert_name_param().
355 self->name = name.name;
356
357 DPRINTF("calling shm_open, name=%s, flags=0x%x, mode=0%o\n",
358 self->name, flags, (int)self->mode);
359 self->fd = shm_open(self->name, flags, (mode_t)self->mode);
360 }
361
362 DPRINTF("shm fd = %d\n", self->fd);
363
364 if (-1 == self->fd) {
365 self->fd = 0;
366 switch (errno) {
367 case EACCES:
368 PyErr_Format(pPermissionsException,
369 "No permission to %s this segment",
370 (flags & O_TRUNC) ? "truncate" : "access"
371 );
372 break;
373
374 case EEXIST:
375 PyErr_SetString(pExistentialException,
376 "Shared memory with the specified name already exists");
377 break;
378
379 case ENOENT:
380 PyErr_SetString(pExistentialException,
381 "No shared memory exists with the specified name");
382 break;
383
384 case EINVAL:
385 PyErr_SetString(PyExc_ValueError, "Invalid parameter(s)");
386 break;
387
388 case EMFILE:
389 PyErr_SetString(PyExc_OSError,
390 "This process already has the maximum number of files open");
391 break;
392
393 case ENFILE:
394 PyErr_SetString(PyExc_OSError,
395 "The system limit on the total number of open files has been reached");
396 break;
397
398 case ENAMETOOLONG:
399 PyErr_SetString(PyExc_ValueError,
400 "The name is too long");
401 break;
402
403 default:
404 PyErr_SetFromErrno(PyExc_OSError);
405 break;
406 }
407
408 goto error_return;
409 }
410 else {
411 if (size) {
412 DPRINTF("calling ftruncate, fd = %d, size = %ld\n", self->fd, size);
413 if (-1 == ftruncate(self->fd, (off_t)size)) {
414 // The code below will raise a Python error. Since that error
415 // is raised during __init__(), it will look to the caller
416 // as if object creation failed entirely. Here I clean up
417 // the system object I just created.
418 close(self->fd);
419 shm_unlink(self->name);
420
421 // ftruncate can return a ton of different errors, but most
422 // are not relevant or are extremely unlikely.
423 switch (errno) {
424 case EINVAL:
425 PyErr_SetString(PyExc_ValueError,
426 "The size is invalid or the memory is read-only");
427 break;
428
429 case EFBIG:
430 PyErr_SetString(PyExc_ValueError,
431 "The size is too large");
432 break;
433
434 case EROFS:
435 case EACCES:
436 PyErr_SetString(pPermissionsException,
437 "The memory is read-only");
438 break;
439
440 default:
441 PyErr_SetFromErrno(PyExc_OSError);
442 break;
443 }
444
445 goto error_return;
446 }
447 }
448 }
449
450 return 0;
451
452 error_return:
453 return -1;
454}
455
456
457static void SharedMemory_dealloc(SharedMemory *self) {
458 DPRINTF("dealloc\n");
459 PyMem_Free(self->name);
460 self->name = NULL;
461
462 Py_TYPE(self)->tp_free((PyObject*)self);
463}
464
465
466PyObject *
467SharedMemory_getsize(SharedMemory *self, void *closure) {
468 struct stat fileinfo;
469 off_t size = -1;
470
471 if (0 == fstat(self->fd, &fileinfo))
472 size = fileinfo.st_size;
473 else {
474 switch (errno) {
475 case EBADF:
476 case EINVAL:
477 PyErr_SetString(pExistentialException,
478 "The segment does not exist");
479 break;
480
481 default:
482 PyErr_SetFromErrno(PyExc_OSError);
483 break;
484 }
485
486 goto error_return;
487 }
488
489 return Py_BuildValue("k", (unsigned long)size);
490
491 error_return:
492 return NULL;
493}
494
495
496PyObject *
497SharedMemory_close_fd(SharedMemory *self) {
498 if (self->fd) {
499 if (-1 == close(self->fd)) {
500 switch (errno) {
501 case EBADF:
502 PyErr_SetString(PyExc_ValueError,
503 "The file descriptor is invalid");
504 break;
505
506 default:
507 PyErr_SetFromErrno(PyExc_OSError);
508 break;
509 }
510
511 goto error_return;
512 }
513 }
514
515 Py_RETURN_NONE;
516
517 error_return:
518 return NULL;
519}
520
521
522PyObject *
523SharedMemory_unlink(SharedMemory *self) {
524 return my_shm_unlink(self->name);
525}
526
527
528/* ===== End Shared Memory functions ===== */
529
530
531/*
532 *
533 * Shared memory meta stuff for describing myself to Python
534 *
535 */
536
537
538static PyMemberDef SharedMemory_members[] = {
539 { "name",
540 T_STRING,
541 offsetof(SharedMemory, name),
542 READONLY,
543 "The name specified in the constructor"
544 },
545 { "fd",
546 T_INT,
547 offsetof(SharedMemory, fd),
548 READONLY,
549 "Shared memory segment file descriptor"
550 },
551 { "mode",
552 T_LONG,
553 offsetof(SharedMemory, mode),
554 READONLY,
555 "The mode specified in the constructor"
556 },
557 {NULL} /* Sentinel */
558};
559
560
561static PyMethodDef SharedMemory_methods[] = {
562 { "close_fd",
563 (PyCFunction)SharedMemory_close_fd,
564 METH_NOARGS,
565 "Closes the file descriptor associated with the shared memory."
566 },
567 { "unlink",
568 (PyCFunction)SharedMemory_unlink,
569 METH_NOARGS,
570 "Unlink (remove) the shared memory."
571 },
572 {NULL, NULL, 0, NULL} /* Sentinel */
573};
574
575
576static PyGetSetDef SharedMemory_getseters[] = {
577 // size is read-only
578 { "size",
579 (getter)SharedMemory_getsize,
580 (setter)NULL,
581 "size",
582 NULL
583 },
584 {NULL} /* Sentinel */
585};
586
587
588static PyTypeObject SharedMemoryType = {
589 PyVarObject_HEAD_INIT(NULL, 0)
590 "_posixshmem._PosixSharedMemory", // tp_name
591 sizeof(SharedMemory), // tp_basicsize
592 0, // tp_itemsize
593 (destructor) SharedMemory_dealloc, // tp_dealloc
594 0, // tp_print
595 0, // tp_getattr
596 0, // tp_setattr
597 0, // tp_compare
598 (reprfunc) shm_repr, // tp_repr
599 0, // tp_as_number
600 0, // tp_as_sequence
601 0, // tp_as_mapping
602 0, // tp_hash
603 0, // tp_call
604 (reprfunc) shm_str, // tp_str
605 0, // tp_getattro
606 0, // tp_setattro
607 0, // tp_as_buffer
608 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
609 // tp_flags
610 "POSIX shared memory object", // tp_doc
611 0, // tp_traverse
612 0, // tp_clear
613 0, // tp_richcompare
614 0, // tp_weaklistoffset
615 0, // tp_iter
616 0, // tp_iternext
617 SharedMemory_methods, // tp_methods
618 SharedMemory_members, // tp_members
619 SharedMemory_getseters, // tp_getset
620 0, // tp_base
621 0, // tp_dict
622 0, // tp_descr_get
623 0, // tp_descr_set
624 0, // tp_dictoffset
625 (initproc) SharedMemory_init, // tp_init
626 0, // tp_alloc
627 (newfunc) SharedMemory_new, // tp_new
628 0, // tp_free
629 0, // tp_is_gc
630 0 // tp_bases
631};
632
633
634/*
635 *
636 * Module-level functions & meta stuff
637 *
638 */
639
640static PyObject *
641posixshmem_unlink_shared_memory(PyObject *self, PyObject *args) {
642 const char *name;
643
644 if (!PyArg_ParseTuple(args, "s", &name))
645 return NULL;
646 else
647 return my_shm_unlink(name);
648}
649
650
651static PyMethodDef module_methods[ ] = {
652 { "unlink_shared_memory",
653 (PyCFunction)posixshmem_unlink_shared_memory,
654 METH_VARARGS,
655 "Unlink shared memory"
656 },
657 {NULL} /* Sentinel */
658};
659
660
661static struct PyModuleDef this_module = {
662 PyModuleDef_HEAD_INIT, // m_base
663 "_posixshmem", // m_name
664 "POSIX shared memory module", // m_doc
665 -1, // m_size (space allocated for module globals)
666 module_methods, // m_methods
667 NULL, // m_reload
668 NULL, // m_traverse
669 NULL, // m_clear
670 NULL // m_free
671};
672
673/* Module init function */
674PyMODINIT_FUNC
675PyInit__posixshmem(void) {
676 PyObject *module;
677 PyObject *module_dict;
678
679 // I call this in case I'm asked to create any random names.
680 srand((unsigned int)time(NULL));
681
682 module = PyModule_Create(&this_module);
683
684 if (!module)
685 goto error_return;
686
687 if (PyType_Ready(&SharedMemoryType) < 0)
688 goto error_return;
689
690 Py_INCREF(&SharedMemoryType);
691 PyModule_AddObject(module, "_PosixSharedMemory", (PyObject *)&SharedMemoryType);
692
693
694 PyModule_AddStringConstant(module, "__copyright__", "Copyright 2012 Philip Semanchuk, 2018-2019 Davin Potts");
695
696 PyModule_AddIntConstant(module, "O_CREAT", O_CREAT);
697 PyModule_AddIntConstant(module, "O_EXCL", O_EXCL);
698 PyModule_AddIntConstant(module, "O_CREX", O_CREAT | O_EXCL);
699 PyModule_AddIntConstant(module, "O_TRUNC", O_TRUNC);
700
701 if (!(module_dict = PyModule_GetDict(module)))
702 goto error_return;
703
704 // Exceptions
705 if (!(pBaseException = PyErr_NewException("_posixshmem.Error", NULL, NULL)))
706 goto error_return;
707 else
708 PyDict_SetItemString(module_dict, "Error", pBaseException);
709
710 if (!(pPermissionsException = PyErr_NewException("_posixshmem.PermissionsError", pBaseException, NULL)))
711 goto error_return;
712 else
713 PyDict_SetItemString(module_dict, "PermissionsError", pPermissionsException);
714
715 if (!(pExistentialException = PyErr_NewException("_posixshmem.ExistentialError", pBaseException, NULL)))
716 goto error_return;
717 else
718 PyDict_SetItemString(module_dict, "ExistentialError", pExistentialException);
719
720 return module;
721
722 error_return:
723 return NULL;
724}