| 1 | //===-- RNBServices.cpp -----------------------------------------*- 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 | // Created by Christopher Friesen on 3/21/08. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "RNBServices.h" |
| 14 | |
| 15 | #include "DNB.h" |
| 16 | #include "CFString.h" |
| 17 | #include "DNBLog.h" |
| 18 | #include "MacOSX/CFUtils.h" |
| 19 | #include <CoreFoundation/CoreFoundation.h> |
| 20 | #include <libproc.h> |
| 21 | #include <sys/sysctl.h> |
| 22 | #include <unistd.h> |
| 23 | #include <vector> |
| 24 | |
| 25 | // For now only SpringBoard has a notion of "Applications" that it can list for |
| 26 | // us. |
| 27 | // So we have to use the SpringBoard API's here. |
| 28 | #if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) |
| 29 | #include <SpringBoardServices/SpringBoardServices.h> |
| 30 | #endif |
| 31 | |
| 32 | int GetProcesses(CFMutableArrayRef plistMutableArray, bool all_users) { |
| 33 | if (plistMutableArray == NULL) |
| 34 | return -1; |
| 35 | |
| 36 | // Running as root, get all processes |
| 37 | std::vector<struct kinfo_proc> proc_infos; |
| 38 | const size_t num_proc_infos = DNBGetAllInfos(proc_infos); |
| 39 | if (num_proc_infos > 0) { |
| 40 | const pid_t our_pid = getpid(); |
| 41 | const uid_t our_uid = getuid(); |
| 42 | uint32_t i; |
| 43 | CFAllocatorRef alloc = kCFAllocatorDefault; |
| 44 | |
| 45 | for (i = 0; i < num_proc_infos; i++) { |
| 46 | struct kinfo_proc &proc_info = proc_infos[i]; |
| 47 | |
| 48 | bool kinfo_user_matches; |
| 49 | // Special case, if lldb is being run as root we can attach to anything. |
| 50 | if (all_users) |
| 51 | kinfo_user_matches = true; |
| 52 | else |
| 53 | kinfo_user_matches = proc_info.kp_eproc.e_pcred.p_ruid == our_uid; |
| 54 | |
| 55 | const pid_t pid = proc_info.kp_proc.p_pid; |
| 56 | // Skip zombie processes and processes with unset status |
| 57 | if (!kinfo_user_matches || // User is acceptable |
| 58 | pid == our_pid || // Skip this process |
| 59 | pid == 0 || // Skip kernel (kernel pid is zero) |
| 60 | proc_info.kp_proc.p_stat == |
| 61 | SZOMB || // Zombies are bad, they like brains... |
| 62 | proc_info.kp_proc.p_flag & P_TRACED || // Being debugged? |
| 63 | proc_info.kp_proc.p_flag & P_WEXIT // Working on exiting? |
| 64 | ) |
| 65 | continue; |
| 66 | |
| 67 | // Create a new mutable dictionary for each application |
| 68 | CFReleaser<CFMutableDictionaryRef> appInfoDict( |
| 69 | ::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, |
| 70 | &kCFTypeDictionaryValueCallBacks)); |
| 71 | |
| 72 | // Get the process id for the app (if there is one) |
| 73 | const int32_t pid_int32 = pid; |
| 74 | CFReleaser<CFNumberRef> pidCFNumber( |
| 75 | ::CFNumberCreate(alloc, kCFNumberSInt32Type, &pid_int32)); |
| 76 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PID_KEY, |
| 77 | pidCFNumber.get()); |
| 78 | |
| 79 | // Set a boolean to indicate if this is the front most |
| 80 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, |
| 81 | kCFBooleanFalse); |
| 82 | |
| 83 | const char *pid_basename = proc_info.kp_proc.p_comm; |
| 84 | char proc_path_buf[PATH_MAX]; |
| 85 | |
| 86 | int return_val = proc_pidpath(pid, proc_path_buf, PATH_MAX); |
| 87 | if (return_val > 0) { |
| 88 | // Okay, now search backwards from that to see if there is a |
| 89 | // slash in the name. Note, even though we got all the args we don't |
| 90 | // care |
| 91 | // because the list data is just a bunch of concatenated null terminated |
| 92 | // strings |
| 93 | // so strrchr will start from the end of argv0. |
| 94 | |
| 95 | pid_basename = strrchr(proc_path_buf, '/'); |
| 96 | if (pid_basename) { |
| 97 | // Skip the '/' |
| 98 | ++pid_basename; |
| 99 | } else { |
| 100 | // We didn't find a directory delimiter in the process argv[0], just |
| 101 | // use what was in there |
| 102 | pid_basename = proc_path_buf; |
| 103 | } |
| 104 | CFString cf_pid_path(proc_path_buf); |
| 105 | if (cf_pid_path.get()) |
| 106 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PATH_KEY, |
| 107 | cf_pid_path.get()); |
| 108 | } |
| 109 | |
| 110 | if (pid_basename && pid_basename[0]) { |
| 111 | CFString pid_name(pid_basename); |
| 112 | ::CFDictionarySetValue(appInfoDict.get(), |
| 113 | DTSERVICES_APP_DISPLAY_NAME_KEY, pid_name.get()); |
| 114 | } |
| 115 | |
| 116 | // Append the application info to the plist array |
| 117 | ::CFArrayAppendValue(plistMutableArray, appInfoDict.get()); |
| 118 | } |
| 119 | } |
| 120 | return 0; |
| 121 | } |
| 122 | int ListApplications(std::string &plist, bool opt_runningApps, |
| 123 | bool opt_debuggable) { |
| 124 | int result = -1; |
| 125 | |
| 126 | CFAllocatorRef alloc = kCFAllocatorDefault; |
| 127 | |
| 128 | // Create a mutable array that we can populate. Specify zero so it can be of |
| 129 | // any size. |
| 130 | CFReleaser<CFMutableArrayRef> plistMutableArray( |
| 131 | ::CFArrayCreateMutable(alloc, 0, &kCFTypeArrayCallBacks)); |
| 132 | |
| 133 | const uid_t our_uid = getuid(); |
| 134 | |
| 135 | #if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) |
| 136 | |
| 137 | if (our_uid == 0) { |
| 138 | bool all_users = true; |
| 139 | result = GetProcesses(plistMutableArray.get(), all_users); |
| 140 | } else { |
| 141 | CFReleaser<CFStringRef> sbsFrontAppID( |
| 142 | ::SBSCopyFrontmostApplicationDisplayIdentifier()); |
| 143 | CFReleaser<CFArrayRef> sbsAppIDs(::SBSCopyApplicationDisplayIdentifiers( |
| 144 | opt_runningApps, opt_debuggable)); |
| 145 | |
| 146 | // Need to check the return value from SBSCopyApplicationDisplayIdentifiers. |
| 147 | CFIndex count = sbsAppIDs.get() ? ::CFArrayGetCount(sbsAppIDs.get()) : 0; |
| 148 | CFIndex i = 0; |
| 149 | for (i = 0; i < count; i++) { |
| 150 | CFStringRef displayIdentifier = |
| 151 | (CFStringRef)::CFArrayGetValueAtIndex(sbsAppIDs.get(), i); |
| 152 | |
| 153 | // Create a new mutable dictionary for each application |
| 154 | CFReleaser<CFMutableDictionaryRef> appInfoDict( |
| 155 | ::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, |
| 156 | &kCFTypeDictionaryValueCallBacks)); |
| 157 | |
| 158 | // Get the process id for the app (if there is one) |
| 159 | pid_t pid = INVALID_NUB_PROCESS; |
| 160 | if (::SBSProcessIDForDisplayIdentifier((CFStringRef)displayIdentifier, |
| 161 | &pid) == true) { |
| 162 | CFReleaser<CFNumberRef> pidCFNumber( |
| 163 | ::CFNumberCreate(alloc, kCFNumberSInt32Type, &pid)); |
| 164 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PID_KEY, |
| 165 | pidCFNumber.get()); |
| 166 | } |
| 167 | |
| 168 | // Set a boolean to indicate if this is the front most |
| 169 | if (sbsFrontAppID.get() && displayIdentifier && |
| 170 | (::CFStringCompare(sbsFrontAppID.get(), displayIdentifier, 0) == |
| 171 | kCFCompareEqualTo)) |
| 172 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, |
| 173 | kCFBooleanTrue); |
| 174 | else |
| 175 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, |
| 176 | kCFBooleanFalse); |
| 177 | |
| 178 | CFReleaser<CFStringRef> executablePath( |
| 179 | ::SBSCopyExecutablePathForDisplayIdentifier(displayIdentifier)); |
| 180 | if (executablePath.get() != NULL) { |
| 181 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PATH_KEY, |
| 182 | executablePath.get()); |
| 183 | } |
| 184 | |
| 185 | CFReleaser<CFStringRef> iconImagePath( |
| 186 | ::SBSCopyIconImagePathForDisplayIdentifier(displayIdentifier)); |
| 187 | if (iconImagePath.get() != NULL) { |
| 188 | ::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_ICON_PATH_KEY, |
| 189 | iconImagePath.get()); |
| 190 | } |
| 191 | |
| 192 | CFReleaser<CFStringRef> localizedDisplayName( |
| 193 | ::SBSCopyLocalizedApplicationNameForDisplayIdentifier( |
| 194 | displayIdentifier)); |
| 195 | if (localizedDisplayName.get() != NULL) { |
| 196 | ::CFDictionarySetValue(appInfoDict.get(), |
| 197 | DTSERVICES_APP_DISPLAY_NAME_KEY, |
| 198 | localizedDisplayName.get()); |
| 199 | } |
| 200 | |
| 201 | // Append the application info to the plist array |
| 202 | ::CFArrayAppendValue(plistMutableArray.get(), appInfoDict.get()); |
| 203 | } |
| 204 | } |
| 205 | #else // #if defined (WITH_SPRINGBOARD) || defined (WITH_BKS) |
| 206 | // When root, show all processes |
| 207 | bool all_users = (our_uid == 0); |
| 208 | GetProcesses(plistMutableArray.get(), all_users); |
| 209 | #endif |
| 210 | |
| 211 | CFReleaser<CFDataRef> plistData(::CFPropertyListCreateData( |
| 212 | alloc, plistMutableArray.get(), kCFPropertyListXMLFormat_v1_0, 0, NULL)); |
| 213 | |
| 214 | // write plist to service port |
| 215 | if (plistData.get() != NULL) { |
| 216 | CFIndex size = ::CFDataGetLength(plistData.get()); |
| 217 | const UInt8 *bytes = ::CFDataGetBytePtr(plistData.get()); |
| 218 | if (bytes != NULL && size > 0) { |
| 219 | plist.assign((const char *)bytes, size); |
| 220 | return 0; // Success |
| 221 | } else { |
| 222 | DNBLogError("empty application property list." ); |
| 223 | result = -2; |
| 224 | } |
| 225 | } else { |
| 226 | DNBLogError("serializing task list." ); |
| 227 | result = -3; |
| 228 | } |
| 229 | |
| 230 | return result; |
| 231 | } |
| 232 | |