34

I want to make syscall in Python and the function is not in libc, is there a way to do it in Python?

More specifically, I want to call getdents, whose manpage says

Note: There are no glibc wrappers for these system calls;

All existing related solutions I found on the web uses ctypes with libc.so: for example.

Please don't question why I want to use getdents directly, I have a very specific reason to do that, and it would be distracting to discuss in this question. Thank you.

Kijewski
  • 25,517
  • 12
  • 101
  • 143
Kan Li
  • 8,557
  • 8
  • 53
  • 93

1 Answers1

43

Libc exposes a function to invoke "custom" syscalls: long syscall(long number, ...);

syscall() is a small library function that invokes the system call whose assembly language interface has the specified number with the specified arguments. Employing syscall() is useful, for example, when invoking a system call that has no wrapper function in the C library.

Just access this function like any foreign function:

import ctypes

libc = ctypes.CDLL(None)
syscall = libc.syscall

e.g.

syscall(39)  # 39 = getpid, but you get the gist

Or to translate the example in the man page:

import os, ctypes

off_t = ctypes.c_long  # YMMV
__NR_getdents = 78  # YMMV

class linux_dirent(ctypes.Structure):
    _fields_ = [
        ('d_ino', ctypes.c_long),
        ('d_off', off_t),
        ('d_reclen', ctypes.c_ushort),
        ('d_name', ctypes.c_char)
    ]

_getdents = ctypes.CDLL(None).syscall
_getdents.restype = ctypes.c_int
_getdents.argtypes = ctypes.c_long, ctypes.c_uint, ctypes.POINTER(ctypes.c_char), ctypes.c_uint

fd = os.open('/tmp/', os.O_RDONLY | os.O_DIRECTORY)

buf = ctypes.ARRAY(ctypes.c_char, 1024)()
while True:
    nread = _getdents(__NR_getdents, fd, buf, len(buf))
    if nread == -1:
        raise OSError('getdents')
    elif nread == 0:
        break

    pos = 0
    while pos < nread:
        d = linux_dirent.from_buffer(buf, pos)

        name = buf[pos + linux_dirent.d_name.offset : pos + d.d_reclen]
        name = name[:name.index('\0')]
        print 'name:', name

        pos += d.d_reclen
Kijewski
  • 25,517
  • 12
  • 101
  • 143
  • 1
    Is this strategy `_getdents = ctypes.CDLL(None).syscall` reusable. Let's say you wanted both `getdents` and some other `syscall` that takes a different set of arguments. Can you create another copy of `syscall` and then modify `argtypes` to something else? Or does `anotherfun = ctypes.CDLL(None).syscall` refer to the same object as `_getdents` and so modifying `argtypes` also modifies argtypes of `_getdents`? – cheshirekow Nov 06 '19 at 23:09
  • 8
    @cheshirekow What a strange coincidence... I was just looking that up myself now, 10 hours after you asked it on a 3 year old question... AFAICT the answer is that for a single call to `CDLL` you can only set the `argtypes` once. But if you call `CDLL(None)` twice, you can set `dll.syscall.argtypes` separately on each copy. – Nathaniel J. Smith Nov 07 '19 at 09:51