| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:process/process.dart'; |
| |
| @immutable |
| class RunningProcessInfo { |
| const RunningProcessInfo(this.pid, this.creationDate, this.commandLine) |
| : assert(pid != null), |
| assert(commandLine != null); |
| |
| final String commandLine; |
| final String pid; |
| final DateTime creationDate; |
| |
| @override |
| bool operator ==(Object other) { |
| return other is RunningProcessInfo |
| && other.pid == pid |
| && other.commandLine == commandLine |
| && other.creationDate == creationDate; |
| } |
| |
| @override |
| int get hashCode { |
| // TODO(dnfield): Replace this when Object.hashValues lands, https://github.com/dart-lang/sdk/issues/11617 |
| int hash = 17; |
| if (pid != null) { |
| hash = hash * 23 + pid.hashCode; |
| } |
| if (commandLine != null) { |
| hash = hash * 23 + commandLine.hashCode; |
| } |
| if (creationDate != null) { |
| hash = hash * 23 + creationDate.hashCode; |
| } |
| return hash; |
| } |
| |
| @override |
| String toString() { |
| return 'RunningProcesses{pid: $pid, commandLine: $commandLine, creationDate: $creationDate}'; |
| } |
| } |
| |
| Future<bool> killProcess(String pid, {ProcessManager processManager}) async { |
| assert(pid != null, 'Must specify a pid to kill'); |
| processManager ??= const LocalProcessManager(); |
| ProcessResult result; |
| if (Platform.isWindows) { |
| result = await processManager.run(<String>[ |
| 'taskkill.exe', |
| '/pid', |
| pid, |
| '/f', |
| ]); |
| } else { |
| result = await processManager.run(<String>[ |
| 'kill', |
| '-9', |
| pid, |
| ]); |
| } |
| return result.exitCode == 0; |
| } |
| |
| Stream<RunningProcessInfo> getRunningProcesses({ |
| String processName, |
| ProcessManager processManager, |
| }) { |
| processManager ??= const LocalProcessManager(); |
| if (Platform.isWindows) { |
| return windowsRunningProcesses(processName); |
| } |
| return posixRunningProcesses(processName, processManager); |
| } |
| |
| @visibleForTesting |
| Stream<RunningProcessInfo> windowsRunningProcesses(String processName) async* { |
| // PowerShell script to get the command line arguments and create time of |
| // a process. |
| // See: https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process |
| final String script = processName != null |
| ? '"Get-CimInstance Win32_Process -Filter \\"name=\'$processName\'\\" | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"' |
| : '"Get-CimInstance Win32_Process | Select-Object ProcessId,CreationDate,CommandLine | Format-Table -AutoSize | Out-String -Width 4096"'; |
| // Unfortunately, there doesn't seem to be a good way to get ProcessManager to |
| // run this. |
| final ProcessResult result = await Process.run( |
| 'powershell -command $script', |
| <String>[], |
| ); |
| if (result.exitCode != 0) { |
| print('Could not list processes!'); |
| print(result.stderr); |
| print(result.stdout); |
| return; |
| } |
| for (final RunningProcessInfo info in processPowershellOutput(result.stdout as String)) { |
| yield info; |
| } |
| } |
| |
| /// Parses the output of the PowerShell script from [windowsRunningProcesses]. |
| /// |
| /// E.g.: |
| /// ProcessId CreationDate CommandLine |
| /// --------- ------------ ----------- |
| /// 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 |
| @visibleForTesting |
| Iterable<RunningProcessInfo> processPowershellOutput(String output) sync* { |
| if (output == null) { |
| return; |
| } |
| |
| const int processIdHeaderSize = 'ProcessId'.length; |
| const int creationDateHeaderStart = processIdHeaderSize + 1; |
| int creationDateHeaderEnd; |
| int commandLineHeaderStart; |
| bool inTableBody = false; |
| for (final String line in output.split('\n')) { |
| if (line.startsWith('ProcessId')) { |
| commandLineHeaderStart = line.indexOf('CommandLine'); |
| creationDateHeaderEnd = commandLineHeaderStart - 1; |
| } |
| if (line.startsWith('--------- ------------')) { |
| inTableBody = true; |
| continue; |
| } |
| if (!inTableBody || line.isEmpty) { |
| continue; |
| } |
| if (line.length < commandLineHeaderStart) { |
| continue; |
| } |
| |
| // 3/11/2019 11:01:54 AM |
| // 12/11/2019 11:01:54 AM |
| String rawTime = line.substring( |
| creationDateHeaderStart, |
| creationDateHeaderEnd, |
| ).trim(); |
| |
| if (rawTime[1] == '/') { |
| rawTime = '0$rawTime'; |
| } |
| if (rawTime[4] == '/') { |
| rawTime = rawTime.substring(0, 3) + '0' + rawTime.substring(3); |
| } |
| final String year = rawTime.substring(6, 10); |
| final String month = rawTime.substring(3, 5); |
| final String day = rawTime.substring(0, 2); |
| String time = rawTime.substring(11, 19); |
| if (time[7] == ' ') { |
| time = '0$time'.trim(); |
| } |
| if (rawTime.endsWith('PM')) { |
| final int hours = int.parse(time.substring(0, 2)); |
| time = '${hours + 12}${time.substring(2)}'; |
| } |
| |
| final String pid = line.substring(0, processIdHeaderSize).trim(); |
| final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time'); |
| final String commandLine = line.substring(commandLineHeaderStart).trim(); |
| yield RunningProcessInfo(pid, creationDate, commandLine); |
| } |
| } |
| |
| @visibleForTesting |
| Stream<RunningProcessInfo> posixRunningProcesses( |
| String processName, |
| ProcessManager processManager, |
| ) async* { |
| // Cirrus is missing this in Linux for some reason. |
| if (!processManager.canRun('ps')) { |
| print('Cannot list processes on this system: `ps` not available.'); |
| return; |
| } |
| final ProcessResult result = await processManager.run(<String>[ |
| 'ps', |
| '-eo', |
| 'lstart,pid,command', |
| ]); |
| if (result.exitCode != 0) { |
| print('Could not list processes!'); |
| print(result.stderr); |
| print(result.stdout); |
| return; |
| } |
| for (final RunningProcessInfo info in processPsOutput(result.stdout as String, processName)) { |
| yield info; |
| } |
| } |
| |
| /// Parses the output of the command in [posixRunningProcesses]. |
| /// |
| /// E.g.: |
| /// |
| /// STARTED PID COMMAND |
| /// Sat Mar 9 20:12:47 2019 1 /sbin/launchd |
| /// Sat Mar 9 20:13:00 2019 49 /usr/sbin/syslogd |
| @visibleForTesting |
| Iterable<RunningProcessInfo> processPsOutput( |
| String output, |
| String processName, |
| ) sync* { |
| if (output == null) { |
| return; |
| } |
| bool inTableBody = false; |
| for (String line in output.split('\n')) { |
| if (line.trim().startsWith('STARTED')) { |
| inTableBody = true; |
| continue; |
| } |
| if (!inTableBody || line.isEmpty) { |
| continue; |
| } |
| |
| if (processName != null && !line.contains(processName)) { |
| continue; |
| } |
| if (line.length < 25) { |
| continue; |
| } |
| |
| // 'Sat Feb 16 02:29:55 2019' |
| // 'Sat Mar 9 20:12:47 2019' |
| const Map<String, String> months = <String, String>{ |
| 'Jan': '01', |
| 'Feb': '02', |
| 'Mar': '03', |
| 'Apr': '04', |
| 'May': '05', |
| 'Jun': '06', |
| 'Jul': '07', |
| 'Aug': '08', |
| 'Sep': '09', |
| 'Oct': '10', |
| 'Nov': '11', |
| 'Dec': '12', |
| }; |
| final String rawTime = line.substring(0, 24); |
| |
| final String year = rawTime.substring(20, 24); |
| final String month = months[rawTime.substring(4, 7)]; |
| final String day = rawTime.substring(8, 10).replaceFirst(' ', '0'); |
| final String time = rawTime.substring(11, 19); |
| |
| final DateTime creationDate = DateTime.parse('$year-$month-${day}T$time'); |
| line = line.substring(24).trim(); |
| final int nextSpace = line.indexOf(' '); |
| final String pid = line.substring(0, nextSpace); |
| final String commandLine = line.substring(nextSpace + 1); |
| yield RunningProcessInfo(pid, creationDate, commandLine); |
| } |
| } |