How to force linkage to older libc `fcntl` instead of `fcntl64`?
How to force linkage to older libc
fcntl
instead offcntl64
?
Compile against an older version of libc
. Period.
Because glibc is not forward compatible, it is only backwards-compatible:
The GNU C Library is designed to be a backwards compatible, portable, and high performance ISO C library. It aims to follow all relevant standards including ISO C11, POSIX.1-2008, and IEEE 754-2008.
Without any guarantees of forward compatibility, you don't know what else won't work properly.
What recourse does one have for this?
The fact that GLIBC didn't have a way to #define USE_FCNTL_NOT_FCNTL64
says a lot. Be it right or wrong, most OS+toolchain makers seem to have decided that targeting binaries for older versions of their systems from a newer one is not a high priority.
The path of least resistance is to keep a virtual machine around of the oldest OS+toolchain that builds your project. Use that to make binaries whenever you think that binary will be run on an old system.
But...
- If you believe your usages are in the subset of fcntl() calls that are not affected by the offset size change (which is to say you don't use byte range locks)
- OR are willing to vet your code for the offset cases to use a backwards-compatible structure definition
- AND are not scared of voodoo
...then keep reading.
The name is different, and fcntl is variadic without a vffcntl that takes a va_list. In such situations you cannot forward an invocation of a variadic function.
...then to apply the wrapping trick mentioned, you have to go line-by-line through fcntl()'s interface documentation, unpack the variadic as it would, and then call the wrapped version with a new variadic invocation.
Fortunately it's not that difficult a case (fcntl takes 0 or 1 arguments with documented types). To try saving anyone else some trouble, here's code for that. Be sure to pass --wrap=fcntl64 to the linker (-Wl,--wrap=fcntl64 if not calling ld directly):
asm (".symver fcntl64, fcntl@GLIBC_2.2.5");
extern "C" int __wrap_fcntl64(int fd, int cmd, ...)
{
int result;
va_list va;
va_start(va, cmd);
switch (cmd) {
//
// File descriptor flags
//
case F_GETFD: goto takes_void;
case F_SETFD: goto takes_int;
// File status flags
//
case F_GETFL: goto takes_void;
case F_SETFL: goto takes_int;
// File byte range locking, not held across fork() or clone()
//
case F_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
case F_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
case F_GETLK: goto takes_flock_ptr_INCOMPATIBLE;
// File byte range locking, held across fork()/clone() -- Not POSIX
//
case F_OFD_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
case F_OFD_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
case F_OFD_GETLK: goto takes_flock_ptr_INCOMPATIBLE;
// Managing I/O availability signals
//
case F_GETOWN: goto takes_void;
case F_SETOWN: goto takes_int;
case F_GETOWN_EX: goto takes_f_owner_ex_ptr;
case F_SETOWN_EX: goto takes_f_owner_ex_ptr;
case F_GETSIG: goto takes_void;
case F_SETSIG: goto takes_int;
// Notified when process tries to open or truncate file (Linux 2.4+)
//
case F_SETLEASE: goto takes_int;
case F_GETLEASE: goto takes_void;
// File and directory change notification
//
case F_NOTIFY: goto takes_int;
// Changing pipe capacity (Linux 2.6.35+)
//
case F_SETPIPE_SZ: goto takes_int;
case F_GETPIPE_SZ: goto takes_void;
// File sealing (Linux 3.17+)
//
case F_ADD_SEALS: goto takes_int;
case F_GET_SEALS: goto takes_void;
// File read/write hints (Linux 4.13+)
//
case F_GET_RW_HINT: goto takes_uint64_t_ptr;
case F_SET_RW_HINT: goto takes_uint64_t_ptr;
case F_GET_FILE_RW_HINT: goto takes_uint64_t_ptr;
case F_SET_FILE_RW_HINT: goto takes_uint64_t_ptr;
default:
fprintf(stderr, "fcntl64 workaround got unknown F_XXX constant")
}
takes_void:
va_end(va);
return fcntl64(fd, cmd);
takes_int:
result = fcntl64(fd, cmd, va_arg(va, int));
va_end(va);
return result;
takes_flock_ptr_INCOMPATIBLE:
//
// !!! This is the breaking case: the size of the flock
// structure changed to accommodate larger files. If you
// need this, you'll have to define a compatibility struct
// with the older glibc and make your own entry point using it,
// then call fcntl64() with it directly (bear in mind that has
// been remapped to the old fcntl())
//
fprintf(stderr, "fcntl64 hack can't use glibc flock directly");
exit(1);
takes_f_owner_ex_ptr:
result = fcntl64(fd, cmd, va_arg(va, struct f_owner_ex*));
va_end(va);
return result;
takes_uint64_t_ptr:
result = fcntl64(fd, cmd, va_arg(va, uint64_t*));
va_end(va);
return result;
}
Note that depending on what version you're actually building on, you might have to #ifdef some of those flag sections out if they're unavailable.
This affects quite a variety of applications...the manual page for fcntl() shows that it's the entry point for a small universe of sub-functions
...and it should probably be a lesson to people: avoid creating such "kitchen sink" functions through variadic abuse.