ctypes help

Python python at bladeshadow.org
Sat Nov 18 20:17:33 EST 2017


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 <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