// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2024 Google LLC. //! Anonymous Shared Memory Subsystem for Android. //! //! The ashmem subsystem is a new shared memory allocator, similar to POSIX SHM but with different //! behavior and sporting a simpler file-based API. //! //! It is, in theory, a good memory allocator for low-memory devices, because it can discard shared //! memory units when under memory pressure. use core::{ pin::Pin, ptr::null_mut, sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}, }; use kernel::{ bindings::{self, ASHMEM_GET_PIN_STATUS, ASHMEM_PIN, ASHMEM_UNPIN}, c_str, error::Result, ffi::c_int, fs::{File, LocalFile}, ioctl::_IOC_SIZE, miscdevice::{loff_t, IovIter, Kiocb, MiscDevice, MiscDeviceOptions, MiscDeviceRegistration}, mm::virt::{flags as vma_flags, VmaNew}, page::{page_align, PAGE_MASK, PAGE_SIZE}, page_size_compat::__page_align, prelude::*, seq_file::{seq_print, SeqFile}, sync::{new_mutex, Mutex, UniqueArc}, task::Task, types::ForeignOwnable, uaccess::{UserSlice, UserSliceReader, UserSliceWriter}, }; const ASHMEM_NAME_LEN: usize = bindings::ASHMEM_NAME_LEN as usize; const ASHMEM_FULL_NAME_LEN: usize = bindings::ASHMEM_FULL_NAME_LEN as usize; const ASHMEM_NAME_PREFIX_LEN: usize = bindings::ASHMEM_NAME_PREFIX_LEN as usize; const ASHMEM_NAME_PREFIX: [u8; ASHMEM_NAME_PREFIX_LEN] = *b"dev/ashmem/"; const ASHMEM_MAX_SIZE: usize = usize::MAX >> 1; const PROT_READ: usize = bindings::PROT_READ as usize; const PROT_EXEC: usize = bindings::PROT_EXEC as usize; const PROT_WRITE: usize = bindings::PROT_WRITE as usize; const PROT_MASK: usize = PROT_EXEC | PROT_READ | PROT_WRITE; mod ashmem_shrinker; mod ashmem_range; use ashmem_range::{Area, AshmemGuard, NewRange, ASHMEM_MUTEX, LRU_COUNT}; mod shmem; use shmem::ShmemFile; mod ashmem_toggle; use ashmem_toggle::{AshmemToggleExec, AshmemToggleMisc, AshmemToggleRead, AshmemToggleShrinker}; /// Does PROT_READ imply PROT_EXEC for this task? fn read_implies_exec(task: &Task) -> bool { // SAFETY: Always safe to read. let personality = unsafe { (*task.as_ptr()).personality }; (personality & bindings::READ_IMPLIES_EXEC) != 0 } /// Calls `capable(CAP_SYS_ADMIN)`. fn has_cap_sys_admin() -> bool { use kernel::bindings::CAP_SYS_ADMIN; unsafe { bindings::capable(CAP_SYS_ADMIN as c_int) } } static NUM_PIN_IOCTLS_WAITING: AtomicUsize = AtomicUsize::new(0); static IGNORE_UNSET_PROT_READ: AtomicBool = AtomicBool::new(false); static IGNORE_UNSET_PROT_EXEC: AtomicBool = AtomicBool::new(false); static ASHMEM_FOPS_PTR: AtomicPtr = AtomicPtr::new(null_mut()); fn shrinker_should_stop() -> bool { NUM_PIN_IOCTLS_WAITING.load(Ordering::Relaxed) > 0 } module! { type: AshmemModule, name: "ashmem_rust", author: "Alice Ryhl", description: "Anonymous Shared Memory Subsystem", license: "GPL", } struct AshmemModule { _misc: Pin>>, _toggle_unpin: Pin>>>, _toggle_read: Pin>>>, _toggle_exec: Pin>>>, } impl kernel::Module for AshmemModule { fn init(_module: &'static kernel::ThisModule) -> Result { // SAFETY: Called once since this is the module initializer. unsafe { shmem::SHMEM_FOPS_ONCE.init() }; // SAFETY: Called once since this is the module initializer. unsafe { ASHMEM_MUTEX.init() }; // SAFETY: Called once since this is the module initializer. unsafe { ashmem_range::ASHMEM_SHRINKER.init() }; pr_info!("Using Rust implementation."); ashmem_range::set_shrinker_enabled(true)?; let ashmem_miscdevice_registration = KBox::pin_init( MiscDeviceRegistration::register(MiscDeviceOptions { name: c_str!("ashmem"), }), GFP_KERNEL, )?; let ashmem_miscdevice_ptr = ashmem_miscdevice_registration.as_raw(); // SAFETY: ashmem_miscdevice_registration is pinned and is never destroyed, so reading // and storing the fops pointer this way should be fine. let fops_ptr = unsafe { (*ashmem_miscdevice_ptr).fops }; ASHMEM_FOPS_PTR.store(fops_ptr.cast_mut(), Ordering::Relaxed); Ok(Self { _misc: ashmem_miscdevice_registration, _toggle_unpin: AshmemToggleMisc::::new()?, _toggle_read: AshmemToggleMisc::::new()?, _toggle_exec: AshmemToggleMisc::::new()?, }) } } /// Represents an open ashmem file. #[pin_data] struct Ashmem { #[pin] inner: Mutex, } struct AshmemInner { size: usize, prot_mask: usize, /// If set, then this holds the ashmem name without the dev/ashmem/ prefix. No zero terminator. name: Option>, file: Option, area: Area, } #[vtable] impl MiscDevice for Ashmem { type Ptr = Pin>; fn open(_: &File, _: &MiscDeviceRegistration) -> Result>> { KBox::try_pin_init( try_pin_init! { Ashmem { inner <- new_mutex!(AshmemInner { size: 0, prot_mask: PROT_MASK, name: None, file: None, area: Area::new(), }), } }, GFP_KERNEL, ) } fn mmap(me: Pin<&Ashmem>, _file: &File, vma: &VmaNew) -> Result<()> { let asma = &mut *me.inner.lock(); // User needs to SET_SIZE before mapping. if asma.size == 0 || asma.size >= ASHMEM_MAX_SIZE { return Err(EINVAL); } // Requested mapping size larger than object size. if vma.end() - vma.start() > __page_align(asma.size) { return Err(EINVAL); } if asma.prot_mask & PROT_WRITE == 0 { vma.try_clear_maywrite().map_err(|_| EPERM)?; } if asma.prot_mask & PROT_EXEC == 0 { vma.try_clear_mayexec().map_err(|_| EPERM)?; } if asma.prot_mask & PROT_READ == 0 { vma.try_clear_mayread().map_err(|_| EPERM)?; } let file = match asma.file.as_ref() { Some(file) => file, None => { let mut name_buffer = [0u8; ASHMEM_FULL_NAME_LEN]; let name = asma.full_name(&mut name_buffer); asma.file .insert(ShmemFile::new(name, asma.size, vma.flags())?) } }; if vma.flags() & vma_flags::SHARED != 0 { // We're really using this just to set vm_ops to `shmem_anon_vm_ops`. Anything else it // does is undone by the call to `set_file` below. shmem::zero_setup(vma)?; } else { shmem::vma_set_anonymous(vma); } shmem::set_file(vma, file.file()); Ok(()) } fn llseek(me: Pin<&Ashmem>, file: &LocalFile, offset: loff_t, whence: c_int) -> Result { let asma_file = { let asma = me.inner.lock(); if asma.size == 0 { return Err(EINVAL); } match asma.file.as_ref() { Some(asma_file) => asma_file.clone(), None => return Err(EBADF), } }; let ret = asma_file.vfs_llseek(offset, whence)?; // SAFETY: We protect the shmem file with the same mechanism as the ashmem file. We are in // llseek, so our caller ensures that accessing f_pos is okay. unsafe { shmem::file_set_fpos(file, shmem::file_get_fpos(asma_file.file())) }; Ok(ret) } fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIter) -> Result { let me = kiocb.private_data(); let asma_file = { let asma = me.inner.lock(); if asma.size == 0 { // If size is not set, or set to 0, always return EOF. return Ok(0); } match asma.file.as_ref() { Some(asma_file) => asma_file.clone(), None => return Err(EBADF), } }; let ret = asma_file.vfs_iter_read(iov, kiocb.ki_pos_mut())?; // SAFETY: We protect the shmem file with the same mechanism as the ashmem file. We are in // read_iter, so our caller ensures that accessing f_pos is okay. unsafe { shmem::file_set_fpos(asma_file.file(), kiocb.ki_pos()) }; Ok(ret as usize) } fn ioctl(me: Pin<&Ashmem>, _file: &File, cmd: u32, arg: usize) -> Result { let size = _IOC_SIZE(cmd); match cmd { bindings::ASHMEM_SET_NAME => me.set_name(UserSlice::new(arg, size).reader()), bindings::ASHMEM_GET_NAME => me.get_name(UserSlice::new(arg, size).writer()), bindings::ASHMEM_SET_SIZE => me.set_size(arg), bindings::ASHMEM_GET_SIZE => me.get_size(), bindings::ASHMEM_SET_PROT_MASK => me.set_prot_mask(arg), bindings::ASHMEM_GET_PROT_MASK => me.get_prot_mask(), bindings::ASHMEM_GET_FILE_ID => me.get_file_id(UserSlice::new(arg, size).writer()), ASHMEM_PIN | ASHMEM_UNPIN | ASHMEM_GET_PIN_STATUS => { me.pin_unpin(cmd, UserSlice::new(arg, size).reader()) } bindings::ASHMEM_PURGE_ALL_CACHES => me.purge_all_caches(), _ => Err(ENOTTY), } } #[cfg(CONFIG_COMPAT)] fn compat_ioctl(me: Pin<&Ashmem>, file: &File, compat_cmd: u32, arg: usize) -> Result { let cmd = match compat_cmd { bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE, bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK, _ => compat_cmd, }; Self::ioctl(me, file, cmd, arg) } fn show_fdinfo(me: Pin<&Ashmem>, m: &SeqFile, _file: &File) { let asma = me.inner.lock(); if let Some(file) = asma.file.as_ref() { seq_print!(m, "inode:\t{}\n", file.inode_ino()); } if let Some(name) = asma.name.as_ref() { let name = core::str::from_utf8(name).unwrap_or(""); seq_print!(m, "name:\t{}\n", name); } seq_print!(m, "size\t{}\n", asma.size); } } impl Ashmem { fn set_name(&self, reader: UserSliceReader) -> Result { let mut buf = [0u8; ASHMEM_NAME_LEN]; let name = reader.strcpy_into_buf(&mut buf)?.as_bytes(); let mut v = KVec::with_capacity(name.len(), GFP_KERNEL)?; v.extend_from_slice(name, GFP_KERNEL)?; let mut asma = self.inner.lock(); if asma.file.is_some() { return Err(EINVAL); } asma.name = Some(v); Ok(0) } fn get_name(&self, mut writer: UserSliceWriter) -> Result { let mut local_name = [0u8; ASHMEM_NAME_LEN]; let asma = self.inner.lock(); let name = asma.name.as_deref().unwrap_or(b"dev/ashmem"); let len = name.len(); let len_with_nul = len + 1; if local_name.len() < len_with_nul { // This shouldn't happen in practice since `set_name` will refuse to store a string // that is too long. return Err(EINVAL); } local_name[..len].copy_from_slice(name); local_name[len] = 0; drop(asma); writer.write_slice(&local_name[..len_with_nul])?; Ok(0) } fn set_size(&self, size: usize) -> Result { let mut asma = self.inner.lock(); if asma.file.is_some() { return Err(EINVAL); } asma.size = size; Ok(0) } fn get_size(&self) -> Result { Ok(self.inner.lock().size as isize) } fn set_prot_mask(&self, mut prot: usize) -> Result { let mut asma = self.inner.lock(); if (prot & PROT_READ != 0) && read_implies_exec(current!()) { prot |= PROT_EXEC; } if IGNORE_UNSET_PROT_READ.load(Ordering::Relaxed) { // Add back PROT_READ if asma.prot_mask has it. prot |= asma.prot_mask & PROT_READ; } if IGNORE_UNSET_PROT_EXEC.load(Ordering::Relaxed) { // Add back PROT_EXEC if asma.prot_mask has it. prot |= asma.prot_mask & PROT_EXEC; } // The user can only remove, not add, protection bits. if (asma.prot_mask & prot) != prot { return Err(EINVAL); } asma.prot_mask = prot; Ok(0) } fn get_prot_mask(&self) -> Result { Ok(self.inner.lock().prot_mask as isize) } fn get_file_id(&self, mut writer: UserSliceWriter) -> Result { let ino = { let asma = self.inner.lock(); let Some(file) = asma.file.as_ref() else { return Err(EINVAL); }; file.inode_ino() }; writer.write(&ino)?; Ok(0) } fn pin_unpin(&self, cmd: u32, mut reader: UserSliceReader) -> Result { let (offset, cmd_len) = { #[allow(dead_code)] // spurious warning because it is never explicitly constructed #[repr(transparent)] struct AshmemPin(bindings::ashmem_pin); // SAFETY: All bit-patterns are valid for `ashmem_pin`. unsafe impl kernel::types::FromBytes for AshmemPin {} let AshmemPin(pin) = reader.read()?; (pin.offset as usize, pin.len as usize) }; // If `pin`/`unpin` needs a new range, they will take it from this `Option`. Otherwise, // they will leave it here, and it gets dropped after the mutexes are released. let new_range = if cmd == ASHMEM_GET_PIN_STATUS { None } else { Some(UniqueArc::new_uninit(GFP_KERNEL)?) }; NUM_PIN_IOCTLS_WAITING.fetch_add(1, Ordering::Relaxed); let mut guard = AshmemGuard(ASHMEM_MUTEX.lock()); NUM_PIN_IOCTLS_WAITING.fetch_sub(1, Ordering::Relaxed); // C ashmem waits for in-flight shrinkers here using a separate mechanism, but we don't // release the lock when calling `punch_hole` in the shrinker, so we don't need to do that. let asma = &mut *self.inner.lock(); let mut new_range = match asma.file.as_ref() { Some(file) => new_range.map(|alloc| NewRange { file, alloc }), None => return Err(EINVAL), }; let max_size = page_align(asma.size); let remaining = max_size.checked_sub(offset).ok_or(EINVAL)?; // Per custom, you can pass zero for len to mean "everything onward". let len = if cmd_len == 0 { remaining } else { cmd_len }; if (offset | len) & !PAGE_MASK != 0 { return Err(EINVAL); } let len_plus_offset = offset.checked_add(len).ok_or(EINVAL)?; if max_size < len_plus_offset { return Err(EINVAL); } let pgstart = offset / PAGE_SIZE; let pgend = pgstart + (len / PAGE_SIZE) - 1; match cmd { ASHMEM_PIN => { if asma.area.pin(pgstart, pgend, &mut new_range, &mut guard) { Ok(bindings::ASHMEM_WAS_PURGED as isize) } else { Ok(bindings::ASHMEM_NOT_PURGED as isize) } } ASHMEM_UNPIN => { asma.area.unpin(pgstart, pgend, &mut new_range, &mut guard); Ok(0) } ASHMEM_GET_PIN_STATUS => { if asma .area .range_has_unpinned_page(pgstart, pgend, &mut guard) { Ok(bindings::ASHMEM_IS_UNPINNED as isize) } else { Ok(bindings::ASHMEM_IS_PINNED as isize) } } _ => unreachable!(), } } fn purge_all_caches(&self) -> Result { if !has_cap_sys_admin() { return Err(EPERM); } let mut guard = AshmemGuard(ASHMEM_MUTEX.lock()); let total_num_pages = LRU_COUNT.load(Ordering::Relaxed); let _num_freed = guard.free_lru(usize::MAX); // ASHMEM_PURGE_ALL_CACHES returns the total number of pages even if we stopped early. Ok(isize::try_from(total_num_pages).unwrap_or(isize::MAX)) } } impl AshmemInner { /// Get the full name. /// /// If the name is `Some(name)`, then this returns `dev/ashmem/name\0`. /// /// If the name is `None`, then this returns `dev/ashmem\0`. fn full_name<'name>(&self, name: &'name mut [u8; ASHMEM_FULL_NAME_LEN]) -> &'name CStr { name[..ASHMEM_NAME_PREFIX_LEN].copy_from_slice(&ASHMEM_NAME_PREFIX); if let Some(set_name) = self.name.as_deref() { name[ASHMEM_NAME_PREFIX_LEN..][..set_name.len()].copy_from_slice(set_name); } else { // Remove last slash if no name set. name[ASHMEM_NAME_PREFIX_LEN - 1] = 0; } name[ASHMEM_FULL_NAME_LEN - 1] = 0; // This unwrap only fails if there's no nul-byte, but we just added one at the end above. let len_with_nul = name .iter() .position(|&c| c == 0) .map(|len| len + 1) .unwrap(); // This unwrap fails if the last byte is not a nul-byte, or if there are any nul-bytes // before the last byte. Neither of those are possible here since `len_with_nul` is the // index of the first nul-byte in `name`. CStr::from_bytes_with_nul(&name[..len_with_nul]).unwrap() } } #[no_mangle] unsafe extern "C" fn ashmem_memfd_ioctl(file: *mut bindings::file, cmd: u32, arg: usize) -> isize { #[cfg(CONFIG_COMPAT)] let cmd = match cmd { bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE, bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK, cmd => cmd, }; // SAFETY: // * The file is valid for the duration of this call. // * There is no active fdget_pos region on the file on this thread. let file = unsafe { File::from_raw_file(file) }; match ashmem_memfd_ioctl_inner(file, cmd, arg) { Ok(ret) => ret, Err(err) => err.to_errno() as isize, } } fn ashmem_memfd_ioctl_inner(file: &File, cmd: u32, arg: usize) -> Result { use kernel::bindings::{F_ADD_SEALS, F_GET_SEALS, F_SEAL_FUTURE_WRITE, F_SEAL_WRITE}; const WRITE_SEALS_MASK: usize = (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE) as usize; /// # Safety /// The file must be a memfd file. unsafe fn get_seals(file: &File) -> Result { // SAFETY: This is a memfd file. let seals: isize = unsafe { bindings::memfd_fcntl(file.as_ptr(), F_GET_SEALS, 0) }; if seals < 0 { return Err(Error::from_errno(seals as i32)); } Ok(seals as usize) } let size = _IOC_SIZE(cmd); match cmd { bindings::ASHMEM_GET_NAME => { let file_ptr = file.as_ptr(); // SAFETY: It's safe to access a file's dentry. let dentry = unsafe { (*file_ptr).f_path.dentry }; // SAFETY: memfd stores the supplied name at this location. A default value is stored // when no name is supplied, so this is always a valid string. let full_name = unsafe { core::slice::from_raw_parts( (*dentry).d_name.name, (*dentry).d_name.__bindgen_anon_1.__bindgen_anon_1.len as usize, ) }; let name = full_name.strip_prefix(b"memfd:").unwrap_or(full_name); let max = usize::min(name.len(), ASHMEM_NAME_LEN); let mut local_name = [0u8; ASHMEM_NAME_LEN]; local_name[..max].copy_from_slice(&name[..max]); local_name[ASHMEM_NAME_LEN - 1] = 0; let mut writer = UserSlice::new(arg, size).writer(); writer.write_slice(&local_name)?; Ok(0) } bindings::ASHMEM_GET_SIZE => { let file_ptr = file.as_ptr(); // SAFETY: It's safe to access a file's inode. let inode = unsafe { (*file_ptr).f_inode }; // SAFETY: It's safe to read the size of an inode. let size = unsafe { bindings::i_size_read(inode) }; Ok(size as isize) } bindings::ASHMEM_SET_PROT_MASK => { // SAFETY: This is a memfd file. let seals = unsafe { get_seals(file) }?; let mut prot = arg; // The memfd compat layer does not support unsetting these. prot |= PROT_READ | PROT_EXEC; let is_writable = seals & WRITE_SEALS_MASK == 0; let should_be_writable = prot & PROT_WRITE != 0; if !is_writable && should_be_writable { // Can't add PROT bits. return Err(EINVAL); } if is_writable && !should_be_writable { // SAFETY: This is a memfd file. let ret = unsafe { bindings::memfd_fcntl(file.as_ptr(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE) }; if ret < 0 { return Err(Error::from_errno(ret as i32)); } } Ok(0) } bindings::ASHMEM_GET_PROT_MASK => { // SAFETY: This is a memfd file. let seals = unsafe { get_seals(file) }?; let mut prot = PROT_READ | PROT_EXEC; if seals & WRITE_SEALS_MASK == 0 { prot |= PROT_WRITE; } Ok(prot as isize) } bindings::ASHMEM_GET_FILE_ID => { // SAFETY: Accessing the ino is always okay. let ino = unsafe { (*(*file.as_ptr()).f_inode).i_ino as usize }; let mut writer = UserSlice::new(arg, size).writer(); writer.write(&ino)?; Ok(0) } // Just ignore unpin requests. ASHMEM_PIN => Ok(bindings::ASHMEM_NOT_PURGED as isize), ASHMEM_UNPIN => Ok(0), ASHMEM_GET_PIN_STATUS => Ok(bindings::ASHMEM_IS_PINNED as isize), bindings::ASHMEM_PURGE_ALL_CACHES => { if !has_cap_sys_admin() { return Err(EPERM); } Ok(0) } // We do not need to implement SET_NAME or SET_SIZE. The ioctls in this function are only // called when you: // // 1. Think you have an ashmem fd. // 2. But actually have a memfd fd. // // This can only happen if you created the fd through the libcutils library, and that // library sets the name and size in the fd constructor where it knows whether ashmem or // memfd is used, so we should never end up here. bindings::ASHMEM_SET_NAME => Err(EINVAL), bindings::ASHMEM_SET_SIZE => Err(EINVAL), _ => Err(EINVAL), } } /// # Safety /// /// The caller must ensure that `file` is valid for the duration of this function. #[no_mangle] unsafe extern "C" fn is_ashmem_file(file: *mut bindings::file) -> bool { let ashmem_fops_ptr = ASHMEM_FOPS_PTR.load(Ordering::Relaxed); if file.is_null() || ashmem_fops_ptr.is_null() { return false; } // SAFETY: Accessing the f_op field of a non-NULL file structure is always okay. let fops_ptr = unsafe { (*file).f_op }; fops_ptr == ashmem_fops_ptr } /// # Safety /// /// The caller must ensure that `file` references a valid file for the duration of 'a. unsafe fn get_ashmem_area<'a>(file: *mut bindings::file) -> Result<&'a Ashmem, Error> { // SAFETY: Caller ensures that file is valid, so this should be safe. if unsafe { is_ashmem_file(file) } { return Err(EINVAL); } // SAFETY: Given that this is an ashmem file, it should be safe to access the private_data // field containing the Ashmem struct. let private = unsafe { (*file).private_data }; // SAFETY: Since this is an ashmem file, we know the type of the struct and can reference it // safely. let ashmem = unsafe { <::Ptr as ForeignOwnable>::borrow(private) }; Ok(ashmem.get_ref()) } /// # Safety /// /// The caller must ensure the following prior to invoking this function: /// 1. `name` is valid for writing and at least of size ASHMEM_FULL_NAME_LEN. /// 2. `file` is valid for the duration of this function. #[no_mangle] unsafe extern "C" fn ashmem_area_name( file: *mut bindings::file, name: *mut kernel::ffi::c_char, ) -> c_int { if name.is_null() { return EINVAL.to_errno() as c_int; } // SAFETY: file is valid for the duration of this function. match unsafe { get_ashmem_area(file) } { Ok(ashmem) => { let name_buffer = name.cast::<[u8; ASHMEM_FULL_NAME_LEN]>(); // SAFETY: Caller guarantees that the pointer is valid for writing. ashmem.inner.lock().full_name(unsafe { &mut *name_buffer }); 0 } Err(err) => err.to_errno() as c_int, } } /// # Safety /// /// The caller must ensure that `file` is valid for the duration of this function. #[no_mangle] unsafe extern "C" fn ashmem_area_size(file: *mut bindings::file) -> isize { // SAFETY: file is valid for the duration of this function. let ashmem = match unsafe { get_ashmem_area(file) } { Ok(area) => area, Err(_err) => return 0, }; match ashmem.get_size() { Ok(size) => size, Err(_err) => 0, } } /// # Safety /// /// The caller must ensure that `file` is valid for the duration of this function. /// /// If this function returns a non-NULL pointer to a file structure, the refcount for that /// file will be incremented by 1. It is the caller's responsibility to decrement the refcount /// when the file is no longer needed. #[no_mangle] unsafe extern "C" fn ashmem_area_vmfile(file: *mut bindings::file) -> *mut bindings::file { // SAFETY: file is valid for the duration of this function. let ashmem = match unsafe { get_ashmem_area(file) } { Ok(area) => area, Err(_err) => return null_mut(), }; let asma = &mut *ashmem.inner.lock(); match asma.file.as_ref() { Some(shmem_file) => { let shmem_file_ptr = shmem_file.file().as_ptr(); // SAFETY: file is valid for the duration of the function, which means shmem file is // also valid at this point. unsafe { bindings::get_file(shmem_file_ptr) }; shmem_file_ptr } None => null_mut(), } }