use super::event_file::EventCreationFlags;
use super::file_ops;
use super::file_ops::{
    get_abs_path_by_fd, get_utimes, AccessibilityCheckFlags, AccessibilityCheckMode, ChownFlags,
    FcntlCmd, FsPath, LinkFlags, StatFlags, UnlinkFlags, Utime, UtimeFlags, AT_FDCWD, UTIME_OMIT,
};
use super::fs_ops;
use super::fs_ops::{MountFlags, MountOptions, UmountFlags};
use super::time::{clockid_t, itimerspec_t, timespec_t, timeval_t, ClockID};
use super::timer_file::{TimerCreationFlags, TimerSetFlags};
use super::*;
use crate::config::{user_rootfs_config, ConfigApp, ConfigMountFsType};
use util::mem_util::from_user;

#[allow(non_camel_case_types)]
pub struct iovec_t {
    base: *const c_void,
    len: size_t,
}

pub fn do_eventfd(init_val: u32) -> Result<isize> {
    do_eventfd2(init_val, 0)
}

pub fn do_eventfd2(init_val: u32, flags: i32) -> Result<isize> {
    info!("eventfd: initval {}, flags {} ", init_val, flags);

    let inner_flags =
        EventCreationFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let file_ref: Arc<dyn File> = {
        let event = EventFile::new(init_val, inner_flags)?;
        Arc::new(event)
    };

    let fd = current!().add_file(
        file_ref,
        inner_flags.contains(EventCreationFlags::EFD_CLOEXEC),
    );
    Ok(fd as isize)
}

pub fn do_timerfd_create(clockid: clockid_t, flags: i32) -> Result<isize> {
    debug!("timerfd: clockid {}, flags {} ", clockid, flags);

    let clockid = ClockID::from_raw(clockid)?;
    match clockid {
        ClockID::CLOCK_REALTIME | ClockID::CLOCK_MONOTONIC => {}
        _ => {
            return_errno!(EINVAL, "invalid clockid");
        }
    }
    let timer_create_flags =
        TimerCreationFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let file_ref: Arc<dyn File> = {
        let timer = TimerFile::new(clockid, timer_create_flags)?;
        Arc::new(timer)
    };

    let fd = current!().add_file(
        file_ref,
        timer_create_flags.contains(TimerCreationFlags::TFD_CLOEXEC),
    );
    Ok(fd as isize)
}

pub fn do_timerfd_settime(
    fd: FileDesc,
    flags: i32,
    new_value_ptr: *const itimerspec_t,
    old_value_ptr: *mut itimerspec_t,
) -> Result<isize> {
    from_user::check_ptr(new_value_ptr)?;
    let new_value = itimerspec_t::from_raw_ptr(new_value_ptr)?;
    let timer_set_flags =
        TimerSetFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;

    let current = current!();
    let file = current.file(fd)?;
    let timerfile = file.as_timer()?;
    let old_value = timerfile.set_time(timer_set_flags, &new_value)?;
    if !old_value_ptr.is_null() {
        from_user::check_mut_ptr(old_value_ptr)?;
        unsafe {
            old_value_ptr.write(old_value);
        }
    }
    Ok(0)
}

pub fn do_timerfd_gettime(fd: FileDesc, curr_value_ptr: *mut itimerspec_t) -> Result<isize> {
    from_user::check_mut_ptr(curr_value_ptr)?;
    let current = current!();
    let file = current.file(fd)?;
    let timerfile = file.as_timer()?;
    let curr_value = timerfile.time()?;
    unsafe {
        curr_value_ptr.write(curr_value);
    }
    Ok(0)
}

pub fn do_creat(path: *const i8, mode: u16) -> Result<isize> {
    let flags =
        AccessMode::O_WRONLY as u32 | (CreationFlags::O_CREAT | CreationFlags::O_TRUNC).bits();
    self::do_open(path, flags, mode)
}

pub fn do_open(path: *const i8, flags: u32, mode: u16) -> Result<isize> {
    self::do_openat(AT_FDCWD, path, flags, mode)
}

pub fn do_openat(dirfd: i32, path: *const i8, flags: u32, mode: u16) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let fs_path = FsPath::new(&path, dirfd, false)?;
    let mode = FileMode::from_bits_truncate(mode);
    let fd = file_ops::do_openat(&fs_path, flags, mode)?;
    Ok(fd as isize)
}

pub fn do_umask(mask: u16) -> Result<isize> {
    let new_mask = FileMode::from_bits_truncate(mask).to_umask();
    let old_mask = current!().process().set_umask(new_mask);
    Ok(old_mask.bits() as isize)
}

pub fn do_close(fd: FileDesc) -> Result<isize> {
    file_ops::do_close(fd)?;
    Ok(0)
}

pub fn do_read(fd: FileDesc, buf: *mut u8, size: usize) -> Result<isize> {
    let safe_buf = {
        from_user::check_mut_array(buf, size)?;
        unsafe { std::slice::from_raw_parts_mut(buf, size) }
    };
    let len = file_ops::do_read(fd, safe_buf)?;
    Ok(len as isize)
}

pub fn do_write(fd: FileDesc, buf: *const u8, size: usize) -> Result<isize> {
    let safe_buf = {
        from_user::check_array(buf, size)?;
        unsafe { std::slice::from_raw_parts(buf, size) }
    };
    let len = file_ops::do_write(fd, safe_buf)?;
    Ok(len as isize)
}

fn do_writev_offset(
    fd: FileDesc,
    iov: *const iovec_t,
    count: i32,
    offset: Option<off_t>,
) -> Result<isize> {
    let count = {
        if count < 0 {
            return_errno!(EINVAL, "Invalid count of iovec");
        }
        count as usize
    };

    from_user::check_array(iov, count)?;
    let bufs_vec = {
        let mut bufs_vec = Vec::with_capacity(count);
        for iov_i in 0..count {
            let iov_ptr = unsafe { iov.offset(iov_i as isize) };
            let iov = unsafe { &*iov_ptr };
            let buf = unsafe { std::slice::from_raw_parts(iov.base as *const u8, iov.len) };
            bufs_vec.push(buf);
        }
        bufs_vec
    };
    let bufs = &bufs_vec[..];

    let len = if let Some(offset) = offset {
        file_ops::do_pwritev(fd, bufs, offset)?
    } else {
        file_ops::do_writev(fd, bufs)?
    };

    Ok(len as isize)
}

pub fn do_writev(fd: FileDesc, iov: *const iovec_t, count: i32) -> Result<isize> {
    do_writev_offset(fd, iov, count, None)
}

fn do_readv_offset(
    fd: FileDesc,
    iov: *mut iovec_t,
    count: i32,
    offset: Option<off_t>,
) -> Result<isize> {
    let count = {
        if count < 0 {
            return_errno!(EINVAL, "Invalid count of iovec");
        }
        count as usize
    };

    from_user::check_array(iov, count)?;
    let mut bufs_vec = {
        let mut bufs_vec = Vec::with_capacity(count);
        for iov_i in 0..count {
            let iov_ptr = unsafe { iov.offset(iov_i as isize) };
            let iov = unsafe { &*iov_ptr };
            let buf = unsafe { std::slice::from_raw_parts_mut(iov.base as *mut u8, iov.len) };
            bufs_vec.push(buf);
        }
        bufs_vec
    };
    let bufs = &mut bufs_vec[..];

    let len = if let Some(offset) = offset {
        file_ops::do_preadv(fd, bufs, offset)?
    } else {
        file_ops::do_readv(fd, bufs)?
    };

    Ok(len as isize)
}

pub fn do_readv(fd: FileDesc, iov: *mut iovec_t, count: i32) -> Result<isize> {
    do_readv_offset(fd, iov, count, None)
}

pub fn do_pread(fd: FileDesc, buf: *mut u8, size: usize, offset: off_t) -> Result<isize> {
    let safe_buf = {
        from_user::check_mut_array(buf, size)?;
        unsafe { std::slice::from_raw_parts_mut(buf, size) }
    };
    let len = file_ops::do_pread(fd, safe_buf, offset)?;
    Ok(len as isize)
}

pub fn do_preadv(fd: FileDesc, iov: *mut iovec_t, count: i32, offset: off_t) -> Result<isize> {
    if offset < 0 {
        return_errno!(EINVAL, "Invalid offset");
    }

    do_readv_offset(fd, iov, count, Some(offset))
}

pub fn do_pwrite(fd: FileDesc, buf: *const u8, size: usize, offset: off_t) -> Result<isize> {
    let safe_buf = {
        from_user::check_array(buf, size)?;
        unsafe { std::slice::from_raw_parts(buf, size) }
    };
    let len = file_ops::do_pwrite(fd, safe_buf, offset)?;
    Ok(len as isize)
}

pub fn do_pwritev(fd: FileDesc, iov: *const iovec_t, count: i32, offset: off_t) -> Result<isize> {
    if offset < 0 {
        return_errno!(EINVAL, "Invalid offset");
    }

    do_writev_offset(fd, iov, count, Some(offset))
}

pub fn do_fstat(fd: FileDesc, stat_buf: *mut Stat) -> Result<isize> {
    from_user::check_mut_ptr(stat_buf)?;

    let stat = file_ops::do_fstat(fd)?;
    unsafe {
        stat_buf.write(stat);
    }
    Ok(0)
}

pub fn do_stat(path: *const i8, stat_buf: *mut Stat) -> Result<isize> {
    self::do_fstatat(AT_FDCWD, path, stat_buf, 0)
}

pub fn do_lstat(path: *const i8, stat_buf: *mut Stat) -> Result<isize> {
    self::do_fstatat(
        AT_FDCWD,
        path,
        stat_buf,
        StatFlags::AT_SYMLINK_NOFOLLOW.bits(),
    )
}

pub fn do_fstatat(dirfd: i32, path: *const i8, stat_buf: *mut Stat, flags: u32) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let flags = StatFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let fs_path = FsPath::new(&path, dirfd, flags.contains(StatFlags::AT_EMPTY_PATH))?;
    from_user::check_mut_ptr(stat_buf)?;
    let stat = file_ops::do_fstatat(&fs_path, flags)?;
    unsafe {
        stat_buf.write(stat);
    }
    Ok(0)
}

pub fn do_access(path: *const i8, mode: u32) -> Result<isize> {
    self::do_faccessat(AT_FDCWD, path, mode, 0)
}

pub fn do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let fs_path = FsPath::new(&path, dirfd, false)?;
    let mode = AccessibilityCheckMode::from_u32(mode)?;
    let flags = AccessibilityCheckFlags::from_u32(flags)?;
    file_ops::do_faccessat(&fs_path, mode, flags).map(|_| 0)
}

pub fn do_lseek(fd: FileDesc, offset: off_t, whence: i32) -> Result<isize> {
    let seek_from = match whence {
        0 => {
            // SEEK_SET
            if offset < 0 {
                return_errno!(EINVAL, "Invalid offset");
            }
            SeekFrom::Start(offset as u64)
        }
        1 => {
            // SEEK_CUR
            SeekFrom::Current(offset)
        }
        2 => {
            // SEEK_END
            SeekFrom::End(offset)
        }
        _ => {
            return_errno!(EINVAL, "Invalid whence");
        }
    };

    let offset = file_ops::do_lseek(fd, seek_from)?;
    Ok(offset as isize)
}

pub fn do_fsync(fd: FileDesc) -> Result<isize> {
    file_ops::do_fsync(fd)?;
    Ok(0)
}

pub fn do_fdatasync(fd: FileDesc) -> Result<isize> {
    file_ops::do_fdatasync(fd)?;
    Ok(0)
}

pub fn do_truncate(path: *const i8, len: usize) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    file_ops::do_truncate(&path, len)?;
    Ok(0)
}

pub fn do_ftruncate(fd: FileDesc, len: usize) -> Result<isize> {
    file_ops::do_ftruncate(fd, len)?;
    Ok(0)
}

pub fn do_getdents64(fd: FileDesc, buf: *mut u8, buf_size: usize) -> Result<isize> {
    let safe_buf = {
        from_user::check_mut_array(buf, buf_size)?;
        unsafe { std::slice::from_raw_parts_mut(buf, buf_size) }
    };
    let len = file_ops::do_getdents64(fd, safe_buf)?;
    Ok(len as isize)
}

pub fn do_getdents(fd: FileDesc, buf: *mut u8, buf_size: usize) -> Result<isize> {
    let safe_buf = {
        from_user::check_mut_array(buf, buf_size)?;
        unsafe { std::slice::from_raw_parts_mut(buf, buf_size) }
    };
    let len = file_ops::do_getdents(fd, safe_buf)?;
    Ok(len as isize)
}

pub fn do_sync() -> Result<isize> {
    fs_ops::do_sync()?;
    Ok(0)
}

pub fn do_pipe(fds_u: *mut i32) -> Result<isize> {
    do_pipe2(fds_u, 0)
}

pub fn do_pipe2(fds_u: *mut i32, flags: u32) -> Result<isize> {
    from_user::check_mut_array(fds_u, 2)?;
    // TODO: how to deal with open flags???
    let fds = pipe::do_pipe2(flags as u32)?;
    unsafe {
        *fds_u.offset(0) = fds[0] as c_int;
        *fds_u.offset(1) = fds[1] as c_int;
    }
    Ok(0)
}

pub fn do_dup(old_fd: FileDesc) -> Result<isize> {
    let new_fd = file_ops::do_dup(old_fd)?;
    Ok(new_fd as isize)
}

pub fn do_dup2(old_fd: FileDesc, new_fd: FileDesc) -> Result<isize> {
    let new_fd = file_ops::do_dup2(old_fd, new_fd)?;
    Ok(new_fd as isize)
}

pub fn do_dup3(old_fd: FileDesc, new_fd: FileDesc, flags: u32) -> Result<isize> {
    let new_fd = file_ops::do_dup3(old_fd, new_fd, flags)?;
    Ok(new_fd as isize)
}

pub fn do_chdir(path: *const i8) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    if path.is_empty() {
        return_errno!(ENOENT, "path is an empty string");
    } else if path.len() > PATH_MAX {
        return_errno!(ENAMETOOLONG, "path name too long");
    }
    fs_ops::do_chdir(&path)?;
    Ok(0)
}

pub fn do_fchdir(fd: FileDesc) -> Result<isize> {
    let path = get_abs_path_by_fd(fd)?;
    fs_ops::do_chdir(&path)?;
    Ok(0)
}

pub fn do_getcwd(buf_ptr: *mut u8, size: usize) -> Result<isize> {
    let buf = {
        from_user::check_mut_array(buf_ptr, size)?;
        unsafe { std::slice::from_raw_parts_mut(buf_ptr, size) }
    };

    let cwd = fs_ops::do_getcwd()?;
    if cwd.len() + 1 > buf.len() {
        return_errno!(ERANGE, "buf is not long enough");
    }
    buf[..cwd.len()].copy_from_slice(cwd.as_bytes());
    buf[cwd.len()] = b'\0';

    // The user-level library returns the pointer of buffer, the kernel just returns
    // the length of the buffer filled (which includes the ending '\0' character).
    Ok((cwd.len() + 1) as isize)
}

pub fn do_rename(oldpath: *const i8, newpath: *const i8) -> Result<isize> {
    self::do_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath)
}

pub fn do_renameat(
    olddirfd: i32,
    oldpath: *const i8,
    newdirfd: i32,
    newpath: *const i8,
) -> Result<isize> {
    let oldpath = from_user::clone_cstring_safely(oldpath)?
        .to_string_lossy()
        .into_owned();
    let newpath = from_user::clone_cstring_safely(newpath)?
        .to_string_lossy()
        .into_owned();
    let old_fs_path = FsPath::new(&oldpath, olddirfd, false)?;
    let new_fs_path = FsPath::new(&newpath, newdirfd, false)?;
    file_ops::do_renameat(&old_fs_path, &new_fs_path)?;
    Ok(0)
}

pub fn do_mkdir(path: *const i8, mode: u16) -> Result<isize> {
    self::do_mkdirat(AT_FDCWD, path, mode)
}

pub fn do_mkdirat(dirfd: i32, path: *const i8, mode: u16) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let fs_path = FsPath::new(&path, dirfd, false)?;
    let mode = FileMode::from_bits_truncate(mode);
    file_ops::do_mkdirat(&fs_path, mode)?;
    Ok(0)
}

pub fn do_rmdir(path: *const i8) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    if path.is_empty() {
        return_errno!(ENOENT, "path is an empty string");
    } else if path.len() > PATH_MAX {
        return_errno!(ENAMETOOLONG, "path name too long");
    }
    file_ops::do_rmdir(&path)?;
    Ok(0)
}

pub fn do_link(oldpath: *const i8, newpath: *const i8) -> Result<isize> {
    self::do_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0)
}

pub fn do_linkat(
    olddirfd: i32,
    oldpath: *const i8,
    newdirfd: i32,
    newpath: *const i8,
    flags: i32,
) -> Result<isize> {
    let oldpath = from_user::clone_cstring_safely(oldpath)?
        .to_string_lossy()
        .into_owned();
    let newpath = from_user::clone_cstring_safely(newpath)?
        .to_string_lossy()
        .into_owned();
    let flags = LinkFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let old_fs_path = FsPath::new(&oldpath, olddirfd, flags.contains(LinkFlags::AT_EMPTY_PATH))?;
    let new_fs_path = FsPath::new(&newpath, newdirfd, false)?;
    file_ops::do_linkat(&old_fs_path, &new_fs_path, flags)?;
    Ok(0)
}

pub fn do_unlink(path: *const i8) -> Result<isize> {
    self::do_unlinkat(AT_FDCWD, path, 0)
}

pub fn do_unlinkat(dirfd: i32, path: *const i8, flags: i32) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let fs_path = FsPath::new(&path, dirfd, false)?;
    let flags =
        UnlinkFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flag value"))?;
    file_ops::do_unlinkat(&fs_path, flags)?;
    Ok(0)
}

pub fn do_readlink(path: *const i8, buf: *mut u8, size: usize) -> Result<isize> {
    self::do_readlinkat(AT_FDCWD, path, buf, size)
}

pub fn do_readlinkat(dirfd: i32, path: *const i8, buf: *mut u8, size: usize) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let buf = {
        from_user::check_array(buf, size)?;
        unsafe { std::slice::from_raw_parts_mut(buf, size) }
    };
    let fs_path = FsPath::new(&path, dirfd, false)?;
    let len = file_ops::do_readlinkat(&fs_path, buf)?;
    Ok(len as isize)
}

pub fn do_symlink(target: *const i8, link_path: *const i8) -> Result<isize> {
    self::do_symlinkat(target, AT_FDCWD, link_path)
}

pub fn do_symlinkat(target: *const i8, new_dirfd: i32, link_path: *const i8) -> Result<isize> {
    let target = from_user::clone_cstring_safely(target)?
        .to_string_lossy()
        .into_owned();
    let link_path = from_user::clone_cstring_safely(link_path)?
        .to_string_lossy()
        .into_owned();
    let fs_path = FsPath::new(&link_path, new_dirfd, false)?;
    file_ops::do_symlinkat(&target, &fs_path)?;
    Ok(0)
}

pub fn do_chmod(path: *const i8, mode: u16) -> Result<isize> {
    self::do_fchmodat(AT_FDCWD, path, mode)
}

pub fn do_fchmod(fd: FileDesc, mode: u16) -> Result<isize> {
    let mode = FileMode::from_bits_truncate(mode);
    file_ops::do_fchmod(fd, mode)?;
    Ok(0)
}

pub fn do_fchmodat(dirfd: i32, path: *const i8, mode: u16) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let mode = FileMode::from_bits_truncate(mode);
    let fs_path = FsPath::new(&path, dirfd, false)?;
    file_ops::do_fchmodat(&fs_path, mode)?;
    Ok(0)
}

pub fn do_chown(path: *const i8, uid: u32, gid: u32) -> Result<isize> {
    self::do_fchownat(AT_FDCWD, path, uid, gid, 0)
}

pub fn do_fchown(fd: FileDesc, uid: u32, gid: u32) -> Result<isize> {
    file_ops::do_fchown(fd, uid, gid)?;
    Ok(0)
}

pub fn do_fchownat(dirfd: i32, path: *const i8, uid: u32, gid: u32, flags: i32) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    let flags = ChownFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let fs_path = FsPath::new(&path, dirfd, flags.contains(ChownFlags::AT_EMPTY_PATH))?;
    file_ops::do_fchownat(&fs_path, uid, gid, flags)?;
    Ok(0)
}

pub fn do_lchown(path: *const i8, uid: u32, gid: u32) -> Result<isize> {
    self::do_fchownat(
        AT_FDCWD,
        path,
        uid,
        gid,
        ChownFlags::AT_SYMLINK_NOFOLLOW.bits(),
    )
}

pub fn do_sendfile(
    out_fd: FileDesc,
    in_fd: FileDesc,
    offset_ptr: *mut off_t,
    count: usize,
) -> Result<isize> {
    let offset = if offset_ptr.is_null() {
        None
    } else {
        from_user::check_mut_ptr(offset_ptr)?;
        Some(unsafe { offset_ptr.read() })
    };

    let (len, offset) = file_ops::do_sendfile(out_fd, in_fd, offset, count)?;
    if !offset_ptr.is_null() {
        unsafe {
            offset_ptr.write(offset as off_t);
        }
    }
    Ok(len as isize)
}

pub fn do_fcntl(fd: FileDesc, cmd: u32, arg: u64) -> Result<isize> {
    let mut cmd = FcntlCmd::from_raw(cmd, arg)?;
    file_ops::do_fcntl(fd, &mut cmd)
}

pub fn do_ioctl(fd: FileDesc, cmd: u32, argp: *mut u8) -> Result<isize> {
    let mut ioctl_cmd = unsafe {
        if argp.is_null() == false {
            from_user::check_mut_ptr(argp)?;
        }
        IoctlCmd::new(cmd, argp)?
    };
    file_ops::do_ioctl(fd, &mut ioctl_cmd)?;
    Ok(0)
}

pub fn do_mount_rootfs(
    key_ptr: *const sgx_key_128bit_t,
    rootfs_config_ptr: *const user_rootfs_config,
) -> Result<isize> {
    let key = if key_ptr.is_null() {
        None
    } else {
        Some(unsafe { key_ptr.read() })
    };
    // If user provided valid parameters, do runtime mount and boot
    // Otherwise, do general mount and boot
    if !rootfs_config_ptr.is_null() {
        from_user::check_ptr(rootfs_config_ptr)?;
        let rootfs_config = unsafe { *rootfs_config_ptr };
        let app_config = ConfigApp::from_user(&rootfs_config)?;
        debug!("user provided app config: {:?}", app_config);
        fs_ops::do_mount_rootfs(&app_config, &key)?;
    } else {
        fs_ops::do_mount_rootfs(&config::LIBOS_CONFIG.get_app_config("app").unwrap(), &key)?;
    }
    Ok((0))
}

pub fn do_fallocate(fd: FileDesc, mode: u32, offset: off_t, len: off_t) -> Result<isize> {
    if offset < 0 || len <= 0 {
        return_errno!(
            EINVAL,
            "offset was less than 0, or len was less than or equal to 0"
        );
    }
    let flags = FallocateFlags::from_u32(mode)?;
    file_ops::do_fallocate(fd, flags, offset as usize, len as usize)?;
    Ok(0)
}

pub fn do_fstatfs(fd: FileDesc, statfs_buf: *mut Statfs) -> Result<isize> {
    from_user::check_mut_ptr(statfs_buf)?;

    let statfs = fs_ops::do_fstatfs(fd)?;
    unsafe {
        statfs_buf.write(statfs);
    }
    Ok(0)
}

pub fn do_statfs(path: *const i8, statfs_buf: *mut Statfs) -> Result<isize> {
    let path = from_user::clone_cstring_safely(path)?
        .to_string_lossy()
        .into_owned();
    if path.is_empty() {
        return_errno!(ENOENT, "path is an empty string");
    } else if path.len() > PATH_MAX {
        return_errno!(ENAMETOOLONG, "path name too long");
    }
    from_user::check_mut_ptr(statfs_buf)?;

    let statfs = fs_ops::do_statfs(&path)?;
    unsafe {
        statfs_buf.write(statfs);
    }
    Ok(0)
}

pub fn do_mount(
    source: *const i8,
    target: *const i8,
    fs_type: *const i8,
    flags: u32,
    options: *const i8,
) -> Result<isize> {
    let source = from_user::clone_cstring_safely(source)?
        .to_string_lossy()
        .into_owned();
    let target = from_user::clone_cstring_safely(target)?
        .to_string_lossy()
        .into_owned();
    if target.is_empty() {
        return_errno!(ENOENT, "target is an empty string");
    } else if target.len() > PATH_MAX {
        return_errno!(ENAMETOOLONG, "target name too long");
    }
    let flags = MountFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
    let mount_options = {
        let fs_type = {
            let fs_type = from_user::clone_cstring_safely(fs_type)?
                .to_string_lossy()
                .into_owned();
            ConfigMountFsType::from_input(fs_type.as_str())?
        };
        MountOptions::from_fs_type_and_options(&fs_type, options)?
    };

    fs_ops::do_mount(&source, &target, flags, mount_options)?;
    Ok(0)
}

pub fn do_umount(target: *const i8, flags: u32) -> Result<isize> {
    let target = from_user::clone_cstring_safely(target)?
        .to_string_lossy()
        .into_owned();
    let flags = UmountFlags::from_u32(flags)?;

    fs_ops::do_umount(&target, flags)?;
    Ok(0)
}

pub fn do_flock(fd: FileDesc, operation: i32) -> Result<isize> {
    let flock_ops = FlockOps::from_i32(operation)?;

    file_ops::do_flock(fd, flock_ops)?;
    Ok(0)
}

pub fn do_utime(path: *const i8, times_u: *const utimbuf_t) -> Result<isize> {
    let times = if !times_u.is_null() {
        from_user::check_ptr(times_u)?;
        let utimbuf = unsafe { *times_u };
        let atime = timespec_t::from(utimbuf.atime());
        atime.validate()?;
        let mtime = timespec_t::from(utimbuf.mtime());
        mtime.validate()?;
        Some((atime, mtime))
    } else {
        None
    };

    let (atime, mtime) = file_ops::get_utimes(times)?;
    self::do_utimes_wrapper(AT_FDCWD, path, atime, mtime, 0)
}

pub fn do_utimes(path: *const i8, times: *const timeval_t) -> Result<isize> {
    self::do_futimesat(AT_FDCWD, path, times)
}

pub fn do_futimesat(dirfd: i32, path: *const i8, times_u: *const timeval_t) -> Result<isize> {
    let times = if !times_u.is_null() {
        from_user::check_array(times_u, 2)?;
        let atime_ptr = unsafe { times_u.offset(0) };
        let atime = unsafe { *atime_ptr };
        let atime = timespec_t::from(atime);
        atime.validate()?;
        let mtime_ptr = unsafe { times_u.offset(1) };
        let mtime = unsafe { *mtime_ptr };
        let mtime = timespec_t::from(mtime);
        mtime.validate()?;
        Some((atime, mtime))
    } else {
        None
    };

    let (atime, mtime) = file_ops::get_utimes(times)?;
    self::do_utimes_wrapper(dirfd, path, atime, mtime, 0)
}

pub fn do_utimensat(
    dirfd: i32,
    path: *const i8,
    times_u: *const timespec_t,
    flags: i32,
) -> Result<isize> {
    let times = if !times_u.is_null() {
        from_user::check_array(times_u, 2)?;
        let atime_ptr = unsafe { times_u.offset(0) };
        let atime = unsafe { *atime_ptr };
        let mtime_ptr = unsafe { times_u.offset(1) };
        let mtime = unsafe { *mtime_ptr };
        if atime.nsec() == UTIME_OMIT && mtime.nsec() == UTIME_OMIT {
            return Ok(0);
        }
        Some((atime, mtime))
    } else {
        None
    };

    let (atime, mtime) = file_ops::get_utimes(times)?;
    self::do_utimes_wrapper(dirfd, path, atime, mtime, flags)
}

fn do_utimes_wrapper(
    dirfd: i32,
    path: *const i8,
    atime: Utime,
    mtime: Utime,
    flags: i32,
) -> Result<isize> {
    if path.is_null() && dirfd != AT_FDCWD {
        file_ops::do_utimes_fd(dirfd as FileDesc, atime, mtime, flags)
    } else {
        let path = from_user::clone_cstring_safely(path)?
            .to_string_lossy()
            .into_owned();
        let flags = UtimeFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
        let fs_path = FsPath::new(&path, dirfd, false)?;
        file_ops::do_utimes_path(&fs_path, atime, mtime, flags)
    }
}
