#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); }