ctypes help
eryk sun
eryksun at gmail.com
Sat Nov 18 05:56:27 EST 2017
On Fri, Nov 17, 2017 at 10:11 PM, Python <python at bladeshadow.org> wrote:
>
> I'm starting to play with ctypes, as I'd like to provide Python
> interfaces to a C/C++ library I have. For now I'm just messing with a
> very simple piece of code to get things sorted out. I'm working with
> this example C++ library, which just wraps a call to stat():
Calling POSIX system functions via ctypes can be difficult if they use
struct layouts that vary with the target platform and architecture.
> class Stat(ctypes.Structure):
> _fields_ = [("st_dev", ctypes.c_ulong),
> ("st_ino", ctypes.c_ulong),
> ("st_mode", ctypes.c_ulong),
> ("st_nlink", ctypes.c_ulong),
> ("st_uid", ctypes.c_ulong),
> ("st_gid", ctypes.c_ulong),
For x64 the above incorrectly uses 64-bit integers for mode, uid, and
gid. Also, it has mode and nlink flipped around in the x86 order
instead of the new x64 order.
> ("st_rdev", ctypes.c_ulong),
> ("st_size", ctypes.c_ulonglong),
> ("st_blksize", ctypes.c_ulonglong),
> ("st_blocks", ctypes.c_ulonglong),
> ("st_atim", Timespec),
> ("st_mtim", Timespec),
> ("st_ctim", Timespec)]
Try to strictly adhere to the defined types. Don't use `long long` for
a `long`, even if it happens to be the same size in a given
architecture. Also, avoid using definitions from docs and man pages.
Use the exact headers that the compiler sees. In this case, you missed
the 3 reserved long-integer fields at the end on x64 builds. Your Stat
struct is 128 bytes overall instead of the required 144 bytes. glibc
will corrupt the heap when it writes to the last 16 bytes. The best
case is that this immediately crashes Python.
Below I've included an example ctypes wrapper for calling stat() on
Linux x64 and x86 systems. I verified the sizes and offsets and tested
in 64-bit Python. From a C test program, I also have the field sizes
and offsets for the two 32-bit cases (with and without large file
support), but I'd need to build 32-bit Python to verify that the
ctypes wrapper is correct. I have no personal use for 32-bit Python,
so I leave that part up to you.
C header definitions from time.h, bits/typesizes.h, bits/types.h, and
bits/stat.h:
#define __DEV_T_TYPE __UQUAD_TYPE
#define __INO_T_TYPE __SYSCALL_ULONG_TYPE
#define __INO64_T_TYPE __UQUAD_TYPE
#ifdef __x86_64__
#define __NLINK_T_TYPE __SYSCALL_ULONG_TYPE
#else
#define __NLINK_T_TYPE __UWORD_TYPE
#endif
#define __MODE_T_TYPE __U32_TYPE
#define __UID_T_TYPE __U32_TYPE
#define __GID_T_TYPE __U32_TYPE
#define __OFF_T_TYPE __SYSCALL_SLONG_TYPE
#define __OFF64_T_TYPE __SQUAD_TYPE
#define __BLKSIZE_T_TYPE __SYSCALL_SLONG_TYPE
#define __BLKCNT_T_TYPE __SYSCALL_SLONG_TYPE
#define __BLKCNT64_T_TYPE __SQUAD_TYPE
#define __TIME_T_TYPE __SYSCALL_SLONG_TYPE
struct timespec
{
__time_t tv_sec;
__syscall_slong_t tv_nsec;
};
struct stat
{
__dev_t st_dev;
#ifndef __x86_64__
unsigned short int __pad1;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__ino_t st_ino;
#else
__ino_t __st_ino;
#endif
#ifndef __x86_64__
__mode_t st_mode;
__nlink_t st_nlink;
#else
__nlink_t st_nlink;
__mode_t st_mode;
#endif
__uid_t st_uid;
__gid_t st_gid;
#ifdef __x86_64__
int __pad0;
#endif
__dev_t st_rdev;
#ifndef __x86_64__
unsigned short int __pad2;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__off_t st_size;
#else
__off64_t st_size;
#endif
__blksize_t st_blksize;
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__blkcnt_t st_blocks;
#else
__blkcnt64_t st_blocks;
#endif
#ifdef __USE_XOPEN2K8
struct timespec st_atim;
struct timespec st_mtim;
struct timespec st_ctim;
#else
__time_t st_atime;
__syscall_ulong_t st_atimensec;
__time_t st_mtime;
__syscall_ulong_t st_mtimensec;
__time_t st_ctime;
__syscall_ulong_t st_ctimensec;
#endif
#ifdef __x86_64__
__syscall_slong_t __glibc_reserved[3];
#else
#ifndef __USE_FILE_OFFSET64
unsigned long int __glibc_reserved4;
unsigned long int __glibc_reserved5;
#else
__ino64_t st_ino;
#endif
#endif
};
ctypes:
import os
import sys
import ctypes
import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
def c_errcheck(result, func, args):
if result == -1:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
return args
class timespec(ctypes.Structure):
_fields_ = (('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long))
_FILE_OFFSET_BITS = 64
class stat_result(ctypes.Structure):
if ctypes.sizeof(ctypes.c_void_p) == 8:
_fields_ = (('st_dev', ctypes.c_uint64),
('st_ino', ctypes.c_ulong),
('st_nlink', ctypes.c_ulong),
('st_mode', ctypes.c_uint32),
('st_uid', ctypes.c_uint32),
('st_gid', ctypes.c_uint32),
('_pad0', ctypes.c_int),
('st_rdev', ctypes.c_uint64),
('st_size', ctypes.c_long),
('st_blksize', ctypes.c_long),
('st_blocks', ctypes.c_long),
('st_atim', timespec),
('st_mtim', timespec),
('st_ctim', timespec),
('_reserved', ctypes.c_long * 3))
elif _FILE_OFFSET_BITS == 64:
_fields_ = (('st_dev', ctypes.c_uint64),
('_pad1', ctypes.c_int),
('_st_ino', ctypes.c_ulong),
('st_mode', ctypes.c_uint32),
('st_nlink', ctypes.c_uint),
('st_uid', ctypes.c_uint32),
('st_gid', ctypes.c_uint32),
('st_rdev', ctypes.c_uint64),
('_pad2', ctypes.c_ushort),
('st_size', ctypes.c_int64),
('st_blksize', ctypes.c_long),
('st_blocks', ctypes.c_int64),
('st_atim', timespec),
('st_mtim', timespec),
('st_ctim', timespec),
('st_ino', ctypes.c_uint64))
else:
_fields_ = (('st_dev', ctypes.c_uint64),
('_pad1', ctypes.c_int),
('st_ino', ctypes.c_ulong),
('st_mode', ctypes.c_uint32),
('st_nlink', ctypes.c_uint),
('st_uid', ctypes.c_uint32),
('st_gid', ctypes.c_uint32),
('st_rdev', ctypes.c_uint64),
('_pad2', ctypes.c_ushort),
('st_size', ctypes.c_long),
('st_blksize', ctypes.c_long),
('st_blocks', ctypes.c_long),
('st_atim', timespec),
('st_mtim', timespec),
('st_ctim', timespec),
('_reserved4', ctypes.c_ulong),
('_reserved5', ctypes.c_ulong))
@property
def st_atime(self):
t = self.st_atim
return t.tv_sec + t.tv_nsec * 1e-9
@property
def st_mtime(self):
t = self.st_mtim
return t.tv_sec + t.tv_nsec * 1e-9
@property
def st_ctime(self):
t = self.st_ctim
return t.tv_sec + t.tv_nsec * 1e-9
libc.__xstat.errcheck = c_errcheck
libc.__xstat.argtypes = (
ctypes.c_int, # API version
ctypes.c_char_p, # filename
ctypes.POINTER(stat_result)) # buf
if ctypes.sizeof(ctypes.c_void_p) == 8:
_STAT_VER_KERNEL = 0
_STAT_VER_LINUX = 1
else:
_STAT_VER_LINUX_OLD = 1
_STAT_VER_KERNEL = 1
_STAT_VER_SVR4 = 2
_STAT_VER_LINUX = 3
def mystat(filename):
if not isinstance(filename, bytes):
if hasattr(os, 'fsencode'):
filename = os.fsencode(filename)
else:
filename = filename.encode(sys.getfilesystemencoding())
st = stat_result()
libc.__xstat(_STAT_VER_LINUX, filename, ctypes.byref(st))
return st
if __name__ == '__main__':
st = os.stat('/')
myst = mystat('/')
assert (st.st_dev, st.st_ino) == (myst.st_dev, myst.st_ino)
assert st.st_mode == myst.st_mode
assert st.st_atime == myst.st_atime
assert st.st_mtime == myst.st_mtime
More information about the Python-list
mailing list