Skip to content
Snippets Groups Projects
Forked from redox-os / relibc
1545 commits behind the upstream repository.
mod.rs 6.05 KiB
//! sys/select.h implementation

use core::mem;

use fs::File;
use header::errno;
use header::sys_epoll::{
    epoll_create1, EPOLL_CLOEXEC,
    epoll_ctl, EPOLL_CTL_ADD,
    epoll_wait,
    EPOLLIN, EPOLLOUT, EPOLLERR,
    epoll_data, epoll_event
};
use header::sys_time::timeval;
use platform;
use platform::types::*;

// fd_set is also defined in C because cbindgen is incompatible with mem::size_of booo

pub const FD_SETSIZE: usize = 1024;

#[repr(C)]
pub struct fd_set {
    pub fds_bits: [c_ulong; FD_SETSIZE / (8 * mem::size_of::<c_ulong>())],
}

impl fd_set {
    fn index(fd: c_int) -> usize {
        (fd as usize) / (8 * mem::size_of::<c_ulong>())
    }

    fn bitmask(fd: c_int) -> c_ulong {
        1 << ((fd as usize) & (8 * mem::size_of::<c_ulong>() - 1)) as c_ulong
    }

    fn zero(&mut self) {
        for i in 0..self.fds_bits.len() {
            self.fds_bits[i] = 0;
        }
    }

    fn set(&mut self, fd: c_int) {
        self.fds_bits[Self::index(fd)] |= Self::bitmask(fd);
    }

    fn clr(&mut self, fd: c_int) {
        self.fds_bits[Self::index(fd)] &= !Self::bitmask(fd);
    }

    fn isset(&self, fd: c_int) -> bool {
        self.fds_bits[Self::index(fd)] & Self::bitmask(fd) > 0
    }
}

pub fn select_epoll(
    nfds: c_int,
    mut readfds: Option<&mut fd_set>,
    mut writefds: Option<&mut fd_set>,
    mut exceptfds: Option<&mut fd_set>,
    timeout: Option<&mut timeval>
) -> c_int {
    if nfds < 0 || nfds > FD_SETSIZE as i32 {
        unsafe { platform::errno = errno::EINVAL };
        return -1;
    };

    let ep = {
        let epfd = epoll_create1(EPOLL_CLOEXEC);
        if epfd < 0 {
            return -1;
        }
        File::new(epfd)
    };

    // Keep track of the number of file descriptors that do not support epoll
    let mut not_epoll = 0;
    for fd in 0..nfds {
        if let Some(ref mut fd_set) = readfds {
            if fd_set.isset(fd) {
                let mut event = epoll_event {
                    events: EPOLLIN,
                    data: epoll_data {
                        fd: fd,
                    },
                };
                if epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &mut event) < 0 {
                    if unsafe { platform::errno == errno::EPERM } {
                        not_epoll += 1;
                    } else {
                        return -1;
                    }
                } else {
                    fd_set.clr(fd);
                }
            }
        }
        if let Some(ref mut fd_set) = writefds {
            if fd_set.isset(fd) {
                let mut event = epoll_event {
                    events: EPOLLOUT,
                    data: epoll_data {
                        fd: fd,
                    },
                };
                if epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &mut event) < 0 {
                    if unsafe { platform::errno == errno::EPERM } {
                        not_epoll += 1;
                    } else {
                        return -1;
                    }
                } else {
                    fd_set.clr(fd);
                }
            }
        }
        if let Some(ref mut fd_set) = exceptfds {
            if fd_set.isset(fd) {
                let mut event = epoll_event {
                    events: EPOLLERR,
                    data: epoll_data {
                        fd: fd,
                    },
                };
                if epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &mut event) < 0 {
                    if unsafe { platform::errno == errno::EPERM } {
                        not_epoll += 1;
                    } else {
                        return -1;
                    }
                } else {
                    fd_set.clr(fd);
                }
            }
        }
    }

    let mut events: [epoll_event; 32] = unsafe { mem::zeroed() };
    let epoll_timeout = if not_epoll > 0 {
        // Do not wait if any non-epoll file descriptors were found
        0
    } else {
        match timeout {
            Some(timeout) => {
                //TODO: Check for overflow
                ((timeout.tv_sec as c_int) * 1000) +
                ((timeout.tv_usec as c_int) / 1000)
            },
            None => -1
        }
    };
    let res = epoll_wait(
        *ep,
        events.as_mut_ptr(),
        events.len() as c_int,
        epoll_timeout
    );
    if res < 0 {
        return -1;
    }

    let mut count = not_epoll;
    for i in 0..res as usize {
        let event = &events[i];
        let fd = unsafe { event.data.fd };
        // TODO: Error status when fd does not match?
        if fd >= 0 && fd <= FD_SETSIZE as c_int {
            if event.events & EPOLLIN > 0 {
                if let Some(ref mut fd_set) = readfds {
                    fd_set.set(fd);
                    count += 1;
                }
            }
            if event.events & EPOLLOUT > 0 {
                if let Some(ref mut fd_set) = writefds {
                    fd_set.set(fd);
                    count += 1;
                }
            }
            if event.events & EPOLLERR > 0 {
                if let Some(ref mut fd_set) = exceptfds {
                    fd_set.set(fd);
                    count += 1;
                }
            }
        }
    }
    count
}

#[no_mangle]
pub unsafe extern "C" fn select(
    nfds: c_int,
    readfds: *mut fd_set,
    writefds: *mut fd_set,
    exceptfds: *mut fd_set,
    timeout: *mut timeval,
) -> c_int {
    trace_expr!(
        select_epoll(
            nfds,
            if readfds.is_null() {
                None
            } else {
                Some(&mut *readfds)
            },
            if writefds.is_null() {
                None
            } else {
                Some(&mut *writefds)
            },
            if exceptfds.is_null() {
                None
            } else {
                Some(&mut *exceptfds)
            },
            if timeout.is_null() {
                None
            } else {
                Some(&mut *timeout)
            }
        ),
        "select({}, {:p}, {:p}, {:p}, {:p})",
        nfds,
        readfds,
        writefds,
        exceptfds,
        timeout
    )
}