diff --git a/src/realpath.c b/src/realpath.c index 744ac2fc45035609fc3eacdadeca8b60adb12e42..dc48fa0b365bfeefcb4e0c4d0365006b67177e8f 100644 --- a/src/realpath.c +++ b/src/realpath.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 + * Copyright (c) 2024 Frederick H. G. Wright II <fw@fwright.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,38 +17,149 @@ /* MP support header */ #include "MacportsLegacySupport.h" -/* realpath wrap */ +/* realpath wrapper */ #if __MPLS_LIB_SUPPORT_REALPATH_WRAP__ -#include <limits.h> -#include <stdlib.h> +/* Keep stdlib from defining any version of realpath */ +#define realpath __MPLS_hide_realpath + #include <dlfcn.h> +#include <errno.h> +#include <stddef.h> /* For NULL */ +#include <stdlib.h> + +#include <sys/syslimits.h> /* For PATH_MAX */ + +/* Now undo our macro kludge */ +#undef realpath + +/* + * This provides a wrapper for realpath() in order to make the 10.6+ + * optional buffer allocation available in <10.6. At present, it does not + * make the optional 10.5+ semantic improvements available in 10.4. + * + * It provides wrappers for all versions of realpath(), even when targeting + * 10.4, since the client may be built with a later SDK. It attempts to + * pass through to the same version as the one invoked (thereby maintaining + * the semantics), but falls back to the basic version if that fails (normally + * only on 10.4). If that fails, it bails with an abort(), since that should + * be impossible with an uncorrupted system library. + * + * The address of the OS realpath() is cached, to avoid repeating the dlsym() + * lookup on every call. In a fallback case, the address of the basic + * version is cached as if it were the address of the intended version, since + * the intended version isn't going to magically appear later. + */ + +/* Function type for realpath() */ +typedef char *rp_func_t(const char * __restrict, char * __restrict); + +/* Macro defining all versions */ +/* Note that the UNIX2003 version never exists in 64-bit builds. */ + +#if !__MPLS_64BIT + +#define RP_ALL \ + RP_ENT(,,basic) \ + RP_ENT($,UNIX2003,posix) \ + RP_ENT($,DARWIN_EXTSN,darwin) + +#else /* 64-bit */ + +#define RP_ALL \ + RP_ENT(,,basic) \ + RP_ENT($,DARWIN_EXTSN,darwin) + +#endif /* 64-bit */ + +/* Table of indices of versions */ +#define RP_ENT(d,x,t) rp_##t, +typedef enum { + RP_ALL +} rp_idx_t; +#undef RP_ENT -char *realpath(const char * __restrict stringsearch, char * __restrict buffer) +/* Table of names */ +#define RP_STR(x) #x +#define RP_ENT(d,x,t) RP_STR(realpath##d##x), +static const char *rp_name[] = { + RP_ALL +}; +#undef RP_ENT + +/* Table of cached addresses */ +#define RP_ENT(d,x,t) NULL, +static rp_func_t *rp_adr[] = { + RP_ALL +}; +#undef RP_ENT + +/* Internal realpath(), with version as a parameter */ +static char * +realpath_internal(const char * __restrict file_name, + char * __restrict resolved_name, + rp_idx_t version) { - char *(*real_realpath)(const char * __restrict, char * __restrict); -#if (__DARWIN_UNIX03 && !defined(_POSIX_C_SOURCE)) || defined(_DARWIN_C_SOURCE) || defined(_DARWIN_BETTER_REALPATH) - real_realpath = dlsym(RTLD_NEXT, "realpath$DARWIN_EXTSN"); -# else - real_realpath = dlsym(RTLD_NEXT, "realpath"); -#endif - if (real_realpath == NULL) { - exit(EXIT_FAILURE); - } + rp_func_t *os_realpath; + char *buf, *result; + int saved_errno; - if (buffer == NULL) { - char *myrealpathbuf = malloc(PATH_MAX); - if (myrealpathbuf != NULL) { - return(real_realpath(stringsearch, myrealpathbuf)); - } else { - return(NULL); - } - } else { - return(real_realpath(stringsearch, buffer)); + /* Locate proper OS realpath(), with fallback if needed */ + if (!(os_realpath = rp_adr[version])) { + os_realpath = rp_adr[version] = dlsym(RTLD_NEXT, rp_name[version]); + if (!os_realpath && version != rp_basic) { + os_realpath = rp_adr[version] = dlsym(RTLD_NEXT, rp_name[rp_basic]); } + if (!os_realpath) abort(); + } + + /* Just pass through the call if a buffer was supplied */ + if (resolved_name) return (*os_realpath)(file_name, resolved_name); + + /* Otherwise allocate a buffer and invoke it with that */ + if (!(buf = malloc(PATH_MAX))) return NULL; + if ((result = (*os_realpath)(file_name, buf))) return result; + + /* On failure, free the allocated buffer */ + /* Although free() shouldn't touch errno, we preserve it just in case */ + saved_errno = errno; + free(buf); + errno = saved_errno; + return NULL; } -/* compatibility function so code does not have to be recompiled */ -char *macports_legacy_realpath(const char * __restrict stringsearch, char * __restrict buffer) { return realpath(stringsearch, buffer); } +/* Now the various public realpath() versions */ + +#define RP_ENT(d,x,t) \ +char * \ +realpath##d##x(const char * __restrict file_name, \ + char * __restrict resolved_name) \ +{ \ + return realpath_internal(file_name, resolved_name, rp_##t); \ +} +RP_ALL +#undef RP_ENT + +/* + * Compatibility function to avoid the need to rebuild existing binaries + * built with the old wrapper-macro implementation (between Jan-2019 and + * Apr-2022). We have no way to determine which realpath version was + * used in such a build (since that information was destroyed by the wrapper + * macro), so we assume the usual default for the SDK matching the target + * OS for *this* build. That's the best we can do under the circumstances. + */ + +#if __MPLS_TARGET_OSVER < 1050 +#define RP_DEFAULT rp_basic +#else +#define RP_DEFAULT rp_darwin +#endif + +char * +macports_legacy_realpath(const char * __restrict file_name, + char * __restrict resolved_name) +{ + return realpath_internal(file_name, resolved_name, RP_DEFAULT); +} #endif /*__MPLS_LIB_SUPPORT_REALPATH_WRAP__*/