1 | //===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "CFBundle.h" |
10 | |
11 | #ifdef __APPLE__ |
12 | #include "llvm/Support/FileSystem.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/raw_ostream.h" |
15 | #include <CoreFoundation/CoreFoundation.h> |
16 | #include <assert.h> |
17 | #include <glob.h> |
18 | #include <memory> |
19 | #endif |
20 | |
21 | namespace llvm { |
22 | namespace dsymutil { |
23 | |
24 | #ifdef __APPLE__ |
25 | /// Deleter that calls CFRelease rather than deleting the pointer. |
26 | template <typename T> struct CFDeleter { |
27 | void operator()(T *P) { |
28 | if (P) |
29 | ::CFRelease(P); |
30 | } |
31 | }; |
32 | |
33 | /// This helper owns any CoreFoundation pointer and will call CFRelease() on |
34 | /// any valid pointer it owns unless that pointer is explicitly released using |
35 | /// the release() member function. |
36 | template <typename T> |
37 | using CFReleaser = std::unique_ptr<std::remove_pointer_t<T>, |
38 | CFDeleter<std::remove_pointer_t<T>>>; |
39 | |
40 | /// RAII wrapper around CFBundleRef. |
41 | class CFString : public CFReleaser<CFStringRef> { |
42 | public: |
43 | CFString(CFStringRef CFStr = nullptr) : CFReleaser<CFStringRef>(CFStr) {} |
44 | |
45 | const char *UTF8(std::string &Str) const { |
46 | return CFString::UTF8(get(), Str); |
47 | } |
48 | |
49 | CFIndex GetLength() const { |
50 | if (CFStringRef Str = get()) |
51 | return CFStringGetLength(Str); |
52 | return 0; |
53 | } |
54 | |
55 | static const char *UTF8(CFStringRef CFStr, std::string &Str); |
56 | }; |
57 | |
58 | /// Static function that puts a copy of the UTF-8 contents of CFStringRef into |
59 | /// std::string and returns the C string pointer that is contained in the |
60 | /// std::string when successful, nullptr otherwise. |
61 | /// |
62 | /// This allows the std::string parameter to own the extracted string, and also |
63 | /// allows that string to be returned as a C string pointer that can be used. |
64 | const char *CFString::UTF8(CFStringRef CFStr, std::string &Str) { |
65 | if (!CFStr) |
66 | return nullptr; |
67 | |
68 | const CFStringEncoding Encoding = kCFStringEncodingUTF8; |
69 | CFIndex MaxUTF8StrLength = CFStringGetLength(CFStr); |
70 | MaxUTF8StrLength = |
71 | CFStringGetMaximumSizeForEncoding(MaxUTF8StrLength, Encoding); |
72 | if (MaxUTF8StrLength > 0) { |
73 | Str.resize(MaxUTF8StrLength); |
74 | if (!Str.empty() && |
75 | CFStringGetCString(CFStr, &Str[0], Str.size(), Encoding)) { |
76 | Str.resize(strlen(Str.c_str())); |
77 | return Str.c_str(); |
78 | } |
79 | } |
80 | |
81 | return nullptr; |
82 | } |
83 | |
84 | /// RAII wrapper around CFBundleRef. |
85 | class CFBundle : public CFReleaser<CFBundleRef> { |
86 | public: |
87 | CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); } |
88 | |
89 | CFBundle(CFURLRef Url) |
90 | : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url) |
91 | : nullptr) {} |
92 | |
93 | /// Return the bundle identifier. |
94 | CFStringRef GetIdentifier() const { |
95 | if (CFBundleRef bundle = get()) |
96 | return ::CFBundleGetIdentifier(bundle); |
97 | return nullptr; |
98 | } |
99 | |
100 | /// Return value for key. |
101 | CFTypeRef GetValueForInfoDictionaryKey(CFStringRef key) const { |
102 | if (CFBundleRef bundle = get()) |
103 | return ::CFBundleGetValueForInfoDictionaryKey(bundle, key); |
104 | return nullptr; |
105 | } |
106 | |
107 | private: |
108 | /// Helper to initialize this instance with a new bundle created from the |
109 | /// given path. This function will recursively remove components from the |
110 | /// path in its search for the nearest Info.plist. |
111 | void SetFromPath(StringRef Path); |
112 | }; |
113 | |
114 | void CFBundle::SetFromPath(StringRef Path) { |
115 | // Start from an empty/invalid CFBundle. |
116 | reset(); |
117 | |
118 | if (Path.empty() || !sys::fs::exists(Path)) |
119 | return; |
120 | |
121 | SmallString<256> RealPath; |
122 | sys::fs::real_path(Path, RealPath, /*expand_tilde*/ true); |
123 | |
124 | do { |
125 | // Create a CFURL from the current path and use it to create a CFBundle. |
126 | CFReleaser<CFURLRef> BundleURL(::CFURLCreateFromFileSystemRepresentation( |
127 | kCFAllocatorDefault, (const UInt8 *)RealPath.data(), RealPath.size(), |
128 | false)); |
129 | reset(::CFBundleCreate(kCFAllocatorDefault, BundleURL.get())); |
130 | |
131 | // If we have a valid bundle and find its identifier we are done. |
132 | if (get() != nullptr) { |
133 | if (GetIdentifier() != nullptr) |
134 | return; |
135 | reset(); |
136 | } |
137 | |
138 | // Remove the last component of the path and try again until there's |
139 | // nothing left but the root. |
140 | sys::path::remove_filename(RealPath); |
141 | } while (RealPath != sys::path::root_name(RealPath)); |
142 | } |
143 | #endif |
144 | |
145 | /// On Darwin, try and find the original executable's Info.plist to extract |
146 | /// information about the bundle. Return default values on other platforms. |
147 | CFBundleInfo getBundleInfo(StringRef ExePath) { |
148 | CFBundleInfo BundleInfo; |
149 | |
150 | #ifdef __APPLE__ |
151 | auto PrintError = [&](CFTypeID TypeID) { |
152 | CFString TypeIDCFStr(::CFCopyTypeIDDescription(TypeID)); |
153 | std::string TypeIDStr; |
154 | errs() << "The Info.plist key \"CFBundleShortVersionString\" is" |
155 | << "a " << TypeIDCFStr.UTF8(TypeIDStr) |
156 | << ", but it should be a string in: " << ExePath << ".\n" ; |
157 | }; |
158 | |
159 | CFBundle Bundle(ExePath); |
160 | if (CFStringRef BundleID = Bundle.GetIdentifier()) { |
161 | CFString::UTF8(BundleID, BundleInfo.IDStr); |
162 | if (CFTypeRef TypeRef = |
163 | Bundle.GetValueForInfoDictionaryKey(CFSTR("CFBundleVersion" ))) { |
164 | CFTypeID TypeID = ::CFGetTypeID(TypeRef); |
165 | if (TypeID == ::CFStringGetTypeID()) |
166 | CFString::UTF8((CFStringRef)TypeRef, BundleInfo.VersionStr); |
167 | else |
168 | PrintError(TypeID); |
169 | } |
170 | if (CFTypeRef TypeRef = Bundle.GetValueForInfoDictionaryKey( |
171 | CFSTR("CFBundleShortVersionString" ))) { |
172 | CFTypeID TypeID = ::CFGetTypeID(TypeRef); |
173 | if (TypeID == ::CFStringGetTypeID()) |
174 | CFString::UTF8((CFStringRef)TypeRef, BundleInfo.ShortVersionStr); |
175 | else |
176 | PrintError(TypeID); |
177 | } |
178 | } |
179 | #endif |
180 | |
181 | return BundleInfo; |
182 | } |
183 | |
184 | } // end namespace dsymutil |
185 | } // end namespace llvm |
186 | |