1/*
2 * tclMacOSXBundle.c --
3 *
4 *	This file implements functions that inspect CFBundle structures on
5 *	MacOS X.
6 *
7 * Copyright 2001, Apple Computer, 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 *	The following terms apply to all files originating from Apple
14 *	Computer, Inc. ("Apple") and associated with the software unless
15 *	explicitly disclaimed in individual files.
16 *
17 *	Apple hereby grants permission to use, copy, modify, distribute, and
18 *	license this software and its documentation for any purpose, provided
19 *	that existing copyright notices are retained in all copies and that
20 *	this notice is included verbatim in any distributions. No written
21 *	agreement, license, or royalty fee is required for any of the
22 *	authorized uses. Modifications to this software may be copyrighted by
23 *	their authors and need not follow the licensing terms described here,
24 *	provided that the new terms are clearly indicated on the first page of
25 *	each file where they apply.
26 *
27 *	IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE SOFTWARE
28 *	BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
29 *	CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS
30 *	DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN IF APPLE OR THE
31 *	AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. APPLE,
32 *	THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
33 *	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
34 *	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
35 *	NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND
36 *	APPLE,THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
37 *	MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
38 *
39 *	GOVERNMENT USE: If you are acquiring this software on behalf of the
40 *	U.S. government, the Government shall have only "Restricted Rights" in
41 *	the software and related documentation as defined in the Federal
42 *	Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you are
43 *	acquiring the software on behalf of the Department of Defense, the
44 *	software shall be classified as "Commercial Computer Software" and the
45 *	Government shall have only "Restricted Rights" as defined in Clause
46 *	252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the
47 *	authors grant the U.S. Government and others acting in its behalf
48 *	permission to use and distribute the software in accordance with the
49 *	terms specified in this license.
50 *
51 * RCS: @(#) $Id: tclMacOSXBundle.c,v 1.3.2.6 2007/04/29 02:21:33 das Exp $
52 */
53
54#include "tclPort.h"
55
56#ifdef HAVE_COREFOUNDATION
57#include <CoreFoundation/CoreFoundation.h>
58#include <mach-o/dyld.h>
59
60#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
61MODULE_SCOPE long tclMacOSXDarwinRelease;
62#endif
63
64#endif /* HAVE_COREFOUNDATION */
65
66/*
67 *----------------------------------------------------------------------
68 *
69 * Tcl_MacOSXOpenBundleResources --
70 *
71 *	Given the bundle name for a shared library, this routine sets
72 *	libraryPath to the Resources/Scripts directory in the framework
73 *	package. If hasResourceFile is true, it will also open the main
74 *	resource file for the bundle.
75 *
76 * Results:
77 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
78 *	TCL_ERROR otherwise.
79 *
80 * Side effects:
81 *	libraryVariableName may be set, and the resource file opened.
82 *
83 *----------------------------------------------------------------------
84 */
85
86int
87Tcl_MacOSXOpenBundleResources(
88    Tcl_Interp *interp,
89    CONST char *bundleName,
90    int hasResourceFile,
91    int maxPathLen,
92    char *libraryPath)
93{
94    return Tcl_MacOSXOpenVersionedBundleResources(interp, bundleName,
95	    NULL, hasResourceFile, maxPathLen, libraryPath);
96}
97
98/*
99 *----------------------------------------------------------------------
100 *
101 * Tcl_MacOSXOpenVersionedBundleResources --
102 *
103 *	Given the bundle and version name for a shared library (version name
104 *	can be NULL to indicate latest version), this routine sets libraryPath
105 *	to the Resources/Scripts directory in the framework package. If
106 *	hasResourceFile is true, it will also open the main resource file for
107 *	the bundle.
108 *
109 * Results:
110 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
111 *	TCL_ERROR otherwise.
112 *
113 * Side effects:
114 *	libraryVariableName may be set, and the resource file opened.
115 *
116 *----------------------------------------------------------------------
117 */
118
119int
120Tcl_MacOSXOpenVersionedBundleResources(
121    Tcl_Interp *interp,
122    CONST char *bundleName,
123    CONST char *bundleVersion,
124    int hasResourceFile,
125    int maxPathLen,
126    char *libraryPath)
127{
128#ifdef HAVE_COREFOUNDATION
129    CFBundleRef bundleRef, versionedBundleRef = NULL;
130    CFStringRef bundleNameRef;
131    CFURLRef libURL;
132
133    libraryPath[0] = '\0';
134
135    bundleNameRef = CFStringCreateWithCString(NULL, bundleName,
136	    kCFStringEncodingUTF8);
137
138    bundleRef = CFBundleGetBundleWithIdentifier(bundleNameRef);
139    CFRelease(bundleNameRef);
140
141    if (bundleVersion && bundleRef) {
142	/*
143	 * Create bundle from bundleVersion subdirectory of 'Versions'.
144	 */
145
146	CFURLRef bundleURL = CFBundleCopyBundleURL(bundleRef);
147
148	if (bundleURL) {
149	    CFStringRef bundleVersionRef = CFStringCreateWithCString(NULL,
150		    bundleVersion, kCFStringEncodingUTF8);
151
152	    if (bundleVersionRef) {
153		CFComparisonResult versionComparison = kCFCompareLessThan;
154		CFStringRef bundleTailRef = CFURLCopyLastPathComponent(
155			bundleURL);
156
157		if (bundleTailRef) {
158		    versionComparison = CFStringCompare(bundleTailRef,
159			    bundleVersionRef, 0);
160		    CFRelease(bundleTailRef);
161		}
162		if (versionComparison != kCFCompareEqualTo) {
163		    CFURLRef versURL = CFURLCreateCopyAppendingPathComponent(
164			    NULL, bundleURL, CFSTR("Versions"), TRUE);
165
166		    if (versURL) {
167			CFURLRef versionedBundleURL =
168				CFURLCreateCopyAppendingPathComponent(
169				NULL, versURL, bundleVersionRef, TRUE);
170
171			if (versionedBundleURL) {
172			    versionedBundleRef = CFBundleCreate(NULL,
173				    versionedBundleURL);
174			    if (versionedBundleRef) {
175				bundleRef = versionedBundleRef;
176			    }
177			    CFRelease(versionedBundleURL);
178			}
179			CFRelease(versURL);
180		    }
181		}
182		CFRelease(bundleVersionRef);
183	    }
184	    CFRelease(bundleURL);
185	}
186    }
187
188    if (bundleRef) {
189	if (hasResourceFile) {
190	    /*
191	     * Dynamically acquire address for CFBundleOpenBundleResourceMap
192	     * symbol, since it is only present in full CoreFoundation on Mac
193	     * OS X and not in CFLite on pure Darwin.
194	     */
195
196	    static int initialized = FALSE;
197	    static short (*openresourcemap)(CFBundleRef) = NULL;
198
199	    if (!initialized) {
200		NSSymbol nsSymbol = NULL;
201		if (NSIsSymbolNameDefinedWithHint(
202			"_CFBundleOpenBundleResourceMap", "CoreFoundation")) {
203		    nsSymbol = NSLookupAndBindSymbolWithHint(
204			    "_CFBundleOpenBundleResourceMap","CoreFoundation");
205		    if (nsSymbol) {
206			openresourcemap = NSAddressOfSymbol(nsSymbol);
207		    }
208		}
209		initialized = TRUE;
210	    }
211
212	    if (openresourcemap) {
213		short refNum;
214
215		refNum = openresourcemap(bundleRef);
216	    }
217	}
218
219	libURL = CFBundleCopyResourceURL(bundleRef, CFSTR("Scripts"),
220		NULL, NULL);
221
222	if (libURL) {
223	    /*
224	     * FIXME: This is a quick fix, it is probably not right for
225	     * internationalization.
226	     */
227
228	    CFURLGetFileSystemRepresentation(libURL, TRUE,
229		    (unsigned char*) libraryPath, maxPathLen);
230	    CFRelease(libURL);
231	}
232	if (versionedBundleRef) {
233#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
234	    /* Workaround CFBundle bug in Tiger and earlier. [Bug 2569449] */
235	    if (tclMacOSXDarwinRelease >= 9)
236#endif
237	    {
238		CFRelease(versionedBundleRef);
239	    }
240	}
241    }
242
243    if (libraryPath[0]) {
244	return TCL_OK;
245    } else {
246	return TCL_ERROR;
247    }
248#else  /* HAVE_COREFOUNDATION */
249    return TCL_ERROR;
250#endif /* HAVE_COREFOUNDATION */
251}
252