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
32int 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}
122int 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