ctrlc-wrapper
Version:
Wrapper enabling to send CTRL+C signal to child process
81 lines (51 loc) • 3.14 kB
Markdown
# ctrlc-wrapper
Windows doesn't support sending signals to other processes as it is possible on POSIX platforms.
Using kill methods on Windows (like `process.kill()` within Node.js) means the target process is getting killed forcefully and abruptly (similar to `SIGKILL`).
However, in a console, processes can be terminated with the `CTRL`+`C` key combination.
Most programming languages do have an implementation to capture this signal (usually as `SIGINT`), allowing applications to handle it and to terminate "gracefully".
The problem is that the `CTRL`+`C` key combination cannot be easily simulated for the following reasons:
- In order to be able to generate a CTRL+C signal programmatically, several [Console Functions](https://docs.microsoft.com/en-us/windows/console/console-functions) need to be called - something which can only be done in lower-level programming languages.
- The process which should receive the CTRL+C signal needs to live in its own console since the CTRL+C signal is sent to all processes attached to that console. Spawning a process in a new console, again, is something which is only possible in lower-level programming languages.
This wrapper application does exactly the points described above.
The wrapper inherits `stdout`, `stderr` and the exit code from the child process and forwards `stdin` to it. If there's an error with the wrapper itself, the exit code is `-1`.
## Usage
```console
npm install ctrlc-wrapper --save-dev
```
```js
import { spawnWithWrapper } from 'ctrlc-wrapper';
const child = spawnWithWrapper('node test/read-echo.js');
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
if (/READING/.test(data)) {
child.sendCtrlC();
}
});
child.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
child.on('close', (code, signal) => {
console.log(`close: ${code} ${signal}`);
});
```
## Test
To start the process `node test/read-echo.js` with the wrapper:
```console
go run ./cmd/start node test/read-echo.js
```
To terminate:
- Press `CTRL`+`C`
- Write `^C` to `stdin` (captured by the wrapper)
- Exit from within the child
## Build
```console
pnpm build
```
## Notes
### Why the separate `ctrlc.exe` binary?
It would be possible to send the CTRL+C signal directly from within `start.exe` but that would require an additional process to be spawned (e.g. `cmd /c pause`) in order to prevent losing the original (parent) console during the console switch (`FreeConsole` -> `AttachConsole`). Using a separate binary to send the CTRL+C signal is much safer.
### `CREATE_NEW_CONSOLE` vs. `CREATE_NEW_PROCESS_GROUP`
Both methods seem to protect from receiving the CTRL+C signal in the current console.
However, spawning the child with `CREATE_NEW_PROCESS_GROUP` would mean that we need to start another "wrapper" in which the "normal processing of CTRL+C input" is enabled first (via `SetConsoleCtrlHandler`) before starting the actual child process.
### `CreateRemoteThread`
Instead of `ctrlc.exe` we might be able to terminate the target process by injecting a thread into it, but this seems to be overly complicated...