| /* |
| * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Linux-specific functions. |
| */ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE 1 |
| #endif |
| #include <Python.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <mntent.h> |
| #include <features.h> |
| #include <utmp.h> |
| #include <sched.h> |
| #include <linux/version.h> |
| #include <sys/syscall.h> |
| #include <sys/sysinfo.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <linux/sockios.h> |
| #include <linux/if.h> |
| #include <linux/ethtool.h> |
| |
| #include "_psutil_linux.h" |
| |
| /* The minimum number of CPUs allocated in a cpu_set_t */ |
| static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT; |
| |
| // Linux >= 2.6.13 |
| #define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set) |
| |
| // Linux >= 2.6.36 (supposedly) and glibc >= 13 |
| #define PSUTIL_HAVE_PRLIMIT \ |
| (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) && \ |
| (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 13) && \ |
| defined(__NR_prlimit64) |
| |
| #if PSUTIL_HAVE_PRLIMIT |
| #define _FILE_OFFSET_BITS 64 |
| #include <time.h> |
| #include <sys/resource.h> |
| #endif |
| |
| |
| #if PSUTIL_HAVE_IOPRIO |
| enum { |
| IOPRIO_WHO_PROCESS = 1, |
| }; |
| |
| // May happen on old RedHat versions, see: |
| // https://github.com/giampaolo/psutil/issues/607 |
| #ifndef DUPLEX_UNKNOWN |
| #define DUPLEX_UNKNOWN 0xff |
| #endif |
| |
| static inline int |
| ioprio_get(int which, int who) |
| { |
| return syscall(__NR_ioprio_get, which, who); |
| } |
| |
| static inline int |
| ioprio_set(int which, int who, int ioprio) |
| { |
| return syscall(__NR_ioprio_set, which, who, ioprio); |
| } |
| |
| #define IOPRIO_CLASS_SHIFT 13 |
| #define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) |
| |
| #define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) |
| #define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) |
| #define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) |
| |
| |
| /* |
| * Return a (ioclass, iodata) Python tuple representing process I/O priority. |
| */ |
| static PyObject * |
| psutil_proc_ioprio_get(PyObject *self, PyObject *args) |
| { |
| long pid; |
| int ioprio, ioclass, iodata; |
| if (! PyArg_ParseTuple(args, "l", &pid)) |
| return NULL; |
| ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); |
| if (ioprio == -1) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| ioclass = IOPRIO_PRIO_CLASS(ioprio); |
| iodata = IOPRIO_PRIO_DATA(ioprio); |
| return Py_BuildValue("ii", ioclass, iodata); |
| } |
| |
| |
| /* |
| * A wrapper around ioprio_set(); sets process I/O priority. |
| * ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE |
| * or 0. iodata goes from 0 to 7 depending on ioclass specified. |
| */ |
| static PyObject * |
| psutil_proc_ioprio_set(PyObject *self, PyObject *args) |
| { |
| long pid; |
| int ioprio, ioclass, iodata; |
| int retval; |
| |
| if (! PyArg_ParseTuple(args, "lii", &pid, &ioclass, &iodata)) |
| return NULL; |
| ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); |
| retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); |
| if (retval == -1) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| Py_RETURN_NONE; |
| } |
| #endif |
| |
| |
| #if PSUTIL_HAVE_PRLIMIT |
| /* |
| * A wrapper around prlimit(2); sets process resource limits. |
| * This can be used for both get and set, in which case extra |
| * 'soft' and 'hard' args must be provided. |
| */ |
| static PyObject * |
| psutil_linux_prlimit(PyObject *self, PyObject *args) |
| { |
| long pid; |
| int ret, resource; |
| struct rlimit old, new; |
| struct rlimit *newp = NULL; |
| PyObject *soft = NULL; |
| PyObject *hard = NULL; |
| |
| if (! PyArg_ParseTuple(args, "li|OO", &pid, &resource, &soft, &hard)) |
| return NULL; |
| |
| // get |
| if (soft == NULL && hard == NULL) { |
| ret = prlimit(pid, resource, NULL, &old); |
| if (ret == -1) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| #if defined(PSUTIL_HAVE_LONG_LONG) |
| if (sizeof(old.rlim_cur) > sizeof(long)) { |
| return Py_BuildValue("LL", |
| (PY_LONG_LONG)old.rlim_cur, |
| (PY_LONG_LONG)old.rlim_max); |
| } |
| #endif |
| return Py_BuildValue("ll", (long)old.rlim_cur, (long)old.rlim_max); |
| } |
| |
| // set |
| else { |
| #if defined(PSUTIL_HAVE_LARGEFILE_SUPPORT) |
| new.rlim_cur = PyLong_AsLongLong(soft); |
| if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) |
| return NULL; |
| new.rlim_max = PyLong_AsLongLong(hard); |
| if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) |
| return NULL; |
| #else |
| new.rlim_cur = PyLong_AsLong(soft); |
| if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) |
| return NULL; |
| new.rlim_max = PyLong_AsLong(hard); |
| if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) |
| return NULL; |
| #endif |
| newp = &new; |
| ret = prlimit(pid, resource, newp, &old); |
| if (ret == -1) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| Py_RETURN_NONE; |
| } |
| } |
| #endif |
| |
| |
| /* |
| * Return disk mounted partitions as a list of tuples including device, |
| * mount point and filesystem type |
| */ |
| static PyObject * |
| psutil_disk_partitions(PyObject *self, PyObject *args) |
| { |
| FILE *file = NULL; |
| struct mntent *entry; |
| PyObject *py_retlist = PyList_New(0); |
| PyObject *py_tuple = NULL; |
| |
| if (py_retlist == NULL) |
| return NULL; |
| |
| // MOUNTED constant comes from mntent.h and it's == '/etc/mtab' |
| Py_BEGIN_ALLOW_THREADS |
| file = setmntent(MOUNTED, "r"); |
| Py_END_ALLOW_THREADS |
| if ((file == 0) || (file == NULL)) { |
| PyErr_SetFromErrnoWithFilename(PyExc_OSError, MOUNTED); |
| goto error; |
| } |
| |
| while ((entry = getmntent(file))) { |
| if (entry == NULL) { |
| PyErr_Format(PyExc_RuntimeError, "getmntent() failed"); |
| goto error; |
| } |
| py_tuple = Py_BuildValue("(ssss)", |
| entry->mnt_fsname, // device |
| entry->mnt_dir, // mount point |
| entry->mnt_type, // fs type |
| entry->mnt_opts); // options |
| if (! py_tuple) |
| goto error; |
| if (PyList_Append(py_retlist, py_tuple)) |
| goto error; |
| Py_DECREF(py_tuple); |
| } |
| endmntent(file); |
| return py_retlist; |
| |
| error: |
| if (file != NULL) |
| endmntent(file); |
| Py_XDECREF(py_tuple); |
| Py_DECREF(py_retlist); |
| return NULL; |
| } |
| |
| |
| /* |
| * A wrapper around sysinfo(), return system memory usage statistics. |
| */ |
| static PyObject * |
| psutil_linux_sysinfo(PyObject *self, PyObject *args) |
| { |
| struct sysinfo info; |
| |
| if (sysinfo(&info) != 0) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| // note: boot time might also be determined from here |
| return Py_BuildValue( |
| "(KKKKKK)", |
| (unsigned long long)info.totalram * info.mem_unit, // total |
| (unsigned long long)info.freeram * info.mem_unit, // free |
| (unsigned long long)info.bufferram * info.mem_unit, // buffer |
| (unsigned long long)info.sharedram * info.mem_unit, // shared |
| (unsigned long long)info.totalswap * info.mem_unit, // swap tot |
| (unsigned long long)info.freeswap * info.mem_unit); // swap free |
| } |
| |
| |
| /* |
| * Return process CPU affinity as a Python list |
| * The dual implementation exists because of: |
| * https://github.com/giampaolo/psutil/issues/536 |
| */ |
| |
| #ifdef CPU_ALLOC |
| |
| static PyObject * |
| psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) |
| { |
| int cpu, ncpus, count, cpucount_s; |
| long pid; |
| size_t setsize; |
| cpu_set_t *mask = NULL; |
| PyObject *res = NULL; |
| |
| if (!PyArg_ParseTuple(args, "i", &pid)) |
| return NULL; |
| ncpus = NCPUS_START; |
| while (1) { |
| setsize = CPU_ALLOC_SIZE(ncpus); |
| mask = CPU_ALLOC(ncpus); |
| if (mask == NULL) |
| return PyErr_NoMemory(); |
| if (sched_getaffinity(pid, setsize, mask) == 0) |
| break; |
| CPU_FREE(mask); |
| if (errno != EINVAL) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| if (ncpus > INT_MAX / 2) { |
| PyErr_SetString(PyExc_OverflowError, "could not allocate " |
| "a large enough CPU set"); |
| return NULL; |
| } |
| ncpus = ncpus * 2; |
| } |
| |
| res = PyList_New(0); |
| if (res == NULL) |
| goto error; |
| |
| cpucount_s = CPU_COUNT_S(setsize, mask); |
| for (cpu = 0, count = cpucount_s; count; cpu++) { |
| if (CPU_ISSET_S(cpu, setsize, mask)) { |
| #if PY_MAJOR_VERSION >= 3 |
| PyObject *cpu_num = PyLong_FromLong(cpu); |
| #else |
| PyObject *cpu_num = PyInt_FromLong(cpu); |
| #endif |
| if (cpu_num == NULL) |
| goto error; |
| if (PyList_Append(res, cpu_num)) { |
| Py_DECREF(cpu_num); |
| goto error; |
| } |
| Py_DECREF(cpu_num); |
| --count; |
| } |
| } |
| CPU_FREE(mask); |
| return res; |
| |
| error: |
| if (mask) |
| CPU_FREE(mask); |
| Py_XDECREF(res); |
| return NULL; |
| } |
| #else |
| |
| |
| /* |
| * Alternative implementation in case CPU_ALLOC is not defined. |
| */ |
| static PyObject * |
| psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) |
| { |
| cpu_set_t cpuset; |
| unsigned int len = sizeof(cpu_set_t); |
| long pid; |
| int i; |
| PyObject* py_retlist = NULL; |
| PyObject *py_cpu_num = NULL; |
| |
| if (!PyArg_ParseTuple(args, "i", &pid)) |
| return NULL; |
| CPU_ZERO(&cpuset); |
| if (sched_getaffinity(pid, len, &cpuset) < 0) |
| return PyErr_SetFromErrno(PyExc_OSError); |
| |
| py_retlist = PyList_New(0); |
| if (py_retlist == NULL) |
| goto error; |
| for (i = 0; i < CPU_SETSIZE; ++i) { |
| if (CPU_ISSET(i, &cpuset)) { |
| py_cpu_num = Py_BuildValue("i", i); |
| if (py_cpu_num == NULL) |
| goto error; |
| if (PyList_Append(py_retlist, py_cpu_num)) |
| goto error; |
| Py_DECREF(py_cpu_num); |
| } |
| } |
| |
| return py_retlist; |
| |
| error: |
| Py_XDECREF(py_cpu_num); |
| Py_DECREF(py_retlist); |
| return NULL; |
| } |
| #endif |
| |
| /* |
| * Set process CPU affinity; expects a bitmask |
| */ |
| static PyObject * |
| psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) |
| { |
| cpu_set_t cpu_set; |
| size_t len; |
| long pid; |
| int i, seq_len; |
| PyObject *py_cpu_set; |
| PyObject *py_cpu_seq = NULL; |
| |
| if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set)) |
| return NULL; |
| |
| if (!PySequence_Check(py_cpu_set)) { |
| PyErr_Format(PyExc_TypeError, "sequence argument expected, got %s", |
| Py_TYPE(py_cpu_set)->tp_name); |
| goto error; |
| } |
| |
| py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); |
| if (!py_cpu_seq) |
| goto error; |
| seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); |
| CPU_ZERO(&cpu_set); |
| for (i = 0; i < seq_len; i++) { |
| PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); |
| #if PY_MAJOR_VERSION >= 3 |
| long value = PyLong_AsLong(item); |
| #else |
| long value = PyInt_AsLong(item); |
| #endif |
| if (value == -1 && PyErr_Occurred()) |
| goto error; |
| CPU_SET(value, &cpu_set); |
| } |
| |
| len = sizeof(cpu_set); |
| if (sched_setaffinity(pid, len, &cpu_set)) { |
| PyErr_SetFromErrno(PyExc_OSError); |
| goto error; |
| } |
| |
| Py_DECREF(py_cpu_seq); |
| Py_RETURN_NONE; |
| |
| error: |
| if (py_cpu_seq != NULL) |
| Py_DECREF(py_cpu_seq); |
| return NULL; |
| } |
| |
| |
| /* |
| * Return currently connected users as a list of tuples. |
| */ |
| static PyObject * |
| psutil_users(PyObject *self, PyObject *args) |
| { |
| PyObject *ret_list = PyList_New(0); |
| PyObject *tuple = NULL; |
| PyObject *user_proc = NULL; |
| struct utmp *ut; |
| |
| if (ret_list == NULL) |
| return NULL; |
| setutent(); |
| while (NULL != (ut = getutent())) { |
| tuple = NULL; |
| user_proc = NULL; |
| if (ut->ut_type == USER_PROCESS) |
| user_proc = Py_True; |
| else |
| user_proc = Py_False; |
| tuple = Py_BuildValue( |
| "(sssfO)", |
| ut->ut_user, // username |
| ut->ut_line, // tty |
| ut->ut_host, // hostname |
| (float)ut->ut_tv.tv_sec, // tstamp |
| user_proc // (bool) user process |
| ); |
| if (! tuple) |
| goto error; |
| if (PyList_Append(ret_list, tuple)) |
| goto error; |
| Py_DECREF(tuple); |
| } |
| endutent(); |
| return ret_list; |
| |
| error: |
| Py_XDECREF(tuple); |
| Py_XDECREF(user_proc); |
| Py_DECREF(ret_list); |
| endutent(); |
| return NULL; |
| } |
| |
| |
| /* |
| * Return stats about a particular network |
| * interface. References: |
| * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py |
| * http://www.i-scream.org/libstatgrab/ |
| */ |
| static PyObject* |
| psutil_net_if_stats(PyObject* self, PyObject* args) |
| { |
| char *nic_name; |
| int sock = 0; |
| int ret; |
| int duplex; |
| int speed; |
| int mtu; |
| struct ifreq ifr; |
| struct ethtool_cmd ethcmd; |
| PyObject *py_is_up = NULL; |
| PyObject *py_ret = NULL; |
| |
| if (! PyArg_ParseTuple(args, "s", &nic_name)) |
| return NULL; |
| |
| sock = socket(AF_INET, SOCK_DGRAM, 0); |
| if (sock == -1) |
| goto error; |
| strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); |
| |
| // is up? |
| ret = ioctl(sock, SIOCGIFFLAGS, &ifr); |
| if (ret == -1) |
| goto error; |
| if ((ifr.ifr_flags & IFF_UP) != 0) |
| py_is_up = Py_True; |
| else |
| py_is_up = Py_False; |
| Py_INCREF(py_is_up); |
| |
| // MTU |
| ret = ioctl(sock, SIOCGIFMTU, &ifr); |
| if (ret == -1) |
| goto error; |
| mtu = ifr.ifr_mtu; |
| |
| // duplex and speed |
| memset(ðcmd, 0, sizeof ethcmd); |
| ethcmd.cmd = ETHTOOL_GSET; |
| ifr.ifr_data = (caddr_t)ðcmd; |
| ret = ioctl(sock, SIOCETHTOOL, &ifr); |
| |
| if (ret != -1) { |
| duplex = ethcmd.duplex; |
| speed = ethcmd.speed; |
| } |
| else { |
| if (errno == EOPNOTSUPP) { |
| // we typically get here in case of wi-fi cards |
| duplex = DUPLEX_UNKNOWN; |
| speed = 0; |
| } |
| else { |
| goto error; |
| } |
| } |
| |
| close(sock); |
| py_ret = Py_BuildValue("[Oiii]", py_is_up, duplex, speed, mtu); |
| if (!py_ret) |
| goto error; |
| Py_DECREF(py_is_up); |
| return py_ret; |
| |
| error: |
| Py_XDECREF(py_is_up); |
| if (sock != 0) |
| close(sock); |
| PyErr_SetFromErrno(PyExc_OSError); |
| return NULL; |
| } |
| |
| |
| /* |
| * Define the psutil C module methods and initialize the module. |
| */ |
| static PyMethodDef |
| PsutilMethods[] = |
| { |
| // --- per-process functions |
| |
| #if PSUTIL_HAVE_IOPRIO |
| {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS, |
| "Get process I/O priority"}, |
| {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS, |
| "Set process I/O priority"}, |
| #endif |
| {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, |
| "Return process CPU affinity as a Python long (the bitmask)."}, |
| {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, |
| "Set process CPU affinity; expects a bitmask."}, |
| |
| // --- system related functions |
| |
| {"disk_partitions", psutil_disk_partitions, METH_VARARGS, |
| "Return disk mounted partitions as a list of tuples including " |
| "device, mount point and filesystem type"}, |
| {"users", psutil_users, METH_VARARGS, |
| "Return currently connected users as a list of tuples"}, |
| {"net_if_stats", psutil_net_if_stats, METH_VARARGS, |
| "Return NIC stats (isup, duplex, speed, mtu)"}, |
| |
| // --- linux specific |
| |
| {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS, |
| "A wrapper around sysinfo(), return system memory usage statistics"}, |
| #if PSUTIL_HAVE_PRLIMIT |
| {"linux_prlimit", psutil_linux_prlimit, METH_VARARGS, |
| "Get or set process resource limits."}, |
| #endif |
| |
| |
| {NULL, NULL, 0, NULL} |
| }; |
| |
| struct module_state { |
| PyObject *error; |
| }; |
| |
| #if PY_MAJOR_VERSION >= 3 |
| #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) |
| #else |
| #define GETSTATE(m) (&_state) |
| #endif |
| |
| #if PY_MAJOR_VERSION >= 3 |
| |
| static int |
| psutil_linux_traverse(PyObject *m, visitproc visit, void *arg) { |
| Py_VISIT(GETSTATE(m)->error); |
| return 0; |
| } |
| |
| static int |
| psutil_linux_clear(PyObject *m) { |
| Py_CLEAR(GETSTATE(m)->error); |
| return 0; |
| } |
| |
| static struct PyModuleDef |
| moduledef = { |
| PyModuleDef_HEAD_INIT, |
| "psutil_linux", |
| NULL, |
| sizeof(struct module_state), |
| PsutilMethods, |
| NULL, |
| psutil_linux_traverse, |
| psutil_linux_clear, |
| NULL |
| }; |
| |
| #define INITERROR return NULL |
| |
| PyMODINIT_FUNC PyInit__psutil_linux(void) |
| |
| #else |
| #define INITERROR return |
| |
| void init_psutil_linux(void) |
| #endif |
| { |
| #if PY_MAJOR_VERSION >= 3 |
| PyObject *module = PyModule_Create(&moduledef); |
| #else |
| PyObject *module = Py_InitModule("_psutil_linux", PsutilMethods); |
| #endif |
| |
| |
| PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); |
| #if PSUTIL_HAVE_PRLIMIT |
| PyModule_AddIntConstant(module, "RLIM_INFINITY", RLIM_INFINITY); |
| PyModule_AddIntConstant(module, "RLIMIT_AS", RLIMIT_AS); |
| PyModule_AddIntConstant(module, "RLIMIT_CORE", RLIMIT_CORE); |
| PyModule_AddIntConstant(module, "RLIMIT_CPU", RLIMIT_CPU); |
| PyModule_AddIntConstant(module, "RLIMIT_DATA", RLIMIT_DATA); |
| PyModule_AddIntConstant(module, "RLIMIT_FSIZE", RLIMIT_FSIZE); |
| PyModule_AddIntConstant(module, "RLIMIT_LOCKS", RLIMIT_LOCKS); |
| PyModule_AddIntConstant(module, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK); |
| PyModule_AddIntConstant(module, "RLIMIT_NOFILE", RLIMIT_NOFILE); |
| PyModule_AddIntConstant(module, "RLIMIT_NPROC", RLIMIT_NPROC); |
| PyModule_AddIntConstant(module, "RLIMIT_RSS", RLIMIT_RSS); |
| PyModule_AddIntConstant(module, "RLIMIT_STACK", RLIMIT_STACK); |
| #ifdef RLIMIT_MSGQUEUE |
| PyModule_AddIntConstant(module, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE); |
| #endif |
| #ifdef RLIMIT_NICE |
| PyModule_AddIntConstant(module, "RLIMIT_NICE", RLIMIT_NICE); |
| #endif |
| #ifdef RLIMIT_RTPRIO |
| PyModule_AddIntConstant(module, "RLIMIT_RTPRIO", RLIMIT_RTPRIO); |
| #endif |
| #ifdef RLIMIT_RTTIME |
| PyModule_AddIntConstant(module, "RLIMIT_RTTIME", RLIMIT_RTTIME); |
| #endif |
| #ifdef RLIMIT_SIGPENDING |
| PyModule_AddIntConstant(module, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING); |
| #endif |
| #endif |
| PyModule_AddIntConstant(module, "DUPLEX_HALF", DUPLEX_HALF); |
| PyModule_AddIntConstant(module, "DUPLEX_FULL", DUPLEX_FULL); |
| PyModule_AddIntConstant(module, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN); |
| |
| if (module == NULL) |
| INITERROR; |
| #if PY_MAJOR_VERSION >= 3 |
| return module; |
| #endif |
| } |