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( |
212 | ::CFPropertyListCreateXMLData(alloc, plistMutableArray.get())); |
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 | |