├── .gitignore
├── README.md
├── Src
├── ProcLib
│ ├── ProcLib.h
│ └── module.modulemap
├── args.swift
├── colors.swift
├── launchdXPC
│ ├── launchdXPC.h
│ ├── launchdXPC.m
│ └── module.modulemap
├── main.swift
├── network.swift
├── node.swift
└── process.swift
├── TrueTree.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── license.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | ## Build generated
3 | build/
4 | DerivedData/
5 | ## Various settings
6 | *.pbxuser
7 | xcuserdata/
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TrueTree
2 |
3 |

4 |
5 | **About** \
6 | Read all about the TrueTree concept [here](https://themittenmac.com/the-truetree-concept/)
7 |
8 |
9 | TrueTree is more than just a pstree command for macOS. It is used to display a process tree for current running processes while using a hierarchy built on additoinal pids that can be collected from the operating system. The standard process tree on macOS that can be built with traditional pids and ppids is less than helpful on macOS due to all the XPC communication at play. The vast majority of processes end up having a parent process of launchd. TrueTree however displays a process tree that is meant to be useful to incident responders, threat hunters, researchers, and everything in between!
10 |
11 | Major Update:
12 | Because of the ever changing features on macOS, since macOS 11 some features don't operate quite the same as they used to when I wrote the original blog post for TrueTree. Apple has introduced the addition of a new process "runningboardd" which ends up being the true parent of many processes. To get around this TrueTree was updated to use the Application Services framework which allowed for aquiring the true parent in some scenarios. However, this no longer allows for the aquiring of a true parent process if that parent process has terminated. Making it difficult to pinpoint some true parents such as the "open" command. A small price to pay to ensure TrueTree continues to operate across macOS releases. Some other signs of a true parent can often be found inside of the launchctl procinfo output. This might be taken into consideration in the future.
13 |
14 | **./TrueTree -h** \
15 | --nocolor -> Do not color code items in output
16 | --timeline -> Sort and print all processes by their creation timestamp
17 | --timestamps -> Include process timestamps
18 | --standard -> Print the standard Unix tree instead of TrueTree
19 | --sources -> Print the source of where each processes parent came from
20 | --nonetwork -> Do not print network connection
21 | --nopid -> Do not print the pid next to each process
22 | --nopath -> Print process name only instead of full paths
23 | --version -> Print the TrueTree version number
24 | -o -> output to file
25 |
26 | Note: Requires Root
27 |
28 | **./TrueTree** \
29 | Displays an enhanced process tree using the TrueTree concept
30 | 
31 |
32 |
33 | **./TrueTree --standard** \
34 | For tree output based on standard pids and ppids use --standard
35 | 
36 |
37 |
38 | **./TrueTree --timestamps** \
39 | For output in either format with process create time added use the --timestamps option
40 | 
41 |
42 |
43 | **./TrueTree --sources** \
44 | To show where each parent pid was aquired from use the --sources option
45 | 
46 |
47 |
48 | **./TrueTree --timeline** \
49 | Does not collect a tree. Instead just prints processes sorted by creation time
50 | 
51 |
--------------------------------------------------------------------------------
/Src/ProcLib/ProcLib.h:
--------------------------------------------------------------------------------
1 | #ifndef ProcLib_h
2 | #define ProcLib_h
3 |
4 | #include
5 |
6 | #endif /* ProcLib_h */
7 |
--------------------------------------------------------------------------------
/Src/ProcLib/module.modulemap:
--------------------------------------------------------------------------------
1 | module ProcLib {
2 | header "ProcLib.h"
3 | link "libproc"
4 | }
5 |
--------------------------------------------------------------------------------
/Src/args.swift:
--------------------------------------------------------------------------------
1 | //
2 | // args.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 2/20/20.
6 | // 2020 TheMittenMac
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class ArgManager {
13 |
14 | var color = true
15 | var timestamps = false
16 | var standardMode = false
17 | var sources = false
18 | var timelineMode = true
19 | var network = true
20 | var showpid = true
21 | var showpath = true
22 | var toFile: String?
23 | let availableArgs = ["--nocolor", "--timeline", "--timestamps", "-o", "--standard", "--version", "--sources", "--nonetwork", "--nopid", "--nopath"]
24 |
25 | init(suppliedArgs: [String]) {
26 | setArgs(suppliedArgs)
27 | }
28 |
29 | func setArgs(_ args:[String]) {
30 | for (x,arg) in (args).enumerated() {
31 | if x == 0 || !arg.starts(with: "-") {
32 | continue
33 | } else if arg == "-h" || arg == "--help" {
34 | self.printHelp()
35 | } else if arg == "--nocolor" {
36 | color.toggle()
37 | } else if arg == "--timestamps" {
38 | timestamps.toggle()
39 | } else if arg == "--standard" {
40 | standardMode.toggle()
41 | } else if arg == "--sources" {
42 | sources.toggle()
43 | } else if arg == "--timeline" {
44 | timelineMode.toggle()
45 | } else if arg == "--nonetwork" {
46 | network.toggle()
47 | } else if arg == "--nopid" {
48 | showpid.toggle()
49 | } else if arg == "--nopath" {
50 | showpath.toggle()
51 | } else if arg == "--version" {
52 | print(version)
53 | exit(0)
54 | } else if arg == "-o" {
55 | if args.count > x+1 && !availableArgs.contains(args[x+1]) {
56 | toFile = args[x+1]
57 | } else {
58 | toFile = "tree_output.txt"
59 | }
60 | } else {
61 | print("Unidentified argument " + arg)
62 | exit(1)
63 | }
64 | }
65 | }
66 |
67 | func printHelp() {
68 | print("--nocolor Do not color code items in output")
69 | print("--timeline Sort and print all processes by their creation timestamp (Non-Tree Mode)")
70 | print("--timestamps Include process timestamps")
71 | print("--standard Print the standard Unix tree instead of TrueTree")
72 | print("--sources Print the source of where each processes parent came from")
73 | print("--nonetwork Do not print network connection")
74 | print("--nopid Do not print the pid next to each process")
75 | print("--nopath Print process name only instead of full paths")
76 | print("--version Print the TrueTree version number")
77 | print("-o Output to file")
78 | exit(0)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Src/colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // colors.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 12/2/22.
6 | // Copyright © 2022 mittenmac. All rights reserved.
7 | //
8 |
9 | extension String {
10 |
11 | var blue: String {
12 | return "\u{001B}[0;34m\(self)\u{001B}[0;0m"
13 | }
14 |
15 | var magenta: String {
16 | return "\u{001B}[0;35m\(self)\u{001B}[0;0m"
17 | }
18 |
19 | var cyan: String {
20 | return "\u{001B}[0;36m\(self)\u{001B}[0;0m"
21 | }
22 |
23 | var red: String {
24 | return "\u{001B}[0;31m\(self)\u{001B}[0;0m"
25 | }
26 |
27 | var yellow: String {
28 | return "\u{001B}[0;33m\(self)\u{001B}[0;0m"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Src/launchdXPC/launchdXPC.h:
--------------------------------------------------------------------------------
1 | //
2 | // launchdXPC.h
3 | //
4 | // Created by Patrick Wardle
5 | // Ported from code by Jonathan Levin
6 | //
7 |
8 | #ifndef launchdXPC_h
9 | #define launchdXPC_h
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | #import
16 |
17 | int getSubmittedPid(int pid);
18 |
19 | //hit up launchd (via XPC) to get process info
20 | NSString* getSubmittedByPlist(unsigned long pid);
21 |
22 | //launchd structs/functions
23 | // inspired by: http://newosxbook.com/articles/jlaunchctl.html
24 | struct xpc_global_data {
25 |
26 | uint64_t a;
27 | uint64_t xpc_flags;
28 | mach_port_t task_bootstrap_port;
29 | #ifndef _64
30 | uint32_t padding;
31 | #endif
32 | xpc_object_t xpc_bootstrap_pipe;
33 | };
34 |
35 | struct _os_alloc_once_s {
36 | long once;
37 | void *ptr;
38 | };
39 | extern struct _os_alloc_once_s _os_alloc_once_table[];
40 |
41 | typedef struct _xpc_pipe_s* xpc_pipe_t;
42 |
43 | __attribute__((weak_import))
44 | int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t message, xpc_object_t *reply);
45 | int xpc_pipe_interface_routine(xpc_pipe_t pipe, int request, xpc_object_t message, xpc_object_t *reply, int unknown);
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/Src/launchdXPC/launchdXPC.m:
--------------------------------------------------------------------------------
1 | //
2 | // launchdXPC.m
3 | // Created by Patrick Wardle
4 | // Ported from code by Jonathan Levin
5 | //
6 |
7 | #include
8 | #import
9 | #import "launchdXPC.h"
10 | #import
11 | #import
12 |
13 | NSDictionary * infoForPID(pid_t pid)
14 | {
15 | NSDictionary *ret = nil;
16 | ProcessSerialNumber psn = { 0, 0 };
17 | if (GetProcessForPID(pid, &psn) == noErr) {
18 | CFDictionaryRef cfDict = ProcessInformationCopyDictionary(&psn,kProcessDictionaryIncludeAllInformationMask);
19 | ret = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)cfDict];
20 | CFRelease(cfDict);
21 | }
22 | return ret;
23 | }
24 |
25 | int getSubmittedPid(int pid) {
26 | NSDictionary* info = infoForPID(pid);
27 | //NSLog(@"info: %@", info);
28 |
29 | long long temp = [[info objectForKey:@"ParentPSN"] longLongValue];
30 | long long hi = (temp >> 32) & 0x00000000FFFFFFFFLL;
31 | long long lo = (temp >> 0) & 0x00000000FFFFFFFFLL;
32 | ProcessSerialNumber parentPSN = {(unsigned long)hi, (unsigned long)lo};
33 |
34 | //NSDictionary* parentDict = (__bridge NSDictionary*)ProcessInformationCopyDictionary (&parentPSN, kProcessDictionaryIncludeAllInformationMask);
35 | //NSLog(@"real parent info: %@", parentDict);
36 | //NSLog(@"real parent pid: %@", parentDict[@"pid"]);
37 |
38 | pid_t p = 0;
39 | GetProcessPID(&parentPSN, &p);
40 | //NSLog(@"real parent pid: %d", p);
41 |
42 | return p;
43 | }
44 |
45 | #define ROUTINE_DUMP_PROCESS 0x2c4
46 |
47 | //function definition
48 | NSMutableDictionary* parse(NSString* data);
49 |
50 | //hit up launchd (via XPC) to get process info
51 | NSString* getSubmittedByPlist(unsigned long pid)
52 | {
53 | //proc info
54 | NSDictionary* processInfo = nil;
55 |
56 | //xpc dictionary
57 | // passed to launchd to get proc info
58 | xpc_object_t procInfoRequest = NULL;
59 |
60 | //shared memory for XPC
61 | xpc_object_t sharedMemory = NULL;
62 |
63 | //xpc (out) dictionary
64 | // don't really contain response, but will have (any) errors
65 | xpc_object_t __autoreleasing response = NULL;
66 |
67 | //result
68 | int result = 0;
69 |
70 | //dylib handle
71 | void *handle = NULL;
72 |
73 | //function pointer to 'xpc_pipe_interface_routine'
74 | static int(*xpc_pipe_interface_routine_FP)(xpc_pipe_t, int, xpc_object_t, xpc_object_t*, int) = NULL;
75 |
76 | //(xpc) error
77 | int64_t xpcError = 0;
78 |
79 | //global data
80 | struct xpc_global_data* globalData = NULL;
81 |
82 | //init dictionary
83 | procInfoRequest = xpc_dictionary_create(NULL,NULL,0);
84 |
85 | //init buffer
86 | // size from reversing launchctl
87 | size_t processInfoLength = 0x100000;
88 | vm_address_t processInfoBuffer = 0;
89 |
90 | //bytes written via XPC call
91 | uint64_t bytesWritten = 0;
92 |
93 | //alloc buffer
94 | if(noErr != vm_allocate(mach_task_self(), &processInfoBuffer, processInfoLength, 0xf0000003))
95 | {
96 | //bail
97 | goto bail;
98 | }
99 |
100 | sharedMemory = xpc_shmem_create((void*)processInfoBuffer, processInfoLength);
101 | if(0 == sharedMemory)
102 | {
103 | //bail
104 | goto bail;
105 | }
106 |
107 | //add pid to request
108 | xpc_dictionary_set_int64(procInfoRequest, "pid", pid);
109 |
110 | //add shared memory object
111 | xpc_dictionary_set_value(procInfoRequest, "shmem", sharedMemory);
112 |
113 | //grab from global data structure
114 | // contains XPC bootstrap pipe (launchd)
115 | globalData = (struct xpc_global_data *)_os_alloc_once_table[1].ptr;
116 |
117 | //open XPC lib
118 | handle = dlopen("/usr/lib/system/libxpc.dylib", RTLD_LAZY);
119 | if(NULL == handle)
120 | {
121 | //bail
122 | goto bail;
123 | }
124 |
125 | xpc_pipe_interface_routine_FP = dlsym(handle, "_xpc_pipe_interface_routine");
126 | if(NULL == xpc_pipe_interface_routine_FP)
127 | {
128 | //bail
129 | goto bail;
130 | }
131 |
132 | //request process info
133 | result = xpc_pipe_interface_routine_FP((__bridge xpc_pipe_t)(globalData->xpc_bootstrap_pipe), ROUTINE_DUMP_PROCESS, procInfoRequest, &response, 0x0);
134 | if(0 != result)
135 | {
136 | //error
137 | goto bail;
138 | }
139 |
140 | //check for other error(s)
141 | xpcError = xpc_dictionary_get_int64(response, "error");
142 | if(0 != xpcError)
143 | {
144 | //error
145 | //printf("error: %llx\n", xpcError);
146 | goto bail;
147 | }
148 |
149 | //get number of bytes written (to shared memory)
150 | bytesWritten = xpc_dictionary_get_uint64(response, "bytes-written");
151 |
152 | //parse
153 | processInfo = parse([[NSString alloc] initWithBytes:(const void *)processInfoBuffer length:bytesWritten encoding:NSUTF8StringEncoding]);
154 |
155 | bail:
156 |
157 | //free buffer
158 | if(0 != processInfoBuffer)
159 | {
160 | //free
161 | vm_deallocate(mach_task_self(), processInfoBuffer, processInfoLength);
162 | processInfoBuffer = 0;
163 | }
164 |
165 | return processInfo[@"path"];
166 | //return processInfo;
167 | }
168 |
169 |
170 | //parse proc info
171 | NSMutableDictionary* parse(NSString* data)
172 | {
173 | //parsed proc info
174 | NSMutableDictionary* procInfo = nil;
175 |
176 | //lines
177 | NSArray* lines = nil;
178 |
179 | //dictionaries
180 | NSMutableArray* dictionaries = nil;
181 |
182 | //alloc
183 | procInfo = [[NSMutableDictionary alloc] init];
184 |
185 | //pool
186 | @autoreleasepool {
187 |
188 | //alloc
189 | dictionaries = [NSMutableArray array];
190 |
191 | //split
192 | lines = [data componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
193 |
194 | //start w/ top level
195 | [dictionaries addObject:procInfo];
196 |
197 | //process 'dictionary'
198 | [lines enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
199 |
200 | //key
201 | NSString* key = nil;
202 |
203 | //tokens
204 | NSArray* tokens = nil;
205 |
206 | //obj should be a string
207 | if(YES != [obj isKindOfClass:[NSString class]]) return;
208 |
209 | //skip first line
210 | if(0 == idx) return;
211 |
212 | //trim object
213 | obj = [obj stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
214 |
215 | //skip empty/blank lines
216 | if(0 == [obj length]) return;
217 |
218 | //key line? (line: "key = {")
219 | // extract key and add new dictionary
220 | if(YES == [obj hasSuffix:@"{"])
221 | {
222 | //tokenize
223 | tokens = [obj componentsSeparatedByString:@"="];
224 |
225 | //extract key
226 | // everything before '='
227 | key = tokens.firstObject;
228 | if(0 == key.length) return;
229 |
230 | //init new dictionary
231 | dictionaries.lastObject[key] = [NSMutableDictionary dictionary];
232 |
233 | //'save' new dictionary
234 | [dictionaries addObject:dictionaries.lastObject[key]];
235 |
236 | return;
237 | }
238 |
239 | //end key line? (line: "}")
240 | // remove dictionary, as it's no longer needed
241 | if(YES == [obj isEqualToString:@"}"])
242 | {
243 | //remove
244 | [dictionaries removeLastObject];
245 |
246 | return;
247 | }
248 |
249 | //line w/ '=>' separator?
250 | // (line: "key => value")
251 | if(NSNotFound != [obj rangeOfString:@" => "].location)
252 | {
253 | //tokenize
254 | tokens = [obj componentsSeparatedByString:@" => "];
255 |
256 | //key is first value
257 | key = tokens.firstObject;
258 | if(0 == key.length) return;
259 |
260 | //add key/value pair
261 | dictionaries.lastObject[key] = tokens.lastObject;
262 |
263 | return;
264 | }
265 |
266 | //line w/ '=' separator?
267 | // (line: "key = value")
268 | if(NSNotFound != [obj rangeOfString:@" = "].location)
269 | {
270 | //tokenize
271 | tokens = [obj componentsSeparatedByString:@" = "];
272 |
273 | //key is first value
274 | key = tokens.firstObject;
275 | if(0 == key.length) return;
276 |
277 | //add key/value pair
278 | dictionaries.lastObject[key] = tokens.lastObject;
279 |
280 | return;
281 | }
282 |
283 | //non-key:value line in embedded dictionary?
284 | if( (dictionaries.lastObject != procInfo) &&
285 | (NSNotFound == [obj rangeOfString:@" = "].location) )
286 | {
287 | //add key/value pair
288 | dictionaries.lastObject[[NSNumber numberWithInteger:[dictionaries.lastObject count]]] = obj;
289 |
290 | return;
291 | }
292 |
293 | }];
294 |
295 | } //pool
296 |
297 | return procInfo;
298 | }
299 |
--------------------------------------------------------------------------------
/Src/launchdXPC/module.modulemap:
--------------------------------------------------------------------------------
1 | module LaunchdXPC [system] {
2 | header "launchdXPC.h"
3 | export *
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/Src/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 11/22/19.
6 | // 2020 TheMittenMac
7 | //
8 |
9 | import Foundation
10 |
11 | let version = 0.8
12 |
13 | // Go through command line arguments and set accordingly
14 | let argManager = ArgManager(suppliedArgs:CommandLine.arguments)
15 |
16 |
17 | // Must be root to gather all pid information
18 | if (NSUserName() != "root" && setuid(0) != 0) {
19 | print("This tool must be run as root in order to view all pid information")
20 | print("Exiting...")
21 | exit(1)
22 | }
23 |
24 |
25 | // Build all process objects
26 | let pc = ProcessCollector()
27 |
28 | // Get the launchd pid for future reference
29 | let rootNode = pc.getNodeForPid(1)
30 | guard rootNode != nil else {
31 | print("Could not find the launchd process. Aborting...")
32 | exit(1)
33 | }
34 |
35 |
36 | // If standard tree mode is active
37 | if argManager.standardMode {
38 | for proc in pc.processes {
39 | // Get the parent of the process
40 | let parentNode = pc.getNodeForPid(proc.ppid)
41 |
42 | // Assign this process as a child
43 | parentNode?.add(child: proc.node)
44 | }
45 | rootNode?.printTree()
46 | exit(0)
47 | }
48 |
49 |
50 | // If timeline mode is active
51 | if argManager.timelineMode == false {
52 | pc.printTimeline(outputFile: argManager.toFile)
53 | exit(0)
54 | }
55 |
56 | // Create a TrueTree
57 | for proc in pc.processes {
58 |
59 | // Create an on the fly node for a plist if one exists
60 | if let plist = proc.submittedByPlist {
61 | // Check if this plist is already in the tree
62 | if let existingPlistNode = rootNode?.searchPlist(value: plist) {
63 | existingPlistNode.add(child: proc.node)
64 | continue
65 | }
66 |
67 | let plistNode = Node(-1, path: plist, timestamp: "00:00:00", source: "launchd_xpc", displayString: plist)
68 | rootNode?.add(child: plistNode)
69 | plistNode.add(child: proc.node)
70 | continue
71 | }
72 |
73 | // Otherwise add the process as a child to its true parent
74 | let parentNode = pc.getNodeForPid(proc.trueParentPid)
75 | parentNode?.add(child: proc.node)
76 |
77 | // Create an on the fly node for any network connections this pid has and add them to itself
78 | if argManager.network {
79 | for x in proc.network {
80 | if let type = x.type {
81 | var displayString = ""
82 | if type == "TCP" {
83 | displayString = "\(type) - \(x.source):\(x.sourcePort) -> \(x.destination):\(x.destinationPort) - \(x.status)"
84 | } else {
85 | displayString = "\(type) - Local Port: \(x.sourcePort)"
86 | }
87 |
88 | let networkNode = Node(-1, path: "none", timestamp: "00:00:00", source: "Network", displayString: displayString)
89 | proc.node.add(child: networkNode)
90 | }
91 | }
92 | }
93 | }
94 |
95 | // print the launchd pid and all of it's children
96 | rootNode?.printTree()
97 |
98 |
--------------------------------------------------------------------------------
/Src/network.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Network.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 1/11/23.
6 | // Copyright © 2023 TheMittenMac. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ProcLib
11 | // Handy reference -> https://stackoverflow.com/questions/29294491/swift-obtaining-ip-address-from-socket-returns-weird-value
12 |
13 | struct NetworkConnection {
14 | let type: String?
15 | let pid: Int
16 | let family: String
17 | let source: String
18 | let sourcePort: UInt16
19 | let destination: String
20 | let destinationPort: UInt16
21 | let status: String
22 | }
23 |
24 | class NetworkConnections {
25 | private let PROC_PIDLISTFD_SIZE = Int32(MemoryLayout.stride)
26 | private let PROC_PIDFDSOCKETINFO_SIZE = Int32(MemoryLayout.stride)
27 | var connections = [NetworkConnection]()
28 | let pid: Int32
29 |
30 | init(pid: Int32) {
31 | self.pid = pid
32 |
33 | // get the size of the number of open files
34 | let size = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nil , 0)
35 |
36 | //get list of open file descriptors
37 | let fdInfo = UnsafeMutablePointer.allocate(capacity: Int(size))
38 | defer { fdInfo.deallocate() }
39 | buildConnections(fdInfo, size)
40 | }
41 |
42 | private func getSocketFamily(socketInfoBuffer: UnsafeMutablePointer) -> String? {
43 | switch socketInfoBuffer.pointee.psi.soi_family {
44 |
45 | case AF_INET:
46 | return "IPv4"
47 |
48 | case AF_INET6:
49 | return "IPv6"
50 |
51 | default:
52 | return nil
53 | }
54 | }
55 |
56 | private func getType(socketInfoBuffer: UnsafeMutablePointer) -> String? {
57 | switch Int(socketInfoBuffer.pointee.psi.soi_kind) {
58 | case SOCKINFO_IN:
59 | return "UDP"
60 |
61 | case SOCKINFO_TCP:
62 | return "TCP"
63 |
64 | default:
65 | return nil
66 | }
67 | }
68 |
69 | private func getLocalPort(socketInfoBuffer: UnsafeMutablePointer, socketType: String) -> UInt16 {
70 | var port = UInt16(0)
71 |
72 | if socketType == "UDP" {
73 | port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_in.insi_lport)
74 | }
75 |
76 | if socketType == "TCP" {
77 | port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport)
78 | }
79 |
80 | return port.byteSwapped
81 | }
82 |
83 | private func getRemotePort(socketInfoBuffer: UnsafeMutablePointer, socketType: String) -> UInt16 {
84 | if socketType == "UDP" {
85 | return 0
86 | }
87 |
88 | let port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport)
89 | return port.byteSwapped
90 | }
91 |
92 | private func getIP4DestinationAddress(socketInfoBuffer: UnsafeMutablePointer) -> String {
93 | var result = [CChar].init(repeating: 0, count: 16)
94 | inet_ntop(AF_INET, &socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_46.i46a_addr4, &result, 16)
95 | let ipAddr = String(cString: result)
96 |
97 | return ipAddr
98 | }
99 |
100 | private func getIP6DestinationAddress(socketInfoBuffer: UnsafeMutablePointer) -> String {
101 | var result = [CChar].init(repeating: 0, count: 128)
102 | inet_ntop(AF_INET6, &(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_6), &result, 128);
103 | let ipAddr = String(cString: result)
104 |
105 | return ipAddr
106 | }
107 |
108 | private func getIP4SourceAddress(socketInfoBuffer: UnsafeMutablePointer) -> String {
109 | var result = [CChar].init(repeating: 0, count: 16)
110 | inet_ntop(AF_INET, &socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_46.i46a_addr4, &result, 16)
111 | let ipAddr = String(cString: result)
112 |
113 | return ipAddr
114 | }
115 |
116 | private func getIP6SourceAddress(socketInfoBuffer: UnsafeMutablePointer) -> String {
117 | var result = [CChar].init(repeating: 0, count: 128)
118 | inet_ntop(AF_INET6, &(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_6), &result, 128);
119 | let ipAddr = String(cString: result)
120 |
121 | return ipAddr
122 | }
123 |
124 | private func getStatus(socketInfoBuffer: UnsafeMutablePointer) -> String {
125 | var status = ""
126 | switch socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_state {
127 | case TSI_S_CLOSED:
128 | status = "CLOSED"
129 | case TSI_S_LISTEN:
130 | status = "LISTENING"
131 | case TSI_S_SYN_SENT:
132 | status = "SYN SENT (active, have sent syn)"
133 | case TSI_S_SYN_RECEIVED:
134 | status = "SYN RECEIVED (have send and received syn)"
135 | case TSI_S_ESTABLISHED:
136 | status = "ESTABLISHED"
137 | case TSI_S__CLOSE_WAIT:
138 | status = "CLOSE WAIT (received fin, waiting for close) "
139 | case TSI_S_FIN_WAIT_1:
140 | status = "FIN WAIT1 (have closed, sent fin)"
141 | case TSI_S_CLOSING:
142 | status = "CLOSING (closed xchd FIN; await FIN ACK)"
143 | case TSI_S_LAST_ACK:
144 | status = "LAST ACK (had fin and close; await FIN ACK)"
145 | case TSI_S_FIN_WAIT_2:
146 | status = "FIN WAIT2 (have closed, fin is acked)"
147 | case TSI_S_TIME_WAIT:
148 | status = "TIME WAIT (in 2*msl quiet wait after close)"
149 | case TSI_S_RESERVED:
150 | status = "RESERVED"
151 | default:
152 | status = "Unknown"
153 | }
154 |
155 | return status
156 | }
157 |
158 | private func buildConnections(_ fdInfo: UnsafeMutablePointer, _ size: Int32) {
159 | proc_pidinfo(self.pid, PROC_PIDLISTFDS, 0, fdInfo, size)
160 |
161 | // Go through each open file descriptor
162 | for x in 0...Int(size/PROC_PIDLISTFD_SIZE) {
163 | var localPort = UInt16(0)
164 | var destinationPort = UInt16(0)
165 | var destination = ""
166 | var source = ""
167 |
168 | // Skip if file descriptor is not a socket
169 | if PROX_FDTYPE_SOCKET != fdInfo[x].proc_fdtype { continue }
170 |
171 | // Get the socket info, skipping if an error occurs
172 | let socketInfo = UnsafeMutablePointer.allocate(capacity: 1)
173 | defer { socketInfo.deallocate() }
174 |
175 | if PROC_PIDFDSOCKETINFO_SIZE != proc_pidfdinfo(self.pid, fdInfo[x].proc_fd, PROC_PIDFDSOCKETINFO, socketInfo, PROC_PIDFDSOCKETINFO_SIZE) {
176 | continue
177 | }
178 |
179 | // Get IPv4 or IPV6
180 | guard let family = getSocketFamily(socketInfoBuffer: socketInfo) else { return }
181 |
182 | // Get UDP or TCP
183 | guard let type = getType(socketInfoBuffer: socketInfo) else { return }
184 |
185 | // If this is a UDP connection
186 | if type == "UDP" {
187 | localPort = getLocalPort(socketInfoBuffer: socketInfo, socketType: type)
188 |
189 | } else if type == "TCP" {
190 | // Far more details can be collected from TCP connections
191 | localPort = getLocalPort(socketInfoBuffer: socketInfo, socketType: type)
192 | destinationPort = UInt16(socketInfo.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport).byteSwapped
193 |
194 | // If this is a IPv4 address get the local and remote connections
195 | if family == "IPv4" {
196 | destination = getIP4DestinationAddress(socketInfoBuffer: socketInfo)
197 | source = getIP4SourceAddress(socketInfoBuffer: socketInfo)
198 |
199 | } else if family == "IPv6" {
200 | destination = getIP6DestinationAddress(socketInfoBuffer: socketInfo)
201 | source = getIP6SourceAddress(socketInfoBuffer: socketInfo)
202 | }
203 | }
204 |
205 | let status = getStatus(socketInfoBuffer: socketInfo)
206 |
207 | let n = NetworkConnection(type: type,
208 | pid: Int(pid),
209 | family: family,
210 | source: source,
211 | sourcePort: localPort,
212 | destination: destination,
213 | destinationPort: destinationPort,
214 | status: status)
215 |
216 | connections.append(n)
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/Src/node.swift:
--------------------------------------------------------------------------------
1 | //
2 | // tree.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 11/2/19.
6 | // 2020 TheMittenMac
7 | //
8 |
9 |
10 | import Foundation
11 |
12 |
13 | final class Node {
14 | let pid: Int
15 | let path: String
16 | let timestamp: String
17 | let source: String
18 | let displayString: String
19 | private(set) var children: [Node]
20 |
21 | var fileName: String {
22 | if let fileName = path.components(separatedBy: "/").last {
23 | return fileName
24 | }
25 |
26 | return path
27 | }
28 |
29 | init(_ pid: Int, path: String, timestamp: String, source: String, displayString: String) {
30 | self.pid = pid
31 | self.path = path
32 | self.timestamp = timestamp
33 | self.source = source
34 | self.displayString = displayString
35 | children = []
36 | }
37 |
38 | func add(child: Node) {
39 | children.append(child)
40 | }
41 |
42 | func searchPlist(value: String) -> Node? {
43 | if value == self.path {
44 | return self
45 | }
46 |
47 | for child in children {
48 | if let found = child.searchPlist(value: value) {
49 | return found
50 | }
51 | }
52 | return nil
53 | }
54 | }
55 |
56 |
57 | // Extension for printing the tree. Great approach. Currently uses global vars from argmanager
58 | //https://stackoverflow.com/questions/46371513/printing-a-tree-with-indents-swift
59 | extension Node {
60 | func treeLines(_ nodeIndent:String="", _ childIndent:String="") -> [String] {
61 | var text = ""
62 | if path.hasSuffix(".plist") {
63 | if argManager.color { text = "\(displayString.blue)" } else { text = "\(displayString)" }
64 |
65 | } else if displayString.hasPrefix("UDP") || displayString.hasPrefix("TCP") {
66 | if argManager.color { text = "\(displayString.yellow)" } else { text = "\(displayString)" }
67 |
68 | } else {
69 | if argManager.color {
70 | if argManager.showpath {
71 | text = "\(displayString)"
72 | } else {
73 | text = "\(fileName)"
74 | }
75 | if argManager.showpid { text += " \(String(pid).magenta)"}
76 | if argManager.timestamps { text += " \(timestamp.cyan)"}
77 | if argManager.sources { text += " Aquired parent from -> \(source)".red }
78 | } else {
79 | text = "\(displayString) \(String(pid))"
80 | if argManager.timestamps { text += " \(timestamp)"}
81 | if argManager.sources { text += " Aquired parent from -> \(source)" }
82 | }
83 |
84 | }
85 |
86 | return [ nodeIndent + text ]
87 | + children.enumerated().map{ ($0 < children.count-1, $1) }
88 | .flatMap{ $0 ? $1.treeLines("┣╸","┃ ") : $1.treeLines("┗╸"," ") }
89 | .map{ childIndent + $0 }
90 |
91 | }
92 |
93 | func printTree() {
94 | let tree = treeLines().joined(separator:"\n")
95 |
96 | if let toFile = argManager.toFile {
97 | let fileUrl = URL(fileURLWithPath: toFile)
98 | do {
99 | try tree.write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8)
100 | } catch {
101 | print("Could not write TrueTree output to specified file")
102 | }
103 | } else {
104 | print(tree)
105 | }
106 | }
107 | }
108 |
109 |
--------------------------------------------------------------------------------
/Src/process.swift:
--------------------------------------------------------------------------------
1 | //
2 | // process.swift
3 | // TrueTree
4 | //
5 | // Created by Jaron Bradley on 11/1/19.
6 | // 2020 TheMittenMac
7 | //
8 |
9 | import Foundation
10 | import ProcLib
11 | import LaunchdXPC
12 |
13 | struct Process {
14 | let pid: Int
15 | let ppid: Int
16 | let responsiblePid: Int
17 | let path: String
18 | let submittedByPid: Int?
19 | let submittedByPlist: String?
20 | let timestamp: String
21 | let node: Node
22 | let trueParentPid: Int
23 | let source: String
24 | let network: [NetworkConnection]
25 | }
26 |
27 |
28 | class ProcessCollector {
29 | var processes = [Process]()
30 | let timestampFormat = DateFormatter()
31 | let InfoSize = Int32(MemoryLayout.stride)
32 | let MaxPathLen = Int(4 * MAXPATHLEN)
33 | typealias rpidFunc = @convention(c) (CInt) -> CInt
34 |
35 | init() {
36 | timestampFormat.dateFormat = "yyyy-MM-dd HH:mm:ss"
37 | self.collect()
38 | }
39 |
40 | func collect() {
41 | // Inspired by https://gist.github.com/kainjow/0e7650cc797a52261e0f4ba851477c2f
42 |
43 | // Call proc_listallpids once with nil/0 args to get the current number of pids
44 | let initialNumPids = proc_listallpids(nil, 0)
45 |
46 | // Allocate a buffer of these number of pids.
47 | // Make sure to deallocate it as this class does not manage memory for us.
48 | let buffer = UnsafeMutablePointer.allocate(capacity: Int(initialNumPids))
49 | defer {
50 | buffer.deallocate()
51 | }
52 |
53 | // Calculate the buffer's total length in bytes
54 | let bufferLength = initialNumPids * Int32(MemoryLayout.size)
55 |
56 | // Call the function again with our inputs now ready
57 | let numPids = proc_listallpids(buffer, bufferLength)
58 |
59 | // Loop through each pid and build a process struct
60 | for i in 0.. 1 {
91 | trueParent = submittedPid
92 | source = "application_services"
93 | } else if responsiblePid != pid {
94 | trueParent = responsiblePid
95 | source = "responsible_pid"
96 | } else {
97 | trueParent = ppid
98 | source = "parent_process"
99 | }
100 |
101 | // Collect a plist if it caused this program to run
102 | var plistNode: String?
103 | if let launchctlPlist = getSubmittedByPlist(UInt(pid)) {
104 | if launchctlPlist.hasSuffix(".plist") {
105 | plistNode = launchctlPlist
106 | source = "launchd_xpc"
107 | }
108 | }
109 |
110 | // Collect network connections
111 | let n = NetworkConnections(pid: Int32(pid))
112 | let networkConnections = n.connections
113 |
114 | // Create the tree node
115 | let node = Node(pid, path: path, timestamp: ts, source: source, displayString: path)
116 |
117 | // Create the process entry
118 | let p = Process(pid: pid,
119 | ppid: ppid,
120 | responsiblePid: responsiblePid,
121 | path: path,
122 | submittedByPid: submittedPid,
123 | submittedByPlist: plistNode ?? nil,
124 | timestamp: ts,
125 | node: node,
126 | trueParentPid: trueParent,
127 | source: source,
128 | network: networkConnections
129 | )
130 |
131 | // Add the process to the array of captured processes
132 | processes.append(p)
133 |
134 | }
135 |
136 | // Sort the processes by time
137 | processes = processes.sorted { $0.timestamp < $1.timestamp }
138 | }
139 |
140 | func getPPID(_ pidOfInterest:Int) -> Int? {
141 | // Call proc_pidinfo and return nil on error
142 | let pidInfo = UnsafeMutablePointer.allocate(capacity: 1)
143 | guard InfoSize == proc_pidinfo(Int32(pidOfInterest), PROC_PIDTBSDINFO, 0, pidInfo, InfoSize) else { return nil }
144 | defer { pidInfo.deallocate() }
145 |
146 | return Int(pidInfo.pointee.pbi_ppid)
147 | }
148 |
149 | func getResponsiblePid(_ pidOfInterest:Int) -> Int {
150 | // Get responsible pid using private Apple API
151 | let rpidSym:UnsafeMutableRawPointer! = dlsym(UnsafeMutableRawPointer(bitPattern: -1), "responsibility_get_pid_responsible_for_pid")
152 | let responsiblePid = unsafeBitCast(rpidSym, to: rpidFunc.self)(CInt(pidOfInterest))
153 |
154 | guard responsiblePid != -1 else {
155 | print("Error getting responsible pid for process \(pidOfInterest). Setting to responsible pid to itself")
156 | return pidOfInterest
157 | }
158 |
159 | return Int(responsiblePid)
160 | }
161 |
162 | func getPath(_ pidOfInterest: Int) -> String {
163 | let pathBuffer = UnsafeMutablePointer.allocate(capacity: MaxPathLen)
164 | defer { pathBuffer.deallocate() }
165 | pathBuffer.initialize(repeating: 0, count: MaxPathLen)
166 |
167 | if proc_pidpath(Int32(pidOfInterest), pathBuffer, UInt32(MemoryLayout.stride * MaxPathLen)) == 0 {
168 | return "unknown"
169 | }
170 |
171 | return String(cString: pathBuffer)
172 | }
173 |
174 | func getTimestamp(_ pidOfInterest: Int) -> Date {
175 | // Call proc_pidinfo and return current date on error
176 | let pidInfo = UnsafeMutablePointer.allocate(capacity: 1)
177 | guard InfoSize == proc_pidinfo(Int32(pidOfInterest), PROC_PIDTBSDINFO, 0, pidInfo, InfoSize) else { return Date() }
178 | defer { pidInfo.deallocate() }
179 |
180 | return Date(timeIntervalSince1970: TimeInterval(pidInfo.pointee.pbi_start_tvsec))
181 | }
182 |
183 | func getNodeForPid(_ pidOfInterest: Int) -> Node? {
184 | for proc in processes {
185 | if proc.pid == pidOfInterest {
186 | return proc.node
187 | }
188 | }
189 |
190 | return nil
191 | }
192 | }
193 |
194 | extension ProcessCollector {
195 | func printTimeline(outputFile:String?) {
196 | for proc in processes {
197 | let text = "\(proc.timestamp) \(proc.path) \(proc.pid)"
198 | if let outputFile = outputFile {
199 | let fileUrl = URL(fileURLWithPath: outputFile)
200 | do {
201 | try text.write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8)
202 | } catch {
203 | print("Could not write TrueTree output to specified file")
204 | }
205 | }
206 | print("\(proc.timestamp) \(proc.path) \(proc.pid)")
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/TrueTree.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8A78B5F72388924B00DA165C /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B5F62388924B00DA165C /* main.swift */; };
11 | 8A78B6002388928900DA165C /* launchdXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B5FF2388928900DA165C /* launchdXPC.m */; };
12 | 8A78B604238C1FD800DA165C /* node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B603238C1FD800DA165C /* node.swift */; };
13 | 8A78B606238C200E00DA165C /* process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B605238C200E00DA165C /* process.swift */; };
14 | 8AFE066523FF1D9A0032DA23 /* args.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE066423FF1D9A0032DA23 /* args.swift */; };
15 | A300C394296FBC8B008FF5F0 /* network.swift in Sources */ = {isa = PBXBuildFile; fileRef = A300C393296FBC8B008FF5F0 /* network.swift */; };
16 | A3315741293A6F640075C2C2 /* colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3315740293A6F640075C2C2 /* colors.swift */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXCopyFilesBuildPhase section */
20 | 8A78B5F12388924B00DA165C /* CopyFiles */ = {
21 | isa = PBXCopyFilesBuildPhase;
22 | buildActionMask = 2147483647;
23 | dstPath = /usr/share/man/man1/;
24 | dstSubfolderSpec = 0;
25 | files = (
26 | );
27 | runOnlyForDeploymentPostprocessing = 1;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 8A78B5F32388924B00DA165C /* TrueTree */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TrueTree; sourceTree = BUILT_PRODUCTS_DIR; };
33 | 8A78B5F62388924B00DA165C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
34 | 8A78B5FE2388928900DA165C /* launchdXPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = launchdXPC.h; sourceTree = ""; };
35 | 8A78B5FF2388928900DA165C /* launchdXPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = launchdXPC.m; sourceTree = ""; };
36 | 8A78B6022389E49200DA165C /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
37 | 8A78B603238C1FD800DA165C /* node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = node.swift; sourceTree = ""; };
38 | 8A78B605238C200E00DA165C /* process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = process.swift; sourceTree = ""; };
39 | 8AB67275239568980040C141 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
40 | 8AB67276239568A70040C141 /* ProcLib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProcLib.h; sourceTree = ""; };
41 | 8AE4947F2410244F00E35A32 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
42 | 8AFE066423FF1D9A0032DA23 /* args.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = args.swift; sourceTree = ""; };
43 | A300C393296FBC8B008FF5F0 /* network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = network.swift; sourceTree = ""; };
44 | A3315740293A6F640075C2C2 /* colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = colors.swift; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 8A78B5F02388924B00DA165C /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 8A78B5EA2388924B00DA165C = {
59 | isa = PBXGroup;
60 | children = (
61 | 8AE4947F2410244F00E35A32 /* README.md */,
62 | 8A78B5F52388924B00DA165C /* Src */,
63 | 8A78B5F42388924B00DA165C /* Products */,
64 | );
65 | sourceTree = "";
66 | };
67 | 8A78B5F42388924B00DA165C /* Products */ = {
68 | isa = PBXGroup;
69 | children = (
70 | 8A78B5F32388924B00DA165C /* TrueTree */,
71 | );
72 | name = Products;
73 | sourceTree = "";
74 | };
75 | 8A78B5F52388924B00DA165C /* Src */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 8AB672742395684F0040C141 /* ProcLib */,
79 | 8A78B5FD2388927500DA165C /* launchdXPC */,
80 | 8AFE066423FF1D9A0032DA23 /* args.swift */,
81 | 8A78B5F62388924B00DA165C /* main.swift */,
82 | 8A78B605238C200E00DA165C /* process.swift */,
83 | 8A78B603238C1FD800DA165C /* node.swift */,
84 | A3315740293A6F640075C2C2 /* colors.swift */,
85 | A300C393296FBC8B008FF5F0 /* network.swift */,
86 | );
87 | path = Src;
88 | sourceTree = "";
89 | };
90 | 8A78B5FD2388927500DA165C /* launchdXPC */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 8A78B6022389E49200DA165C /* module.modulemap */,
94 | 8A78B5FE2388928900DA165C /* launchdXPC.h */,
95 | 8A78B5FF2388928900DA165C /* launchdXPC.m */,
96 | );
97 | path = launchdXPC;
98 | sourceTree = "";
99 | };
100 | 8AB672742395684F0040C141 /* ProcLib */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 8AB67275239568980040C141 /* module.modulemap */,
104 | 8AB67276239568A70040C141 /* ProcLib.h */,
105 | );
106 | path = ProcLib;
107 | sourceTree = "";
108 | };
109 | /* End PBXGroup section */
110 |
111 | /* Begin PBXNativeTarget section */
112 | 8A78B5F22388924B00DA165C /* TrueTree */ = {
113 | isa = PBXNativeTarget;
114 | buildConfigurationList = 8A78B5FA2388924B00DA165C /* Build configuration list for PBXNativeTarget "TrueTree" */;
115 | buildPhases = (
116 | 8A78B5EF2388924B00DA165C /* Sources */,
117 | 8A78B5F02388924B00DA165C /* Frameworks */,
118 | 8A78B5F12388924B00DA165C /* CopyFiles */,
119 | );
120 | buildRules = (
121 | );
122 | dependencies = (
123 | );
124 | name = TrueTree;
125 | productName = TrueTree;
126 | productReference = 8A78B5F32388924B00DA165C /* TrueTree */;
127 | productType = "com.apple.product-type.tool";
128 | };
129 | /* End PBXNativeTarget section */
130 |
131 | /* Begin PBXProject section */
132 | 8A78B5EB2388924B00DA165C /* Project object */ = {
133 | isa = PBXProject;
134 | attributes = {
135 | LastSwiftUpdateCheck = 1100;
136 | LastUpgradeCheck = 1240;
137 | ORGANIZATIONNAME = mittenmac;
138 | TargetAttributes = {
139 | 8A78B5F22388924B00DA165C = {
140 | CreatedOnToolsVersion = 11.0;
141 | LastSwiftMigration = 1100;
142 | };
143 | };
144 | };
145 | buildConfigurationList = 8A78B5EE2388924B00DA165C /* Build configuration list for PBXProject "TrueTree" */;
146 | compatibilityVersion = "Xcode 9.3";
147 | developmentRegion = en;
148 | hasScannedForEncodings = 0;
149 | knownRegions = (
150 | en,
151 | Base,
152 | );
153 | mainGroup = 8A78B5EA2388924B00DA165C;
154 | productRefGroup = 8A78B5F42388924B00DA165C /* Products */;
155 | projectDirPath = "";
156 | projectRoot = "";
157 | targets = (
158 | 8A78B5F22388924B00DA165C /* TrueTree */,
159 | );
160 | };
161 | /* End PBXProject section */
162 |
163 | /* Begin PBXSourcesBuildPhase section */
164 | 8A78B5EF2388924B00DA165C /* Sources */ = {
165 | isa = PBXSourcesBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | 8A78B5F72388924B00DA165C /* main.swift in Sources */,
169 | 8AFE066523FF1D9A0032DA23 /* args.swift in Sources */,
170 | A3315741293A6F640075C2C2 /* colors.swift in Sources */,
171 | 8A78B6002388928900DA165C /* launchdXPC.m in Sources */,
172 | 8A78B604238C1FD800DA165C /* node.swift in Sources */,
173 | A300C394296FBC8B008FF5F0 /* network.swift in Sources */,
174 | 8A78B606238C200E00DA165C /* process.swift in Sources */,
175 | );
176 | runOnlyForDeploymentPostprocessing = 0;
177 | };
178 | /* End PBXSourcesBuildPhase section */
179 |
180 | /* Begin XCBuildConfiguration section */
181 | 8A78B5F82388924B00DA165C /* Debug */ = {
182 | isa = XCBuildConfiguration;
183 | buildSettings = {
184 | ALWAYS_SEARCH_USER_PATHS = NO;
185 | CLANG_ANALYZER_NONNULL = YES;
186 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
188 | CLANG_CXX_LIBRARY = "libc++";
189 | CLANG_ENABLE_MODULES = YES;
190 | CLANG_ENABLE_OBJC_ARC = YES;
191 | CLANG_ENABLE_OBJC_WEAK = YES;
192 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
193 | CLANG_WARN_BOOL_CONVERSION = YES;
194 | CLANG_WARN_COMMA = YES;
195 | CLANG_WARN_CONSTANT_CONVERSION = YES;
196 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
197 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
198 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
199 | CLANG_WARN_EMPTY_BODY = YES;
200 | CLANG_WARN_ENUM_CONVERSION = YES;
201 | CLANG_WARN_INFINITE_RECURSION = YES;
202 | CLANG_WARN_INT_CONVERSION = YES;
203 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
204 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
205 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
207 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
208 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
209 | CLANG_WARN_STRICT_PROTOTYPES = YES;
210 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
211 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
212 | CLANG_WARN_UNREACHABLE_CODE = YES;
213 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
214 | COPY_PHASE_STRIP = NO;
215 | DEBUG_INFORMATION_FORMAT = dwarf;
216 | ENABLE_STRICT_OBJC_MSGSEND = YES;
217 | ENABLE_TESTABILITY = YES;
218 | GCC_C_LANGUAGE_STANDARD = gnu11;
219 | GCC_DYNAMIC_NO_PIC = NO;
220 | GCC_NO_COMMON_BLOCKS = YES;
221 | GCC_OPTIMIZATION_LEVEL = 0;
222 | GCC_PREPROCESSOR_DEFINITIONS = (
223 | "DEBUG=1",
224 | "$(inherited)",
225 | );
226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
228 | GCC_WARN_UNDECLARED_SELECTOR = YES;
229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
230 | GCC_WARN_UNUSED_FUNCTION = YES;
231 | GCC_WARN_UNUSED_VARIABLE = YES;
232 | MACOSX_DEPLOYMENT_TARGET = 10.15;
233 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
234 | MTL_FAST_MATH = YES;
235 | ONLY_ACTIVE_ARCH = NO;
236 | SDKROOT = macosx;
237 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
238 | SWIFT_INCLUDE_PATHS = "";
239 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
240 | };
241 | name = Debug;
242 | };
243 | 8A78B5F92388924B00DA165C /* Release */ = {
244 | isa = XCBuildConfiguration;
245 | buildSettings = {
246 | ALWAYS_SEARCH_USER_PATHS = NO;
247 | CLANG_ANALYZER_NONNULL = YES;
248 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
250 | CLANG_CXX_LIBRARY = "libc++";
251 | CLANG_ENABLE_MODULES = YES;
252 | CLANG_ENABLE_OBJC_ARC = YES;
253 | CLANG_ENABLE_OBJC_WEAK = YES;
254 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
255 | CLANG_WARN_BOOL_CONVERSION = YES;
256 | CLANG_WARN_COMMA = YES;
257 | CLANG_WARN_CONSTANT_CONVERSION = YES;
258 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
259 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
260 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
261 | CLANG_WARN_EMPTY_BODY = YES;
262 | CLANG_WARN_ENUM_CONVERSION = YES;
263 | CLANG_WARN_INFINITE_RECURSION = YES;
264 | CLANG_WARN_INT_CONVERSION = YES;
265 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
266 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
267 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
269 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
270 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
271 | CLANG_WARN_STRICT_PROTOTYPES = YES;
272 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
273 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
274 | CLANG_WARN_UNREACHABLE_CODE = YES;
275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
276 | COPY_PHASE_STRIP = NO;
277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
278 | ENABLE_NS_ASSERTIONS = NO;
279 | ENABLE_STRICT_OBJC_MSGSEND = YES;
280 | GCC_C_LANGUAGE_STANDARD = gnu11;
281 | GCC_NO_COMMON_BLOCKS = YES;
282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
284 | GCC_WARN_UNDECLARED_SELECTOR = YES;
285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
286 | GCC_WARN_UNUSED_FUNCTION = YES;
287 | GCC_WARN_UNUSED_VARIABLE = YES;
288 | MACOSX_DEPLOYMENT_TARGET = 10.15;
289 | MTL_ENABLE_DEBUG_INFO = NO;
290 | MTL_FAST_MATH = YES;
291 | ONLY_ACTIVE_ARCH = NO;
292 | SDKROOT = macosx;
293 | SWIFT_COMPILATION_MODE = wholemodule;
294 | SWIFT_INCLUDE_PATHS = "";
295 | SWIFT_OPTIMIZATION_LEVEL = "-O";
296 | };
297 | name = Release;
298 | };
299 | 8A78B5FB2388924B00DA165C /* Debug */ = {
300 | isa = XCBuildConfiguration;
301 | buildSettings = {
302 | CLANG_ENABLE_MODULES = YES;
303 | CLANG_USE_OPTIMIZATION_PROFILE = NO;
304 | CODE_SIGN_IDENTITY = "Apple Development";
305 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
306 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
307 | CODE_SIGN_STYLE = Manual;
308 | DEVELOPMENT_TEAM = "";
309 | "DEVELOPMENT_TEAM[sdk=macosx*]" = "";
310 | ENABLE_HARDENED_RUNTIME = YES;
311 | INSTALL_PATH = /usr/local/bin/;
312 | MACOSX_DEPLOYMENT_TARGET = 10.13;
313 | "OTHER_CODE_SIGN_FLAGS[sdk=*]" = "--timestamp";
314 | PRODUCT_BUNDLE_IDENTIFIER = truetree;
315 | PRODUCT_NAME = "$(TARGET_NAME)";
316 | PROVISIONING_PROFILE_SPECIFIER = "";
317 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Src/launchdXPC $(SRCROOT) $(SRCROOT)/Src/ProcLib";
318 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h";
319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
320 | SWIFT_VERSION = 5.0;
321 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)";
322 | };
323 | name = Debug;
324 | };
325 | 8A78B5FC2388924B00DA165C /* Release */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | CLANG_ENABLE_MODULES = YES;
329 | CLANG_USE_OPTIMIZATION_PROFILE = NO;
330 | CODE_SIGN_IDENTITY = "Apple Development";
331 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
332 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
333 | CODE_SIGN_STYLE = Manual;
334 | DEVELOPMENT_TEAM = "";
335 | "DEVELOPMENT_TEAM[sdk=macosx*]" = "";
336 | ENABLE_HARDENED_RUNTIME = YES;
337 | INSTALL_PATH = /usr/local/bin/;
338 | MACOSX_DEPLOYMENT_TARGET = 10.13;
339 | ONLY_ACTIVE_ARCH = NO;
340 | PRODUCT_BUNDLE_IDENTIFIER = truetree;
341 | PRODUCT_NAME = "$(TARGET_NAME)";
342 | PROVISIONING_PROFILE_SPECIFIER = "";
343 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Src/launchdXPC $(SRCROOT) $(SRCROOT)/Src/ProcLib";
344 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h";
345 | SWIFT_VERSION = 5.0;
346 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)";
347 | };
348 | name = Release;
349 | };
350 | /* End XCBuildConfiguration section */
351 |
352 | /* Begin XCConfigurationList section */
353 | 8A78B5EE2388924B00DA165C /* Build configuration list for PBXProject "TrueTree" */ = {
354 | isa = XCConfigurationList;
355 | buildConfigurations = (
356 | 8A78B5F82388924B00DA165C /* Debug */,
357 | 8A78B5F92388924B00DA165C /* Release */,
358 | );
359 | defaultConfigurationIsVisible = 0;
360 | defaultConfigurationName = Release;
361 | };
362 | 8A78B5FA2388924B00DA165C /* Build configuration list for PBXNativeTarget "TrueTree" */ = {
363 | isa = XCConfigurationList;
364 | buildConfigurations = (
365 | 8A78B5FB2388924B00DA165C /* Debug */,
366 | 8A78B5FC2388924B00DA165C /* Release */,
367 | );
368 | defaultConfigurationIsVisible = 0;
369 | defaultConfigurationName = Release;
370 | };
371 | /* End XCConfigurationList section */
372 | };
373 | rootObject = 8A78B5EB2388924B00DA165C /* Project object */;
374 | }
375 |
--------------------------------------------------------------------------------
/TrueTree.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TrueTree.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | 
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
2 |
--------------------------------------------------------------------------------