1use crate::error::RegentError;
73use crate::hosts::managed_host::InternalApiCallOutcome;
74use crate::hosts::managed_host::{AssessCompliance, ReachCompliance, Timeout};
75use crate::hosts::properties::{HostProperties, InitSystem, LinuxFlavor, LinuxSpecifics, OsKind};
76use crate::secrets::SecretProvidersPool;
77use crate::state::Check;
78use crate::state::attribute::HostHandler;
79use crate::state::attribute::Privilege;
80use crate::state::attribute::Remediation;
81use crate::state::attribute::RemediationsList;
82use crate::state::compliance::AttributeComplianceAssessment;
83use serde::{Deserialize, Serialize};
84use std::time::Duration;
85
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "PascalCase")]
88pub enum ServiceExpectedStatus {
89 Started,
90 Stopped,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(rename_all = "PascalCase")]
95pub enum ServiceAction {
96 Restarted,
97 Reloaded,
98}
99
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133#[serde(rename_all_fields = "PascalCase")]
134#[serde(untagged)]
135pub enum ServiceExpectedState {
136 State {
141 name: String,
143 #[serde(default)]
145 state: Option<ServiceExpectedStatus>,
146 enabled: bool,
148 },
149 Action {
151 name: String,
153 action: ServiceAction,
155 },
156}
157
158impl Timeout for ServiceExpectedState {
159 fn default_timeout(&self) -> Duration {
160 Duration::from_secs(10)
161 }
162}
163
164impl ServiceExpectedState {
165 pub fn state(name: &str, state: ServiceExpectedStatus, enabled: bool) -> ServiceExpectedState {
167 ServiceExpectedState::State {
168 name: name.to_string(),
169 state: Some(state),
170 enabled,
171 }
172 }
173
174 pub fn started_and_enabled(name: &str) -> ServiceExpectedState {
176 ServiceExpectedState::State {
177 name: name.to_string(),
178 state: Some(ServiceExpectedStatus::Started),
179 enabled: true,
180 }
181 }
182
183 pub fn started_and_disabled(name: &str) -> ServiceExpectedState {
185 Self::state(name, ServiceExpectedStatus::Started, false)
186 }
187
188 pub fn stopped_and_enabled(name: &str) -> ServiceExpectedState {
190 Self::state(name, ServiceExpectedStatus::Stopped, true)
191 }
192
193 pub fn stopped_and_disabled(name: &str) -> ServiceExpectedState {
195 Self::state(name, ServiceExpectedStatus::Stopped, false)
196 }
197
198 pub fn enabled(name: &str, enabled: bool) -> ServiceExpectedState {
200 ServiceExpectedState::State {
201 name: name.to_string(),
202 state: None,
203 enabled,
204 }
205 }
206
207 pub fn disabled(name: &str) -> ServiceExpectedState {
209 Self::enabled(name, false)
210 }
211
212 pub fn restarted(name: &str) -> ServiceExpectedState {
214 ServiceExpectedState::Action {
215 name: name.to_string(),
216 action: ServiceAction::Restarted,
217 }
218 }
219
220 pub fn reloaded(name: &str) -> ServiceExpectedState {
222 ServiceExpectedState::Action {
223 name: name.to_string(),
224 action: ServiceAction::Reloaded,
225 }
226 }
227}
228
229impl Check for ServiceExpectedState {
230 fn check(&self) -> Result<(), RegentError> {
231 Ok(())
232 }
233
234 fn check_host_compatibility(
235 &self,
236 host_properties: &HostProperties,
237 ) -> Result<(), RegentError> {
238 use crate::hosts::properties::InitSystem;
239 match host_properties.os_kind() {
240 OsKind::Linux(linux_specifics) => match linux_specifics.init_system {
241 InitSystem::Systemd => Ok(()),
242 InitSystem::Unknown => Err(RegentError::IncompatibleHost(
243 "systemctl requires systemd but init system could not be detected".to_string(),
244 )),
245 },
246 #[cfg(feature = "windows")]
247 OsKind::Windows(_) => Ok(()),
248 incompatible_os_kind => Err(RegentError::IncompatibleHost(format!(
249 "Host is {:?} but service management is only supported on Linux with systemd or Windows (with windows feature)",
250 incompatible_os_kind
251 ))),
252 }
253 }
254}
255
256impl<Handler: HostHandler> AssessCompliance<Handler> for ServiceExpectedState {
257 async fn assess_compliance(
258 &self,
259 host_handler: &mut Handler,
260 host_properties: &Option<HostProperties>,
261 privilege: &Privilege,
262 _optional_secret_provider: &Option<SecretProvidersPool>,
263 ) -> Result<AttributeComplianceAssessment, RegentError> {
264 if let Some(props) = host_properties {
265 self.check_host_compatibility(props)?;
266 }
267
268 let os_kind = host_properties
270 .as_ref()
271 .map(|props| props.os_kind())
272 .unwrap_or(&OsKind::Linux(LinuxSpecifics {
273 linux_flavor: LinuxFlavor::Debian,
274 init_system: InitSystem::Systemd,
275 }));
276
277 match os_kind {
278 #[cfg(feature = "windows")]
279 OsKind::Windows(_) => {
280 let command_available = host_handler
281 .is_this_command_available("sc", privilege)
282 .await
283 .unwrap_or(false);
284
285 if !command_available {
286 return Err(RegentError::FailedDryRunEvaluation(
287 "Service management commands (sc) are not available on this Windows host"
288 .to_string(),
289 ));
290 }
291 }
292 OsKind::Linux(_) => {
293 let command_available = host_handler
294 .is_this_command_available("systemctl", privilege)
295 .await
296 .unwrap_or(false);
297
298 if !command_available {
299 return Err(RegentError::FailedDryRunEvaluation(
300 "Service management commands (systemctl) are not available on this Linux host".to_string(),
301 ));
302 }
303 }
304 OsKind::FreeBsd(_) | OsKind::MacOs(_) | OsKind::Unknown => {}
305 }
306
307 let mut remediations: Vec<Remediation> = Vec::new();
308
309 match &self {
310 Self::State {
311 name,
312 state,
313 enabled,
314 } => {
315 match os_kind {
317 #[cfg(feature = "windows")]
318 OsKind::Windows(_) => {
319 if let Some(expected_state) = state {
320 match windows_service_is_active(host_handler, name).await {
321 Ok(current_service_activity) => {
322 match (current_service_activity, expected_state) {
323 (
324 ServiceActivityStatus::Active,
325 ServiceExpectedStatus::Started,
326 )
327 | (
328 ServiceActivityStatus::Inactive,
329 ServiceExpectedStatus::Stopped,
330 ) => {}
331 (
332 ServiceActivityStatus::Active,
333 ServiceExpectedStatus::Stopped,
334 ) => {
335 remediations.push(Remediation::Service(
336 ServiceApiCall::from(
337 ServiceModuleInternalApiCall::Stop(
338 name.clone(),
339 ),
340 privilege.clone(),
341 ),
342 ));
343 }
344 (
345 ServiceActivityStatus::Inactive,
346 ServiceExpectedStatus::Started,
347 ) => {
348 remediations.push(Remediation::Service(
349 ServiceApiCall::from(
350 ServiceModuleInternalApiCall::Start(
351 name.clone(),
352 ),
353 privilege.clone(),
354 ),
355 ));
356 }
357 (
358 ServiceActivityStatus::ServiceNotFound,
359 _expected_state,
360 ) => {
361 return Ok(
362 AttributeComplianceAssessment::NonCompliantFatal(
363 "service not found".to_string(),
364 ),
365 );
366 }
367 (
368 ServiceActivityStatus::UnsupportedCase(details),
369 _expected_state,
370 ) => {
371 return Ok(
372 AttributeComplianceAssessment::NonCompliantFatal(
373 details.to_string(),
374 ),
375 );
376 }
377 }
378 }
379 Err(error_details) => {
380 return Err(RegentError::FailedTaskDryRun(format!(
381 "Unable to check service activity : {error_details}"
382 )));
383 }
384 }
385 }
386
387 match windows_service_is_enabled(host_handler, name).await {
388 Ok(current_service_enabling) => {
389 match (current_service_enabling, *enabled) {
390 (ServiceEnablingStatus::Enabled, true)
391 | (ServiceEnablingStatus::Disabled, false) => {}
392 (ServiceEnablingStatus::Enabled, false) => {
393 remediations.push(Remediation::Service(
394 ServiceApiCall::from(
395 ServiceModuleInternalApiCall::Disable(name.clone()),
396 privilege.clone(),
397 ),
398 ));
399 remediations.push(Remediation::Service(
400 ServiceApiCall::from(
401 ServiceModuleInternalApiCall::Enable(name.clone()),
402 privilege.clone(),
403 ),
404 ));
405 }
406 (ServiceEnablingStatus::Disabled, true) => {
407 remediations.push(Remediation::Service(
408 ServiceApiCall::from(
409 ServiceModuleInternalApiCall::Enable(name.clone()),
410 privilege.clone(),
411 ),
412 ));
413 }
414 (ServiceEnablingStatus::ServiceNotFound, _expected_state) => {
415 return Ok(
416 AttributeComplianceAssessment::NonCompliantFatal(
417 "service not found".to_string(),
418 ),
419 );
420 }
421 (
422 ServiceEnablingStatus::UnsupportedCase(details),
423 _expected_state,
424 ) => {
425 return Ok(
426 AttributeComplianceAssessment::NonCompliantFatal(
427 details.to_string(),
428 ),
429 );
430 }
431 }
432 }
433 Err(error_details) => {
434 return Err(RegentError::FailedTaskDryRun(format!(
435 "Unable to check service activity : {error_details}"
436 )));
437 }
438 }
439 }
440 OsKind::Linux(_) => {
441 if let Some(expected_state) = state {
442 match service_is_active(host_handler, name).await {
443 Ok(current_service_activity) => {
444 match (current_service_activity, expected_state) {
445 (
446 ServiceActivityStatus::Active,
447 ServiceExpectedStatus::Started,
448 )
449 | (
450 ServiceActivityStatus::Inactive,
451 ServiceExpectedStatus::Stopped,
452 ) => {}
453 (
454 ServiceActivityStatus::Active,
455 ServiceExpectedStatus::Stopped,
456 ) => {
457 remediations.push(Remediation::Service(
458 ServiceApiCall::from(
459 ServiceModuleInternalApiCall::Stop(
460 name.clone(),
461 ),
462 privilege.clone(),
463 ),
464 ));
465 }
466 (
467 ServiceActivityStatus::Inactive,
468 ServiceExpectedStatus::Started,
469 ) => {
470 remediations.push(Remediation::Service(
471 ServiceApiCall::from(
472 ServiceModuleInternalApiCall::Start(
473 name.clone(),
474 ),
475 privilege.clone(),
476 ),
477 ));
478 }
479 (
480 ServiceActivityStatus::ServiceNotFound,
481 _expected_state,
482 ) => {
483 return Ok(
484 AttributeComplianceAssessment::NonCompliantFatal(
485 "service not found".to_string(),
486 ),
487 );
488 }
489 (
490 ServiceActivityStatus::UnsupportedCase(details),
491 _expected_state,
492 ) => {
493 return Ok(
494 AttributeComplianceAssessment::NonCompliantFatal(
495 details.to_string(),
496 ),
497 );
498 }
499 }
500 }
501 Err(error_details) => {
502 return Err(RegentError::FailedTaskDryRun(format!(
503 "Unable to check service activity : {error_details}"
504 )));
505 }
506 }
507 }
508
509 match service_is_enabled(host_handler, name).await {
510 Ok(current_service_enabling) => {
511 match (current_service_enabling, *enabled) {
512 (ServiceEnablingStatus::Enabled, true)
513 | (ServiceEnablingStatus::Disabled, false) => {}
514 (ServiceEnablingStatus::Enabled, false) => {
515 remediations.push(Remediation::Service(
516 ServiceApiCall::from(
517 ServiceModuleInternalApiCall::Disable(name.clone()),
518 privilege.clone(),
519 ),
520 ));
521 remediations.push(Remediation::Service(
522 ServiceApiCall::from(
523 ServiceModuleInternalApiCall::Enable(name.clone()),
524 privilege.clone(),
525 ),
526 ));
527 }
528 (ServiceEnablingStatus::Disabled, true) => {
529 remediations.push(Remediation::Service(
530 ServiceApiCall::from(
531 ServiceModuleInternalApiCall::Enable(name.clone()),
532 privilege.clone(),
533 ),
534 ));
535 }
536 (ServiceEnablingStatus::ServiceNotFound, _expected_state) => {
537 return Ok(
538 AttributeComplianceAssessment::NonCompliantFatal(
539 "service not found".to_string(),
540 ),
541 );
542 }
543 (
544 ServiceEnablingStatus::UnsupportedCase(details),
545 _expected_state,
546 ) => {
547 return Ok(
548 AttributeComplianceAssessment::NonCompliantFatal(
549 details.to_string(),
550 ),
551 );
552 }
553 }
554 }
555 Err(error_details) => {
556 return Err(RegentError::FailedTaskDryRun(format!(
557 "Unable to check service activity : {error_details}"
558 )));
559 }
560 }
561 }
562 OsKind::FreeBsd(_) | OsKind::MacOs(_) | OsKind::Unknown => {
563 return Err(RegentError::FailedDryRunEvaluation(format!(
564 "Service management is not supported on {:?}",
565 os_kind
566 )));
567 }
568 }
569 }
570 Self::Action { name, action } => match os_kind {
571 #[cfg(feature = "windows")]
572 OsKind::Windows(_) => match &action {
573 ServiceAction::Restarted => {
574 remediations.push(Remediation::Service(ServiceApiCall::from(
575 ServiceModuleInternalApiCall::Restart(name.clone()),
576 privilege.clone(),
577 )));
578 }
579 ServiceAction::Reloaded => {
580 remediations.push(Remediation::Service(ServiceApiCall::from(
581 ServiceModuleInternalApiCall::Reload(name.clone()),
582 privilege.clone(),
583 )));
584 }
585 },
586 OsKind::Linux(_) => match &action {
587 ServiceAction::Restarted => {
588 remediations.push(Remediation::Service(ServiceApiCall::from(
589 ServiceModuleInternalApiCall::Restart(name.clone()),
590 privilege.clone(),
591 )));
592 }
593 ServiceAction::Reloaded => {
594 remediations.push(Remediation::Service(ServiceApiCall::from(
595 ServiceModuleInternalApiCall::Reload(name.clone()),
596 privilege.clone(),
597 )));
598 }
599 },
600 OsKind::FreeBsd(_) | OsKind::MacOs(_) | OsKind::Unknown => {
601 return Err(RegentError::FailedDryRunEvaluation(format!(
602 "Service management is not supported on {:?}",
603 os_kind
604 )));
605 }
606 },
607 }
608
609 if remediations.is_empty() {
610 Ok(AttributeComplianceAssessment::Compliant)
611 } else {
612 Ok(AttributeComplianceAssessment::NonCompliant(
613 RemediationsList::from(remediations)?,
614 ))
615 }
616 }
617}
618
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
620#[serde(rename_all = "PascalCase")]
621pub enum ServiceModuleInternalApiCall {
622 Start(String),
623 Stop(String),
624 Restart(String),
625 Reload(String),
626 Enable(String),
627 Disable(String),
628}
629
630impl std::fmt::Display for ServiceModuleInternalApiCall {
631 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
632 match self {
633 ServiceModuleInternalApiCall::Start(s) => write!(f, "start {}", s),
634 ServiceModuleInternalApiCall::Stop(s) => write!(f, "stop {}", s),
635 ServiceModuleInternalApiCall::Restart(s) => write!(f, "restart {}", s),
636 ServiceModuleInternalApiCall::Reload(s) => write!(f, "reload {}", s),
637 ServiceModuleInternalApiCall::Enable(s) => write!(f, "enable {}", s),
638 ServiceModuleInternalApiCall::Disable(s) => write!(f, "disable {}", s),
639 }
640 }
641}
642
643#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
644pub struct ServiceApiCall {
645 pub api_call: ServiceModuleInternalApiCall,
646 privilege: Privilege,
647}
648
649impl ServiceApiCall {
650 pub fn display(&self) -> String {
651 match &self.api_call {
652 ServiceModuleInternalApiCall::Start(s) => format!("Start service {}", s),
653 ServiceModuleInternalApiCall::Stop(s) => format!("Stop service {}", s),
654 ServiceModuleInternalApiCall::Restart(s) => format!("Restart service {}", s),
655 ServiceModuleInternalApiCall::Reload(s) => format!("Reload service {}", s),
656 ServiceModuleInternalApiCall::Enable(s) => format!("Enable service {}", s),
657 ServiceModuleInternalApiCall::Disable(s) => format!("Disable service {}", s),
658 }
659 }
660
661 fn from(api_call: ServiceModuleInternalApiCall, privilege: Privilege) -> ServiceApiCall {
662 ServiceApiCall {
663 api_call,
664 privilege,
665 }
666 }
667}
668
669impl Check for ServiceApiCall {
670 fn check(&self) -> Result<(), RegentError> {
671 Ok(())
672 }
673
674 fn check_host_compatibility(
675 &self,
676 host_properties: &HostProperties,
677 ) -> Result<(), RegentError> {
678 use crate::hosts::properties::InitSystem;
679 match host_properties.os_kind() {
680 OsKind::Linux(linux_specifics) => match linux_specifics.init_system {
681 InitSystem::Systemd => Ok(()),
682 InitSystem::Unknown => Err(RegentError::IncompatibleHost(
683 "systemctl requires systemd but init system could not be detected".to_string(),
684 )),
685 },
686 #[cfg(feature = "windows")]
687 OsKind::Windows(_) => Ok(()),
688 incompatible_os_kind => Err(RegentError::IncompatibleHost(format!(
689 "Host is {:?} but service management is only supported on Linux with systemd or Windows (with windows feature)",
690 incompatible_os_kind
691 ))),
692 }
693 }
694}
695
696impl<Handler: HostHandler> ReachCompliance<Handler> for ServiceApiCall {
697 async fn call(
698 &self,
699 host_handler: &mut Handler,
700 host_properties: &Option<HostProperties>,
701 _optional_secret_provider: &Option<SecretProvidersPool>,
702 ) -> Result<InternalApiCallOutcome, RegentError> {
703 if let Some(props) = host_properties {
704 self.check_host_compatibility(props)?;
705 }
706
707 let os_kind = host_properties
709 .as_ref()
710 .map(|props| props.os_kind())
711 .unwrap_or(&OsKind::Linux(LinuxSpecifics {
712 linux_flavor: LinuxFlavor::Debian,
713 init_system: InitSystem::Systemd,
714 }));
715
716 match os_kind {
717 #[cfg(feature = "windows")]
718 OsKind::Windows(_) => {
719 let cmd = match &self.api_call {
720 ServiceModuleInternalApiCall::Start(s) => format!("net start {}", s),
721 ServiceModuleInternalApiCall::Stop(s) => format!("net stop {}", s),
722 ServiceModuleInternalApiCall::Restart(s) => {
723 format!("net stop {} && net start {}", s, s)
725 }
726 ServiceModuleInternalApiCall::Reload(s) => {
727 format!("sc control {} 128", s) }
731 ServiceModuleInternalApiCall::Enable(s) => {
732 format!("sc config {} start= auto", s)
733 }
734 ServiceModuleInternalApiCall::Disable(s) => {
735 format!("sc config {} start= disabled", s)
736 }
737 };
738
739 let result = host_handler.run_windows_command(&cmd).await;
740
741 match result {
742 Ok(result) => {
743 if result.return_code == 0 {
744 Ok(InternalApiCallOutcome::Success(None))
745 } else {
746 Ok(InternalApiCallOutcome::Failure(format!(
747 "RC: {}, STDOUT: {}, STDERR: {}",
748 result.return_code, result.stdout, result.stderr
749 )))
750 }
751 }
752 Err(e) => Ok(InternalApiCallOutcome::Failure(format!(
753 "Command execution failed: {:?}",
754 e
755 ))),
756 }
757 }
758 OsKind::Linux(_) => {
759 let cmd = match &self.api_call {
760 ServiceModuleInternalApiCall::Start(s) => format!("systemctl start {}", s),
761 ServiceModuleInternalApiCall::Stop(s) => format!("systemctl stop {}", s),
762 ServiceModuleInternalApiCall::Restart(s) => format!("systemctl restart {}", s),
763 ServiceModuleInternalApiCall::Reload(s) => format!("systemctl reload {}", s),
764 ServiceModuleInternalApiCall::Enable(s) => format!("systemctl enable {}", s),
765 ServiceModuleInternalApiCall::Disable(s) => format!("systemctl disable {}", s),
766 };
767
768 let result = host_handler.run_command(&cmd, &self.privilege).await;
769
770 match result {
771 Ok(result) => {
772 if result.return_code == 0 {
773 Ok(InternalApiCallOutcome::Success(None))
774 } else {
775 Ok(InternalApiCallOutcome::Failure(format!(
776 "RC: {}, STDOUT: {}, STDERR: {}",
777 result.return_code, result.stdout, result.stderr
778 )))
779 }
780 }
781 Err(e) => Ok(InternalApiCallOutcome::Failure(format!(
782 "Command execution failed: {:?}",
783 e
784 ))),
785 }
786 }
787 OsKind::FreeBsd(_) | OsKind::MacOs(_) | OsKind::Unknown => {
788 Err(RegentError::FailedDryRunEvaluation(format!(
789 "Service management is not supported on {:?}",
790 os_kind
791 )))
792 }
793 }
794 }
795}
796
797async fn service_is_active<Handler: HostHandler>(
798 host_handler: &mut Handler,
799 name: &str,
800) -> Result<ServiceActivityStatus, String> {
801 match host_handler
802 .run_command(&format!("systemctl is-active {}", name), &Privilege::None)
803 .await
804 {
805 Ok(r) => match r.return_code {
806 0 => Ok(ServiceActivityStatus::Active),
807 3 => Ok(ServiceActivityStatus::Inactive),
808 4 => Ok(ServiceActivityStatus::ServiceNotFound),
809 _ => Ok(ServiceActivityStatus::UnsupportedCase(format!("{:?}", r))),
810 },
811 Err(e) => Err(format!("Unable to check active state of {}: {:?}", name, e)),
812 }
813}
814
815async fn service_is_enabled<Handler: HostHandler>(
816 host_handler: &mut Handler,
817 name: &str,
818) -> Result<ServiceEnablingStatus, String> {
819 match host_handler
820 .run_command(&format!("systemctl is-enabled {}", name), &Privilege::None)
821 .await
822 {
823 Ok(r) => match r.return_code {
824 0 => Ok(ServiceEnablingStatus::Enabled),
825 1 | 3 => Ok(ServiceEnablingStatus::Disabled),
826 4 => Ok(ServiceEnablingStatus::ServiceNotFound),
827 _ => Ok(ServiceEnablingStatus::UnsupportedCase(format!("{:?}", r))),
828 },
829 Err(e) => Err(format!(
830 "Unable to check enabled state of {}: {:?}",
831 name, e
832 )),
833 }
834}
835
836#[cfg(feature = "windows")]
837async fn windows_service_is_active<Handler: HostHandler>(
838 host_handler: &mut Handler,
839 name: &str,
840) -> Result<ServiceActivityStatus, String> {
841 match host_handler
842 .run_windows_command(&format!("sc query {}", name))
843 .await
844 {
845 Ok(r) => {
846 if r.return_code != 0 {
849 if r.stdout.contains("does not exist") || r.stderr.contains("does not exist") {
851 return Ok(ServiceActivityStatus::ServiceNotFound);
852 } else {
853 return Ok(ServiceActivityStatus::UnsupportedCase(format!(
854 "Unable to check service enabling status (command issue): {:?}",
855 r
856 )));
857 }
858 }
859
860 let output = r.stdout.to_lowercase();
863 if output.contains("running") {
864 Ok(ServiceActivityStatus::Active)
865 } else if output.contains("stopped") || output.contains("pending") {
866 Ok(ServiceActivityStatus::Inactive)
867 } else {
868 Ok(ServiceActivityStatus::UnsupportedCase(format!(
869 "Unable to check service activity (output parsing issue): {:?}",
870 r
871 )))
872 }
873 }
874 Err(e) => Err(format!("Unable to check active state of {}: {:?}", name, e)),
875 }
876}
877
878#[cfg(feature = "windows")]
879async fn windows_service_is_enabled<Handler: HostHandler>(
880 host_handler: &mut Handler,
881 name: &str,
882) -> Result<ServiceEnablingStatus, String> {
883 match host_handler
884 .run_windows_command(&format!("sc qc {}", name))
885 .await
886 {
887 Ok(r) => {
888 if r.return_code != 0 {
891 if r.stdout.contains("does not exist") || r.stderr.contains("does not exist") {
892 return Ok(ServiceEnablingStatus::ServiceNotFound);
893 } else {
894 return Ok(ServiceEnablingStatus::UnsupportedCase(format!(
895 "Unable to check service enabling status (command issue): {:?}",
896 r
897 )));
898 }
899 }
900
901 let output = r.stdout.to_lowercase();
904 if output.contains("auto_start") || output.contains("2") {
905 Ok(ServiceEnablingStatus::Enabled)
906 } else if output.contains("disabled") || output.contains("3") || output.contains("4") {
907 Ok(ServiceEnablingStatus::Disabled)
908 } else {
909 Ok(ServiceEnablingStatus::UnsupportedCase(format!(
910 "Unable to check service enabling status (output parsing issue): {:?}",
911 r
912 )))
913 }
914 }
915 Err(e) => Err(format!(
916 "Unable to check enabled state of {}: {:?}",
917 name, e
918 )),
919 }
920}
921
922enum ServiceActivityStatus {
923 Active,
924 Inactive,
925 ServiceNotFound,
926 UnsupportedCase(String),
927}
928
929enum ServiceEnablingStatus {
930 Enabled,
931 Disabled,
932 ServiceNotFound,
933 UnsupportedCase(String),
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939
940 #[test]
941 fn parsing_service_module_block_from_yaml_str() {
942 let raw = "---
943- Name: nginx
944 State: Started
945 Enabled: true
946
947- Name: nginx
948 State: Stopped
949 Enabled: false
950
951- Name: nginx
952 Action: Restarted
953
954- Name: nginx
955 Action: Reloaded
956
957- Name: nginx
958 Enabled: true
959 ";
960 let _: Vec<ServiceExpectedState> = yaml_serde::from_str(raw).unwrap();
961 }
962
963 #[test]
964 fn named_constructors_match_explicit_state() {
965 assert_eq!(
966 ServiceExpectedState::started_and_enabled("nginx"),
967 ServiceExpectedState::state("nginx", ServiceExpectedStatus::Started, true)
968 );
969 assert_eq!(
970 ServiceExpectedState::started_and_disabled("nginx"),
971 ServiceExpectedState::state("nginx", ServiceExpectedStatus::Started, false)
972 );
973 assert_eq!(
974 ServiceExpectedState::stopped_and_enabled("nginx"),
975 ServiceExpectedState::state("nginx", ServiceExpectedStatus::Stopped, true)
976 );
977 assert_eq!(
978 ServiceExpectedState::stopped_and_disabled("nginx"),
979 ServiceExpectedState::state("nginx", ServiceExpectedStatus::Stopped, false)
980 );
981 assert_eq!(
982 ServiceExpectedState::disabled("nginx"),
983 ServiceExpectedState::enabled("nginx", false)
984 );
985 }
986}