From 7f7bb6b3071ac82119bc393e1bc19f8c4d0766df Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 17 Aug 2026 20:28:16 -0400 Subject: [PATCH 1/5] Identify class images by a caller-supplied address The marker symbol created the failure it then had to guard against: an image could only forget to declare itself because declaring itself was required, and dlsym does not stop at the image it is given, so one that forgot resolved a dependency's marker and was registered under the wrong base address. Guarding that meant asking the loader to relate a handle to an image, which is the platform code the marker was meant to retire. There are only two sources of truth for which image is behind a handle: the loader, or the caller. Take it from the caller. An application loads an image in order to call into it, so it holds an address within that image already, and passing it costs a parameter and no convention. This retires OBJECTIVELY_CLASS_IMAGE, markerBelongsToImage, and the reservation of Objectively as a Class name. Identify class images by an exported marker symbol removeClassImage was handed a handle and had to resolve it to the base address that Classes record, which has no one spelling: Windows hands out the module as the handle, glibc answers from the link map, and macOS, having neither, matched the handle against every loaded image by opening and closing each one in turn. The platforms report a base address for an address, not for a handle. So require an image that provides Classes to say so, with a marker symbol that addClassImage resolves and asks dladdr about once, at registration. removeClassImage then matches on the handle alone and imageForHandle is gone, along with the mach-o and link map includes it needed. The marker is shaped like an archetype and returns NULL, so that a lookup of a Class named Objectively resolves it harmlessly rather than calling something that is not an archetype. dlsym searches an image ahead of its dependencies, so an image resolves its own marker rather than one from the library it links against. The registry becomes a list, which retires MAX_CLASS_IMAGES and the assert that guarded it - a bounds check that compiled out under NDEBUG, leaving the ninth image to write past the array. Registering an image that declares no Classes, or unregistering one that was never registered, now abort. Both were silent, and both leave behind exactly the Classes this exists to remove. Remove the Windows __sync_ shims Nothing calls them since Objectively moved to the __atomic_ builtins, which clang-cl provides directly, so the Interlocked wrappers behind them have no remaining caller. Verify the class image marker belongs to the image dlsym does not stop at the image it is given, so an image that omits OBJECTIVELY_CLASS_IMAGE resolves a dependency's marker instead of failing. It was then registered under the dependency's base address, which made removeClassImage unregister the dependency's Classes and leave its own behind, reachable by name and about to be unmapped - the failure the marker exists to prevent, reached silently. Confirm the marker was defined by the image behind the handle, by asking which image defines it and reopening that one RTLD_NOLOAD to compare. Windows hands out the module as the handle, so there the base address answers directly. Retire image entries in place rather than unlinking and freeing them. A concurrent classForName walks this list, and freeing a node out from under it turned a stale read into a use after free. Retired entries are skipped on lookup and freed at teardown. Also tie the marker's definition to its lookup through one macro, so the two cannot drift, and drop the remark claiming an image declares nothing. Order Class.c to follow Class.h removeClassImage preceded addClassImage because imageForHandle, its only helper, sat directly above it. That helper is gone, and the pair had been left reading backwards with markerBelongsToImage stranded between them. Definitions now follow the order the header declares them in, and each static helper sits directly above its first caller. Co-Authored-By: Claude Opus 5 (1M context) --- Objectively.vs15/Sources/Windowly.c | 12 +-- Objectively.vs15/Sources/Windowly.h | 9 -- Sources/Objectively/Class.c | 122 ++++++++++++++-------------- Sources/Objectively/Class.h | 10 ++- 4 files changed, 69 insertions(+), 84 deletions(-) diff --git a/Objectively.vs15/Sources/Windowly.c b/Objectively.vs15/Sources/Windowly.c index 0a5f50d..905337e 100644 --- a/Objectively.vs15/Sources/Windowly.c +++ b/Objectively.vs15/Sources/Windowly.c @@ -32,16 +32,6 @@ static const int64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000; -long __sync_val_compare_and_swap_(long volatile *Destination, long Exchange, long Comparand) { - return InterlockedCompareExchange(Destination, Exchange, Comparand); -} -long __sync_add_and_fetch_(long volatile *Append, long Value) { - return InterlockedAdd(Append, Value); -} -void *__sync_lock_test_and_set_(void *volatile *Target, void *Value) { - return InterlockedExchangePointer(Target, Value); -} - int gettimeofday(struct timeval *tv, struct timezone *tz) { if (tv) @@ -133,4 +123,4 @@ int asprintf(char **str, const char *fmt, ...) return size; } -#pragma endregion asprintf \ No newline at end of file +#pragma endregion asprintf diff --git a/Objectively.vs15/Sources/Windowly.h b/Objectively.vs15/Sources/Windowly.h index 4112050..27dbc8f 100644 --- a/Objectively.vs15/Sources/Windowly.h +++ b/Objectively.vs15/Sources/Windowly.h @@ -88,12 +88,3 @@ OBJECTIVELY_EXPORT int vasprintf(char **str, const char *fmt, va_list args); #endif #define iconv(c0, c1, c2, c3, c4) libiconv(c0, (const char **)c1, c2, c3, c4) - -// INTERLOCK STUFF -OBJECTIVELY_EXPORT long __sync_val_compare_and_swap_(long volatile *Destination, long Exchange, long Comparand); -OBJECTIVELY_EXPORT long __sync_add_and_fetch_(long volatile *Append, long Value); -OBJECTIVELY_EXPORT void *__sync_lock_test_and_set_(void *volatile *Target, void *Value); - -#define __sync_val_compare_and_swap(c0, c1, c2) __sync_val_compare_and_swap_((volatile long *) c0, c1, c2) -#define __sync_add_and_fetch(c0, c1) __sync_add_and_fetch_((volatile long *) c0, c1) -#define __sync_lock_test_and_set(c0, c1) __sync_lock_test_and_set_((void *volatile *) c0, (void *) c1) \ No newline at end of file diff --git a/Sources/Objectively/Class.c b/Sources/Objectively/Class.c index 22345cc..1aa9764 100644 --- a/Sources/Objectively/Class.c +++ b/Sources/Objectively/Class.c @@ -31,10 +31,6 @@ #if defined(_WIN32) #include -#elif defined(__APPLE__) -#include -#else -#include #endif #if HAVE_UNISTD_H @@ -49,11 +45,22 @@ size_t _pageSize; static Class *_classes; /** - * @brief The registered images providing Classes. + * @brief A registered image: the handle the application holds, and the base + * address that the Classes it declares record in `Class::image`. + */ +typedef struct ClassImage ClassImage; +struct ClassImage { + ident handle; + ident image; + ClassImage *next; +}; + +/** + * @brief The registered images providing Classes, most recently added first. + * @remarks A plain list rather than a MutableArray because Class is beneath the + * collections: initializing one here would reenter `_initialize`. */ -#define MAX_CLASS_IMAGES 8 -static ident _classImages[MAX_CLASS_IMAGES]; -static size_t _classImageCount; +static ClassImage *_images; /** * @brief Called `atexit` to teardown Objectively. @@ -80,6 +87,16 @@ static void teardown(void) { c = next; } + + ClassImage *i = _images; + while (i) { + + ClassImage *next = i->next; + + free(i); + + i = next; + } } /** @@ -207,87 +224,64 @@ ident _cast(const Class *clazz, const ident obj) { return (ident) obj; } -/** - * @return The base address of the image behind `handle`, or `NULL`. - * @remarks There is no one call for this. Windows hands out the module itself as - * the handle, which `GetModuleFileName` confirms rather than assumes; glibc - * answers from the link map; and macOS, which has neither, is left with matching - * the handle against the loaded images. - */ -static ident imageForHandle(ident handle) { +void addClassImage(ident handle, const ident address) { assert(handle); + assert(address); -#if defined(_WIN32) - char path[MAX_PATH]; - if (GetModuleFileNameA((HMODULE) handle, path, sizeof(path))) { - return handle; + const ident image = imageForAddress(address); + if (image == NULL) { + fprintf(stderr, "%s: no image contains %p\n", __func__, address); + abort(); } -#elif defined(__APPLE__) - for (uint32_t i = 0; i < _dyld_image_count(); i++) { - - /* RTLD_LOCAL is not the default on macOS, and dlopen of an image already - * loaded promotes it to the global namespace, which would put every image in - * the process there - undoing the isolation the caller loaded it for. */ - ident image = dlopen(_dyld_get_image_name(i), RTLD_LAZY | RTLD_NOLOAD | RTLD_LOCAL); - if (image == NULL) { - continue; - } - dlclose(image); + ClassImage *classImage = calloc(1, sizeof(ClassImage)); + assert(classImage); - if (image == handle) { - return (ident) _dyld_get_image_header(i); - } - } -#else - struct link_map *map; - if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0 && map) { - return (ident) map->l_addr; - } -#endif + classImage->handle = handle; + classImage->image = image; + classImage->next = _images; - return NULL; + _images = classImage; } void removeClassImage(ident handle) { assert(handle); - for (size_t i = 0; i < _classImageCount; i++) { - if (_classImages[i] == handle) { - memmove(_classImages + i, _classImages + i + 1, (_classImageCount - i - 1) * sizeof(ident)); - _classImages[--_classImageCount] = NULL; + ident image = NULL; + + /* Retired in place rather than unlinked, so that a concurrent classForName + * parked on this node still has a next to follow. Retired nodes are skipped + * on lookup and freed at teardown; reusing one would put a newly registered + * image where the retired one sat, and lookup order is newest first. */ + for (ClassImage *i = _images; i; i = i->next) { + if (i->handle == handle) { + image = i->image; + i->handle = NULL; + i->image = NULL; break; } } - const ident image = imageForHandle(handle); if (image == NULL) { - return; + fprintf(stderr, "%s: %p was never registered\n", __func__, handle); + abort(); } - Class **link = &_classes; - while (*link) { - Class *clazz = *link; + Class **classes = &_classes; + while (*classes) { + Class *clazz = *classes; if (clazz->image == image) { - *link = clazz->next; + *classes = clazz->next; clazz->next = NULL; } else { - link = &clazz->next; + classes = &clazz->next; } } } -void addClassImage(ident handle) { - - assert(handle); - assert(_classImageCount < MAX_CLASS_IMAGES); - - _classImages[_classImageCount++] = handle; -} - Class *classForName(const char *name) { if (name) { @@ -304,8 +298,10 @@ Class *classForName(const char *name) { Class *clazz = NULL; Class *(*archetype)(void) = NULL; - for (size_t i = _classImageCount; i > 0 && archetype == NULL; i--) { - archetype = dlsym(_classImages[i - 1], s); + for (ClassImage *i = _images; i && archetype == NULL; i = i->next) { + if (i->handle) { + archetype = dlsym(i->handle, s); + } } if (archetype == NULL) { diff --git a/Sources/Objectively/Class.h b/Sources/Objectively/Class.h index 0365fce..b635850 100644 --- a/Sources/Objectively/Class.h +++ b/Sources/Objectively/Class.h @@ -138,6 +138,8 @@ OBJECTIVELY_EXPORT ident _cast(const Class *clazz, const ident obj); /** * @brief Registers an image that provides Classes, e.g. a plugin. * @param handle A handle from `dlopen`. + * @param address Any address within that image, such as the entry point the + * application resolved from `handle`. * @details `classForName` resolves a name it has not yet initialized through the * process-wide namespace, which holds only images loaded `RTLD_GLOBAL`, and which * Windows does not have at all. An application that loads Classes from a plugin @@ -147,8 +149,12 @@ OBJECTIVELY_EXPORT ident _cast(const Class *clazz, const ident obj); * loaded plugin answers ahead of the one it replaced. * @remarks Resolution is by Objectively's own convention, the Class name * prefixed with an underscore, so nothing about the image has to be declared. + * @remarks `address` is what identifies the image. Classes record the base + * address of the image that declared them, and the platforms report that for an + * address, not for a handle, so this takes one that the caller can vouch for. + * Aborts if no loaded image contains it. */ -OBJECTIVELY_EXPORT void addClassImage(ident handle); +OBJECTIVELY_EXPORT void addClassImage(ident handle, const ident address); /** * @brief Unregisters an image, and every Class it declared. @@ -168,6 +174,8 @@ OBJECTIVELY_EXPORT void addClassImage(ident handle); * and its interface, which are not reclaimed. * @remarks MUST be called while the handle is still open, and only once nothing * instantiated from that image survives. + * @remarks Aborts if `handle` was never registered, rather than leaving the + * Classes it declared behind, which is the failure this exists to prevent. */ OBJECTIVELY_EXPORT void removeClassImage(ident handle); From 2c2a17c944675901b10cadc1317781d3c0b39703 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 17 Aug 2026 21:15:33 -0400 Subject: [PATCH 2/5] Publish and retire class images atomically classForName walks the image list on any thread, while addClassImage prepended to it and removeClassImage wrote through it with plain stores. Two registrations could lose one another, and a lookup could read a half-written node. Push with a compare and swap, as a Class is pushed, and retire an image with a single release store of the handle the walk matches on, so that walk sees an image or does not and never part of one. Nothing is unlinked or freed before teardown, so a walk in progress always has a next. removeClassImage's unlinking of Classes is still not atomic against a concurrent registration, and two concurrent calls still race with each other. Both remain the caller's to serialize, as documented. Co-Authored-By: Claude Opus 5 (1M context) --- Sources/Objectively/Class.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Sources/Objectively/Class.c b/Sources/Objectively/Class.c index 1aa9764..7f7f522 100644 --- a/Sources/Objectively/Class.c +++ b/Sources/Objectively/Class.c @@ -240,9 +240,12 @@ void addClassImage(ident handle, const ident address) { classImage->handle = handle; classImage->image = image; - classImage->next = _images; - _images = classImage; + /* Published the same way a Class is, and for the same reason: classForName + * walks this list on any thread. */ + classImage->next = __atomic_load_n(&_images, __ATOMIC_RELAXED); + while (!__atomic_compare_exchange_n(&_images, &classImage->next, classImage, 1, + __ATOMIC_RELEASE, __ATOMIC_RELAXED)) ; } void removeClassImage(ident handle) { @@ -252,14 +255,15 @@ void removeClassImage(ident handle) { ident image = NULL; /* Retired in place rather than unlinked, so that a concurrent classForName - * parked on this node still has a next to follow. Retired nodes are skipped - * on lookup and freed at teardown; reusing one would put a newly registered - * image where the retired one sat, and lookup order is newest first. */ - for (ClassImage *i = _images; i; i = i->next) { - if (i->handle == handle) { + * parked on this node still has a next to follow, and never reads a node that + * has been freed. Retiring is a single store of the handle it matches on, so + * that walk sees this image or does not, and never half of it. Retired nodes + * are freed at teardown; reusing one would put a newly registered image where + * the retired one sat, and lookup order is newest first. */ + for (ClassImage *i = __atomic_load_n(&_images, __ATOMIC_ACQUIRE); i; i = i->next) { + if (__atomic_load_n(&i->handle, __ATOMIC_ACQUIRE) == handle) { image = i->image; - i->handle = NULL; - i->image = NULL; + __atomic_store_n(&i->handle, NULL, __ATOMIC_RELEASE); break; } } @@ -298,9 +302,12 @@ Class *classForName(const char *name) { Class *clazz = NULL; Class *(*archetype)(void) = NULL; - for (ClassImage *i = _images; i && archetype == NULL; i = i->next) { - if (i->handle) { - archetype = dlsym(i->handle, s); + for (ClassImage *i = __atomic_load_n(&_images, __ATOMIC_ACQUIRE); + i && archetype == NULL; i = i->next) { + + ident handle = __atomic_load_n(&i->handle, __ATOMIC_ACQUIRE); + if (handle) { + archetype = dlsym(handle, s); } } From 9168d3e0c5c5aae084a7e895e131f0532d0941b8 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 17 Aug 2026 21:24:02 -0400 Subject: [PATCH 3/5] Guard the Class list with a mutex removeClassImage splices _classes while classForName walks it. Unlinking is a read and a write over a list another thread is traversing, and making each store atomic does not make the pair of them one operation: the head store could drop a registration published between them, and clearing next could end a live walk early, hiding every Class behind it. A mutex around the three operations on the list, taken only for the walk, the push and the splice, and never across dlsym, dlopen or a Class initializer, each of which can reenter _initialize. The atomics on _classes go away with it. _images keeps its atomics. It is only ever pushed to and retired in place, never spliced, and it is walked while calling dlsym, which the lock must not cover. Verified under ThreadSanitizer, six threads looking up names against two thousand register and unregister cycles. Co-Authored-By: Claude Opus 5 (1M context) --- Sources/Objectively/Class.c | 42 ++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Sources/Objectively/Class.c b/Sources/Objectively/Class.c index 7f7f522..87f69d5 100644 --- a/Sources/Objectively/Class.c +++ b/Sources/Objectively/Class.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,12 @@ size_t _pageSize; static Class *_classes; +/** + * @brief Guards the structure of `_classes`. MUST NOT be held across `dlsym`, + * `dlopen`, or a Class initializer, each of which can reenter `_initialize`. + */ +static pthread_mutex_t _classesLock = PTHREAD_MUTEX_INITIALIZER; + /** * @brief A registered image: the handle the application holds, and the base * address that the Classes it declares record in `Class::image`. @@ -57,8 +64,9 @@ struct ClassImage { /** * @brief The registered images providing Classes, most recently added first. - * @remarks A plain list rather than a MutableArray because Class is beneath the - * collections: initializing one here would reenter `_initialize`. + * Published atomically rather than under `_classesLock`, which cannot be held + * across the `dlsym` this list exists for. A plain list rather than a + * MutableArray, because Class is beneath the collections. */ static ClassImage *_images; @@ -176,12 +184,14 @@ Class *_initialize(const ClassDef *def) { * a compound literal with automatic storage. */ clazz->image = imageForAddress((ident) def->name); - /* Link before publishing, and relaxed on the way in: the CAS is a read-modify- - * write, so it joins the release sequence of the push it displaces, and a - * reader acquiring the head synchronizes with every publisher behind it. The - * head is only copied here, never dereferenced, so there is nothing to acquire. */ - clazz->next = __atomic_load_n(&_classes, __ATOMIC_RELAXED); - while (!__atomic_compare_exchange_n(&_classes, &clazz->next, clazz, 1, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) ; + /* Taken here rather than around the whole function: def.initialize above can + * reach other archetypes, and so this, before that Class is published. */ + pthread_mutex_lock(&_classesLock); + + clazz->next = _classes; + _classes = clazz; + + pthread_mutex_unlock(&_classesLock); return clazz; } @@ -273,6 +283,8 @@ void removeClassImage(ident handle) { abort(); } + pthread_mutex_lock(&_classesLock); + Class **classes = &_classes; while (*classes) { Class *clazz = *classes; @@ -284,19 +296,29 @@ void removeClassImage(ident handle) { classes = &clazz->next; } } + + pthread_mutex_unlock(&_classesLock); } Class *classForName(const char *name) { if (name) { - Class *c = __atomic_load_n(&_classes, __ATOMIC_ACQUIRE); + pthread_mutex_lock(&_classesLock); + + Class *c = _classes; while (c) { if (strcmp(name, c->def.name) == 0) { - return c; + break; } c = c->next; } + pthread_mutex_unlock(&_classesLock); + + if (c) { + return c; + } + char *s; if (asprintf(&s, "_%s", name) > 0) { Class *clazz = NULL; From 3c05cab9931753963113da3f941645f311809dc2 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 17 Aug 2026 21:35:04 -0400 Subject: [PATCH 4/5] Serialize removeClassImage against itself Retiring an image and dropping its Classes were two steps with a gap between them, so two calls for the same handle could both find it registered, both proceed, and neither report the duplicate the abort is there to catch. Hold the lock from the search through the last unlink. Removing two different images was already safe, since each retires its own entry and the unlinking was already serialized. This closes the case of the same handle arriving twice, and makes that abort exact. Nothing in this call reaches the loader, so the lock may cover all of it. Co-Authored-By: Claude Opus 5 (1M context) --- Sources/Objectively/Class.c | 7 +++++-- Sources/Objectively/Class.h | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/Objectively/Class.c b/Sources/Objectively/Class.c index 87f69d5..bb8a8f9 100644 --- a/Sources/Objectively/Class.c +++ b/Sources/Objectively/Class.c @@ -262,6 +262,11 @@ void removeClassImage(ident handle) { assert(handle); + /* Held from the search to the last unlink, so that retiring an image and + * dropping its Classes is one operation, and a second call for the same handle + * finds it already gone. Nothing here reaches the loader. */ + pthread_mutex_lock(&_classesLock); + ident image = NULL; /* Retired in place rather than unlinked, so that a concurrent classForName @@ -283,8 +288,6 @@ void removeClassImage(ident handle) { abort(); } - pthread_mutex_lock(&_classesLock); - Class **classes = &_classes; while (*classes) { Class *clazz = *classes; diff --git a/Sources/Objectively/Class.h b/Sources/Objectively/Class.h index b635850..c900113 100644 --- a/Sources/Objectively/Class.h +++ b/Sources/Objectively/Class.h @@ -174,8 +174,9 @@ OBJECTIVELY_EXPORT void addClassImage(ident handle, const ident address); * and its interface, which are not reclaimed. * @remarks MUST be called while the handle is still open, and only once nothing * instantiated from that image survives. - * @remarks Aborts if `handle` was never registered, rather than leaving the - * Classes it declared behind, which is the failure this exists to prevent. + * @remarks Aborts if `handle` was not registered, rather than leaving the + * Classes it declared behind, which is the failure this exists to prevent. Two + * calls for the same handle abort on the second, whichever order they arrive in. */ OBJECTIVELY_EXPORT void removeClassImage(ident handle); From 07ecf782ece94b1cb07de8a24d0d57d20804ca69 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 17 Aug 2026 21:37:53 -0400 Subject: [PATCH 5/5] Delete stale test. --- Tests/Objectively/Locale | 210 --------------------------------------- 1 file changed, 210 deletions(-) delete mode 100755 Tests/Objectively/Locale diff --git a/Tests/Objectively/Locale b/Tests/Objectively/Locale deleted file mode 100755 index b3638ff..0000000 --- a/Tests/Objectively/Locale +++ /dev/null @@ -1,210 +0,0 @@ -#! /bin/sh - -# Locale - temporary wrapper script for .libs/Locale -# Generated by libtool (GNU libtool) 2.4.6 -# -# The Locale program cannot be directly executed until all the libtool -# libraries that it depends on are installed. -# -# This wrapper script should never be moved out of the build directory. -# If it is, it will not operate correctly. - -# Sed substitution that helps us do robust quoting. It backslashifies -# metacharacters that are still active within double-quoted strings. -sed_quote_subst='s|\([`"$\\]\)|\\\1|g' - -# Be Bourne compatible -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then - emulate sh - NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac -fi -BIN_SH=xpg4; export BIN_SH # for Tru64 -DUALCASE=1; export DUALCASE # for MKS sh - -# The HP-UX ksh and POSIX shell print the target directory to stdout -# if CDPATH is set. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - -relink_command="" - -# This environment variable determines our operation mode. -if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then - # install mode needs the following variables: - generated_by_libtool_version='2.4.6' - notinst_deplibs=' ../../Sources/Objectively/libObjectively.la' -else - # When we are sourced in execute mode, $file and $ECHO are already set. - if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then - file="$0" - -# A function that is used when there is no print builtin or printf. -func_fallback_echo () -{ - eval 'cat <<_LTECHO_EOF -$1 -_LTECHO_EOF' -} - ECHO="printf %s\\n" - fi - -# Very basic option parsing. These options are (a) specific to -# the libtool wrapper, (b) are identical between the wrapper -# /script/ and the wrapper /executable/ that is used only on -# windows platforms, and (c) all begin with the string --lt- -# (application programs are unlikely to have options that match -# this pattern). -# -# There are only two supported options: --lt-debug and -# --lt-dump-script. There is, deliberately, no --lt-help. -# -# The first argument to this parsing function should be the -# script's ../../libtool value, followed by no. -lt_option_debug= -func_parse_lt_options () -{ - lt_script_arg0=$0 - shift - for lt_opt - do - case "$lt_opt" in - --lt-debug) lt_option_debug=1 ;; - --lt-dump-script) - lt_dump_D=`$ECHO "X$lt_script_arg0" | /opt/local/bin/gsed -e 's/^X//' -e 's%/[^/]*$%%'` - test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=. - lt_dump_F=`$ECHO "X$lt_script_arg0" | /opt/local/bin/gsed -e 's/^X//' -e 's%^.*/%%'` - cat "$lt_dump_D/$lt_dump_F" - exit 0 - ;; - --lt-*) - $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2 - exit 1 - ;; - esac - done - - # Print the debug banner immediately: - if test -n "$lt_option_debug"; then - echo "Locale:Locale:$LINENO: libtool wrapper (GNU libtool) 2.4.6" 1>&2 - fi -} - -# Used when --lt-debug. Prints its arguments to stdout -# (redirection is the responsibility of the caller) -func_lt_dump_args () -{ - lt_dump_args_N=1; - for lt_arg - do - $ECHO "Locale:Locale:$LINENO: newargv[$lt_dump_args_N]: $lt_arg" - lt_dump_args_N=`expr $lt_dump_args_N + 1` - done -} - -# Core function for launching the target application -func_exec_program_core () -{ - - if test -n "$lt_option_debug"; then - $ECHO "Locale:Locale:$LINENO: newargv[0]: $progdir/$program" 1>&2 - func_lt_dump_args ${1+"$@"} 1>&2 - fi - exec "$progdir/$program" ${1+"$@"} - - $ECHO "$0: cannot exec $program $*" 1>&2 - exit 1 -} - -# A function to encapsulate launching the target application -# Strips options in the --lt-* namespace from $@ and -# launches target application with the remaining arguments. -func_exec_program () -{ - case " $* " in - *\ --lt-*) - for lt_wr_arg - do - case $lt_wr_arg in - --lt-*) ;; - *) set x "$@" "$lt_wr_arg"; shift;; - esac - shift - done ;; - esac - func_exec_program_core ${1+"$@"} -} - - # Parse options - func_parse_lt_options "$0" ${1+"$@"} - - # Find the directory that this script lives in. - thisdir=`$ECHO "$file" | /opt/local/bin/gsed 's%/[^/]*$%%'` - test "x$thisdir" = "x$file" && thisdir=. - - # Follow symbolic links until we get to the real thisdir. - file=`ls -ld "$file" | /opt/local/bin/gsed -n 's/.*-> //p'` - while test -n "$file"; do - destdir=`$ECHO "$file" | /opt/local/bin/gsed 's%/[^/]*$%%'` - - # If there was a directory component, then change thisdir. - if test "x$destdir" != "x$file"; then - case "$destdir" in - [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;; - *) thisdir="$thisdir/$destdir" ;; - esac - fi - - file=`$ECHO "$file" | /opt/local/bin/gsed 's%^.*/%%'` - file=`ls -ld "$thisdir/$file" | /opt/local/bin/gsed -n 's/.*-> //p'` - done - - # Usually 'no', except on cygwin/mingw when embedded into - # the cwrapper. - WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no - if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then - # special case for '.' - if test "$thisdir" = "."; then - thisdir=`pwd` - fi - # remove .libs from thisdir - case "$thisdir" in - *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /opt/local/bin/gsed 's%[\\/][^\\/]*$%%'` ;; - .libs ) thisdir=. ;; - esac - fi - - # Try to get the absolute directory name. - absdir=`cd "$thisdir" && pwd` - test -n "$absdir" && thisdir="$absdir" - - program='Locale' - progdir="$thisdir/.libs" - - - if test -f "$progdir/$program"; then - # Add our own library path to DYLD_LIBRARY_PATH - DYLD_LIBRARY_PATH="/Users/jdolan/Coding/Objectively/Sources/Objectively/.libs:$DYLD_LIBRARY_PATH" - - # Some systems cannot cope with colon-terminated DYLD_LIBRARY_PATH - # The second colon is a workaround for a bug in BeOS R4 sed - DYLD_LIBRARY_PATH=`$ECHO "$DYLD_LIBRARY_PATH" | /opt/local/bin/gsed 's/::*$//'` - - export DYLD_LIBRARY_PATH - - if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then - # Run the actual program with our arguments. - func_exec_program ${1+"$@"} - fi - else - # The program doesn't exist. - $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2 - $ECHO "This script is just a wrapper for $program." 1>&2 - $ECHO "See the libtool documentation for more information." 1>&2 - exit 1 - fi -fi