summaryrefslogtreecommitdiff
path: root/semaphore.c
diff options
context:
space:
mode:
Diffstat (limited to 'semaphore.c')
-rw-r--r--semaphore.c776
1 files changed, 776 insertions, 0 deletions
diff --git a/semaphore.c b/semaphore.c
new file mode 100644
index 0000000..57cf2aa
--- /dev/null
+++ b/semaphore.c
@@ -0,0 +1,776 @@
+#include "Python.h"
+#include "structmember.h"
+
+#include "common.h"
+#include "semaphore.h"
+
+#define ONE_BILLION 1000000000
+
+// This enum has to start at zero because its values are used as an
+// arry index in sem_perform_semop().
+enum SEMOP_TYPE {
+ SEMOP_P = 0,
+ SEMOP_V,
+ SEMOP_Z
+};
+
+/* Struct to contain a timeout which can be None */
+typedef struct {
+ int is_none;
+ int is_zero;
+ struct timespec timestamp;
+} NoneableTimeout;
+
+
+// It is recommended practice to define this union here in the .c module, but
+// it's been common practice for platforms to define it themselves in header
+// files. For instance, BSD and OS X do so (provisionally) in sem.h. As a
+// result, I need to surround this with an #ifdef.
+#ifdef _SEM_SEMUN_UNDEFINED
+union semun {
+ int val; /* used for SETVAL only */
+ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
+ unsigned short *array; /* used for GETALL and SETALL */
+};
+#endif
+
+
+static int
+convert_timeout(PyObject *py_timeout, void *converted_timeout) {
+ // Converts a PyObject into a timeout if possible. The PyObject should
+ // be None or some sort of numeric value (e.g. int, float, etc.)
+ // converted_timeout should point to a NoneableTimeout. When this function
+ // returns, if the NoneableTimeout's is_none is true, then the rest of the
+ // struct is undefined. Otherwise, the rest of the struct is populated.
+ int rc = 0;
+ double simple_timeout = 0;
+ NoneableTimeout *p_timeout = (NoneableTimeout *)converted_timeout;
+
+ // The timeout can be None or any Python numeric type (float,
+ // int, long).
+ if (py_timeout == Py_None)
+ rc = 1;
+ else if (PyFloat_Check(py_timeout)) {
+ rc = 1;
+ simple_timeout = PyFloat_AsDouble(py_timeout);
+ }
+#if PY_MAJOR_VERSION < 3
+ else if (PyInt_Check(py_timeout)) {
+ rc = 1;
+ simple_timeout = (double)PyInt_AsLong(py_timeout);
+ }
+#endif
+ else if (PyLong_Check(py_timeout)) {
+ rc = 1;
+ simple_timeout = (double)PyLong_AsLong(py_timeout);
+ }
+
+ // The timeout may not be negative.
+ if ((rc) && (simple_timeout < 0))
+ rc = 0;
+
+ if (!rc)
+ PyErr_SetString(PyExc_TypeError,
+ "The timeout must be None or a non-negative number");
+ else {
+ if (py_timeout == Py_None)
+ p_timeout->is_none = 1;
+ else {
+ p_timeout->is_none = 0;
+
+ p_timeout->is_zero = (!simple_timeout);
+
+ // Note the difference between this and POSIX timeouts. System V
+ // timeouts expect tv_sec to represent a delta from the current
+ // time whereas POSIX semaphores expect an absolute value.
+ p_timeout->timestamp.tv_sec = (time_t)floor(simple_timeout);
+ p_timeout->timestamp.tv_nsec = (long)((simple_timeout - floor(simple_timeout)) * ONE_BILLION);
+ }
+ }
+
+ return rc;
+}
+
+
+PyObject *
+sem_str(Semaphore *self) {
+#if PY_MAJOR_VERSION > 2
+ return PyUnicode_FromFormat("Key=%ld, id=%d", (long)self->key, self->id);
+#else
+ return PyString_FromFormat("Key=%ld, id=%d", (long)self->key, self->id);
+#endif
+}
+
+
+PyObject *
+sem_repr(Semaphore *self) {
+#if PY_MAJOR_VERSION > 2
+ return PyUnicode_FromFormat("sysv_ipc.Semaphore(%ld)", (long)self->key);
+#else
+ return PyString_FromFormat("sysv_ipc.Semaphore(%ld)", (long)self->key);
+#endif
+}
+
+
+static void
+sem_set_error(void) {
+ switch (errno) {
+ case ENOENT:
+ case EINVAL:
+ PyErr_SetString(pExistentialException,
+ "No semaphore exists with the specified key");
+ break;
+
+ case EEXIST:
+ PyErr_SetString(pExistentialException,
+ "A semaphore with the specified key already exists");
+ break;
+
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case ERANGE:
+ PyErr_Format(PyExc_ValueError,
+ "The semaphore's value must remain between 0 and %ld (SEMAPHORE_VALUE_MAX)",
+ (long)SEMAPHORE_VALUE_MAX);
+ break;
+
+ case EAGAIN:
+ PyErr_SetString(pBusyException, "The semaphore is busy");
+ break;
+
+ case EIDRM:
+ PyErr_SetString(pExistentialException, "The semaphore was removed");
+ break;
+
+ case EINTR:
+ PyErr_SetString(pBaseException, "Signaled while waiting");
+ break;
+
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+}
+
+
+static PyObject *
+sem_perform_semop(enum SEMOP_TYPE op_type, Semaphore *self, PyObject *args, PyObject *keywords) {
+ int rc = 0;
+ NoneableTimeout timeout;
+ struct sembuf op[1];
+ /* delta (a.k.a. struct sembuf.sem_op) is a short
+ ref: http://www.opengroup.org/onlinepubs/000095399/functions/semop.html
+ */
+ short int delta;
+ char *keyword_list[3][3] = {
+ {"timeout", "delta", NULL}, // P == acquire
+ {"delta", NULL}, // V == release
+ {"timeout", NULL} // Z == zero test
+ };
+
+
+ /* Initialize this to the default value. If the user doesn't pass a
+ timeout, Python won't call convert_timeout() and so the timeout
+ will be otherwise uninitialized.
+ */
+ timeout.is_none = 1;
+
+ /* op_type is P, V or Z corresponding to the 3 Semaphore methods
+ that call that call semop(). */
+ switch (op_type) {
+ case SEMOP_P:
+ // P == acquire
+ delta = -1;
+ rc = PyArg_ParseTupleAndKeywords(args, keywords, "|O&h",
+ keyword_list[SEMOP_P],
+ convert_timeout, &timeout,
+ &delta);
+
+ if (rc && !delta) {
+ rc = 0;
+ PyErr_SetString(PyExc_ValueError, "The delta must be non-zero");
+ }
+ else
+ delta = -abs(delta);
+ break;
+
+ case SEMOP_V:
+ // V == release
+ delta = 1;
+ rc = PyArg_ParseTupleAndKeywords(args, keywords, "|h",
+ keyword_list[SEMOP_V],
+ &delta);
+
+ if (rc && !delta) {
+ rc = 0;
+ PyErr_SetString(PyExc_ValueError, "The delta must be non-zero");
+ }
+ else
+ delta = abs(delta);
+ break;
+
+ case SEMOP_Z:
+ // Z = Zero test
+ delta = 0;
+ rc = PyArg_ParseTupleAndKeywords(args, keywords, "|O&",
+ keyword_list[SEMOP_Z],
+ convert_timeout, &timeout);
+ break;
+
+ default:
+ PyErr_Format(pInternalException, "Bad op_type (%d)", op_type);
+ rc = 0;
+ break;
+ }
+
+ if (!rc)
+ goto error_return;
+
+ // Now that the caller's params have been vetted, I set up the op struct
+ // that I'm going to pass to semop().
+ op[0].sem_num = 0;
+ op[0].sem_op = delta;
+ op[0].sem_flg = self->op_flags;
+
+ Py_BEGIN_ALLOW_THREADS;
+#ifdef SEMTIMEDOP_EXISTS
+ // Call semtimedop() if appropriate, otherwise call semop()
+ if (!timeout.is_none) {
+ DPRINTF("calling semtimedop on id %d, op.sem_op=%d, op.flags=0x%x\n",
+ self->id, op[0].sem_op, op[0].sem_flg);
+ DPRINTF("timeout tv_sec = %ld; timeout tv_nsec = %ld\n",
+ timeout.timestamp.tv_sec, timeout.timestamp.tv_nsec);
+ rc = semtimedop(self->id, op, 1, &timeout.timestamp);
+ }
+ else {
+ DPRINTF("calling semop on id %d, op.sem_op = %d, op.flags=%x\n",
+ self->id, op[0].sem_op, op[0].sem_flg);
+ rc = semop(self->id, op, 1);
+ }
+#else
+ // no support for semtimedop(), always call semop() instead.
+ DPRINTF("calling semop on id %d, op.sem_op = %d, op.flags=%x\n",
+ self->id, op[0].sem_op, op[0].sem_flg);
+ rc = semop(self->id, op, 1);
+#endif
+ Py_END_ALLOW_THREADS;
+
+ if (rc == -1) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+// cmd can be any of the values defined in the documentation for semctl().
+static PyObject *
+sem_get_semctl_value(int semaphore_id, int cmd) {
+ int rc;
+
+ // semctl() returns an int
+ // ref: http://www.opengroup.org/onlinepubs/000095399/functions/semctl.html
+ rc = semctl(semaphore_id, 0, cmd);
+
+ if (-1 == rc) {
+ sem_set_error();
+ goto error_return;
+ }
+
+#if PY_MAJOR_VERSION > 2
+ return PyLong_FromLong(rc);
+#else
+ return PyInt_FromLong(rc);
+#endif
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+sem_get_ipc_perm_value(int id, enum GET_SET_IDENTIFIERS field) {
+ struct semid_ds sem_info;
+ union semun arg;
+ PyObject *py_value = NULL;
+
+ arg.buf = &sem_info;
+
+ // Here I get the values currently associated with the semaphore.
+ if (-1 == semctl(id, 0, IPC_STAT, arg)) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ switch (field) {
+ case SVIFP_IPC_PERM_UID:
+ py_value = UID_T_TO_PY(sem_info.sem_perm.uid);
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+ py_value = GID_T_TO_PY(sem_info.sem_perm.gid);
+ break;
+
+ case SVIFP_IPC_PERM_CUID:
+ py_value = UID_T_TO_PY(sem_info.sem_perm.cuid);
+ break;
+
+ case SVIFP_IPC_PERM_CGID:
+ py_value = GID_T_TO_PY(sem_info.sem_perm.cgid);
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+ py_value = MODE_T_TO_PY(sem_info.sem_perm.mode);
+ break;
+
+ // This isn't an ipc_perm value but it fits here anyway.
+ case SVIFP_SEM_OTIME:
+ py_value = TIME_T_TO_PY(sem_info.sem_otime);
+ break;
+
+ default:
+ PyErr_Format(pInternalException,
+ "Bad field %d passed to sem_get_ipc_perm_value", field);
+ goto error_return;
+ break;
+ }
+
+ return py_value;
+
+ error_return:
+ return NULL;
+}
+
+
+static int
+sem_set_ipc_perm_value(int id, enum GET_SET_IDENTIFIERS field, PyObject *py_value) {
+ struct semid_ds sem_info;
+ union semun arg;
+
+ arg.buf = &sem_info;
+
+#if PY_MAJOR_VERSION > 2
+ if (!PyLong_Check(py_value))
+#else
+ if (!PyInt_Check(py_value))
+#endif
+ {
+ PyErr_Format(PyExc_TypeError, "The attribute must be an integer");
+ goto error_return;
+ }
+
+ arg.buf = &sem_info;
+
+ /* Here I get the current values associated with the semaphore. It's
+ critical to populate sem_info with current values here (rather than
+ just using the struct filled with whatever garbage it acquired from
+ being declared on the stack) because the call to semctl(...IPC_SET...)
+ below will copy uid, gid and mode to the kernel's data structure.
+ */
+ if (-1 == semctl(id, 0, IPC_STAT, arg)) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ // Below I'm stuffing a Python int converted to a C long into a
+ // uid_t, gid_t or mode_t. A long might not fit, hence the explicit
+ // cast. If the user passes a value that's too big, tough cookies.
+ switch (field) {
+ case SVIFP_IPC_PERM_UID:
+#if PY_MAJOR_VERSION > 2
+ sem_info.sem_perm.uid = (uid_t)PyLong_AsLong(py_value);
+#else
+ sem_info.sem_perm.uid = (uid_t)PyInt_AsLong(py_value);
+#endif
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+#if PY_MAJOR_VERSION > 2
+ sem_info.sem_perm.gid = (gid_t)PyLong_AsLong(py_value);
+#else
+ sem_info.sem_perm.gid = (gid_t)PyInt_AsLong(py_value);
+#endif
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+#if PY_MAJOR_VERSION > 2
+ sem_info.sem_perm.mode = (mode_t)PyLong_AsLong(py_value);
+#else
+ sem_info.sem_perm.mode = (mode_t)PyInt_AsLong(py_value);
+#endif
+ break;
+
+ default:
+ PyErr_Format(pInternalException,
+ "Bad field %d passed to sem_set_ipc_perm_value", field);
+ goto error_return;
+ break;
+ }
+
+ if (-1 == semctl(id, 0, IPC_SET, arg)) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+sem_remove(int id) {
+ if (NULL == sem_get_semctl_value(id, IPC_RMID))
+ return NULL;
+ else
+ Py_RETURN_NONE;
+}
+
+
+void
+Semaphore_dealloc(Semaphore *self) {
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyObject *
+Semaphore_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
+ Semaphore *self;
+
+ self = (Semaphore *)type->tp_alloc(type, 0);
+
+ return (PyObject *)self;
+}
+
+
+int
+Semaphore_init(Semaphore *self, PyObject *args, PyObject *keywords) {
+ int mode = 0600;
+ int initial_value = 0;
+ int flags = 0;
+ union semun arg;
+ char *keyword_list[ ] = {"key", "flags", "mode", "initial_value", NULL};
+ NoneableKey key;
+
+ //Semaphore(key, [flags = 0, [mode = 0600, [initial_value = 0]]])
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "O&|iii", keyword_list,
+ &convert_key_param, &key, &flags,
+ &mode, &initial_value))
+ goto error_return;
+
+ DPRINTF("key is none = %d, key value = %ld\n", key.is_none, (long)key.value);
+
+ if ( !(flags & IPC_CREAT) && (flags & IPC_EXCL) ) {
+ PyErr_SetString(PyExc_ValueError,
+ "IPC_EXCL must be combined with IPC_CREAT");
+ goto error_return;
+ }
+
+ if (key.is_none && ((flags & IPC_EXCL) != IPC_EXCL)) {
+ PyErr_SetString(PyExc_ValueError,
+ "Key can only be None if IPC_EXCL is set");
+ goto error_return;
+ }
+
+ self->op_flags = 0;
+
+ // I mask the caller's flags against the two IPC_* flags to ensure that
+ // nothing funky sneaks into the flags.
+ flags &= (IPC_CREAT | IPC_EXCL);
+
+ // Note that Sys V sems can be in "sets" (arrays) but I hardcode this
+ // to always be a set with just one member.
+ // Permissions and flags (i.e. IPC_CREAT | IPC_EXCL) are both crammed
+ // into the 3rd param.
+ if (key.is_none) {
+ // (key == None) ==> generate a key for the caller
+ do {
+ errno = 0;
+ self->key = get_random_key();
+
+ DPRINTF("Calling semget, key=%ld, mode=%o, flags=%x\n",
+ (long)self->key, mode, flags);
+ self->id = semget(self->key, 1, mode | flags);
+ } while ( (-1 == self->id) && (EEXIST == errno) );
+ }
+ else {
+ // (key != None) ==> use key supplied by the caller
+ self->key = key.value;
+
+ DPRINTF("Calling semget, key=%ld, mode=%o, flags=%x\n",
+ (long)self->key, mode, flags);
+ self->id = semget(self->key, 1, mode | flags);
+ }
+
+ DPRINTF("id == %d\n", self->id);
+
+ if (self->id == -1) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ // Before attempting to set the initial value, I have to be sure that
+ // I created this semaphore and that I have write access to it.
+ if ((flags & IPC_CREX) && (mode & 0200)) {
+ DPRINTF("setting initial value to %d\n", initial_value);
+ arg.val = initial_value;
+
+ if (-1 == semctl(self->id, 0, SETVAL, arg)) {
+ sem_set_error();
+ goto error_return;
+ }
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+Semaphore_P(Semaphore *self, PyObject *args, PyObject *keywords) {
+ return sem_perform_semop(SEMOP_P, self, args, keywords);
+}
+
+
+PyObject *
+Semaphore_acquire(Semaphore *self, PyObject *args, PyObject *keywords) {
+ return Semaphore_P(self, args, keywords);
+}
+
+
+PyObject *
+Semaphore_V(Semaphore *self, PyObject *args, PyObject *keywords) {
+ return sem_perform_semop(SEMOP_V, self, args, keywords);
+}
+
+
+PyObject *
+Semaphore_release(Semaphore *self, PyObject *args, PyObject *keywords) {
+ return Semaphore_V(self, args, keywords);
+}
+
+
+PyObject *
+Semaphore_Z(Semaphore *self, PyObject *args, PyObject *keywords) {
+ return sem_perform_semop(SEMOP_Z, self, args, keywords);
+}
+
+
+PyObject *
+Semaphore_remove(Semaphore *self) {
+ return sem_remove(self->id);
+}
+
+PyObject *
+Semaphore_enter(Semaphore *self) {
+ PyObject *args = PyTuple_New(0);
+ PyObject *retval = NULL;
+
+ if (Semaphore_acquire(self, args, NULL)) {
+ retval = (PyObject *)self;
+ Py_INCREF(self);
+ }
+
+ Py_DECREF(args);
+
+ return retval;
+}
+
+PyObject *
+Semaphore_exit(Semaphore *self, PyObject *args) {
+ PyObject *release_args = PyTuple_New(0);
+ PyObject *retval = NULL;
+
+ DPRINTF("exiting context and releasing semaphore %s\n", self->key);
+
+ retval = Semaphore_release(self, release_args, NULL);
+
+ Py_DECREF(release_args);
+
+ return retval;
+}
+
+PyObject *
+sem_get_key(Semaphore *self) {
+ return KEY_T_TO_PY(self->key);
+}
+
+PyObject *
+sem_get_value(Semaphore *self) {
+ return sem_get_semctl_value(self->id, GETVAL);
+}
+
+
+int
+sem_set_value(Semaphore *self, PyObject *py_value)
+{
+ union semun arg;
+ long value;
+
+#if PY_MAJOR_VERSION > 2
+ if (!PyLong_Check(py_value))
+#else
+ if (!PyInt_Check(py_value))
+#endif
+ {
+ PyErr_Format(PyExc_TypeError, "Attribute 'value' must be an integer");
+ goto error_return;
+ }
+
+#if PY_MAJOR_VERSION > 2
+ value = PyLong_AsLong(py_value);
+#else
+ value = PyInt_AsLong(py_value);
+#endif
+
+ DPRINTF("C value is %ld\n", value);
+
+ if ((-1 == value) && PyErr_Occurred()) {
+ // No idea wht could cause this -- just raise it to the caller.
+ goto error_return;
+ }
+
+ if ((value < 0) || (value > SEMAPHORE_VALUE_MAX)) {
+ PyErr_Format(PyExc_ValueError,
+ "Attribute 'value' must be between 0 and %ld (SEMAPHORE_VALUE_MAX)",
+ (long)SEMAPHORE_VALUE_MAX);
+ goto error_return;
+ }
+
+ arg.val = value;
+
+ if (-1 == semctl(self->id, 0, SETVAL, arg)) {
+ sem_set_error();
+ goto error_return;
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+sem_get_block(Semaphore *self) {
+ DPRINTF("op_flags: %x\n", self->op_flags);
+ return PyBool_FromLong( (self->op_flags & IPC_NOWAIT) ? 0 : 1);
+}
+
+
+int
+sem_set_block(Semaphore *self, PyObject *py_value)
+{
+ DPRINTF("op_flags before: %x\n", self->op_flags);
+
+ if (PyObject_IsTrue(py_value))
+ self->op_flags &= ~IPC_NOWAIT;
+ else
+ self->op_flags |= IPC_NOWAIT;
+
+ DPRINTF("op_flags after: %x\n", self->op_flags);
+
+ return 0;
+}
+
+
+PyObject *
+sem_get_mode(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_IPC_PERM_MODE);
+}
+
+
+int
+sem_set_mode(Semaphore *self, PyObject *py_value) {
+ return sem_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_MODE, py_value);
+}
+
+
+PyObject *
+sem_get_undo(Semaphore *self) {
+ return PyBool_FromLong( (self->op_flags & SEM_UNDO) ? 1 : 0 );
+}
+
+
+int
+sem_set_undo(Semaphore *self, PyObject *py_value)
+{
+ DPRINTF("op_flags before: %x\n", self->op_flags);
+
+ if (PyObject_IsTrue(py_value))
+ self->op_flags |= SEM_UNDO;
+ else
+ self->op_flags &= ~SEM_UNDO;
+
+ DPRINTF("op_flags after: %x\n", self->op_flags);
+
+ return 0;
+}
+
+
+PyObject *
+sem_get_uid(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_IPC_PERM_UID);
+}
+
+
+int
+sem_set_uid(Semaphore *self, PyObject *py_value) {
+ return sem_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_UID, py_value);
+}
+
+
+PyObject *
+sem_get_gid(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_IPC_PERM_GID);
+}
+
+
+int
+sem_set_gid(Semaphore *self, PyObject *py_value) {
+ return sem_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_GID, py_value);
+}
+
+PyObject *
+sem_get_c_uid(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_IPC_PERM_CUID);
+}
+
+PyObject *
+sem_get_c_gid(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_IPC_PERM_CGID);
+}
+
+PyObject *
+sem_get_last_pid(Semaphore *self) {
+ return sem_get_semctl_value(self->id, GETPID);
+}
+
+PyObject *
+sem_get_waiting_for_nonzero(Semaphore *self) {
+ return sem_get_semctl_value(self->id, GETNCNT);
+}
+
+PyObject *
+sem_get_waiting_for_zero(Semaphore *self) {
+ return sem_get_semctl_value(self->id, GETZCNT);
+}
+
+PyObject *
+sem_get_o_time(Semaphore *self) {
+ return sem_get_ipc_perm_value(self->id, SVIFP_SEM_OTIME);
+}
+