1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use base::deserialize_and_recv;
6 use base::named_pipes;
7 use base::named_pipes::BlockingMode;
8 use base::named_pipes::FramingMode;
9 use base::serialize_and_send;
10 use base::Error as SysError;
11 use base::FromRawDescriptor;
12 use base::IntoRawDescriptor;
13 use base::PipeConnection;
14 use base::SafeDescriptor;
15 use base::Tube;
16 use base::TubeError;
17 use serde::Deserialize;
18 use serde::Serialize;
19 use thiserror::Error as ThisError;
20
21 pub type PackedTubeResult<T> = Result<T, PackedTubeError>;
22
23 #[derive(Debug, ThisError)]
24 pub enum PackedTubeError {
25 #[error("Serializing and recving failed: {0}")]
26 DeserializeRecvError(TubeError),
27 #[error("Named pipe error: {0}")]
28 PipeError(SysError),
29 #[error("Serializing and sending failed: {0}")]
30 SerializeSendError(TubeError),
31 }
32
33 #[derive(Deserialize, Serialize)]
34 struct PackedTube {
35 tube: Tube,
36 server_pipe: PipeConnection,
37 }
38
39 /// Sends a [Tube] through a protocol that expects a [RawDescriptor].
40 ///
41 /// A packed tube works by creating a named pipe pair, and serializing both the Tube and the
42 /// server end of the pipe. Then, it returns the client end of the named pipe pair, which can be
43 /// used as the desired descriptor to send / duplicate to the target.
44 ///
45 /// The receiver will need to use [packed_tube::unpack] to read the message off the pipe, and thus
46 /// extract a real [Tube]. It will also read the server end of the pipe, and close it. The
47 /// `receiver_pid` is the pid of the process that will be unpacking the tube.
48 ///
49 /// # Safety
50 /// To prevent dangling handles, the resulting descriptor must be passed to [packed_tube::unpack],
51 /// in the process which corresponds to `receiver_pid`.
pack(tube: Tube, receiver_pid: u32) -> PackedTubeResult<SafeDescriptor>52 pub unsafe fn pack(tube: Tube, receiver_pid: u32) -> PackedTubeResult<SafeDescriptor> {
53 let (server_pipe, client_pipe) = named_pipes::pair(
54 &FramingMode::Message,
55 &BlockingMode::Wait,
56 /* timeout= */ 0,
57 )
58 .map_err(SysError::from)
59 .map_err(PackedTubeError::PipeError)?;
60
61 let packed = PackedTube { tube, server_pipe };
62
63 // Serialize the packed tube, which also duplicates the server end of the pipe into the other
64 // process. This lets us drop it on our side without destroying the channel.
65 serialize_and_send(
66 |buf| packed.server_pipe.write(buf),
67 &packed,
68 Some(receiver_pid),
69 )
70 .map_err(PackedTubeError::SerializeSendError)?;
71
72 Ok(SafeDescriptor::from_raw_descriptor(
73 client_pipe.into_raw_descriptor(),
74 ))
75 }
76
77 /// Unpacks a tube from a client descriptor. This must come from a packed tube.
78 ///
79 /// # Safety
80 /// The descriptor passed in must come from [packed_tube::pack].
unpack(descriptor: SafeDescriptor) -> PackedTubeResult<Tube>81 pub unsafe fn unpack(descriptor: SafeDescriptor) -> PackedTubeResult<Tube> {
82 let pipe = PipeConnection::from_raw_descriptor(
83 descriptor.into_raw_descriptor(),
84 FramingMode::Message,
85 BlockingMode::Wait,
86 );
87 // Safe because we own the descriptor and it came from a PackedTube.
88 let unpacked: PackedTube = deserialize_and_recv(|buf| pipe.read(buf))
89 .map_err(PackedTubeError::DeserializeRecvError)?;
90 // By dropping `unpacked` we close the server end of the pipe.
91 Ok(unpacked.tube)
92 }
93
94 #[cfg(test)]
95 mod tests {
96 use base::Tube;
97
98 use crate::packed_tube;
99
100 #[test]
101 /// Tests packing and unpacking.
test_pack_unpack()102 fn test_pack_unpack() {
103 let (tube_server, tube_client) = Tube::pair().unwrap();
104 let packed_tube = unsafe { packed_tube::pack(tube_client, std::process::id()).unwrap() };
105
106 // Safe because get_descriptor clones the underlying pipe.
107 let recovered_tube = unsafe { packed_tube::unpack(packed_tube).unwrap() };
108
109 let test_message = "Test message".to_string();
110 tube_server.send(&test_message).unwrap();
111 let received: String = recovered_tube.recv().unwrap();
112
113 assert_eq!(test_message, received);
114 }
115 }
116