port_claim/
lib.rs

1use std::io::{self, Error, ErrorKind};
2use std::net::TcpListener;
3use std::net::{IpAddr, Ipv4Addr, SocketAddr};
4use std::process::Command as ProcessCommand;
5
6/// Checks if a port is available.
7///
8/// Returns true if the port is available (not in use), false otherwise.
9/// If verbose is true, prints information about the port state.
10pub fn port_available(port: u16, verbose: bool) -> bool {
11    let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
12
13    match TcpListener::bind(socket_addr) {
14        Ok(_) => {
15            if verbose {
16                println!("Port {} is available", port);
17            }
18            true
19        }
20        Err(_) => {
21            if verbose {
22                println!("Port {} is in use", port);
23            }
24            false
25        }
26    }
27}
28
29/// Kills the process using the given port.
30///
31/// Returns Ok(()) if successful, or an error if the process could not be killed.
32/// If verbose is true, prints information about the process.
33pub fn kill_port_process(port: u16, verbose: bool) {
34    #[cfg(target_os = "windows")]
35    let result = kill_port_process_windows(port, verbose);
36
37    #[cfg(not(target_os = "windows"))]
38    let result = kill_port_process_unix(port, verbose);
39
40    if let Err(e) = result {
41        if verbose {
42            eprintln!("Failed to kill process on port {}: {}", port, e);
43        }
44    }
45}
46
47#[cfg(not(target_os = "windows"))]
48fn kill_port_process_unix(port: u16, verbose: bool) -> io::Result<()> {
49    // Find PID using lsof
50    let lsof_output = ProcessCommand::new("lsof")
51        .args(["-i", &format!(":{}", port), "-t"])
52        .output()?;
53
54    if !lsof_output.status.success() {
55        return Err(Error::new(
56            ErrorKind::Other,
57            "Failed to execute lsof command",
58        ));
59    }
60
61    let pid_str = String::from_utf8_lossy(&lsof_output.stdout)
62        .trim()
63        .to_string();
64
65    if pid_str.is_empty() {
66        return Err(Error::new(
67            ErrorKind::Other,
68            format!("No process found using port {}", port),
69        ));
70    }
71
72    // Kill process
73    let kill_output = ProcessCommand::new("kill")
74        .arg("-9")
75        .arg(&pid_str)
76        .output()?;
77
78    if !kill_output.status.success() {
79        return Err(Error::new(
80            ErrorKind::Other,
81            format!("Failed to kill process with PID {}", pid_str),
82        ));
83    }
84
85    if verbose {
86        println!(
87            "Successfully killed process {} using port {}",
88            pid_str, port
89        );
90    }
91
92    Ok(())
93}
94
95#[cfg(target_os = "windows")]
96fn kill_port_process_windows(port: u16, verbose: bool) -> io::Result<()> {
97    // Find process using port with netstat
98    let netstat_output = ProcessCommand::new("netstat")
99        .args(["-ano", "|", "findstr", &format!(":{}", port)])
100        .output()?;
101
102    if !netstat_output.status.success() {
103        return Err(Error::new(
104            ErrorKind::Other,
105            "Failed to execute netstat command",
106        ));
107    }
108
109    let output = String::from_utf8_lossy(&netstat_output.stdout);
110    let lines: Vec<&str> = output.lines().collect();
111
112    if lines.is_empty() {
113        return Err(Error::new(
114            ErrorKind::Other,
115            format!("No process found using port {}", port),
116        ));
117    }
118
119    // Extract PID from netstat output
120    let last_line = lines[0];
121    let parts: Vec<&str> = last_line.split_whitespace().collect();
122
123    if parts.len() < 5 {
124        return Err(Error::new(
125            ErrorKind::Other,
126            "Failed to parse netstat output",
127        ));
128    }
129
130    let pid_str = parts[4];
131
132    // Kill process
133    let taskkill_output = ProcessCommand::new("taskkill")
134        .args(["/F", "/PID", pid_str])
135        .output()?;
136
137    if !taskkill_output.status.success() {
138        return Err(Error::new(
139            ErrorKind::Other,
140            format!("Failed to kill process with PID {}", pid_str),
141        ));
142    }
143
144    if verbose {
145        println!(
146            "Successfully killed process {} using port {}",
147            pid_str, port
148        );
149    }
150
151    Ok(())
152}