use std::path::PathBuf;
use super::{
HandoffAttemptDecision, HandoffAttemptFailure, HandoffFallbackDecision, HandoffFallbackReason,
HandoffToken,
};
pub const SCM_RIGHTS_TRANSPORT_SUPPORTED: bool =
running_process_platform_internal::LEGACY_SCM_RIGHTS_TRANSPORT_SUPPORTED;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct UnixFileDescriptor(i32);
impl UnixFileDescriptor {
pub fn new(raw_fd: i32) -> Self {
Self(raw_fd)
}
pub fn raw(self) -> i32 {
self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnixHandoffSocket {
pub path: PathBuf,
}
impl UnixHandoffSocket {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScmRightsAttempt {
pub fd: UnixFileDescriptor,
pub backend_socket: UnixHandoffSocket,
pub handoff_token: HandoffToken,
}
impl ScmRightsAttempt {
pub fn new(
fd: UnixFileDescriptor,
backend_socket: UnixHandoffSocket,
handoff_token: HandoffToken,
) -> Self {
Self {
fd,
backend_socket,
handoff_token,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScmRightsSuccess {
pub sent_fd: UnixFileDescriptor,
pub backend_socket: UnixHandoffSocket,
pub handoff_token: HandoffToken,
}
impl ScmRightsSuccess {
pub fn new(
sent_fd: UnixFileDescriptor,
backend_socket: UnixHandoffSocket,
handoff_token: HandoffToken,
) -> Self {
Self {
sent_fd,
backend_socket,
handoff_token,
}
}
}
pub type ScmRightsResult = Result<ScmRightsSuccess, ScmRightsError>;
pub fn try_send_scm_rights(attempt: &ScmRightsAttempt) -> ScmRightsResult {
running_process_platform_internal::legacy_send_fd_to(
&attempt.backend_socket.path,
attempt.fd.raw(),
attempt.handoff_token.as_bytes(),
)
.map_err(|error| legacy_error(attempt, error, true))?;
Ok(ScmRightsSuccess::new(
attempt.fd,
attempt.backend_socket.clone(),
attempt.handoff_token,
))
}
pub fn try_send_scm_rights_over(socket_fd: i32, attempt: &ScmRightsAttempt) -> ScmRightsResult {
running_process_platform_internal::legacy_send_fd_over(
socket_fd,
attempt.fd.raw(),
attempt.handoff_token.as_bytes(),
)
.map_err(|error| legacy_error(attempt, error, false))?;
Ok(ScmRightsSuccess::new(
attempt.fd,
attempt.backend_socket.clone(),
attempt.handoff_token,
))
}
fn legacy_error(
attempt: &ScmRightsAttempt,
error: running_process_platform_internal::LegacyHandoffError,
connecting: bool,
) -> ScmRightsError {
use running_process_platform_internal::platform::ipc::HandoffTransferErrorKind;
if let Some((sent_bytes, expected_bytes)) = error.partial_counts() {
return ScmRightsError::PartialSend {
fd: attempt.fd.raw(),
socket: attempt.backend_socket.path.clone(),
sent_bytes,
expected_bytes,
};
}
match error.kind() {
HandoffTransferErrorKind::Unsupported => ScmRightsError::UnsupportedPlatform,
HandoffTransferErrorKind::PermissionDenied => ScmRightsError::PermissionDenied {
fd: if connecting { -1 } else { attempt.fd.raw() },
socket: attempt.backend_socket.path.clone(),
},
HandoffTransferErrorKind::BackendUnavailable => ScmRightsError::BackendSocketUnavailable {
socket: attempt.backend_socket.path.clone(),
},
HandoffTransferErrorKind::WouldBlock => ScmRightsError::WouldBlock {
socket: attempt.backend_socket.path.clone(),
},
HandoffTransferErrorKind::Failed => ScmRightsError::SendFailed {
fd: attempt.fd.raw(),
socket: attempt.backend_socket.path.clone(),
raw_os_error: error.raw_os_error(),
},
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum ScmRightsError {
#[error("SCM_RIGHTS handoff transport is unsupported on this platform")]
UnsupportedPlatform,
#[error("permission denied passing fd {fd} to backend handoff socket {socket}")]
PermissionDenied {
fd: i32,
socket: PathBuf,
},
#[error("backend handoff socket is unavailable: {socket}")]
BackendSocketUnavailable {
socket: PathBuf,
},
#[error("SCM_RIGHTS send would block for backend handoff socket {socket}")]
WouldBlock {
socket: PathBuf,
},
#[error("SCM_RIGHTS send failed for fd {fd} to backend handoff socket {socket}")]
SendFailed {
fd: i32,
socket: PathBuf,
raw_os_error: Option<i32>,
},
#[error("handoff offer delivery failed after passing fd {fd} to backend socket {socket}")]
PostTransferDeliveryFailed {
fd: i32,
socket: PathBuf,
},
#[error(
"SCM_RIGHTS send was partial ({sent_bytes}/{expected_bytes} bytes) for fd {fd} to backend handoff socket {socket}"
)]
PartialSend {
fd: i32,
socket: PathBuf,
sent_bytes: usize,
expected_bytes: usize,
},
#[error("backend handoff socket {socket} did not acknowledge passed fd")]
BackendAckTimeout {
socket: PathBuf,
},
}
impl ScmRightsError {
pub fn attempt_failure(&self) -> Option<HandoffAttemptFailure> {
match self {
Self::UnsupportedPlatform => None,
Self::PermissionDenied { .. } => Some(HandoffAttemptFailure::PermissionDenied),
Self::BackendSocketUnavailable { .. }
| Self::WouldBlock { .. }
| Self::SendFailed { .. }
| Self::PostTransferDeliveryFailed { .. }
| Self::PartialSend { .. }
| Self::BackendAckTimeout { .. } => Some(HandoffAttemptFailure::BackendAckTimeout),
}
}
pub fn fallback_reason(&self) -> HandoffFallbackReason {
match self.attempt_failure() {
Some(failure) => failure.into(),
None => HandoffFallbackReason::ServicePolicyDisabled,
}
}
pub fn fallback_decision(&self) -> HandoffFallbackDecision {
HandoffFallbackDecision::new(self.fallback_reason())
}
pub fn fallback_attempt_decision(&self) -> HandoffAttemptDecision {
HandoffAttemptDecision::FallbackToReconnect(self.fallback_decision())
}
pub fn is_fallback_safe(&self) -> bool {
let fallback = self.fallback_decision();
fallback.uses_backend_reconnect() && !fallback.sends_client_error()
}
pub fn fd_may_have_reached_backend(&self) -> bool {
matches!(self, Self::PostTransferDeliveryFailed { .. })
|| matches!(self, Self::PartialSend { sent_bytes, .. } if *sent_bytes > 0)
}
}
#[cfg(test)]
mod platform_neutral_tests {
use super::ScmRightsError;
#[test]
fn positive_partial_send_tracks_indeterminate_fd_delivery() {
let error = ScmRightsError::PartialSend {
fd: 7,
socket: "handoff".into(),
sent_bytes: 1,
expected_bytes: 16,
};
assert!(error.fd_may_have_reached_backend());
assert!(error.is_fallback_safe());
}
#[test]
fn failed_offer_after_transfer_tracks_backend_ownership() {
let error = ScmRightsError::PostTransferDeliveryFailed {
fd: 7,
socket: "handoff".into(),
};
assert!(error.fd_may_have_reached_backend());
assert!(error.is_fallback_safe());
}
}