v4l2r 0.0.7

Safe and flexible abstraction over V4L2
Documentation
use nix::libc::c_int;
use nix::poll::{PollFd, PollTimeout};
use std::convert::TryFrom;
use std::fs::File;
use std::os::unix::io::{AsFd, AsRawFd, RawFd};
use std::os::unix::prelude::FromRawFd;
use thiserror::Error;

pub use nix::poll::PollFlags;

#[doc(hidden)]
mod ioctl {
    use nix::libc::c_int;
    nix::ioctl_read!(media_ioc_request_alloc, b'|', 5, c_int);
    nix::ioctl_none!(media_request_ioc_queue, b'|', 0x80);
    nix::ioctl_none!(media_request_ioc_reinit, b'|', 0x81);
}

#[derive(Debug, Error)]
pub enum RequestError {
    #[error("Unexpected ioctl error: {0}")]
    IoctlError(nix::Error),
    #[error("Unknown poll flag returned")]
    UnknownPollFlagReturned,
    #[error("Duration is too large")]
    InvalidDuration,
}

#[derive(Debug)]
pub struct Request {
    fd: File,
}

impl Request {
    pub fn alloc(media_fd: &impl AsRawFd) -> Result<Self, RequestError> {
        let mut request_fd: RawFd = 0;
        // SAFETY: the 'data' argument is an address of a variable compatible with a C integer
        match unsafe {
            ioctl::media_ioc_request_alloc(
                media_fd.as_raw_fd(),
                &mut request_fd as *mut _ as *mut c_int,
            )
        } {
            // SAFETY: correctly executed ioctl guarantees request_fd is a correct file descriptor
            Ok(_) => Ok(Request {
                fd: unsafe { File::from_raw_fd(request_fd) },
            }),
            Err(e) => Err(RequestError::IoctlError(e)),
        }
    }

    pub fn queue(&self) -> Result<(), RequestError> {
        // SAFETY: the file descriptor is correct
        match unsafe { ioctl::media_request_ioc_queue(self.as_raw_fd()) } {
            Ok(_) => Ok(()),
            Err(e) => Err(RequestError::IoctlError(e)),
        }
    }

    pub fn reinit(&self) -> Result<(), RequestError> {
        // SAFETY: the file descriptor is correct
        match unsafe { ioctl::media_request_ioc_reinit(self.as_raw_fd()) } {
            Ok(_) => Ok(()),
            Err(e) => Err(RequestError::IoctlError(e)),
        }
    }

    pub fn poll(
        &self,
        video_fd: &impl AsFd,
        events: PollFlags,
        timeout: Option<u32>,
    ) -> Result<PollFlags, RequestError> {
        let mut poll_fd = [PollFd::new(video_fd.as_fd(), events)];
        let timeout = timeout.map_or(PollTimeout::NONE, |d| PollTimeout::try_from(d).unwrap());

        match nix::poll::poll(&mut poll_fd, timeout) {
            Ok(0) => Ok(PollFlags::empty()),
            Ok(_) => {
                if let Some(poll_flags) = poll_fd[0].revents() {
                    Ok(poll_flags)
                } else {
                    Err(RequestError::UnknownPollFlagReturned)
                }
            }
            Err(e) => Err(RequestError::IoctlError(e)),
        }
    }
}

impl AsRawFd for Request {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}