| 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 |