/* 
 * Python interface to the Xen Store Daemon.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2005 Mike Wray Hewlett-Packard
 * Copyright (C) 2005 Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
 * Copyright (C) 2005 XenSource Ltd.
 */

#include <Python.h>

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <xenstore.h>

/** @file
 * Python interface to the Xen Store Daemon (xs).
 */

/* Needed for Python versions earlier than 2.3. */
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC DL_EXPORT(void)
#endif

#define PKG "xen.lowlevel.xs"
#define CLS "xs"

#if PY_MAJOR_VERSION < 3
/* Python 2 compatibility */
#define PyLong_FromLong PyInt_FromLong
#undef PyLong_Check
#define PyLong_Check PyInt_Check
#define PyLong_AsLong PyInt_AsLong
#endif

static PyObject *xs_error;

/** Python wrapper round an xs handle.
 */
typedef struct XsHandle {
    PyObject_HEAD;
    struct xs_handle *xh;
    PyObject *watches;
} XsHandle;

static void xs_set_error(int value)
{
	errno = value;
	PyErr_SetFromErrno(xs_error);
}

static inline struct xs_handle *xshandle(XsHandle *self)
{
    struct xs_handle *xh = self->xh;
    if (!xh)
	xs_set_error(EINVAL);
    return xh;
}

static void remove_watch(XsHandle *xsh, PyObject *token);

static PyObject *match_watch_by_token(XsHandle *self, char **xsval);

static PyObject *none(bool result);

static int parse_transaction_path(XsHandle *self, PyObject *args,
                                  struct xs_handle **xh,
                                  xs_transaction_t *th,
                                  char **path);


#define xspy_read_doc "\n"                              \
	"Read data from a path.\n"                      \
	" transaction [string]: transaction handle\n"	\
	" path [string]:        xenstore path\n"	\
	"\n"                                            \
	"Returns: [string] data read.\n"                \
	"         None if key doesn't exist.\n"         \
	"Raises xen.lowlevel.xs.Error on error.\n"               \
	"\n"

static PyObject *xspy_read(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh;
    xs_transaction_t th;
    char *path;

    char *xsval;
    unsigned int xsval_n;

    if (!parse_transaction_path(self, args, &xh, &th, &path))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    xsval = xs_read(xh, th, path, &xsval_n);
    Py_END_ALLOW_THREADS
    if (xsval) {
        PyObject *val = PyBytes_FromStringAndSize(xsval, xsval_n);
        free(xsval);
        return val;
    }
    else {
        return none(errno == ENOENT);
    }
}


#define xspy_write_doc "\n"					\
	"Write data to a path.\n"				\
	" transaction [string]: transaction handle\n"           \
	" path   [string] : xenstore path to write to\n."	\
	" data   [string] : data to write.\n"			\
	"\n"							\
	"Returns None on success.\n"				\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_write(XsHandle *self, PyObject *args)
{
    static char *arg_spec = "sss#";
    struct xs_handle *xh = xshandle(self);
    xs_transaction_t th;
    char *thstr;
    char *path;
    char *data;
    int data_n;
    bool result;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, arg_spec, &thstr, &path, &data, &data_n))
        return NULL;

    th = strtoul(thstr, NULL, 16);

    Py_BEGIN_ALLOW_THREADS
    result = xs_write(xh, th, path, data, data_n);
    Py_END_ALLOW_THREADS

    return none(result);
}


#define xspy_ls_doc "\n"					\
	"List a directory.\n"					\
	" transaction [string]: transaction handle\n"           \
	" path [string]:        path to list.\n"                \
	"\n"							\
	"Returns: [string array] list of subdirectory names.\n"	\
	"         None if key doesn't exist.\n"			\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_ls(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh;
    xs_transaction_t th;
    char *path;

    char **xsval;
    unsigned int xsval_n;

    if (!parse_transaction_path(self, args, &xh, &th, &path))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    xsval = xs_directory(xh, th, path, &xsval_n);
    Py_END_ALLOW_THREADS

    if (xsval) {
        size_t i;
        PyObject *val = PyList_New(xsval_n);
        for (i = 0; i < xsval_n; i++)
#if PY_MAJOR_VERSION >= 3
            PyList_SetItem(val, i, PyUnicode_FromString(xsval[i]));
#else
            PyList_SetItem(val, i, PyBytes_FromString(xsval[i]));
#endif
        free(xsval);
        return val;
    }
    else {
        return none(errno == ENOENT);
    }
}


#define xspy_mkdir_doc "\n"					\
	"Make a directory.\n"					\
	" transaction [string]: transaction handle.\n"		\
	" path [string]       : path to directory to create.\n"	\
	"\n"							\
	"Returns None on success.\n"				\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_mkdir(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh;
    xs_transaction_t th;
    char *path;

    bool result;

    if (!parse_transaction_path(self, args, &xh, &th, &path))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_mkdir(xh, th, path);
    Py_END_ALLOW_THREADS

    return none(result);
}


#define xspy_rm_doc "\n"                                \
	"Remove a path.\n"                              \
	" transaction [string]: transaction handle\n"	\
	" path [string] : path to remove\n"             \
	"\n"                                            \
	"Returns None on success.\n"                    \
	"Raises xen.lowlevel.xs.Error on error.\n"               \
	"\n"

static PyObject *xspy_rm(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh;
    xs_transaction_t th;
    char *path;

    bool result;

    if (!parse_transaction_path(self, args, &xh, &th, &path))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_rm(xh, th, path);
    Py_END_ALLOW_THREADS

    return none(result || errno == ENOENT);
}


#define xspy_get_permissions_doc "\n"                   \
	"Get the permissions for a path\n"              \
	" transaction [string]: transaction handle\n"	\
	" path [string]:        xenstore path.\n"       \
	"\n"                                            \
	"Returns: permissions array.\n"                 \
	"Raises xen.lowlevel.xs.Error on error.\n"               \
	"\n"

static PyObject *xspy_get_permissions(XsHandle *self, PyObject *args)
{
    static char *arg_spec = "ss";
    char *path = NULL;

    struct xs_handle *xh = xshandle(self);
    struct xs_permissions *perms;
    unsigned int perms_n = 0;
    size_t i;

    xs_transaction_t th;
    char *thstr;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, arg_spec, &thstr, &path))
        return NULL;

    th = strtoul(thstr, NULL, 16);

    Py_BEGIN_ALLOW_THREADS
    perms = xs_get_permissions(xh, th, path, &perms_n);
    Py_END_ALLOW_THREADS

    if (perms) {
        PyObject *val = PyList_New(perms_n);
        for (i = 0; i < perms_n; i++) {
            PyObject *p =
                Py_BuildValue("{s:i,s:i,s:i}",
                              "dom",   perms[i].id,
                              "read",  perms[i].perms & XS_PERM_READ,
                              "write", perms[i].perms & XS_PERM_WRITE);
            PyList_SetItem(val, i, p);
        }

        free(perms);
        return val;
    }
    else {
        PyErr_SetFromErrno(xs_error);
        return NULL;
    }
}

#define xspy_set_permissions_doc "\n"                   \
	"Set the permissions for a path\n"              \
	" transaction [string]: transaction handle\n"	\
	" path  [string]      : xenstore path.\n"	\
	" perms               : permissions.\n"         \
	"\n"                                            \
	"Returns None on success.\n"                    \
	"Raises xen.lowlevel.xs.Error on error.\n"               \
	"\n"

static PyObject *xspy_set_permissions(XsHandle *self, PyObject *args)
{
    char *path;
    PyObject *perms;
    static char *perm_names[] = { "dom", "read", "write", NULL };
    static char *perm_spec = "i|ii";

    struct xs_handle *xh = xshandle(self);
    int i, result;
    struct xs_permissions *xsperms = NULL;
    int xsperms_n;
    PyObject *tuple0 = NULL;

    xs_transaction_t th;
    char *thstr;
    PyObject *ret = NULL;

    if (!xh)
        goto exit;
    if (!PyArg_ParseTuple(args, "ssO", &thstr, &path, &perms))
        goto exit;

    th = strtoul(thstr, NULL, 16);

    if (!PyList_Check(perms)) {
	xs_set_error(EINVAL);
        goto exit;
    }

    xsperms_n = PyList_Size(perms);
    /* NB. alloc +1 so we can change the owner if necessary. */
    xsperms = calloc(xsperms_n + 1, sizeof(struct xs_permissions));
    if (!xsperms) {
	xs_set_error(ENOMEM);
        goto exit;
    }

    tuple0 = PyTuple_New(0);
    if (!tuple0)
        goto exit;

    for (i = 0; i < xsperms_n; i++) {
        /* Read/write perms. Set these. */
        int p_read = 0, p_write = 0;
        PyObject *p = PyList_GetItem(perms, i);
        if (!PyArg_ParseTupleAndKeywords(tuple0, p, perm_spec, perm_names,
                                         &xsperms[i].id, &p_read, &p_write))
            goto exit;
        if (p_read)
            xsperms[i].perms |= XS_PERM_READ;
        if (p_write)
            xsperms[i].perms |= XS_PERM_WRITE;
    }

    /*
     * Is the caller trying to restrict access to the first specified
     * domain? If so then it cannot be owner, so we force dom0 as owner.
     */
    if (xsperms_n && xsperms[0].perms && xsperms[0].id) {
        memmove(&xsperms[1], &xsperms[0], xsperms_n * sizeof(*xsperms));
        xsperms[0].id = xsperms[0].perms = 0;
        xsperms_n++;
    }

    Py_BEGIN_ALLOW_THREADS
    result = xs_set_permissions(xh, th, path, xsperms, xsperms_n);
    Py_END_ALLOW_THREADS
    if (!result) {
        PyErr_SetFromErrno(xs_error);
        goto exit;
    }

    Py_INCREF(Py_None);
    ret = Py_None;

 exit:
    Py_XDECREF(tuple0);
    free(xsperms);
    return ret;
}

#define xspy_watch_doc "\n"						\
	"Watch a path, get notifications when it changes.\n"		\
	" path     [string] : xenstore path.\n"				\
	" token    [string] : returned in watch notification.\n"	\
	"\n"								\
	"Returns None on success.\n"					\
	"Raises xen.lowlevel.xs.Error on error.\n"				\
	"\n"

/* Each 10 bits takes ~ 3 digits, plus one, plus one for nul terminator. */
#define MAX_STRLEN(x) ((sizeof(x) * CHAR_BIT + CHAR_BIT-1) / 10 * 3 + 2)

static PyObject *xspy_watch(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh = xshandle(self);
    char *path;
    PyObject *token;
    char token_str[MAX_STRLEN(unsigned long) + 1];
    int result;
    int i;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "sO", &path, &token))
        return NULL;

    /* Note that we have to store the watch token in the xs->watches list
       before registering the watch with xs_watch, otherwise this function
       races with xs_read_watch.
    */

    for (i = 0; i < PyList_Size(self->watches); i++) {
        if (PyList_GetItem(self->watches, i) == Py_None) {
            PySequence_SetItem(self->watches, i, token);
            break;
        }
    }
    if (i == PyList_Size(self->watches))
        PyList_Append(self->watches, token);

    snprintf(token_str, sizeof(token_str), "%li", (unsigned long)token);
    Py_BEGIN_ALLOW_THREADS
    result = xs_watch(xh, path, token_str);
    Py_END_ALLOW_THREADS

    if (!result)
        remove_watch(self, token);

    return none(result);
}


#define xspy_fileno_doc "\n"                              \
	"Return the FD to poll for notifications when watches fire.\n"   \
	"Returns: [int] file descriptor.\n"                \
	"\n"

static PyObject *xspy_fileno(XsHandle *self)
{
    struct xs_handle *xh = xshandle(self);
    int fd;

    if (!xh)
        return NULL;

    fd = xs_fileno(xh);

    return PyLong_FromLong(fd);
}


#define xspy_check_watch_doc "\n"				\
	"Check for watch notifications without blocking.\n"	\
	"\n"							\
	"Returns: [tuple] (path, token).\n"			\
	"         None if no watches have fired.\n"             \
	"Raises xen.lowlevel.xs.Error on error.\n"	        \
	"\n"

static PyObject *xspy_check_watch(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh = xshandle(self);
    PyObject *val = NULL;
    char **xsval;

    if (!xh)
        return NULL;

    xsval = xs_check_watch(xh);
    if (!xsval) {
        return none(errno == EAGAIN);
    }

    val = match_watch_by_token(self, xsval);
    free(xsval);
    return val;
}

#define xspy_read_watch_doc "\n"				\
	"Read a watch notification.\n"				\
	"\n"							\
	"Returns: [tuple] (path, token).\n"			\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_read_watch(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh = xshandle(self);
    PyObject *val = NULL;
    char **xsval;
    unsigned int num;

    if (!xh)
        return NULL;

again:
    Py_BEGIN_ALLOW_THREADS
    xsval = xs_read_watch(xh, &num);
    Py_END_ALLOW_THREADS
    if (!xsval) {
        PyErr_SetFromErrno(xs_error);
        return val;
    }

    val = match_watch_by_token(self, xsval);
    free(xsval);

    if (!val && errno == EAGAIN) {
        PyErr_Clear();
        goto again;
    }

    return val;
}

#define xspy_unwatch_doc "\n"				\
	"Stop watching a path.\n"			\
	" path  [string] : xenstore path.\n"		\
	" token [string] : token from the watch.\n"	\
	"\n"						\
	"Returns None on success.\n"			\
	"Raises xen.lowlevel.xs.Error on error.\n"		\
	"\n"

static PyObject *xspy_unwatch(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh = xshandle(self);
    char *path;
    PyObject *token;
    char token_str[MAX_STRLEN(unsigned long) + 1];
    int result;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "sO", &path, &token))
        return NULL;

    snprintf(token_str, sizeof(token_str), "%li", (unsigned long)token);
    Py_BEGIN_ALLOW_THREADS
    result = xs_unwatch(xh, path, token_str);
    Py_END_ALLOW_THREADS

    remove_watch(self, token);

    return none(result);
}

#define xspy_transaction_start_doc "\n"				\
	"Start a transaction.\n"				\
	"\n"							\
	"Returns transaction handle on success.\n"		\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_transaction_start(XsHandle *self)
{
    struct xs_handle *xh = xshandle(self);
    xs_transaction_t th;
    char thstr[MAX_STRLEN(unsigned long) + 1];

    if (!xh)
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    th = xs_transaction_start(xh);
    Py_END_ALLOW_THREADS

    if (th == XBT_NULL) {
        PyErr_SetFromErrno(xs_error);
        return NULL;
    }

    snprintf(thstr, sizeof(thstr), "%lX", (unsigned long)th);
#if PY_MAJOR_VERSION >= 3
    return PyUnicode_FromString(thstr);
#else
    return PyBytes_FromString(thstr);
#endif
}

#define xspy_transaction_end_doc "\n"					\
	"End the current transaction.\n"				\
	"Attempts to commit the transaction unless abort is true.\n"	\
	" transaction [string] : transaction handle.\n"			\
	" abort [int]          : abort flag (default 0).\n"		\
	"\n"								\
	"Returns True on success, False if you need to try again.\n"	\
	"Raises xen.lowlevel.xs.Error on error.\n"				\
	"\n"

static PyObject *xspy_transaction_end(XsHandle *self, PyObject *args,
                                      PyObject *kwds)
{
    static char *kwd_spec[] = { "transaction", "abort", NULL };
    static char *arg_spec = "s|i";
    int abort = 0;

    struct xs_handle *xh = xshandle(self);
    bool result;

    xs_transaction_t th;
    char *thstr;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec,
                                     &thstr, &abort))
        return NULL;

    th = strtoul(thstr, NULL, 16);

    Py_BEGIN_ALLOW_THREADS
    result = xs_transaction_end(xh, th, abort);
    Py_END_ALLOW_THREADS

    if (result) {
        Py_INCREF(Py_True);
        return Py_True;
    }
    else if (errno == EAGAIN) {
        Py_INCREF(Py_False);
        return Py_False;
    }
    else {
        PyErr_SetFromErrno(xs_error);
        return NULL;
    }
}


#define xspy_introduce_domain_doc "\n"					\
	"Tell xenstore about a domain so it can talk to it.\n"		\
	" dom  [int]   : domain id\n"					\
	" page [long]  : address of domain's xenstore page\n"		\
	" port [int]   : port the domain is using for xenstore\n"	\
	"\n"								\
	"Returns None on success.\n"					\
	"Raises xen.lowlevel.xs.Error on error.\n"				\
	"\n"

static PyObject *xspy_introduce_domain(XsHandle *self, PyObject *args)
{
    uint32_t dom;
    unsigned long page;
    unsigned int port;

    struct xs_handle *xh = xshandle(self);
    bool result = 0;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "ili", &dom, &page, &port))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_introduce_domain(xh, dom, page, port);
    Py_END_ALLOW_THREADS

    return none(result);
}

#define xspy_set_target_doc "\n"					\
        "Tell xenstore that a domain is targetting another one so it\n" \
        "should let it tinker with it.\n"	                        \
	" dom    [int]   : domain id\n"					\
	" target [int]   : domain id of the target\n"			\
	"\n"								\
	"Returns None on success.\n"					\
	"Raises xen.lowlevel.xs.Error on error.\n"			\
	"\n"

static PyObject *xspy_set_target(XsHandle *self, PyObject *args)
{
    uint32_t dom;
    uint32_t target;

    struct xs_handle *xh = xshandle(self);
    bool result = 0;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "ii", &dom, &target))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_set_target(xh, dom, target);
    Py_END_ALLOW_THREADS

    return none(result);
}

#define xspy_resume_domain_doc "\n"                                \
	"Tell xenstore to clear its shutdown flag for a domain.\n" \
	"This ensures that a subsequent shutdown will fire the\n"  \
	"appropriate watches.\n"                                   \
	" dom [int]: domain id\n"			           \
        "\n"						           \
        "Returns None on success.\n"				   \
        "Raises xen.lowlevel.xs.Error on error.\n"

static PyObject *xspy_resume_domain(XsHandle *self, PyObject *args)
{
    uint32_t dom;

    struct xs_handle *xh = xshandle(self);
    bool result = 0;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "i", &dom))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_resume_domain(xh, dom);
    Py_END_ALLOW_THREADS

    return none(result);
}

#define xspy_release_domain_doc "\n"					\
	"Tell xenstore to release its channel to a domain.\n"		\
	"Unless this is done the domain will not be released.\n"	\
	" dom [int]: domain id\n"					\
	"\n"								\
	"Returns None on success.\n"					\
	"Raises xen.lowlevel.xs.Error on error.\n"				\
	"\n"

static PyObject *xspy_release_domain(XsHandle *self, PyObject *args)
{
    uint32_t dom;

    struct xs_handle *xh = xshandle(self);
    bool result = 0;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "i", &dom))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    result = xs_release_domain(xh, dom);
    Py_END_ALLOW_THREADS

    return none(result);
}


#define xspy_close_doc "\n"			\
	"Close the connection to xenstore.\n"	\
	"\n"					\
	"Returns None on success.\n"		\
	"Raises xen.lowlevel.xs.Error on error.\n"	\
	"\n"

static PyObject *xspy_close(XsHandle *self)
{
    struct xs_handle *xh = xshandle(self);
    int i;

    if (!xh)
        return NULL;

    for (i = 0; i < PyList_Size(self->watches); i++) {
        /* TODO: xs_unwatch watches */
        PySequence_SetItem(self->watches, i, Py_None);
    }

    xs_close(xh);
    self->xh = NULL;

    Py_INCREF(Py_None);
    return Py_None;
}


#define xspy_get_domain_path_doc "\n"			\
	"Return store path of domain, whether or not the domain exists.\n" \
	" domid [int]: domain id\n"			\
	"\n"						\
	"Returns: [string] domain store path.\n"	\
	"Raises xen.lowlevel.xs.Error on error.\n"		\
	"\n"

static PyObject *xspy_get_domain_path(XsHandle *self, PyObject *args)
{
    struct xs_handle *xh = xshandle(self);
    uint32_t domid;
    char *xsval;

    if (!xh)
        return NULL;
    if (!PyArg_ParseTuple(args, "i", &domid))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    xsval = xs_get_domain_path(xh, domid);
    Py_END_ALLOW_THREADS

    if (xsval) {
#if PY_MAJOR_VERSION >= 3
        PyObject *val = PyUnicode_FromString(xsval);
#else
        PyObject *val = PyBytes_FromString(xsval);
#endif
        free(xsval);
        return val;
    }
    else {
        return none(errno == ENOENT);
    }
}


/**
 * Remove the given token from the watches list belonging to the given
 * XsHandle, if present.
 */
static void remove_watch(XsHandle *self, PyObject *token)
{
    int i;

    for (i = 0; i < PyList_Size(self->watches); i++) {
        if (PyList_GetItem(self->watches, i) == token) {
            PySequence_SetItem(self->watches, i, Py_None);
            return;
        }
    }
}


/**
 * Parse transaction and path arguments from the given args and kwds,
 * convert the given self value to an xs_handle, and return all three by
 * reference.
 * 
 * @return 1 on success, in which case *xh, *th, and *path are valid, or 0 on
 * failure.
 */
static int parse_transaction_path(XsHandle *self, PyObject *args,
                                  struct xs_handle **xh,
                                  xs_transaction_t *th,
                                  char **path)
{
    char *thstr;

    *xh = xshandle(self);

    if (!*xh)
        return 0;

    if (!PyArg_ParseTuple(args, "ss", &thstr, path))
        return 0;

    *th = strtoul(thstr, NULL, 16);

    return 1;
}


static PyObject *match_watch_by_token(XsHandle *self, char **xsval)
{
    PyObject *token;
    int i;

    if (sscanf(xsval[XS_WATCH_TOKEN], "%li", (unsigned long *)&token) != 1) {
        xs_set_error(EINVAL);
        return NULL;
    }
    for (i = 0; i < PyList_Size(self->watches); i++) {
        if (token == PyList_GetItem(self->watches, i))
            break;
    }
    if (i == PyList_Size(self->watches)) {
        /* We do not have a registered watch for the one that has just fired.
           Ignore this -- a watch that has been recently deregistered can still
           have watches in transit.
        */
        xs_set_error(EAGAIN);
        return NULL;
    }

    /* Create tuple (path, token). */
    return Py_BuildValue("(sO)", xsval[XS_WATCH_PATH], token);
}


static PyObject *none(bool result)
{
    if (result) {
        Py_INCREF(Py_None);
        return Py_None;
    }
    else {
        PyErr_SetFromErrno(xs_error);
        return NULL;
    }
}


#define XSPY_METH(_name, _args) {               \
    .ml_name  = #_name,				\
    .ml_meth  = (PyCFunction) xspy_ ## _name,	\
    .ml_flags = _args,                          \
    .ml_doc   = xspy_ ## _name ## _doc }

static PyMethodDef xshandle_methods[] = {
    XSPY_METH(read,              METH_VARARGS),
    XSPY_METH(write,             METH_VARARGS),
    XSPY_METH(ls,                METH_VARARGS),
    XSPY_METH(mkdir,             METH_VARARGS),
    XSPY_METH(rm,                METH_VARARGS),
    XSPY_METH(get_permissions,   METH_VARARGS),
    XSPY_METH(set_permissions,   METH_VARARGS),
    XSPY_METH(watch,             METH_VARARGS),
    XSPY_METH(read_watch,        METH_NOARGS),
    XSPY_METH(check_watch,       METH_NOARGS),
    XSPY_METH(unwatch,           METH_VARARGS),
    XSPY_METH(transaction_start, METH_NOARGS),
    XSPY_METH(transaction_end,   METH_VARARGS | METH_KEYWORDS),
    XSPY_METH(introduce_domain,  METH_VARARGS),
    XSPY_METH(set_target,        METH_VARARGS),
    XSPY_METH(resume_domain,     METH_VARARGS),
    XSPY_METH(release_domain,    METH_VARARGS),
    XSPY_METH(close,             METH_NOARGS),
    XSPY_METH(get_domain_path,   METH_VARARGS),
    XSPY_METH(fileno,            METH_NOARGS),
    { NULL /* Sentinel. */ },
};

static PyObject *
xshandle_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    XsHandle *self = (XsHandle *)type->tp_alloc(type, 0);

    if (self == NULL)
        return NULL;

    self->xh = NULL;
    self->watches = PyList_New(0);
    if (!self->watches)
        goto fail;

    return (PyObject *)self;
fail:
    /* Decreasing the object's reference to 0 will result in xshandle_dealloc
       being called. */
    Py_DECREF(self);
    return NULL;
}

static int
xshandle_init(XsHandle *self, PyObject *args, PyObject *kwds)
{
    static char *kwd_spec[] = { "readonly", NULL };
    static char *arg_spec = "|i";
    int readonly = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec,
                                     &readonly))
        goto fail;

    self->xh = xs_open(0);
    if (!self->xh)
        goto fail;

    return 0;

 fail:
    PyErr_SetFromErrno(xs_error);
    return -1;
}

static void xshandle_dealloc(XsHandle *self)
{
    if (self->xh) {
        xs_close(self->xh);
        self->xh = NULL;
    }

    Py_XDECREF(self->watches);

    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyTypeObject xshandle_type = {
#if PY_MAJOR_VERSION >= 3
    .ob_base = { PyObject_HEAD_INIT(NULL) },
#else
    PyObject_HEAD_INIT(NULL)
#endif
    .tp_name = PKG "." CLS,
    .tp_basicsize = sizeof(XsHandle),
    .tp_itemsize = 0,
    .tp_dealloc = (destructor)xshandle_dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "Xenstore connections",
    .tp_methods = xshandle_methods,
    .tp_init = (initproc)xshandle_init,
    .tp_new = xshandle_new,
};

static PyMethodDef xs_methods[] = { { NULL } };

#if PY_MAJOR_VERSION >= 3
static PyModuleDef xs_module = {
    PyModuleDef_HEAD_INIT,
    PKG,     /* name */
    NULL,   /* docstring */
    -1,     /* size of per-interpreter state, -1 means the module use global
               variables */
    xs_methods
};
#endif

#if PY_MAJOR_VERSION >= 3
#define INITERROR return NULL
PyMODINIT_FUNC PyInit_xs(void)
#else
#define INITERROR return
PyMODINIT_FUNC initxs(void)
#endif
{
    PyObject* m;

    if (PyType_Ready(&xshandle_type) < 0)
        INITERROR;

#if PY_MAJOR_VERSION >= 3
    m = PyModule_Create(&xs_module);
#else
    m = Py_InitModule(PKG, xs_methods);
#endif

    if (m == NULL)
        INITERROR;

    xs_error = PyErr_NewException(PKG ".Error", PyExc_RuntimeError, NULL);
    if (xs_error == NULL) {
        Py_DECREF(m);
        INITERROR;
    }

    Py_INCREF(&xshandle_type);
    PyModule_AddObject(m, CLS, (PyObject *)&xshandle_type);

    Py_INCREF(xs_error);
    PyModule_AddObject(m, "Error", xs_error);
#if PY_MAJOR_VERSION >= 3
    return m;
#endif
}


/*
 * Local variables:
 *  mode: C
 *  c-basic-offset: 4
 * End:
 */
