258
+ − 1 #include "animone/util/osx.h"
+ − 2
+ − 3 #include <memory>
+ − 4 #include <string>
+ − 5
+ − 6 #include <libproc.h>
+ − 7 #include <sys/sysctl.h>
+ − 8
+ − 9 namespace animone::internal::osx::util {
+ − 10
+ − 11 typedef CFTypeRef (*LSASNCreateWithPidSpec)(CFAllocatorRef, pid_t);
+ − 12 typedef CFDictionaryRef (*LSCopyApplicationInformationSpec)(int, CFTypeRef, CFArrayRef);
+ − 13
+ − 14 /* retrieved dynamically from launchservices */
+ − 15 static LSCopyApplicationInformationSpec LSCopyApplicationInformation = nullptr;
+ − 16 static LSASNCreateWithPidSpec LSASNCreateWithPid = nullptr;
+ − 17
+ − 18 static CFStringRef kLSDisplayNameKey = nullptr;
+ − 19 static CFStringRef kLSPIDKey = nullptr;
+ − 20
+ − 21 /* retrieved from LaunchServicesSPI.h in WebKit */
+ − 22 static constexpr int kLSDefaultSessionID = -2;
+ − 23
+ − 24 static const CFStringRef kLaunchServicesBundleID = CFSTR("com.apple.LaunchServices");
+ − 25
+ − 26 static bool GetLaunchServicesPrivateSymbols() {
+ − 27 CFBundleRef launch_services_bundle = CFBundleGetBundleWithIdentifier(kLaunchServicesBundleID);
+ − 28 if (!launch_services_bundle)
+ − 29 return false;
+ − 30
+ − 31 LSCopyApplicationInformation = reinterpret_cast<LSCopyApplicationInformationSpec>(
+ − 32 CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSCopyApplicationInformation")));
+ − 33 if (!LSCopyApplicationInformation)
+ − 34 return false;
+ − 35
+ − 36 LSASNCreateWithPid = reinterpret_cast<LSASNCreateWithPidSpec>(
+ − 37 CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSASNCreateWithPid")));
+ − 38 if (!LSASNCreateWithPid)
+ − 39 return false;
+ − 40
+ − 41 CFStringRef* ptr_kLSDisplayNameKey = reinterpret_cast<CFStringRef*>(
+ − 42 CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSDisplayNameKey")));
+ − 43 if (!ptr_kLSDisplayNameKey)
+ − 44 return false;
+ − 45 kLSDisplayNameKey = *ptr_kLSDisplayNameKey;
+ − 46
+ − 47 CFStringRef* ptr_kLSPIDKey =
+ − 48 reinterpret_cast<CFStringRef*>(CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSPIDKey")));
+ − 49 if (!ptr_kLSPIDKey)
+ − 50 return false;
+ − 51 kLSPIDKey = *ptr_kLSPIDKey;
+ − 52
+ − 53 return true;
+ − 54 }
+ − 55
+ − 56 static bool LaunchServicesGetProcessName(pid_t pid, std::string& result) {
+ − 57 if (!LSCopyApplicationInformation || !LSASNCreateWithPid || !kLSDisplayNameKey || !kLSPIDKey)
+ − 58 if (!GetLaunchServicesPrivateSymbols())
+ − 59 return false;
+ − 60
+ − 61 /* what the hell is an `asn`? */
+ − 62 CFPtr<CFTypeRef> asn = LSASNCreateWithPid(kCFAllocatorDefault, pid);
+ − 63 if (!asn)
+ − 64 return false;
+ − 65
+ − 66 CFPtr<CFArrayRef> request_array = CFArrayCreate(NULL, (const void**)kLSDisplayNameKey, 1, NULL);
+ − 67 if (!request_array)
+ − 68 return false;
+ − 69
+ − 70 CFPtr<CFDictionaryRef> dictionary =
+ − 71 LSCopyApplicationInformation(kLSDefaultSessionID, asn.get(), request_array.get());
+ − 72 if (!dictionary)
+ − 73 return false;
+ − 74
+ − 75 {
+ − 76 /* this doesn't need to be free'd */
+ − 77 CFStringRef rstr;
+ − 78
+ − 79 if (!CFDictionaryGetValueIfPresent(dictionary, kLSDisplayNameKey, (CFTypeRef*)&rstr) || !rstr)
+ − 80 return false;
+ − 81
+ − 82 if (!StringFromCFString(rstr, result))
+ − 83 return false;
+ − 84 }
+ − 85
+ − 86 result.resize(result.find('\0'));
+ − 87
+ − 88 return true;
+ − 89 }
+ − 90
+ − 91 bool StringFromCFString(CFStringRef string, std::string& result) {
+ − 92 if (!string)
+ − 93 return false;
+ − 94
+ − 95 result.resize(CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1);
+ − 96 if (!CFStringGetCString(string, &result.front(), result.length(), kCFStringEncodingUTF8))
+ − 97 return false;
+ − 98
+ − 99 return true;
+ − 100 }
+ − 101
+ − 102 static bool GetProcessArgs(pid_t pid, std::string& args) {
+ − 103 /* sysctl shouldn't touch these, so we define them as const */
+ − 104 int mib[3] = {CTL_KERN, KERN_PROCARGS2, static_cast<int>(pid)};
+ − 105 const size_t mib_size = sizeof(mib) / sizeof(*mib);
+ − 106
+ − 107 /* Get the initial size of the array
+ − 108 *
+ − 109 * NOTE: it IS possible for this value to change inbetween calls to sysctl().
+ − 110 * Unfortunately, I couldn't care less about handling this. :)
+ − 111 *
+ − 112 * is that really true, though? these should be constant values. but are
+ − 113 * argc and argv *really* constant?
+ − 114 */
+ − 115 size_t size;
+ − 116 {
+ − 117 int ret = sysctl((int*)mib, mib_size, nullptr, &size, nullptr, 0);
+ − 118 if (ret)
+ − 119 return false;
+ − 120 }
+ − 121
+ − 122 /* Reserve the space for it in args */
+ − 123 args.resize(size);
+ − 124
+ − 125 /* Get the contents of argc and argv */
+ − 126 {
+ − 127 int ret = sysctl((int*)mib, mib_size, &args.front(), &size, NULL, 0);
+ − 128 if (ret)
+ − 129 return false;
+ − 130 }
+ − 131
+ − 132 /* Is the size big enough to hold at least argc? */
+ − 133 if (size < sizeof(int))
+ − 134 return false;
+ − 135
+ − 136 args.resize(size);
+ − 137 return true;
+ − 138 }
+ − 139
+ − 140 static bool GetProcessNameFromArgs(pid_t pid, std::string& result) {
+ − 141 if (!GetProcessArgs(pid, result))
+ − 142 return false;
+ − 143
+ − 144 /* Get argc using memcpy */
+ − 145 int argc = 0;
+ − 146 memcpy(&argc, &result.front(), sizeof(argc));
+ − 147
+ − 148 /* Do we even have argv[0]? */
+ − 149 if (argc < 1)
+ − 150 return false;
+ − 151
+ − 152 /* Find the first null character */
+ − 153 size_t null_pos = result.find('\0', sizeof(argc));
+ − 154 if (null_pos == std::string::npos)
+ − 155 return false;
+ − 156
+ − 157 /* Find the last slash */
+ − 158 size_t last_slash = result.rfind('/', null_pos);
+ − 159 if (last_slash == std::string::npos)
+ − 160 return false;
+ − 161
+ − 162 /* Return our result */
+ − 163 result = result.substr(last_slash + 1, null_pos - last_slash - 1);
+ − 164 return true;
+ − 165 }
+ − 166
+ − 167 static bool GetProcessNameFromKernel(pid_t pid, std::string& result) {
+ − 168 result.resize(2 * MAXCOMLEN);
+ − 169
+ − 170 int size = proc_name(pid, &result.front(), result.length());
+ − 171 if (!size)
+ − 172 return false;
+ − 173
+ − 174 result.resize(size);
+ − 175 return true;
+ − 176 }
+ − 177
+ − 178 bool GetProcessName(pid_t pid, std::string& result) {
+ − 179 if (LaunchServicesGetProcessName(pid, result))
+ − 180 return true;
+ − 181
+ − 182 /* Try parsing the arguments, this prevents the process name being
+ − 183 * cut off to 2*MAXCOMLEN (32 chars) */
+ − 184 if (GetProcessNameFromArgs(pid, result))
+ − 185 return true;
+ − 186
+ − 187 /* Then attempt getting it from the kernel, which results in the
+ − 188 * process name being cut to 32 chars (worse, 16 chars if p_name is
+ − 189 * unavailable) */
+ − 190 if (GetProcessNameFromKernel(pid, result))
+ − 191 return true;
+ − 192
+ − 193 return false;
+ − 194 }
+ − 195
+ − 196 } // namespace animone::internal::osx::util