FBControlCore/Processes/FBProcessFetcher.m (283 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBProcessFetcher.h" #import "FBControlCoreError.h" #import "FBFuture.h" #include <libproc.h> #include <limits.h> #include <string.h> #include <sys/sysctl.h> #import "FBProcessInfo.h" #define PID_MAX 99999 #pragma mark Calling libproc typedef BOOL(^ProcessIterator)(pid_t pid); typedef int(^LibProcCaller)(void); static void IterateWith(pid_t *pidBuffer, size_t pidBufferSize, ProcessIterator iterator, LibProcCaller caller) { int actualSize = caller(); if (actualSize < 1) { return; } for (int index = 0; index < actualSize; index++) { pid_t processIdentifier = *(pidBuffer + index); if (!iterator(processIdentifier)) { break; } } } static void IterateAllProcesses(pid_t *pidBuffer, size_t pidBufferSize, ProcessIterator iterator) { IterateWith(pidBuffer, pidBufferSize, iterator, ^ int () { return proc_listallpids(pidBuffer, (int) pidBufferSize); }); } static void IterateSubprocessesOf(pid_t *pidBuffer, size_t pidBufferSize, pid_t parent, ProcessIterator iterator) { IterateWith(pidBuffer, pidBufferSize, iterator, ^ int () { return proc_listchildpids(parent, pidBuffer, (int) pidBufferSize); }); } static void IterateOpenFiles(pid_t *pidBuffer, size_t pidBufferSize, const char *path, ProcessIterator iterator) { IterateWith(pidBuffer, pidBufferSize,iterator, ^ int () { return proc_listpidspath( PROC_LISTPIDSPATH_PATH_IS_VOLUME, PROC_ALL_PIDS, path, 0, pidBuffer, (int) pidBufferSize ); }); } static inline FBProcessInfo *ProcessInfoForProcessIdentifier(pid_t processIdentifier, char *buffer, size_t bufferSize) { // Much of the layout information here comes from libtop.c in Apple's top(1) Open Source implementation. int name[3] = {CTL_KERN, KERN_PROCARGS2, processIdentifier}; size_t actualSize = bufferSize; if (sysctl(name, 3, buffer, &actualSize, NULL, 0) == -1) { return nil; } if (actualSize == 0) { return nil; } // First Position is argc. const char *startPosition = buffer; const int argc = *startPosition; // If argc isn't 1 or more, something is wrong if (argc < 1) { return nil; } // launch path starts above argc char *currentPosition = (char *) startPosition + sizeof(int); NSString *launchPath = [[NSString alloc] initWithCString:currentPosition encoding:NSASCIIStringEncoding]; currentPosition += strlen(currentPosition); currentPosition += 1; // Move through the padding to get to the the argv while (*currentPosition == '\0') { currentPosition++; } // Enumerate up to the value of argc NSMutableArray *arguments = [NSMutableArray array]; for (int index = 0; index < argc; index++) { // Create Objective-C String from current position. NSString *argument = [[NSString alloc] initWithCString:currentPosition encoding:NSASCIIStringEncoding]; [arguments addObject:argument]; // Move the current string position passed the null-character. currentPosition += strlen(currentPosition); currentPosition += 1; } // Now the environment is here NSMutableDictionary *environment = [NSMutableDictionary dictionary]; while (*currentPosition != '\0') { NSString *string = [[NSString alloc] initWithCString:currentPosition encoding:NSASCIIStringEncoding]; NSArray *tokens = [string componentsSeparatedByString:@"="]; // If we don't get 2 tokens, something is malformed. if (tokens.count != 2) { break; } environment[tokens[0]] = tokens[1]; // Move the current string position passed the null-character. currentPosition += strlen(currentPosition); currentPosition += 1; } FBProcessInfo *process = [[FBProcessInfo alloc] initWithProcessIdentifier:processIdentifier launchPath:launchPath arguments:arguments environment:environment]; return process; } static inline BOOL ProcInfoForProcessIdentifier(pid_t processIdentifier, struct kinfo_proc* procOut) { size_t size = sizeof(struct kinfo_proc); int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, processIdentifier }; if (sysctl(name, 4, procOut, &size, NULL, 0) == -1) { return NO; } return YES; } static BOOL ProcessNameForProcessIdentifier(pid_t processIdentifier, char *buffer, size_t bufferSize) { return proc_name(processIdentifier, buffer, (uint32_t) bufferSize) > 1; } @interface FBProcessFetcher () @property (nonatomic, assign, readonly) size_t argumentBufferSize; @property (nonatomic, assign, readonly) char *argumentBuffer; @property (nonatomic, assign, readonly) size_t pidBufferSize; @property (nonatomic, assign, readonly) pid_t *pidBuffer; @end @implementation FBProcessFetcher #pragma mark Lifecycle static size_t MaxArgumentBufferSize = ARG_MAX; // A temporary value that is filled on load static size_t const MaxPidBufferSize = 5568 * 2 * sizeof(int); // From 'ulimit -u', but twice as large, in ints. + (void)load { int name[2] = {CTL_KERN, KERN_ARGMAX}; size_t size = sizeof(MaxArgumentBufferSize); int status = sysctl(name, 2, &MaxArgumentBufferSize, &size, NULL, 0); NSAssert(status != -1, @"Failed to get the KERN_ARGMAX from sysctl %s", strerror(errno)); } - (instancetype)init { self = [super init]; if (!self) { return nil; } _argumentBufferSize = MaxArgumentBufferSize; _argumentBuffer = malloc(_argumentBufferSize); _pidBufferSize = MaxPidBufferSize; _pidBuffer = malloc(_pidBufferSize); return self; } - (void)dealloc { free(_argumentBuffer); free(_pidBuffer); } #pragma mark Queries - (nullable FBProcessInfo *)processInfoFor:(pid_t)processIdentifier { return ProcessInfoForProcessIdentifier( processIdentifier, self.argumentBuffer, self.argumentBufferSize ); } - (NSArray<FBProcessInfo *> *)subprocessesOf:(pid_t)parent { NSMutableArray *subprocesses = [NSMutableArray array]; IterateSubprocessesOf(self.pidBuffer, self.pidBufferSize, parent, ^ BOOL (pid_t pid) { FBProcessInfo *info = [self processInfoFor:pid]; if (info) { [subprocesses addObject:info]; } return YES; }); return [subprocesses copy]; } - (NSArray<FBProcessInfo *> *)processesWithProcessName:(NSString *)processName { NSMutableArray *subprocesses = [NSMutableArray array]; size_t bufferSize = self.argumentBufferSize; char *buffer = self.argumentBuffer; const char *needle = processName.UTF8String; IterateAllProcesses(self.pidBuffer, self.pidBufferSize, ^ BOOL (pid_t pid) { if (!ProcessNameForProcessIdentifier(pid, buffer, bufferSize)) { return YES; } if (strcmp(needle, buffer) != 0) { return YES; } FBProcessInfo *info = [self processInfoFor:pid]; if (!info) { return YES; } [subprocesses addObject:info]; return YES; }); return [subprocesses copy]; } - (pid_t)subprocessOf:(pid_t)parent withName:(NSString *)needleString { __block pid_t foundProcess = -1; size_t argumentBufferSize = self.argumentBufferSize; char *argumentBuffer = self.argumentBuffer; const char *needle = needleString.UTF8String; IterateSubprocessesOf(self.pidBuffer, self.pidBufferSize, parent, ^ BOOL (pid_t pid) { if (proc_name(pid, argumentBuffer, (uint32_t) argumentBufferSize) == -1) { return YES; } if (strstr(argumentBuffer, needle) == NULL) { return YES; } foundProcess = pid; return NO; }); return foundProcess; } - (pid_t)processWithOpenFileTo:(const char *)filename { __block pid_t processIdentifier = -1; IterateOpenFiles(self.pidBuffer, self.pidBufferSize, filename, ^ BOOL (pid_t pid) { processIdentifier = pid; return NO; }); return processIdentifier; } - (pid_t)parentOf:(pid_t)child { struct kinfo_proc proc; if (!ProcInfoForProcessIdentifier(child, &proc)) { return -1; } return proc.kp_eproc.e_ppid; } - (struct kinfo_proc) fetchProcessInfo:(pid_t)processIdentifier error:(NSError **)error { struct kinfo_proc proc_info; if (!ProcInfoForProcessIdentifier(processIdentifier, &proc_info) || proc_info.kp_proc.p_pid != processIdentifier) { [[FBControlCoreError describeFormat:@"Failed fetching process info for (pid %d)", processIdentifier] failBool:error]; } return proc_info; } - (BOOL) isProcessRunning:(pid_t)processIdentifier error:(NSError **)error { struct kinfo_proc proc_info = [self fetchProcessInfo:processIdentifier error:error]; return *error == nil && proc_info.kp_proc.p_stat == SRUN; } - (BOOL) isProcessStopped:(pid_t)processIdentifier error:(NSError **)error { struct kinfo_proc proc_info = [self fetchProcessInfo:processIdentifier error:error]; return *error == nil && proc_info.kp_proc.p_stat == SSTOP; } - (BOOL) isDebuggerAttachedTo:(pid_t)processIdentifier error:(NSError **)error { struct kinfo_proc proc_info = [self fetchProcessInfo:processIdentifier error:error]; // When a debugger (a.k.a tracer) attaches to the test proccess, the parent of tracee will // change to tracer's pid with the original parent pid being store in `p_oppid`. // We detect debugger attachment by checking that parent pid has changed. return *error == nil && proc_info.kp_proc.p_oppid != 0 && proc_info.kp_eproc.e_ppid != proc_info.kp_proc.p_oppid; } + (FBFuture<NSNull *> *) waitForDebuggerToAttachAndContinueFor:(pid_t)processIdentifier { FBProcessFetcher *processFetcher = [[FBProcessFetcher alloc] init]; // Report from the current queue, but wait in a special queue. dispatch_queue_t waitQueue = dispatch_queue_create("com.facebook.corecontrol.debugger_wait", DISPATCH_QUEUE_SERIAL); return [FBFuture onQueue:waitQueue resolveOrFailWhen:^FBFutureLoopState (NSError **error){ if ( [processFetcher isDebuggerAttachedTo:processIdentifier error:error] && [processFetcher isProcessRunning:processIdentifier error:error] ) { return FBFutureLoopFinished; } else if (*error != nil){ return FBFutureLoopFailed; } else { return FBFutureLoopContinue; } }]; } + (FBFuture<NSNull *> *) waitStopSignalForProcess:(pid_t) processIdentifier { FBProcessFetcher *processFetcher = [[FBProcessFetcher alloc] init]; dispatch_queue_t waitQueue = dispatch_queue_create("com.facebook.corecontrol.wait_for_stop", DISPATCH_QUEUE_SERIAL); return [FBFuture onQueue:waitQueue resolveOrFailWhen:^FBFutureLoopState (NSError **error){ if ( [processFetcher isProcessStopped:processIdentifier error:error] ) { return FBFutureLoopFinished; } else if (*error != nil){ return FBFutureLoopFailed; } else { return FBFutureLoopContinue; } }]; } @end