Merge pull request #2 from vberger/shm_handler

[WIP] SHM helpers
This commit is contained in:
Victor Berger 2017-02-22 13:55:23 +01:00 committed by GitHub
commit 3e595dca88
4 changed files with 571 additions and 1 deletions

View File

@ -4,4 +4,10 @@ version = "0.1.0"
authors = ["Victor Berger <victor.berger@thalesgroup.com>"]
[dependencies]
wayland-server = "0.7.6"
wayland-server = "0.8.4"
nix = "0.7.0"
slog = { version = "~1.5.2", features = ["max_level_trace", "release_max_level_info"] }
slog-stdlog = "~1.1.0"
[dev-dependencies]
slog-term = "~1.5"

View File

@ -1 +1,11 @@
#![warn(missing_docs)]
#[macro_use]
extern crate wayland_server;
extern crate nix;
#[macro_use]
extern crate slog;
extern crate slog_stdlog;
pub mod shm;

299
src/shm/mod.rs Normal file
View File

@ -0,0 +1,299 @@
//! SHM handling helpers
//!
//! This module provides helpers to handle SHM-based buffers from wayland clients.
//!
//! To use it, first add a `ShmGlobal` to your event loop, specifying the formats
//! you want to support (ARGB8888 and XRGB8888 are always considered as supported,
//! as specified by the wayland protocol) and obtain its `ShmGlobalToken`.
//!
//! ```
//! extern crate wayland_server;
//! extern crate smithay;
//!
//! use smithay::shm::ShmGlobal;
//! use wayland_server::protocol::wl_shm;
//!
//! # fn main() {
//! # let (_, mut event_loop) = wayland_server::create_display();
//!
//! // Insert the ShmGlobal as a handler to your event loop
//! // Here, we specify that Yuyv and C8 format are supported
//! // additionnaly to the standart Argb8888 and Xrgb8888.
//! let handler_id = event_loop.add_handler_with_init(ShmGlobal::new(
//! vec![wl_shm::Format::Yuyv, wl_shm::Format::C8],
//! None // we don't provide a logger here
//! ));
//! // Register this handler to advertise a wl_shm global of version 1
//! let shm_global = event_loop.register_global::<wl_shm::WlShm,ShmGlobal>(handler_id, 1);
//! // Retrieve the shm token for later use to access the buffers
//! let shm_token = {
//! let state = event_loop.state();
//! state.get_handler::<ShmGlobal>(handler_id).get_token()
//! };
//!
//! # }
//! ```
//!
//! Then, when you have a `WlBuffer` and need to retrieve its contents, use the token method to
//! do it:
//!
//! ```ignore
//! shm_token.with_buffer_contents(&buffer,
//! |slice: &[u8], buffer_metadata: &BufferData| {
//! // do something to draw it on the screen
//! }
//! );
//! ```
//!
//! **Note**
//!
//! This handler makes itself safe regading the client providing a wring size for the memory pool
//! by using a SIGBUS handler.
//!
//! If you are already using an handler for this signal, you probably don't want to use this handler.
use std::os::unix::io::RawFd;
use std::sync::Arc;
use wayland_server::{GlobalHandler, EventLoopHandle, Client, Init, Resource, Destroy, resource_is_registered};
use wayland_server::protocol::{wl_shm, wl_shm_pool, wl_buffer};
use self::pool::{Pool, ResizeError};
mod pool;
/// A global for handling SHM pool and buffers
///
/// You must register it to an event loop using `register_with_init`, or it will
/// quickly panic.
pub struct ShmGlobal {
formats: Vec<wl_shm::Format>,
handler_id: Option<usize>,
log: ::slog::Logger
}
impl ShmGlobal {
/// Create a new SHM global advertizing given supported formats.
///
/// This global will always advertize `ARGB8888` and `XRGB8888` format
/// as they are required by the protocol. Formats given as argument
/// as additionnaly advertized.
///
/// An optionnal `slog::Logger` can be provided and will be used as a drain
/// for logging. If `None` is provided, it'll log to `slog-stdlog`.
pub fn new<L>(mut formats: Vec<wl_shm::Format>, logger: L) -> ShmGlobal
where L: Into<Option<::slog::Logger>>
{
use slog::DrainExt;
let log = logger.into().unwrap_or(::slog::Logger::root(::slog_stdlog::StdLog.fuse(), o!()));
// always add the mandatory formats
formats.push(wl_shm::Format::Argb8888);
formats.push(wl_shm::Format::Xrgb8888);
ShmGlobal {
formats: formats,
handler_id: None,
log: log.new(o!("smithay_module" => "shm_handler"))
}
}
/// Retreive a token from the SHM global.
///
/// This can only be called once the `ShmGlobal` has been added to and event loop
/// and has been initialized. If it is not the case, this method will panic.
///
/// This is needed to retrieve the contents of the shm pools and buffers.
pub fn get_token(&self) -> ShmGlobalToken {
ShmGlobalToken {
hid: self.handler_id.clone().expect("ShmGlobal was not initialized."),
}
}
}
/// An SHM global token
///
/// It is needed to access the contents of the buffers & pools managed by the
/// associated ShmGlobal.
pub struct ShmGlobalToken {
hid: usize
}
/// Error that can occur when accessing an SHM buffer
pub enum BufferAccessError {
/// This buffer is not managed by the SHM handler
NotManaged,
/// An error occured while accessing the memory map
///
/// This can happen if the client advertized a wrong size
/// for the memory map.
///
/// If this error occurs, the client has been killed as a result.
BadMap
}
impl ShmGlobalToken {
/// Call given closure with the contents of the given buffer
///
/// If the buffer is managed by the associated ShmGlobal, its contents are
/// extracted and the closure is extracted with them:
///
/// - The first argument is a data slice of the contents of the pool
/// - The second argument is the specification of this buffer is this pool
///
/// If the buffer is not managed by the associated ShmGlobal, the closure is not called
/// and this method will return `Err(())` (this will be the case for an EGL buffer for example).
pub fn with_buffer_contents<F>(&self, buffer: &wl_buffer::WlBuffer, f: F) -> Result<(), BufferAccessError>
where F: FnOnce(&[u8], BufferData)
{
if !resource_is_registered::<_, ShmHandler>(buffer, self.hid) {
return Err(BufferAccessError::NotManaged)
}
let data = unsafe { &* (buffer.get_user_data() as *mut InternalBufferData) };
if data.pool.with_data_slice(|slice| f(slice, data.data.clone())).is_err() {
// SIGBUS error occured
buffer.post_error(wl_shm::Error::InvalidFd as u32, "Bad pool size.".into());
return Err(BufferAccessError::BadMap)
}
Ok(())
}
}
impl Init for ShmGlobal {
fn init(&mut self, evqh: &mut EventLoopHandle, _index: usize) {
let id = evqh.add_handler_with_init(ShmHandler {
my_id: ::std::usize::MAX,
valid_formats: self.formats.clone(),
log: self.log.clone()
});
self.handler_id = Some(id);
}
}
impl GlobalHandler<wl_shm::WlShm> for ShmGlobal {
fn bind(&mut self, evqh: &mut EventLoopHandle, _: &Client, global: wl_shm::WlShm) {
let hid = self.handler_id.clone().expect("ShmGlobal was not initialized.");
// register an handler for this shm
evqh.register::<_, ShmHandler>(&global, hid);
// and then the custom formats
for f in &self.formats {
global.format(*f);
}
}
}
struct ShmHandler {
my_id: usize,
valid_formats: Vec<wl_shm::Format>,
log: ::slog::Logger
}
impl Init for ShmHandler {
fn init(&mut self, _evqh: &mut EventLoopHandle, index: usize) {
self.my_id = index;
debug!(self.log, "Init finished")
}
}
impl wl_shm::Handler for ShmHandler {
fn create_pool(&mut self, evqh: &mut EventLoopHandle, _client: &Client, shm: &wl_shm::WlShm,
pool: wl_shm_pool::WlShmPool, fd: RawFd, size: i32) {
if size <= 0 {
shm.post_error(wl_shm::Error::InvalidFd as u32, "Invalid size for a new wl_shm_pool.".into());
return
}
let mmap_pool = match Pool::new(fd, size as usize, self.log.clone()) {
Ok(p) => p,
Err(()) => {
shm.post_error(wl_shm::Error::InvalidFd as u32, format!("Failed mmap of fd {}.", fd));
return
}
};
let arc_pool = Box::new(Arc::new(mmap_pool));
evqh.register_with_destructor::<_, ShmHandler, ShmHandler>(&pool, self.my_id);
pool.set_user_data(Box::into_raw(arc_pool) as *mut ());
}
}
impl Destroy<wl_shm_pool::WlShmPool> for ShmHandler {
fn destroy(pool: &wl_shm_pool::WlShmPool) {
let arc_pool = unsafe { Box::from_raw(pool.get_user_data() as *mut Arc<Pool>) };
drop(arc_pool)
}
}
declare_handler!(ShmHandler, wl_shm::Handler, wl_shm::WlShm);
/// Details of the contents of a buffer relative to its pool
#[derive(Copy,Clone,Debug)]
pub struct BufferData {
/// Offset of the start of the buffer relative to the beginning of the pool in bytes
pub offset: i32,
/// Wwidth of the buffer in bytes
pub width: i32,
/// Height of the buffer in bytes
pub height: i32,
/// Stride of the buffer in bytes
pub stride: i32,
/// Format used by this buffer
pub format: wl_shm::Format
}
struct InternalBufferData {
pool: Arc<Pool>,
data: BufferData
}
impl wl_shm_pool::Handler for ShmHandler {
fn create_buffer(&mut self, evqh: &mut EventLoopHandle, _client: &Client,
pool: &wl_shm_pool::WlShmPool, buffer: wl_buffer::WlBuffer, offset: i32,
width: i32, height: i32, stride: i32, format: wl_shm::Format)
{
if !self.valid_formats.contains(&format) {
buffer.post_error(wl_shm::Error::InvalidFormat as u32, String::new());
return
}
let arc_pool = unsafe { &*(pool.get_user_data() as *mut Arc<Pool>) };
let data = Box::into_raw(Box::new(InternalBufferData {
pool: arc_pool.clone(),
data: BufferData {
offset: offset,
width: width,
height: height,
stride: stride,
format: format
}
}));
evqh.register_with_destructor::<_, ShmHandler, ShmHandler>(&buffer, self.my_id);
buffer.set_user_data(data as *mut ());
}
fn resize(&mut self, _evqh: &mut EventLoopHandle, _client: &Client,
pool: &wl_shm_pool::WlShmPool, size: i32)
{
let arc_pool = unsafe { &*(pool.get_user_data() as *mut Arc<Pool>) };
match arc_pool.resize(size) {
Ok(()) => {},
Err(ResizeError::InvalidSize) => {
pool.post_error(wl_shm::Error::InvalidFd as u32, "Invalid new size for a wl_shm_pool.".into());
},
Err(ResizeError::MremapFailed) => {
pool.post_error(wl_shm::Error::InvalidFd as u32, "mremap failed.".into());
}
}
}
}
impl Destroy<wl_buffer::WlBuffer> for ShmHandler {
fn destroy(buffer: &wl_buffer::WlBuffer) {
let buffer_data = unsafe { Box::from_raw(buffer.get_user_data() as *mut InternalBufferData) };
drop(buffer_data)
}
}
declare_handler!(ShmHandler, wl_shm_pool::Handler, wl_shm_pool::WlShmPool);
impl wl_buffer::Handler for ShmHandler {
}
declare_handler!(ShmHandler, wl_buffer::Handler, wl_buffer::WlBuffer);

255
src/shm/pool.rs Normal file
View File

@ -0,0 +1,255 @@
use std::cell::Cell;
use std::os::unix::io::RawFd;
use std::sync::{RwLock, Once, ONCE_INIT};
use std::ptr;
use nix::{c_int, c_void, libc, unistd};
use nix::sys::mman;
use nix::sys::signal::{self, SigAction, Signal, SigHandler};
thread_local!(static SIGBUS_GUARD: Cell<(*const MemMap, bool)> = Cell::new((ptr::null_mut(), false)));
static SIGBUS_INIT: Once = ONCE_INIT;
static mut OLD_SIGBUS_HANDLER: *mut SigAction = 0 as *mut SigAction;
pub struct Pool {
map: RwLock<MemMap>,
fd: RawFd,
log: ::slog::Logger
}
pub enum ResizeError {
InvalidSize,
MremapFailed
}
impl Pool {
pub fn new(fd: RawFd, size: usize, log: ::slog::Logger) -> Result<Pool,()> {
let memmap = MemMap::new(fd, size)?;
trace!(log, "Creating new shm pool"; "fd" => fd as i32, "size" => size);
Ok(Pool {
map: RwLock::new(memmap),
fd: fd,
log: log
})
}
pub fn resize(&self, newsize: i32) -> Result<(),ResizeError> {
let mut guard = self.map.write().unwrap();
let oldsize = guard.size();
if newsize <= 0 || oldsize > (newsize as usize) {
return Err(ResizeError::InvalidSize)
}
trace!(self.log, "Resizing shm pool"; "fd" => self.fd as i32, "oldsize" => oldsize, "newsize" => newsize);
guard.remap(newsize as usize).map_err(|()| {
debug!(self.log, "SHM pool resize failed"; "fd" => self.fd as i32, "oldsize" => oldsize, "newsize" => newsize);
ResizeError::MremapFailed
})
}
pub fn with_data_slice<F: FnOnce(&[u8])>(&self, f: F) -> Result<(),()> {
// Place the sigbus handler
SIGBUS_INIT.call_once(|| {
unsafe { place_sigbus_handler(); }
});
let pool_guard = self.map.read().unwrap();
trace!(self.log, "Buffer access on shm pool"; "fd" => self.fd as i32);
// Prepare the access
SIGBUS_GUARD.with(|guard| {
let (p,_) = guard.get();
if !p.is_null() {
// Recursive call of this method is not supported
panic!("Recursive access to a SHM pool content is not supported.");
}
guard.set((&*pool_guard as *const MemMap, false))
});
let slice = pool_guard.get_slice();
f(slice);
// Cleanup Post-access
SIGBUS_GUARD.with(|guard| {
let (_, triggered) = guard.get();
guard.set((ptr::null_mut(), false));
if triggered {
debug!(self.log, "SIGBUS caught on access on shm pool"; "fd" => self.fd);
Err(())
} else {
Ok(())
}
})
}
}
impl Drop for Pool {
fn drop(&mut self) {
trace!(self.log, "Deleting SHM pool"; "fd" => self.fd);
let _ = unsafe { unistd::close(self.fd) };
}
}
struct MemMap {
ptr: *mut u8,
fd: RawFd,
size: usize
}
impl MemMap {
fn new(fd: RawFd, size: usize) -> Result<MemMap,()> {
Ok(MemMap {
ptr: unsafe { map(fd, size) }?,
fd: fd,
size: size
})
}
fn remap(&mut self, newsize: usize) -> Result<(),()> {
if self.ptr.is_null() {
return Err(())
}
// memunmap cannot fail, as we are unmapping a pre-existing map
let _ = unsafe { unmap(self.ptr, self.size) };
// remap the fd with the new size
match unsafe { map(self.fd, newsize) } {
Ok(ptr) => {
// update the parameters
self.ptr = ptr;
self.size = newsize;
Ok(())
},
Err(()) => {
// set ourselves in an empty state
self.ptr = ptr::null_mut();
self.size = 0;
self.fd = -1;
Err(())
}
}
}
fn size(&self) -> usize {
self.size
}
fn get_slice(&self) -> &[u8] {
// if we are in the 'invalid state', self.size == 0 and we return &[]
// which is perfectly safe even if self.ptr is null
unsafe { ::std::slice::from_raw_parts(self.ptr, self.size) }
}
fn contains(&self, ptr: *mut u8) -> bool {
ptr >= self.ptr && ptr < unsafe { self.ptr.offset(self.size as isize) }
}
fn nullify(&self) -> Result<(),()> {
unsafe { nullify_map(self.ptr, self.size) }
}
}
impl Drop for MemMap {
fn drop(&mut self) {
if !self.ptr.is_null() {
let _ = unsafe { unmap(self.ptr, self.size) };
}
}
}
// mman::mmap should really be unsafe... why isn't it?
unsafe fn map(fd: RawFd, size: usize) -> Result<*mut u8, ()> {
let ret = mman::mmap(
ptr::null_mut(),
size,
mman::PROT_READ,
mman::MAP_SHARED,
fd,
0
);
ret.map(|p| p as *mut u8).map_err(|_| ())
}
// mman::munmap should really be unsafe... why isn't it?
unsafe fn unmap(ptr: *mut u8, size: usize) -> Result<(),()> {
let ret = mman::munmap(ptr as *mut _, size);
ret.map_err(|_| ())
}
unsafe fn nullify_map(ptr: *mut u8, size: usize) -> Result<(), ()> {
let ret = mman::mmap(
ptr as *mut _,
size,
mman::PROT_READ,
mman::MAP_ANONYMOUS | mman::MAP_PRIVATE | mman::MAP_FIXED,
-1,
0
);
ret.map(|_| ()).map_err(|_| ())
}
unsafe fn place_sigbus_handler() {
// create our sigbus handler
let action = SigAction::new(
SigHandler::SigAction(sigbus_handler),
signal::SA_NODEFER,
signal::SigSet::empty()
);
match signal::sigaction(Signal::SIGBUS, &action) {
Ok(old_signal) => {
OLD_SIGBUS_HANDLER = Box::into_raw(Box::new(old_signal));
},
Err(e) => {
panic!("sigaction failed sor SIGBUS handler: {:?}", e)
}
}
}
unsafe fn reraise_sigbus() {
// reset the old sigaction
let _ = signal::sigaction(Signal::SIGBUS, &*OLD_SIGBUS_HANDLER);
let _ = signal::raise(Signal::SIGBUS);
}
extern "C" fn sigbus_handler(_signum: c_int, info: *mut libc::siginfo_t, _context: *mut c_void) {
let faulty_ptr = unsafe { siginfo_si_addr(info) } as *mut u8;
SIGBUS_GUARD.with(|guard| {
let (memmap, _) = guard.get();
match unsafe { memmap.as_ref() }.map(|m| (m, m.contains(faulty_ptr))) {
Some((m, true)) => {
// we are in a faulty memory pool !
// remember that it was faulty
guard.set((memmap, true));
// nullify the pool
if m.nullify().is_err() {
// something terrible occured !
unsafe { reraise_sigbus() }
}
},
_ => {
// something else occured, let's die honorably
unsafe { reraise_sigbus() }
}
}
});
}
// This was shamelessly stolen from rustc's source
// so I expect it to work whevener rust works
// I guess it's good enough?
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> *mut c_void {
#[repr(C)]
struct siginfo_t {
a: [libc::c_int; 3], // si_signo, si_errno, si_code
si_addr: *mut libc::c_void,
}
(*(info as *const siginfo_t)).si_addr
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> *mut c_void {
(*info).si_addr
}