rwmem 0.1.2

rwmem is a Rust library to read from / write to / search on memory of a process.
Documentation
// Copyright (c) 2026 Eray Erdin
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::path;
#[cfg(target_os = "linux")]
use std::{fs, io};

/// Process spawned by OS.
#[derive(Debug, PartialEq)]
#[cfg(target_os = "linux")]
pub struct Process {
    pub(crate) pid: i32,
}

impl Process {
    /// Creates a new process. If it fails, it uses [ProcessError].
    #[cfg(target_os = "linux")]
    pub fn try_new(pid: i32) -> Result<Self, ProcessError> {
        let mem_handle = path::PathBuf::from(format!("/proc/{pid}/mem"));

        if !mem_handle.is_file() {
            return Err(ProcessError::AbsentProcess);
        }

        Ok(Self { pid })
    }

    #[cfg(target_os = "linux")]
    fn mem_path(&self) -> path::PathBuf {
        path::PathBuf::from(format!("/proc/{}/mem", self.pid))
    }

    #[cfg(target_os = "linux")]
    pub(crate) fn mem_file(&self, is_write: bool) -> io::Result<fs::File> {
        if is_write {
            fs::OpenOptions::new().write(true).open(self.mem_path())
        } else {
            fs::OpenOptions::new().read(true).open(self.mem_path())
        }
    }
}

/// Errors for [Process].
#[derive(Debug, PartialEq, Error)]
pub enum ProcessError {
    /// When [Process] with given id does not exist.
    #[error("Process with given id does not exist or you lack permission to view processes.")]
    AbsentProcess,
}

#[cfg(test)]
mod tests {
    use std::{
        env,
        io::{self, BufRead},
        process,
    };

    use super::*;

    struct DummyProcess {
        child: process::Child,
        pub pid: i32,
    }

    impl Drop for DummyProcess {
        fn drop(&mut self) {
            let _ = self.child.kill();
            let _ = self.child.wait();
        }
    }

    #[fixture]
    fn dummy_process() -> DummyProcess {
        let cwd = env::current_dir().expect("Could not get the current working directory.");
        let dummy_process_dir = cwd.join("dummy_process");

        let build_status = process::Command::new("cargo")
            .arg("build")
            .current_dir(&dummy_process_dir)
            .status()
            .expect("Failed to run dummy_process compilation.");
        assert!(build_status.success(), "Failed to build dummy_process");

        let bin_path = dummy_process_dir.join("target/debug/dummy_process");
        let mut child = process::Command::new(bin_path)
            .stdout(process::Stdio::piped())
            .spawn()
            .expect("Could not spawn dummy_process.");

        let port = {
            let stdout = child.stdout.as_mut().expect("Failed to get stdout.");
            let reader = io::BufReader::new(stdout);
            let port_line = reader
                .lines()
                .next()
                .expect("Port line does not exist.")
                .expect("Could not read port line of stdout.");
            port_line
                .trim()
                .split(":")
                .last()
                .expect("Could not read port part from stdout.")
                .parse::<u16>()
                .expect("Could not parse port from stdout.")
        };

        let pid = {
            let body = reqwest::blocking::get(format!("http://127.0.0.1:{port}/pid"))
                .expect("Could not request PID of dummy process.");
            body.text()
                .expect("Could not decode the charset of PID of dummy process.")
                .parse::<i32>()
                .expect("Could not parse PID of dummy process")
        };

        DummyProcess { child, pid }
    }

    #[rstest]
    #[cfg(target_os = "linux")]
    fn test_absent_process() {
        let process_id = i32::MAX;
        let process = Process::try_new(process_id);
        assert_eq!(process, Err(ProcessError::AbsentProcess));
    }

    #[rstest]
    #[cfg(target_os = "linux")]
    fn test_present_process(dummy_process: DummyProcess) {
        let process = Process::try_new(dummy_process.pid);
        assert_eq!(
            process,
            Ok(Process {
                pid: dummy_process.pid,
            })
        );
    }
}