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 21namespace llvm { 22namespace dsymutil { 23 24#ifdef __APPLE__ 25/// Deleter that calls CFRelease rather than deleting the pointer. 26template <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. 36template <typename T> 37using CFReleaser = std::unique_ptr<std::remove_pointer_t<T>, 38 CFDeleter<std::remove_pointer_t<T>>>; 39 40/// RAII wrapper around CFBundleRef. 41class CFString : public CFReleaser<CFStringRef> { 42public: 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. 64const 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. 85class CFBundle : public CFReleaser<CFBundleRef> { 86public: 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 107private: 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 114void 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. 147CFBundleInfo 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