1/*
2 * tclMacOSXBundle.c --
3 *
4 *	This file implements functions that inspect CFBundle structures on
5 *	MacOS X.
6 *
7 * Copyright 2001-2009, Apple Inc.
8 * Copyright (c) 2003-2009 Daniel A. Steffen <das@users.sourceforge.net>
9 *
10 * See the file "license.terms" for information on usage and redistribution of
11 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id: tclMacOSXBundle.c,v 1.11.4.4 2009/10/05 02:41:13 das Exp $
14 */
15
16#include "tclPort.h"
17
18#ifdef HAVE_COREFOUNDATION
19#include <CoreFoundation/CoreFoundation.h>
20
21#ifndef TCL_DYLD_USE_DLFCN
22/*
23 * Use preferred dlfcn API on 10.4 and later
24 */
25#   if !defined(NO_DLFCN_H) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1040
26#	define TCL_DYLD_USE_DLFCN 1
27#   else
28#	define TCL_DYLD_USE_DLFCN 0
29#   endif
30#endif
31
32#ifndef TCL_DYLD_USE_NSMODULE
33/*
34 * Use deprecated NSModule API only to support 10.3 and earlier:
35 */
36#   if MAC_OS_X_VERSION_MIN_REQUIRED < 1040
37#	define TCL_DYLD_USE_NSMODULE 1
38#   else
39#	define TCL_DYLD_USE_NSMODULE 0
40#   endif
41#endif
42
43#if TCL_DYLD_USE_DLFCN
44#include <dlfcn.h>
45#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
46/*
47 * Support for weakly importing dlfcn API.
48 */
49extern void *dlsym(void *handle, const char *symbol) WEAK_IMPORT_ATTRIBUTE;
50extern char *dlerror(void) WEAK_IMPORT_ATTRIBUTE;
51#endif
52#endif
53
54#if TCL_DYLD_USE_NSMODULE
55#include <mach-o/dyld.h>
56#endif
57
58#if (TCL_DYLD_USE_DLFCN && MAC_OS_X_VERSION_MIN_REQUIRED < 1040) || \
59	(MAC_OS_X_VERSION_MIN_REQUIRED < 1050)
60MODULE_SCOPE long tclMacOSXDarwinRelease;
61#endif
62
63#ifdef TCL_DEBUG_LOAD
64#define TclLoadDbgMsg(m, ...) do { \
65	    fprintf(stderr, "%s:%d: %s(): " m ".\n", \
66	    strrchr(__FILE__, '/')+1, __LINE__, __func__, ##__VA_ARGS__); \
67	} while (0)
68#else
69#define TclLoadDbgMsg(m, ...)
70#endif
71
72#endif /* HAVE_COREFOUNDATION */
73
74/*
75 *----------------------------------------------------------------------
76 *
77 * Tcl_MacOSXOpenBundleResources --
78 *
79 *	Given the bundle name for a shared library, this routine sets
80 *	libraryPath to the Resources/Scripts directory in the framework
81 *	package. If hasResourceFile is true, it will also open the main
82 *	resource file for the bundle.
83 *
84 * Results:
85 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
86 *	TCL_ERROR otherwise.
87 *
88 * Side effects:
89 *	libraryVariableName may be set, and the resource file opened.
90 *
91 *----------------------------------------------------------------------
92 */
93
94int
95Tcl_MacOSXOpenBundleResources(
96    Tcl_Interp *interp,
97    CONST char *bundleName,
98    int hasResourceFile,
99    int maxPathLen,
100    char *libraryPath)
101{
102    return Tcl_MacOSXOpenVersionedBundleResources(interp, bundleName,
103	    NULL, hasResourceFile, maxPathLen, libraryPath);
104}
105
106/*
107 *----------------------------------------------------------------------
108 *
109 * Tcl_MacOSXOpenVersionedBundleResources --
110 *
111 *	Given the bundle and version name for a shared library (version name
112 *	can be NULL to indicate latest version), this routine sets libraryPath
113 *	to the Resources/Scripts directory in the framework package. If
114 *	hasResourceFile is true, it will also open the main resource file for
115 *	the bundle.
116 *
117 * Results:
118 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
119 *	TCL_ERROR otherwise.
120 *
121 * Side effects:
122 *	libraryVariableName may be set, and the resource file opened.
123 *
124 *----------------------------------------------------------------------
125 */
126
127int
128Tcl_MacOSXOpenVersionedBundleResources(
129    Tcl_Interp *interp,
130    CONST char *bundleName,
131    CONST char *bundleVersion,
132    int hasResourceFile,
133    int maxPathLen,
134    char *libraryPath)
135{
136#ifdef HAVE_COREFOUNDATION
137    CFBundleRef bundleRef, versionedBundleRef = NULL;
138    CFStringRef bundleNameRef;
139    CFURLRef libURL;
140
141    libraryPath[0] = '\0';
142
143    bundleNameRef = CFStringCreateWithCString(NULL, bundleName,
144	    kCFStringEncodingUTF8);
145
146    bundleRef = CFBundleGetBundleWithIdentifier(bundleNameRef);
147    CFRelease(bundleNameRef);
148
149    if (bundleVersion && bundleRef) {
150	/*
151	 * Create bundle from bundleVersion subdirectory of 'Versions'.
152	 */
153
154	CFURLRef bundleURL = CFBundleCopyBundleURL(bundleRef);
155
156	if (bundleURL) {
157	    CFStringRef bundleVersionRef = CFStringCreateWithCString(NULL,
158		    bundleVersion, kCFStringEncodingUTF8);
159
160	    if (bundleVersionRef) {
161		CFComparisonResult versionComparison = kCFCompareLessThan;
162		CFStringRef bundleTailRef = CFURLCopyLastPathComponent(
163			bundleURL);
164
165		if (bundleTailRef) {
166		    versionComparison = CFStringCompare(bundleTailRef,
167			    bundleVersionRef, 0);
168		    CFRelease(bundleTailRef);
169		}
170		if (versionComparison != kCFCompareEqualTo) {
171		    CFURLRef versURL = CFURLCreateCopyAppendingPathComponent(
172			    NULL, bundleURL, CFSTR("Versions"), TRUE);
173
174		    if (versURL) {
175			CFURLRef versionedBundleURL =
176				CFURLCreateCopyAppendingPathComponent(
177				NULL, versURL, bundleVersionRef, TRUE);
178
179			if (versionedBundleURL) {
180			    versionedBundleRef = CFBundleCreate(NULL,
181				    versionedBundleURL);
182			    if (versionedBundleRef) {
183				bundleRef = versionedBundleRef;
184			    }
185			    CFRelease(versionedBundleURL);
186			}
187			CFRelease(versURL);
188		    }
189		}
190		CFRelease(bundleVersionRef);
191	    }
192	    CFRelease(bundleURL);
193	}
194    }
195
196    if (bundleRef) {
197	if (hasResourceFile) {
198	    /*
199	     * Dynamically acquire address for CFBundleOpenBundleResourceMap
200	     * symbol, since it is only present in full CoreFoundation on Mac
201	     * OS X and not in CFLite on pure Darwin.
202	     */
203
204	    static int initialized = FALSE;
205	    static short (*openresourcemap)(CFBundleRef) = NULL;
206
207	    if (!initialized) {
208#if TCL_DYLD_USE_DLFCN
209#if MAC_OS_X_VERSION_MIN_REQUIRED < 1040
210		if (tclMacOSXDarwinRelease >= 8)
211#endif
212		{
213		    const char *errMsg = nil;
214		    openresourcemap = dlsym(RTLD_NEXT,
215			    "CFBundleOpenBundleResourceMap");
216		    if (!openresourcemap) {
217			errMsg = dlerror();
218			TclLoadDbgMsg("dlsym() failed: %s", errMsg);
219		    }
220		}
221		if (!openresourcemap)
222#endif
223		{
224#if TCL_DYLD_USE_NSMODULE
225		    NSSymbol nsSymbol = NULL;
226		    if (NSIsSymbolNameDefinedWithHint(
227			    "_CFBundleOpenBundleResourceMap",
228			    "CoreFoundation")) {
229			nsSymbol = NSLookupAndBindSymbolWithHint(
230				"_CFBundleOpenBundleResourceMap",
231				"CoreFoundation");
232			if (nsSymbol) {
233			    openresourcemap = NSAddressOfSymbol(nsSymbol);
234			}
235		    }
236#endif
237		}
238		initialized = TRUE;
239	    }
240
241	    if (openresourcemap) {
242		short refNum;
243
244		refNum = openresourcemap(bundleRef);
245	    }
246	}
247
248	libURL = CFBundleCopyResourceURL(bundleRef, CFSTR("Scripts"),
249		NULL, NULL);
250
251	if (libURL) {
252	    /*
253	     * FIXME: This is a quick fix, it is probably not right for
254	     * internationalization.
255	     */
256
257	    CFURLGetFileSystemRepresentation(libURL, TRUE,
258		    (unsigned char*) libraryPath, maxPathLen);
259	    CFRelease(libURL);
260	}
261	if (versionedBundleRef) {
262#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
263	    /* Workaround CFBundle bug in Tiger and earlier. [Bug 2569449] */
264	    if (tclMacOSXDarwinRelease >= 9)
265#endif
266	    {
267		CFRelease(versionedBundleRef);
268	    }
269	}
270    }
271
272    if (libraryPath[0]) {
273	return TCL_OK;
274    } else {
275	return TCL_ERROR;
276    }
277#else  /* HAVE_COREFOUNDATION */
278    return TCL_ERROR;
279#endif /* HAVE_COREFOUNDATION */
280}
281
282/*
283 * Local Variables:
284 * mode: c
285 * c-basic-offset: 4
286 * fill-column: 78
287 * End:
288 */
289