On Fri, Nov 17, 2017 at 10:11 PM, Python <pyt...@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 -- https://mail.python.org/mailman/listinfo/python-list