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