Merge "libsnapshot: OTA upgrade when vendor partition is S"
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index d5327db..efc13df 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "debuggerd_test"
+ },
+ {
+ "name": "libtombstoned_client_rust_test"
}
]
}
diff --git a/debuggerd/rust/tombstoned_client/Android.bp b/debuggerd/rust/tombstoned_client/Android.bp
new file mode 100644
index 0000000..981f8a4
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/Android.bp
@@ -0,0 +1,58 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libtombstoned_client_wrapper",
+ srcs: [
+ "wrapper.cpp",
+ ],
+ generated_sources: [
+ "libtombstoned_client_rust_bridge_code"
+ ],
+ header_libs: [
+ "libbase_headers",
+ "libdebuggerd_common_headers",
+ ],
+ shared_libs: [
+ "libtombstoned_client",
+ ],
+}
+
+rust_defaults {
+ name: "libtombstoned_client_rust_defaults",
+ crate_name: "tombstoned_client",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libcxx",
+ "libthiserror",
+ ],
+ static_libs: [
+ "libtombstoned_client_wrapper",
+ ],
+ shared_libs: [
+ "libtombstoned_client",
+ ],
+}
+
+rust_library {
+ name: "libtombstoned_client_rust",
+ defaults: ["libtombstoned_client_rust_defaults"],
+ apex_available: ["com.android.virt"],
+}
+
+rust_test {
+ name: "libtombstoned_client_rust_test",
+ defaults: ["libtombstoned_client_rust_defaults"],
+ require_root: true,
+ test_suites: ["device-tests"],
+}
+
+genrule {
+ name: "libtombstoned_client_rust_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["libtombstoned_client_cxx_generated.cc"],
+}
diff --git a/debuggerd/rust/tombstoned_client/src/lib.rs b/debuggerd/rust/tombstoned_client/src/lib.rs
new file mode 100644
index 0000000..5c8abef
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/src/lib.rs
@@ -0,0 +1,153 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Rust wrapper for tombstoned client.
+
+pub use ffi::DebuggerdDumpType;
+use std::fs::File;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use thiserror::Error;
+
+/// Error communicating with tombstoned.
+#[derive(Clone, Debug, Error, Eq, PartialEq)]
+#[error("Error communicating with tombstoned")]
+pub struct Error;
+
+/// File descriptors for communicating with tombstoned.
+pub struct TombstonedConnection {
+ /// The socket connection to tombstoned.
+ ///
+ /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library
+ /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds`
+ /// or something when all we do is pass it back to C++ or close it.
+ tombstoned_socket: File,
+ /// The file descriptor for text output.
+ pub text_output: Option<File>,
+ /// The file descriptor for proto output.
+ pub proto_output: Option<File>,
+}
+
+impl TombstonedConnection {
+ unsafe fn from_raw_fds(
+ tombstoned_socket: RawFd,
+ text_output_fd: RawFd,
+ proto_output_fd: RawFd,
+ ) -> Self {
+ Self {
+ tombstoned_socket: File::from_raw_fd(tombstoned_socket),
+ text_output: if text_output_fd >= 0 {
+ Some(File::from_raw_fd(text_output_fd))
+ } else {
+ None
+ },
+ proto_output: if proto_output_fd >= 0 {
+ Some(File::from_raw_fd(proto_output_fd))
+ } else {
+ None
+ },
+ }
+ }
+
+ /// Connects to tombstoned.
+ pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> {
+ let mut tombstoned_socket = -1;
+ let mut text_output_fd = -1;
+ let mut proto_output_fd = -1;
+ if ffi::tombstoned_connect_files(
+ pid,
+ &mut tombstoned_socket,
+ &mut text_output_fd,
+ &mut proto_output_fd,
+ dump_type,
+ ) {
+ Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
+ } else {
+ Err(Error)
+ }
+ }
+
+ /// Notifies tombstoned that the dump is complete.
+ pub fn notify_completion(&self) -> Result<(), Error> {
+ if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) {
+ Ok(())
+ } else {
+ Err(Error)
+ }
+ }
+}
+
+#[cxx::bridge]
+mod ffi {
+ /// The type of dump.
+ enum DebuggerdDumpType {
+ /// A native backtrace.
+ #[cxx_name = "kDebuggerdNativeBacktrace"]
+ NativeBacktrace,
+ /// A tombstone.
+ #[cxx_name = "kDebuggerdTombstone"]
+ Tombstone,
+ /// A Java backtrace.
+ #[cxx_name = "kDebuggerdJavaBacktrace"]
+ JavaBacktrace,
+ /// Any intercept.
+ #[cxx_name = "kDebuggerdAnyIntercept"]
+ AnyIntercept,
+ /// A tombstone proto.
+ #[cxx_name = "kDebuggerdTombstoneProto"]
+ TombstoneProto,
+ }
+
+ unsafe extern "C++" {
+ include!("wrapper.hpp");
+
+ type DebuggerdDumpType;
+
+ fn tombstoned_connect_files(
+ pid: i32,
+ tombstoned_socket: &mut i32,
+ text_output_fd: &mut i32,
+ proto_output_fd: &mut i32,
+ dump_type: DebuggerdDumpType,
+ ) -> bool;
+
+ fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::{io::Write, process};
+
+ // Verify that we can connect to tombstoned, write something to the file descriptor it returns,
+ // and notify completion, without any errors.
+ #[test]
+ fn test() {
+ let connection =
+ TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone)
+ .expect("Failed to connect to tombstoned.");
+
+ assert!(connection.proto_output.is_none());
+ connection
+ .text_output
+ .as_ref()
+ .expect("No text output FD returned.")
+ .write_all(b"test data")
+ .expect("Failed to write to text output FD.");
+
+ connection
+ .notify_completion()
+ .expect("Failed to notify completion.");
+ }
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.cpp b/debuggerd/rust/tombstoned_client/wrapper.cpp
new file mode 100644
index 0000000..7492329
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wrapper.hpp"
+
+#include <android-base/unique_fd.h>
+
+#include "tombstoned/tombstoned.h"
+
+using android::base::unique_fd;
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+ int& proto_output_fd, DebuggerdDumpType dump_type) {
+ unique_fd tombstoned_socket_unique, text_output_unique, proto_output_unique;
+
+ bool result = tombstoned_connect(pid, &tombstoned_socket_unique, &text_output_unique,
+ &proto_output_unique, dump_type);
+ if (result) {
+ tombstoned_socket = tombstoned_socket_unique.release();
+ text_output_fd = text_output_unique.release();
+ proto_output_fd = proto_output_unique.release();
+ }
+
+ return result;
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.hpp b/debuggerd/rust/tombstoned_client/wrapper.hpp
new file mode 100644
index 0000000..95d3865
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include "tombstoned/tombstoned.h"
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+ int& proto_output_fd, DebuggerdDumpType dump_type);
diff --git a/init/service.cpp b/init/service.cpp
index 2ebf87e..077477a 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -491,10 +491,15 @@
// Wait until the cgroups have been created and until the cgroup controllers have been
// activated.
- if (std::byte byte; read((*pipefd)[0], &byte, 1) < 0) {
+ char byte = 0;
+ if (read((*pipefd)[0], &byte, 1) < 0) {
PLOG(ERROR) << "failed to read from notification channel";
}
pipefd.reset();
+ if (!byte) {
+ LOG(FATAL) << "Service '" << name_ << "' failed to start due to a fatal error";
+ _exit(EXIT_FAILURE);
+ }
if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
LOG(ERROR) << "failed to set task profiles";
@@ -647,9 +652,14 @@
limit_percent_ != -1 || !limit_property_.empty();
errno = -createProcessGroup(proc_attr_.uid, pid_, use_memcg);
if (errno != 0) {
- PLOG(ERROR) << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
- << ") failed for service '" << name_ << "'";
- } else if (use_memcg) {
+ if (char byte = 0; write((*pipefd)[1], &byte, 1) < 0) {
+ return ErrnoError() << "sending notification failed";
+ }
+ return Error() << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
+ << ") failed for service '" << name_ << "'";
+ }
+
+ if (use_memcg) {
ConfigureMemcg();
}
@@ -657,7 +667,7 @@
LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
}
- if (write((*pipefd)[1], "", 1) < 0) {
+ if (char byte = 1; write((*pipefd)[1], &byte, 1) < 0) {
return ErrnoError() << "sending notification failed";
}
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 35bd415..9e914ee 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -27,6 +27,7 @@
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <hidl-util/FQName.h>
+#include <processgroup/processgroup.h>
#include <system/thread_defs.h>
#include "lmkd_service.h"
@@ -395,7 +396,15 @@
Result<void> ServiceParser::ParseTaskProfiles(std::vector<std::string>&& args) {
args.erase(args.begin());
- service_->task_profiles_ = std::move(args);
+ if (service_->task_profiles_.empty()) {
+ service_->task_profiles_ = std::move(args);
+ } else {
+ // Some task profiles might have been added during writepid conversions
+ service_->task_profiles_.insert(service_->task_profiles_.end(),
+ std::make_move_iterator(args.begin()),
+ std::make_move_iterator(args.end()));
+ args.clear();
+ }
return {};
}
@@ -521,8 +530,37 @@
return {};
}
+// Convert legacy paths used to migrate processes between cgroups using writepid command.
+// We can't get these paths from TaskProfiles because profile definitions are changing
+// when we migrate to cgroups v2 while these hardcoded paths stay the same.
+static std::optional<const std::string> ConvertTaskFileToProfile(const std::string& file) {
+ static const std::map<const std::string, const std::string> map = {
+ {"/dev/stune/top-app/tasks", "MaxPerformance"},
+ {"/dev/stune/foreground/tasks", "HighPerformance"},
+ {"/dev/cpuset/camera-daemon/tasks", "CameraServiceCapacity"},
+ {"/dev/cpuset/foreground/tasks", "ProcessCapacityHigh"},
+ {"/dev/cpuset/system-background/tasks", "ServiceCapacityLow"},
+ {"/dev/stune/nnapi-hal/tasks", "NNApiHALPerformance"},
+ {"/dev/blkio/background/tasks", "LowIoPriority"},
+ };
+ auto iter = map.find(file);
+ return iter == map.end() ? std::nullopt : std::make_optional<const std::string>(iter->second);
+}
+
Result<void> ServiceParser::ParseWritepid(std::vector<std::string>&& args) {
args.erase(args.begin());
+ // Convert any cgroup writes into appropriate task_profiles
+ for (auto iter = args.begin(); iter != args.end();) {
+ auto task_profile = ConvertTaskFileToProfile(*iter);
+ if (task_profile) {
+ LOG(WARNING) << "'writepid " << *iter << "' is converted into 'task_profiles "
+ << task_profile.value() << "' for service " << service_->name();
+ service_->task_profiles_.push_back(task_profile.value());
+ iter = args.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
service_->writepid_files_ = std::move(args);
return {};
}
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 263cb73..eed5c65 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -18,6 +18,7 @@
#include <fcntl.h>
#include <grp.h>
+#include <map>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/wait.h>
@@ -305,6 +306,16 @@
} else {
LOG(ERROR) << "cpuset cgroup controller is not mounted!";
}
+
+ // Issue a warning whenever writepid is being used with a cgroup. This can't be done during
+ // command parsing because cgroups might not be configured at the time or parsing.
+ for (const auto& file : *files) {
+ if (CgroupGetControllerFromPath(file, nullptr)) {
+ LOG(WARNING) << "writepid usage with cgroups path '" << file
+ << "' is obsolete, please use task_profiles!";
+ }
+ }
+
std::string pid_str = std::to_string(getpid());
for (const auto& file : *files) {
if (!WriteStringToFile(pid_str, file)) {
diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp
new file mode 100644
index 0000000..0c6349a
--- /dev/null
+++ b/trusty/libtrusty-rs/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libtrusty-rs",
+ crate_name: "trusty",
+ srcs: [
+ "src/lib.rs"
+ ],
+ rustlibs: [
+ "libnix",
+ ],
+}
diff --git a/trusty/libtrusty-rs/src/lib.rs b/trusty/libtrusty-rs/src/lib.rs
new file mode 100644
index 0000000..fe65a27
--- /dev/null
+++ b/trusty/libtrusty-rs/src/lib.rs
@@ -0,0 +1,146 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functionality for communicating with Trusty services.
+//!
+//! This crate provides the [`TipcChannel`] type, which allows you to establish a
+//! connection to a Trusty service and then communicate with that service.
+//!
+//! # Usage
+//!
+//! To connect to a Trusty service you need two things:
+//!
+//! * The filesystem path to the Trusty IPC device. This is usually
+//! `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`].
+//! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`.
+//!
+//! Pass these values to [`TipcChannel::connect`] to establish a connection to a
+//! service.
+//!
+//! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv]
+//! methods to communicate with the service. Messages are passed as byte buffers, and
+//! each Trusty service has its own protocol for what data messages are expected to
+//! contain. Consult the documentation for the service you are communicating with to
+//! determine how to format outgoing messages and interpret incoming ones.
+//!
+//! The connection is closed automatically when [`TipcChannel`] is dropped.
+//!
+//! # Examples
+//!
+//! This example is a simplified version of the echo test from `tipc-test-rs`:
+//!
+//! ```no_run
+//! use trusty::{DEFAULT_DEVICE, TipcChannel};
+//! use std::io::{Read, Write};
+//!
+//! let mut chann = TipcChannel::connect(
+//! DEFAULT_DEVICE,
+//! "com.android.ipc-unittest.srv.echo",
+//! ).unwrap();
+//!
+//! chann.send("Hello, world!".as_bytes()).unwrap();
+//!
+//! let mut read_buf = [0u8; 1024];
+//! let read_len = stream.read(&mut read_buf[..]).unwrap();
+//!
+//! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap();
+//! assert_eq!("Hello, world!", response);
+//!
+//! // The connection is closed here.
+//! ```
+
+use crate::sys::tipc_connect;
+use std::ffi::CString;
+use std::fs::File;
+use std::io;
+use std::io::prelude::*;
+use std::os::unix::prelude::AsRawFd;
+use std::path::Path;
+
+mod sys;
+
+/// The default filesystem path for the Trusty IPC device.
+pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0";
+
+/// A channel for communicating with a Trusty service.
+///
+/// See the [crate-level documentation][crate] for usage details and examples.
+#[derive(Debug)]
+pub struct TipcChannel(File);
+
+impl TipcChannel {
+ /// Attempts to establish a connection to the specified Trusty service.
+ ///
+ /// The first argument is the path of the Trusty device in the local filesystem,
+ /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service
+ /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`.
+ ///
+ /// # Panics
+ ///
+ /// This function will panic if `service` contains any intermediate `NUL`
+ /// bytes. This is handled with a panic because the service names are all
+ /// hard-coded constants, and so such an error should always be indicative of a
+ /// bug in the calling code.
+ pub fn connect(device: impl AsRef<Path>, service: &str) -> io::Result<Self> {
+ let file = File::options().read(true).write(true).open(device)?;
+
+ let srv_name = CString::new(service).expect("Service name contained null bytes");
+ unsafe {
+ tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?;
+ }
+
+ Ok(TipcChannel(file))
+ }
+
+ /// Sends a message to the connected service.
+ ///
+ /// The entire contents of `buf` will be sent as a single message to the
+ /// connected service.
+ pub fn send(&mut self, buf: &[u8]) -> io::Result<()> {
+ let write_len = self.0.write(buf)?;
+
+ // Verify that the expected number of bytes were written. The entire message
+ // should always be written with a single `write` call, or an error should have
+ // been returned if the message couldn't be written. An assertion failure here
+ // potentially means a bug in the kernel driver.
+ assert_eq!(
+ buf.len(),
+ write_len,
+ "Failed to send full message ({} of {} bytes written)",
+ write_len,
+ buf.len(),
+ );
+
+ Ok(())
+ }
+
+ /// Receives a message from the connected service.
+ ///
+ /// Returns the number of bytes in the received message, or any error that
+ /// occurred when reading the message. A return value of 0 indicates that
+ /// there were no incoming messages to receive.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error with native error code 90 (`EMSGSIZE`) if `buf` isn't
+ /// large enough to contain the incoming message. Use
+ /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code
+ /// to determine if you need to increase the size of `buf`.
+ pub fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.0.read(buf)
+ }
+
+ // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports
+ // sending shared memory buffers.
+}
diff --git a/trusty/libtrusty-rs/src/sys.rs b/trusty/libtrusty-rs/src/sys.rs
new file mode 100644
index 0000000..f1c8c5f
--- /dev/null
+++ b/trusty/libtrusty-rs/src/sys.rs
@@ -0,0 +1,31 @@
+// NOTE: The ioctl definitions are sequestered into this module because the
+// `ioctl_*!` macros provided by the nix crate generate public functions that we
+// don't want to be part of this crate's public API.
+//
+// NOTE: We are manually re-declaring the types and constants here instead of using
+// bindgen and a separate `-sys` crate because the defines used for the ioctl
+// numbers (`TIPC_IOC_CONNECT` and `TIPC_IOC_SEND_MSG`) can't currently be
+// translated by bindgen.
+
+use std::os::raw::c_char;
+
+const TIPC_IOC_MAGIC: u8 = b'r';
+
+// NOTE: We use `ioctl_write_ptr_bad!` here due to an error in how the ioctl
+// code is defined in `trusty/ipc.h`.
+//
+// If we were to do `ioctl_write_ptr!(TIPC_IOC_MAGIC, 0x80, c_char)` it would
+// generate a function that takes a `*const c_char` data arg and would use
+// `size_of::<c_char>()` when generating the ioctl number. However, in
+// `trusty/ipc.h` the definition for `TIPC_IOC_CONNECT` declares the ioctl with
+// `char*`, meaning we need to use `size_of::<*const c_char>()` to generate an
+// ioctl number that matches what Trusty expects.
+//
+// To maintain compatibility with the `trusty/ipc.h` and the kernel driver we
+// use `ioctl_write_ptr_bad!` and manually use `request_code_write!` to generate
+// the ioctl number using the correct size.
+nix::ioctl_write_ptr_bad!(
+ tipc_connect,
+ nix::request_code_write!(TIPC_IOC_MAGIC, 0x80, std::mem::size_of::<*const c_char>()),
+ c_char
+);