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 
GetProcesses(CFMutableArrayRef plistMutableArray,bool all_users)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 }
ListApplications(std::string & plist,bool opt_runningApps,bool opt_debuggable)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