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