#![deny(unsafe_code)]
#![allow(non_snake_case)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::bool_comparison)]
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use windows_sys::core::GUID;
use windows_sys::Win32::UI::Shell::FOLDERID_Profile;
use windows_sys::Win32::UI::Shell::FOLDERID_ProgramData;
const PROGRAM_VERSION: &str = env!("CARGO_PKG_VERSION");
const PROGRAM_FLAGS: &str = "h?vzwd";
#[cfg(not(target_os = "windows"))]
fn main() -> ExitCode {
eprintln!("This program is supported only on Windows.");
ExitCode::From(3)
}
fn do_help() {
println!("i2pd-launch [ -z ] | [ -d ] [ -w ] | [ -h | -v | -? ]");
println!("Clear i2pd local router state and launch it");
println!();
println!(" -h | -? display this help message");
println!(" -v print program version and exit");
println!(" -z do not start i2pd only clear sensitive information");
println!(" -w wait for i2pd to exit and then clear sensitive information again");
println!(" -d do not spawn into background while waiting for i2pd to exit");
println!();
println!("This is free and unencumbered software released into the public domain.");
}
#[cfg(target_os = "windows")]
fn main() -> ExitCode {
let g = getopt3::new(getopt3::hideBin(std::env::args()), PROGRAM_FLAGS).unwrap();
if g.has('v') {
println!("{}", PROGRAM_VERSION);
ExitCode::SUCCESS
} else if g.has('h') || g.has('?') {
do_help();
ExitCode::SUCCESS
} else {
let (global_i2pd, user_i2pd) = detect_scoop_i2pd_installations();
if !(global_i2pd || user_i2pd) {
eprintln!("Error: no scoop i2pd installation detected.");
ExitCode::from(1)
} else {
println!(
"scoop i2pd installation detected: global {}, user {}.",
global_i2pd, user_i2pd
);
if global_i2pd && user_i2pd {
println!(concat!(
"both global and user i2pd installations detected,",
" using user i2pd."
));
};
let workdir = {
if user_i2pd {
get_scoop_i2pd_dir(FOLDERID_Profile)
} else {
get_scoop_i2pd_dir(FOLDERID_ProgramData)
}
};
let mut wipeOP = wipe_workdir!(&workdir);
if !g.has('z') {
let exefile = {
let mut p = PathBuf::from(&workdir);
p.push(I2PD_EXE);
p
};
if !exefile.exists() {
eprintln!(
concat!("Error: {} not exists /", " might have been deleted by antivirus."),
I2PD_EXE
);
return ExitCode::from(2);
}
let mut semaphore = sem::NULL_HANDLE;
let semopen = sem::create(1, 1)
.map(|hnd| {
semaphore = hnd;
hnd
})
.map_err(|e| {
eprintln!("Error: Can not open semaphore: {}", e);
e
});
if semopen.is_err() {
return ExitCode::from(7);
}
let mut reacq_fail = false;
let semacq = semopen.and_then(|sem| sem::acquire(sem, 1000)).map_err(|e| {
eprintln!("Error: Can not acquire semaphore, other instance may be running. {}", e);
e
});
if semacq.is_err() {
return ExitCode::from(8);
}
if g.has('w') && !g.has('d') {
let my_exe: Option<PathBuf> = std::env::current_exe()
.map_err(|e| {
eprintln!("Error: current_exe() failed: {}", e);
e
})
.ok()
.or_else(|| {
std::env::args()
.next()
.or_else(|| {
eprintln!("Error: argv[0] fallback returned no executable name.");
None
})
.map(|p| PathBuf::from(p))
});
if my_exe
.map(|me| {
println!("Respawning to background.");
me
})
.and_then(|me| sem::release(semaphore).map(|_| me).ok())
.and_then(|me| {
spawn::background_detached(&me)
.map_err(|e| {
eprintln!("Error: self respawning to background failed: {}", e);
e
})
.map_err(|e| {
let _ = sem::acquire(semaphore, 1000).map_err(|e2| {
eprintln!("Error: can not re-acquire semaphore: {}", e2);
reacq_fail = true;
e2
});
e
})
.ok()
})
.is_some()
{
return ExitCode::SUCCESS;
}
if reacq_fail {
return ExitCode::from(8);
}
};
if let Err(e) = std::env::set_current_dir(&workdir) {
eprintln!(
concat!(
"Error: Failed to change working directory",
" to scoop i2pd installation: {}"
),
e
);
return ExitCode::from(6);
}
println!("starting {}", I2PD_EXE);
match std::process::Command::new(I2PD_EXE).spawn() {
Err(e) => {
eprintln!("Error: Failed to launch i2pd: {}", e);
return ExitCode::from(5);
}
Ok(mut pid) if g.has('w') => {
println!("Waiting for {} to exit.", I2PD_EXE);
let exitcode = pid.wait();
if let Err(e) = exitcode {
eprintln!("Error: Failed to wait for i2pd: {}", e);
return ExitCode::from(5);
} else if let Ok(code) = exitcode {
println!("{} exited with code: {}.", I2PD_EXE, code.code().unwrap_or(137));
}
wipeOP = wipe_workdir!(&workdir);
}
Ok(_) => (),
};
}
if wipeOP == true {
ExitCode::SUCCESS
} else {
ExitCode::from(4)
}
}
}
}
fn get_scoop_i2pd_dir(folder: GUID) -> PathBuf {
let mut f = dirs_sys::known_folder(folder).unwrap();
f.push(SCOOP_I2PD_INSTALL);
f
}
fn detect_scoop_i2pd(search: impl AsRef<Path>) -> bool {
#[cfg(debug_assertions)]
println!("Testing {}", search.as_ref().display());
let mut p = PathBuf::from(search.as_ref());
if p.is_dir() {
p.push(SCOOP_I2PD_INSTALL);
#[cfg(debug_assertions)]
println!("Testing2 {}", p.display());
p.is_dir()
} else {
false
}
}
fn wipe_files(workdir: &Path) -> bool {
let mut TO_RETRY = Vec::new();
for file in TO_DELETE.iter() {
let file_path = workdir.join(file);
if file_path.exists() {
let rc = wipe_and_delete!(&file_path);
if rc.is_err() {
eprintln!("Can not delete {}, will retry.", file_path.display());
TO_RETRY.push(file_path);
}
}
}
let mut RETRY_COUNTER = RETRY_MAX;
while !TO_RETRY.is_empty() && RETRY_COUNTER > 0 {
std::thread::sleep(std::time::Duration::from_secs(RETRY_WAIT));
RETRY_COUNTER -= 1;
TO_RETRY.retain(|file_path| {
if file_path.exists() {
match wipe_and_delete!(file_path) {
Ok(()) => {
eprintln!("Successfully deleted {}", file_path.display());
false }
Err(_) => {
eprintln!("Still cannot delete {}", file_path.display());
true }
}
} else {
false }
});
}
TO_RETRY.is_empty()
}
fn detect_scoop_i2pd_installations() -> (bool, bool) {
let global_i2pd = dirs_sys::known_folder(FOLDERID_ProgramData).map_or(false, |f| detect_scoop_i2pd(&f));
let user_i2pd = dirs_sys::known_folder(FOLDERID_Profile).map_or(false, |f| detect_scoop_i2pd(&f));
(global_i2pd, user_i2pd)
}
const TO_DELETE: [&str; 10] = [
"i2pd.log",
"ntcp2.keys",
"ssu2.keys",
"router.info",
"router.keys",
"http-proxy-keys.dat",
"socks-proxy-keys.dat",
"irc-keys.dat",
"smtp-keys.dat",
"pop3-keys.dat",
];
const RETRY_MAX: usize = 17;
const RETRY_WAIT: u64 = 3;
const SCOOP_I2PD_INSTALL: &str = "scoop\\apps\\i2pd\\current";
const I2PD_EXE: &str = "i2pd.exe";
mod wipe;
mod spawn;
mod sem;