Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OSX stat() and libffi

I'm trying to access stat() on macos (10.11) using libffi. This is part of a new FFI-based foreign interface for SWI-Prolog. This interface parses the <sys/stat.h> header to get the function prototype and struct stat type. However, I get bogus values. It works fine if I compile the code below and link mystat from the generated .dylib.

#include <sys/stat.h>

int
mystat(const char *name, struct stat *buf)
{ return stat(name, buf);
}

My suspicion is that I get a wrong stat function from dlsym(). Tried both on /usr/lib/libc.dylib and /usr/lib/libSystem.B.dylib. That gives the same result. If I run nm on the dylib from the above I get U _stat$INODE64. dlsym doesn't work on stat$INODE64. Looking at libc.dylib we get

534_> nm /usr/lib/libc.dylib | grep stat
0000000000001ac5 T R8289209$_pthread_attr_setdetachstate
0000000000001af7 T R8289209$_stat
                 U _pthread_attr_setdetachstate
                 U _stat

How do the two relate? Anyone has any clue what might be going on?

like image 625
Jan Wielemaker Avatar asked Oct 29 '22 20:10

Jan Wielemaker


1 Answers

The symbol you're looking for is defined in /usr/lib/system/libsystem_kernel.dylib:

$  ~ nm -g /usr/lib/system/libsystem_kernel.dylib | grep '_stat\$INODE64'
0000000000002ed0 T _stat$INODE64

It can be found using dlsym:

#include <sys/stat.h>

#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

int
(*real_stat)(const char *path, struct stat *buf);

int main() {
  const char *path = "/usr/lib/system/libsystem_kernel.dylib";
  int err;
  struct stat st;

  void *lib = dlopen(path, RTLD_LOCAL);
  real_stat = dlsym(lib, "stat$INODE64");

  if((err = real_stat(path, &st)))
    fprintf(stderr, "Can't stat %s: %s\n", path, strerror(err));

  printf("%s inode: %lld\n", path, st.st_ino);

  dlclose(lib);
  return 0;
}

The inode number returned by the real_stat matches the one returned by stat(1):

$  ~ cc stat.c
$  ~ ./a.out
/usr/lib/system/libsystem_kernel.dylib inode: 4335860614

$  ~ stat -r /usr/lib/system/libsystem_kernel.dylib
16777220 4335860614 0100755 1 0 0 0 545424 1564436981 1564436981 1565194657 1564436981 4096 448 524320 /usr/lib/system/libsystem_kernel.dylib

There could be a case when stat is declared incorrectly like this:

struct stat;
int stat(const char *restrict path, struct stat *restrict buf);

int mystat(const char *path, struct stat *buf) {
  return stat(path, buf);
}

The lib does indeed reference legacy stat:

$  ~ cc -dynamiclib wrong-stat.c -o libwrongstat.dylib
$  ~ nm libwrongstat.dylib
0000000000000f70 T _mystat
                 U _stat
                 U dyld_stub_binder

<sys/stat.h> declares stat using special assembler names for functions with $INODE64 suffix, to avoid clash with existing stat (see stat(2) for details). The library can be fixed if stat is declared to reference the new assembler name with the suffix:

struct stat;
int stat(const char *path, struct stat *buf) __asm("_stat$INODE64");

int mystat(const char *path, struct stat *buf) {
  return stat(path, buf);
}
$  ~ cc -dynamiclib correct-stat.c -o libcorrectstat.dylib
$  ~ nm libcorrectstat.dylib
0000000000000f70 T _mystat
                 U _stat$INODE64
                 U dyld_stub_binder

But honestly I'd use <sys/stat.h> to pull correct symbol declarations.

like image 139
roolebo Avatar answered Nov 15 '22 09:11

roolebo