1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:io';
6
7import 'package:meta/meta.dart';
8import 'package:process/process.dart';
9
10@immutable
11class RunningProcessInfo {
12 const RunningProcessInfo(this.pid, this.commandLine, this.creationDate);
13
14 final int pid;
15 final String commandLine;
16 final DateTime creationDate;
17
18 @override
19 bool operator ==(Object other) {
20 return other is RunningProcessInfo &&
21 other.pid == pid &&
22 other.commandLine == commandLine &&
23 other.creationDate == creationDate;
24 }
25
26 Future<bool> terminate({required ProcessManager processManager}) async {
27 // This returns true when the signal is sent, not when the process goes away.
28 // See also https://github.com/dart-lang/sdk/issues/40759 (killPid should wait for process to be terminated).
29 if (Platform.isWindows) {
30 // TODO(ianh): Move Windows to killPid once we can.
31 // - killPid on Windows has not-useful return code: https://github.com/dart-lang/sdk/issues/47675
32 final ProcessResult result = await processManager.run(<String>[
33 'taskkill.exe',
34 '/pid',
35 '$pid',
36 '/f',
37 ]);
38 return result.exitCode == 0;
39 }
40 return processManager.killPid(pid, ProcessSignal.sigkill);
41 }
42
43 @override
44 int get hashCode => Object.hash(pid, commandLine, creationDate);
45
46 @override
47 String toString() {
48 return 'RunningProcesses(pid: $pid, commandLine: $commandLine, creationDate: $creationDate)';
49 }
50}
51
52Future<Set<RunningProcessInfo>> getRunningProcesses({
53 String? processName,
54 required ProcessManager processManager,
55}) {
56 if (Platform.isWindows) {
57 return windowsRunningProcesses(processName, processManager);
58 }
59 return posixRunningProcesses(processName, processManager);
60}
61
62@visibleForTesting
63Future<Set<RunningProcessInfo>> windowsRunningProcesses(
64 String? processName,
65 ProcessManager processManager,
66) async {
67 // PowerShell script to get the command line arguments and create time of a process.
68 // See: https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
69 final String script =
70 processName != null
71 ? '"Get-CimInstance Win32_Process -Filter \\"name=\'$processName\'\\" | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"'
72 : '"Get-CimInstance Win32_Process | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"';
73 // TODO(ianh): Unfortunately, there doesn't seem to be a good way to get
74 // ProcessManager to run this.
75 final ProcessResult result = await Process.run('powershell -command $script', <String>[]);
76 if (result.exitCode != 0) {
77 print('Could not list processes!');
78 print(result.stderr);
79 print(result.stdout);
80 return <RunningProcessInfo>{};
81 }
82 return processPowershellOutput(result.stdout as String).toSet();
83}
84
85/// Parses the output of the PowerShell script from [windowsRunningProcesses].
86///
87/// E.g.:
88/// ProcessId CreationDate CommandLine
89/// --------- ------------ -----------
90/// 2904 3/11/2019 11:01:54 AM "C:\Program Files\Android\Android Studio\jre\bin\java.exe" -Xmx1536M -Dfile.encoding=windows-1252 -Duser.country=US -Duser.language=en -Duser.variant -cp C:\Users\win1\.gradle\wrapper\dists\gradle-4.10.2-all\9fahxiiecdb76a5g3aw9oi8rv\gradle-4.10.2\lib\gradle-launcher-4.10.2.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 4.10.2
91@visibleForTesting
92Iterable<RunningProcessInfo> processPowershellOutput(String output) sync* {
93 const int processIdHeaderSize = 'ProcessId'.length;
94 const int creationDateHeaderStart = processIdHeaderSize + 1;
95 late int creationDateHeaderEnd;
96 late int commandLineHeaderStart;
97 bool inTableBody = false;
98 for (final String line in output.split('\n')) {
99 if (line.startsWith('ProcessId')) {
100 commandLineHeaderStart = line.indexOf('CommandLine');
101 creationDateHeaderEnd = commandLineHeaderStart - 1;
102 }
103 if (line.startsWith('--------- ------------')) {
104 inTableBody = true;
105 continue;
106 }
107 if (!inTableBody || line.isEmpty) {
108 continue;
109 }
110 if (line.length < commandLineHeaderStart) {
111 continue;
112 }
113
114 // 3/11/2019 11:01:54 AM
115 // 12/11/2019 11:01:54 AM
116 String rawTime = line.substring(creationDateHeaderStart, creationDateHeaderEnd).trim();
117
118 if (rawTime[1] == '/') {
119 rawTime = '0$rawTime';
120 }
121 if (rawTime[4] == '/') {
122 rawTime = '${rawTime.substring(0, 3)}0${rawTime.substring(3)}';
123 }
124 final String year = rawTime.substring(6, 10);
125 final String month = rawTime.substring(3, 5);
126 final String day = rawTime.substring(0, 2);
127 String time = rawTime.substring(11, 19);
128 if (time[7] == ' ') {
129 time = '0$time'.trim();
130 }
131 if (rawTime.endsWith('PM')) {
132 final int hours = int.parse(time.substring(0, 2));
133 time = '${hours + 12}${time.substring(2)}';
134 }
135
136 final int pid = int.parse(line.substring(0, processIdHeaderSize).trim());
137 final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time');
138 final String commandLine = line.substring(commandLineHeaderStart).trim();
139 yield RunningProcessInfo(pid, commandLine, creationDate);
140 }
141}
142
143@visibleForTesting
144Future<Set<RunningProcessInfo>> posixRunningProcesses(
145 String? processName,
146 ProcessManager processManager,
147) async {
148 final ProcessResult result = await processManager.run(<String>[
149 'ps',
150 '-eo',
151 'lstart,pid,command',
152 ]);
153 if (result.exitCode != 0) {
154 print('Could not list processes!');
155 print(result.stderr);
156 print(result.stdout);
157 return <RunningProcessInfo>{};
158 }
159 return processPsOutput(result.stdout as String, processName).toSet();
160}
161
162/// Parses the output of the command in [posixRunningProcesses].
163///
164/// E.g.:
165///
166/// STARTED PID COMMAND
167/// Sat Mar 9 20:12:47 2019 1 /sbin/launchd
168/// Sat Mar 9 20:13:00 2019 49 /usr/sbin/syslogd
169@visibleForTesting
170Iterable<RunningProcessInfo> processPsOutput(String output, String? processName) sync* {
171 bool inTableBody = false;
172 for (String line in output.split('\n')) {
173 if (line.trim().startsWith('STARTED')) {
174 inTableBody = true;
175 continue;
176 }
177 if (!inTableBody || line.isEmpty) {
178 continue;
179 }
180
181 if (processName != null && !line.contains(processName)) {
182 continue;
183 }
184 if (line.length < 25) {
185 continue;
186 }
187
188 // 'Sat Feb 16 02:29:55 2019'
189 // 'Sat Mar 9 20:12:47 2019'
190 const Map<String, String> months = <String, String>{
191 'Jan': '01',
192 'Feb': '02',
193 'Mar': '03',
194 'Apr': '04',
195 'May': '05',
196 'Jun': '06',
197 'Jul': '07',
198 'Aug': '08',
199 'Sep': '09',
200 'Oct': '10',
201 'Nov': '11',
202 'Dec': '12',
203 };
204 final String rawTime = line.substring(0, 24);
205
206 final String year = rawTime.substring(20, 24);
207 final String month = months[rawTime.substring(4, 7)]!;
208 final String day = rawTime.substring(8, 10).replaceFirst(' ', '0');
209 final String time = rawTime.substring(11, 19);
210
211 final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time');
212 line = line.substring(24).trim();
213 final int nextSpace = line.indexOf(' ');
214 final int pid = int.parse(line.substring(0, nextSpace));
215 final String commandLine = line.substring(nextSpace + 1);
216 yield RunningProcessInfo(pid, commandLine, creationDate);
217 }
218}
219

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com