├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── bin └── example.rs ├── daemon.rs ├── lib.rs ├── posix.rs ├── singleton.rs └── windows.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rs text eol=lf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | rustfmt: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: checkout 8 | uses: actions/checkout@v4 9 | - name: rustup 10 | uses: dtolnay/rust-toolchain@stable 11 | with: 12 | components: rustfmt 13 | - run: cargo fmt --all -- --check 14 | test: 15 | strategy: 16 | matrix: 17 | platform: [ ubuntu-latest, macos-latest, windows-latest ] 18 | runs-on: ${{ matrix.platform }} 19 | steps: 20 | - name: checkout 21 | uses: actions/checkout@v4 22 | - name: rustup 23 | uses: dtolnay/rust-toolchain@stable 24 | - run: cargo test --all-targets 25 | clippy: 26 | strategy: 27 | matrix: 28 | platform: [ ubuntu-latest, macos-latest, windows-latest ] 29 | runs-on: ${{ matrix.platform }} 30 | steps: 31 | - name: checkout 32 | uses: actions/checkout@v4 33 | - name: rustup 34 | uses: dtolnay/rust-toolchain@stable 35 | with: 36 | components: clippy 37 | - run: cargo clippy --all-targets -- -D warnings 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | /*.lock 13 | 14 | /.idea/ 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "daemon" 3 | license = "MIT" 4 | edition = "2021" 5 | version = "0.0.8" 6 | description = "Library for creating simple Linux demons and Windows services." 7 | authors = [ "Artem V. Navrotskiy " ] 8 | repository = "https://github.com/bozaro/daemon-rs" 9 | documentation = "https://bozaro.github.io/daemon-rs/daemon/" 10 | 11 | [dependencies] 12 | libc = "0.2" 13 | 14 | [target.'cfg(windows)'.dependencies] 15 | winapi = { version = "0.3", features = [ "consoleapi", "winsvc" ] } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Artem V. Navrotskiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # daemon-rs 2 | [![Build Status](https://travis-ci.org/bozaro/daemon-rs.svg?branch=master)](https://travis-ci.org/bozaro/daemon-rs) 3 | [![Build status](https://ci.appveyor.com/api/projects/status/6bt4tugu4iyu8i6j/branch/master?svg=true)](https://ci.appveyor.com/project/bozaro/daemon-rs/branch/master) 4 | [![Crates.io](https://img.shields.io/crates/v/daemon.svg)](https://crates.io/crates/daemon) 5 | 6 | Wrapper for POSIX daemon/Windows service in Rust 7 | 8 | Rustdoc: https://bozaro.github.io/daemon-rs/daemon/ 9 | -------------------------------------------------------------------------------- /src/bin/example.rs: -------------------------------------------------------------------------------- 1 | extern crate daemon; 2 | 3 | use daemon::Daemon; 4 | use daemon::DaemonRunner; 5 | use daemon::State; 6 | use std::env; 7 | use std::fs::OpenOptions; 8 | use std::io::Error; 9 | use std::io::Write; 10 | use std::sync::mpsc::Receiver; 11 | 12 | fn main() -> Result<(), Error> { 13 | log("Example started."); 14 | let daemon = Daemon { 15 | name: "example".to_string(), 16 | }; 17 | daemon.run(move |rx: Receiver| { 18 | log("Worker started."); 19 | for signal in rx.iter() { 20 | match signal { 21 | State::Start => log("Worker: Start"), 22 | State::Reload => log("Worker: Reload"), 23 | State::Stop => log("Worker: Stop"), 24 | }; 25 | } 26 | log("Worker finished."); 27 | })?; 28 | log("Example finished."); 29 | 30 | Ok(()) 31 | } 32 | 33 | fn log(message: &str) { 34 | let _ = log_safe(message); 35 | } 36 | 37 | fn log_safe(message: &str) -> Result<(), Error> { 38 | println!("{}", message); 39 | let path = env::current_exe()?.with_extension("log"); 40 | let mut file = OpenOptions::new().create(true).append(true).open(&path)?; 41 | file.write_all(message.as_bytes())?; 42 | file.write_all(b"\n")?; 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/daemon.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use std::sync::mpsc::Receiver; 3 | 4 | pub enum State { 5 | Start, 6 | Reload, 7 | Stop, 8 | } 9 | 10 | pub struct Daemon { 11 | // Daemon name 12 | pub name: String, 13 | } 14 | 15 | pub trait DaemonRunner { 16 | fn run)>(&self, f: F) -> Result<(), Error>; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | extern crate winapi; 3 | 4 | pub mod daemon; 5 | pub use daemon::*; 6 | 7 | mod singleton; 8 | 9 | #[cfg(target_os = "windows")] 10 | pub mod windows; 11 | #[cfg(target_os = "windows")] 12 | pub use windows::*; 13 | 14 | #[cfg(any(target_os = "linux", target_os = "macos"))] 15 | pub mod posix; 16 | #[cfg(any(target_os = "linux", target_os = "macos"))] 17 | pub use posix::*; 18 | -------------------------------------------------------------------------------- /src/posix.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | use super::daemon::*; 4 | use std::io::{Error, ErrorKind}; 5 | use std::sync::mpsc::channel; 6 | use std::sync::mpsc::Receiver; 7 | use std::sync::mpsc::Sender; 8 | 9 | declare_singleton!( 10 | singleton, 11 | DaemonHolder, 12 | DaemonHolder { 13 | holder: std::ptr::null_mut::() 14 | } 15 | ); 16 | 17 | struct DaemonHolder { 18 | holder: *mut DaemonStatic, 19 | } 20 | 21 | struct DaemonStatic { 22 | holder: Box, 23 | } 24 | 25 | trait DaemonFunc { 26 | fn exec(&mut self) -> Result<(), Error>; 27 | fn send(&mut self, state: State) -> Result<(), Error>; 28 | fn take_tx(&mut self) -> Option>; 29 | } 30 | 31 | struct DaemonFuncHolder)> { 32 | tx: Option>, 33 | func: Option<(F, Receiver)>, 34 | } 35 | 36 | impl)> DaemonFunc for DaemonFuncHolder { 37 | fn exec(&mut self) -> Result<(), Error> { 38 | match self.func.take() { 39 | Some((func, rx)) => { 40 | func(rx); 41 | Ok(()) 42 | } 43 | None => Err(Error::new( 44 | ErrorKind::Other, 45 | "INTERNAL ERROR: Can't unwrap daemon function", 46 | )), 47 | } 48 | } 49 | 50 | fn send(&mut self, state: State) -> Result<(), Error> { 51 | match self.tx { 52 | Some(ref tx) => match tx.send(state) { 53 | Ok(_) => Ok(()), 54 | Err(e) => Err(Error::new(ErrorKind::Other, e)), 55 | }, 56 | None => Err(Error::new(ErrorKind::Other, "Service is already exited")), 57 | } 58 | } 59 | 60 | fn take_tx(&mut self) -> Option> { 61 | self.tx.take() 62 | } 63 | } 64 | 65 | fn daemon_wrapper R>(func: F) -> R { 66 | let singleton = singleton(); 67 | let result = match singleton.lock() { 68 | Ok(ref mut daemon) => func(daemon), 69 | Err(e) => { 70 | panic!("Mutex error: {:?}", e); 71 | } 72 | }; 73 | result 74 | } 75 | 76 | impl DaemonRunner for Daemon { 77 | fn run)>(&self, func: F) -> Result<(), Error> { 78 | let (tx, rx) = channel(); 79 | tx.send(State::Start).unwrap(); 80 | let mut daemon = DaemonStatic { 81 | holder: Box::new(DaemonFuncHolder { 82 | tx: Some(tx), 83 | func: Some((func, rx)), 84 | }), 85 | }; 86 | guard_compare_and_swap(daemon_null(), &mut daemon)?; 87 | let result = daemon_console(&mut daemon); 88 | guard_compare_and_swap(&mut daemon, daemon_null())?; 89 | result 90 | } 91 | } 92 | 93 | fn guard_compare_and_swap( 94 | old_value: *mut DaemonStatic, 95 | new_value: *mut DaemonStatic, 96 | ) -> Result<(), Error> { 97 | daemon_wrapper(|daemon_static: &mut DaemonHolder| -> Result<(), Error> { 98 | if daemon_static.holder != old_value { 99 | return Err(Error::new( 100 | ErrorKind::Other, 101 | "This function is not reentrant.", 102 | )); 103 | } 104 | daemon_static.holder = new_value; 105 | Ok(()) 106 | }) 107 | } 108 | 109 | fn daemon_console(daemon: &mut DaemonStatic) -> Result<(), Error> { 110 | daemon.holder.exec() 111 | } 112 | 113 | fn daemon_null() -> *mut DaemonStatic { 114 | std::ptr::null_mut::() 115 | } 116 | -------------------------------------------------------------------------------- /src/singleton.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | /** 3 | * Thanks for http://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton 4 | */ 5 | use std::sync::{Arc, LockResult, Mutex, MutexGuard}; 6 | 7 | //#[derive(Copy)] 8 | pub struct SingletonHolder { 9 | // Since we will be used in many threads, we need to protect 10 | // concurrent access 11 | inner: Arc>, 12 | } 13 | 14 | impl SingletonHolder { 15 | pub fn new(mutex: Arc>) -> SingletonHolder { 16 | SingletonHolder { inner: mutex } 17 | } 18 | 19 | pub fn lock(&self) -> LockResult> { 20 | self.inner.lock() 21 | } 22 | } 23 | 24 | impl Clone for SingletonHolder { 25 | fn clone(&self) -> SingletonHolder { 26 | SingletonHolder { 27 | inner: self.inner.clone(), 28 | } 29 | } 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! declare_singleton { 34 | ($name:ident, $t:ty, $init:expr) => { 35 | fn $name() -> $crate::singleton::SingletonHolder<$t> { 36 | static mut SINGLETON: *const $crate::singleton::SingletonHolder<$t> = 37 | 0 as *const $crate::singleton::SingletonHolder<$t>; 38 | static ONCE: ::std::sync::Once = ::std::sync::Once::new(); 39 | 40 | unsafe { 41 | ONCE.call_once(|| { 42 | let singleton = $crate::singleton::SingletonHolder::new(::std::sync::Arc::new( 43 | ::std::sync::Mutex::new($init), 44 | )); 45 | 46 | // Put it in the heap so it can outlive this call 47 | SINGLETON = ::std::mem::transmute(Box::new(singleton)); 48 | 49 | // TODO: Make sure to free heap memory at exit 50 | }); 51 | (*SINGLETON).clone() 52 | } 53 | } 54 | }; 55 | } 56 | 57 | #[cfg(test)] 58 | mod test { 59 | #[test] 60 | fn smoke_test() { 61 | declare_singleton!(simple_singleton, u32, 0); 62 | let simple = simple_singleton(); 63 | match simple.lock() { 64 | Ok(_) => {} 65 | Err(_) => {} 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use super::daemon::*; 2 | 3 | use std::io::{Error, ErrorKind}; 4 | use std::ptr; 5 | use std::sync::mpsc::channel; 6 | use std::sync::mpsc::Receiver; 7 | use std::sync::mpsc::Sender; 8 | 9 | use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPVOID, TRUE}; 10 | use winapi::um::consoleapi::SetConsoleCtrlHandler; 11 | use winapi::um::winnt::{LPWSTR, SERVICE_WIN32_OWN_PROCESS}; 12 | use winapi::um::winsvc::*; 13 | 14 | declare_singleton!( 15 | singleton, 16 | DaemonHolder, 17 | DaemonHolder { 18 | holder: 0 as *mut DaemonStatic 19 | } 20 | ); 21 | 22 | struct DaemonHolder { 23 | holder: *mut DaemonStatic, 24 | } 25 | 26 | struct DaemonStatic { 27 | name: String, 28 | holder: Box, 29 | handle: SERVICE_STATUS_HANDLE, 30 | } 31 | 32 | trait DaemonFunc { 33 | fn exec(&mut self) -> Result<(), Error>; 34 | fn send(&mut self, state: State) -> Result<(), Error>; 35 | fn take_tx(&mut self) -> Option>; 36 | } 37 | 38 | struct DaemonFuncHolder)> { 39 | tx: Option>, 40 | func: Option<(F, Receiver)>, 41 | } 42 | 43 | impl)> DaemonFunc for DaemonFuncHolder { 44 | fn exec(&mut self) -> Result<(), Error> { 45 | match self.func.take() { 46 | Some((func, rx)) => { 47 | func(rx); 48 | Ok(()) 49 | } 50 | None => Err(Error::new( 51 | ErrorKind::Other, 52 | "INTERNAL ERROR: Can't unwrap daemon function", 53 | )), 54 | } 55 | } 56 | 57 | fn send(&mut self, state: State) -> Result<(), Error> { 58 | match self.tx { 59 | Some(ref tx) => match tx.send(state) { 60 | Ok(()) => Ok(()), 61 | Err(e) => Err(Error::new(ErrorKind::Other, e)), 62 | }, 63 | None => Err(Error::new(ErrorKind::Other, "Service is already exited")), 64 | } 65 | } 66 | 67 | fn take_tx(&mut self) -> Option> { 68 | self.tx.take() 69 | } 70 | } 71 | 72 | fn daemon_wrapper R>(func: F) -> R { 73 | let singleton = singleton(); 74 | let result = match singleton.lock() { 75 | Ok(ref mut daemon) => func(daemon), 76 | Err(e) => { 77 | panic!("Mutex error: {:?}", e); 78 | } 79 | }; 80 | result 81 | } 82 | 83 | impl DaemonRunner for Daemon { 84 | fn run)>(&self, func: F) -> Result<(), Error> { 85 | let (tx, rx) = channel(); 86 | tx.send(State::Start).unwrap(); 87 | let mut daemon = DaemonStatic { 88 | name: self.name.clone(), 89 | holder: Box::new(DaemonFuncHolder { 90 | tx: Some(tx), 91 | func: Some((func, rx)), 92 | }), 93 | handle: 0 as SERVICE_STATUS_HANDLE, 94 | }; 95 | guard_compare_and_swap(daemon_null(), &mut daemon)?; 96 | let result = daemon_service(&mut daemon); 97 | guard_compare_and_swap(&mut daemon, daemon_null())?; 98 | result 99 | } 100 | } 101 | 102 | fn guard_compare_and_swap( 103 | old_value: *mut DaemonStatic, 104 | new_value: *mut DaemonStatic, 105 | ) -> Result<(), Error> { 106 | daemon_wrapper(|daemon_static: &mut DaemonHolder| -> Result<(), Error> { 107 | if daemon_static.holder != old_value { 108 | return Err(Error::new( 109 | ErrorKind::Other, 110 | "This function is not reentrant.", 111 | )); 112 | } 113 | daemon_static.holder = new_value; 114 | Ok(()) 115 | }) 116 | } 117 | 118 | fn daemon_service(daemon: &mut DaemonStatic) -> Result<(), Error> { 119 | unsafe { 120 | let service_name = service_name(&daemon.name); 121 | let service_table: &[*const SERVICE_TABLE_ENTRYW] = &[ 122 | &SERVICE_TABLE_ENTRYW { 123 | lpServiceName: service_name.as_ptr(), 124 | lpServiceProc: Some(service_main), 125 | }, 126 | ptr::null(), 127 | ]; 128 | match StartServiceCtrlDispatcherW(*service_table.as_ptr()) { 129 | 0 => daemon_console(daemon), 130 | _ => Ok(()), 131 | } 132 | } 133 | } 134 | 135 | unsafe fn daemon_console(daemon: &mut DaemonStatic) -> Result<(), Error> { 136 | let result; 137 | if SetConsoleCtrlHandler(Some(console_handler), TRUE) == FALSE { 138 | return Err(Error::last_os_error()); 139 | } 140 | result = daemon.holder.exec(); 141 | if SetConsoleCtrlHandler(Some(console_handler), FALSE) == FALSE { 142 | return Err(Error::last_os_error()); 143 | } 144 | result 145 | } 146 | 147 | fn service_name(name: &str) -> Vec { 148 | let mut result: Vec = name.chars().map(|c| c as u16).collect(); 149 | result.push(0); 150 | result 151 | } 152 | 153 | fn create_service_status(current_state: DWORD) -> SERVICE_STATUS { 154 | SERVICE_STATUS { 155 | dwServiceType: SERVICE_WIN32_OWN_PROCESS, 156 | dwCurrentState: current_state, 157 | dwControlsAccepted: SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, 158 | dwWin32ExitCode: 0, 159 | dwServiceSpecificExitCode: 0, 160 | dwCheckPoint: 0, 161 | dwWaitHint: 0, 162 | } 163 | } 164 | 165 | unsafe extern "system" fn service_main( 166 | _: DWORD, // dw_num_services_args 167 | _: *mut LPWSTR, // lp_service_arg_vectors 168 | ) { 169 | let daemon_holder = daemon_wrapper(|daemon_static: &mut DaemonHolder| { 170 | if daemon_static.holder != daemon_null() { 171 | let daemon = &mut *daemon_static.holder; 172 | let service_name = service_name(&daemon.name); 173 | daemon.handle = RegisterServiceCtrlHandlerExW( 174 | service_name.as_ptr(), 175 | Some(service_handler), 176 | ptr::null_mut(), 177 | ); 178 | SetServiceStatus( 179 | daemon.handle, 180 | &mut create_service_status(SERVICE_START_PENDING), 181 | ); 182 | SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_RUNNING)); 183 | 184 | Some(daemon) 185 | } else { 186 | None 187 | } 188 | }); 189 | if let Some(daemon) = daemon_holder { 190 | daemon.holder.exec().unwrap(); 191 | } 192 | daemon_wrapper(|daemon_static: &mut DaemonHolder| { 193 | if daemon_static.holder != daemon_null() { 194 | let daemon = &mut *daemon_static.holder; 195 | SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_STOPPED)); 196 | } 197 | }); 198 | } 199 | 200 | unsafe extern "system" fn service_handler( 201 | dw_control: DWORD, 202 | _: DWORD, // dw_event_type 203 | _: LPVOID, // lp_event_data 204 | _: LPVOID, // lp_context 205 | ) -> DWORD { 206 | daemon_wrapper(|daemon_static: &mut DaemonHolder| { 207 | let daemon = &mut *daemon_static.holder; 208 | match dw_control { 209 | SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => match daemon.holder.take_tx() { 210 | Some(ref tx) => { 211 | SetServiceStatus( 212 | daemon.handle, 213 | &mut create_service_status(SERVICE_STOP_PENDING), 214 | ); 215 | let _ = tx.send(State::Stop); 216 | } 217 | None => {} 218 | }, 219 | _ => {} 220 | }; 221 | }); 222 | 0 223 | } 224 | 225 | unsafe extern "system" fn console_handler(_: DWORD) -> BOOL { 226 | daemon_wrapper(|daemon_static: &mut DaemonHolder| -> BOOL { 227 | if daemon_static.holder != daemon_null() { 228 | let daemon = &mut *daemon_static.holder; 229 | match daemon.holder.take_tx() { 230 | Some(ref tx) => match tx.send(State::Stop) { 231 | Ok(_) => TRUE, 232 | Err(_) => FALSE, 233 | }, 234 | None => TRUE, 235 | } 236 | } else { 237 | FALSE 238 | } 239 | }) 240 | } 241 | 242 | fn daemon_null() -> *mut DaemonStatic { 243 | 0 as *mut DaemonStatic 244 | } 245 | --------------------------------------------------------------------------------