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