| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| use napi_derive::napi; | |
| /// Returns an opaque identifier for one OS process lifetime. Unlike a command | |
| /// line, this value cannot be forged accidentally by an argument or path. | |
| #[napi] | |
| pub fn read_process_start_identity(pid: u32) -> Option<String> { | |
| if pid == 0 { | |
| return None; | |
| } | |
| read_platform_process_start_identity(pid) | |
| } | |
| #[cfg(target_os = "linux")] | |
| fn read_platform_process_start_identity(pid: u32) -> Option<String> { | |
| let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?; | |
| let boot_id = std::fs::read_to_string("/proc/sys/kernel/random/boot_id").ok()?; | |
| let boot_id = boot_id.trim(); | |
| if boot_id.is_empty() | |
| || !boot_id | |
| .bytes() | |
| .all(|byte| byte.is_ascii_hexdigit() || byte == b'-') | |
| { | |
| return None; | |
| } | |
| let start_ticks = linux_process_start_ticks(&stat)?; | |
| Some(format!("linux:{boot_id}:{start_ticks}")) | |
| } | |
| #[cfg(target_os = "linux")] | |
| fn linux_process_start_ticks(stat: &str) -> Option<&str> { | |
| // `comm` (field 2) may contain spaces and parentheses. The final `)` is | |
| // the only safe boundary before field 3; starttime is field 22. | |
| let mut fields = stat.get(stat.rfind(')')? + 1..)?.split_ascii_whitespace(); | |
| let start_ticks = fields.nth(19)?; | |
| (!start_ticks.is_empty() && start_ticks.bytes().all(|byte| byte.is_ascii_digit())) | |
| .then_some(start_ticks) | |
| } | |
| #[cfg(target_os = "macos")] | |
| fn read_platform_process_start_identity(pid: u32) -> Option<String> { | |
| use std::mem::{MaybeUninit, size_of}; | |
| const PROC_PIDTBSDINFO: i32 = 3; | |
| #[repr(C)] | |
| struct ProcBsdInfo { | |
| pbi_flags: u32, | |
| pbi_status: u32, | |
| pbi_xstatus: u32, | |
| pbi_pid: u32, | |
| pbi_ppid: u32, | |
| pbi_uid: u32, | |
| pbi_gid: u32, | |
| pbi_ruid: u32, | |
| pbi_rgid: u32, | |
| pbi_svuid: u32, | |
| pbi_svgid: u32, | |
| rfu_1: u32, | |
| pbi_comm: [u8; 16], | |
| pbi_name: [u8; 32], | |
| pbi_nfiles: u32, | |
| pbi_pgid: u32, | |
| pbi_pjobc: u32, | |
| e_tdev: u32, | |
| e_tpgid: u32, | |
| pbi_nice: i32, | |
| pbi_start_tvsec: u64, | |
| pbi_start_tvusec: u64, | |
| } | |
| unsafe extern "C" { | |
| fn proc_pidinfo( | |
| pid: i32, | |
| flavor: i32, | |
| arg: u64, | |
| buffer: *mut core::ffi::c_void, | |
| buffer_size: i32, | |
| ) -> i32; | |
| } | |
| let mut info = MaybeUninit::<ProcBsdInfo>::zeroed(); | |
| let expected_size = size_of::<ProcBsdInfo>(); | |
| // SAFETY: `info` points to writable storage of exactly the size passed to | |
| // libproc. The value is read only when libproc reports a complete record. | |
| let read_size = unsafe { | |
| proc_pidinfo( | |
| pid as i32, | |
| PROC_PIDTBSDINFO, | |
| 0, | |
| info.as_mut_ptr().cast(), | |
| expected_size as i32, | |
| ) | |
| }; | |
| if read_size != expected_size as i32 { | |
| return None; | |
| } | |
| // SAFETY: the successful full-size call initialized every byte. | |
| let info = unsafe { info.assume_init() }; | |
| if info.pbi_pid != pid || info.pbi_start_tvsec == 0 || info.pbi_start_tvusec >= 1_000_000 { | |
| return None; | |
| } | |
| Some(format!( | |
| "darwin:{}:{}", | |
| info.pbi_start_tvsec, info.pbi_start_tvusec | |
| )) | |
| } | |
| #[cfg(target_os = "windows")] | |
| fn read_platform_process_start_identity(pid: u32) -> Option<String> { | |
| use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; | |
| use windows::Win32::{ | |
| Foundation::{FILETIME, HANDLE}, | |
| System::Threading::{GetProcessTimes, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION}, | |
| }; | |
| // SAFETY: the returned valid handle is immediately placed under RAII. | |
| let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }.ok()?; | |
| // SAFETY: ownership of the newly opened handle is transferred exactly once. | |
| let owned = unsafe { OwnedHandle::from_raw_handle(handle.0) }; | |
| let handle = HANDLE(owned.as_raw_handle()); | |
| let mut created = FILETIME::default(); | |
| let mut exited = FILETIME::default(); | |
| let mut kernel = FILETIME::default(); | |
| let mut user = FILETIME::default(); | |
| // SAFETY: the handle remains live and all four output pointers are valid. | |
| unsafe { GetProcessTimes(handle, &mut created, &mut exited, &mut kernel, &mut user) }.ok()?; | |
| let ticks = (u64::from(created.dwHighDateTime) << 32) | u64::from(created.dwLowDateTime); | |
| (ticks != 0).then(|| format!("windows:{ticks}")) | |
| } | |
| #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] | |
| fn read_platform_process_start_identity(_pid: u32) -> Option<String> { | |
| None | |
| } | |
| #[cfg(test)] | |
| mod common_tests { | |
| use super::read_process_start_identity; | |
| #[test] | |
| fn current_process_identity_is_stable() { | |
| let first = read_process_start_identity(std::process::id()); | |
| assert!(first.is_some()); | |
| assert_eq!(read_process_start_identity(std::process::id()), first); | |
| } | |
| } | |
| #[cfg(all(test, target_os = "linux"))] | |
| mod linux_tests { | |
| use super::linux_process_start_ticks; | |
| #[test] | |
| fn parses_start_ticks_after_a_hostile_process_name() { | |
| let stat = "42 (workspace --startup-attempt-id 00000000-0000-4000-8000-000000000001) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 98765 20"; | |
| assert_eq!(linux_process_start_ticks(stat), Some("98765")); | |
| } | |
| } |