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 = processName != null
70 ? '"Get-CimInstance Win32_Process -Filter \\"name=\'$processName\'\\" | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"'
71 : '"Get-CimInstance Win32_Process | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"';
72 // TODO(ianh): Unfortunately, there doesn't seem to be a good way to get
73 // ProcessManager to run this.
74 final ProcessResult result = await Process.run(
75 'powershell -command $script',
76 <String>[],
77 );
78 if (result.exitCode != 0) {
79 print('Could not list processes!');
80 print(result.stderr);
81 print(result.stdout);
82 return <RunningProcessInfo>{};
83 }
84 return processPowershellOutput(result.stdout as String).toSet();
85}
86
87/// Parses the output of the PowerShell script from [windowsRunningProcesses].
88///
89/// E.g.:
90/// ProcessId CreationDate CommandLine
91/// --------- ------------ -----------
92/// 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
93@visibleForTesting
94Iterable<RunningProcessInfo> processPowershellOutput(String output) sync* {
95 const int processIdHeaderSize = 'ProcessId'.length;
96 const int creationDateHeaderStart = processIdHeaderSize + 1;
97 late int creationDateHeaderEnd;
98 late int commandLineHeaderStart;
99 bool inTableBody = false;
100 for (final String line in output.split('\n')) {
101 if (line.startsWith('ProcessId')) {
102 commandLineHeaderStart = line.indexOf('CommandLine');
103 creationDateHeaderEnd = commandLineHeaderStart - 1;
104 }
105 if (line.startsWith('--------- ------------')) {
106 inTableBody = true;
107 continue;
108 }
109 if (!inTableBody || line.isEmpty) {
110 continue;
111 }
112 if (line.length < commandLineHeaderStart) {
113 continue;
114 }
115
116 // 3/11/2019 11:01:54 AM
117 // 12/11/2019 11:01:54 AM
118 String rawTime = line.substring(
119 creationDateHeaderStart,
120 creationDateHeaderEnd,
121 ).trim();
122
123 if (rawTime[1] == '/') {
124 rawTime = '0$rawTime';
125 }
126 if (rawTime[4] == '/') {
127 rawTime = '${rawTime.substring(0, 3)}0${rawTime.substring(3)}';
128 }
129 final String year = rawTime.substring(6, 10);
130 final String month = rawTime.substring(3, 5);
131 final String day = rawTime.substring(0, 2);
132 String time = rawTime.substring(11, 19);
133 if (time[7] == ' ') {
134 time = '0$time'.trim();
135 }
136 if (rawTime.endsWith('PM')) {
137 final int hours = int.parse(time.substring(0, 2));
138 time = '${hours + 12}${time.substring(2)}';
139 }
140
141 final int pid = int.parse(line.substring(0, processIdHeaderSize).trim());
142 final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time');
143 final String commandLine = line.substring(commandLineHeaderStart).trim();
144 yield RunningProcessInfo(pid, commandLine, creationDate);
145 }
146}
147
148@visibleForTesting
149Future<Set<RunningProcessInfo>> posixRunningProcesses(
150 String? processName,
151 ProcessManager processManager,
152) async {
153 // Cirrus is missing this in Linux for some reason.
154 if (!processManager.canRun('ps')) {
155 print('Cannot list processes on this system: "ps" not available.');
156 return <RunningProcessInfo>{};
157 }
158 final ProcessResult result = await processManager.run(<String>[
159 'ps',
160 '-eo',
161 'lstart,pid,command',
162 ]);
163 if (result.exitCode != 0) {
164 print('Could not list processes!');
165 print(result.stderr);
166 print(result.stdout);
167 return <RunningProcessInfo>{};
168 }
169 return processPsOutput(result.stdout as String, processName).toSet();
170}
171
172/// Parses the output of the command in [posixRunningProcesses].
173///
174/// E.g.:
175///
176/// STARTED PID COMMAND
177/// Sat Mar 9 20:12:47 2019 1 /sbin/launchd
178/// Sat Mar 9 20:13:00 2019 49 /usr/sbin/syslogd
179@visibleForTesting
180Iterable<RunningProcessInfo> processPsOutput(
181 String output,
182 String? processName,
183) sync* {
184 bool inTableBody = false;
185 for (String line in output.split('\n')) {
186 if (line.trim().startsWith('STARTED')) {
187 inTableBody = true;
188 continue;
189 }
190 if (!inTableBody || line.isEmpty) {
191 continue;
192 }
193
194 if (processName != null && !line.contains(processName)) {
195 continue;
196 }
197 if (line.length < 25) {
198 continue;
199 }
200
201 // 'Sat Feb 16 02:29:55 2019'
202 // 'Sat Mar 9 20:12:47 2019'
203 const Map<String, String> months = <String, String>{
204 'Jan': '01',
205 'Feb': '02',
206 'Mar': '03',
207 'Apr': '04',
208 'May': '05',
209 'Jun': '06',
210 'Jul': '07',
211 'Aug': '08',
212 'Sep': '09',
213 'Oct': '10',
214 'Nov': '11',
215 'Dec': '12',
216 };
217 final String rawTime = line.substring(0, 24);
218
219 final String year = rawTime.substring(20, 24);
220 final String month = months[rawTime.substring(4, 7)]!;
221 final String day = rawTime.substring(8, 10).replaceFirst(' ', '0');
222 final String time = rawTime.substring(11, 19);
223
224 final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time');
225 line = line.substring(24).trim();
226 final int nextSpace = line.indexOf(' ');
227 final int pid = int.parse(line.substring(0, nextSpace));
228 final String commandLine = line.substring(nextSpace + 1);
229 yield RunningProcessInfo(pid, commandLine, creationDate);
230 }
231}
232

Provided by KDAB

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