ant_service_management/
control.rs

1// Copyright (C) 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use crate::error::{Error, Result};
10use service_manager::{
11    ServiceInstallCtx, ServiceLabel, ServiceLevel, ServiceManager, ServiceStartCtx, ServiceStopCtx,
12    ServiceUninstallCtx,
13};
14use std::{
15    net::{SocketAddr, TcpListener},
16    path::Path,
17};
18use sysinfo::System;
19
20/// A thin wrapper around the `service_manager::ServiceManager`, which makes our own testing
21/// easier.
22///
23/// We can make an assumption that this external component works correctly, so our own tests only
24/// need assert that the service manager is used. Testing code that used the real service manager
25/// would result in real services on the machines we are testing on; that can leave a bit of a mess
26/// to clean up, especially if the tests fail.
27pub trait ServiceControl: Sync {
28    fn create_service_user(&self, username: &str) -> Result<()>;
29    fn get_available_port(&self) -> Result<u16>;
30    fn install(&self, install_ctx: ServiceInstallCtx, user_mode: bool) -> Result<()>;
31    fn get_process_pid(&self, path: &Path) -> Result<u32>;
32    fn start(&self, service_name: &str, user_mode: bool) -> Result<()>;
33    fn stop(&self, service_name: &str, user_mode: bool) -> Result<()>;
34    fn uninstall(&self, service_name: &str, user_mode: bool) -> Result<()>;
35    fn wait(&self, delay: u64);
36}
37
38pub struct ServiceController {}
39
40impl ServiceControl for ServiceController {
41    #[cfg(target_os = "linux")]
42    fn create_service_user(&self, username: &str) -> Result<()> {
43        use std::process::Command;
44
45        let output = Command::new("id")
46            .arg("-u")
47            .arg(username)
48            .output()
49            .inspect_err(|err| error!("Failed to execute id -u: {err:?}"))?;
50        if output.status.success() {
51            println!("The {username} user already exists");
52            return Ok(());
53        }
54
55        let useradd_exists = Command::new("which")
56            .arg("useradd")
57            .output()
58            .inspect_err(|err| error!("Failed to execute which useradd: {err:?}"))?
59            .status
60            .success();
61        let adduser_exists = Command::new("which")
62            .arg("adduser")
63            .output()
64            .inspect_err(|err| error!("Failed to execute which adduser: {err:?}"))?
65            .status
66            .success();
67
68        let output = if useradd_exists {
69            Command::new("useradd")
70                .arg("-m")
71                .arg("-s")
72                .arg("/bin/bash")
73                .arg(username)
74                .output()
75                .inspect_err(|err| error!("Failed to execute useradd: {err:?}"))?
76        } else if adduser_exists {
77            Command::new("adduser")
78                .arg("-s")
79                .arg("/bin/busybox")
80                .arg("-D")
81                .arg(username)
82                .output()
83                .inspect_err(|err| error!("Failed to execute adduser: {err:?}"))?
84        } else {
85            error!("Neither useradd nor adduser is available. ServiceUserAccountCreationFailed");
86            return Err(Error::ServiceUserAccountCreationFailed);
87        };
88
89        if !output.status.success() {
90            error!("Failed to create {username} user account: {output:?}");
91            return Err(Error::ServiceUserAccountCreationFailed);
92        }
93        println!("Created {username} user account for running the service");
94        info!("Created {username} user account for running the service");
95        Ok(())
96    }
97
98    #[cfg(target_os = "macos")]
99    fn create_service_user(&self, username: &str) -> Result<()> {
100        use std::process::Command;
101        use std::str;
102
103        let output = Command::new("dscl")
104            .arg(".")
105            .arg("-list")
106            .arg("/Users")
107            .output()
108            .inspect_err(|err| error!("Failed to execute dscl: {err:?}"))?;
109        let output_str = str::from_utf8(&output.stdout)
110            .inspect_err(|err| error!("Error while converting output to utf8: {err:?}"))?;
111        if output_str.lines().any(|line| line == username) {
112            return Ok(());
113        }
114
115        let output = Command::new("dscl")
116            .arg(".")
117            .arg("-list")
118            .arg("/Users")
119            .arg("UniqueID")
120            .output()
121            .inspect_err(|err| error!("Failed to execute dscl: {err:?}"))?;
122        let output_str = str::from_utf8(&output.stdout)
123            .inspect_err(|err| error!("Error while converting output to utf8: {err:?}"))?;
124        let mut max_id = 0;
125
126        for line in output_str.lines() {
127            let parts: Vec<&str> = line.split_whitespace().collect();
128            if parts.len() == 2 {
129                if let Ok(id) = parts[1].parse::<u32>() {
130                    if id > max_id {
131                        max_id = id;
132                    }
133                }
134            }
135        }
136        let new_unique_id = max_id + 1;
137
138        let commands = vec![
139            format!("dscl . -create /Users/{}", username),
140            format!(
141                "dscl . -create /Users/{} UserShell /usr/bin/false",
142                username
143            ),
144            format!(
145                "dscl . -create /Users/{} UniqueID {}",
146                username, new_unique_id
147            ),
148            format!("dscl . -create /Users/{} PrimaryGroupID 20", username),
149        ];
150        for cmd in commands {
151            let status = Command::new("sh")
152                .arg("-c")
153                .arg(&cmd)
154                .status()
155                .inspect_err(|err| error!("Error while executing dscl command: {err:?}"))?;
156            if !status.success() {
157                error!("The command {cmd} failed to execute. ServiceUserAccountCreationFailed");
158                return Err(Error::ServiceUserAccountCreationFailed);
159            }
160        }
161        Ok(())
162    }
163
164    #[cfg(target_os = "windows")]
165    fn create_service_user(&self, _username: &str) -> Result<()> {
166        Ok(())
167    }
168
169    fn get_available_port(&self) -> Result<u16> {
170        let addr: SocketAddr = "127.0.0.1:0".parse()?;
171
172        let socket = TcpListener::bind(addr)?;
173        let port = socket.local_addr()?.port();
174        drop(socket);
175        trace!("Got available port: {port}");
176
177        Ok(port)
178    }
179
180    fn get_process_pid(&self, bin_path: &Path) -> Result<u32> {
181        debug!(
182            "Searching for process with binary at {}",
183            bin_path.to_string_lossy()
184        );
185        let system = System::new_all();
186        for (pid, process) in system.processes() {
187            if let Some(path) = process.exe() {
188                if bin_path == path {
189                    // There does not seem to be any easy way to get the process ID from the `Pid`
190                    // type. Probably something to do with representing it in a cross-platform way.
191                    trace!("Found process {bin_path:?} with PID: {pid}");
192                    return Ok(pid.to_string().parse::<u32>()?);
193                }
194            }
195        }
196        error!(
197            "No process was located with a path at {}",
198            bin_path.to_string_lossy()
199        );
200        Err(Error::ServiceProcessNotFound(
201            bin_path.to_string_lossy().to_string(),
202        ))
203    }
204
205    fn install(&self, install_ctx: ServiceInstallCtx, user_mode: bool) -> Result<()> {
206        debug!("Installing service: {install_ctx:?}");
207        let mut manager = <dyn ServiceManager>::native()
208            .inspect_err(|err| error!("Could not get native ServiceManage: {err:?}"))?;
209        if user_mode {
210            manager
211                .set_level(ServiceLevel::User)
212                .inspect_err(|err| error!("Could not set service to user mode: {err:?}"))?;
213        }
214        manager
215            .install(install_ctx)
216            .inspect_err(|err| error!("Error while installing service: {err:?}"))?;
217        Ok(())
218    }
219
220    fn start(&self, service_name: &str, user_mode: bool) -> Result<()> {
221        debug!("Starting service: {service_name}");
222        let label: ServiceLabel = service_name.parse()?;
223        let mut manager = <dyn ServiceManager>::native()
224            .inspect_err(|err| error!("Could not get native ServiceManage: {err:?}"))?;
225        if user_mode {
226            manager
227                .set_level(ServiceLevel::User)
228                .inspect_err(|err| error!("Could not set service to user mode: {err:?}"))?;
229        }
230        manager
231            .start(ServiceStartCtx { label })
232            .inspect_err(|err| error!("Error while starting service: {err:?}"))?;
233        Ok(())
234    }
235
236    fn stop(&self, service_name: &str, user_mode: bool) -> Result<()> {
237        debug!("Stopping service: {service_name}");
238        let label: ServiceLabel = service_name.parse()?;
239        let mut manager = <dyn ServiceManager>::native()
240            .inspect_err(|err| error!("Could not get native ServiceManage: {err:?}"))?;
241        if user_mode {
242            manager
243                .set_level(ServiceLevel::User)
244                .inspect_err(|err| error!("Could not set service to user mode: {err:?}"))?;
245        }
246        manager
247            .stop(ServiceStopCtx { label })
248            .inspect_err(|err| error!("Error while stopping service: {err:?}"))?;
249
250        Ok(())
251    }
252
253    fn uninstall(&self, service_name: &str, user_mode: bool) -> Result<()> {
254        debug!("Uninstalling service: {service_name}");
255        let label: ServiceLabel = service_name.parse()?;
256        let mut manager = <dyn ServiceManager>::native()
257            .inspect_err(|err| error!("Could not get native ServiceManage: {err:?}"))?;
258
259        if user_mode {
260            manager
261                .set_level(ServiceLevel::User)
262                .inspect_err(|err| error!("Could not set service to user mode: {err:?}"))?;
263        }
264        match manager.uninstall(ServiceUninstallCtx { label }) {
265            Ok(()) => Ok(()),
266            Err(err) => {
267                if std::io::ErrorKind::NotFound == err.kind() {
268                    error!("Error while uninstall service, service file might have been removed manually: {service_name}");
269                    // In this case the user has removed the service definition file manually,
270                    // which the service manager crate treats as an error. We can propagate the
271                    // it to the caller and they can decide how to handle it.
272                    Err(Error::ServiceRemovedManually(service_name.to_string()))
273                } else if err.raw_os_error() == Some(267) {
274                    // This requires the unstable io_error_more feature, use raw code for now
275                    // else if err.kind() == std::io::ErrorKind::NotADirectory {}
276
277                    // This happens on windows when the service has been already cleared, but was not updated
278                    // in the registry. Happens when the Service application (in windows) is open while calling
279                    // 'remove' or 'reset'.
280                    Err(Error::ServiceDoesNotExists(service_name.to_string()))
281                } else {
282                    error!("Error while uninstalling service: {err:?}");
283                    Err(err.into())
284                }
285            }
286        }
287    }
288
289    /// Provide a delay for the service to start or stop.
290    ///
291    /// This is wrapped mainly just for unit testing.
292    fn wait(&self, delay: u64) {
293        trace!("Waiting for {delay} milliseconds");
294        std::thread::sleep(std::time::Duration::from_millis(delay));
295    }
296}