Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/node/processes.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as cp from 'child_process';
7
import { Stats, promises } from 'fs';
8
import { getCaseInsensitive } from '../common/objects.js';
9
import * as path from '../common/path.js';
10
import * as Platform from '../common/platform.js';
11
import * as processCommon from '../common/process.js';
12
import { CommandOptions, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from '../common/processes.js';
13
import * as Types from '../common/types.js';
14
import * as pfs from './pfs.js';
15
import { FileAccess } from '../common/network.js';
16
import Stream from 'stream';
17
export { Source, TerminateResponseCode, type CommandOptions, type ForkOptions, type SuccessData, type TerminateResponse };
18
19
export type ValueCallback<T> = (value: T | Promise<T>) => void;
20
export type ErrorCallback = (error?: any) => void;
21
export type ProgressCallback<T> = (progress: T) => void;
22
23
24
export function getWindowsShell(env = processCommon.env as Platform.IProcessEnvironment): string {
25
return env['comspec'] || 'cmd.exe';
26
}
27
28
export interface IQueuedSender {
29
send: (msg: any) => void;
30
}
31
32
// Wrapper around process.send() that will queue any messages if the internal node.js
33
// queue is filled with messages and only continue sending messages when the internal
34
// queue is free again to consume messages.
35
// On Windows we always wait for the send() method to return before sending the next message
36
// to workaround https://github.com/nodejs/node/issues/7657 (IPC can freeze process)
37
export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender {
38
let msgQueue: string[] = [];
39
let useQueue = false;
40
41
const send = function (msg: any): void {
42
if (useQueue) {
43
msgQueue.push(msg); // add to the queue if the process cannot handle more messages
44
return;
45
}
46
47
const result = childProcess.send(msg, (error: Error | null) => {
48
if (error) {
49
console.error(error); // unlikely to happen, best we can do is log this error
50
}
51
52
useQueue = false; // we are good again to send directly without queue
53
54
// now send all the messages that we have in our queue and did not send yet
55
if (msgQueue.length > 0) {
56
const msgQueueCopy = msgQueue.slice(0);
57
msgQueue = [];
58
msgQueueCopy.forEach(entry => send(entry));
59
}
60
});
61
62
if (!result || Platform.isWindows /* workaround https://github.com/nodejs/node/issues/7657 */) {
63
useQueue = true;
64
}
65
};
66
67
return { send };
68
}
69
70
async function fileExistsDefault(path: string): Promise<boolean> {
71
if (await pfs.Promises.exists(path)) {
72
let statValue: Stats | undefined;
73
try {
74
statValue = await promises.stat(path);
75
} catch (e) {
76
if (e.message.startsWith('EACCES')) {
77
// it might be symlink
78
statValue = await promises.lstat(path);
79
}
80
}
81
return statValue ? !statValue.isDirectory() : false;
82
}
83
return false;
84
}
85
86
export async function findExecutable(command: string, cwd?: string, paths?: string[], env: Platform.IProcessEnvironment = processCommon.env as Platform.IProcessEnvironment, fileExists: (path: string) => Promise<boolean> = fileExistsDefault): Promise<string | undefined> {
87
// If we have an absolute path then we take it.
88
if (path.isAbsolute(command)) {
89
return await fileExists(command) ? command : undefined;
90
}
91
if (cwd === undefined) {
92
cwd = processCommon.cwd();
93
}
94
const dir = path.dirname(command);
95
if (dir !== '.') {
96
// We have a directory and the directory is relative (see above). Make the path absolute
97
// to the current working directory.
98
const fullPath = path.join(cwd, command);
99
return await fileExists(fullPath) ? fullPath : undefined;
100
}
101
const envPath = getCaseInsensitive(env, 'PATH');
102
if (paths === undefined && Types.isString(envPath)) {
103
paths = envPath.split(path.delimiter);
104
}
105
// No PATH environment. Make path absolute to the cwd.
106
if (paths === undefined || paths.length === 0) {
107
const fullPath = path.join(cwd, command);
108
return await fileExists(fullPath) ? fullPath : undefined;
109
}
110
111
// We have a simple file name. We get the path variable from the env
112
// and try to find the executable on the path.
113
for (const pathEntry of paths) {
114
// The path entry is absolute.
115
let fullPath: string;
116
if (path.isAbsolute(pathEntry)) {
117
fullPath = path.join(pathEntry, command);
118
} else {
119
fullPath = path.join(cwd, pathEntry, command);
120
}
121
if (Platform.isWindows) {
122
const pathExt = getCaseInsensitive(env, 'PATHEXT') as string || '.COM;.EXE;.BAT;.CMD';
123
const pathExtsFound = pathExt.split(';').map(async ext => {
124
const withExtension = fullPath + ext;
125
return await fileExists(withExtension) ? withExtension : undefined;
126
});
127
for (const foundPromise of pathExtsFound) {
128
const found = await foundPromise;
129
if (found) {
130
return found;
131
}
132
}
133
}
134
135
if (await fileExists(fullPath)) {
136
return fullPath;
137
}
138
}
139
const fullPath = path.join(cwd, command);
140
return await fileExists(fullPath) ? fullPath : undefined;
141
}
142
143
/**
144
* Kills a process and all its children.
145
* @param pid the process id to kill
146
* @param forceful whether to forcefully kill the process (default: false). Note
147
* that on Windows, terminal processes can _only_ be killed forcefully and this
148
* will throw when not forceful.
149
*/
150
export async function killTree(pid: number, forceful = false) {
151
let child: cp.ChildProcessByStdio<null, Stream.Readable, Stream.Readable>;
152
if (Platform.isWindows) {
153
const windir = process.env['WINDIR'] || 'C:\\Windows';
154
const taskKill = path.join(windir, 'System32', 'taskkill.exe');
155
156
const args = ['/T'];
157
if (forceful) {
158
args.push('/F');
159
}
160
args.push('/PID', String(pid));
161
child = cp.spawn(taskKill, args, { stdio: ['ignore', 'pipe', 'pipe'] });
162
} else {
163
const killScript = FileAccess.asFileUri('vs/base/node/terminateProcess.sh').fsPath;
164
child = cp.spawn('/bin/sh', [killScript, String(pid), forceful ? '9' : '15'], { stdio: ['ignore', 'pipe', 'pipe'] });
165
}
166
167
return new Promise<void>((resolve, reject) => {
168
const stdout: Buffer[] = [];
169
child.stdout.on('data', (data) => stdout.push(data));
170
child.stderr.on('data', (data) => stdout.push(data));
171
child.on('error', reject);
172
child.on('exit', (code) => {
173
if (code === 0) {
174
resolve();
175
} else {
176
reject(new Error(`taskkill exited with code ${code}: ${Buffer.concat(stdout).toString()}`));
177
}
178
});
179
});
180
}
181
182