chiark / gitweb /
systemd-python: wrap sd_login_monitor
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 9 May 2013 22:10:30 +0000 (18:10 -0400)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 9 May 2013 22:13:33 +0000 (18:13 -0400)
man/sd_login_monitor_new.xml
src/python-systemd/_daemon.c
src/python-systemd/_reader.c
src/python-systemd/docs/journal.rst
src/python-systemd/docs/login.rst
src/python-systemd/login.c
src/python-systemd/pyutil.c
src/python-systemd/pyutil.h

index 26af0ea247f3eb358ba7bde6d93152572756165c..261ef1d9d357046c534db762a7507a2c45df5f5e 100644 (file)
@@ -96,7 +96,7 @@
                 <title>Description</title>
 
                 <para><function>sd_login_monitor_new()</function> may
-                be used to monitor login sessions, users, seats and
+                be used to monitor login sessions, users, seats, and
                 virtual machines/containers. Via a monitor object a
                 file descriptor can be integrated into an application
                 defined event loop which is woken up each time a user
index d3b4807368aced0c0a07a825ab3851934fe8bc59..c6b14d46652c0bdd4c16abaea4a3e53357869442 100644 (file)
@@ -40,21 +40,6 @@ PyDoc_STRVAR(module__doc__,
         "running under systemd."
 );
 
-static PyObject* set_error(int r, const char* invalid_message) {
-        assert (r < 0);
-
-        if (r == -EINVAL && invalid_message)
-                PyErr_SetString(PyExc_ValueError, invalid_message);
-        else if (r == -ENOMEM)
-                PyErr_SetString(PyExc_MemoryError, "Not enough memory");
-        else {
-                errno = -r;
-                PyErr_SetFromErrno(PyExc_OSError);
-        }
-
-        return NULL;
-}
-
 
 #if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1
 static int Unicode_FSConverter(PyObject* obj, void *_result) {
@@ -88,8 +73,8 @@ static PyObject* booted(PyObject *self, PyObject *args) {
         assert(args == NULL);
 
         r = sd_booted();
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, NULL, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
@@ -120,8 +105,8 @@ static PyObject* listen_fds(PyObject *self, PyObject *args) {
 #endif
 
         r = sd_listen_fds(unset);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, NULL, NULL))
+                return NULL;
 
         return long_FromLong(r);
 }
@@ -148,8 +133,8 @@ static PyObject* is_fifo(PyObject *self, PyObject *args) {
 #endif
 
         r = sd_is_fifo(fd, path);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, path, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
@@ -176,8 +161,8 @@ static PyObject* is_mq(PyObject *self, PyObject *args) {
 #endif
 
         r = sd_is_mq(fd, path);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, path, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
@@ -200,8 +185,8 @@ static PyObject* is_socket(PyObject *self, PyObject *args) {
                 return NULL;
 
         r = sd_is_socket(fd, family, type, listening);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, NULL, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
@@ -221,12 +206,14 @@ static PyObject* is_socket_inet(PyObject *self, PyObject *args) {
                               &fd, &family, &type, &listening, &port))
                 return NULL;
 
-        if (port < 0 || port > INT16_MAX)
-                return set_error(-EINVAL, "port must fit into uint16_t");
+        if (port < 0 || port > INT16_MAX) {
+                set_error(-EINVAL, NULL, "port must fit into uint16_t");
+                return NULL;
+        }
 
         r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, NULL, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
@@ -260,8 +247,8 @@ static PyObject* is_socket_unix(PyObject *self, PyObject *args) {
 #endif
 
         r = sd_is_socket_unix(fd, type, listening, path, length);
-        if (r < 0)
-                return set_error(r, NULL);
+        if (set_error(r, path, NULL))
+                return NULL;
 
         return PyBool_FromLong(r);
 }
index d20c58d2a8764d6269cee84882f53b4e2bae982d..c4c4fdfe1d19ddc29a2c4b3ab8c027997739ee42 100644 (file)
@@ -38,20 +38,6 @@ typedef struct {
 } Reader;
 static PyTypeObject ReaderType;
 
-static int set_error(int r, const char* path, const char* invalid_message) {
-    if (r >= 0)
-        return r;
-    if (r == -EINVAL && invalid_message)
-        PyErr_SetString(PyExc_ValueError, invalid_message);
-    else if (r == -ENOMEM)
-        PyErr_SetString(PyExc_MemoryError, "Not enough memory");
-    else {
-        errno = -r;
-        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
-    }
-    return -1;
-}
-
 
 PyDoc_STRVAR(module__doc__,
              "Class to reads the systemd journal similar to journalctl.");
@@ -177,7 +163,7 @@ PyDoc_STRVAR(Reader_get_timeout__doc__,
              "Returns a timeout value for usage in poll(), the time since the\n"
              "epoch of clock_gettime(2) in microseconds, or None if no timeout\n"
              "is necessary.\n\n"
-             "The return value must be converted to a relative timeout in \n"
+             "The return value must be converted to a relative timeout in\n"
              "milliseconds if it is to be used as an argument for poll().\n"
              "See man:sd_journal_get_timeout(3) for further discussion.");
 static PyObject* Reader_get_timeout(Reader *self, PyObject *args)
@@ -275,11 +261,7 @@ PyDoc_STRVAR(Reader___exit____doc__,
              "Closes the journal.\n");
 static PyObject* Reader___exit__(Reader *self, PyObject *args)
 {
-    assert(self);
-
-    sd_journal_close(self->j);
-    self->j = NULL;
-    Py_RETURN_NONE;
+    return Reader_close(self, args);
 }
 
 
index 08756b99be2b7e8c1d15382c889da1f45fc19031..e6c42061f34febeaaa77dcc94c39ad90861da958 100644 (file)
@@ -42,7 +42,7 @@ event loop:
   >>> j = journal.Reader()
   >>> j.seek_tail()
   >>> p = select.poll()
-  >>> p.register(j, select.POLLIN)
+  >>> p.register(j, j.get_events())
   >>> p.poll()
   [(3, 1)]
   >>> j.get_next()
index 2cd9d8cbeed50afe21a060432fe435e6934de80c..6b4de64c55340f291d1376d39f2adafa46f30e8c 100644 (file)
@@ -3,3 +3,26 @@
 
 .. automodule:: systemd.login
    :members:
+
+.. autoclass:: Monitor
+   :undoc-members:
+   :inherited-members:
+
+Example: polling for events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This example shows that session/uid/seat/machine events can be waited
+for (using e.g. `poll`). This makes it easy to integrate Monitor in an
+external event loop:
+
+  >>> import select
+  >>> from systemd import login
+  >>> m = login.Monitor("machine")
+  >>> p = select.poll()
+  >>> p.register(m, m.get_events())
+  >>> login.machine_names()
+  []
+  >>> p.poll()
+  [(3, 1)]
+  >>> login.machine_names()
+  ['fedora-19.nspawn']
index 1dbe5ac5bf70f9e5176537cc85dd40bd23122014..57dc080215bc4379874f2a36d85f580e3f4c39bc 100644 (file)
@@ -133,6 +133,198 @@ static PyMethodDef methods[] = {
         {} /* Sentinel */
 };
 
+
+typedef struct {
+        PyObject_HEAD
+        sd_login_monitor *monitor;
+} Monitor;
+static PyTypeObject MonitorType;
+
+static void Monitor_dealloc(Monitor* self)
+{
+    sd_login_monitor_unref(self->monitor);
+    Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyDoc_STRVAR(Monitor__doc__,
+             "Monitor([category]) -> ...\n\n"
+             "Monitor may be used to monitor login sessions, users, seats,\n"
+             "and virtual machines/containers. Monitor provides a file\n"
+             "descriptor which can be integrated in an external event loop.\n"
+             "See man:sd_login_monitor_new(3) for the details about what\n"
+             "can be monitored.");
+static int Monitor_init(Monitor *self, PyObject *args, PyObject *keywds)
+{
+        const char *category = NULL;
+        int r;
+
+        static const char* const kwlist[] = {"category", NULL};
+        if (!PyArg_ParseTupleAndKeywords(args, keywds, "|z", (char**) kwlist,
+                                         &category))
+                return -1;
+
+        Py_BEGIN_ALLOW_THREADS
+        r = sd_login_monitor_new(category, &self->monitor);
+        Py_END_ALLOW_THREADS
+
+        return set_error(r, NULL, "Invalid category");
+}
+
+
+PyDoc_STRVAR(Monitor_fileno__doc__,
+             "fileno() -> int\n\n"
+             "Get a file descriptor to poll for events.\n"
+             "This method wraps sd_login_monitor_get_fd(3).");
+static PyObject* Monitor_fileno(Monitor *self, PyObject *args)
+{
+        int fd = sd_login_monitor_get_fd(self->monitor);
+        set_error(fd, NULL, NULL);
+        if (fd < 0)
+                return NULL;
+        return long_FromLong(fd);
+}
+
+
+PyDoc_STRVAR(Monitor_get_events__doc__,
+             "get_events() -> int\n\n"
+             "Returns a mask of poll() events to wait for on the file\n"
+             "descriptor returned by .fileno().\n\n"
+             "See man:sd_login_monitor_get_events(3) for further discussion.");
+static PyObject* Monitor_get_events(Monitor *self, PyObject *args)
+{
+    int r = sd_login_monitor_get_events(self->monitor);
+    set_error(r, NULL, NULL);
+    if (r < 0)
+        return NULL;
+    return long_FromLong(r);
+}
+
+
+PyDoc_STRVAR(Monitor_get_timeout__doc__,
+             "get_timeout() -> int or None\n\n"
+             "Returns a timeout value for usage in poll(), the time since the\n"
+             "epoch of clock_gettime(2) in microseconds, or None if no timeout\n"
+             "is necessary.\n\n"
+             "The return value must be converted to a relative timeout in\n"
+             "milliseconds if it is to be used as an argument for poll().\n"
+             "See man:sd_login_monitor_get_timeout(3) for further discussion.");
+static PyObject* Monitor_get_timeout(Monitor *self, PyObject *args)
+{
+    int r;
+    uint64_t t;
+
+    r = sd_login_monitor_get_timeout(self->monitor, &t);
+    set_error(r, NULL, NULL);
+    if (r < 0)
+        return NULL;
+
+    if (t == (uint64_t) -1)
+        Py_RETURN_NONE;
+
+    assert_cc(sizeof(unsigned long long) == sizeof(t));
+    return PyLong_FromUnsignedLongLong(t);
+}
+
+
+PyDoc_STRVAR(Monitor_get_timeout_ms__doc__,
+             "get_timeout_ms() -> int\n\n"
+             "Returns a timeout value suitable for usage in poll(), the value\n"
+             "returned by .get_timeout() converted to relative ms, or -1 if\n"
+             "no timeout is necessary.");
+static PyObject* Monitor_get_timeout_ms(Monitor *self, PyObject *args)
+{
+    int r;
+    uint64_t t;
+
+    r = sd_login_monitor_get_timeout(self->monitor, &t);
+    set_error(r, NULL, NULL);
+    if (r < 0)
+        return NULL;
+
+    return absolute_timeout(t);
+}
+
+
+PyDoc_STRVAR(Monitor_close__doc__,
+             "close() -> None\n\n"
+             "Free resources allocated by this Monitor object.\n"
+             "This method invokes sd_login_monitor_unref().\n"
+             "See man:sd_login_monitor_unref(3).");
+static PyObject* Monitor_close(Monitor *self, PyObject *args)
+{
+        assert(self);
+        assert(!args);
+
+        sd_login_monitor_unref(self->monitor);
+        self->monitor = NULL;
+        Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(Monitor_flush__doc__,
+             "flush() -> None\n\n"
+             "Reset the wakeup state of the monitor object.\n"
+             "This method invokes sd_login_monitor_flush().\n"
+             "See man:sd_login_monitor_flush(3).");
+static PyObject* Monitor_flush(Monitor *self, PyObject *args)
+{
+        assert(self);
+        assert(!args);
+
+        sd_login_monitor_flush(self->monitor);
+        Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(Monitor___enter____doc__,
+             "__enter__() -> self\n\n"
+             "Part of the context manager protocol.\n"
+             "Returns self.\n");
+static PyObject* Monitor___enter__(PyObject *self, PyObject *args)
+{
+    assert(self);
+    assert(!args);
+
+    Py_INCREF(self);
+    return self;
+}
+
+
+PyDoc_STRVAR(Monitor___exit____doc__,
+             "__exit__(type, value, traceback) -> None\n\n"
+             "Part of the context manager protocol.\n"
+             "Closes the monitor..\n");
+static PyObject* Monitor___exit__(Monitor *self, PyObject *args)
+{
+        return Monitor_close(self, args);
+}
+
+
+static PyMethodDef Monitor_methods[] = {
+    {"fileno",          (PyCFunction) Monitor_fileno, METH_NOARGS, Monitor_fileno__doc__},
+    {"get_events",      (PyCFunction) Monitor_get_events, METH_NOARGS, Monitor_get_events__doc__},
+    {"get_timeout",     (PyCFunction) Monitor_get_timeout, METH_NOARGS, Monitor_get_timeout__doc__},
+    {"get_timeout_ms",  (PyCFunction) Monitor_get_timeout_ms, METH_NOARGS, Monitor_get_timeout_ms__doc__},
+    {"close",           (PyCFunction) Monitor_close, METH_NOARGS, Monitor_close__doc__},
+    {"flush",           (PyCFunction) Monitor_flush, METH_NOARGS, Monitor_flush__doc__},
+    {"__enter__",       (PyCFunction) Monitor___enter__, METH_NOARGS, Monitor___enter____doc__},
+    {"__exit__",        (PyCFunction) Monitor___exit__, METH_VARARGS, Monitor___exit____doc__},
+    {}  /* Sentinel */
+};
+
+static PyTypeObject MonitorType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    .tp_name = "login.Monitor",
+    .tp_basicsize = sizeof(Monitor),
+    .tp_dealloc = (destructor) Monitor_dealloc,
+    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+    .tp_doc = Monitor__doc__,
+    .tp_methods = Monitor_methods,
+    .tp_init = (initproc) Monitor_init,
+    .tp_new = PyType_GenericNew,
+};
+
+
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
 
@@ -141,10 +333,17 @@ static PyMethodDef methods[] = {
 PyMODINIT_FUNC initlogin(void) {
         PyObject *m;
 
+        if (PyType_Ready(&MonitorType) < 0)
+                return;
+
         m = Py_InitModule3("login", methods, module__doc__);
         if (m == NULL)
                 return;
+
         PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
+
+        Py_INCREF(&MonitorType);
+        PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType);
 }
 #else
 
@@ -159,6 +358,9 @@ static struct PyModuleDef module = {
 PyMODINIT_FUNC PyInit_login(void) {
         PyObject *m;
 
+        if (PyType_Ready(&MonitorType) < 0)
+                return NULL;
+
         m = PyModule_Create(&module);
         if (m == NULL)
                 return NULL;
@@ -168,6 +370,13 @@ PyMODINIT_FUNC PyInit_login(void) {
                 return NULL;
         }
 
+        Py_INCREF(&MonitorType);
+        if (PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType)) {
+                Py_DECREF(&MonitorType);
+                Py_DECREF(m);
+                return NULL;
+        }
+
         return m;
 }
 
index 9510acdddbcc76a6dfb072b19dc8c9ac30c102e3..2f047e643ab4f3337e11786c173e54f7611f094c 100644 (file)
@@ -44,3 +44,17 @@ PyObject* absolute_timeout(uint64_t t) {
         return PyLong_FromLong(msec);
     }
 }
+
+int set_error(int r, const char* path, const char* invalid_message) {
+    if (r >= 0)
+        return r;
+    if (r == -EINVAL && invalid_message)
+        PyErr_SetString(PyExc_ValueError, invalid_message);
+    else if (r == -ENOMEM)
+        PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+    else {
+        errno = -r;
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
+    }
+    return -1;
+}
index 5c7ea37cdbaaf927abb8da9b0f8a8fc936eeb00f..ea88840fa7fc7575c58aeb0bc64ac717012fc9b7 100644 (file)
@@ -28,6 +28,7 @@
 
 void cleanup_Py_DECREFp(PyObject **p);
 PyObject* absolute_timeout(uint64_t t);
+int set_error(int r, const char* path, const char* invalid_message);
 
 #define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp)))