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