use std::os::fd::{AsFd, OwnedFd};
use super::heap::{MappedHeap, MemoryHeapBase};
use super::{IMemoryHeap, FLAG_MEMFD_ALLOW_SEALING, FLAG_READ_ONLY};
use crate::error::{Result, StatusCode};
use crate::file_descriptor::ParcelFileDescriptor;
use crate::parcel::Parcel;
use crate::parcelable::{Deserialize, DeserializeOption, Serialize, SerializeOption};
#[derive(Debug)]
pub struct SharedMemory(Inner);
#[derive(Debug)]
enum Inner {
Owner(MemoryHeapBase),
Mapped(MappedHeap),
}
impl SharedMemory {
pub fn create(size: usize) -> Result<Self> {
Self::create_named(size, "SharedMemory")
}
pub fn create_named(size: usize, name: &str) -> Result<Self> {
Ok(Self(Inner::Owner(MemoryHeapBase::new_named(
size,
FLAG_MEMFD_ALLOW_SEALING,
name,
)?)))
}
pub fn from_fd(fd: OwnedFd) -> Result<Self> {
let size = region_size(&fd)?;
let flags = if write_sealed(&fd) { FLAG_READ_ONLY } else { 0 };
Ok(Self(Inner::Mapped(MappedHeap::from_fd(
fd, size, 0, flags,
)?)))
}
pub fn size(&self) -> usize {
match &self.0 {
Inner::Owner(h) => h.size(),
Inner::Mapped(h) => h.size(),
}
}
pub fn is_read_only(&self) -> bool {
match &self.0 {
Inner::Owner(_) => false,
Inner::Mapped(h) => h.flags() & FLAG_READ_ONLY != 0,
}
}
pub fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<()> {
match &self.0 {
Inner::Owner(h) => h.read_at(off, dst),
Inner::Mapped(h) => h.read_at(off, dst),
}
}
pub fn write_at(&self, off: usize, src: &[u8]) -> Result<()> {
match &self.0 {
Inner::Owner(h) => h.write_at(off, src),
Inner::Mapped(h) => h.write_at(off, src),
}
}
pub fn as_ptr(&self) -> Option<*mut u8> {
match &self.0 {
Inner::Owner(h) => h.as_ptr(),
Inner::Mapped(h) => h.as_ptr(),
}
}
pub fn seal_read_only(&self) -> Result<()> {
match &self.0 {
Inner::Owner(h) => h.seal_future_write(),
Inner::Mapped(h) => h.seal_future_write(),
}
}
pub fn to_parcel_fd(&self) -> Result<ParcelFileDescriptor> {
match &self.0 {
Inner::Owner(h) => h.to_parcel_fd(),
Inner::Mapped(h) => h.to_parcel_fd(),
}
}
pub fn as_heap(&self) -> &dyn IMemoryHeap {
match &self.0 {
Inner::Owner(h) => h,
Inner::Mapped(h) => h,
}
}
}
impl AsFd for SharedMemory {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
match &self.0 {
Inner::Owner(h) => h.as_fd(),
Inner::Mapped(h) => h.as_fd(),
}
}
}
impl Serialize for SharedMemory {
fn serialize(&self, parcel: &mut Parcel) -> Result<()> {
self.to_parcel_fd()?.serialize(parcel)
}
}
impl SerializeOption for SharedMemory {
fn serialize_option(this: Option<&Self>, parcel: &mut Parcel) -> Result<()> {
match this {
Some(s) => s.serialize(parcel),
None => ParcelFileDescriptor::serialize_option(None, parcel),
}
}
}
impl Deserialize for SharedMemory {
fn deserialize(parcel: &mut Parcel) -> Result<Self> {
let pfd: ParcelFileDescriptor = parcel.read()?;
Self::from_fd(OwnedFd::from(pfd))
}
}
impl DeserializeOption for SharedMemory {
fn deserialize_option(parcel: &mut Parcel) -> Result<Option<Self>> {
match ParcelFileDescriptor::deserialize_option(parcel)? {
Some(pfd) => Ok(Some(Self::from_fd(OwnedFd::from(pfd))?)),
None => Ok(None),
}
}
}
pub fn region_size<F: AsFd>(fd: F) -> Result<usize> {
let st = rustix::fs::fstat(fd.as_fd())?;
if st.st_size > 0 {
return usize::try_from(st.st_size).map_err(|_| StatusCode::BadValue);
}
ashmem_size(fd.as_fd())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn is_ashmem_fd(fd: std::os::fd::BorrowedFd<'_>) -> bool {
rsbinder_fmq::shm::is_ashmem_fd(fd)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub(crate) fn is_ashmem_fd(_fd: std::os::fd::BorrowedFd<'_>) -> bool {
false
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn ashmem_size(fd: std::os::fd::BorrowedFd<'_>) -> Result<usize> {
let size = rsbinder_fmq::shm::ashmem_size(fd).map_err(|e| match e {
rsbinder_fmq::Error::Os(errno) => StatusCode::from(errno),
_ => StatusCode::BadValue,
})?;
usize::try_from(size).map_err(|_| StatusCode::BadValue)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn ashmem_size(_fd: std::os::fd::BorrowedFd<'_>) -> Result<usize> {
Err(StatusCode::BadValue)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn write_sealed<F: AsFd>(fd: F) -> bool {
use rustix::fs::SealFlags;
if is_ashmem_fd(fd.as_fd()) {
return ashmem_write_protected(fd.as_fd());
}
rustix::fs::fcntl_get_seals(fd)
.map(|s| s.intersects(SealFlags::WRITE | SealFlags::FUTURE_WRITE))
.unwrap_or(false)
}
#[cfg(target_os = "android")]
fn ashmem_write_protected(fd: std::os::fd::BorrowedFd<'_>) -> bool {
use std::os::fd::AsRawFd;
const ASHMEM_GET_PROT_MASK: libc::c_ulong = 0x7706;
let prot = unsafe { libc::ioctl(fd.as_raw_fd(), ASHMEM_GET_PROT_MASK as _, 0) };
prot >= 0 && prot & libc::PROT_WRITE == 0
}
#[cfg(target_os = "linux")]
fn ashmem_write_protected(_fd: std::os::fd::BorrowedFd<'_>) -> bool {
false
}
#[cfg(target_os = "macos")]
fn write_sealed<F: AsFd>(fd: F) -> bool {
super::heap::is_read_only_fd(fd)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
fn write_sealed<F: AsFd>(_fd: F) -> bool {
false
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "android", target_os = "macos")
))]
mod tests {
use super::*;
fn page() -> usize {
rustix::param::page_size()
}
#[test]
fn create_then_adopt_by_fd_recovers_size_and_shares_pages() {
let owner = SharedMemory::create(page() + 1).unwrap();
assert_eq!(owner.size(), page() * 2);
assert!(!owner.is_read_only());
let rx = SharedMemory::from_fd(OwnedFd::from(owner.to_parcel_fd().unwrap())).unwrap();
assert_eq!(rx.size(), page() * 2);
assert!(!rx.is_read_only());
owner.write_at(page(), b"shm").unwrap();
let mut b = [0u8; 3];
rx.read_at(page(), &mut b).unwrap();
assert_eq!(&b, b"shm");
assert_eq!(rx.as_heap().size(), page() * 2);
}
#[test]
fn region_size_rejects_non_region_fd() {
let (a, _b) = std::os::unix::net::UnixStream::pair().unwrap();
assert!(region_size(&a).is_err());
}
#[test]
fn parcel_roundtrip_is_a_bare_fd() {
let owner = SharedMemory::create(page()).unwrap();
owner.write_at(0, b"parcel").unwrap();
let mut p = Parcel::new();
p.write(&owner).unwrap();
p.write(&Option::<SharedMemory>::None).unwrap();
p.set_data_position(0);
let pfd: ParcelFileDescriptor = p.read().unwrap();
let rx = SharedMemory::from_fd(OwnedFd::from(pfd)).unwrap();
let mut b = [0u8; 6];
rx.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"parcel");
let none: Option<SharedMemory> = p.read().unwrap();
assert!(none.is_none());
let mut p = Parcel::new();
p.write(&owner).unwrap();
p.set_data_position(0);
let rx2: SharedMemory = p.read().unwrap();
assert_eq!(rx2.size(), page());
}
#[test]
fn sealed_read_only_is_detected_by_receiver() {
let owner = SharedMemory::create(page()).unwrap();
owner.seal_read_only().unwrap();
owner.write_at(0, b"ok").unwrap(); let rx = SharedMemory::from_fd(OwnedFd::from(owner.to_parcel_fd().unwrap())).unwrap();
assert!(rx.is_read_only());
assert_eq!(
rx.write_at(0, b"xx").unwrap_err(),
StatusCode::PermissionDenied
);
let mut b = [0u8; 2];
rx.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"ok");
}
#[test]
fn received_region_cannot_be_sealed_on_macos_but_can_on_linux() {
let owner = SharedMemory::create(page()).unwrap();
let rx = SharedMemory::from_fd(OwnedFd::from(owner.to_parcel_fd().unwrap())).unwrap();
let r = rx.seal_read_only();
if cfg!(target_os = "macos") {
assert_eq!(r.unwrap_err(), StatusCode::InvalidOperation);
} else {
r.unwrap();
}
}
}