Eryk, Thanks much for the excellent and highly detailed response! That made a lot of things clear.
On Sat, Nov 18, 2017 at 10:56:27AM +0000, eryk sun wrote: > 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