Finalized watcher
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
*.log
|
||||||
117
Cargo.lock
generated
117
Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
@ -90,18 +96,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"simplelog",
|
|
||||||
"toml",
|
|
||||||
"widestring",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.227"
|
version = "1.0.227"
|
||||||
@ -152,6 +146,19 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sxp-service-watcher"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"simplelog",
|
||||||
|
"toml",
|
||||||
|
"widestring",
|
||||||
|
"winapi",
|
||||||
|
"windows-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
@ -278,7 +285,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -293,6 +300,26 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-service"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"widestring",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.1"
|
version = "0.61.1"
|
||||||
@ -302,6 +329,70 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rust"
|
name = "sxp-service-watcher"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@ -9,4 +9,5 @@ serde = { version = "1.0.227", features = ["derive"] }
|
|||||||
simplelog = "0.12.2"
|
simplelog = "0.12.2"
|
||||||
toml = "0.9.7"
|
toml = "0.9.7"
|
||||||
widestring = "1.2.0"
|
widestring = "1.2.0"
|
||||||
winapi = "0.3.9"
|
winapi = {version = "0.3.9", features = ["winsvc", "winerror", "winnt", "handleapi", "errhandlingapi", "winbase"]}
|
||||||
|
windows-service = "0.8.0"
|
||||||
|
|||||||
6
config.toml
Normal file
6
config.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
period = 30
|
||||||
|
watch_path = "C:\\Users\\dn\\Desktop\\test"
|
||||||
|
watch_file_prefix = "sv_"
|
||||||
|
dir = "C:\\Users\\dn\\Desktop\\errorlocation"
|
||||||
|
watch_file_size = 0
|
||||||
|
service_name = "gitea"
|
||||||
84
register.ps1
Normal file
84
register.ps1
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# Register SV SXP Service Watcher
|
||||||
|
# Run this script as Administrator
|
||||||
|
|
||||||
|
# Check if running as Administrator
|
||||||
|
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
|
||||||
|
if (-not $isAdmin) {
|
||||||
|
Write-Error "This script must be run as Administrator!"
|
||||||
|
Write-Host "Please right-click PowerShell and select 'Run as Administrator'" -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service configuration
|
||||||
|
$serviceName = "SV SXP Service watcher"
|
||||||
|
$binaryPath = "C:\Users\dn\Desktop\SXP-Service-watcher\sxp-service-watcher.exe"
|
||||||
|
$displayName = "SV SXP Service watcher"
|
||||||
|
$description = "SV SXP Service watcher"
|
||||||
|
$startupType = "Automatic"
|
||||||
|
|
||||||
|
# Check if service already exists
|
||||||
|
$existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($existingService) {
|
||||||
|
Write-Host "Service '$serviceName' already exists." -ForegroundColor Yellow
|
||||||
|
$response = Read-Host "Do you want to remove and recreate it? (y/n)"
|
||||||
|
|
||||||
|
if ($response -eq 'y') {
|
||||||
|
Write-Host "Stopping service..." -ForegroundColor Cyan
|
||||||
|
Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
|
Write-Host "Removing service..." -ForegroundColor Cyan
|
||||||
|
# Use sc.exe for compatibility with Windows PowerShell 5.1
|
||||||
|
$result = sc.exe delete $serviceName
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Failed to remove service: $result"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Write-Host "Service removed successfully." -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Registration cancelled." -ForegroundColor Yellow
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if binary exists
|
||||||
|
if (-not (Test-Path $binaryPath)) {
|
||||||
|
Write-Error "Binary not found at: $binaryPath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register the service
|
||||||
|
try {
|
||||||
|
Write-Host "Registering service '$serviceName'..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
New-Service -Name $serviceName `
|
||||||
|
-BinaryPathName $binaryPath `
|
||||||
|
-DisplayName $displayName `
|
||||||
|
-Description $description `
|
||||||
|
-StartupType $startupType `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
Write-Host "Service registered successfully!" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Ask if user wants to start the service
|
||||||
|
$startService = Read-Host "Do you want to start the service now? (y/n)"
|
||||||
|
|
||||||
|
if ($startService -eq 'y') {
|
||||||
|
Write-Host "Starting service..." -ForegroundColor Cyan
|
||||||
|
Start-Service -Name $serviceName
|
||||||
|
|
||||||
|
$status = Get-Service -Name $serviceName
|
||||||
|
Write-Host "Service status: $($status.Status)" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Error "Failed to register service: $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nService registration complete!" -ForegroundColor Green
|
||||||
90
src/app.rs
90
src/app.rs
@ -1,10 +1,19 @@
|
|||||||
use std::fs;
|
use crate::config::Config;
|
||||||
|
use crate::util::restart_service;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::exit;
|
||||||
|
use std::sync::mpsc;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use crate::config::Config;
|
use std::{fs, sync::mpsc::Receiver};
|
||||||
|
|
||||||
pub fn move_file_to_dir(watch_path_string: &String, filename: &String, move_path_string: &String) -> Result<(), Box<dyn std::error::Error>> {
|
use crate::config::{CONFIG_PATH, ConfigErr, create_default_config, load_config};
|
||||||
|
|
||||||
|
pub fn move_file_to_dir(
|
||||||
|
watch_path_string: &String,
|
||||||
|
filename: &String,
|
||||||
|
move_path_string: &String,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let move_path = Path::new(move_path_string);
|
let move_path = Path::new(move_path_string);
|
||||||
let watch_path = Path::new(watch_path_string);
|
let watch_path = Path::new(watch_path_string);
|
||||||
let file_path = watch_path.join(Path::new(filename));
|
let file_path = watch_path.join(Path::new(filename));
|
||||||
@ -27,17 +36,35 @@ pub fn move_file_to_dir(watch_path_string: &String, filename: &String, move_path
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart_server() {
|
pub fn restart_server(service_name: String) {
|
||||||
|
restart_service(service_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn watch_files(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn watch_files(
|
||||||
|
config: Config,
|
||||||
|
shutdown_rx: Receiver<()>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
loop {
|
loop {
|
||||||
|
// Poll shutdown event.
|
||||||
|
match shutdown_rx.recv_timeout(Duration::from_secs(1)) {
|
||||||
|
// Break the loop either upon stop or channel disconnect
|
||||||
|
Ok(_) | Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||||
|
info!("Received shutdown event. Shutting down...");
|
||||||
|
break
|
||||||
|
},
|
||||||
|
|
||||||
|
// Continue work if no events were received within the timeout
|
||||||
|
Err(mpsc::RecvTimeoutError::Timeout) => (),
|
||||||
|
};
|
||||||
|
|
||||||
let mut has_found = false;
|
let mut has_found = false;
|
||||||
for file in fs::read_dir(Path::new(&config.watch_path))? {
|
for file in fs::read_dir(Path::new(&config.watch_path))? {
|
||||||
if file.is_ok() {
|
if file.is_ok() {
|
||||||
let filename = file.unwrap().file_name().into_string().unwrap();
|
let file_obj = file.unwrap();
|
||||||
if filename.starts_with(&config.watch_file_prefix) {
|
let filename = file_obj.file_name().into_string().unwrap();
|
||||||
|
if filename.starts_with(&config.watch_file_prefix)
|
||||||
|
&& file_obj.metadata().unwrap().len() == config.watch_file_size
|
||||||
|
{
|
||||||
info!("Found file: {}. Moving file.", filename);
|
info!("Found file: {}. Moving file.", filename);
|
||||||
move_file_to_dir(&config.watch_path, &filename, &config.dir)?;
|
move_file_to_dir(&config.watch_path, &filename, &config.dir)?;
|
||||||
has_found = true;
|
has_found = true;
|
||||||
@ -46,10 +73,53 @@ pub fn watch_files(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if has_found {
|
if has_found {
|
||||||
info!("Restarting server");
|
info!("Restarting target service");
|
||||||
restart_server();
|
restart_server(config.service_name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(Duration::from_secs(config.period as u64));
|
sleep(Duration::from_secs(config.period as u64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(shutdown_rx: Receiver<()>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Step 1: Get or create config file
|
||||||
|
let config_res = load_config(CONFIG_PATH);
|
||||||
|
|
||||||
|
if let Err(err) = &config_res {
|
||||||
|
match err {
|
||||||
|
ConfigErr::NotExist => {
|
||||||
|
error!("Failed to find existing config file. Create one.");
|
||||||
|
match create_default_config(CONFIG_PATH) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => error!("Failed to create config file."),
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigErr::WriteError(e) => {
|
||||||
|
error!("Failed to write config file.");
|
||||||
|
error!("Message: {e}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigErr::ParsingError(e) => {
|
||||||
|
error!("Failed to parse config file");
|
||||||
|
error!("Message: {e}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigErr::Unknown(e) => {
|
||||||
|
error!("Unknown error");
|
||||||
|
error!("Message: {e}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch_files(config_res.unwrap(), shutdown_rx)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
use std::env;
|
||||||
use std::fs::{read_to_string, File};
|
use std::fs::{read_to_string, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use serde::*;
|
use serde::*;
|
||||||
@ -30,10 +31,23 @@ pub enum ConfigErr {
|
|||||||
NotExist,
|
NotExist,
|
||||||
ParsingError(String),
|
ParsingError(String),
|
||||||
WriteError(String),
|
WriteError(String),
|
||||||
|
Unknown(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(config_file: &str) -> Result<Config, ConfigErr> {
|
pub fn load_config(config_file: &str) -> Result<Config, ConfigErr> {
|
||||||
let toml_contents = read_to_string(config_file);
|
let exe_path_res = env::current_exe();
|
||||||
|
|
||||||
|
if let Err(e) = &exe_path_res {
|
||||||
|
error!("Failed to get current working directory.");
|
||||||
|
return Err(ConfigErr::Unknown(String::from(format!("{}", e))));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//let exe_path = exe_path_res.unwrap();
|
||||||
|
let binding = exe_path_res.unwrap();
|
||||||
|
let exe_dir = binding.parent().unwrap();
|
||||||
|
let config_path = exe_dir.join(config_file);
|
||||||
|
let toml_contents = read_to_string(config_path);
|
||||||
|
|
||||||
if let Ok(toml_contents) = toml_contents {
|
if let Ok(toml_contents) = toml_contents {
|
||||||
let config = toml::from_str::<Config>(&toml_contents);
|
let config = toml::from_str::<Config>(&toml_contents);
|
||||||
|
|||||||
168
src/main.rs
168
src/main.rs
@ -1,52 +1,150 @@
|
|||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use simplelog::*;
|
|
||||||
|
|
||||||
use crate::app::watch_files;
|
|
||||||
use crate::config::{create_default_config, load_config, ConfigErr, CONFIG_PATH};
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
mod app;
|
mod app;
|
||||||
|
mod config;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
use simplelog::*;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
const LOG_FILE: &str = "sv_file_watcher.log";
|
const LOG_FILE: &str = "sv_file_watcher.log";
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
#[cfg(windows)]
|
||||||
CombinedLogger::init(
|
fn main() -> windows_service::Result<()> {
|
||||||
vec![
|
let current_exe = std::env::current_exe().expect("Failed to get current working directory");
|
||||||
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
|
let parent = current_exe.parent();
|
||||||
WriteLogger::new(LevelFilter::Info, Config::default(), File::create(LOG_FILE).unwrap()),
|
let cwd = parent.expect("Failed to get parent directory");
|
||||||
]
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// Step 1: Get or create config file
|
CombinedLogger::init(vec![
|
||||||
let mut config_res = load_config(CONFIG_PATH);
|
TermLogger::new(
|
||||||
|
LevelFilter::Info,
|
||||||
|
simplelog::Config::default(),
|
||||||
|
TerminalMode::Mixed,
|
||||||
|
ColorChoice::Auto,
|
||||||
|
),
|
||||||
|
WriteLogger::new(
|
||||||
|
LevelFilter::Info,
|
||||||
|
simplelog::Config::default(),
|
||||||
|
File::create(cwd.join(LOG_FILE)).unwrap(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if let Err(err) = &config_res {
|
info!("Initialized logger");
|
||||||
match err {
|
|
||||||
ConfigErr::NotExist => {
|
ping_service::run()
|
||||||
error!("Failed to find existing config file. Create one.");
|
}
|
||||||
config_res = create_default_config(CONFIG_PATH);
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn main() {
|
||||||
|
panic!("This program is only intended to run on Windows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod ping_service {
|
||||||
|
use crate::app::run as run_watch;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
sync::mpsc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use windows_service::{
|
||||||
|
Result, define_windows_service,
|
||||||
|
service::{
|
||||||
|
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
||||||
|
ServiceType,
|
||||||
},
|
},
|
||||||
|
service_control_handler::{self, ServiceControlHandlerResult},
|
||||||
|
service_dispatcher,
|
||||||
|
};
|
||||||
|
|
||||||
ConfigErr::WriteError(e) => {
|
const SERVICE_NAME: &str = "SV: SXP Service Watcher";
|
||||||
error!("Failed to write config file.");
|
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
|
||||||
error!("Message: {e}");
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
|
|
||||||
ConfigErr::ParsingError(e) => {
|
pub fn run() -> Result<()> {
|
||||||
error!("Failed to parse config file");
|
// Register generated `ffi_service_main` with the system and start the service, blocking
|
||||||
error!("Message: {e}");
|
// this thread until the service is stopped.
|
||||||
return Ok(());
|
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = config_res.unwrap();
|
// Generate the windows service boilerplate.
|
||||||
|
// The boilerplate contains the low-level service entry function (ffi_service_main) that parses
|
||||||
|
// incoming service arguments into Vec<OsString> and passes them to user defined service
|
||||||
|
// entry (my_service_main).
|
||||||
|
define_windows_service!(ffi_service_main, my_service_main);
|
||||||
|
|
||||||
watch_files(config)?;
|
// Service entry function which is called on background thread by the system with service
|
||||||
|
// parameters. There is no stdout or stderr at this point so make sure to configure the log
|
||||||
|
// output to file if needed.
|
||||||
|
pub fn my_service_main(_arguments: Vec<OsString>) {
|
||||||
|
if let Err(e) = run_service() {
|
||||||
|
// Handle the error, by logging or something.
|
||||||
|
error!("Service failed: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_service() -> Result<()> {
|
||||||
|
// Create a channel to be able to poll a stop event from the service worker loop.
|
||||||
|
let (shutdown_tx, shutdown_rx) = mpsc::channel();
|
||||||
|
|
||||||
|
// Define system service event handler that will be receiving service events.
|
||||||
|
let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
||||||
|
match control_event {
|
||||||
|
// Notifies a service to report its current status information to the service
|
||||||
|
// control manager. Always return NoError even if not implemented.
|
||||||
|
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||||
|
|
||||||
|
// Handle stop
|
||||||
|
ServiceControl::Stop => {
|
||||||
|
shutdown_tx.send(()).unwrap();
|
||||||
|
ServiceControlHandlerResult::NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
// treat the UserEvent as a stop request
|
||||||
|
ServiceControl::UserEvent(code) => {
|
||||||
|
if code.to_raw() == 130 {
|
||||||
|
shutdown_tx.send(()).unwrap();
|
||||||
|
}
|
||||||
|
ServiceControlHandlerResult::NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => ServiceControlHandlerResult::NotImplemented,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register system service event handler.
|
||||||
|
// The returned status handle should be used to report service status changes to the system.
|
||||||
|
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
|
||||||
|
|
||||||
|
// Tell the system that service is running
|
||||||
|
status_handle.set_service_status(ServiceStatus {
|
||||||
|
service_type: SERVICE_TYPE,
|
||||||
|
current_state: ServiceState::Running,
|
||||||
|
controls_accepted: ServiceControlAccept::STOP,
|
||||||
|
exit_code: ServiceExitCode::Win32(0),
|
||||||
|
checkpoint: 0,
|
||||||
|
wait_hint: Duration::default(),
|
||||||
|
process_id: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Err(e) = run_watch(shutdown_rx) {
|
||||||
|
error!("Service crashed. Error: {}", e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the system that service has stopped.
|
||||||
|
status_handle.set_service_status(ServiceStatus {
|
||||||
|
service_type: SERVICE_TYPE,
|
||||||
|
current_state: ServiceState::Stopped,
|
||||||
|
controls_accepted: ServiceControlAccept::empty(),
|
||||||
|
exit_code: ServiceExitCode::Win32(0),
|
||||||
|
checkpoint: 0,
|
||||||
|
wait_hint: Duration::default(),
|
||||||
|
process_id: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
130
src/util.rs
130
src/util.rs
@ -0,0 +1,130 @@
|
|||||||
|
use std::{ptr::null_mut, time::Duration};
|
||||||
|
use winapi::{
|
||||||
|
shared::minwindef::DWORD,
|
||||||
|
um::{
|
||||||
|
errhandlingapi::GetLastError,
|
||||||
|
winsvc::{
|
||||||
|
CloseServiceHandle, ControlService, OpenSCManagerW, OpenServiceW, QueryServiceStatus, StartServiceW, SC_HANDLE__, SC_MANAGER_CONNECT, SERVICE_CONTROL_STOP, SERVICE_QUERY_STATUS, SERVICE_START, SERVICE_STOP
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use widestring::U16CString;
|
||||||
|
|
||||||
|
const SERVICE_STATUS_RUNNING: u32 = 0x00000004;
|
||||||
|
const SERVICE_STATUS_STOPPED: u32 = 0x00000001;
|
||||||
|
|
||||||
|
fn open_sc_manager(desired_access: DWORD) -> Result<*mut SC_HANDLE__, DWORD> {
|
||||||
|
let sc_manager_handle = unsafe { OpenSCManagerW(null_mut(), null_mut(), desired_access) };
|
||||||
|
if sc_manager_handle.is_null() {
|
||||||
|
Err(unsafe { GetLastError() })
|
||||||
|
} else {
|
||||||
|
Ok(sc_manager_handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn open_service(
|
||||||
|
sc_manager_handle: *mut SC_HANDLE__,
|
||||||
|
service_name: &str,
|
||||||
|
desired_access: DWORD,
|
||||||
|
) -> Result<*mut SC_HANDLE__, DWORD> {
|
||||||
|
let service_name_wstr = U16CString::from_str(service_name).unwrap();
|
||||||
|
let service_handle = unsafe {
|
||||||
|
OpenServiceW(
|
||||||
|
sc_manager_handle,
|
||||||
|
service_name_wstr.as_ptr(),
|
||||||
|
desired_access,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if service_handle.is_null() {
|
||||||
|
Err(unsafe { GetLastError() })
|
||||||
|
} else {
|
||||||
|
Ok(service_handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_service(service_handle: *mut SC_HANDLE__) -> Result<(), DWORD> {
|
||||||
|
let result = unsafe { StartServiceW(service_handle, 0, null_mut()) };
|
||||||
|
if result == 0 {
|
||||||
|
Err(unsafe { GetLastError() })
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_service(service_handle: *mut SC_HANDLE__) -> Result<(), DWORD> {
|
||||||
|
let mut service_status = unsafe { std::mem::zeroed() };
|
||||||
|
let result = unsafe {
|
||||||
|
ControlService(
|
||||||
|
service_handle,
|
||||||
|
SERVICE_CONTROL_STOP,
|
||||||
|
&mut service_status,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if result == 0 {
|
||||||
|
Err(unsafe { GetLastError() })
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_service_status(service_handle: *mut SC_HANDLE__) -> Result<DWORD, DWORD> {
|
||||||
|
let mut service_status = unsafe { std::mem::zeroed() };
|
||||||
|
let result = unsafe { QueryServiceStatus(service_handle, &mut service_status) };
|
||||||
|
if result == 0 {
|
||||||
|
Err(unsafe { GetLastError() })
|
||||||
|
} else {
|
||||||
|
Ok(service_status.dwCurrentState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restart_service(service_name: String) {
|
||||||
|
let sc_manager_handle = open_sc_manager(SC_MANAGER_CONNECT).unwrap();
|
||||||
|
|
||||||
|
let service_handle = open_service(
|
||||||
|
sc_manager_handle,
|
||||||
|
service_name.as_str(),
|
||||||
|
SERVICE_QUERY_STATUS | SERVICE_START | SERVICE_STOP,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let service_status_res = query_service_status(service_handle);
|
||||||
|
|
||||||
|
if let Err(e) = service_status_res {
|
||||||
|
error!("Failed to query service status. Error code: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let service_status = service_status_res.unwrap();
|
||||||
|
|
||||||
|
if service_status == SERVICE_STATUS_RUNNING {
|
||||||
|
// Stop the service
|
||||||
|
match stop_service(service_handle) {
|
||||||
|
Ok(_) => info!("Service stopped successfully."),
|
||||||
|
Err(error) => info!("Failed to stop service. Error: {}", error),
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Waiting for service being stopped successfully");
|
||||||
|
std::thread::sleep(Duration::from_secs(10));
|
||||||
|
|
||||||
|
// Start the service
|
||||||
|
match start_service(service_handle) {
|
||||||
|
Ok(_) => info!("Service started successfully."),
|
||||||
|
Err(error) => info!("Failed to start service. Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if service_status == SERVICE_STATUS_STOPPED {
|
||||||
|
// Start the service
|
||||||
|
match start_service(service_handle) {
|
||||||
|
Ok(_) => info!("Service started successfully."),
|
||||||
|
Err(error) => info!("Failed to start service. Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the service and SCM handles
|
||||||
|
unsafe {
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
CloseServiceHandle(sc_manager_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user