summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2014-09-12 14:16:22 +0000
committer <>2014-10-24 11:04:40 +0000
commita77e3a63f004e6ee789fa05e4a5bbc333b1529f1 (patch)
treeefe8a68996c19b7b0765ebc689937721d1d99cf7
downloadsysv-ipc-tarball-a77e3a63f004e6ee789fa05e4a5bbc333b1529f1.tar.gz
Imported from /home/lorry/working-area/delta_python-packages_sysv-ipc-tarball/sysv_ipc-0.6.8.tar.gz.HEADsysv_ipc-0.6.8master
-rw-r--r--INSTALL4
-rw-r--r--LICENSE24
-rw-r--r--PKG-INFO37
-rw-r--r--README12
-rw-r--r--ReadMe.html1505
-rw-r--r--VERSION3
-rw-r--r--common.c100
-rw-r--r--common.h212
-rw-r--r--demo/ReadMe.txt54
-rw-r--r--demo/SampleIpcConversation.pngbin0 -> 11909 bytes
-rw-r--r--demo/cleanup.py29
-rw-r--r--demo/conclusion.c139
-rwxr-xr-xdemo/conclusion.py72
-rwxr-xr-xdemo/make_all.sh7
-rw-r--r--demo/md5.c381
-rw-r--r--demo/md5.h91
-rw-r--r--demo/params.txt18
-rw-r--r--demo/premise.c211
-rwxr-xr-xdemo/premise.py106
-rw-r--r--demo/utils.c128
-rw-r--r--demo/utils.h17
-rw-r--r--demo/utils.py64
-rw-r--r--demo/utils_for_2.py4
-rw-r--r--demo/utils_for_3.py4
-rw-r--r--demo2/ReadMe.txt41
-rw-r--r--demo2/SampleIpcConversation.pngbin0 -> 11909 bytes
-rwxr-xr-xdemo2/cleanup.py17
-rw-r--r--demo2/conclusion.py66
-rw-r--r--demo2/params.txt4
-rw-r--r--demo2/premise.py71
-rw-r--r--demo2/utils.py41
-rw-r--r--demo2/utils_for_2.py4
-rw-r--r--demo2/utils_for_3.py4
-rw-r--r--demo4/ReadMe.txt15
-rw-r--r--demo4/child.py23
-rw-r--r--demo4/parent.py46
-rw-r--r--ftok_experiment.py50
-rw-r--r--memory.c834
-rw-r--r--memory.h57
-rw-r--r--mq.c667
-rw-r--r--mq.h79
-rw-r--r--prober.py186
-rw-r--r--prober/probe_page_size.c22
-rw-r--r--prober/semtimedop_test.c11
-rw-r--r--prober/sniff_union_semun_defined.c12
-rw-r--r--semaphore.c776
-rw-r--r--semaphore.h53
-rw-r--r--setup.py62
-rw-r--r--sysv_ipc_module.c876
49 files changed, 7239 insertions, 0 deletions
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..95eef8f
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,4 @@
+To install, just use the normal setup.py routine:
+
+sudo python setup.py install
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c914ace
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2008, Philip Semanchuk
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of sysv_ipc nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY Philip Semanchuk ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Philip Semanchuk BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..f11ee5f
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,37 @@
+Metadata-Version: 1.1
+Name: sysv_ipc
+Version: 0.6.8
+Summary: System V IPC primitives (semaphores, shared memory and message queues) for Python
+Home-page: http://semanchuk.com/philip/sysv_ipc/
+Author: Philip Semanchuk
+Author-email: philip@semanchuk.com
+License: http://creativecommons.org/licenses/BSD/
+Download-URL: http://semanchuk.com/philip/sysv_ipc/sysv_ipc-0.6.8.tar.gz
+Description: Sysv_ipc gives Python programs access to System V semaphores, shared memory
+ and message queues. Most (all?) Unixes (including OS X) support System V IPC.
+ Windows+Cygwin 1.7 might also work.
+
+ Sample code is included.
+
+ sysv_ipc is free software (free as in speech and free as in beer) released
+ under a 3-clause BSD license. Complete licensing information is available in
+ the LICENSE file.
+
+ You might also be interested in the similar POSIX IPC module at:
+ http://semanchuk.com/philip/posix_ipc/
+
+Keywords: ipc inter-process communication semaphore shared memory shm message queue
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX :: BSD :: FreeBSD
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: POSIX :: SunOS/Solaris
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Unix
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Utilities
diff --git a/README b/README
new file mode 100644
index 0000000..48e9ee8
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+Sysv_ipc gives Python programs access to System V semaphores, shared memory
+and message queues. Most (all?) Unixes (including OS X) support System V IPC.
+Windows+Cygwin 1.7 might also work.
+
+Sample code is included.
+
+sysv_ipc is free software (free as in speech and free as in beer) released
+under a 3-clause BSD license. Complete licensing information is available in
+the LICENSE file.
+
+You might also be interested in the similar POSIX IPC module at:
+http://semanchuk.com/philip/posix_ipc/
diff --git a/ReadMe.html b/ReadMe.html
new file mode 100644
index 0000000..686eee6
--- /dev/null
+++ b/ReadMe.html
@@ -0,0 +1,1505 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+
+<html>
+
+<head>
+ <meta name="author" content="Philip Semanchuk">
+ <meta name="copyright" content="All contents &copy; 2014 Philip Semanchuk">
+ <meta name="keywords" content="python sysv system v ipc semaphore shared memory">
+
+ <title>System V IPC for Python - Semaphores, Shared Memory and Message Queues</title>
+
+ <style type="text/css">
+ dt {
+ font-family: monospace;
+ font-weight: bold;
+ padding-bottom: .33em;
+ margin-top: 1em;
+ }
+ span[lang] { font-style: italic; }
+
+ span.param {
+ font-family: monospace;
+ font-style: italic;
+ }
+
+ span.book_title {
+ text-decoration: underline;
+ }
+
+ pre { margin-left: 2em; }
+
+ li { margin-top: 1em; margin-bottom: 1em; }
+
+ form {
+ width: 20em;
+ float: right;
+ font-size: 90%;
+ margin-left: 1em;
+ margin-bottom: 1em;
+ float: right;
+ }
+ fieldset, legend {
+ background-color: #d0d0a9;
+ }
+ /* This style is only present on the local version of the readme.
+ In the online version, the RSS feed is displayed. */
+ div.rss { display: none; }
+ </style>
+</head>
+
+<body>
+
+<h2>System V IPC for Python - Semaphores, Shared Memory and Message Queues</h2>
+
+<div class="rss">
+ <a href="rss.xml"><img src="/common/images/rss.png" width="28" height="28" alt=""></a>
+ <br><a href="rss.xml">RSS</a>
+</div>
+
+<p>This describes the <tt>sysv_ipc</tt> module which gives Python access
+to System V inter-process semaphores, shared memory and message queues
+on most (all?) *nix flavors. Examples include OS X, Linux, FreeBSD,
+OpenSolaris 2008.11,
+and <a href="http://f-utility.tumblr.com/post/525726296/compiling-sysv-ipc-python-module-with-gcc-on-aix-5-2">AIX 5.2</a>.
+It might also work under Windows with a library like
+<a href="http://www.cygwin.com/">Cygwin</a>.
+</p>
+
+<p>It works with Python 2.4 &ndash; 3.x.
+It's released
+under a <a href="http://creativecommons.org/licenses/BSD/">BSD license</a>.
+</p>
+
+<p>You can <strong>download
+<a href="sysv_ipc-0.6.8.tar.gz">sysv_ipc version 0.6.8</a>
+</strong>
+(<a href="sysv_ipc-0.6.8.md5.txt">[md5 sum]</a>,
+<a href="sysv_ipc-0.6.8.sha1.txt">[sha1 sum]</a>)
+
+which contains the source code, setup.py, installation instructions and
+<a href="#samples">sample code</a>. You can read about
+<a href="#current">all of the changes in this version</a>.
+</p>
+
+<p>
+You might also want to read
+about some <a href="#bugs">known bugs</a>.
+</p>
+
+<p>You might be interested in the very similar module
+<a href="/philip/posix_ipc/"><tt>posix_ipc</tt></a>
+which provides Python access to POSIX IPC primitives. POSIX IPC is a little
+easier to use than SysV IPC, but not all operating systems support it
+completely.
+</p>
+
+<h2>Module <tt>sysv_ipc</tt></h2>
+
+<p>Jump to <a href="#semaphore">semaphores</a>,
+<a href="#shared_memory">shared memory</a>, or
+<a href="#message_queue">message queues</a>.</p>
+
+
+<h3>Module Functions</h3>
+
+<dl>
+ <dt>attach(id, [address = None, [flags = 0]])</dt>
+ <dd>Attaches the (existing) shared memory that has the given <tt>id</tt> and
+ returns a new SharedMemory object. See
+ <a href="#attach">SharedMemory.attach()</a> for details on the
+ <tt>address</tt> and <tt>flags</tt> parameters.
+
+ <p>This method is useful only under fairly unusual circumstances.
+ You probably don't need it.
+ </p>
+ </dd>
+
+ <dt>ftok(path, id, [silence_warning = False])</dt>
+ <dd>Calls <tt>ftok(path, id)</tt>. Note that
+ <a href="#ftok_weakness"><tt>ftok()</tt> has limitations</a>, and this
+ function will issue a warning to that effect unless
+ <tt>silence_warning</tt> is True.
+ </dd>
+
+ <dt>remove_semaphore(id)</dt>
+ <dd>Removes the semaphore with the given <tt>id</tt>.</dd>
+
+ <dt>remove_shared_memory(id)</dt>
+ <dd>Removes the shared memory with the given <tt>id</tt>.</dd>
+
+ <dt>remove_message_queue(id)</dt>
+ <dd>Removes the message queue with the given <tt>id</tt>.</dd>
+</dl>
+
+
+<h3>Module Constants</h3>
+
+<dl>
+ <dt>IPC_CREAT, IPC_EXCL and IPC_CREX</dt>
+ <dd><tt>IPC_CREAT</tt> and <tt>IPC_EXCL</tt> are flags used when
+ creating IPC objects. They're
+ bitwise unique and can be ORed together. <tt>IPC_CREX</tt> is
+ shorthand for <tt>IPC_CREAT | IPC_EXCL</tt>.
+
+ <p>When passed to an IPC object's constructor, <tt>IPC_CREAT</tt> indicates
+ that you want to create a new object or open an existing one. If you want
+ the call to fail if an object with that key already exists, specify
+ the <tt>IPC_EXCL</tt> flag, too.
+ </p>
+ </dd>
+
+ <dt>IPC_PRIVATE</dt>
+ <dd>This is a special value that can be passed in place of a key. It implies that
+ the IPC object should be available only to the creating process or its
+ child processes (e.g. those created with <tt>fork()</tt>).
+ </dd>
+
+ <dt>KEY_MIN and KEY_MAX</dt>
+ <dd>Denote the range of keys that this module accepts. Your OS might
+ limit keys to a smaller range depending on the typedef of
+ <tt>key_t</tt>.
+
+ <p>Keys randomly generated by this module are in the range
+ <tt>1 &lte; key &lte; SHRT_MAX</tt>.
+ That's type-safe unless your OS has a very bizarre
+ definition of <tt>key_t</tt>.
+ </p>
+ </dd>
+
+ <dt>SEMAPHORE_VALUE_MAX</dt>
+ <dd>The maximum value of a semaphore.
+ </dd>
+
+ <dt>PAGE_SIZE</dt>
+ <dd>The operating system's memory page size, in bytes. It's probably a good
+ idea to make shared memory segments some multiple of this size.
+ </dd>
+
+ <dt>SEMAPHORE_TIMEOUT_SUPPORTED</dt>
+ <dd>True if the platform supports timed semaphore waits, False otherwise.</dd>
+
+ <dt>SHM_RND</dt>
+ <dd>You probably don't need this, but it can be used when attaching shared
+ memory to force the address to be
+ rounded down to SHMLBA. See your system's man page for <tt>shmat()</tt>
+ for more information.
+ </dd>
+
+ <dt>SHM_HUGETLB, SHM_NORESERVE and SHM_REMAP</dt>
+ <dd>You probably don't need these. They're Linux-specific flags that can
+ be passed to the SharedMemory
+ constructor, or to the <tt>.attach()</tt> function in the case of
+ SHM_REMAP. See your system's man page for <tt>shmget()</tt>
+ and <tt>shmat()</tt> for more information.
+ </dd>
+</dl>
+
+<h3>Module Errors</h3>
+
+<p>In addition to standard Python errors (e.g. <tt>ValueError</tt>),
+this module raises custom errors. These errors cover
+situations specific to IPC.
+</p>
+
+<dl>
+ <dt>Error</dt>
+ <dd>The base error class for all the custom errors in this module. This
+ error is occasionally raised on its own but you'll almost
+ always see a more specific error.
+ </dd>
+
+ <dt>InternalError</dt>
+ <dd>Indicates that something has gone very wrong in the module code. Please
+ report this to the maintainer.
+ </dd>
+
+ <dt>PermissionsError</dt>
+ <dd>Indicates that you've attempted something that the permissions on the
+ IPC object don't allow.
+ </dd>
+
+ <dt>ExistentialError</dt>
+ <dd>Indicates an error related to the existence or non-existence of
+ an IPC object.
+ </dd>
+
+ <dt>BusyError</dt>
+ <dd>Raised when a semaphore call to <tt>.P()</tt> or <tt>.Z()</tt> either times out
+ or would be forced to wait when its <tt>block</tt> attribute is False.
+ </dd>
+
+ <dt>NotAttachedError</dt>
+ <dd>Raised when a process attempts to read from or write to a shared memory
+ segment to which it is not attached.
+ </dd>
+</dl>
+
+
+<h3 id="semaphore">The Semaphore Class</h3>
+
+<p>This is a handle to a semaphore.</p>
+
+<h4>Methods</h4>
+
+<dl>
+ <dt>Semaphore(key, [flags = 0, [mode = 0600, [initial_value = 0]]])</dt>
+ <dd>Creates a new semaphore or opens an existing one.
+
+ <p><span class="param">key</span> must be <tt>None</tt>,
+ <tt>IPC_PRIVATE</tt> or
+ an integer &gt; <tt>KEY_MIN</tt> and &le; <tt>KEY_MAX</tt>. If the key
+ is <tt>None</tt>, the module chooses a random unused key.
+ </p>
+
+ <p>The <span class="param">flags</span> specify whether you want to create a
+ new semaphore or open an existing one.
+ </p>
+
+ <ul>
+ <li>With <span class="param">flags</span> set to the <strong>default</strong> of <tt>0</tt>, the module attempts
+ to <strong>open an existing</strong> semaphore identified by <span class="param">key</span> and raises
+ a <tt>ExistentialError</tt> if that semaphore doesn't exist.
+ </li>
+
+ <li>With <span class="param">flags</span> set to <tt>IPC_CREAT</tt>, the module
+ <strong>opens</strong> the semaphore identified by
+ <span class="param">key</span> <strong>or creates</strong> a new
+ one if no such semaphore exists. Using <tt>IPC_CREAT</tt> by itself
+ is not recommended. (See <a href="#sem_init">Semaphore Initialization</a>.)
+ </li>
+
+ <li>With <span class="param">flags</span> set to
+ <tt>IPC_CREX</tt> (<tt>IPC_CREAT | IPC_EXCL</tt>),
+ the module
+ <strong>creates a new semaphore</strong> identified by <span class="param">key</span>. If a
+ semaphore with that key already exists, the call raises an
+ <tt>ExistentialError</tt>.
+ <strong>The <span class="param">initial_value</span> is ignored unless
+ both of these flags are specified</strong> or
+ if the semaphore is read-only.
+ </li>
+ </ul>
+
+ <p>When opening an existing semaphore, <span class="param">mode</span> is ignored.
+ </p>
+ </dd>
+
+ <dt>acquire([timeout = None, [delta = 1]])</dt>
+ <dd>Waits (conditionally) until the semaphore's value is &gt; 0 and then
+ returns, decrementing the semaphore.
+
+ <p>The <span class="param">timeout</span> (which can be a float) specifies how
+ many seconds this call should wait, if at all.
+ </p>
+ <p>The semantics of the timeout <a href="#v0_3">changed a little in
+ version 0.3</a>.
+ </p>
+
+ <ul>
+ <li>A <span class="param">timeout</span> of None (the default)
+ implies no time limit. The call will not return until its wait
+ condition is satisfied.
+ </li>
+
+ <li>When <span class="param">timeout</span> is 0, the call
+ raises a <tt>BusyError</tt> if it can't immediately
+ acquire the semaphore. Since it will
+ return immediately if <em>not</em> asked to wait, this can be
+ thought of as "non-blocking" mode.
+ </li>
+
+ <li>When the <span class="param">timeout</span> is &gt; 0, the call
+ will wait no longer than <span class="param">timeout</span>
+ seconds before either returning (having acquired the semaphore)
+ or raising a <tt>BusyError</tt>.
+ </li>
+ </ul>
+
+ <p>When the call returns, the semaphore's value decreases by
+ <span class="param">delta</span>
+ (or more precisely, <tt>abs(<span class="param">delta</span>)</tt>)
+ which defaults to 1.
+ </p>
+
+ <p>On platforms that don't support the <tt>semtimedop()</tt> API call,
+ all timeouts (including zero) are treated as infinite. The call
+ will not return until its wait condition is satisfied.
+ </p>
+
+ <p>Most platforms provide <tt>semtimedop()</tt>. OS X is a
+ notable exception. The module's Boolean constant
+ <tt>SEMAPHORE_TIMEOUT_SUPPORTED</tt>
+ is True on platforms that support <tt>semtimedop()</tt>.
+ </p>
+ </dd>
+
+
+ <dt>release([delta = 1])</dt>
+ <dd>
+ Releases (increments) the semaphore.
+
+ <p>The semaphore's value increases by <span class="param">delta</span>
+ (or more precisely, <tt>abs(<span class="param">delta</span>)</tt>)
+ which defaults to 1.
+ </p>
+ </dd>
+
+ <dt>P()</dt>
+ <dd>A synonym for <tt>.acquire()</tt> that takes the same parameters.
+
+ <p>"P" stands for
+ <span lang="nl">prolaag</span> or <span lang="nl">probeer te verlagen</span>
+ (try to decrease), the original name given by
+ <a href="http://en.wikipedia.org/wiki/Semaphore_(programming)">Edsger Dijkstra</a>.
+ </p>
+ </dd>
+
+ <dt>V()</dt>
+ <dd>A synonym for <tt>.release()</tt> that takes the same parameters.
+
+ <p>"V" stands for
+ <span lang="nl">verhoog</span> (increase), the original name given by
+ <a href="http://en.wikipedia.org/wiki/Semaphore_(programming)">Edsger Dijkstra</a>.
+ </p>
+ </dd>
+
+ <dt>Z([timeout = None])</dt>
+ <dd>Blocks until zee zemaphore is zero.
+
+ <p><span class="param">Timeout</span> has
+ the same meaning as described in <tt>.acquire()</tt>.
+ </p>
+ </dd>
+
+ <dt>remove()</dt>
+ <dd>
+ Removes (deletes) the semaphore from the system.
+
+ <p>As far as I can tell, the effect of deleting a semaphore that
+ other processes are still using is OS-dependent. Check your system's
+ man pages for <tt>semctl(IPC_RMID)</tt>.
+ </p>
+ </dd>
+</dl>
+
+<h4>Attributes</h4>
+
+<dl>
+ <dt>key (read-only)</dt>
+ <dd>The key passed in the call to the constructor.</dd>
+
+ <dt>id (read-only)</dt>
+ <dd>The id assigned to this semaphore by the OS.</dd>
+
+ <dt>value</dt>
+ <dd>The integer value of the semaphore.</dd>
+
+ <dt>undo</dt>
+ <dd>Defaults to False.
+
+ <p>When True, operations that change the
+ semaphore's value will be undone (reversed) when
+ the process exits. Note that when a process exits, an undo operation
+ may imply that a semaphore's value should become negative or
+ exceed its maximum.
+ Behavior in this case is system-dependent, which means that
+ <strong>using this flag can make your code non-portable</strong>.
+ </p>
+ </dd>
+
+ <dt>block</dt>
+ <dd>
+ Defaults to True, which means that calls to <tt>acquire()</tt> and
+ <tt>release()</tt> will not return
+ until their wait conditions are satisfied.
+
+ <p>When False, these calls
+ will not block but will instead raise an error if they are unable
+ to return immediately.
+ </p>
+ </dd>
+
+ <dt>mode</dt>
+ <dd>The semaphore's permission bits.
+
+ <p>Tip: the following Python code will display
+ the mode in octal:<br>
+ <tt>print int(str(my_sem.mode), 8)</tt>
+ </p>
+ </dd>
+
+ <dt>uid</dt>
+ <dd>The semaphore's user id.</dd>
+
+ <dt>gid</dt>
+ <dd>The semaphore's group id.</dd>
+
+ <dt>cuid (read-only)</dt>
+ <dd>The semaphore creator's user id.</dd>
+
+ <dt>cgid (read-only)</dt>
+ <dd>The semaphore creator's group id.</dd>
+
+ <dt>last_pid (read-only)</dt>
+ <dd>The PID of the process that last called <tt>semop()</tt> (<tt>.P()</tt>,
+ <tt>.V()</tt> or <tt>.Z()</tt>) on this semaphore.
+ </dd>
+
+ <dt>waiting_for_nonzero (read-only)</dt>
+ <dd>The number of processes waiting for the value of the semaphore to become
+ non-zero (i.e. the number waiting in a call to <tt>.P()</tt>).
+ </dd>
+
+ <dt>waiting_for_zero (read-only)</dt>
+ <dd>The number of processes waiting for the value of the semaphore to become
+ zero (i.e. the number waiting in a call to <tt>.Z()</tt>).
+ </dd>
+
+ <dt>o_time (read-only)</dt>
+ <dd>The last time <tt>semop()</tt> (i.e. <tt>.P()</tt>, <tt>.V()</tt> or
+ <tt>.Z()</tt>) was called on this semaphore.
+ </dd>
+</dl>
+
+<h4>Context Manager Support</h4>
+
+<p>These semaphores provide <tt>__enter__()</tt> and <tt>__exit__()</tt>
+methods so they can be used in context managers. For instance --
+</p>
+
+<pre>
+with sysv_ipc.Semaphore(name) as sem:
+ # Do something...
+</pre>
+
+<p>Entering the context acquires the semaphore, exiting the context releases
+ the semaphore. See <tt>demo4/child.py</tt> for a complete example.
+</p>
+
+
+<h3 id="shared_memory">The SharedMemory Class</h3>
+
+<p>This is a handle to a shared memory segment.
+</p>
+
+
+<h4>Methods</h4>
+
+<dl>
+ <dt>SharedMemory(key, [flags = 0, [mode = 0600, [size = 0 or PAGE_SIZE, [init_character = ' ']]]])</dt>
+ <dd>Creates a new shared memory segment or opens an existing one.
+ The memory is automatically attached.
+
+ <p><span class="param">key</span> must be <tt>None</tt>,
+ <tt>IPC_PRIVATE</tt> or
+ an integer &gt; <tt>0</tt> and &le; <tt>KEY_MAX</tt>. If the key
+ is <tt>None</tt>, the module chooses a random unused key.
+ </p>
+
+ <p>The <span class="param">flags</span> specify whether you want to create a
+ new shared memory segment or open an existing one.
+ </p>
+
+ <ul>
+ <li>With <span class="param">flags</span> set to the
+ <strong>default</strong> of <tt>0</tt>, the module attempts
+ to <strong>open an existing</strong> shared memory segment identified by
+ <span class="param">key</span> and raises
+ a <tt>ExistentialError</tt> if it doesn't exist.
+ </li>
+
+ <li>With <span class="param">flags</span> set to <strong><tt>IPC_CREAT</tt></strong>, the module
+ <strong>opens</strong> the shared memory segment identified
+ by <span class="param">key</span> <strong>or
+ creates</strong> a new one if no such segment exists.
+ Using <tt>IPC_CREAT</tt> by itself
+ is not recommended. (See <a href="#mem_init">Memory Initialization</a>.)
+ </li>
+
+ <li>With <span class="param">flags</span> set to
+ <strong><tt>IPC_CREX</tt></strong> (<tt>IPC_CREAT | IPC_EXCL</tt>),
+ the module
+ <strong>creates</strong> a new shared memory segment identified by
+ <span class="param">key</span>. If
+ a segment with that key already exists, the call raises
+ a <tt>ExistentialError</tt>.
+
+ <p>When both <tt>IPC_CREX</tt> is specified
+ and the caller has write permission, each byte in the new memory segment will be
+ initialized to the value of <span class="param">init_character</span>.
+ </p>
+ </li>
+ </ul>
+
+ <p>The value of <span class="param">size</span> depends on whether
+ one is opening an existing segment or creating a new one.
+ </p>
+ <ul>
+ <li>When opening an existing segment, <span class="param">size</span>
+ must be ≤ the existing segment's size. Zero is
+ always valid.
+ </li>
+
+ <li>When creating an new segment,
+ many (most? all?) operating systems insist on a <span class="param">size</span>
+ &gt; <tt>0</tt>.
+ In addition, some round the size
+ up to the next multiple of PAGE_SIZE.
+ </li>
+ </ul>
+
+ <p>This module supplies a default
+ <span class="param">size</span> of <tt>PAGE_SIZE</tt> when
+ <tt>IPC_CREX</tt> is specified and <tt>0</tt> otherwise.
+ </p>
+ </dd>
+
+ <dt id="attach">attach([address = None, [flags = 0]])</dt>
+ <dd>
+ Attaches this process to the shared memory. The memory must be attached
+ before calling <tt>.read()</tt> or <tt>.write()</tt>. Note that the
+ constructor automatically attaches the memory
+ so you won't need to call this method unless you explicitly detach it
+ and then want to use it again.
+
+ <p>The address parameter allows one to specify (as a Python long) a memory
+ address at which to attach the segment. Passing None (the default)
+ is equivalent to passing NULL to <tt>shmat()</tt>. See that
+ function's man page for details.
+ </p>
+
+ <p>The flags are mostly only relevant if one specifies a specific address.
+ One exception is the flag <tt>SHM_RDONLY</tt> which, surprisingly,
+ attaches the segment read-only.
+ </p>
+
+ <p>Note that on some (and perhaps all) platforms, each call to <tt>.attach()</tt>
+ increments the system's "attached" count. Thus, if each call to
+ <tt>.attach()</tt> isn't paired with a call to <tt>.detach()</tt>,
+ the system's "attached" count for the shared memory segment will not
+ go to zero when the process exits. As a result, the shared memory
+ segment may not disappear even when its creator calls <tt>.remove()</tt>
+ and exits.
+ </p>
+ </dd>
+
+ <dt>detach()</dt>
+ <dd>Detaches this process from the shared memory.</dd>
+
+ <dt>read([byte_count = 0, [offset = 0]])</dt>
+ <dd>Reads up to <span class="param">byte_count</span> bytes from the
+ shared memory segment starting at <span class="param">offset</span>
+ and returns them as a string under Python 2 or as a bytes object
+ under Python 3.
+
+ <p>If <span class="param">byte_count</span> is zero (the default) the
+ entire buffer is returned.
+ </p>
+
+ <p>This method will never attempt to read past the end of the shared
+ memory segment, even when
+ <span class="param">offset</span> + <span class="param">byte_count</span>
+ exceeds the memory segment's size. In that case, the bytes
+ from <span class="param">offset</span> to the end of the segment are returned.
+ </p>
+ </dd>
+
+ <dt>write(s, [offset = 0])</dt>
+ <dd>Writes the string <span class="param">s</span> to the shared memory,
+ starting at <span class="param">offset</span>.
+
+ <p>At most <tt><i>n</i></tt> bytes will be written, where
+ <tt><i>n</i></tt> = the segment's size minus <span class="param">offset</span>.
+ </p>
+
+ <p>The string may contain embedded NULL bytes ('\0').
+ </dd>
+
+ <dt>remove()</dt>
+ <dd>Removes (destroys) the shared memory. Note that actual destruction of the
+ segment only occurs when all processes have detached.
+ </dd>
+
+</dl>
+
+<h4>Attributes</h4>
+
+<dl>
+ <dt>key (read-only)</dt>
+ <dd>The key provided in the constructor.</dd>
+
+ <dt>id (read-only)</dt>
+ <dd>The id assigned to this semaphore by the OS.</dd>
+
+ <dt>size (read-only)</dt>
+ <dd>The size of the segment in bytes.</dd>
+
+ <dt>address (read-only)</dt>
+ <dd>The address of the segment as Python long.</dd>
+
+ <dt>attached (read-only)</dt>
+ <dd>If True, this segment is currently attached.</dd>
+
+ <dt>last_attach_time (read-only)</dt>
+ <dd>The last time a process attached this segment.</dd>
+
+ <dt>last_detach_time (read-only)</dt>
+ <dd>The last time a process detached this segment.</dd>
+
+ <dt>last_change_time (read-only)</dt>
+ <dd>The last time a process changed the uid, gid or mode on this segment.</dd>
+
+ <dt>creator_pid (read-only)</dt>
+ <dd>The PID of the process that created this segment.</dd>
+
+ <dt>last_pid (read-only)</dt>
+ <dd>The PID of the most last process to attach or detach this segment.</dd>
+
+ <dt>number_attached (read-only)</dt>
+ <dd>The number of processes attached to this segment.</dd>
+
+ <dt>uid</dt>
+ <dd>The segment's user id.</dd>
+
+ <dt>gid</dt>
+ <dd>The segment's group id.</dd>
+
+ <dt>mode</dt>
+ <dd>The shared memory's permission bits.
+
+ <p>Tip: the following Python code will display
+ the mode in octal:<br>
+ <tt>print int(str(my_mem.mode), 8)</tt>
+ </p>
+ </dd>
+
+ <dt>cuid (read-only)</dt>
+ <dd>The segment creator's user id.</dd>
+
+ <dt>cgid (read-only)</dt>
+ <dd>The segment creator's group id.</dd>
+</dl>
+
+
+<h3 id="message_queue">The MessageQueue Class</h3>
+
+<p>This is a handle to a message queue.</p>
+
+<h4>Methods</h4>
+
+<dl>
+ <dt>MessageQueue(key, [flags = 0, [mode = 0600, [max_message_size = 2048]]])</dt>
+ <dd>Creates a new message queue or opens an existing one.
+
+ <p><span class="param">key</span> must be <tt>None</tt>,
+ <tt>IPC_PRIVATE</tt> or
+ an integer &gt; <tt>0</tt> and &le; <tt>KEY_MAX</tt>. If the key
+ is <tt>None</tt>, the module chooses a random unused key.
+ </p>
+
+ <p>The <span class="param">flags</span> specify whether you want to create a
+ new queue or open an existing one.
+ </p>
+
+ <ul>
+ <li>With <span class="param">flags</span> set to the
+ <strong>default</strong> of <tt>0</tt>, the module attempts
+ to <strong>open an existing</strong> message queue identified by
+ <span class="param">key</span> and raises
+ a <tt>ExistentialError</tt> if it doesn't exist.
+ </li>
+
+ <li>With <span class="param">flags</span> set to <strong><tt>IPC_CREAT</tt></strong>, the module
+ <strong>opens</strong> the message queue identified
+ by <span class="param">key</span> <strong>or
+ creates</strong> a new one if no such queue exists.
+ </li>
+
+ <li>With <span class="param">flags</span> set to
+ <strong><tt>IPC_CREX</tt></strong> (<tt>IPC_CREAT | IPC_EXCL</tt>),
+ the module
+ <strong>creates</strong> a new message queue identified by
+ <span class="param">key</span>. If
+ a queue with that key already exists, the call raises
+ a <tt>ExistentialError</tt>.
+ </li>
+ </ul>
+
+ <p>The <span class="param">max_message_size</span> can be increased
+ from the default, but be aware of the issues discussed in
+ <a href="#message_queue_limits">Message Queue Limits</a>.
+ </p>
+ </dd>
+
+ <dt>send(message, [block = True, [type = 1]])</dt>
+ <dd>Puts a message on the queue.
+
+ <p>The <span class="param">message</span> string can contain embedded
+ NULLs (ASCII <tt>0x00</tt>).
+ </p>
+
+ <p>The <span class="param">block</span> flag specifies whether or
+ not the call should wait if the message can't be sent (if, for
+ example, the queue is full). When <span class="param">block</span>
+ is <tt>False</tt>, the call will raise a <tt>BusyError</tt> if
+ the message can't be sent immediately.
+ </p>
+
+ <p>The <span class="param">type</span> is
+ associated with the message and is relevant when calling
+ <tt>receive()</tt>. It must be &gt; 0.
+ </p>
+ </dd>
+
+ <dt>receive([block = True, [type = 0]])</dt>
+ <dd>
+ Receives a message from the queue, returning a tuple of
+ <tt>(message, type)</tt>. Under Python 3, the message is a
+ bytes object.
+
+ <p>The <span class="param">block</span> flag specifies whether or
+ not the call should wait if there's no messages of the
+ specified type to retrieve. When <span class="param">block</span>
+ is <tt>False</tt>, the call will raise a <tt>BusyError</tt> if
+ a message can't be received immediately.
+ </p>
+
+ <p>The <span class="param">type</span> permits some control over
+ which messages are retrieved.
+ </p>
+
+ <ul>
+ <li>When <span class="param">type</span> <tt>== 0</tt>, the call
+ returns the first message on the queue regardless of its
+ type.
+ </li>
+ <li>When <span class="param">type</span> <tt>&gt; 0</tt>, the call
+ returns the first message of that type.
+ </li>
+ <li>When <span class="param">type</span> <tt>&lt; 0</tt>, the call
+ returns the first message of the lowest type that is ≤ the
+ absolute value of <span class="param">type</span>.
+ </li>
+ </ul>
+ </dd>
+
+ <dt>remove()</dt>
+ <dd>Removes (deletes) the message queue.</dd>
+</dl>
+
+<h4>Attributes</h4>
+
+<dl>
+ <dt>key (read-only)</dt>
+ <dd>The key provided in the constructor.</dd>
+
+ <dt>id (read-only)</dt>
+ <dd>The id assigned to this queue by the OS.</dd>
+
+ <dt id="queue_max_size">max_size</dt>
+ <dd>The maximum size of the queue in bytes. Only a process with
+ "appropriate privileges" can increase this value, and on some
+ systems even that won't work. See
+ <a href="#message_queue_limits">Message Queue Limits</a> for details.
+ </dd>
+
+ <dt>last_send_time (read-only)</dt>
+ <dd>The last time a message was placed on the queue.</dd>
+
+ <dt>last_receive_time (read-only)</dt>
+ <dd>The last time a message was received from the queue.</dd>
+
+ <dt>last_change_time (read-only)</dt>
+ <dd>The last time a process changed the queue's attributes.</dd>
+
+ <dt>last_send_pid (read-only)</dt>
+ <dd>The id of the most recent process to send a message.</dd>
+
+ <dt>last_receive_pid (read-only)</dt>
+ <dd>The id of the most recent process to receive a message.</dd>
+
+ <dt>current_messages (read-only)</dt>
+ <dd>The number of messages currently in the queue.</dd>
+
+ <dt>uid</dt>
+ <dd>The queue's user id.</dd>
+
+ <dt>gid</dt>
+ <dd>The queue's group id.</dd>
+
+ <dt>mode</dt>
+ <dd>The queue's permission bits.
+
+ <p>Tip: the following Python code will display
+ the mode in octal:<br>
+ <tt>print int(str(my_mem.mode), 8)</tt>
+ </p>
+ </dd>
+
+ <dt>cuid (read-only)</dt>
+ <dd>The queue creator's user id.</dd>
+
+ <dt>cgid (read-only)</dt>
+ <dd>The queue creator's group id.</dd>
+</dl>
+
+
+
+<h3>Supported Features and Differences from SHM</h3>
+
+<p>This module is almost, but not quite, a superset of
+<a href="http://nikitathespider.com/python/shm/"><tt>shm</tt></a>.
+Some of the additional features are the ability to override the <tt>block</tt>
+flag on a per-call basis, the ability to change the semaphore's value
+in increments &gt; 1 when calling <tt>.P()</tt> and <tt>.V()</tt>
+and exposure of <tt>sem_otime</tt>.
+</p>
+
+<p>Differences that might trip you up are listed below.</p>
+
+<ul>
+ <li><tt>Shm</tt> compiles under Python 2.3 and older; this module has
+ not been tested with Python older than 2.4
+ </li>
+ <li>Attribute names and method signatures are different.</li>
+ <li>This module offers neither the functions <tt>semaphore_haskey()</tt>
+ nor <tt>memory_haskey()</tt>.
+ </li>
+ <li>This module's default permission on objects is <tt>0600</tt> as opposed
+ to <tt>shm</tt>'s <tt>0666</tt>.
+ </li>
+ <li><tt>Shm</tt> maintained an internal dictionary of semaphores and shared memory
+ segments. The object keys served as the dictionary keys.
+ If you asked for the same object multiple times, <tt>shm</tt> would
+ return the same Python object. I'm not convinced this was safe,
+ particularly in the case where an object may have been destroyed
+ and another with the same key created in its place.
+ </li>
+</ul>
+
+<h3>Usage Tips</h3>
+
+<h4>Sample Code</h4>
+
+<p>This module comes with two demonstration apps. The first (in the
+directory <tt>demo</tt>) shows how to use shared memory and semaphores.
+The second (in the directory <tt>demo2</tt>) shows how to use
+message queues.
+
+
+<h4 id="ftok_weakness">The Weakness of <tt>ftok()</tt></h4>
+
+<p>
+Most System V IPC sample code recommends <tt>ftok()</tt> for generating an
+integer key that's more-or-less random.
+It does not, however, guarantee that the key it generates is unused. If
+<tt>ftok()</tt> gives your application a key that some other application is
+already using,
+your app is in trouble unless it has a reliable second mechanism for generating
+a key. And if that's the case, why not just abandon <tt>ftok()</tt> and use the
+second mechanism exclusively?
+</p>
+
+<p>This is the weakness of <tt>ftok()</tt> -- it isn't guaranteed to give you
+what you want. The <a href="http://www.unix.com/man-page/FreeBSD/3/ftok/">BSD
+man page for <tt>ftok</tt></a> says it is "quite possible for the routine to
+return duplicate keys". The term "quite possible" isn't quantified, but suppose
+it means one-tenth of one percent. Who wants to have 1-in-1000 odds of a
+catastrophic failure in their program, or even 1-in-10000?
+</p>
+
+<p>This module obviates the need for <tt>ftok()</tt> by generating random
+keys for you. If your application can't use <tt>sysv_ipc</tt>'s automatically
+generated keys because it needs to know the key in advance, hardcoding a
+random number like 123456 in your app might be no worse than using
+<tt>ftok()</tt> and has the advantage of not hiding its limitations.
+</p>
+
+<p>This module provides <tt>ftok()</tt> in case you want to experiment with it.
+However, to emphasize its weakness, this version of <tt>ftok()</tt> raises a
+warning with every call unless you explicitly pass a flag to silence it.
+</p>
+
+<p>This package also provides <tt>ftok_experiment.py</tt> so that you can observe
+how often <tt>ftok()</tt> generates duplicate keys on your system.
+</p>
+
+
+<h4 id="sem_init">Semaphore Initialization</h4>
+
+<p>When a System V sempahore is created at the C API level, the OS is not required
+to initialize the semaphore's value. (This per
+<a href="http://www.opengroup.org/onlinepubs/009695399/functions/semget.html">the
+SUSv3 standard for <tt>semget()</tt></a>.)
+Some (most? all?) operating systems initialize it to zero, but this behavior
+is non-standard and therefore can't be relied upon.
+</p>
+
+<p>If sempahore creation happens in an predictable, orderly fashion, this isn't a
+problem. But a
+race condition arises when multiple processes vie to create/open the same semaphore. The
+problem lies in the fact that when an application calls <tt>semget()</tt> with only
+the <tt>IPC_CREAT</tt> flag, the caller can't tell whether or not he has
+created a new semaphore or opened an existing one.
+<strong>This makes it
+difficult to create reliable code without using <tt>IPC_EXCL</tt>.</strong>
+W. Richard Stevens' <span class='book_title'>Unix Network Programming Volume 2</span>
+calls this "a fatal flaw in the design of System V semaphores" (p 284).
+</p>
+
+<p>
+For instance, imagine processes P1 and P2. They're executing the same code,
+and that code intends to share a binary semaphore.
+Consider the following sequence of events at the startup of P1 and P2 &ndash;
+</p>
+
+<ol>
+ <li>P1 calls <tt>semget(IPC_CREAT)</tt> to create the semaphore S.</li>
+ <li>P2 calls <tt>semget(IPC_CREAT)</tt> to open S.</li>
+ <li>P1 initializes the semaphore's value to 1.</li>
+ <li>P1 calls <tt>acquire()</tt>, decrementing the value to 0.</li>
+ <li>P2, assuming S is a newly-created semaphore that needs to be initialized,
+ incorrectly sets the semaphore's value to 1.</li>
+ <li>P2 calls <tt>acquire()</tt>, decrementing the value to 0. Both processes
+ now think they own the lock.</li>
+</ol>
+
+<p>W. Richard Stevens' solution for this race condition is to check the value of
+<tt>sem_otime</tt> (an element in the <tt>semid_ds</tt> struct that's
+populated on the call to <tt>semctl(IPC_STAT)</tt> and which is exposed to
+Python by this module) which
+is initialized to zero when the semaphore is created and otherwise holds
+the time of the last
+call to <tt>semop()</tt> (which is called by <tt>P()</tt>/<tt>acquire()</tt>,
+<tt>V()</tt>/<tt>release()</tt>, and <tt>Z()</tt>).
+</p>
+
+<p>In Python, each process would run something like this:
+<pre>
+try:
+ sem = sysv_ipc.Semaphore(42, sysv_ipc.IPC_CREX)
+except sysv_ipc.ExistentialError:
+ # One of my peers created the semaphore already
+ sem = sysv_ipc.Semaphore(42)
+ # Waiting for that peer to do the first acquire or release
+ while not sem.o_time:
+ time.sleep(.1)
+else:
+ # Initializing sem.o_time to nonzero value
+ sem.release()
+# Now the semaphore is safe to use.
+</pre>
+
+
+<h4 id="mem_init">Shared Memory Initialization</h4>
+
+<p>With shared memory,
+using the <tt>IPC_CREAT</tt> flag without <tt>IPC_EXCL</tt>
+is problematic <em>unless you know the size of the segment
+you're potentially opening</em>.
+</p>
+
+<p>Why? Because when creating a new segment,
+many (most? all?) operating systems demand a non-zero size. However,
+when opening an existing segment, zero is the only guaranteed safe value
+(again, assuming one doesn't know the size of the segment in advance).
+Since <tt>IPC_CREAT</tt>
+can open or create a segment, there's no safe value for the size under
+this circumstance.
+</p>
+
+<p>As a (sort of) side note, the
+<a href="http://www.opengroup.org/onlinepubs/009695399/functions/shmget.html">SUSv3
+specification for <tt>shmget()</tt></a> says only that the size of a new
+segment must not be less than "the system-imposed minimum". I
+gather that at one time, some systems set the minimum at zero despite the
+fact that it doesn't make much sense to create a zero-length shared memory
+segment. I think most modern systems do the sensible thing and insist on
+a minimum length of 1.
+</p>
+
+
+<h4 id="message_queue_limits">Message Queue Limits</h4>
+
+<p>Python programmers can usually remain blissfully ignorant of memory
+allocation issues. Unfortunately, a combination of factors makes them
+relevant when dealing with System V message queues.
+</p>
+
+<p><strong>Some implementations impose extremely stingy limits.</strong>
+For instance, many BSDish systems (OS X, FreeBSD,
+<a href="http://fxr.watson.org/fxr/source/sys/msg.h?v=NETBSD">NetBSD</a>, and
+<a href="http://fxr.watson.org/fxr/source/sys/msg.h?v=OPENBSD">OpenBSD</a>)
+limit queues to 2048 bytes. Note that that's the <em>total
+queue size</em>, not the message size. Two 1k messages would fill the queue.
+</p>
+
+<p><strong>Those limits can be very difficult to change.</strong> At best,
+only privileged processes can increase the limit. At worst, the limit
+is a kernel parameter and requires a kernel change via a tunable or
+a recompile.
+</p>
+
+<p><strong>This module can't figure out what the limits are</strong>, so
+it can't cushion them or even report them to you.
+On some systems the limits are expressed in header files, on others
+they're available through kernel interfaces (like FreeBSD's <tt>sysctl</tt>).
+Under OS X and to some extent OpenSolaris I can't figure out where they're
+defined and what I report here is the result of experimentation and educated
+guesses formed by Googling.
+</p>
+
+<p>The good news is that this module will still behave as advertised no
+matter what these limits are. Nevertheless you might be surprised when a
+call to <tt>.send()</tt> get stuck because a queue is full even though you've
+only put 2048 bytes of messages in it.
+</p>
+
+<p>Here are the limits I've been able to find under my test operating
+systems, ordered from best (most generous) to worst (most stingy).
+<strong>This information was current as of 2009</strong> when I wrote the
+message queue code. It's getting pretty stale now. I hope the situation has
+improved over the 2009 numbers I describe below.
+</p>
+
+<p>Under <strong>OpenSolaris 2008.05</strong> each queue's maximum size defaults
+to 64k. A privileged process (e.g. root) can change this through the
+<tt>max_size</tt> attribute of a <tt>sysv_ipc.MessageQueue</tt> object.
+I was able to increase it to 16M and successfully sent sixteen 1M messages to
+the queue.
+</p>
+
+<p>Under <strong>Ubuntu 8.04</strong> (and perhaps other Linuxes) each
+queue's maximum size defaults to 16k. As with OpenSolaris, I was able to
+increase this to 16M, but only for a privileged process.
+</p>
+
+<p>Under <strong>FreeBSD 7</strong> and I think NetBSD and OpenBSD, each
+queue's maximum size defaults to 2048 bytes. Furthermore, one can (as root)
+set <tt>max_size</tt> to something larger and FreeBSD doesn't complain, but
+it also ignores the change.
+</p>
+
+<p><strong>OS X</strong> is the worst of the lot. Each queue is limited
+to 2048 bytes and OS X silently ignores attempts to increase this (just like
+FreeBSD). To add insult to injury, there appears to be no way to increase
+this limit short of recompiling the kernel.
+I'm guessing at this based on the
+<a href="http://www.google.com/search?q=site%3Aopensource.apple.com+%22msg.h%22">Darwin
+message queue limits</a>.
+</p>
+
+<p>If you want
+to search for these limits on your operating system, the key constants are
+<tt>MSGSEG</tt>, <tt>MSGSSZ</tt>, <tt>MSGTQL</tt>, <tt>MSGMNB</tt>,
+<tt>MSGMNI</tt> and <tt>MSGMAX</tt>. Under BSD, <tt>sysctl kern.ipc</tt>
+should tell you what you need to know and may allow you to change these
+parameters.
+</p>
+
+<h4>Nobody Likes a Mr. Messy</h4>
+
+<p>Semaphores and especially shared memory are a little different from most Python objects
+and therefore require a little more care on the part of the programmer. When a
+program creates a semaphore or shared memory object, it creates something that
+resides <em>outside of its own process</em>, just like a file on a hard drive. It
+won't go away when your process ends unless you explicitly remove it.
+</p>
+
+<p>In short, remember to clean up after yourself.</p>
+
+<h4>Consult Your Local <tt>man</tt> Pages</h4>
+
+<p>The sysv_ipc module is just a wrapper around your system's API. If your
+system's implementation has quirks, the <tt>man</tt> pages for <tt>semget, semctl, semop
+shmget, shmat, shmdt</tt> and <tt>shmctl</tt> will probably cover them.
+</p>
+
+<h4>Interesting Tools</h4>
+
+<p>Many systems (although not some older versions of OS X) come
+with <tt>ipcs</tt> and <tt>ipcrm</tt>.
+The former shows existing shared memory, semaphores and message queues on your system and
+the latter allows you to remove them.
+</p>
+
+
+<h4>Last But Not Least</h4>
+
+<p>For Pythonistas &ndash;</p>
+<ul>
+ <li><a href="https://www.youtube.com/watch?v=Xe1a1wHxTyo">A meditation on the inaccuracy
+ of shared memories</a>
+ </li>
+</ul>
+
+<h3 id="bugs">Known Bugs</h3>
+
+<p>Bugs? My code never has bugs! There are, however, some suboptimal anomalies...</p>
+
+<ul>
+ <li>Under OS X, <tt>sys/ipc.h</tt> defines two versions of the
+ <tt>ipc_perm</tt> struct. This module picks up the old, deprecated
+ one which stores uid and gid values in 16 bit types. It's definitions
+ in <tt>Python.h</tt> that cause this, though, so there's not much
+ I can do to change it.
+ </li>
+ <li>This module can't report the exact min and max values for a key.
+ It turns out
+ that <a href="http://groups.google.com/group/comp.lang.c/browse_thread/thread/14a1cdbfb111f4eb">it's
+ really difficult to determine the maximum value that a
+ typedef-ed variable can hold</a>.
+ </li>
+</ul>
+
+<h3>Version History</h3>
+
+<ul id="history">
+ <li id="v0_6_8"><strong><span id="current">Current</span> &ndash; 0.6.8 (12 Sept 2014) &ndash;</strong>
+ <ul>
+ <li>Fixed a bug in <tt>prober.py</tt> where prober would fail
+ on systems where Python's include file was not named
+ <tt>pyconfig.h</tt>. (RHEL 6 is one such system.) Thanks to
+ Joshua Harlow for the bug report and patch.
+ </li>
+
+ <li><i>Spasibo</i> again to Yuriy Taraday for pointing out that
+ the semaphore initialization example I changed in the previous
+ version was still not
+ quite correct, and for suggesting something better.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_6_7">0.6.7 (1 August 2014) &ndash;
+ <p><i>Spasibo</i> to Yuriy Taraday for reporting some doc errors
+ corrected in this version.
+ </p>
+
+ <ul>
+ <li>Added KEY_MIN as a module-level constant. The documentation
+ has claimed that it was available for a long time so now the
+ code finally matches the documentation.
+ </li>
+
+ <li>Changed randomly generated keys to never use a value of 0.</li>
+
+ <li>Fixed two documenation errors in the special section on
+ semaphore initialization and gave long overdue credit to
+ W. Richard Stevens' book for the code idea. (Any code mistakes
+ are mine, not his.)
+ </li>
+
+ <li>This is the first version downloadable from PyPI.</li>
+ </ul>
+ </li>
+
+ <li id="v0_6_6">0.6.6 (15 October 2013) &ndash;
+ <p>Added the ability to use Semaphores in context managers.
+ Thanks to Matt Ruffalo for the suggestion and patch.
+ </p>
+ </li>
+
+ <li id="v0_6_5">0.6.5 (31 March 2013) &ndash;
+ <p>Fixed a bug where SharedMemory.write() claimed to accept
+ keyword arguments but didn't actually do so. Thanks to
+ Matt Ruffalo for the bug report and patch.
+ </p>
+ </li>
+
+ <li id="v0_6_4">0.6.4 (19 Dec 2012) &ndash;
+ <ul>
+ <li>Added a module-level <tt>attach()</tt> method based on a
+ suggestion by Vladimír Včelák.
+ </li>
+ <li>Added the <tt>ftok()</tt> method along with dire warnings about
+ its use.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_6_3">0.6.3 (3 Oct 2010) &ndash;
+ <ul>
+ <li>Fixed a segfault in <tt>write()</tt> that occurred any time
+ an offset was passed. This was introduced in v0.6.0.
+ <i>Tack</i> to Johan Bernhardtson for the bug report.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_6_2">0.6.2 (6 July 2010) &ndash;
+ <ul>
+ <li>Updated setup.py metadata to reflect Python 3 compatibility.
+ No functional change.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_6_1">0.6.1 (26 June 2010) &ndash;
+ <ul>
+ <li>Fixed a typo introduced in the previous version that caused
+ unpredictable behavior (usually a segfault) if a block flag
+ was passed to <tt>MessageQueue.send()</tt>. <i>Obrigado</i>
+ to Álvaro Justen and Ronald Kaiser for the bug report and
+ patch.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_6_0">0.6.0 (20 May 2010) &ndash;
+ <ul>
+ <li>Added Python 3 support.</li>
+ <li>Renamed a constant that caused a problem under AIX.
+ Thanks to Loic Nageleisen for the bug report.
+ </li>
+ <li>Updated this documentation a little.</li>
+ </ul>
+ </li>
+
+ <li id="v0_5_2">0.5.2 (17 Jan 2010)</strong>
+ <ul>
+ <li>Fixed a bug that insisted on keys &gt; 0. Thanks to 原志
+ (Daniel) for the bug report and patch.
+ </li>
+ <li>Fixed a bug where the module could have generated invalid
+ keys on systems that typedef <tt>key_t</tt> as a
+ <tt>short</tt>. I don't think such a system exists, though.
+ </li>
+ <li>Fixed a LICENSE file typo.</li>
+ <li>Added an RSS feed to this page.</li>
+ </ul>
+ </li>
+
+ <li id="v0_5_1">0.5.1 (1 Dec 2009) &ndash;
+ <p>No code changes in this version.</p>
+ <ul>
+ <li>Fixed the comment in <tt>sysv_ipc_module.c</tt> that
+ still referred to the GPL license.
+ </li>
+ <li>Added the attributes <tt>__version</tt>, <tt>__author__</tt>,
+ <tt>__license__</tt> and <tt>__copyright__</tt>.
+ </li>
+ <li>Removed <tt>file()</tt> from <tt>setup.py</tt> in favor
+ of <tt>open()</tt>.
+ </li>
+ </ul>
+ </li>
+
+ <li id="v0_5">0.5 (6 Oct 2009) &ndash;
+ <p>No code changes in this version.</p>
+ <ul>
+ <li>Changed the license from GPL to BSD.</li>
+ </ul>
+ </li>
+
+ <li id="v0_4_2">0.4.2 (22 Mar 2009) &ndash;
+ <p>No code changes in this version.</p>
+ <ul>
+ <li>Fixed broken documentation links to youtube.</li>
+ <li>Fixed the project name in the LICENSE file.</li>
+ </ul>
+ </li>
+
+ <li id="v0_4_1"> 0.4.1 (12 Feb 2009) &ndash;
+ <ul>
+ <li>Changed status to beta.</li>
+ <li>Added automatic generation of keys.</li>
+ <li>Added a message queue demo.</li>
+ <li>Added <tt>str()</tt> and <tt>repr()</tt> support to all
+ objects.</li>
+ <li>Fixed a bug in <tt>SharedMemory.write()</tt> that could cause
+ a spurious error of "Attempt to write past end of memory
+ segment". This bug only occurred on platforms using
+ different sizes for <tt>long</tt> and <tt>int</tt>, or
+ <tt>long</tt> and <tt>py_size_t</tt>
+ (depending on Python version). <em>Tack</em> to Jesper
+ for debug help.
+ </li>
+ <li>Plugged a memory leak in <tt>MessageQueue.receive()</tt>.</li>
+ <li>Added a VERSION attribute to the module.</li>
+ </ul>
+ </li>
+
+ <li id="v0_4">0.4 (28 Jan 2009) &ndash;
+ <ul>
+ <li>Added message queues.</li>
+ <li>Fixed a bug where the <tt>key</tt> attribute of SharedMemory objects
+ returned garbage.
+ </li>
+ <li>Fixed a bug where keys &gt; INT_MAX would get truncated on
+ platforms where longs are bigger than ints.
+ </li>
+ <li>Provided decent inline documentation for object attributes
+ (available via the <tt>help()</tt> command).
+ </li>
+ <li>Rearranged the code which was suffering growing pains.</li>
+ </ul>
+ </li>
+
+ <li id="v0_3">0.3 (16 Jan 2009) &ndash;
+ <p>This version features a rename of classes and errors
+ (sorry about breaking the names), some modifications to
+ semaphore timeouts, and many small fixes.
+ </p>
+
+ <p>A semaphore's <tt>.block</tt> flag now consistently trumps the
+ timeout. When <tt>False</tt>, the timeout is irrelevant -- calls
+ will never block. In prior versions, the flag was ignored
+ when the timeout was non-zero.
+ </p>
+
+ <p>Also, on platforms (such as OS X) where <tt>semtimedop()</tt> is
+ not supported, all timeouts are now treated as <tt>None</tt>.
+ In other words, when <tt>.block</tt> is True, all calls
+ wait as long as necessary.
+ </p>
+
+ <p>Other fixes &ndash;</p>
+
+ <ul>
+ <li>Fixed the poor choices I'd made for class and
+ error names by removing the leading "SysV" and "SysVIpc".
+ </li>
+ <li>Removed dependencies on Python 2.5. The module now works
+ with Python 2.4.4 and perhaps earlier versions.
+ </li>
+ <li>Fixed a bug where I was not following SysV semaphore semantics
+ for interaction between timeouts and the block flag.
+ </li>
+ <li>Fixed compile problems on OpenSolaris 2008.05.</li>
+ <li>Got rid of OS X compile warnings.</li>
+ <li>Fixed many instances where I was making potentially lossy
+ conversions between Python values and Unix-specific
+ types like <tt>key_t</tt>, <tt>pid_t</tt>, etc.
+ </li>
+ <li>Fixed a bug in the SharedMemory <tt>attach()</tt> function
+ that would set an error string but not return an error
+ value if the passed address was not <tt>None</tt> or a long.
+ </li>
+ <li>Simplified the code a little.</li>
+ <li>Restricted the semaphore <tt>acquire()</tt> and
+ <tt>release()</tt> delta to be between SHORT_MIN and
+ SHORT_MAX since
+ <a href="http://opengroup.org/onlinepubs/007908775/xsh/semop.html">it
+ is a short in the SUSv2 spec</a>.
+ </li>
+ <li>Fixed a bug where calling <tt>acquire()</tt> or
+ <tt>release()</tt> with a delta of <tt>0</tt> would call
+ <tt>.Z()</tt> instead.
+ </li>
+ <li>Disallowed byte counts ≤ <tt>0</tt> in
+ <tt>SharedMemory.read()</tt>
+ </li>
+ <li>Fixed a bug in the <tt>SharedMemory</tt> init code that
+ could, under vanishingly rare circumstances, return failure
+ without setting the error code.
+ </li>
+ <li>Removed some dead code relating to the <tt>.seq</tt> member
+ of the <tt>sem_perm</tt> and <tt>shm_perm</tt> structs.
+ </li>
+ <li>Stopped accessing the non-standard <tt>key</tt>
+ (a.k.a. <tt>_key</tt> and <tt>__key</tt>) element of the
+ <tt>ipc_perm</tt> struct.
+ </li>
+ <li>Added code to ensure the module doesn't try to create
+ a string that's larger than Python permits when reading
+ from shared memory.
+ </li>
+ </ul>
+ </li>
+
+ <li>0.2.1 (3 Jan 2009) &ndash;
+ <ul>
+ <li>Fixed a bug that prevented the module-specific
+ errors (<tt>ExistentialError</tt>, etc.) from
+ being visible in the module.
+ </li>
+ <li>Fixed a bug that re-initialized shared memory with
+ the init character when only <tt>IPC_CREAT</tt> was specified
+ and an existing segment was opened.
+ </li>
+ <li>Fixed a bug that always defaulted the size of a shared
+ memory segment to <tt>PAGE_SIZE</tt>. Updated code and
+ documentation to use intelligent defaults. <em>Tack</em> to
+ Jesper for the bug report.
+ </li>
+ <li>Several cosmetic changes. (Added more metadata to setup.py,
+ added a newline to the end of probe_results.h to avoid
+ compiler complaints, etc.)
+ </li>
+ </ul>
+ </li>
+
+ <li>0.2 (16 Dec 2008) &ndash;
+ Lots of small fixes.
+
+ <ul>
+ <li>Fixed a bug where calling <tt>shm.remove()</tt> on shared
+ memory that was already removed would cause a SystemError.
+ (I wasn't setting the Python error before returning.)
+ </li>
+
+ <li>Fixed a couple of bugs that would cause the creation
+ of a new, read-only shared memory segment to fail.
+ </li>
+
+ <li>Fixed a bug that would cause the creation
+ of a new, read-only semaphore to fail.
+ </li>
+
+ <li>Added the constant <tt>IPC_CREX</tt>.</li>
+
+ <li>Renamed (sorry) <tt>MAX_KEY</tt> to <tt>KEY_MAX</tt> and
+ <tt>MAX_SEMAPHORE_VALUE</tt> to <tt>SEMAPHORE_VALUE_MAX</tt>
+ to be consistent with the C naming convention in limits.h.
+ </li>
+
+ <li>Hardcoded <tt>SEMAPHORE_VALUE_MAX</tt> to 32767 until I can
+ find a reliable way to determine it at install time.
+ </li>
+
+ <li>Changed prober.py to write out a C header file with
+ platform-specific definitions in it.
+ </li>
+
+ <li>Replaced OSError in shared memory functions with custom
+ errors from this module.
+ </li>
+
+ <li>Added code to raise a ValueError when an attempt is made
+ to assign an out-of-range value to a semaphore.
+ </li>
+
+ <li>Added code to raise a ValueError when an out-of-range key
+ is passed to a constructor.
+ </li>
+
+ <li>Fixed a bug in the demo program conclusion.c that caused
+ some informational messages to be printed twice.
+ </li>
+
+ <li>Fixed some documentation bugs.</li>
+ </ul>
+ </li>
+ <li>0.1 (4 Dec 2008) &ndash; Original (alpha) version.</li>
+</ul>
+
+
+<h3>Future Features/Changes</h3>
+
+<p>These are features that may or may not be added depending on technical
+difficulty, user interest and so forth.
+</p>
+
+<ul>
+ <li>Update this documentation with a list of platforms that support semtimedop().</li>
+
+ <li>Find a way to make <tt>SEMAPHORE_VALUE_MAX</tt> more accurate.</li>
+</ul>
+
+<p>I don't plan on adding support for semaphore sets.</p>
+
+
+</body>
+</html>
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..fef123b
--- /dev/null
+++ b/VERSION
@@ -0,0 +1,3 @@
+0.6.8
+
+
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..a55f99c
--- /dev/null
+++ b/common.c
@@ -0,0 +1,100 @@
+#include "Python.h"
+#include "structmember.h"
+
+#include "common.h"
+
+#include <stdlib.h>
+
+key_t
+get_random_key(void) {
+ int key;
+
+ /* ******************************************************************
+ The inability to know the range of a key_t requires careful code here.
+ Remember that KEY_MIN and KEY_MAX refer only to the limits inherent in the
+ variable type I use internally when turning a key into a Python object and
+ vice versa. Those limits may exceed the operating system's limits of key_t.
+
+ For instance, if key_t is typedef-ed as uint, I should generate a key
+ where 0 <= key <= UINT_MAX.
+
+ Since I can't know what key_t is typedef-ed as, I take a conservative
+ approach and generate only keys where
+ 1 <= key <= SHRT_MAX.
+
+ Such values will work if key_t is typedef-ed as a short, int, uint,
+ long or ulong.
+ ****************************************************************** */
+ do {
+ // ref: http://www.c-faq.com/lib/randrange.html
+ key = ((int)((double)rand() / ((double)RAND_MAX + 1) * (SHRT_MAX - 1))) + 1;
+ } while (key == IPC_PRIVATE);
+
+ return (key_t)key;
+}
+
+#if PY_MAJOR_VERSION < 3
+PyObject *
+py_int_or_long_from_ulong(unsigned long value) {
+ // Python ints are guaranteed to accept up to LONG_MAX. Anything
+ // larger needs to be a Python long.
+ if (value > LONG_MAX)
+ return PyLong_FromUnsignedLong(value);
+ else
+ return PyInt_FromLong(value);
+}
+#endif
+
+
+int
+convert_key_param(PyObject *py_key, void *converted_key) {
+ // Converts a PyObject into a key if possible. Returns 0 on failure.
+ // The converted_key param should point to a NoneableKey type.
+ // None is an acceptable key, in which case converted_key->is_none
+ // is non-zero and converted_key->value is undefined.
+ int rc = 0;
+ long key = 0;
+
+ ((NoneableKey *)converted_key)->is_none = 0;
+
+ if (py_key == Py_None) {
+ rc = 1;
+ ((NoneableKey *)converted_key)->is_none = 1;
+ }
+#if PY_MAJOR_VERSION < 3
+ else if (PyInt_Check(py_key)) {
+ rc = 1;
+ key = PyInt_AsLong(py_key);
+ }
+#endif
+ else if (PyLong_Check(py_key)) {
+ rc = 1;
+ key = PyLong_AsLong(py_key);
+ if (PyErr_Occurred()) {
+ // This happens when the Python long is too big for a C long.
+ rc = 0;
+ PyErr_Format(PyExc_ValueError,
+ "Key must be between %ld (KEY_MIN) and %ld (KEY_MAX)",
+ KEY_MIN, KEY_MAX);
+ }
+ }
+
+ if (rc) {
+ // Param is OK
+ if (! ((NoneableKey *)converted_key)->is_none) {
+ // It's not None; ensure it is in range
+ if ((key >= KEY_MIN) && (key <= KEY_MAX))
+ ((NoneableKey *)converted_key)->value = (key_t)key;
+ else {
+ rc = 0;
+ PyErr_Format(PyExc_ValueError,
+ "Key must be between %ld (KEY_MIN) and %ld (KEY_MAX)",
+ KEY_MIN, KEY_MAX);
+ }
+ }
+ }
+ else
+ PyErr_SetString(PyExc_TypeError, "Key must be an integer or None");
+
+ return rc;
+}
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..89fa376
--- /dev/null
+++ b/common.h
@@ -0,0 +1,212 @@
+#include "Python.h"
+#include "structmember.h"
+
+#if PY_MAJOR_VERSION >= 0x02050000
+#define PY_SSIZE_T_CLEAN
+#define PY_STRING_LENGTH_MAX PY_SSIZE_T
+#else
+#define PY_STRING_LENGTH_MAX INT_MAX
+#endif
+
+// define Py_TYPE for versions before Python 2.6
+#ifndef Py_TYPE
+#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
+#endif
+
+// define PyVarObject_HEAD_INIT for versions before Python 2.6
+#ifndef PyVarObject_HEAD_INIT
+#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
+#endif
+
+/* In sys/ipc.h under OS X, I get stuck with the old, "do not use" version
+of the ipc_perm struct. This is unavoidable because Python.h #undefs
+_POSIX_C_SOURCE and _XOPEN_SOURCE. This causes cdefs.h to #define
+__DARWIN_UNIX03 to 0 (implying the "default" compilation environment, as
+opposed to "strict") and therefore I get the crappy, old version of the
+struct. I don't think there's any way around this unless Python changes
+its headers.
+*/
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/msg.h>
+
+#include "probe_results.h"
+
+/* Struct to contain a key which can be None */
+typedef struct {
+ int is_none;
+ key_t value;
+} NoneableKey;
+
+
+/* These identifiers are prefixed with SVIFP_ which stands for SysV Ipc
+For Python. It's really just a random string of letters to prevent clashes
+with other constants (as happens with SHM_SIZE on AIX).
+*/
+enum GET_SET_IDENTIFIERS {
+ SVIFP_IPC_PERM_UID = 1,
+ SVIFP_IPC_PERM_GID,
+ SVIFP_IPC_PERM_CUID,
+ SVIFP_IPC_PERM_CGID,
+ SVIFP_IPC_PERM_MODE,
+ SVIFP_SEM_OTIME,
+ SVIFP_SHM_SIZE,
+ SVIFP_SHM_LAST_ATTACH_TIME,
+ SVIFP_SHM_LAST_DETACH_TIME,
+ SVIFP_SHM_LAST_CHANGE_TIME,
+ SVIFP_SHM_CREATOR_PID,
+ SVIFP_SHM_LAST_AT_DT_PID,
+ SVIFP_SHM_NUMBER_ATTACHED,
+ SVIFP_MQ_LAST_SEND_TIME,
+ SVIFP_MQ_LAST_RECEIVE_TIME,
+ SVIFP_MQ_LAST_CHANGE_TIME,
+ SVIFP_MQ_CURRENT_MESSAGES,
+ SVIFP_MQ_QUEUE_BYTES_MAX,
+ SVIFP_MQ_LAST_SEND_PID,
+ SVIFP_MQ_LAST_RECEIVE_PID
+};
+
+// Shorthand for lazy typists like me
+#define IPC_CREX (IPC_CREAT | IPC_EXCL)
+
+#ifdef SYSV_IPC_DEBUG
+#define DPRINTF(fmt, args...) fprintf(stderr, "+++ " fmt, ## args)
+#else
+#define DPRINTF(fmt, args...)
+#endif
+
+/* **************************************************************************
+I have to do some guessing about types, mostly with regard to key_t. Since I
+schlep these values back and forth between C and Python, I need to know
+approximately what types these represent so that I can call the appropriate
+Python function to convert them to Python values (e.g. PyInt_FromLong(),
+PyFloat_FromDouble(), etc.)
+
+For most of these types the SUSv3 specification states that they're
+integer-ish which means I can stuff them into a long and not worry. The macros
+below safely convert each type and above each macro I document why my type
+assumptions are safe.
+
+Unfortunately, key_t is an exception. The SUSv3 specification doesn't get more
+detailed than saying it is "arithmetic", which includes signed and unsigned
+short, int, long, long long, float and double (but thankfully excludes
+pointers). I feel I can safely ignore long long since support for it is far
+from ubiquitous.
+
+Ideally I would cast key_t to double and call PyWhatever_FromDouble() when
+sending values to Python. Representing it as a double would ensure no data
+loss regardless of whether it is typedef-ed as int, long, float, etc.
+
+In practice, this would be awkward. I have yet to see a platform where
+key_t is not integer-ish, so most or all users of this library would be
+surprised if it returned keys as floats.
+
+So I store keys as key_t types and cast them to long when Python forces
+me to be specific. The two disadvantages to this are, (1) if any platforms
+typedef key_t as a float or double, this code will break, and (2) KEY_MIN
+and KEY_MAX don't represent the real min and max of keys.
+
+Point #1 is mitigated somewhat because if any OS typedefs key_t as float or
+double, this code should complain loudly during compilation.
+
+Typedefs of key_t (for 32 bit systems):
+OpenSolaris 2008.11 - int
+OS X 10.5.8 - __int32_t (int)
+Ubuntu 9.04 - int
+Freebsd - l_int (int)
+************************************************************************** */
+
+
+// key_t is guaranteed to be an arithmetic type. Some earlier versions
+// of the standard didn't guarantee that it was arithmetic; the standard was
+// changed to guarantee that it *is* arithmetic.
+// ref: http://pubs.opengroup.org/onlinepubs/009696899/functions/xsh_chap02_12.html
+// I assume it is a long; see comment above.
+// Some functions return (key_t)-1, so I guess this has to be a signed type.
+// ref: http://www.opengroup.org/austin/interps/doc.tpl?gdid=6226
+#if PY_MAJOR_VERSION > 2
+ #define KEY_T_TO_PY(key) PyLong_FromLong(key)
+#else
+ #define KEY_T_TO_PY(key) PyInt_FromLong(key)
+#endif
+// SUSv3 guarantees a uid_t to be an integer type. Some functions return
+// (uid_t)-1, so I guess this has to be a signed type.
+// ref: http://www.opengroup.org/onlinepubs/009695399/basedefs/sys/types.h.html
+// ref: http://www.opengroup.org/onlinepubs/9699919799/functions/chown.html
+#if PY_MAJOR_VERSION > 2
+ #define UID_T_TO_PY(uid) PyLong_FromLong(uid)
+#else
+ #define UID_T_TO_PY(uid) PyInt_FromLong(uid)
+#endif
+
+// SUSv3 guarantees a gid_t to be an integer type. Some functions return
+// (gid_t)-1, so I guess this has to be a signed type.
+// ref: http://www.opengroup.org/onlinepubs/009695399/basedefs/sys/types.h.html
+#if PY_MAJOR_VERSION > 2
+ #define GID_T_TO_PY(gid) PyLong_FromLong(gid)
+#else
+ #define GID_T_TO_PY(gid) PyInt_FromLong(gid)
+#endif
+
+// I'm not sure what guarantees SUSv3 makes about a mode_t, but param 3 of
+// shmget() is an int that contains flags in addition to the mode, so
+// mode must be able to fit into an int.
+// ref: http://www.opengroup.org/onlinepubs/009695399/functions/shmget.html
+#if PY_MAJOR_VERSION > 2
+ #define MODE_T_TO_PY(mode) PyLong_FromLong(mode)
+#else
+ #define MODE_T_TO_PY(mode) PyInt_FromLong(mode)
+#endif
+
+// I'm not sure what guarantees SUSv3 makes about a time_t, but the times
+// I deal with here are all guaranteed to be after 1 Jan 1970 which means
+// they'll always be positive numbers. A ulong sounds appropriate to me,
+// and Python agrees in posixmodule.c.
+#if PY_MAJOR_VERSION > 2
+ #define TIME_T_TO_PY(time) PyLong_FromUnsignedLong(time)
+#else
+ #define TIME_T_TO_PY(time) py_int_or_long_from_ulong(time)
+#endif
+
+// C89 guarantees a size_t to be unsigned and fit into a ulong or smaller.
+#if PY_MAJOR_VERSION > 2
+ #define SIZE_T_TO_PY(size) PyLong_FromUnsignedLong(size)
+#else
+ #define SIZE_T_TO_PY(size) py_int_or_long_from_ulong(size)
+#endif
+
+// SUSv3 guarantees a pid_t to be a signed integer type. Some functions
+// return (pid_t)-1 so I guess this has to be signed.
+// ref: http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/types.h.html#tag_13_67
+#if PY_MAJOR_VERSION > 2
+ #define PID_T_TO_PY(pid) PyLong_FromLong(pid)
+#else
+ #define PID_T_TO_PY(pid) PyInt_FromLong(pid)
+#endif
+
+// The SUS guarantees a msglen_t to be an unsigned integer type.
+// Ditto: msgqnum_t.
+// ref: http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/msg.h.html
+#if PY_MAJOR_VERSION > 2
+ #define MSGLEN_T_TO_PY(msglen) PyLong_FromUnsignedLong(msglen)
+ #define MSGQNUM_T_TO_PY(msgqnum) PyLong_FromUnsignedLong(msgqnum)
+#else
+ #define MSGLEN_T_TO_PY(msglen) py_int_or_long_from_ulong(msglen)
+ #define MSGQNUM_T_TO_PY(msgqnum) py_int_or_long_from_ulong(msgqnum)
+#endif
+
+/* Utility functions */
+key_t get_random_key(void);
+int convert_key_param(PyObject *, void *);
+#if PY_MAJOR_VERSION < 3
+PyObject *py_int_or_long_from_ulong(unsigned long);
+#endif
+
+/* Custom Exceptions/Errors */
+extern PyObject *pBaseException;
+extern PyObject *pInternalException;
+extern PyObject *pPermissionsException;
+extern PyObject *pExistentialException;
+extern PyObject *pBusyException;
+extern PyObject *pNotAttachedException;
diff --git a/demo/ReadMe.txt b/demo/ReadMe.txt
new file mode 100644
index 0000000..35d819b
--- /dev/null
+++ b/demo/ReadMe.txt
@@ -0,0 +1,54 @@
+This demonstrates use of shared memory and semaphores via two applications
+named after Mrs. Premise and Mrs. Conclusion of the Monty Python sketch.
+http://www.youtube.com/watch?v=crIJvcWkVcs
+
+Like those two characters, these programs chat back and forth and the result
+is a lot of nonsense. In this case, what the programs are saying isn't the
+interesting part. What's interesting is how they're doing it.
+
+Mrs. Premise and Mrs. Conclusion (the programs, not the sketch characters)
+communicate through IPC shared memory with a semaphore to control access
+to that memory.
+
+Mrs. Premise starts things off by creating the shared memory and semaphore
+and writing a random string (the current time) to the memory. She then sits
+in a loop reading the memory. If it holds the same message she wrote, she
+does nothing. If it is a new message, it must be from Mrs. Conclusion.
+
+Meanwhile, Mrs. Conclusion is doing exactly the same thing, except that she
+assumes Mrs. Premise will write the first message.
+
+When either of these programs reads a new message, they write back an md5
+hash of that message. This serves two purposes. First, it ensures that
+subsequent messages are very different so that if a message somehow gets
+corrupted (say by being partially overwritten by the next message), it will
+not escape notice. Second, it ensures that corruption can be detected if
+it happens, because Mrs. Premise and Mrs. Conclusion can calculate what the
+other's response to their message should be.
+
+Since they use a semaphore to control access to the shared memory, Mrs.
+Premise and Mrs. Conclusion won't ever find their messages corrupted no
+matter how many messages they exchange. You can experiment with this by
+setting ITERATIONS in params.txt to a very large value. You can change
+LIVE_DANGEROUSLY (also in params.txt) to a non-zero value to tell Mrs.
+Premise and Mrs. Conclusion to run without using the semaphore. The shared
+memory will probably get corrupted in fewer than 1000 iterations.
+
+To run the demo, start Mrs. Premise first in one window and then run
+Mrs. Conclusion in another.
+
+
+ The Fancy Version
+ =================
+
+If you want to get fancy, you can play with C versions of Mrs. Premise and
+Mrs. Conclusion. The script make_all.sh will compile them for you. (Linux
+users will need to edit the script and uncomment the line for the
+Linux-specific linker option.)
+
+The resulting executables are called premise and conclusion and work exactly
+the same as their Python counterparts. You can have the two C programs talk
+to one another, or you can have premise.py talk to the C version of
+conclusion...the possibilities are endless. (Actually, there are only four
+possible combinations but "endless" sounds better.)
+
diff --git a/demo/SampleIpcConversation.png b/demo/SampleIpcConversation.png
new file mode 100644
index 0000000..9d11e7a
--- /dev/null
+++ b/demo/SampleIpcConversation.png
Binary files differ
diff --git a/demo/cleanup.py b/demo/cleanup.py
new file mode 100644
index 0000000..44c4ea6
--- /dev/null
+++ b/demo/cleanup.py
@@ -0,0 +1,29 @@
+# 3rd party modules
+import sysv_ipc
+
+# Modules for this project
+import utils
+
+params = utils.read_params()
+
+
+try:
+ semaphore = sysv_ipc.Semaphore(params["KEY"], 0)
+except:
+ semaphore = None
+
+if semaphore:
+ semaphore.remove()
+
+print ("The semaphore is cleaned up.")
+
+
+try:
+ memory = sysv_ipc.SharedMemory(params["KEY"], 0)
+except:
+ memory = None
+
+if memory:
+ memory.remove()
+
+print ("The shared memory is cleaned up.")
diff --git a/demo/conclusion.c b/demo/conclusion.c
new file mode 100644
index 0000000..7b10aef
--- /dev/null
+++ b/demo/conclusion.c
@@ -0,0 +1,139 @@
+#include <sys/ipc.h> /* for system's IPC_xxx definitions */
+#include <sys/shm.h> /* for shmget, shmat, shmdt, shmctl */
+#include <sys/sem.h> /* for semget, semctl, semop */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#include "md5.h"
+#include "utils.h"
+
+static const char MY_NAME[] = "Mrs. Conclusion";
+
+// Set up a Mrs. Premise & Mrs. Conclusion conversation.
+
+int main() {
+ int sem_id = 0;
+ int shm_id = 0;
+ int rc;
+ char s[1024];
+ int i;
+ int done;
+ char last_message_i_wrote[256];
+ char md5ified_message[256];
+ void *address = NULL;
+ struct param_struct params;
+
+ say(MY_NAME, "Oooo 'ello, I'm Mrs. Conclusion!");
+
+ read_params(&params);
+
+ // Mrs. Premise has already created the semaphore and shared memory.
+ // I just need to get handles to them.
+ sem_id = semget(params.key, 0, params.permissions);
+
+ if (-1 == sem_id) {
+ sem_id = 0;
+ sprintf(s, "Getting a handle to the semaphore failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ // get a handle to the shared memory
+ shm_id = shmget(params.key, params.size, params.permissions);
+
+ if (shm_id == -1) {
+ shm_id = 0;
+ sprintf(s, "Couldn't get a handle to the shared memory; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ sprintf(s, "Shared memory's id is %d", shm_id);
+ say(MY_NAME, s);
+
+ // Attach the memory.
+ address = shmat(shm_id, NULL, 0);
+
+ if ((void *)-1 == address) {
+ address = NULL;
+ sprintf(s, "Attaching the shared memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ sprintf(s, "shared memory address = %p", address);
+ say(MY_NAME, s);
+
+ i = 0;
+ done = 0;
+ last_message_i_wrote[0] = '\0';
+ while (!done) {
+ sprintf(s, "iteration %d", i);
+ say(MY_NAME, s);
+
+ // Wait for Mrs. Premise to free up the semaphore.
+ rc = acquire_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ if (rc)
+ done = 1;
+ else {
+ while ( (!rc) && \
+ (!strcmp((char *)address, last_message_i_wrote))
+ ) {
+ // Nothing new; give Mrs. Premise another change to respond.
+ sprintf(s, "Read %zu characters '%s'", strlen((char *)address), (char *)address);
+ say(MY_NAME, s);
+ rc = release_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ if (!rc) {
+ rc = acquire_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ }
+ }
+
+ md5ify(last_message_i_wrote, md5ified_message);
+
+ // I always accept the first message (when i == 0)
+ if ( (i == 0) || (!strcmp(md5ified_message, (char *)address)) ) {
+ // All is well
+ i++;
+
+ if (i == params.iterations)
+ done = 1;
+
+ // MD5 the reply and write back to Mrs. Premise.
+ md5ify((char *)address, md5ified_message);
+
+ // Write back to Mrs. Premise.
+ sprintf(s, "Writing %zu characters '%s'", strlen(md5ified_message), md5ified_message);
+ say(MY_NAME, s);
+
+ strcpy((char *)address, md5ified_message);
+
+ strcpy(last_message_i_wrote, md5ified_message);
+ }
+ else {
+ sprintf(s, "Shared memory corruption after %d iterations.", i);
+ say(MY_NAME, s);
+ sprintf(s, "Mismatch; rc = %d, new message is '%s', expected '%s'.", rc, (char *)address, md5ified_message);
+ say(MY_NAME, s);
+ done = 1;
+ }
+ }
+
+ // Release the semaphore.
+ rc = release_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ if (rc)
+ done = 1;
+ }
+
+ if (-1 == shmdt(address)) {
+ sprintf(s, "Detaching the memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ address = NULL;
+ }
+ }
+ }
+
+
+ return 0;
+}
diff --git a/demo/conclusion.py b/demo/conclusion.py
new file mode 100755
index 0000000..304f93e
--- /dev/null
+++ b/demo/conclusion.py
@@ -0,0 +1,72 @@
+# Python modules
+import os
+import sys
+PY_MAJOR_VERSION = sys.version_info[0]
+# hashlib is only available in Python >= 2.5. I still want to support
+# older Pythons so I import md5 if hashlib is not available. Fortunately
+# md5 can masquerade as hashlib for my purposes.
+try:
+ import hashlib
+except ImportError:
+ import md5 as hashlib
+
+# 3rd party modules
+import sysv_ipc
+
+# Utils for this demo
+import utils
+if PY_MAJOR_VERSION > 2:
+ import utils_for_3 as flex_utils
+else:
+ import utils_for_2 as flex_utils
+
+utils.say("Oooo 'ello, I'm Mrs. Conclusion!")
+
+params = utils.read_params()
+
+semaphore = sysv_ipc.Semaphore(params["KEY"])
+memory = sysv_ipc.SharedMemory(params["KEY"])
+
+utils.say("memory attached at %d" % memory.address)
+
+what_i_wrote = ""
+s = ""
+
+for i in range(0, params["ITERATIONS"]):
+ utils.say("i = %d" % i)
+ if not params["LIVE_DANGEROUSLY"]:
+ # Wait for Mrs. Premise to free up the semaphore.
+ utils.say("acquiring the semaphore...")
+ semaphore.acquire()
+
+ s = utils.read_from_memory(memory)
+
+ while s == what_i_wrote:
+ if not params["LIVE_DANGEROUSLY"]:
+ # Release the semaphore...
+ utils.say("releasing the semaphore")
+ semaphore.release()
+ # ...and wait for it to become available again.
+ utils.say("acquiring for the semaphore...")
+ semaphore.acquire()
+
+ s = utils.read_from_memory(memory)
+
+ if what_i_wrote:
+ if PY_MAJOR_VERSION > 2:
+ what_i_wrote = what_i_wrote.encode()
+ try:
+ assert(s == hashlib.md5(what_i_wrote).hexdigest())
+ except AssertionError:
+ flex_utils.raise_error(AssertionError,
+ "Shared memory corruption after %d iterations." % i)
+
+ if PY_MAJOR_VERSION > 2:
+ s = s.encode()
+ what_i_wrote = hashlib.md5(s).hexdigest()
+
+ utils.write_to_memory(memory, what_i_wrote)
+
+ if not params["LIVE_DANGEROUSLY"]:
+ utils.say("releasing the semaphore")
+ semaphore.release()
diff --git a/demo/make_all.sh b/demo/make_all.sh
new file mode 100755
index 0000000..41e17c4
--- /dev/null
+++ b/demo/make_all.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+gcc -Wall -c -o md5.o md5.c
+gcc -Wall -c -o utils.o utils.c
+gcc -Wall -L. md5.o utils.o -o premise premise.c
+gcc -Wall -L. md5.o utils.o -o conclusion conclusion.c
+
diff --git a/demo/md5.c b/demo/md5.c
new file mode 100644
index 0000000..c35d96c
--- /dev/null
+++ b/demo/md5.c
@@ -0,0 +1,381 @@
+/*
+ Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.c is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+ either statically or dynamically; added missing #include <string.h>
+ in library.
+ 2002-03-11 lpd Corrected argument list for main(), and added int return
+ type, in test program and T value program.
+ 2002-02-21 lpd Added missing #include <stdio.h> in test program.
+ 2000-07-03 lpd Patched to eliminate warnings about "constant is
+ unsigned in ANSI C, signed in traditional"; made test program
+ self-checking.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+ 1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+# define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3 0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6 0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9 0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13 0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16 0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19 0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22 0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25 0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28 0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31 0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35 0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38 0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41 0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44 0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47 0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50 0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53 0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57 0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60 0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63 0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+ md5_word_t
+ a = pms->abcd[0], b = pms->abcd[1],
+ c = pms->abcd[2], d = pms->abcd[3];
+ md5_word_t t;
+#if BYTE_ORDER > 0
+ /* Define storage only for big-endian CPUs. */
+ md5_word_t X[16];
+#else
+ /* Define storage for little-endian or both types of CPUs. */
+ md5_word_t xbuf[16];
+ const md5_word_t *X;
+#endif
+
+ {
+#if BYTE_ORDER == 0
+ /*
+ * Determine dynamically whether this is a big-endian or
+ * little-endian machine, since we can use a more efficient
+ * algorithm on the latter.
+ */
+ static const int w = 1;
+
+ if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0 /* little-endian */
+ {
+ /*
+ * On little-endian machines, we can process properly aligned
+ * data without copying it.
+ */
+ if (!((data - (const md5_byte_t *)0) & 3)) {
+ /* data are properly aligned */
+ X = (const md5_word_t *)data;
+ } else {
+ /* not aligned */
+ memcpy(xbuf, data, 64);
+ X = xbuf;
+ }
+ }
+#endif
+#if BYTE_ORDER == 0
+ else /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0 /* big-endian */
+ {
+ /*
+ * On big-endian machines, we must arrange the bytes in the
+ * right order.
+ */
+ const md5_byte_t *xp = data;
+ int i;
+
+# if BYTE_ORDER == 0
+ X = xbuf; /* (dynamic only) */
+# else
+# define xbuf X /* (static only) */
+# endif
+ for (i = 0; i < 16; ++i, xp += 4)
+ xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+ }
+#endif
+ }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+ /* Round 1. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + F(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 7, T1);
+ SET(d, a, b, c, 1, 12, T2);
+ SET(c, d, a, b, 2, 17, T3);
+ SET(b, c, d, a, 3, 22, T4);
+ SET(a, b, c, d, 4, 7, T5);
+ SET(d, a, b, c, 5, 12, T6);
+ SET(c, d, a, b, 6, 17, T7);
+ SET(b, c, d, a, 7, 22, T8);
+ SET(a, b, c, d, 8, 7, T9);
+ SET(d, a, b, c, 9, 12, T10);
+ SET(c, d, a, b, 10, 17, T11);
+ SET(b, c, d, a, 11, 22, T12);
+ SET(a, b, c, d, 12, 7, T13);
+ SET(d, a, b, c, 13, 12, T14);
+ SET(c, d, a, b, 14, 17, T15);
+ SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+ /* Round 2. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + G(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 1, 5, T17);
+ SET(d, a, b, c, 6, 9, T18);
+ SET(c, d, a, b, 11, 14, T19);
+ SET(b, c, d, a, 0, 20, T20);
+ SET(a, b, c, d, 5, 5, T21);
+ SET(d, a, b, c, 10, 9, T22);
+ SET(c, d, a, b, 15, 14, T23);
+ SET(b, c, d, a, 4, 20, T24);
+ SET(a, b, c, d, 9, 5, T25);
+ SET(d, a, b, c, 14, 9, T26);
+ SET(c, d, a, b, 3, 14, T27);
+ SET(b, c, d, a, 8, 20, T28);
+ SET(a, b, c, d, 13, 5, T29);
+ SET(d, a, b, c, 2, 9, T30);
+ SET(c, d, a, b, 7, 14, T31);
+ SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+ /* Round 3. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + H(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 5, 4, T33);
+ SET(d, a, b, c, 8, 11, T34);
+ SET(c, d, a, b, 11, 16, T35);
+ SET(b, c, d, a, 14, 23, T36);
+ SET(a, b, c, d, 1, 4, T37);
+ SET(d, a, b, c, 4, 11, T38);
+ SET(c, d, a, b, 7, 16, T39);
+ SET(b, c, d, a, 10, 23, T40);
+ SET(a, b, c, d, 13, 4, T41);
+ SET(d, a, b, c, 0, 11, T42);
+ SET(c, d, a, b, 3, 16, T43);
+ SET(b, c, d, a, 6, 23, T44);
+ SET(a, b, c, d, 9, 4, T45);
+ SET(d, a, b, c, 12, 11, T46);
+ SET(c, d, a, b, 15, 16, T47);
+ SET(b, c, d, a, 2, 23, T48);
+#undef SET
+
+ /* Round 4. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + I(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 6, T49);
+ SET(d, a, b, c, 7, 10, T50);
+ SET(c, d, a, b, 14, 15, T51);
+ SET(b, c, d, a, 5, 21, T52);
+ SET(a, b, c, d, 12, 6, T53);
+ SET(d, a, b, c, 3, 10, T54);
+ SET(c, d, a, b, 10, 15, T55);
+ SET(b, c, d, a, 1, 21, T56);
+ SET(a, b, c, d, 8, 6, T57);
+ SET(d, a, b, c, 15, 10, T58);
+ SET(c, d, a, b, 6, 15, T59);
+ SET(b, c, d, a, 13, 21, T60);
+ SET(a, b, c, d, 4, 6, T61);
+ SET(d, a, b, c, 11, 10, T62);
+ SET(c, d, a, b, 2, 15, T63);
+ SET(b, c, d, a, 9, 21, T64);
+#undef SET
+
+ /* Then perform the following additions. (That is increment each
+ of the four registers by the value it had before this block
+ was started.) */
+ pms->abcd[0] += a;
+ pms->abcd[1] += b;
+ pms->abcd[2] += c;
+ pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+ pms->count[0] = pms->count[1] = 0;
+ pms->abcd[0] = 0x67452301;
+ pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+ pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+ pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+ const md5_byte_t *p = data;
+ int left = nbytes;
+ int offset = (pms->count[0] >> 3) & 63;
+ md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+ if (nbytes <= 0)
+ return;
+
+ /* Update the message length. */
+ pms->count[1] += nbytes >> 29;
+ pms->count[0] += nbits;
+ if (pms->count[0] < nbits)
+ pms->count[1]++;
+
+ /* Process an initial partial block. */
+ if (offset) {
+ int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+ memcpy(pms->buf + offset, p, copy);
+ if (offset + copy < 64)
+ return;
+ p += copy;
+ left -= copy;
+ md5_process(pms, pms->buf);
+ }
+
+ /* Process full blocks. */
+ for (; left >= 64; p += 64, left -= 64)
+ md5_process(pms, p);
+
+ /* Process a final partial block. */
+ if (left)
+ memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+ static const md5_byte_t pad[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ md5_byte_t data[8];
+ int i;
+
+ /* Save the length before padding. */
+ for (i = 0; i < 8; ++i)
+ data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+ /* Pad to 56 bytes mod 64. */
+ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+ /* Append the length. */
+ md5_append(pms, data, 8);
+ for (i = 0; i < 16; ++i)
+ digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
diff --git a/demo/md5.h b/demo/md5.h
new file mode 100644
index 0000000..698c995
--- /dev/null
+++ b/demo/md5.h
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.h is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Removed support for non-ANSI compilers; removed
+ references to Ghostscript; clarified derivation from RFC 1321;
+ now handles byte order either statically or dynamically.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+ added conditionalization for C++ compilation from Martin
+ Purschke <purschke@bnl.gov>.
+ 1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+# define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+ md5_word_t count[2]; /* message length in bits, lsw first */
+ md5_word_t abcd[4]; /* digest buffer */
+ md5_byte_t buf[64]; /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+} /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
diff --git a/demo/params.txt b/demo/params.txt
new file mode 100644
index 0000000..eff48bd
--- /dev/null
+++ b/demo/params.txt
@@ -0,0 +1,18 @@
+# These parameters control how Mrs. Premise and Mrs. Conclusion behave.
+
+# ITERATIONS is the number of times they'll talk to one another.
+# LIVE_DANGEROUSLY is a Boolean (0 or 1); if set to 1 the programs
+# won't use the semaphore to coordinate access to the shared
+# memory. Corruption will likely result.
+# KEY is the key to be used for the semaphore and shared memory.
+# PERMISSIONS are in octal (note the leading 0).
+# SHM_SIZE is the size of the shared memory segment in bytes.
+
+ITERATIONS=1000
+LIVE_DANGEROUSLY=0
+KEY=42
+PERMISSIONS=0600
+SHM_SIZE=4096
+
+
+
diff --git a/demo/premise.c b/demo/premise.c
new file mode 100644
index 0000000..11cbb4d
--- /dev/null
+++ b/demo/premise.c
@@ -0,0 +1,211 @@
+#include <sys/ipc.h> /* for system's IPC_xxx definitions */
+#include <sys/shm.h> /* for shmget, shmat, shmdt, shmctl */
+#include <sys/sem.h> /* for semget, semctl, semop */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdarg.h>
+
+#include "md5.h"
+#include "utils.h"
+
+const char MY_NAME[] = "Mrs. Premise";
+
+// Set up a Mrs. Premise & Mrs. Conclusion conversation.
+
+void get_current_time(char *);
+
+int main() {
+ int rc;
+ char s[1024];
+ char last_message_i_wrote[256];
+ char md5ified_message[256];
+ int i = 0;
+ int done = 0;
+ struct param_struct params;
+ int shm_id;
+ void *address = NULL;
+ int sem_id;
+ struct shmid_ds shm_info;
+
+ say(MY_NAME, "Oooo 'ello, I'm Mrs. Premise!");
+
+ read_params(&params);
+
+ // Create the shared memory
+ shm_id = shmget(params.key, params.size, IPC_CREAT | IPC_EXCL | params.permissions);
+
+ if (shm_id == -1) {
+ shm_id = 0;
+ sprintf(s, "Creating the shared memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ sprintf(s, "Shared memory's id is %d", shm_id);
+ say(MY_NAME, s);
+
+ // Attach the memory.
+ address = shmat(shm_id, NULL, 0);
+
+ if ((void *)-1 == address) {
+ address = NULL;
+ sprintf(s, "Attaching the shared memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ sprintf(s, "shared memory address = %p", address);
+ say(MY_NAME, s);
+ }
+ }
+
+ if (address) {
+ // Create the semaphore
+ sem_id = semget(params.key, 1, IPC_CREAT | IPC_EXCL | params.permissions);
+
+ if (-1 == sem_id) {
+ sem_id = 0;
+ sprintf(s, "Creating the semaphore failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ else {
+ sprintf(s, "the semaphore id is %d", sem_id);
+ say(MY_NAME, s);
+
+ // I seed the shared memory with a random string (the current time).
+ get_current_time(s);
+
+ strcpy((char *)address, s);
+ strcpy(last_message_i_wrote, s);
+
+ sprintf(s, "Wrote %zu characters: %s", strlen(last_message_i_wrote), last_message_i_wrote);
+ say(MY_NAME, s);
+
+ i = 0;
+ while (!done) {
+ sprintf(s, "iteration %d", i);
+ say(MY_NAME, s);
+
+ // Release the semaphore...
+ rc = release_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ // ...and wait for it to become available again. In real code
+ // I might want to sleep briefly before calling .acquire() in
+ // order to politely give other processes an opportunity to grab
+ // the semaphore while it is free so as to avoid starvation. But
+ // this code is meant to be a stress test that maximizes the
+ // opportunity for shared memory corruption and politeness is
+ // not helpful in stress tests.
+ if (!rc)
+ rc = acquire_semaphore(MY_NAME, sem_id, params.live_dangerously);
+
+ if (rc)
+ done = 1;
+ else {
+ // I keep checking the shared memory until something new has
+ // been written.
+ while ( (!rc) && \
+ (!strcmp((char *)address, last_message_i_wrote))
+ ) {
+ // Nothing new; give Mrs. Conclusion another change to respond.
+ sprintf(s, "Read %zu characters '%s'", strlen((char *)address), (char *)address);
+ say(MY_NAME, s);
+ rc = release_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ if (!rc) {
+ rc = acquire_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ }
+ }
+
+
+ if (rc)
+ done = 1;
+ else {
+ sprintf(s, "Read %zu characters '%s'", strlen((char *)address), (char *)address);
+ say(MY_NAME, s);
+
+ // What I read must be the md5 of what I wrote or something's
+ // gone wrong.
+ md5ify(last_message_i_wrote, md5ified_message);
+
+ if (strcmp(md5ified_message, (char *)address) == 0) {
+ // Yes, the message is OK
+ i++;
+ if (i == params.iterations)
+ done = 1;
+
+ // MD5 the reply and write back to Mrs. Conclusion.
+ md5ify(md5ified_message, md5ified_message);
+
+ sprintf(s, "Writing %zu characters '%s'", strlen(md5ified_message), md5ified_message);
+ say(MY_NAME, s);
+
+ strcpy((char *)address, md5ified_message);
+ strcpy((char *)last_message_i_wrote, md5ified_message);
+ }
+ else {
+ sprintf(s, "Shared memory corruption after %d iterations.", i);
+ say(MY_NAME, s);
+ sprintf(s, "Mismatch; new message is '%s', expected '%s'.", (char *)address, md5ified_message);
+ say(MY_NAME, s);
+ done = 1;
+ }
+ }
+ }
+ }
+
+ // Announce for one last time that the semaphore is free again so that
+ // Mrs. Conclusion can exit.
+ say(MY_NAME, "Final release of the semaphore followed by a 5 second pause");
+ rc = release_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ sleep(5);
+ // ...before beginning to wait until it is free again.
+ // Technically, this is bad practice. It's possible that on a
+ // heavily loaded machine, Mrs. Conclusion wouldn't get a chance
+ // to acquire the semaphore. There really ought to be a loop here
+ // that waits for some sort of goodbye message but for purposes of
+ // simplicity I'm skipping that.
+
+ say(MY_NAME, "Final wait to acquire the semaphore");
+ rc = acquire_semaphore(MY_NAME, sem_id, params.live_dangerously);
+ if (!rc) {
+ say(MY_NAME, "Destroying the shared memory.");
+
+ if (-1 == shmdt(address)) {
+ sprintf(s, "Detaching the memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ address = NULL;
+
+
+ if (-1 == shmctl(shm_id, IPC_RMID, &shm_info)) {
+ sprintf(s, "Removing the memory failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ }
+ }
+
+ say(MY_NAME, "Destroying the semaphore.");
+ // Clean up the semaphore
+ if (-1 == semctl(sem_id, 0, IPC_RMID)) {
+ sprintf(s, "Removing the semaphore failed; errno is %d", errno);
+ say(MY_NAME, s);
+ }
+ }
+ return 0;
+}
+
+
+void get_current_time(char *s) {
+ time_t the_time;
+ struct tm *the_localtime;
+ char *pAscTime;
+
+ the_time = time(NULL);
+ the_localtime = localtime(&the_time);
+ pAscTime = asctime(the_localtime);
+
+ strcpy(s, pAscTime);
+}
diff --git a/demo/premise.py b/demo/premise.py
new file mode 100755
index 0000000..98dd30c
--- /dev/null
+++ b/demo/premise.py
@@ -0,0 +1,106 @@
+# Python modules
+import time
+import os
+import sys
+PY_MAJOR_VERSION = sys.version_info[0]
+# hashlib is only available in Python >= 2.5. I still want to support
+# older Pythons so I import md5 if hashlib is not available. Fortunately
+# md5 can masquerade as hashlib for my purposes.
+try:
+ import hashlib
+except ImportError:
+ import md5 as hashlib
+
+# 3rd party modules
+import sysv_ipc
+
+# Utils for this demo
+import utils
+if PY_MAJOR_VERSION > 2:
+ import utils_for_3 as flex_utils
+else:
+ import utils_for_2 as flex_utils
+
+utils.say("Oooo 'ello, I'm Mrs. Premise!")
+
+params = utils.read_params()
+
+# Create the semaphore & shared memory. I read somewhere that semaphores
+# and shared memory have separate key spaces, so one can safely use the
+# same key for each. This seems to be true in my experience.
+
+# For purposes of simplicity, this demo code makes no allowance for the
+# failure of the semaphore or memory constructors. This is unrealistic
+# because one can never predict whether or not a given key will be available,
+# so your code must *always* be prepared for these functions to fail.
+
+semaphore = sysv_ipc.Semaphore(params["KEY"], sysv_ipc.IPC_CREX)
+memory = sysv_ipc.SharedMemory(params["KEY"], sysv_ipc.IPC_CREX)
+
+# I seed the shared memory with a random value which is the current time.
+what_i_wrote = time.asctime()
+s = what_i_wrote
+
+utils.write_to_memory(memory, what_i_wrote)
+
+for i in range(0, params["ITERATIONS"]):
+ utils.say("iteration %d" % i)
+ if not params["LIVE_DANGEROUSLY"]:
+ # Releasing the semaphore...
+ utils.say("releasing the semaphore")
+ semaphore.release()
+ # ...and wait for it to become available again. In real code it'd be
+ # wise to sleep briefly before calling .acquire() in order to be
+ # polite and give other processes an opportunity to grab the semaphore
+ # while it is free and thereby avoid starvation. But this code is meant
+ # to be a stress test that maximizes the opportunity for shared memory
+ # corruption, and politeness has no place in that.
+ utils.say("acquiring the semaphore...")
+ semaphore.acquire()
+
+ s = utils.read_from_memory(memory)
+
+ # I keep checking the shared memory until something new has been written.
+ while s == what_i_wrote:
+ if not params["LIVE_DANGEROUSLY"]:
+ utils.say("releasing the semaphore")
+ semaphore.release()
+ utils.say("acquiring the semaphore...")
+ semaphore.acquire()
+
+ # Once the call to .acquire() completes, I own the shared resource and
+ # I'm free to read from the memory.
+ s = utils.read_from_memory(memory)
+
+ # What I read must be the md5 of what I wrote or something's gone wrong.
+ if PY_MAJOR_VERSION > 2:
+ what_i_wrote = what_i_wrote.encode()
+
+ try:
+ assert(s == hashlib.md5(what_i_wrote).hexdigest())
+ except AssertionError:
+ flex_utils.raise_error(AssertionError,
+ "Shared memory corruption after %d iterations." % i)
+
+ # MD5 the reply and write back to Mrs. Conclusion.
+ if PY_MAJOR_VERSION > 2:
+ s = s.encode()
+ what_i_wrote = hashlib.md5(s).hexdigest()
+ utils.write_to_memory(memory, what_i_wrote)
+
+
+# Announce for one last time that the semaphore is free again so that
+# Mrs. Conclusion can exit.
+if not params["LIVE_DANGEROUSLY"]:
+ utils.say("Final release of the semaphore followed by a 5 second pause")
+ semaphore.release()
+ time.sleep(5)
+ # ...before beginning to wait until it is free again.
+ utils.say("Final acquisition of the semaphore")
+ semaphore.acquire()
+
+utils.say("Destroying semaphore and shared memory")
+# It'd be more natural to call memory.remove() and semaphore.remove() here,
+# but I'll use the module-level functions instead to demonstrate their use.
+sysv_ipc.remove_shared_memory(memory.id)
+sysv_ipc.remove_semaphore(semaphore.id)
diff --git a/demo/utils.c b/demo/utils.c
new file mode 100644
index 0000000..30780c4
--- /dev/null
+++ b/demo/utils.c
@@ -0,0 +1,128 @@
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <semaphore.h>
+#include <string.h>
+
+
+#include <sys/ipc.h> /* for system's IPC_xxx definitions */
+#include <sys/shm.h> /* for shmget, shmat, shmdt, shmctl */
+#include <sys/sem.h> /* for semget, semctl, semop */
+
+#include "utils.h"
+#include "md5.h"
+
+
+void md5ify(char *inString, char *outString) {
+ md5_state_t state;
+ md5_byte_t digest[16];
+ int i;
+
+ md5_init(&state);
+ md5_append(&state, (const md5_byte_t *)inString, strlen(inString));
+ md5_finish(&state, digest);
+
+ for (i = 0; i < 16; i++)
+ sprintf(&outString[i * 2], "%02x", digest[i]);
+}
+
+void say(const char *pName, char *pMessage) {
+ time_t the_time;
+ struct tm *the_localtime;
+ char timestamp[256];
+
+ the_time = time(NULL);
+
+ the_localtime = localtime(&the_time);
+
+ strftime(timestamp, 255, "%H:%M:%S", the_localtime);
+
+ printf("%s @ %s: %s\n", pName, timestamp, pMessage);
+
+}
+
+
+int release_semaphore(const char *pName, int sem_id, int live_dangerously) {
+ int rc = 0;
+ struct sembuf op[1];
+ char s[1024];
+
+ say(pName, "Releasing the semaphore.");
+
+ if (!live_dangerously) {
+ op[0].sem_num = 0;
+ op[0].sem_op = 1;
+ op[0].sem_flg = 0;
+
+ if (-1 == semop(sem_id, op, (size_t)1)) {
+ sprintf(s, "Releasing the semaphore failed; errno is %d\n", errno);
+ say(pName, s);
+ }
+ }
+
+ return rc;
+}
+
+
+int acquire_semaphore(const char *pName, int sem_id, int live_dangerously) {
+ int rc = 0;
+ struct sembuf op[1];
+ char s[1024];
+
+ say(pName, "Waiting to acquire the semaphore.");
+
+ if (!live_dangerously) {
+ op[0].sem_num = 0;
+ op[0].sem_op = -1;
+ op[0].sem_flg = 0;
+ if (-1 == semop(sem_id, op, (size_t)1)) {
+ sprintf(s, "Acquiring the semaphore failed; errno is %d\n", errno);
+ say(pName, s);
+ }
+ }
+
+ return rc;
+}
+
+
+void read_params(struct param_struct *params) {
+ char line[1024];
+ char name[1024];
+ int value = 0;
+
+ FILE *fp;
+
+ fp = fopen("params.txt", "r");
+
+ while (fgets(line, 1024, fp)) {
+ if (strlen(line) && ('#' == line[0]))
+ ; // comment in input, ignore
+ else {
+ sscanf(line, "%[ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghjiklmnopqrstuvwxyz]=%i\n", name, &value);
+
+ // printf("name = %s, value = %d\n", name, value);
+
+ if (!strcmp(name, "ITERATIONS"))
+ params->iterations = value;
+ if (!strcmp(name, "LIVE_DANGEROUSLY"))
+ params->live_dangerously = value;
+ if (!strcmp(name, "KEY"))
+ params->key = value;
+ if (!strcmp(name, "PERMISSIONS"))
+ params->permissions = value;
+ if (!strcmp(name, "SHM_SIZE"))
+ params->size = value;
+
+ name[0] = '\0';
+ value = 0;
+ }
+ }
+
+ // printf("iterations = %d\n", params->iterations);
+ // printf("danger = %d\n", params->live_dangerously);
+ // printf("key = %d\n", params->key);
+ // printf("permissions = %o\n", params->permissions);
+ // printf("size = %d\n", params->size);
+}
diff --git a/demo/utils.h b/demo/utils.h
new file mode 100644
index 0000000..9564d63
--- /dev/null
+++ b/demo/utils.h
@@ -0,0 +1,17 @@
+struct param_struct {
+ int iterations;
+ int live_dangerously;
+ int key;
+ int permissions;
+ int size;
+};
+
+
+void md5ify(char *, char *);
+void say(const char *, char *);
+int acquire_semaphore(const char *, int, int);
+int release_semaphore(const char *, int, int);
+void read_params(struct param_struct *);
+
+
+
diff --git a/demo/utils.py b/demo/utils.py
new file mode 100644
index 0000000..a1f4db2
--- /dev/null
+++ b/demo/utils.py
@@ -0,0 +1,64 @@
+import time
+import sys
+
+PY_MAJOR_VERSION = sys.version_info[0]
+
+if PY_MAJOR_VERSION > 2:
+ NULL_CHAR = 0
+else:
+ NULL_CHAR = '\0'
+
+def say(s):
+ who = sys.argv[0]
+ if who.endswith(".py"):
+ who = who[:-3]
+ s = "%s@%1.6f: %s" % (who, time.time(), s)
+ print (s)
+
+def write_to_memory(memory, s):
+ say("writing %s " % s)
+ # Pad with NULLs in case I'm communicating with a C program.
+ #memory.write(s + (memory.size - len(s)) * '\0')
+ s += '\0'
+ if PY_MAJOR_VERSION > 2:
+ s = s.encode()
+ memory.write(s)
+
+def read_from_memory(memory):
+ s = memory.read()
+ if PY_MAJOR_VERSION > 2:
+ s = s.decode()
+ i = s.find('\0')
+ if i != -1:
+ s = s[:i]
+ say("read %s" % s)
+
+ return s
+
+def read_params():
+ params = { }
+
+ f = open("params.txt", "r")
+
+ for line in f:
+ line = line.strip()
+ if len(line):
+ if line.startswith('#'):
+ pass # comment in input, ignore
+ else:
+ name, value = line.split('=')
+ name = name.upper().strip()
+
+ if name == "PERMISSIONS":
+ value = int(value, 8)
+ else:
+ value = int(value)
+
+ #print "name = %s, value = %d" % (name, value)
+
+ params[name] = value
+
+ f.close()
+
+ return params
+
diff --git a/demo/utils_for_2.py b/demo/utils_for_2.py
new file mode 100644
index 0000000..c00f914
--- /dev/null
+++ b/demo/utils_for_2.py
@@ -0,0 +1,4 @@
+
+def raise_error(error, message):
+ raise error, message
+
diff --git a/demo/utils_for_3.py b/demo/utils_for_3.py
new file mode 100644
index 0000000..34155ae
--- /dev/null
+++ b/demo/utils_for_3.py
@@ -0,0 +1,4 @@
+
+def raise_error(error, message):
+ raise error(message)
+
diff --git a/demo2/ReadMe.txt b/demo2/ReadMe.txt
new file mode 100644
index 0000000..871eba6
--- /dev/null
+++ b/demo2/ReadMe.txt
@@ -0,0 +1,41 @@
+This demonstrates use of message queues via two applications named after
+Mrs. Premise and Mrs. Conclusion of the Monty Python sketch.
+http://www.youtube.com/watch?v=crIJvcWkVcs
+
+Like those two characters, these programs chat back and forth and the result
+is a lot of nonsense. In this case, what the programs are saying isn't the
+interesting part. What's interesting is how they're doing it.
+
+Mrs. Premise and Mrs. Conclusion (the programs, not the sketch characters)
+communicate with Sys V message queues.
+
+Mrs. Premise starts things off by creating the queue and sending a random
+string (the current time) to it. She then sits in a loop receiving whatever
+message is on the queue. If it is the same message she wrote, she sends it
+back to the queue. If it is a new message, it must be from Mrs. Conclusion.
+
+Meanwhile, Mrs. Conclusion is doing exactly the same thing, except that she
+assumes Mrs. Premise will write the first message.
+
+When either of these programs receives a new message, they send back an
+md5 hash of that message. This serves two purposes. First, it ensures that
+subsequent messages are very different so that if a message somehow gets
+corrupted (say by being partially overwritten by the next message), it will
+not escape notice. Second, it ensures that corruption can be detected if
+it happens, because Mrs. Premise and Mrs. Conclusion can calculate what the
+other's response to their message should be.
+
+Since message queues manage all of the concurrency issues transparently,
+Mrs. Premise and Mrs. Conclusion won't ever find their messages corrupted
+no matter how many messages they exchange. You can experiment with this by
+setting ITERATIONS in params.txt to a very large value.
+
+These programs are not meant as a demonstration on how to make best use of a
+message queue. In fact, they're very badly behaved because they poll the
+queue as fast as possible -- they'll send your CPU usage right up to 100%.
+Remember, they're trying as hard as they can to step one another so as to
+expose any concurrency problems that might be present.
+
+Real code would want to sleep (or do something useful) in between calling
+send() and receive().
+
diff --git a/demo2/SampleIpcConversation.png b/demo2/SampleIpcConversation.png
new file mode 100644
index 0000000..9d11e7a
--- /dev/null
+++ b/demo2/SampleIpcConversation.png
Binary files differ
diff --git a/demo2/cleanup.py b/demo2/cleanup.py
new file mode 100755
index 0000000..3bc2aac
--- /dev/null
+++ b/demo2/cleanup.py
@@ -0,0 +1,17 @@
+import sysv_ipc
+import utils
+
+params = utils.read_params()
+
+
+try:
+ mq = sysv_ipc.MessageQueue(params["KEY"])
+ mq.remove()
+ s = "message queue %d removed" % params["KEY"]
+ print (s)
+except:
+ print ("message queue doesn't exist")
+
+
+
+print ("\nAll clean!") \ No newline at end of file
diff --git a/demo2/conclusion.py b/demo2/conclusion.py
new file mode 100644
index 0000000..3e1701a
--- /dev/null
+++ b/demo2/conclusion.py
@@ -0,0 +1,66 @@
+# Python modules
+import time
+import sys
+PY_MAJOR_VERSION = sys.version_info[0]
+# hashlib is only available in Python >= 2.5. I still want to support
+# older Pythons so I import md5 if hashlib is not available. Fortunately
+# md5 can masquerade as hashlib for my purposes.
+try:
+ import hashlib
+except ImportError:
+ import md5 as hashlib
+
+# 3rd party modules
+import sysv_ipc
+
+# Utils for this demo
+import utils
+if PY_MAJOR_VERSION > 2:
+ import utils_for_3 as flex_utils
+else:
+ import utils_for_2 as flex_utils
+
+params = utils.read_params()
+
+# Mrs. Premise has already created the message queue. I just need a handle
+# to it.
+mq = sysv_ipc.MessageQueue(params["KEY"])
+
+what_i_sent = ""
+
+for i in range(0, params["ITERATIONS"]):
+ utils.say("iteration %d" % i)
+
+ s, _ = mq.receive()
+ s = s.decode()
+ utils.say("Received %s" % s)
+
+ while s == what_i_sent:
+ # Nothing new; give Mrs. Premise another chance to respond.
+ mq.send(s)
+
+ s, _ = mq.receive()
+ s = s.decode()
+ utils.say("Received %s" % s)
+
+ if what_i_sent:
+ if PY_MAJOR_VERSION > 2:
+ what_i_sent = what_i_sent.encode()
+ try:
+ assert(s == hashlib.md5(what_i_sent).hexdigest())
+ except:
+ flex_utils.raise_error(AssertionError,
+ "Message corruption after %d iterations." % i)
+ #else:
+ # When what_i_sent is blank, this is the first message which
+ # I always accept without question.
+
+ # MD5 the reply and write back to Mrs. Premise.
+ s = hashlib.md5(s.encode()).hexdigest()
+ utils.say("Sending %s" % s)
+ mq.send(s)
+ what_i_sent = s
+
+
+utils.say("")
+utils.say("%d iterations complete" % (i + 1))
diff --git a/demo2/params.txt b/demo2/params.txt
new file mode 100644
index 0000000..e847cca
--- /dev/null
+++ b/demo2/params.txt
@@ -0,0 +1,4 @@
+# These parameters control how Mrs. Premise and Mrs. Conclusion behave.
+
+ITERATIONS=1000
+KEY=42
diff --git a/demo2/premise.py b/demo2/premise.py
new file mode 100644
index 0000000..942bba6
--- /dev/null
+++ b/demo2/premise.py
@@ -0,0 +1,71 @@
+# Python modules
+import time
+import sys
+PY_MAJOR_VERSION = sys.version_info[0]
+# hashlib is only available in Python >= 2.5. I still want to support
+# older Pythons so I import md5 if hashlib is not available. Fortunately
+# md5 can masquerade as hashlib for my purposes.
+try:
+ import hashlib
+except ImportError:
+ import md5 as hashlib
+
+# 3rd party modules
+import sysv_ipc
+
+# Utils for this demo
+import utils
+if PY_MAJOR_VERSION > 2:
+ import utils_for_3 as flex_utils
+else:
+ import utils_for_2 as flex_utils
+
+utils.say("Oooo 'ello, I'm Mrs. Premise!")
+
+params = utils.read_params()
+
+# Create the message queue.
+mq = sysv_ipc.MessageQueue(params["KEY"], sysv_ipc.IPC_CREX)
+
+# The first message is a random string (the current time).
+s = time.asctime()
+utils.say("Sending %s" % s)
+mq.send(s)
+what_i_sent = s
+
+for i in range(0, params["ITERATIONS"]):
+ utils.say("iteration %d" % i)
+
+ s, _ = mq.receive()
+ s = s.decode()
+ utils.say("Received %s" % s)
+
+ # If the message is what I wrote, put it back on the queue.
+ while s == what_i_sent:
+ # Nothing new; give Mrs. Conclusion another chance to respond.
+ mq.send(s)
+
+ s, _ = mq.receive()
+ s = s.decode()
+ utils.say("Received %s" % s)
+
+ # What I read must be the md5 of what I wrote or something's
+ # gone wrong.
+ if PY_MAJOR_VERSION > 2:
+ what_i_sent = what_i_sent.encode()
+ try:
+ assert(s == hashlib.md5(what_i_sent).hexdigest())
+ except AssertionError:
+ flex_utils.raise_error(AssertionError, "Message corruption after %d iterations." % i)
+
+ # MD5 the reply and write back to Mrs. Conclusion.
+ s = hashlib.md5(s.encode()).hexdigest()
+ utils.say("Sending %s" % s)
+ mq.send(s)
+ what_i_sent = s
+
+utils.say("")
+utils.say("%d iterations complete" % (i + 1))
+
+utils.say("Destroying the message queue.")
+mq.remove()
diff --git a/demo2/utils.py b/demo2/utils.py
new file mode 100644
index 0000000..0c66bee
--- /dev/null
+++ b/demo2/utils.py
@@ -0,0 +1,41 @@
+import time
+import sys
+
+def say(s):
+ who = sys.argv[0]
+ if who.endswith(".py"):
+ who = who[:-3]
+ s = "%s@%1.6f: %s" % (who, time.time(), s)
+ print (s)
+
+
+def read_params():
+ params = { }
+
+ f = open("params.txt", "r")
+
+ for line in f:
+ line = line.strip()
+ if len(line):
+ if line.startswith('#'):
+ pass # comment in input, ignore
+ else:
+ name, value = line.split('=')
+ name = name.upper().strip()
+
+ if name == "PERMISSIONS":
+ value = int(value, 8)
+ elif "NAME" in name:
+ # This is a string; leave it alone.
+ pass
+ else:
+ value = int(value)
+
+ #print "name = %s, value = %d" % (name, value)
+
+ params[name] = value
+
+ f.close()
+
+ return params
+
diff --git a/demo2/utils_for_2.py b/demo2/utils_for_2.py
new file mode 100644
index 0000000..c00f914
--- /dev/null
+++ b/demo2/utils_for_2.py
@@ -0,0 +1,4 @@
+
+def raise_error(error, message):
+ raise error, message
+
diff --git a/demo2/utils_for_3.py b/demo2/utils_for_3.py
new file mode 100644
index 0000000..34155ae
--- /dev/null
+++ b/demo2/utils_for_3.py
@@ -0,0 +1,4 @@
+
+def raise_error(error, message):
+ raise error(message)
+
diff --git a/demo4/ReadMe.txt b/demo4/ReadMe.txt
new file mode 100644
index 0000000..25f73e9
--- /dev/null
+++ b/demo4/ReadMe.txt
@@ -0,0 +1,15 @@
+This demonstrates the use of a semaphore with a Python context manager (a
+'with' statement).
+
+To run the demo, simply run `python parent.py`. It launches child.py.
+
+The programs parent.py and child.py share a semaphore; the latter acquires the
+semaphore via a context manager. The child process deliberately kills itself via
+an error about half the time (randomly) in order to demonstrate that the
+context manager releases the semaphore regardless of whether or not the context
+block is exited gracefully.
+
+Once the child releases the semaphore, the parent destroys it.
+
+The whole thing happens in less than 10 seconds.
+
diff --git a/demo4/child.py b/demo4/child.py
new file mode 100644
index 0000000..f635769
--- /dev/null
+++ b/demo4/child.py
@@ -0,0 +1,23 @@
+import sysv_ipc
+import time
+import sys
+import random
+
+# The parent passes the semaphore's name to me.
+key = sys.argv[1]
+
+sem = sysv_ipc.Semaphore(int(key))
+
+print('Child: waiting to aquire semaphore ' + key)
+
+with sem:
+ print('Child: semaphore {} aquired; holding for 3 seconds.'.format(sem.key))
+
+ # Flip a coin to determine whether or not to bail out of the context.
+ if random.randint(0, 1):
+ print("Child: raising ValueError to demonstrate unplanned context exit")
+ raise ValueError
+
+ time.sleep(3)
+
+ print('Child: gracefully exiting context (releasing the semaphore).')
diff --git a/demo4/parent.py b/demo4/parent.py
new file mode 100644
index 0000000..79f988c
--- /dev/null
+++ b/demo4/parent.py
@@ -0,0 +1,46 @@
+import subprocess
+import sysv_ipc
+import time
+import os
+
+sem = sysv_ipc.Semaphore(None, sysv_ipc.IPC_CREX, initial_value = 1)
+print("Parent: created semaphore {}.".format(sem.key))
+
+sem.acquire()
+
+# Spawn a child that will wait on this semaphore.
+path, _ = os.path.split(__file__)
+print("Parent: spawning child process...")
+subprocess.Popen(["python", os.path.join(path, 'child.py'), str(sem.key)])
+
+for i in range(3, 0, -1):
+ print("Parent: child process will acquire the semaphore in {} seconds...".format(i))
+ time.sleep(1)
+
+sem.release()
+
+# Sleep for a second to give the child a chance to acquire the semaphore.
+# This technique is a little sloppy because technically the child could still
+# starve, but it's certainly sufficient for this demo.
+time.sleep(1)
+
+# Wait for the child to release the semaphore.
+print("Parent: waiting for the child to release the semaphore.")
+sem.acquire()
+
+# Clean up.
+print("Parent: destroying the semaphore.")
+sem.release()
+sem.remove()
+
+msg = """
+By the time you're done reading this, the parent will have exited and so the
+operating system will have destroyed the semaphore. You can prove that the
+semaphore is gone by running this command and observing that it raises
+sysv_ipc.ExistentialError --
+
+ python -c "import sysv_ipc; sysv_ipc.Semaphore({})"
+
+""".format(sem.key)
+
+print(msg)
diff --git a/ftok_experiment.py b/ftok_experiment.py
new file mode 100644
index 0000000..95fcd21
--- /dev/null
+++ b/ftok_experiment.py
@@ -0,0 +1,50 @@
+"""This is an experiment to see how often ftok() returns duplicate keys for
+different filenames.
+"""
+
+
+import sys
+import os
+import sysv_ipc
+
+if len(sys.argv) == 2:
+ start_path = sys.argv[1]
+else:
+ msg = "Start path? [Default = your home directory] "
+ start_path = raw_input(msg)
+ if not start_path:
+ start_path = "~"
+
+# Expand paths that start with a tilde and then absolutize.
+start_path = os.path.expanduser(start_path)
+start_path = os.path.abspath(start_path)
+
+# For every filename in the tree, generate a key and associate the filename
+# with that key via a dictionary.
+d = { }
+nfilenames = 0
+for path, dirnames, filenames in os.walk(start_path):
+ for filename in filenames:
+ # Fully qualify the path
+ filename = os.path.join(path, filename)
+
+ nfilenames += 1
+
+ #print "Processing %s..." % filename
+
+ key = sysv_ipc.ftok(filename, 42, True)
+
+ if key not in d:
+ d[key] = [ ]
+
+ d[key].append(filename)
+
+# Print statistics, including files with non-unique keys.
+ndups = 0
+for key in d:
+ if len(d[key]) > 1:
+ ndups += len(d[key])
+ print key, d[key]
+
+print "Out of {0} unique filenames, I found {1} duplicate keys.".format(nfilenames, ndups)
+
diff --git a/memory.c b/memory.c
new file mode 100644
index 0000000..5fc5102
--- /dev/null
+++ b/memory.c
@@ -0,0 +1,834 @@
+#include "Python.h"
+#include "structmember.h"
+
+#include "common.h"
+#include "memory.h"
+
+
+/****************** Internal use only **********************/
+PyObject *
+shm_str(SharedMemory *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 *
+shm_repr(SharedMemory *self) {
+#if PY_MAJOR_VERSION > 2
+ return PyUnicode_FromFormat("sysv_ipc.SharedMemory(%ld)", (long)self->key);
+#else
+ return PyString_FromFormat("sysv_ipc.SharedMemory(%ld)", (long)self->key);
+#endif
+}
+
+PyObject *
+shm_attach(SharedMemory *self, int shmat_flags) {
+ DPRINTF("attaching memory @ address %p with id %d using flags 0x%x\n",
+ self->address, self->id, shmat_flags);
+
+ self->address = shmat(self->id, self->address, shmat_flags);
+
+ if ( (void *)-1 == self->address) {
+ self->address = NULL;
+ switch (errno) {
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "No permission to attach");
+ break;
+
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ break;
+
+ case EINVAL:
+ PyErr_SetString(PyExc_ValueError, "Invalid id or address");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+PyObject *
+shm_remove(int shared_memory_id) {
+ struct shmid_ds shm_info;
+
+ DPRINTF("removing shm with id %d\n", shared_memory_id);
+ if (-1 == shmctl(shared_memory_id, IPC_RMID, &shm_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "No shared memory with id %d exists",
+ shared_memory_id);
+ break;
+
+ case EPERM:
+ PyErr_SetString(pPermissionsException,
+ "You do not have permission to remove the shared memory");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+shm_get_value(int shared_memory_id, enum GET_SET_IDENTIFIERS field) {
+ struct shmid_ds shm_info;
+ PyObject *py_value = NULL;
+
+ DPRINTF("Calling shmctl(...IPC_STAT...), field = %d\n", field);
+ if (-1 == shmctl(shared_memory_id, IPC_STAT, &shm_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "No shared memory with id %d exists",
+ shared_memory_id);
+ break;
+
+ case EACCES:
+ PyErr_SetString(pPermissionsException,
+ "You do not have permission to read the shared memory attribute");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+ switch (field) {
+ case SVIFP_SHM_SIZE:
+ py_value = SIZE_T_TO_PY(shm_info.shm_segsz);
+ break;
+
+ case SVIFP_SHM_LAST_ATTACH_TIME:
+ py_value = TIME_T_TO_PY(shm_info.shm_atime);
+ break;
+
+ case SVIFP_SHM_LAST_DETACH_TIME:
+ py_value = TIME_T_TO_PY(shm_info.shm_dtime);
+ break;
+
+ case SVIFP_SHM_LAST_CHANGE_TIME:
+ py_value = TIME_T_TO_PY(shm_info.shm_ctime);
+ break;
+
+ case SVIFP_SHM_CREATOR_PID:
+ py_value = PID_T_TO_PY(shm_info.shm_cpid);
+ break;
+
+ case SVIFP_SHM_LAST_AT_DT_PID:
+ py_value = PID_T_TO_PY(shm_info.shm_lpid);
+ break;
+
+ case SVIFP_SHM_NUMBER_ATTACHED:
+ // shm_nattch is unsigned
+ // ref: http://www.opengroup.org/onlinepubs/007908799/xsh/sysshm.h.html
+#if PY_MAJOR_VERSION > 2
+ py_value = PyLong_FromUnsignedLong(shm_info.shm_nattch);
+#else
+ py_value = py_int_or_long_from_ulong(shm_info.shm_nattch);
+#endif
+ break;
+
+ case SVIFP_IPC_PERM_UID:
+ py_value = UID_T_TO_PY(shm_info.shm_perm.uid);
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+ py_value = GID_T_TO_PY(shm_info.shm_perm.gid);
+ break;
+
+ case SVIFP_IPC_PERM_CUID:
+ py_value = UID_T_TO_PY(shm_info.shm_perm.cuid);
+ break;
+
+ case SVIFP_IPC_PERM_CGID:
+ py_value = GID_T_TO_PY(shm_info.shm_perm.cgid);
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+ py_value = MODE_T_TO_PY(shm_info.shm_perm.mode);
+ break;
+
+ default:
+ PyErr_Format(pInternalException, "Bad field %d passed to shm_get_value", field);
+ goto error_return;
+ break;
+ }
+
+ return py_value;
+
+ error_return:
+ return NULL;
+}
+
+
+static int
+shm_set_ipc_perm_value(int id, enum GET_SET_IDENTIFIERS field, union ipc_perm_value value) {
+ struct shmid_ds shm_info;
+
+ if (-1 == shmctl(id, IPC_STAT, &shm_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "No shared memory with id %d exists", id);
+ break;
+
+ case EACCES:
+ PyErr_SetString(pPermissionsException,
+ "You do not have permission to read the shared memory attribute");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ switch (field) {
+ case SVIFP_IPC_PERM_UID:
+ shm_info.shm_perm.uid = value.uid;
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+ shm_info.shm_perm.gid = value.gid;
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+ shm_info.shm_perm.mode = value.mode;
+ break;
+
+ default:
+ PyErr_Format(pInternalException,
+ "Bad field %d passed to shm_set_ipc_perm_value",
+ field);
+ goto error_return;
+ break;
+ }
+
+ if (-1 == shmctl(id, IPC_SET, &shm_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "No shared memory with id %d exists", id);
+ break;
+
+ case EPERM:
+ PyErr_SetString(pPermissionsException,
+ "You do not have permission to change the shared memory's attributes");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+
+/****************** Class methods **********************/
+
+
+
+void
+SharedMemory_dealloc(SharedMemory *self) {
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyObject *
+SharedMemory_new(PyTypeObject *type, PyObject *args, PyObject *kwlist) {
+ SharedMemory *self;
+
+ self = (SharedMemory *)type->tp_alloc(type, 0);
+
+ if (NULL != self) {
+ self->key = (key_t)-1;
+ self->id = 0;
+ self->address = NULL;
+ }
+
+ return (PyObject *)self;
+}
+
+
+int
+SharedMemory_init(SharedMemory *self, PyObject *args, PyObject *keywords) {
+ NoneableKey key;
+ int mode = 0600;
+ unsigned long size = 0;
+ int shmget_flags = 0;
+ int shmat_flags = 0;
+ char init_character = ' ';
+ char *keyword_list[ ] = {"key", "flags", "mode", "size", "init_character", NULL};
+ PyObject *py_size = NULL;
+
+ DPRINTF("Inside SharedMemory_init()\n");
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "O&|iikc", keyword_list,
+ &convert_key_param, &key,
+ &shmget_flags, &mode, &size,
+ &init_character))
+ goto error_return;
+
+ mode &= 0777;
+ shmget_flags &= ~0777;
+
+ DPRINTF("key is none = %d, key value = %ld\n", key.is_none, (long)key.value);
+
+ if ( !(shmget_flags & IPC_CREAT) && (shmget_flags & IPC_EXCL) ) {
+ PyErr_SetString(PyExc_ValueError,
+ "IPC_EXCL must be combined with IPC_CREAT");
+ goto error_return;
+ }
+
+ if (key.is_none && ((shmget_flags & IPC_EXCL) != IPC_EXCL)) {
+ PyErr_SetString(PyExc_ValueError,
+ "Key can only be None if IPC_EXCL is set");
+ goto error_return;
+ }
+
+ // When creating a new segment, the default size is PAGE_SIZE.
+ if (((shmget_flags & IPC_CREX) == IPC_CREX) && (!size))
+ size = PAGE_SIZE;
+
+ if (key.is_none) {
+ // (key == None) ==> generate a key for the caller
+ do {
+ errno = 0;
+ self->key = get_random_key();
+
+ DPRINTF("Calling shmget, key=%ld, size=%lu, mode=%o, flags=0x%x\n",
+ (long)self->key, size, mode, shmget_flags);
+ self->id = shmget(self->key, size, mode | shmget_flags);
+ } while ( (-1 == self->id) && (EEXIST == errno) );
+ }
+ else {
+ // (key != None) ==> use key supplied by the caller
+ self->key = key.value;
+
+ DPRINTF("Calling shmget, key=%ld, size=%lu, mode=%o, flags=0x%x\n",
+ (long)self->key, size, mode, shmget_flags);
+ self->id = shmget(self->key, size, mode | shmget_flags);
+ }
+
+ DPRINTF("id == %d\n", self->id);
+
+ if (self->id == -1) {
+ switch (errno) {
+ case EACCES:
+ PyErr_Format(pPermissionsException,
+ "Permission %o cannot be granted on the existing segment",
+ mode);
+ break;
+
+ case EEXIST:
+ PyErr_Format(pExistentialException,
+ "Shared memory with the key %ld already exists",
+ (long)self->key);
+ break;
+
+ case ENOENT:
+ PyErr_Format(pExistentialException,
+ "No shared memory exists with the key %ld", (long)self->key);
+ break;
+
+ case EINVAL:
+ PyErr_SetString(PyExc_ValueError, "The size is invalid");
+ break;
+
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ break;
+
+ case ENOSPC:
+ PyErr_SetString(PyExc_OSError,
+ "Not enough shared memory identifiers available (ENOSPC)");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ // Attach the memory. If no write permissions requested, attach read-only.
+ shmat_flags = (mode & 0200) ? 0 : SHM_RDONLY;
+ if (NULL == shm_attach(self, shmat_flags)) {
+ // Bad news, something went wrong.
+ goto error_return;
+ }
+
+ if ( ((shmget_flags & IPC_CREX) == IPC_CREX) && (!(shmat_flags & SHM_RDONLY)) ) {
+ // Initialize the memory.
+
+ py_size = shm_get_value(self->id, SVIFP_SHM_SIZE);
+
+ if (!py_size)
+ goto error_return;
+ else {
+#if PY_MAJOR_VERSION > 2
+ size = PyLong_AsUnsignedLongMask(py_size);
+#else
+ size = PyInt_AsUnsignedLongMask(py_size);
+#endif
+
+ DPRINTF("memsetting address %p to %lu bytes of ASCII 0x%x (%c)\n", \
+ self->address, size, (int)init_character, init_character);
+ memset(self->address, init_character, size);
+ }
+
+ Py_DECREF(py_size);
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+SharedMemory_attach(SharedMemory *self, PyObject *args) {
+ PyObject *py_address = NULL;
+ void *address = NULL;
+ int flags = 0;
+
+ if (!PyArg_ParseTuple(args, "|Oi", &py_address, &flags))
+ goto error_return;
+
+ if ((!py_address) || (py_address == Py_None))
+ address = NULL;
+ else {
+ if (PyLong_Check(py_address))
+ address = PyLong_AsVoidPtr(py_address);
+ else {
+ PyErr_SetString(PyExc_TypeError, "address must be a long");
+ goto error_return;
+ }
+ }
+
+ self->address = shmat(self->id, address, flags);
+
+ if ((void *)-1 == self->address) {
+ self->address = NULL;
+ switch (errno) {
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "No permission to attach");
+ break;
+
+ case EINVAL:
+ PyErr_SetString(PyExc_ValueError, "Invalid address or flags");
+ break;
+
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+PyObject *
+SharedMemory_detach(SharedMemory *self) {
+ if (-1 == shmdt(self->address)) {
+ self->address = NULL;
+ switch (errno) {
+ case EINVAL:
+ PyErr_SetNone(pNotAttachedException);
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ self->address = NULL;
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+PyObject *
+SharedMemory_read(SharedMemory *self, PyObject *args, PyObject *keywords) {
+ /* Tricky business here. A memory segment's size is a size_t which is
+ ulong or smaller. However, the largest string that Python can
+ construct is of ssize_t which is long or smaller. Therefore, the
+ size and offset variables must be ulongs while the byte_count
+ must be a long (and must not exceed LONG_MAX).
+ Mind your math!
+ */
+ long byte_count = 0;
+ unsigned long offset = 0;
+ unsigned long size;
+ PyObject *py_size;
+ char *keyword_list[ ] = {"byte_count", "offset", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "|lk", keyword_list,
+ &byte_count, &offset))
+ goto error_return;
+
+ if (self->address == NULL) {
+ PyErr_SetString(pNotAttachedException,
+ "Read attempt on unattached memory segment");
+ goto error_return;
+ }
+
+ if ( (py_size = shm_get_value(self->id, SVIFP_SHM_SIZE)) ) {
+#if PY_MAJOR_VERSION > 2
+ size = PyLong_AsUnsignedLongMask(py_size);
+#else
+ size = PyInt_AsUnsignedLongMask(py_size);
+#endif
+ Py_DECREF(py_size);
+ }
+ else
+ goto error_return;
+
+ DPRINTF("offset = %lu, byte_count = %ld, size = %lu\n",
+ offset, byte_count, size);
+
+ if (offset >= size) {
+ PyErr_SetString(PyExc_ValueError, "The offset must be less than the segment size");
+ goto error_return;
+ }
+
+ if (byte_count < 0) {
+ PyErr_SetString(PyExc_ValueError, "The byte_count cannot be negative");
+ goto error_return;
+ }
+
+ /* If the caller didn't specify a byte count or specified one that would
+ read past the end of the segment, return everything from the offset to
+ the end of the segment.
+ Be careful here not to express the second if condition w/addition, e.g.
+ (byte_count + offset > size)
+ It might be more intuitive but since byte_count is a long and offset
+ is a ulong, their sum could cause an arithmetic overflow. */
+ if ((!byte_count) || ((unsigned long)byte_count > size - offset)) {
+ // byte_count needs to be calculated
+ if (size - offset <= (unsigned long)PY_STRING_LENGTH_MAX)
+ byte_count = size - offset;
+ else {
+ // Caller is asking for more bytes than I can stuff into
+ // a Python string.
+ PyErr_Format(PyExc_ValueError,
+ "The byte_count cannot exceed Python's max string length %ld",
+ (long)PY_STRING_LENGTH_MAX);
+ goto error_return;
+ }
+ }
+
+
+#if PY_MAJOR_VERSION > 2
+ return PyBytes_FromStringAndSize(self->address + offset, byte_count);
+#else
+ return PyString_FromStringAndSize(self->address + offset, byte_count);
+#endif
+
+ error_return:
+ return NULL;
+}
+
+
+PyObject *
+SharedMemory_write(SharedMemory *self, PyObject *args, PyObject *kw) {
+ /* See comments for read() regarding "size issues". Note that here
+ Python provides the byte_count so it can't be negative.
+
+ In Python >= 2.5, the Python argument specifier 's#' expects a
+ py_ssize_t for its second parameter. A long is long enough. It might
+ be too big, though, on platforms where a long is larger than
+ py_ssize_t. Therefore I *must* initialize it to 0 so that whatever
+ Python doesn't write to is zeroed out.
+ */
+ unsigned long offset = 0;
+ unsigned long size;
+ PyObject *py_size;
+ char *keyword_list[ ] = {"s", "offset", NULL};
+#if PY_MAJOR_VERSION > 2
+ static char args_format[] = "s*|l";
+ Py_buffer data;
+#else
+ static char args_format[] = "s#|l";
+ typedef struct {
+ const char *buf;
+ long len;
+ } MyBuffer;
+ MyBuffer data;
+ data.len = 0;
+#endif
+
+ if (!PyArg_ParseTupleAndKeywords(args, kw, args_format, keyword_list,
+#if PY_MAJOR_VERSION > 2
+ &data,
+#else
+ &(data.buf), &(data.len),
+#endif
+ &offset))
+ goto error_return;
+
+ if (self->address == NULL) {
+ PyErr_SetString(pNotAttachedException, "Write attempt on unattached memory segment");
+ goto error_return;
+ }
+
+ if ( (py_size = shm_get_value(self->id, SVIFP_SHM_SIZE)) ) {
+#if PY_MAJOR_VERSION > 2
+ size = PyLong_AsUnsignedLongMask(py_size);
+#else
+ size = PyInt_AsUnsignedLongMask(py_size);
+#endif
+ Py_DECREF(py_size);
+ }
+ else
+ goto error_return;
+
+ DPRINTF("write size check; size=%lu, offset=%lu, dat.len=%ld\n",
+ size, offset, data.len);
+
+ if ((unsigned long)data.len > size - offset) {
+ PyErr_SetString(PyExc_ValueError, "Attempt to write past end of memory segment");
+ goto error_return;
+ }
+
+ memcpy((self->address + offset), data.buf, data.len);
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+PyObject *
+SharedMemory_remove(SharedMemory *self) {
+ return shm_remove(self->id);
+}
+
+
+PyObject *
+shm_get_key(SharedMemory *self) {
+ return KEY_T_TO_PY(self->key);
+}
+
+PyObject *
+shm_get_size(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_SIZE);
+}
+
+PyObject *
+shm_get_address(SharedMemory *self) {
+ return PyLong_FromVoidPtr(self->address);
+}
+
+PyObject *
+shm_get_attached(SharedMemory *self) {
+ if (self->address)
+ Py_RETURN_TRUE;
+ else
+ Py_RETURN_FALSE;
+}
+
+PyObject *
+shm_get_last_attach_time(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_LAST_ATTACH_TIME);
+}
+
+PyObject *
+shm_get_last_detach_time(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_LAST_DETACH_TIME);
+}
+
+PyObject *
+shm_get_last_change_time(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_LAST_CHANGE_TIME);
+}
+
+PyObject *
+shm_get_creator_pid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_CREATOR_PID);
+}
+
+PyObject *
+shm_get_last_pid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_LAST_AT_DT_PID);
+}
+
+PyObject *
+shm_get_number_attached(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_SHM_NUMBER_ATTACHED);
+}
+
+PyObject *
+shm_get_uid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_IPC_PERM_UID);
+}
+
+PyObject *
+shm_get_cuid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_IPC_PERM_CUID);
+}
+
+PyObject *
+shm_get_cgid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_IPC_PERM_CGID);
+}
+
+PyObject *
+shm_get_mode(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_IPC_PERM_MODE);
+}
+
+int
+shm_set_uid(SharedMemory *self, PyObject *py_value) {
+ union ipc_perm_value new_value;
+
+#if PY_MAJOR_VERSION > 2
+ if (!PyLong_Check(py_value))
+#else
+ if (!PyInt_Check(py_value))
+#endif
+ {
+ PyErr_SetString(PyExc_TypeError, "Attribute 'uid' must be an integer");
+ goto error_return;
+ }
+
+#if PY_MAJOR_VERSION > 2
+ new_value.uid = PyLong_AsLong(py_value);
+#else
+ new_value.uid = PyInt_AsLong(py_value);
+#endif
+
+ if (((uid_t)-1 == new_value.uid) && PyErr_Occurred()) {
+ // no idea what could have gone wrong -- punt it up to the caller
+ goto error_return;
+ }
+
+ return shm_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_UID, new_value);
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+shm_get_gid(SharedMemory *self) {
+ return shm_get_value(self->id, SVIFP_IPC_PERM_GID);
+}
+
+int
+shm_set_gid(SharedMemory *self, PyObject *py_value) {
+ union ipc_perm_value new_value;
+
+#if PY_MAJOR_VERSION > 2
+ if (!PyLong_Check(py_value))
+#else
+ if (!PyInt_Check(py_value))
+#endif
+ {
+ PyErr_Format(PyExc_TypeError, "attribute 'gid' must be an integer");
+ goto error_return;
+ }
+
+#if PY_MAJOR_VERSION > 2
+ new_value.gid = PyLong_AsLong(py_value);
+#else
+ new_value.gid = PyInt_AsLong(py_value);
+#endif
+
+ if (((gid_t)-1 == new_value.gid) && PyErr_Occurred()) {
+ // no idea what could have gone wrong -- punt it up to the caller
+ goto error_return;
+ }
+
+ return shm_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_GID, new_value);
+
+ error_return:
+ return -1;
+}
+
+int
+shm_set_mode(SharedMemory *self, PyObject *py_value) {
+ union ipc_perm_value new_value;
+
+#if PY_MAJOR_VERSION > 2
+ if (!PyLong_Check(py_value))
+#else
+ if (!PyInt_Check(py_value))
+#endif
+ {
+ PyErr_Format(PyExc_TypeError, "attribute 'mode' must be an integer");
+ goto error_return;
+ }
+
+#if PY_MAJOR_VERSION > 2
+ new_value.mode = PyLong_AsLong(py_value);
+#else
+ new_value.mode = PyInt_AsLong(py_value);
+#endif
+
+ if (((mode_t)-1 == new_value.mode) && PyErr_Occurred()) {
+ // no idea what could have gone wrong -- punt it up to the caller
+ goto error_return;
+ }
+
+ return shm_set_ipc_perm_value(self->id, SVIFP_IPC_PERM_MODE, new_value);
+
+ error_return:
+ return -1;
+}
+
diff --git a/memory.h b/memory.h
new file mode 100644
index 0000000..d6c3cfc
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,57 @@
+typedef struct {
+ PyObject_HEAD
+ key_t key;
+ int id;
+ void *address;
+} SharedMemory;
+
+/* Union for passing values to shm_set_ipc_perm_value() */
+union ipc_perm_value {
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+};
+
+/* Object methods */
+PyObject *SharedMemory_new(PyTypeObject *, PyObject *, PyObject *);
+int SharedMemory_init(SharedMemory *, PyObject *, PyObject *);
+void SharedMemory_dealloc(SharedMemory *);
+PyObject *SharedMemory_attach(SharedMemory *, PyObject *);
+PyObject *SharedMemory_detach(SharedMemory *);
+PyObject *SharedMemory_read(SharedMemory *, PyObject *, PyObject *);
+PyObject *SharedMemory_write(SharedMemory *, PyObject *, PyObject *);
+PyObject *SharedMemory_remove(SharedMemory *);
+
+/* Object attributes (read-write & read-only) */
+
+PyObject *shm_get_uid(SharedMemory *);
+int shm_set_uid(SharedMemory *, PyObject *);
+
+PyObject *shm_get_gid(SharedMemory *);
+int shm_set_gid(SharedMemory *, PyObject *);
+
+PyObject *shm_get_mode(SharedMemory *);
+int shm_set_mode(SharedMemory *, PyObject *);
+
+PyObject *shm_get_key(SharedMemory *);
+PyObject *shm_get_size(SharedMemory *);
+PyObject *shm_get_address(SharedMemory *);
+PyObject *shm_get_attached(SharedMemory *);
+PyObject *shm_get_last_attach_time(SharedMemory *);
+PyObject *shm_get_last_detach_time(SharedMemory *);
+PyObject *shm_get_last_change_time(SharedMemory *);
+PyObject *shm_get_creator_pid(SharedMemory *);
+PyObject *shm_get_last_pid(SharedMemory *);
+PyObject *shm_get_number_attached(SharedMemory *);
+PyObject *shm_get_cuid(SharedMemory *);
+PyObject *shm_get_cgid(SharedMemory *);
+
+PyObject *shm_str(SharedMemory *);
+PyObject *shm_repr(SharedMemory *);
+
+
+/* Utility functions */
+PyObject *shm_remove(int);
+
+PyObject *shm_attach(SharedMemory *, int);
+
diff --git a/mq.c b/mq.c
new file mode 100644
index 0000000..b80f790
--- /dev/null
+++ b/mq.c
@@ -0,0 +1,667 @@
+#include "Python.h"
+#include "structmember.h"
+
+#include "common.h"
+#include "mq.h"
+
+
+PyObject *
+mq_str(MessageQueue *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 *
+mq_repr(MessageQueue *self) {
+#if PY_MAJOR_VERSION > 2
+ return PyUnicode_FromFormat("sysv_ipc.MessageQueue(%ld)", (long)self->key);
+#else
+ return PyString_FromFormat("sysv_ipc.MessageQueue(%ld)", (long)self->key);
+#endif
+}
+
+
+static PyObject *
+get_a_value(int queue_id, enum GET_SET_IDENTIFIERS field) {
+ struct msqid_ds q_info;
+ PyObject *py_value = NULL;
+
+ DPRINTF("Calling msgctl(...IPC_STAT...), field = %d\n", field);
+ if (-1 == msgctl(queue_id, IPC_STAT, &q_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+ switch (field) {
+ case SVIFP_MQ_LAST_SEND_TIME:
+ py_value = TIME_T_TO_PY(q_info.msg_stime);
+ break;
+
+ case SVIFP_MQ_LAST_RECEIVE_TIME:
+ py_value = TIME_T_TO_PY(q_info.msg_rtime);
+ break;
+
+ case SVIFP_MQ_LAST_CHANGE_TIME:
+ py_value = TIME_T_TO_PY(q_info.msg_ctime);
+ break;
+
+ case SVIFP_MQ_CURRENT_MESSAGES:
+ py_value = MSGQNUM_T_TO_PY(q_info.msg_qnum);
+ break;
+
+ case SVIFP_MQ_QUEUE_BYTES_MAX:
+ py_value = MSGLEN_T_TO_PY(q_info.msg_qbytes);
+ break;
+
+ case SVIFP_MQ_LAST_SEND_PID:
+ py_value = PID_T_TO_PY(q_info.msg_lspid);
+ break;
+
+ case SVIFP_MQ_LAST_RECEIVE_PID:
+ py_value = PID_T_TO_PY(q_info.msg_lrpid);
+ break;
+
+ case SVIFP_IPC_PERM_UID:
+ py_value = UID_T_TO_PY(q_info.msg_perm.uid);
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+ py_value = GID_T_TO_PY(q_info.msg_perm.gid);
+ break;
+
+ case SVIFP_IPC_PERM_CUID:
+ py_value = UID_T_TO_PY(q_info.msg_perm.cuid);
+ break;
+
+ case SVIFP_IPC_PERM_CGID:
+ py_value = GID_T_TO_PY(q_info.msg_perm.cgid);
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+ py_value = MODE_T_TO_PY(q_info.msg_perm.mode);
+ break;
+
+ default:
+ PyErr_Format(pInternalException,
+ "Bad field %d passed to get_a_value", field);
+ goto error_return;
+ break;
+ }
+
+ return py_value;
+
+ error_return:
+ return NULL;
+}
+
+
+int
+set_a_value(int id, enum GET_SET_IDENTIFIERS field, PyObject *py_value) {
+ struct msqid_ds mq_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;
+ }
+
+ /* Here I get the current values associated with the queue. 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 msgctl(...IPC_SET...)
+ below will copy uid, gid and mode to the kernel's data structure.
+ */
+ if (-1 == msgctl(id, IPC_STAT, &mq_info)) {
+ switch (errno) {
+ case EACCES:
+ case EPERM:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case EINVAL:
+ PyErr_SetString(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ switch (field) {
+ case SVIFP_IPC_PERM_UID:
+#if PY_MAJOR_VERSION > 2
+ mq_info.msg_perm.uid = PyLong_AsLong(py_value);
+#else
+ mq_info.msg_perm.uid = PyInt_AsLong(py_value);
+#endif
+ break;
+
+ case SVIFP_IPC_PERM_GID:
+#if PY_MAJOR_VERSION > 2
+ mq_info.msg_perm.gid = PyLong_AsLong(py_value);
+#else
+ mq_info.msg_perm.gid = PyInt_AsLong(py_value);
+#endif
+ break;
+
+ case SVIFP_IPC_PERM_MODE:
+#if PY_MAJOR_VERSION > 2
+ mq_info.msg_perm.mode = PyLong_AsLong(py_value);
+#else
+ mq_info.msg_perm.mode = PyInt_AsLong(py_value);
+#endif
+ break;
+
+ case SVIFP_MQ_QUEUE_BYTES_MAX:
+ // A msglen_t is unsigned.
+ // ref: http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/msg.h.html
+#if PY_MAJOR_VERSION > 2
+ mq_info.msg_qbytes = PyLong_AsUnsignedLongMask(py_value);
+#else
+ mq_info.msg_qbytes = PyInt_AsUnsignedLongMask(py_value);
+#endif
+ break;
+
+ default:
+ PyErr_Format(pInternalException,
+ "Bad field %d passed to set_a_value", field);
+ goto error_return;
+ break;
+ }
+
+ if (-1 == msgctl(id, IPC_SET, &mq_info)) {
+ switch (errno) {
+ case EACCES:
+ case EPERM:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case EINVAL:
+ PyErr_SetString(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+mq_get_key(MessageQueue *self) {
+ return KEY_T_TO_PY(self->key);
+}
+
+PyObject *
+mq_get_last_send_time(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_LAST_SEND_TIME);
+}
+
+PyObject *
+mq_get_last_receive_time(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_LAST_RECEIVE_TIME);
+}
+
+PyObject *
+mq_get_last_change_time(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_LAST_CHANGE_TIME);
+}
+
+PyObject *
+mq_get_last_send_pid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_LAST_SEND_PID);
+}
+
+PyObject *
+mq_get_last_receive_pid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_LAST_RECEIVE_PID);
+}
+
+PyObject *
+mq_get_current_messages(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_CURRENT_MESSAGES);
+}
+
+PyObject *
+mq_get_max_size(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_MQ_QUEUE_BYTES_MAX);
+}
+
+int
+mq_set_max_size(MessageQueue *self, PyObject *py_value) {
+ return set_a_value(self->id, SVIFP_MQ_QUEUE_BYTES_MAX, py_value);
+}
+
+PyObject *
+mq_get_mode(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_IPC_PERM_MODE);
+}
+
+int
+mq_set_mode(MessageQueue *self, PyObject *py_value) {
+ return set_a_value(self->id, SVIFP_IPC_PERM_MODE, py_value);
+}
+
+PyObject *
+mq_get_uid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_IPC_PERM_UID);
+}
+
+int
+mq_set_uid(MessageQueue *self, PyObject *py_value) {
+ return set_a_value(self->id, SVIFP_IPC_PERM_UID, py_value);
+}
+
+PyObject *
+mq_get_gid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_IPC_PERM_GID);
+}
+
+int
+mq_set_gid(MessageQueue *self, PyObject *py_value) {
+ return set_a_value(self->id, SVIFP_IPC_PERM_GID, py_value);
+}
+
+PyObject *
+mq_get_c_uid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_IPC_PERM_CUID);
+}
+
+PyObject *
+mq_get_c_gid(MessageQueue *self) {
+ return get_a_value(self->id, SVIFP_IPC_PERM_CGID);
+}
+
+
+PyObject *
+mq_remove(int queue_id) {
+ struct msqid_ds mq_info;
+
+ DPRINTF("calling msgctl(...IPC_RMID...) on id %d\n", queue_id);
+ if (-1 == msgctl(queue_id, IPC_RMID, &mq_info)) {
+ switch (errno) {
+ case EIDRM:
+ case EINVAL:
+ PyErr_Format(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ case EPERM:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+
+void
+MessageQueue_dealloc(MessageQueue *self) {
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyObject *
+MessageQueue_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
+ MessageQueue *self;
+
+ self = (MessageQueue *)type->tp_alloc(type, 0);
+
+ return (PyObject *)self;
+}
+
+
+int
+MessageQueue_init(MessageQueue *self, PyObject *args, PyObject *keywords) {
+ int flags = 0;
+ int mode = 0600;
+ NoneableKey key;
+ unsigned long max_message_size = QUEUE_MESSAGE_SIZE_MAX_DEFAULT;
+ char *keyword_list[ ] = {"key", "flags", "mode", "max_message_size", NULL};
+
+ //MessageQueue(key, [flags = 0, [mode = 0600, [max_message_size = QUEUE_MESSAGE_SIZE_MAX_DEFAULT]])
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "O&|iik", keyword_list,
+ convert_key_param, &key, &flags,
+ &mode, &max_message_size))
+ goto error_return;
+
+ if (max_message_size > QUEUE_MESSAGE_SIZE_MAX) {
+ PyErr_Format(PyExc_ValueError, "The message length must be <= %lu\n",
+ (unsigned long)QUEUE_MESSAGE_SIZE_MAX);
+ goto error_return;
+ }
+
+ 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->max_message_size = max_message_size;
+
+ // 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);
+
+ mode &= 0777;
+
+ if (key.is_none) {
+ // (key == None) ==> generate a key for the caller
+ do {
+ errno = 0;
+ self->key = get_random_key();
+
+ DPRINTF("Calling msgget, key=%ld, flags=0x%x\n",
+ (long)self->key, flags);
+ self->id = msgget(self->key, mode | flags);
+ } while ( (-1 == self->id) && (EEXIST == errno) );
+ }
+ else {
+ // (key != None) ==> use key supplied by the caller
+ self->key = key.value;
+
+ DPRINTF("Calling msgget, key=%ld, flags=0x%x\n", (long)self->key, flags);
+ self->id = msgget(self->key, mode | flags);
+ }
+
+ DPRINTF("id == %d\n", self->id);
+
+ if (self->id == -1) {
+ switch (errno) {
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case EEXIST:
+ PyErr_SetString(pExistentialException,
+ "A queue with the specified key already exists");
+ break;
+
+ case ENOENT:
+ PyErr_SetString(pExistentialException,
+ "No queue exists with the specified key");
+ break;
+
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ break;
+
+ case ENOSPC:
+ PyErr_SetString(PyExc_OSError,
+ "The system limit for message queues has been reached");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+ goto error_return;
+ }
+
+ return 0;
+
+ error_return:
+ return -1;
+}
+
+
+PyObject *
+MessageQueue_send(MessageQueue *self, PyObject *args, PyObject *keywords) {
+ /* In Python >= 2.5, the Python argument specifier 's#' expects a
+ py_ssize_t for its second parameter. A ulong is long enough to hold
+ a py_ssize_t.
+ It might be too big, though, on platforms where a long is larger than
+ py_ssize_t. Therefore I *must* initialize it to 0 so that whatever
+ Python doesn't write to is zeroed out.
+ */
+#if PY_MAJOR_VERSION > 2
+ static char args_format[] = "s*|Oi";
+ Py_buffer user_msg;
+#else
+ static char args_format[] = "s#|Oi";
+ typedef struct {
+ char *buf;
+ long len;
+ } MyBuffer;
+ MyBuffer user_msg;
+ user_msg.len = 0;
+#endif
+ PyObject *py_block = NULL;
+ int flags = 0;
+ int type = 1;
+ int rc;
+ struct queue_message *p_msg = NULL;
+ char *keyword_list[ ] = {"message", "block", "type", NULL};
+
+ // send(message, [block = True, [type = 1]])
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, args_format, keyword_list,
+#if PY_MAJOR_VERSION > 2
+ &user_msg,
+#else
+ &(user_msg.buf), &(user_msg.len),
+#endif
+ &py_block, &type))
+ goto error_return;
+
+ if (type <= 0) {
+ PyErr_SetString(PyExc_ValueError, "The type must be > 0");
+ goto error_return;
+ }
+
+ if (user_msg.len > self->max_message_size) {
+ PyErr_Format(PyExc_ValueError,
+ "The message length exceeds queue's max_message_size (%lu)",
+ self->max_message_size);
+ goto error_return;
+ }
+ // default behavior (when py_block == NULL) is to block/wait.
+ if (py_block && PyObject_Not(py_block))
+ flags |= IPC_NOWAIT;
+
+ p_msg = (struct queue_message *)malloc(offsetof(struct queue_message, message) + user_msg.len);
+
+ DPRINTF("p_msg is %p\n", p_msg);
+
+ if (!p_msg) {
+ PyErr_SetString(PyExc_MemoryError, "Out of memory");
+ goto error_return;
+ }
+
+ memcpy(p_msg->message, user_msg.buf, user_msg.len);
+ p_msg->type = type;
+
+ Py_BEGIN_ALLOW_THREADS
+ DPRINTF("Calling msgsnd(), id=%ld, p_msg=%p, length=%lu, flags=0x%x\n",
+ (long)self->id, p_msg, user_msg.len, flags);
+ rc = msgsnd(self->id, p_msg, (size_t)user_msg.len, flags);
+ Py_END_ALLOW_THREADS
+
+ if (-1 == rc) {
+ switch (errno) {
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case EAGAIN:
+ PyErr_SetString(pBusyException,
+ "The queue is full, or a system-wide limit on the number of queue messages has been reached");
+ break;
+
+ case EIDRM:
+ PyErr_SetString(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ case EINTR:
+ PyErr_SetString(pBaseException, "Signaled while waiting");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+
+#if PY_MAJOR_VERSION > 2
+ PyBuffer_Release(&user_msg);
+#endif
+
+ free(p_msg);
+
+ Py_RETURN_NONE;
+
+ error_return:
+#if PY_MAJOR_VERSION > 2
+ PyBuffer_Release(&user_msg);
+#endif
+ free(p_msg);
+ return NULL;
+}
+
+
+PyObject *
+MessageQueue_receive(MessageQueue *self, PyObject *args, PyObject *keywords) {
+ PyObject *py_block = NULL;
+ PyObject *py_return_tuple = NULL;
+ int flags = 0;
+ int type = 0;
+ ssize_t rc;
+ struct queue_message *p_msg = NULL;
+ char *keyword_list[ ] = {"block", "type", NULL};
+
+ // receive([block = True, [type = 0]])
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "|Oi", keyword_list,
+ &py_block, &type))
+ goto error_return;
+
+ // default behavior (when py_block == NULL) is to block/wait.
+ if (py_block && PyObject_Not(py_block))
+ flags |= IPC_NOWAIT;
+
+ p_msg = (struct queue_message *)malloc(sizeof(struct queue_message) + self->max_message_size);
+
+ DPRINTF("p_msg is %p, size = %lu\n",
+ p_msg, sizeof(struct queue_message) + self->max_message_size);
+
+ if (!p_msg) {
+ PyErr_SetString(PyExc_MemoryError, "Out of memory");
+ goto error_return;
+ }
+
+ p_msg->type = type;
+
+ Py_BEGIN_ALLOW_THREADS;
+ rc = msgrcv(self->id, p_msg, (size_t)self->max_message_size,
+ type, flags);
+ Py_END_ALLOW_THREADS;
+
+ DPRINTF("after msgrcv, p_msg->type=%ld, rc (size)=%ld\n",
+ p_msg->type, (long)rc);
+
+ if ((ssize_t)-1 == rc) {
+ switch (errno) {
+ case EACCES:
+ PyErr_SetString(pPermissionsException, "Permission denied");
+ break;
+
+ case EIDRM:
+ case EINVAL:
+ PyErr_SetString(pExistentialException,
+ "The queue no longer exists");
+ break;
+
+ case EINTR:
+ PyErr_SetString(pBaseException, "Signaled while waiting");
+ break;
+
+ case ENOMSG:
+ PyErr_SetString(pBusyException,
+ "No available messages of the specified type");
+ break;
+
+ default:
+ PyErr_SetFromErrno(PyExc_OSError);
+ break;
+ }
+
+ goto error_return;
+ }
+
+ py_return_tuple = Py_BuildValue("NN",
+#if PY_MAJOR_VERSION > 2
+ PyBytes_FromStringAndSize(p_msg->message, rc),
+ PyLong_FromLong(p_msg->type)
+#else
+ PyString_FromStringAndSize(p_msg->message, rc),
+ PyInt_FromLong(p_msg->type)
+#endif
+ );
+
+ free(p_msg);
+
+ return py_return_tuple;
+
+ error_return:
+ free(p_msg);
+ return NULL;
+}
+
+
+PyObject *
+MessageQueue_remove(MessageQueue *self) {
+ return mq_remove(self->id);
+}
+
diff --git a/mq.h b/mq.h
new file mode 100644
index 0000000..b5af78f
--- /dev/null
+++ b/mq.h
@@ -0,0 +1,79 @@
+#include <limits.h> // for definition of SSIZE_MAX
+
+typedef struct {
+ PyObject_HEAD
+ key_t key;
+ int id;
+ unsigned long max_message_size;
+} MessageQueue;
+
+/* Message queue message struct for send() & receive()
+On many systems this is defined in sys/msg.h already, but it's better
+for me to define it here. Name it something other than msgbuf to avoid
+conflict with the struct that the OS header file might define.
+*/
+struct queue_message {
+ long type;
+ char message[];
+};
+
+/* Maximum message size is limited by (a) the largest Python string I can
+create and (b) SSIZE_T_MAX. The latter restriction comes from the spec which
+says, "If the value of msgsz is greater than {SSIZE_MAX}, the result is
+implementation-defined."
+ref: http://www.opengroup.org/onlinepubs/000095399/functions/msgrcv.html
+*/
+#if SSIZE_MAX > PY_STRING_LENGTH_MAX
+#define QUEUE_MESSAGE_SIZE_MAX PY_STRING_LENGTH_MAX
+#else
+#define QUEUE_MESSAGE_SIZE_MAX SSIZE_MAX
+#endif
+
+/* The max message size is probably a very big number, and since a
+max-sized buffer is allocated every time receive() is called, it would be
+ugly if the default message size for new queues was the same as the max.
+In addition, many operating systems limit the entire queue to 2048 bytes,
+so defaulting the max message to something larger seems a bit stupid.
+
+This value is also present in numeric form in ReadMe.html, so if you
+change it here, change it there too.
+*/
+#define QUEUE_MESSAGE_SIZE_MAX_DEFAULT 2048
+
+/* Object methods */
+PyObject *MessageQueue_new(PyTypeObject *, PyObject *, PyObject *);
+int MessageQueue_init(MessageQueue *, PyObject *, PyObject *);
+void MessageQueue_dealloc(MessageQueue *);
+PyObject *MessageQueue_send(MessageQueue *, PyObject *, PyObject *);
+PyObject *MessageQueue_receive(MessageQueue *, PyObject *, PyObject *);
+PyObject *MessageQueue_remove(MessageQueue *);
+
+/* Object attributes (read-write & read-only) */
+PyObject *mq_get_mode(MessageQueue *);
+int mq_set_mode(MessageQueue *, PyObject *);
+
+PyObject *mq_get_uid(MessageQueue *);
+int mq_set_uid(MessageQueue *, PyObject *);
+
+PyObject *mq_get_gid(MessageQueue *);
+int mq_set_gid(MessageQueue *, PyObject *);
+
+PyObject *mq_get_max_size(MessageQueue *);
+int mq_set_max_size(MessageQueue *, PyObject *);
+
+PyObject *mq_get_key(MessageQueue *);
+PyObject *mq_get_last_send_time(MessageQueue *);
+PyObject *mq_get_last_receive_time(MessageQueue *);
+PyObject *mq_get_last_change_time(MessageQueue *);
+PyObject *mq_get_last_send_pid(MessageQueue *);
+PyObject *mq_get_last_receive_pid(MessageQueue *);
+PyObject *mq_get_current_messages(MessageQueue *);
+PyObject *mq_get_c_uid(MessageQueue *);
+PyObject *mq_get_c_gid(MessageQueue *);
+
+PyObject *mq_str(MessageQueue *);
+PyObject *mq_repr(MessageQueue *);
+
+/* Misc. */
+PyObject *mq_remove(int);
+
diff --git a/prober.py b/prober.py
new file mode 100644
index 0000000..08f5fe0
--- /dev/null
+++ b/prober.py
@@ -0,0 +1,186 @@
+import os.path
+import os
+import subprocess
+import sys
+import distutils.sysconfig
+
+# Set these to None for debugging or subprocess.PIPE to silence compiler
+# warnings and errors.
+STDOUT = subprocess.PIPE
+STDERR = subprocess.PIPE
+# STDOUT = None
+# STDERR = None
+
+# This is the max length that I want a printed line to be.
+MAX_LINE_LENGTH = 78
+
+PYTHON_INCLUDE_DIR = os.path.dirname(distutils.sysconfig.get_config_h_filename())
+#print (PYTHON_INCLUDE_DIR)
+
+def line_wrap_paragraph(s):
+ # Format s with terminal-friendly line wraps.
+ done = False
+ beginning = 0
+ end = MAX_LINE_LENGTH - 1
+ lines = [ ]
+ while not done:
+ if end >= len(s):
+ done = True
+ lines.append(s[beginning:])
+ else:
+ last_space = s[beginning:end].rfind(' ')
+
+ lines.append(s[beginning:beginning + last_space])
+ beginning += (last_space + 1)
+ end = beginning + MAX_LINE_LENGTH - 1
+
+ return lines
+
+
+def print_bad_news(value_name, default):
+ s = "Setup can't determine %s on your system, so it will default to %s which may not be correct." \
+ % (value_name, default)
+ plea = "Please report this message and your operating system info to the package maintainer listed in the README file."
+
+ lines = line_wrap_paragraph(s) + [''] + line_wrap_paragraph(plea)
+
+ border = '*' * MAX_LINE_LENGTH
+
+ s = border + "\n* " + ('\n* '.join(lines)) + '\n' + border
+
+ print (s)
+
+
+def does_build_succeed(filename):
+ # Utility function that returns True if the file compiles and links
+ # successfully, False otherwise.
+ cmd = "cc -Wall -I%s -o ./prober/foo ./prober/%s" % \
+ (PYTHON_INCLUDE_DIR, filename)
+
+ p = subprocess.Popen(cmd, shell=True, stdout=STDOUT, stderr=STDERR)
+
+ # p.wait() returns the process' return code, so 0 implies that
+ # the compile & link succeeded.
+ return not bool(p.wait())
+
+def compile_and_run(filename, linker_options = ""):
+ # Utility function that returns the stdout output from running the
+ # compiled source file; None if the compile fails.
+ cmd = "cc -Wall -I%s -o ./prober/foo %s ./prober/%s" % \
+ (PYTHON_INCLUDE_DIR, linker_options, filename)
+
+ p = subprocess.Popen(cmd, shell=True, stdout=STDOUT, stderr=STDERR)
+
+ if p.wait():
+ # uh-oh, compile failed
+ return None
+ else:
+ s = subprocess.Popen(["./prober/foo"],
+ stdout=subprocess.PIPE).communicate()[0]
+ return s.strip().decode()
+
+
+def sniff_semtimedop():
+ return does_build_succeed("semtimedop_test.c")
+
+
+def sniff_union_semun_defined():
+ # AFAICT the semun union is supposed to be declared in one's code.
+ # However, a lot of legacy code gets this wrong and some header files
+ # define it, e.g.sys/sem.h on OS X where it's #ifdef-ed so that legacy
+ # code won't break. On some systems, it appears and disappears based
+ # on the #define value of _XOPEN_SOURCE.
+ return does_build_succeed("sniff_union_semun_defined.c")
+
+
+def probe_semvmx():
+ # This is the hardcoded default that I chose for two reasons. First,
+ # it's the default on my Mac so I know at least one system needs it
+ # this low. Second, it fits neatly into a 16-bit signed int which
+ # makes me hope that it's low enough to be safe on all systems.
+ semvmx = 32767
+
+ # FIXME -- Ways to get SEMVMX --
+ # 1) Try to compile .c code assuming SEMVMX is #defined
+ # 2) Parse the output from `sysctl kern.sysv.semvmx` (doesn't work
+ # on OS X).
+ # 3) Parse the output from `ipcs -S`
+ # 4) Run .c code that loops, releasing a semaphore until semop()
+ # returns ERANGE or the value goes wonky. OS X lets the latter
+ # happen, which AFAICT is a violation of the spec.
+
+ return semvmx
+
+
+
+def probe_page_size():
+ DEFAULT_PAGE_SIZE = 4096
+
+ page_size = compile_and_run("probe_page_size.c")
+
+ if page_size is None:
+ page_size = DEFAULT_PAGE_SIZE
+ print_bad_news("the value of PAGE_SIZE", page_size)
+
+ return page_size
+
+
+def probe():
+ d = { "KEY_MAX" : "LONG_MAX",
+ "KEY_MIN" : "LONG_MIN"
+ }
+
+ conditionals = [ "_SEM_SEMUN_UNDEFINED" ]
+
+ f = open("VERSION")
+ version = f.read().strip()
+ f.close()
+
+ d["SYSV_IPC_VERSION"] = '"%s"' % version
+ d["PAGE_SIZE"] = probe_page_size()
+ if sniff_semtimedop():
+ d["SEMTIMEDOP_EXISTS"] = ""
+ d["SEMAPHORE_VALUE_MAX"] = probe_semvmx()
+ # Some (all?) Linux platforms #define _SEM_SEMUN_UNDEFINED if it's up
+ # to my code to declare this union, so I use that flag as my standard.
+ if not sniff_union_semun_defined():
+ d["_SEM_SEMUN_UNDEFINED"] = ""
+
+
+
+ msg = """/*
+This header file was generated when you ran setup. Once created, the setup
+process won't overwrite it, so you can adjust the values by hand and
+recompile if you need to.
+
+To recreate this file, just delete it and re-run setup.py.
+
+KEY_MIN, KEY_MAX and SEMAPHORE_VALUE_MAX are stored internally in longs, so
+you should never #define them to anything larger than LONG_MAX.
+*/
+
+"""
+
+ filename = "probe_results.h"
+ if not os.path.exists(filename):
+ lines = [ ]
+
+ for key in d:
+ if key in conditionals:
+ lines.append("#ifndef %s" % key)
+
+ lines.append("#define %s\t\t%s" % (key, d[key]))
+
+ if key in conditionals:
+ lines.append("#endif")
+
+ # A trailing '\n' keeps compilers happy...
+ f = open(filename, "w")
+ f.write(msg + '\n'.join(lines) + '\n')
+ f.close()
+
+ return d
+
+if __name__ == "__main__":
+ s = probe()
+ print (s)
diff --git a/prober/probe_page_size.c b/prober/probe_page_size.c
new file mode 100644
index 0000000..bc72bc1
--- /dev/null
+++ b/prober/probe_page_size.c
@@ -0,0 +1,22 @@
+//#define _XOPEN_SOURCE 500
+#include "Python.h"
+
+#include <stdio.h>
+
+// Code for determining page size swiped from Python's mmapmodule.c
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+static int
+my_getpagesize(void)
+{
+ return sysconf(_SC_PAGESIZE);
+}
+#else
+#include <unistd.h>
+#define my_getpagesize getpagesize
+#endif
+
+int main(void) {
+ printf("%d\n", my_getpagesize());
+
+ return 0;
+}
diff --git a/prober/semtimedop_test.c b/prober/semtimedop_test.c
new file mode 100644
index 0000000..e165629
--- /dev/null
+++ b/prober/semtimedop_test.c
@@ -0,0 +1,11 @@
+//#define _XOPEN_SOURCE 500
+#include "Python.h"
+
+#include <sys/sem.h>
+#include <stdlib.h>
+
+int main(void) {
+ semtimedop(0, NULL, 0, NULL);
+
+ return 0;
+}
diff --git a/prober/sniff_union_semun_defined.c b/prober/sniff_union_semun_defined.c
new file mode 100644
index 0000000..8ec64eb
--- /dev/null
+++ b/prober/sniff_union_semun_defined.c
@@ -0,0 +1,12 @@
+//#define _XOPEN_SOURCE 500
+#include "Python.h"
+
+#include <sys/sem.h>
+
+int main(void) {
+ union semun foo;
+
+ foo.val = 42;
+
+ return 0;
+}
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);
+}
+
diff --git a/semaphore.h b/semaphore.h
new file mode 100644
index 0000000..b4ee63d
--- /dev/null
+++ b/semaphore.h
@@ -0,0 +1,53 @@
+typedef struct {
+ PyObject_HEAD
+ key_t key;
+ int id;
+ short op_flags;
+} Semaphore;
+
+
+/* Object methods */
+PyObject *Semaphore_new(PyTypeObject *type, PyObject *, PyObject *);
+int Semaphore_init(Semaphore *, PyObject *, PyObject *);
+void Semaphore_dealloc(Semaphore *);
+PyObject *Semaphore_enter(Semaphore *);
+PyObject *Semaphore_exit(Semaphore *, PyObject *);
+PyObject *Semaphore_P(Semaphore *, PyObject *, PyObject *);
+PyObject *Semaphore_acquire(Semaphore *, PyObject *, PyObject *);
+PyObject *Semaphore_V(Semaphore *, PyObject *, PyObject *);
+PyObject *Semaphore_release(Semaphore *, PyObject *, PyObject *);
+PyObject *Semaphore_Z(Semaphore *, PyObject *, PyObject *);
+PyObject *Semaphore_remove(Semaphore *);
+
+/* Object attributes (read-write & read-only) */
+PyObject *sem_get_value(Semaphore *);
+int sem_set_value(Semaphore *self, PyObject *py_value);
+
+PyObject *sem_get_block(Semaphore *);
+int sem_set_block(Semaphore *self, PyObject *py_value);
+
+PyObject *sem_get_mode(Semaphore *);
+int sem_set_mode(Semaphore *, PyObject *);
+
+PyObject *sem_get_undo(Semaphore *);
+int sem_set_undo(Semaphore *self, PyObject *py_value);
+
+PyObject *sem_get_uid(Semaphore *);
+int sem_set_uid(Semaphore *, PyObject *);
+
+PyObject *sem_get_gid(Semaphore *);
+int sem_set_gid(Semaphore *, PyObject *);
+
+PyObject *sem_get_key(Semaphore *);
+PyObject *sem_get_c_uid(Semaphore *);
+PyObject *sem_get_c_gid(Semaphore *);
+PyObject *sem_get_last_pid(Semaphore *);
+PyObject *sem_get_waiting_for_nonzero(Semaphore *);
+PyObject *sem_get_waiting_for_zero(Semaphore *);
+PyObject *sem_get_o_time(Semaphore *);
+
+PyObject *sem_str(Semaphore *);
+PyObject *sem_repr(Semaphore *);
+
+/* Utility functions */
+PyObject *sem_remove(int);
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..f1dfc2f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,62 @@
+# Python modules
+import distutils.core as duc
+
+# sysv_ipc installation helper module
+import prober
+
+VERSION = open("VERSION", "r").read().strip()
+
+name = "sysv_ipc"
+description = "System V IPC primitives (semaphores, shared memory and message queues) for Python"
+long_description = open("README", "r").read()
+author = "Philip Semanchuk"
+author_email = "philip@semanchuk.com"
+maintainer = "Philip Semanchuk"
+url = "http://semanchuk.com/philip/sysv_ipc/"
+download_url = "http://semanchuk.com/philip/sysv_ipc/sysv_ipc-%s.tar.gz" % VERSION
+source_files = ["sysv_ipc_module.c", "common.c", "semaphore.c", "memory.c",
+ "mq.c" ]
+# http://pypi.python.org/pypi?:action=list_classifiers
+classifiers = [ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: POSIX :: BSD :: FreeBSD",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: POSIX :: SunOS/Solaris",
+ "Operating System :: POSIX",
+ "Operating System :: Unix",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3",
+ "Topic :: Utilities" ]
+license = "http://creativecommons.org/licenses/BSD/"
+keywords = "ipc inter-process communication semaphore shared memory shm message queue"
+
+prober.probe()
+
+extension = duc.Extension("sysv_ipc",
+ source_files,
+# extra_compile_args=['-E']
+ depends = [ "common.c", "common.h", "memory.c",
+ "memory.h", "mq.c", "mq.h",
+ "probe_results.h", "semaphore.c",
+ "semaphore.h", "sysv_ipc_module.c",
+ ],
+ )
+
+duc.setup(name = name,
+ version = VERSION,
+ description = description,
+ long_description = long_description,
+ author = author,
+ author_email = author_email,
+ maintainer = maintainer,
+ url = url,
+ download_url = download_url,
+ classifiers = classifiers,
+ license = license,
+ keywords = keywords,
+ ext_modules = [ extension ]
+ )
+
diff --git a/sysv_ipc_module.c b/sysv_ipc_module.c
new file mode 100644
index 0000000..fd0dfdf
--- /dev/null
+++ b/sysv_ipc_module.c
@@ -0,0 +1,876 @@
+/*
+sysv_ipc - A Python module for accessing System V semaphores, shared memory
+ and message queues.
+
+Copyright (c) 2008, Philip Semanchuk
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of sysv_ipc nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY Philip Semanchuk ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Philip Semanchuk BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+#include "Python.h"
+#include "structmember.h"
+
+// For memset
+#include <string.h>
+
+// For srand
+#include <stdlib.h>
+#include <time.h>
+
+// For the math surrounding timeouts for semtimedop()
+#include <math.h>
+
+#include "common.h"
+#include "semaphore.h"
+#include "memory.h"
+#include "mq.h"
+
+PyObject *pBaseException;
+PyObject *pInternalException;
+PyObject *pPermissionsException;
+PyObject *pExistentialException;
+PyObject *pBusyException;
+PyObject *pNotAttachedException;
+
+// sysv_ipc_attach() needs this forward declaration of SharedMemoryType
+static PyTypeObject SharedMemoryType;
+
+/*
+
+ Module methods
+
+*/
+
+
+static PyObject *
+sysv_ipc_attach(PyObject *self, PyObject *args, PyObject *keywords) {
+ // Given the id of an extant shared memory segment and (optionally) an
+ // address and shmat flags, attempts to attach the memory. If successful,
+ // returns a new SharedMemory object with the key permanently set to -1.
+ SharedMemory *shm = NULL;
+ PyObject *py_address = NULL;
+ int id = -1;
+ void *address = NULL;
+ int flags = 0;
+ char *keyword_list[ ] = {"id", "address", "flags", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "i|Oi", keyword_list,
+ &id, &py_address, &flags))
+ goto error_return;
+
+ if ((!py_address) || (py_address == Py_None))
+ address = NULL;
+ else {
+ if (PyLong_Check(py_address))
+ address = PyLong_AsVoidPtr(py_address);
+ else {
+ PyErr_SetString(PyExc_TypeError, "address must be a long");
+ goto error_return;
+ }
+ }
+
+ DPRINTF("About to create a new SharedMemory object.\n");
+
+ /* Create a new SharedMemory object. Some tutorials recommend using
+ PyObject_CallObject() to create this, but that invokes the __init__ method
+ which I don't want to do.
+ */
+ shm = (SharedMemory *)PyObject_New(SharedMemory, &SharedMemoryType);
+ shm->id = id;
+ shm->address = address;
+
+ DPRINTF("About to call shm_attach()\n");
+ if (Py_None == shm_attach(shm, flags))
+ // All is well
+ return (PyObject *)shm;
+ else
+ // abandon this object and fall through to the error return below.
+ Py_DECREF(shm);
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+sysv_ipc_ftok(PyObject *self, PyObject *args, PyObject *keywords) {
+ char *path;
+ int id = 0;
+ int silence_warning = 0;
+ char *keyword_list[ ] = {"path", "id", "silence_warning", NULL};
+
+ key_t rc = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "si|i", keyword_list,
+ &path, &id, &silence_warning))
+ goto error_return;
+
+ if (!silence_warning) {
+ DPRINTF("path=%s, id=%d, rc=%ld\n", path, id, rc);
+ PyErr_WarnEx(PyExc_Warning,
+ "Use of ftok() is not recommended; see sysv_ipc documentation", 1);
+ }
+
+ rc = ftok(path, id);
+
+ DPRINTF("path=%s, id=%d, rc=%ld\n", path, id, rc);
+
+ return Py_BuildValue("i", rc);
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+sysv_ipc_remove_semaphore(PyObject *self, PyObject *args) {
+ int id;
+
+ if (!PyArg_ParseTuple(args, "i", &id))
+ goto error_return;
+
+ DPRINTF("removing sem with id %d\n", id);
+ if (NULL == sem_remove(id))
+ goto error_return;
+
+ Py_RETURN_NONE;
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+sysv_ipc_remove_shared_memory(PyObject *self, PyObject *args) {
+ int id;
+
+ if (!PyArg_ParseTuple(args, "i", &id))
+ goto error_return;
+
+ return shm_remove(id);
+
+ error_return:
+ return NULL;
+}
+
+
+static PyObject *
+sysv_ipc_remove_message_queue(PyObject *self, PyObject *args) {
+ int id;
+
+ if (!PyArg_ParseTuple(args, "i", &id))
+ goto error_return;
+
+ return mq_remove(id);
+
+ error_return:
+ return NULL;
+}
+
+
+static PyMemberDef Semaphore_members[] = {
+ {"id", T_INT, offsetof(Semaphore, id), READONLY, "The id assigned by the system"},
+ {NULL} /* Sentinel */
+};
+
+
+static PyMethodDef Semaphore_methods[] = {
+ { "__enter__",
+ (PyCFunction)Semaphore_enter,
+ METH_NOARGS,
+ },
+ { "__exit__",
+ (PyCFunction)Semaphore_exit,
+ METH_VARARGS,
+ },
+ { "P",
+ (PyCFunction)Semaphore_P,
+ METH_VARARGS | METH_KEYWORDS,
+ "Acquire (decrement) the semaphore, waiting if necessary"
+ },
+ { "acquire",
+ (PyCFunction)Semaphore_acquire,
+ METH_VARARGS | METH_KEYWORDS,
+ "Acquire (decrement) the semaphore, waiting if necessary"
+ },
+ { "V",
+ (PyCFunction)Semaphore_V,
+ METH_VARARGS | METH_KEYWORDS,
+ "Release (increment) the semaphore"
+ },
+ { "release",
+ (PyCFunction)Semaphore_release,
+ METH_VARARGS | METH_KEYWORDS,
+ "Release (increment) the semaphore"
+ },
+ { "Z",
+ (PyCFunction)Semaphore_Z,
+ METH_VARARGS | METH_KEYWORDS,
+ "Waits until zee zemaphore is zero"
+ },
+ { "remove",
+ (PyCFunction)Semaphore_remove,
+ METH_NOARGS,
+ "Removes (deletes) the semaphore from the system"
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+
+static PyGetSetDef Semaphore_gets_and_sets[] = {
+ { "key",
+ (getter)sem_get_key,
+ (setter)NULL,
+ "The key passed to the constructor",
+ NULL
+ },
+ { "value",
+ (getter)sem_get_value,
+ (setter)sem_set_value,
+ "The semaphore's current value",
+ NULL
+ },
+ { "undo",
+ (getter)sem_get_undo,
+ (setter)sem_set_undo,
+ "When True, acquire/release operations will be undone when the process exits. Non-portable.",
+ NULL
+ },
+ { "block",
+ (getter)sem_get_block,
+ (setter)sem_set_block,
+ "When True (the default), calls to acquire/release/P/V/Z will wait (block) if the semaphore is busy",
+ NULL
+ },
+ { "mode",
+ (getter)sem_get_mode,
+ (setter)sem_set_mode,
+ "Permissions",
+ NULL
+ },
+ { "uid",
+ (getter)sem_get_uid,
+ (setter)sem_set_uid,
+ "The semaphore's UID",
+ NULL
+ },
+ { "gid",
+ (getter)sem_get_gid,
+ (setter)sem_set_gid,
+ "The semaphore's GID",
+ NULL
+ },
+ { "cuid",
+ (getter)sem_get_c_uid,
+ (setter)NULL,
+ "The semaphore creator's UID. Read only.",
+ NULL
+ },
+ { "cgid",
+ (getter)sem_get_c_gid,
+ (setter)NULL,
+ "The semaphore creator's GID. Read only.",
+ NULL
+ },
+ { "last_pid",
+ (getter)sem_get_last_pid,
+ (setter)NULL,
+ "The id of the last process to call acquire()/release()/Z() on this semaphore. Read only.",
+ NULL
+ },
+ { "waiting_for_nonzero",
+ (getter)sem_get_waiting_for_nonzero,
+ (setter)NULL,
+ "The number of processes waiting for the semaphore to become non-zero. Read only.",
+ NULL
+ },
+ { "waiting_for_zero",
+ (getter)sem_get_waiting_for_zero,
+ (setter)NULL,
+ "The number of processes waiting for the semaphore to become zero. Read only.",
+ NULL
+ },
+ { "o_time",
+ (getter)sem_get_o_time,
+ (setter)NULL,
+ "The last time semop (acquire/release/P/V/Z) was called on this semaphore. Initialized to zero. Read only.",
+ NULL
+ },
+ {NULL} /* Sentinel */
+};
+
+
+
+
+static PyTypeObject SemaphoreType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "sysv_ipc.Semaphore", // tp_name
+ sizeof(Semaphore), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Semaphore_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ (reprfunc)sem_repr, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ (reprfunc)sem_str, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "System V semaphore object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ Semaphore_methods, // tp_methods
+ Semaphore_members, // tp_members
+ Semaphore_gets_and_sets, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Semaphore_init, // tp_init
+ 0, // tp_alloc
+ Semaphore_new, // tp_new
+};
+
+
+
+static PyMemberDef SharedMemory_members[] = {
+ {"id", T_INT, offsetof(SharedMemory, id), READONLY, "The id assigned by the system"},
+ {NULL} /* Sentinel */
+};
+
+
+static PyMethodDef SharedMemory_methods[] = {
+ { "read",
+ (PyCFunction)SharedMemory_read,
+ METH_VARARGS | METH_KEYWORDS,
+ "Read n bytes from the shared memory at the given offset into a Python string"
+ },
+ { "write",
+ (PyCFunction)SharedMemory_write,
+ METH_VARARGS | METH_KEYWORDS,
+ "Write the string to the shared memory at the offset given"
+ },
+ { "remove",
+ (PyCFunction)SharedMemory_remove,
+ METH_NOARGS,
+ "Removes (deletes) the shared memory from the system"
+ },
+ { "attach",
+ (PyCFunction)SharedMemory_attach,
+ METH_VARARGS,
+ "Attaches the shared memory"
+ },
+ { "detach",
+ (PyCFunction)SharedMemory_detach,
+ METH_NOARGS,
+ "Detaches the shared memory"
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+
+static PyGetSetDef SharedMemory_gets_and_sets[] = {
+ { "key",
+ (getter)shm_get_key,
+ (setter)NULL,
+ "The key passed to the constructor. Read only.",
+ NULL
+ },
+ { "size",
+ (getter)shm_get_size,
+ (setter)NULL,
+ "The size of the segment in bytes. Read only.",
+ NULL
+ },
+ { "address",
+ (getter)shm_get_address,
+ (setter)NULL,
+ "The memory address of the segment. Read only.",
+ NULL
+ },
+ { "attached",
+ (getter)shm_get_attached,
+ (setter)NULL,
+ "True if the segment is attached. Read only.",
+ NULL
+ },
+ { "last_attach_time",
+ (getter)shm_get_last_attach_time,
+ (setter)NULL,
+ "The most recent time this segment was attached. Read only.",
+ NULL
+ },
+ { "last_detach_time",
+ (getter)shm_get_last_detach_time,
+ (setter)NULL,
+ "The most recent time this segment was detached. Read only.",
+ NULL
+ },
+ { "last_change_time",
+ (getter)shm_get_last_change_time,
+ (setter)NULL,
+ "The time of the most recent change to this segment's uid, gid, mode, or the time the segment was removed. Read only.",
+ NULL
+ },
+ { "creator_pid",
+ (getter)shm_get_creator_pid,
+ (setter)NULL,
+ "The process id of the creator. Read only.",
+ NULL
+ },
+ { "last_pid",
+ (getter)shm_get_last_pid,
+ (setter)NULL,
+ "The id of the process that performed the most recent attach or detach. Read only.",
+ NULL
+ },
+ { "number_attached",
+ (getter)shm_get_number_attached,
+ (setter)NULL,
+ "The current number of attached processes. Read only.",
+ NULL
+ },
+ { "uid",
+ (getter)shm_get_uid,
+ (setter)shm_set_uid,
+ "The segment's UID.",
+ NULL
+ },
+ { "gid",
+ (getter)shm_get_gid,
+ (setter)shm_set_gid,
+ "The segment's GID.",
+ NULL
+ },
+ { "cuid",
+ (getter)shm_get_cuid,
+ (setter)NULL,
+ "The UID of the segment's creator. Read only.",
+ NULL
+ },
+ { "cgid",
+ (getter)shm_get_cgid,
+ (setter)NULL,
+ "The GID of the segment's creator. Read only.",
+ NULL
+ },
+ { "mode",
+ (getter)shm_get_mode,
+ (setter)shm_set_mode,
+ "Permissions.",
+ NULL
+ },
+ {NULL} /* Sentinel */
+};
+
+
+
+static PyTypeObject SharedMemoryType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "sysv_ipc.SharedMemory", // tp_name
+ sizeof(SharedMemory), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)SharedMemory_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ (reprfunc)shm_repr, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ (reprfunc)shm_str, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "System V shared memory object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ SharedMemory_methods, // tp_methods
+ SharedMemory_members, // tp_members
+ SharedMemory_gets_and_sets, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)SharedMemory_init, // tp_init
+ 0, // tp_alloc
+ SharedMemory_new, // tp_new
+};
+
+
+
+
+static PyMemberDef MessageQueue_members[] = {
+ {"id", T_INT, offsetof(MessageQueue, id), READONLY, "Message queue id"},
+ {NULL} /* Sentinel */
+};
+
+
+static PyMethodDef MessageQueue_methods[] = {
+ { "send",
+ (PyCFunction)MessageQueue_send,
+ METH_VARARGS | METH_KEYWORDS,
+ "Place a message on the queue"
+ },
+ { "receive",
+ (PyCFunction)MessageQueue_receive,
+ METH_VARARGS | METH_KEYWORDS,
+ "Receive a message from the queue"
+ },
+ { "remove",
+ (PyCFunction)MessageQueue_remove,
+ METH_NOARGS,
+ "Removes (deletes) the queue from the system"
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+
+static PyGetSetDef MessageQueue_gets_and_sets[] = {
+ { "key",
+ (getter)mq_get_key,
+ (setter)NULL,
+ "The key passed to the constructor.",
+ NULL
+ },
+ { "last_send_time",
+ (getter)mq_get_last_send_time,
+ (setter)NULL,
+ "A Unix timestamp representing the last time a message was sent.",
+ NULL
+ },
+ { "last_receive_time",
+ (getter)mq_get_last_receive_time,
+ (setter)NULL,
+ "A Unix timestamp representing the last time a message was received.",
+ NULL
+ },
+ { "last_change_time",
+ (getter)mq_get_last_change_time,
+ (setter)NULL,
+ "A Unix timestamp representing the last time the queue was changed.",
+ NULL
+ },
+ { "current_messages",
+ (getter)mq_get_current_messages,
+ (setter)NULL,
+ "The number of messages currently in the queue",
+ NULL
+ },
+ { "last_send_pid",
+ (getter)mq_get_last_send_pid,
+ (setter)NULL,
+ "The id of the last process which sent via the queue",
+ NULL},
+ { "last_receive_pid",
+ (getter)mq_get_last_receive_pid,
+ (setter)NULL,
+ "The id of the last process which received from the queue",
+ NULL
+ },
+ { "max_size",
+ (getter)mq_get_max_size,
+ (setter)mq_set_max_size,
+ "The maximum size of the queue (in bytes). Read-write if you have sufficient privileges.",
+ NULL
+ },
+ { "mode",
+ (getter)mq_get_mode,
+ (setter)mq_set_mode,
+ "Permissions",
+ NULL
+ },
+ { "uid",
+ (getter)mq_get_uid,
+ (setter)mq_set_uid,
+ "The queue's UID.",
+ NULL
+ },
+ { "gid",
+ (getter)mq_get_gid,
+ (setter)mq_set_gid,
+ "The queue's GID.",
+ NULL
+ },
+ { "cuid",
+ (getter)mq_get_c_uid,
+ (setter)NULL,
+ "The UID of the queue's creator. Read only.",
+ NULL
+ },
+ { "cgid",
+ (getter)mq_get_c_gid,
+ (setter)NULL,
+ "The GID of the queue's creator. Read only.",
+ NULL
+ },
+ {NULL} /* Sentinel */
+};
+
+
+static PyTypeObject MessageQueueType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "sysv_ipc.MessageQueue", // tp_name
+ sizeof(MessageQueue), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)MessageQueue_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ (reprfunc)mq_repr, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ (reprfunc)mq_str, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags
+ "System V message queue object", // tp_doc
+ 0, // tp_traverse
+ 0, // tp_clear
+ 0, // tp_richcompare
+ 0, // tp_weaklistoffset
+ 0, // tp_iter
+ 0, // tp_iternext
+ MessageQueue_methods, // tp_methods
+ MessageQueue_members, // tp_members
+ MessageQueue_gets_and_sets, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ 0, // tp_descr_get
+ 0, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)MessageQueue_init, // tp_init
+ 0, // tp_alloc
+ MessageQueue_new, // tp_new
+};
+
+
+/*
+
+ Module level stuff
+
+*/
+
+static PyMethodDef module_methods[ ] = {
+ { "attach",
+ (PyCFunction)sysv_ipc_attach,
+ METH_VARARGS | METH_KEYWORDS,
+ "Attaches the memory identified by the id and returns a new SharedMemory object."
+ },
+ { "ftok",
+ (PyCFunction)sysv_ipc_ftok,
+ METH_VARARGS | METH_KEYWORDS,
+ "Calls ftok(). Not recommended; see sysv_ipc documentation."
+ },
+ { "remove_semaphore",
+ (PyCFunction)sysv_ipc_remove_semaphore,
+ METH_VARARGS,
+ "Remove (delete) the semaphore identified by id"
+ },
+ { "remove_shared_memory",
+ (PyCFunction)sysv_ipc_remove_shared_memory,
+ METH_VARARGS,
+ "Remove shared memory identified by id"
+ },
+ { "remove_message_queue",
+ (PyCFunction)sysv_ipc_remove_message_queue,
+ METH_VARARGS,
+ "Remove the message queue identified by id"
+ },
+ {NULL} /* Sentinel */
+};
+
+
+#if PY_MAJOR_VERSION > 2
+static struct PyModuleDef this_module = {
+ PyModuleDef_HEAD_INIT, // m_base
+ "sysv_ipc", // m_name
+ "SYSV IPC module", // m_doc
+ -1, // m_size (space allocated for module globals)
+ module_methods, // m_methods
+ NULL, // m_reload
+ NULL, // m_traverse
+ NULL, // m_clear
+ NULL // m_free
+};
+#endif
+
+
+/* Module init function */
+#if PY_MAJOR_VERSION > 2
+#define SYSV_IPC_INIT_FUNCTION_NAME PyInit_sysv_ipc
+#else
+#define SYSV_IPC_INIT_FUNCTION_NAME initsysv_ipc
+#endif
+
+
+/* Module init function */
+PyMODINIT_FUNC
+SYSV_IPC_INIT_FUNCTION_NAME(void) {
+ PyObject *module;
+ PyObject *module_dict;
+
+ // I seed the random number generator in case I'm asked to make some
+ // random keys.
+ srand((unsigned int)time(NULL));
+
+#if PY_MAJOR_VERSION > 2
+ module = PyModule_Create(&this_module);
+#else
+ module = Py_InitModule3("sysv_ipc", module_methods, "System V IPC module");
+#endif
+
+ if (!module)
+ goto error_return;
+
+ if (PyType_Ready(&SemaphoreType) < 0)
+ goto error_return;
+
+ if (PyType_Ready(&SharedMemoryType) < 0)
+ goto error_return;
+
+ if (PyType_Ready(&MessageQueueType) < 0)
+ goto error_return;
+
+#ifdef SEMTIMEDOP_EXISTS
+ Py_INCREF(Py_True);
+ PyModule_AddObject(module, "SEMAPHORE_TIMEOUT_SUPPORTED", Py_True);
+#else
+ Py_INCREF(Py_False);
+ PyModule_AddObject(module, "SEMAPHORE_TIMEOUT_SUPPORTED", Py_False);
+#endif
+
+ PyModule_AddStringConstant(module, "VERSION", SYSV_IPC_VERSION);
+ PyModule_AddStringConstant(module, "__version__", SYSV_IPC_VERSION);
+ PyModule_AddStringConstant(module, "__copyright__", "Copyright 2008 - 2014 Philip Semanchuk");
+ PyModule_AddStringConstant(module, "__author__", "Philip Semanchuk");
+ PyModule_AddStringConstant(module, "__license__", "BSD");
+
+ PyModule_AddIntConstant(module, "PAGE_SIZE", PAGE_SIZE);
+ PyModule_AddIntConstant(module, "KEY_MIN", KEY_MIN);
+ PyModule_AddIntConstant(module, "KEY_MAX", KEY_MAX);
+ PyModule_AddIntConstant(module, "SEMAPHORE_VALUE_MAX", SEMAPHORE_VALUE_MAX);
+ PyModule_AddIntConstant(module, "IPC_CREAT", IPC_CREAT);
+ PyModule_AddIntConstant(module, "IPC_EXCL", IPC_EXCL);
+ PyModule_AddIntConstant(module, "IPC_CREX", IPC_CREX);
+ PyModule_AddIntConstant(module, "IPC_PRIVATE", IPC_PRIVATE);
+ PyModule_AddIntConstant(module, "SHM_RND", SHM_RND);
+ PyModule_AddIntConstant(module, "SHM_RDONLY", SHM_RDONLY);
+
+
+ // These flags are Linux-specific.
+#ifdef SHM_HUGETLB
+ PyModule_AddIntConstant(module, "SHM_HUGETLB", SHM_HUGETLB);
+#endif
+#ifdef SHM_NORESERVE
+ PyModule_AddIntConstant(module, "SHM_NORESERVE", SHM_NORESERVE);
+#endif
+#ifdef SHM_REMAP
+ PyModule_AddIntConstant(module, "SHM_REMAP", SHM_REMAP);
+#endif
+
+ Py_INCREF(&SemaphoreType);
+ PyModule_AddObject(module, "Semaphore", (PyObject *)&SemaphoreType);
+
+ Py_INCREF(&SharedMemoryType);
+ PyModule_AddObject(module, "SharedMemory", (PyObject *)&SharedMemoryType);
+
+ Py_INCREF(&MessageQueueType);
+ PyModule_AddObject(module, "MessageQueue", (PyObject *)&MessageQueueType);
+
+ // Exceptions
+ if (!(module_dict = PyModule_GetDict(module)))
+ goto error_return;
+
+ if (!(pBaseException = PyErr_NewException("sysv_ipc.Error", NULL, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "Error", pBaseException);
+
+ if (!(pInternalException = PyErr_NewException("sysv_ipc.InternalError", NULL, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "InternalError", pInternalException);
+
+ if (!(pPermissionsException = PyErr_NewException("sysv_ipc.PermissionsError", pBaseException, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "PermissionsError", pPermissionsException);
+
+ if (!(pExistentialException = PyErr_NewException("sysv_ipc.ExistentialError", pBaseException, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "ExistentialError", pExistentialException);
+
+ if (!(pBusyException = PyErr_NewException("sysv_ipc.BusyError", pBaseException, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "BusyError", pBusyException);
+
+ if (!(pNotAttachedException = PyErr_NewException("sysv_ipc.NotAttachedError", pBaseException, NULL)))
+ goto error_return;
+ else
+ PyDict_SetItemString(module_dict, "NotAttachedError", pNotAttachedException);
+
+#if PY_MAJOR_VERSION > 2
+ return module;
+#endif
+
+ error_return:
+#if PY_MAJOR_VERSION > 2
+ return NULL;
+#else
+ ; // Nothing to do
+#endif
+}
+