Skip to content

Commit 89911d5

Browse files
committed
Initial commit of lib
1 parent 0e34067 commit 89911d5

6 files changed

Lines changed: 220 additions & 1 deletion

File tree

.github/workflows/ci.yaml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch:
11+
12+
jobs:
13+
test:
14+
name: Test - ${{ matrix.build }}
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
build:
20+
- linux
21+
- macos
22+
- windows
23+
include:
24+
# Linux
25+
- build: linux
26+
label: linux_x64
27+
os: ubuntu-latest
28+
29+
# Mac
30+
- build: macos
31+
label: macos_arm64
32+
os: macos-latest
33+
34+
# Windows
35+
- build: windows
36+
label: windows_x64
37+
os: windows-latest
38+
39+
steps:
40+
- name: Checkout source code
41+
uses: actions/checkout@v5
42+
43+
- name: Setup Rust
44+
uses: actions-rust-lang/setup-rust-toolchain@v1
45+
with:
46+
components: llvm-tools-preview
47+
48+
- name: Cache Rust
49+
uses: actions/cache@v4
50+
with:
51+
path: |
52+
~/.cargo/registry
53+
~/.cargo/git
54+
crates/target
55+
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('crates/Cargo.lock') }}
56+
restore-keys: |
57+
${{ runner.os }}-cargo-coverage-
58+
59+
- name: Install Rust Tools
60+
uses: taiki-e/install-action@v2
61+
with:
62+
tool: cargo-nextest, cargo-llvm-cov
63+
64+
- name: Run tests
65+
run: cargo test
66+
67+
- name: Run tests with coverage
68+
run: cargo llvm-cov nextest --output-path codecov.json --codecov
69+
70+
- name: Upload coverage reports to Codecov
71+
uses: codecov/codecov-action@v5
72+
with:
73+
files: codecov.json
74+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ target
1919
# and can be added to the global gitignore or merged into this file. For a more nuclear
2020
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
2121
#.idea/
22+
23+
24+
# Added by cargo
25+
26+
/target
27+
28+
.vscode

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "send_ctrlc"
3+
version = "0.1.0"
4+
authors = ["Scott Meeuwsen <smeeuwsen@gmail.com>"]
5+
license = "MIT OR Apache-2.0"
6+
description = "A cross platform crate for sending ctrl-c to child processes"
7+
repository = "https://github.com/nu11ptr/send_ctrlc"
8+
documentation = "https://docs.rs/send_ctrlc"
9+
keywords = ["ctrl-c", "ctrlc", "sigint", "signal"]
10+
categories = ["os"]
11+
readme = "README.md"
12+
13+
[target.'cfg(unix)'.dependencies]
14+
libc = "0.2"
15+
16+
[target.'cfg(windows)'.dependencies]
17+
windows-sys = { version = "0.61", features = ["Win32_System_Console"] }

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
# send_ctrlc
2-
Cross platform crate for sending interrupts/ctrl-c to child processes
2+
3+
[![Crate](https://img.shields.io/crates/v/send_ctrlc)](https://crates.io/crates/send_ctrlc)
4+
[![Docs](https://docs.rs/send_ctrlc/badge.svg)](https://docs.rs/send_ctrlc)
5+
[![Build](https://github.com/nu11ptr/send_ctrlc/workflows/CI/badge.svg)](https://github.com/nu11ptr/send_ctrlc/actions)
6+
[![codecov](https://codecov.io/github/nu11ptr/send_ctrlc/graph/badge.svg?token=3M5tvBewE5)](https://codecov.io/github/nu11ptr/send_ctrlc)
7+
8+
A cross platform crate for sending ctrl-c to child processes

src/lib.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::ffi::OsStr;
2+
use std::io;
3+
use std::process::{Child, Command};
4+
5+
#[cfg(windows)]
6+
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
7+
8+
/// Trait for sending interrupts/ctrl-c to child processes
9+
pub trait Interruptable: InterruptablePid {
10+
#[cfg(all(not(windows), not(unix)))]
11+
fn send_ctrl_c(&self) -> io::Result<()> {
12+
unimplemented!("Not implemented for this platform");
13+
}
14+
15+
#[cfg(unix)]
16+
fn send_ctrl_c(&self) -> io::Result<()> {
17+
if unsafe { libc::kill(self.pid() as i32, libc::SIGINT) } == 0 {
18+
Ok(())
19+
} else {
20+
Err(io::Error::last_os_error())
21+
}
22+
}
23+
24+
#[cfg(windows)]
25+
fn send_ctrl_c(&self) -> io::Result<()> {
26+
use windows_sys::Win32::System::Console::{CTRL_C_EVENT, GenerateConsoleCtrlEvent};
27+
28+
unsafe {
29+
// NOTE: This only works if the process is in a new process group
30+
if GenerateConsoleCtrlEvent(CTRL_C_EVENT, self.pid()) == 0 {
31+
Err(io::Error::last_os_error())
32+
} else {
33+
Ok(())
34+
}
35+
}
36+
}
37+
}
38+
39+
impl<T> Interruptable for T where T: InterruptablePid {}
40+
41+
/// Trait for getting the pid of a child process
42+
pub trait InterruptablePid {
43+
fn pid(&self) -> u32;
44+
}
45+
46+
impl InterruptablePid for Child {
47+
fn pid(&self) -> u32 {
48+
self.id()
49+
}
50+
}
51+
52+
/// Create a new interruptable command
53+
#[cfg(unix)]
54+
pub fn new_interruptable_command<S: AsRef<OsStr>>(program: S) -> Command {
55+
Command::new(program)
56+
}
57+
58+
/// Create a new interruptable command
59+
#[cfg(windows)]
60+
pub fn new_interruptable_command<S: AsRef<OsStr>>(program: S) -> Command {
61+
use std::os::windows::process::CommandExt as _;
62+
63+
let mut command = Command::new(program);
64+
command.creation_flags(CREATE_NEW_PROCESS_GROUP);
65+
command
66+
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
72+
#[test]
73+
fn test_new_interruptable_command() {
74+
let mut command = new_interruptable_command("ping");
75+
#[cfg(windows)]
76+
command.arg("-t");
77+
command.arg("127.0.0.1");
78+
79+
let mut child = command.spawn().unwrap();
80+
child.send_ctrl_c().unwrap();
81+
child.wait().unwrap();
82+
}
83+
}

0 commit comments

Comments
 (0)