Florian Margaine
Florian Margaine

Reputation: 60717

cffi function call hangs

I want to use stat(2) from Common Lisp.

I've defined the structs used by the stat function:

(cffi:defctype mode_t :unsigned-int)
(cffi:defctype ino_t :unsigned-int)
(cffi:defctype dev_t :int)
(cffi:defctype nlink_t :int)
(cffi:defctype uid_t :unsigned-int)
(cffi:defctype gid_t :unsigned-int)
(cffi:defctype off_t :int)
(cffi:defctype time_t :long)
(cffi:defctype blksize_t :unsigned-int)
(cffi:defctype blkcnt_t :int)

(cffi:defcstruct stat
  (st_dev dev_t)
  (st_ino ino_t)
  (st_mode mode_t)
  (st_nlink nlink_t)
  (st_uid uid_t)
  (st_gid gid_t)
  (st_rdev dev_t)
  (st_size off_t)
  (st_atime time_t)
  (st_mtime time_t)
  (st_ctime time_t)
  (st_blksize blksize_t)
  (st_blocks blkcnt_t))

And the function itself:

(cffi:defcfun "stat" :int
  (path :string)
  (buf (:pointer (:struct stat))))

I'm trying to call it quite simply like this:

(cffi:with-foreign-object (buf '(:pointer (:struct stat)))
  (stat "/home/florian/tmp/msg.txt" buf)
  (cffi:with-foreign-slots ((st_mode) buf (:struct stat))
    st_mode))

And Slime just hangs. No error, and the REPL input doesn't come back.

Upvotes: 3

Views: 476

Answers (2)

Florian Margaine
Florian Margaine

Reputation: 60717

@jlahd's answer helped me to find the right solution.

After finding the correct structure, my calling code was still wrong. Here is the final code:

(cffi:with-foreign-object (buf '(:struct stat))
  (stat "/home/florian/tmp/msg.txt" buf)
  (cffi:with-foreign-slots ((st_mode) buf (:struct stat))
    st_mode))

Note that it uses '(:struct stat) instead of '(:pointer (:struct stat)) for buf foreign type.


How did I get the correct structure though? I wanted to document it.

Here is the final cffi structure that I've used:

(cffi:defcstruct (stat :size 144)
  (st_mode :unsigned-int :offset 24))

Here is the C program I used to find out the sizes and offset of the stat struct and its members:

#include <stddef.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
    struct stat foo;
    printf("stat sizeof: %zu\n", sizeof(struct stat));
    printf("st_mode sizeof: %zu\n", sizeof(foo.st_mode));
    printf("st_mode offset: %zu\n", offsetof(struct stat, st_mode));
}

On my computer, this gives me this:

$ gcc stat.c
$ ./a.out
stat sizeof: 144
st_mode sizeof: 4
st_mode offset: 24

I could then check that the size of :unsigned-int was 4 bytes:

CL-USER> (cffi:foreign-type-size :unsigned-int)
4

And check the size of my cffi struct:

CL-USER> (cffi:foreign-type-size '(:struct stat))
144

Which matches sizeof(struct stat) in C.

Upvotes: 2

jlahd
jlahd

Reputation: 6293

If you look into your *inferior-lisp* buffer, you will see that SBCL has dropped into its low-level debugger due to some severe memory corruption.

The specific layout of struct stat depends quite heavily on the architecture you have. On my 64-bit Ubuntu 14.04, the following seems to be 144 bytes in length, while your definition is only 52 bytes long - no wonder that trying to write into it causes memory corruption.

It is probably a Bad Idea to try writing defcstruct forms for structs that the operating system is free to define in any way it wants. The code may run on your computer, but probably won't run on everybody else's system, if they are using a different OS or processor architecture - but if you really need to get it running on your system only, the easiest way is probably to write a short C program that dumps the sizes and offsets of all the fields on screen, then build up from there.

If you need to write code that runs on multiple different systems and that uses stat, a good option is to write a simple proxy module in C yourself, with a well-defined, constant interface, that calls stat on your behalf. This is the approach I have used on several occasions, keeping foreign stuff safe on the C side and only passing the really required data over the FFI.

For more robust CFFI definitions, there is also SWIG.

Upvotes: 3

Related Questions