blob: 633040427a85b9af9744e8b0cb23e74bc208d024 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use base::deserialize_and_recv;
use base::named_pipes;
use base::named_pipes::BlockingMode;
use base::named_pipes::FramingMode;
use base::serialize_and_send;
use base::Error as SysError;
use base::FromRawDescriptor;
use base::IntoRawDescriptor;
use base::PipeConnection;
use base::SafeDescriptor;
use base::Tube;
use base::TubeError;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error as ThisError;
pub type PackedTubeResult<T> = Result<T, PackedTubeError>;
#[derive(Debug, ThisError)]
pub enum PackedTubeError {
#[error("Serializing and recving failed: {0}")]
DeserializeRecvError(TubeError),
#[error("Named pipe error: {0}")]
PipeError(SysError),
#[error("Serializing and sending failed: {0}")]
SerializeSendError(TubeError),
}
#[derive(Deserialize, Serialize)]
struct PackedTube {
tube: Tube,
server_pipe: PipeConnection,
}
/// Sends a [`Tube`] through a protocol that expects a [`RawDescriptor`](base::RawDescriptor).
///
/// A packed tube works by creating a named pipe pair, and serializing both the Tube and the
/// server end of the pipe. Then, it returns the client end of the named pipe pair, which can be
/// used as the desired descriptor to send / duplicate to the target.
///
/// The receiver will need to use [`unpack()`] to read the message off the pipe, and thus
/// extract a real [Tube]. It will also read the server end of the pipe, and close it. The
/// `receiver_pid` is the pid of the process that will be unpacking the tube.
///
/// # Safety
/// To prevent dangling handles, the resulting descriptor must be passed to [`unpack()`],
/// in the process which corresponds to `receiver_pid`.
pub unsafe fn pack(tube: Tube, receiver_pid: u32) -> PackedTubeResult<SafeDescriptor> {
let (server_pipe, client_pipe) = named_pipes::pair(
&FramingMode::Message,
&BlockingMode::Wait,
/* timeout= */ 0,
)
.map_err(SysError::from)
.map_err(PackedTubeError::PipeError)?;
let packed = PackedTube { tube, server_pipe };
// Serialize the packed tube, which also duplicates the server end of the pipe into the other
// process. This lets us drop it on our side without destroying the channel.
serialize_and_send(
|buf| packed.server_pipe.write(buf),
&packed,
Some(receiver_pid),
)
.map_err(PackedTubeError::SerializeSendError)?;
Ok(SafeDescriptor::from_raw_descriptor(
client_pipe.into_raw_descriptor(),
))
}
/// Unpacks a tube from a client descriptor. This must come from a packed tube.
///
/// # Safety
/// The descriptor passed in must come from [`pack()`].
pub unsafe fn unpack(descriptor: SafeDescriptor) -> PackedTubeResult<Tube> {
let pipe = PipeConnection::from_raw_descriptor(
descriptor.into_raw_descriptor(),
FramingMode::Message,
BlockingMode::Wait,
);
// Safe because we own the descriptor and it came from a PackedTube.
let unpacked: PackedTube = deserialize_and_recv(|buf| pipe.read(buf))
.map_err(PackedTubeError::DeserializeRecvError)?;
// By dropping `unpacked` we close the server end of the pipe.
Ok(unpacked.tube)
}
#[cfg(test)]
mod tests {
use base::Tube;
use crate::packed_tube;
#[test]
/// Tests packing and unpacking.
fn test_pack_unpack() {
let (tube_server, tube_client) = Tube::pair().unwrap();
// SAFETY: we provide a valid descriptor for pack()
let packed_tube = unsafe { packed_tube::pack(tube_client, std::process::id()).unwrap() };
// SAFETY: get_descriptor clones the underlying pipe.
let recovered_tube = unsafe { packed_tube::unpack(packed_tube).unwrap() };
let test_message = "Test message".to_string();
tube_server.send(&test_message).unwrap();
let received: String = recovered_tube.recv().unwrap();
assert_eq!(test_message, received);
}
}