Skip to content

Commit 209db01

Browse files
committed
Add powershell detection
1 parent 0f8c369 commit 209db01

File tree

6 files changed

+240
-7
lines changed

6 files changed

+240
-7
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ home = "0.5"
5555
ignore = "0.4"
5656
# Dependency graphing
5757
krates = { version = "0.17.1", features = ["metadata"] }
58+
# Parent process retrieval
59+
libc = "0.2"
5860
# Logging macros
5961
log = "0.4"
6062
# Better heap allocator over system one (usually)

src/bindings.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
output = "win_bindings.rs"
2+
binds = [
3+
"MAX_PATH",
4+
"NtQueryInformationProcess",
5+
"OpenProcess",
6+
"ProcessBasicInformation",
7+
"ProcessImageFileName",
8+
"PROCESS_BASIC_INFORMATION",
9+
"PROCESS_QUERY_INFORMATION",
10+
"STATUS_SUCCESS",
11+
"UNICODE_STRING",
12+
]
13+
14+
[bind-mode]
15+
mode = "minwin"
16+
17+
[bind-mode.config]
18+
enum-style = "minwin"
19+
fix-naming = true
20+
use-rust-casing = true
21+
linking-style = "raw-dylib"

src/cargo-about/generate.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ pub fn cmd(args: Args, color: crate::Color) -> anyhow::Result<()> {
170170
"handlebars template(s) must be specified when using handlebars output format"
171171
);
172172

173+
// Check if the parent process is powershell, if it is, assume that it will
174+
// screw up the output https://github.com/EmbarkStudios/cargo-about/issues/198
175+
// and inform the user about the -o, --output-file option
176+
let redirect_stdout =
177+
args.output_file.is_none() || args.output_file.as_deref() == Some(Path::new("-"));
178+
if redirect_stdout {
179+
anyhow::ensure!(!cargo_about::is_powershell_parent(), "cargo-about should not redirect its output in powershell, please use the -o, --output-file option to redirect to a file to avoid powershell encoding issues");
180+
}
181+
173182
rayon::scope(|s| {
174183
s.spawn(|_| {
175184
log::info!("gathering crates for {manifest_path}");
@@ -289,13 +298,11 @@ pub fn cmd(args: Args, color: crate::Color) -> anyhow::Result<()> {
289298
serde_json::to_string(&input)?
290299
};
291300

292-
match args.output_file.as_ref() {
293-
None => println!("{output}"),
294-
Some(path) if path == Path::new("-") => println!("{output}"),
295-
Some(path) => {
296-
std::fs::write(path, output)
297-
.with_context(|| format!("output file {path} could not be written"))?;
298-
}
301+
if let Some(path) = &args.output_file.filter(|_| !redirect_stdout) {
302+
std::fs::write(path, output)
303+
.with_context(|| format!("output file {path} could not be written"))?;
304+
} else {
305+
println!("{output}");
299306
}
300307

301308
Ok(())

src/lib.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,107 @@ pub fn validate_sha256(buffer: &str, expected: &str) -> anyhow::Result<()> {
216216

217217
Ok(())
218218
}
219+
220+
#[cfg(target_family = "unix")]
221+
#[allow(unsafe_code)]
222+
pub fn is_powershell_parent() -> bool {
223+
if !cfg!(target_os = "linux") {
224+
// Making the assumption that no one on MacOS or any of the *BSDs uses powershell...
225+
return false;
226+
}
227+
228+
// SAFETY: no invariants to uphold
229+
let parent_id = unsafe { libc::getppid() };
230+
let Ok(cmd) = std::fs::read_to_string(format!("/proc/{parent_id}/cmdline")) else {
231+
return false;
232+
};
233+
234+
let Some(proc) = cmd
235+
.split('\0')
236+
.nth(0)
237+
.and_then(|path| path.split('/').last())
238+
else {
239+
return false;
240+
};
241+
dbg!(proc) == "pwsh"
242+
}
243+
244+
#[cfg(target_family = "windows")]
245+
mod win_bindings;
246+
247+
#[cfg(target_family = "windows")]
248+
#[allow(unsafe_code)]
249+
pub fn is_powershell_parent() -> bool {
250+
use std::os::windows::ffi::OsStringExt as _;
251+
use win_bindings::*;
252+
253+
unsafe {
254+
let mut basic_info = std::mem::MaybeUninit::<ProcessBasicInformation>::uninit();
255+
let mut length = 0;
256+
if nt_query_information_process(
257+
-1, /* NtCurrentProcess */
258+
Processinfoclass::ProcessBasicInformation,
259+
basic_info.as_mut_ptr().cast(),
260+
std::mem::size_of::<ProcessBasicInformation>() as _,
261+
&mut length,
262+
) != StatusSuccess
263+
{
264+
return false;
265+
}
266+
267+
if length != std::mem::size_of::<ProcessBasicInformation>() as u32 {
268+
return false;
269+
}
270+
271+
let basic_info = basic_info.assume_init();
272+
273+
// The API for this is extremely irritating, the struct and string buffer
274+
// need to be the same :/
275+
let mut file_name = [0u16; MaxPath as usize + std::mem::size_of::<UnicodeString>() / 2];
276+
{
277+
let ustr = &mut *file_name.as_mut_ptr().cast::<UnicodeString>();
278+
ustr.length = 0;
279+
ustr.maximum_length = MaxPath as _;
280+
ustr.buffer = file_name
281+
.as_mut_ptr()
282+
.byte_offset(std::mem::size_of::<UnicodeString>() as _);
283+
}
284+
285+
if nt_query_information_process(
286+
basic_info.inherited_from_unique_process_id as _,
287+
Processinfoclass::ProcessImageFileName,
288+
file_name.as_mut_ptr().cast(),
289+
(file_name.len() * 2) as _,
290+
&mut length,
291+
) != StatusSuccess
292+
{
293+
return false;
294+
}
295+
296+
let ustr = &*file_name.as_ptr().cast::<UnicodeString>();
297+
let os = std::ffi::OsString::from_wide(
298+
&file_name[std::mem::size_of::<UnicodeString>() * 2
299+
..std::mem::size_of::<UnicodeString>() * 2 + ustr.length as usize],
300+
);
301+
302+
let path = os.to_string_lossy();
303+
304+
dbg!(&path);
305+
false
306+
}
307+
}
308+
309+
#[cfg(test)]
310+
mod test {
311+
#[test]
312+
#[ignore = "call when actually run from powershell"]
313+
fn is_powershell_true() {
314+
assert!(super::is_powershell_parent());
315+
}
316+
317+
#[test]
318+
#[ignore = "call when not actually run from powershell"]
319+
fn is_powershell_false() {
320+
assert!(!super::is_powershell_parent());
321+
}
322+
}

src/win_bindings.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! Bindings generated by `minwin` 0.1.0
2+
#![allow(
3+
non_snake_case,
4+
non_upper_case_globals,
5+
non_camel_case_types,
6+
clippy::upper_case_acronyms
7+
)]
8+
#[link(name = "kernel32", kind = "raw-dylib")]
9+
extern "system" {
10+
#[link_name = "OpenProcess"]
11+
pub fn open_process(
12+
desired_access: ProcessAccessRights::Enum,
13+
inherit_handle: Bool,
14+
process_id: u32,
15+
) -> Handle;
16+
}
17+
#[link(name = "ntdll", kind = "raw-dylib")]
18+
extern "system" {
19+
#[link_name = "NtQueryInformationProcess"]
20+
pub fn nt_query_information_process(
21+
process_handle: Handle,
22+
process_information_class: Processinfoclass::Enum,
23+
process_information: *mut ::core::ffi::c_void,
24+
process_information_length: u32,
25+
return_length: *mut u32,
26+
) -> Ntstatus;
27+
}
28+
pub const MaxPath: u32 = 260;
29+
pub type Bool = i32;
30+
pub type Handle = isize;
31+
#[repr(C)]
32+
pub struct ListEntry {
33+
pub flink: *mut ListEntry,
34+
pub blink: *mut ListEntry,
35+
}
36+
pub type Ntstatus = i32;
37+
pub const StatusSuccess: Ntstatus = 0;
38+
#[repr(C)]
39+
pub struct Peb {
40+
pub reserved1: [u8; 2],
41+
pub being_debugged: u8,
42+
pub reserved2: [u8; 1],
43+
pub reserved3: [*mut ::core::ffi::c_void; 2],
44+
pub ldr: *mut PebLdrData,
45+
pub process_parameters: *mut RtlUserProcessParameters,
46+
pub reserved4: [*mut ::core::ffi::c_void; 3],
47+
pub atl_thunk_s_list_ptr: *mut ::core::ffi::c_void,
48+
pub reserved5: *mut ::core::ffi::c_void,
49+
pub reserved6: u32,
50+
pub reserved7: *mut ::core::ffi::c_void,
51+
pub reserved8: u32,
52+
pub atl_thunk_s_list_ptr32: u32,
53+
pub reserved9: [*mut ::core::ffi::c_void; 45],
54+
pub reserved10: [u8; 96],
55+
pub post_process_init_routine: PpsPostProcessInitRoutine,
56+
pub reserved11: [u8; 128],
57+
pub reserved12: [*mut ::core::ffi::c_void; 1],
58+
pub session_id: u32,
59+
}
60+
#[repr(C)]
61+
pub struct PebLdrData {
62+
pub reserved1: [u8; 8],
63+
pub reserved2: [*mut ::core::ffi::c_void; 3],
64+
pub in_memory_order_module_list: ListEntry,
65+
}
66+
pub type PpsPostProcessInitRoutine = ::core::option::Option<unsafe extern "system" fn()>;
67+
pub mod ProcessAccessRights {
68+
pub type Enum = u32;
69+
pub const ProcessQueryInformation: Enum = 1024;
70+
}
71+
#[repr(C)]
72+
pub struct ProcessBasicInformation {
73+
pub exit_status: Ntstatus,
74+
pub peb_base_address: *mut Peb,
75+
pub affinity_mask: usize,
76+
pub base_priority: i32,
77+
pub unique_process_id: usize,
78+
pub inherited_from_unique_process_id: usize,
79+
}
80+
pub mod Processinfoclass {
81+
pub type Enum = i32;
82+
pub const ProcessBasicInformation: Enum = 0;
83+
pub const ProcessImageFileName: Enum = 27;
84+
}
85+
pub type Pwstr = *mut u16;
86+
#[repr(C)]
87+
pub struct RtlUserProcessParameters {
88+
pub reserved1: [u8; 16],
89+
pub reserved2: [*mut ::core::ffi::c_void; 10],
90+
pub image_path_name: UnicodeString,
91+
pub command_line: UnicodeString,
92+
}
93+
#[repr(C)]
94+
pub struct UnicodeString {
95+
pub length: u16,
96+
pub maximum_length: u16,
97+
pub buffer: Pwstr,
98+
}

0 commit comments

Comments
 (0)