use grate::tracing; use windows::{ Win32::{ Foundation, System::Services, }, core::{ HSTRING, PWSTR, }, }; lazy_static::lazy_static! { static ref PSQL_REGEX: regex::Regex = regex::Regex::new(r#"^(?:postgresql|psql|postgres)-.*([\d\.]*)"#).unwrap(); } pub fn ensure_postgres_started() -> windows::core::Result { let sc_manager = unsafe { Services::OpenSCManagerW( None, None, (Foundation::GENERIC_READ | Foundation::GENERIC_WRITE).0, )? }; let Some(svc) = get_psql_service(sc_manager)? else { return Ok(false); }; match svc.status.dwCurrentState { Services::SERVICE_RUNNING => { tracing::info!("postgres was already running, done"); return Ok(false); }, Services::SERVICE_START_PENDING => { tracing::info!("postgres was already in startup, done"); return Ok(false); }, _ => { tracing::info!("postgres is not running: starting it") }, } unsafe { let service = Services::OpenServiceW(sc_manager, &HSTRING::from(&svc.name), Services::SERVICE_START)?; Services::StartServiceW(service, None)?; tracing::info!("started postgres service"); Services::CloseServiceHandle(service)?; Services::CloseServiceHandle(sc_manager)?; } Ok(true) } pub fn shutdown_postgres() -> windows::core::Result<()> { let sc_manager = unsafe { Services::OpenSCManagerW( None, None, (Foundation::GENERIC_READ | Foundation::GENERIC_WRITE).0, ) }?; let Some(svc) = get_psql_service(sc_manager)? else { tracing::warn!("wasn't able to find postgres service"); return Ok(()); }; if svc.status.dwCurrentState != Services::SERVICE_RUNNING { tracing::warn!("postgres wasn't in running state, not attempting to shut it down"); return Ok(()); } let service = unsafe { Services::OpenServiceW(sc_manager, &HSTRING::from(&svc.name), Services::SERVICE_STOP) }?; let mut params = Services::SERVICE_CONTROL_STATUS_REASON_PARAMSW { dwReason: Services::SERVICE_STOP_REASON_FLAG_PLANNED | Services::SERVICE_STOP_REASON_MAJOR_NONE | Services::SERVICE_STOP_REASON_MINOR_NONE, pszComment: PWSTR::null(), ServiceStatus: Services::SERVICE_STATUS_PROCESS::default(), }; unsafe { Services::ControlServiceExW( service, Services::SERVICE_CONTROL_STOP, Services::SERVICE_CONTROL_STATUS_REASON_INFO, &mut params as *mut _ as _, )?; } tracing::info!("stopped postgres service"); unsafe { Services::CloseServiceHandle(service)?; Services::CloseServiceHandle(sc_manager)?; } Ok(()) } pub fn get_psql_service( sc_manager: Services::SC_HANDLE, ) -> windows::core::Result> { let services = list_services(sc_manager)?; let mut psql_services = services.into_iter().filter(|status| PSQL_REGEX.is_match(&status.name)).collect::>(); match psql_services.len() { 1 => {}, 0 => { tracing::warn!("unable to identify an installed postgres service"); return Ok(None); }, other => { tracing::warn!(n_candidates = other, "unable to identify postgres service"); return Ok(None); }, } let svc = psql_services.remove(0); tracing::debug!(name = %svc.name, "identified postgres service"); Ok(Some(svc)) } const RESULT_SIZE: usize = size_of::(); pub struct ServiceStatus { pub display_name: String, pub name: String, pub status: Services::SERVICE_STATUS_PROCESS, } pub fn list_services(sc_manager: Services::SC_HANDLE) -> windows::core::Result> { let mut bytes_needed: u32 = 0; let mut services_returned: u32 = 0; let mut resume = 0; let mut svcs = vec![]; loop { let mut data = [0u8; 256 * 1024]; let result = unsafe { Services::EnumServicesStatusExW( sc_manager, Services::SC_ENUM_PROCESS_INFO, Services::SERVICE_WIN32, Services::SERVICE_STATE_ALL, Some(&mut data), &mut bytes_needed as *mut _, &mut services_returned as *mut _, Some(&mut resume), None, ) }; if let Err(ref e) = result && e.code() != Foundation::ERROR_MORE_DATA.to_hresult() { return result.map(|_| unreachable!()); } let data = bytemuck::cast_slice::( &data[..services_returned as usize * RESULT_SIZE], ); let elems = data .iter() .map(|x| unsafe { core::mem::transmute::<_, Services::ENUM_SERVICE_STATUS_PROCESSW>(*x) }) .map(|x| ServiceStatus { display_name: unsafe { x.lpDisplayName.to_string() }.unwrap(), name: unsafe { x.lpServiceName.to_string() }.unwrap(), status: x.ServiceStatusProcess, }); svcs.extend(elems); if result.is_ok() { break; } } Ok(svcs) }