view dep/animia/src/util/osx.cc @ 165:8937fb7f2d66

dep/animia/osx: that's not how std::string works
author Paper <mrpapersonic@gmail.com>
date Sat, 18 Nov 2023 01:12:02 -0500
parents 44c5e6dd9488
children e44b7c428d7c
line wrap: on
line source

#include "animia/util/osx.h"

#ifdef HAVE_COREFOUNDATION
#include <CoreFoundation/CoreFoundation.h>
#endif

namespace animia::internal::osx::util {

#ifdef HAVE_COREFOUNDATION
/* All of these LaunchServices things use *internal functions* that are subject
** to change. Granted, it's not very likely that these will change very much
** because I'm fairly sure Apple uses them lots in their own internal code.
*/

/* from RDProcess */
typedef CFTypeRef (*LSASNCreateWithPidSpec)(CFAllocatorRef, pid_t);
typedef CFDictionaryRef (*LSCopyApplicationInformationSpec)(int, CFTypeRef, CFArrayRef);

static LSCopyApplicationInformationSpec LSCopyApplicationInformation = nullptr;
static LSASNCreateWithPidSpec LSASNCreateWithPid = nullptr;

/* retrieved from LaunchServicesSPI.h in WebKit */
static constexpr int kLSDefaultSessionID = -2;
static const CFStringRef kLaunchServicesBundleID = CFSTR("com.apple.LaunchServices");

/* retrieved dynamically */
static CFStringRef kLSDisplayNameKey = nullptr;
static CFStringRef kLSPIDKey = nullptr;

static bool GetLaunchServicesPrivateSymbols() {
	CFBundleRef launch_services_bundle = CFBundleGetBundleWithIdentifier(kLaunchServicesBundleID);
	if (!launch_services_bundle)
		return false;

	LSCopyApplicationInformation = (LSCopyApplicationInformationSpec)CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSCopyApplicationInformation"));
	if (!LSCopyApplicationInformation)
		return false;

	LSASNCreateWithPid = (LSASNCreateWithPidSpec)CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSASNCreateWithPid"));
	if (!LSASNCreateWithPid)
		return false;

	kLSDisplayNameKey = *(CFStringRef*)CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSDisplayNameKey"));
	if (!kLSDisplayNameKey)
		return false;

	kLSPIDKey = *(CFStringRef*)CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSPIDKey"));
	if (!kLSPIDKey)
		return false;

	return true;
}

static bool LaunchServicesGetProcessName(pid_t pid, std::string& result) {
	if (!LSCopyApplicationInformation || !LSASNCreateWithPid)
		if (!GetLaunchServicesPrivateSymbols())
			return false;

	CFTypeRef asn = LSASNCreateWithPid(kCFAllocatorDefault, pid);

	CFArrayRef request_array = CFArrayCreate(NULL, (const void **)kLSDisplayNameKey, 1, NULL);

	CFDictionaryRef dictionary = LSCopyApplicationInformation(kLaunchServicesMagicConstant, asn, request_array);

	CFRelease(request_array);
	if (!dictionary)
		return false;

	CFStringRef str;
	if (!CFDictionaryGetValueIfPresent(dictionary, kLSDisplayNameKey, (CFTypeRef*)&str) || !str) {
		CFRelease(dictionary);
		return false;
	}
	CFRetain(str);

	CFRelease(dictionary);

	result.reserve(CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8) + 1);

	if (!CFStringGetCString(str, &result.front(), result.length(), result.length())) {
		CFRelease(str);
		return false;
	}

	CFRelease(str);

	return true;
}
#endif // HAVE_COREFOUNDATION

static bool GetProcessArgs(pid_t pid, std::string& args) {
	/* sysctl shouldn't touch these, so we define them as const */
	const int mib[3] = {CTL_KERN, KERN_PROCARGS2, static_cast<int>(pid)};
	const size_t mib_size = sizeof(mib)/sizeof(*mib);

	/* Get the initial size of the array */
	size_t size;
	{
		int ret = sysctl((int*)mib, mib_size, nullptr, &size, nullptr, 0);
		if (ret)
			return false;
	}

	/* Reserve the space for it in args */
	args.resize(size);

	/* Get the contents of argc and argv */
	{
		int ret = sysctl((int*)mib, mib_size, &args.front(), &size, NULL, 0);
		if (ret)
			return false;
	}

	/* Is the size big enough to hold at least argc? */
	if (size < sizeof(int))
		return false;

	args.resize(size);
}

static bool GetProcessNameFromArgs(pid_t pid, std::string& result) {
	if (!GetProcessArgs(pid, result))
		return false;

	/* Get argc using memcpy */
	int argc;
	memcpy(&argc, &args.front(), sizeof(argc));

	/* Do we even have argv[0]? */
	if (argc < 1)
		return false;

	/* Find the first null character */
	size_t null_pos = args.find('\0', sizeof(argc));
	if (null_pos == std::string::npos)
		return false;

	/* Find the last slash */
	size_t last_slash = args.rfind('/', null_pos);
	if (last_slash == std::string::npos)
		return false;

	/* Return our result */
	result = args.substr(last_slash + 1, null_pos - last_slash - 1);
	return true;
}

static bool GetProcessNameFromKernel(pid_t pid, std::string& result) {
	result.resize(2 * MAXCOMLEN);

	int size = proc_name(pid, &result.front(), result.length());
	if (!size)
		return false;

	result.resize(size);
	return true;
}

static bool GetProcessName(pid_t pid, std::string& result) {
#ifdef HAVE_COREFOUNDATION
	if (LaunchServicesGetProcessName(pid, result))
		return true;
#endif // HAVE_COREFOUNDATION

	/* Try parsing the arguments, this prevents the process name being
	   cut off to 2*MAXCOMLEN (32 chars) */
	if (GetProcessNameFromArgs(pid, result))
		return true;

	/* Then attempt getting it from the kernel, which results in the
	   process name being cut to 32 chars (worse, 16 chars if p_name is
	   unavailable) */
	if (GetProcessNameFromKernel(pid, result))
		return true;

	return false;
}

}