diff --git a/src/generate.sh b/src/generate.sh
deleted file mode 100755
index 31792a7216923eeae0a86e669366b8bbfae65592..0000000000000000000000000000000000000000
--- a/src/generate.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-echo "Generating SchemeBlock from Scheme"
-sed 's/trait Scheme/trait SchemeBlock/' scheme.rs \
-| sed 's/fn handle(\&mut self, packet: \&mut Packet)/fn handle(\&mut self, packet: \&Packet) -> Option<usize>/' \
-| sed 's/packet.a = Error::mux(res);/res.transpose().map(Error::mux)/' \
-| sed 's/\.map(|f| f\.bits())/\.map(|f| f.map(|f| f.bits()))/' \
-| sed 's/\.map(|o| o as usize)/.map(|o| o.map(|o| o as usize))/' \
-| sed 's/Ok(0)/Ok(Some(0))/g' \
-| sed 's/Result<\([^>]\+\)>/Result<Option<\1>>/g' \
-| sed 's/convert_to_this_scheme/convert_to_this_scheme_block/g' \
-> scheme_block.rs
diff --git a/src/lib.rs b/src/lib.rs
index bca9cc91388f784c8f4893abf38ca0759446ea79..a9cb6e7a4f1a138b6863ff19731b80923068a36d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,22 +2,15 @@
 
 extern crate alloc;
 use alloc::format;
-use syscall::dirent::DirentBuf;
 
 use core::str;
 
 use libredox::flag;
 use syscall::schemev2::{Cqe, CqeOpcode, NewFdFlags, Opcode, Sqe};
-use syscall::{
-    Error, EventFlags, FobtainFdFlags, MapFlags, MunmapFlags, Packet, Result, SendFdFlags, Stat,
-    StatVfs, TimeSpec, EBADF, EINTR, EINVAL, ENOENT, EOPNOTSUPP,
-};
+use syscall::error::{Result, Error, EINTR};
+use syscall::flag::{FobtainFdFlags, SendFdFlags};
 
-pub use self::scheme::Scheme;
-pub use self::scheme_block::SchemeBlock;
-
-mod scheme;
-mod scheme_block;
+pub mod scheme;
 
 pub struct CallerCtx {
     pub pid: usize,
@@ -30,32 +23,6 @@ pub enum OpenResult {
     OtherScheme { fd: usize },
 }
 
-// TODO: Find a better solution than generate.sh
-pub(crate) fn convert_to_this_scheme(r: Result<usize>) -> Result<OpenResult> {
-    r.map(|number| OpenResult::ThisScheme {
-        number,
-        flags: NewFdFlags::empty(),
-    })
-}
-pub(crate) fn convert_to_this_scheme_block(r: Result<Option<usize>>) -> Result<Option<OpenResult>> {
-    r.map(|o| {
-        o.map(|number| OpenResult::ThisScheme {
-            number,
-            flags: NewFdFlags::empty(),
-        })
-    })
-}
-
-impl CallerCtx {
-    pub fn from_packet(packet: &Packet) -> Self {
-        Self {
-            pid: packet.pid,
-            uid: packet.uid,
-            gid: packet.gid,
-        }
-    }
-}
-
 use core::mem::size_of;
 
 #[repr(transparent)]
@@ -99,306 +66,6 @@ impl CallRequest {
     }
 }
 
-impl CallRequest {
-    pub fn handle_scheme(self, scheme: &mut impl Scheme) -> Response {
-        let Some(opcode) = Opcode::try_from_raw(self.inner.sqe.opcode) else {
-            return Response::new(&self, Err(Error::new(EOPNOTSUPP)));
-        };
-        let args = self.inner.sqe.args;
-
-        let hack_uid = args[5] as u32;
-        let hack_gid = (args[5] >> 32) as u32;
-        let ctx = CallerCtx {
-            pid: self.inner.sqe.caller as usize,
-            uid: hack_uid,
-            gid: hack_gid,
-        };
-
-        let [a, b, c, d, e, _f] = args.map(|a| a as usize);
-        let result = unsafe {
-            use core::{slice, str};
-            match opcode {
-                Opcode::Open => match scheme.xopen(
-                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                    c,
-                    &ctx,
-                ) {
-                    Ok(OpenResult::ThisScheme { number, flags }) => {
-                        return Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [flags.bits(), 0, 0],
-                            flags: CqeOpcode::RespondRegular as u8,
-                            result: number as u64,
-                        })
-                    }
-                    Err(err) => Err(err),
-                    Ok(OpenResult::OtherScheme { fd }) => {
-                        return Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [0_u8; 3],
-                            flags: CqeOpcode::RespondWithFd as u8,
-                            result: fd as u64,
-                        })
-                    }
-                },
-                Opcode::Rmdir => scheme.rmdir(
-                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                    hack_uid,
-                    hack_gid,
-                ),
-                Opcode::Unlink => scheme.unlink(
-                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                    hack_uid,
-                    hack_gid,
-                ),
-                Opcode::Dup => match scheme.xdup(a, slice::from_raw_parts(b as *const u8, c), &ctx)
-                {
-                    Ok(OpenResult::ThisScheme { number, flags }) => {
-                        return Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [flags.bits(), 0, 0],
-                            flags: CqeOpcode::RespondRegular as u8,
-                            result: number as u64,
-                        })
-                    }
-                    Err(err) => Err(err),
-                    Ok(OpenResult::OtherScheme { fd }) => {
-                        return Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [0_u8; 3],
-                            flags: CqeOpcode::RespondWithFd as u8,
-                            result: fd as u64,
-                        })
-                    }
-                },
-                Opcode::Read => scheme.read(
-                    a,
-                    slice::from_raw_parts_mut(b as *mut u8, c),
-                    args[3],
-                    args[4] as u32,
-                ),
-                Opcode::Write => scheme.write(
-                    a,
-                    slice::from_raw_parts(b as *const u8, c),
-                    args[3],
-                    args[4] as u32,
-                ),
-                Opcode::Getdents => {
-                    DirentBuf::new(slice::from_raw_parts_mut(b as *mut u8, c), d as u16)
-                        .map_or(Err(Error::new(EINVAL)), |buf| {
-                            scheme.getdents(a, buf, e as u64)
-                        })
-                        .map(|b| b.finalize())
-                }
-
-                // TODO: 64-bit offset on 32-bit platforms
-                Opcode::Fsize => scheme.fsize(a).map(|o| o as usize),
-                Opcode::Fchmod => scheme.fchmod(a, b as u16),
-                Opcode::Fchown => scheme.fchown(a, b as u32, c as u32),
-                Opcode::Fcntl => scheme.fcntl(a, b, c),
-                Opcode::Fevent => scheme
-                    .fevent(a, EventFlags::from_bits_retain(b))
-                    .map(|fl| fl.bits()),
-                Opcode::Fpath => scheme.fpath(a, slice::from_raw_parts_mut(b as *mut u8, c)),
-                Opcode::Frename => scheme.frename(
-                    a,
-                    str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)),
-                    hack_uid,
-                    hack_gid,
-                ),
-                Opcode::Fstat => {
-                    assert!(c >= size_of::<Stat>());
-                    scheme.fstat(a, &mut *(b as *mut Stat))
-                }
-                Opcode::Fstatvfs => {
-                    assert!(c >= size_of::<StatVfs>());
-                    scheme.fstatvfs(a, &mut *(b as *mut StatVfs))
-                }
-                Opcode::Fsync => scheme.fsync(a),
-                Opcode::Ftruncate => scheme.ftruncate(a, b),
-                Opcode::Futimens => {
-                    assert!(c <= 2 * core::mem::size_of::<TimeSpec>());
-                    scheme.futimens(
-                        a,
-                        slice::from_raw_parts(
-                            b as *const TimeSpec,
-                            c / core::mem::size_of::<TimeSpec>(),
-                        ),
-                    )
-                }
-
-                Opcode::MmapPrep => scheme.mmap_prep(a, args[3], b, MapFlags::from_bits_retain(c)),
-                Opcode::Munmap => scheme.munmap(a, args[3], b, MunmapFlags::from_bits_retain(c)),
-
-                _ => return Response::new(&self, Err(Error::new(EOPNOTSUPP))),
-            }
-        };
-        Response::new(&self, result)
-    }
-    // TODO: Copy paste is bad, but ./generate.sh is worse
-    pub fn handle_scheme_block(self, scheme: &mut impl SchemeBlock) -> Option<Response> {
-        let Some(opcode) = Opcode::try_from_raw(self.inner.sqe.opcode) else {
-            return Some(Response::new(&self, Err(Error::new(EOPNOTSUPP))));
-        };
-        let args = self.inner.sqe.args;
-
-        let hack_uid = args[5] as u32;
-        let hack_gid = (args[5] >> 32) as u32;
-        let ctx = CallerCtx {
-            pid: self.inner.sqe.caller as usize,
-            uid: hack_uid,
-            gid: hack_gid,
-        };
-
-        let [a, b, c, d, e, _f] = args.map(|a| a as usize);
-        let result = unsafe {
-            use core::{slice, str};
-            match opcode {
-                Opcode::Open => match scheme
-                    .xopen(
-                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                        c,
-                        &ctx,
-                    )
-                    .transpose()?
-                {
-                    Ok(OpenResult::ThisScheme { number, flags }) => {
-                        return Some(Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [flags.bits(), 0, 0],
-                            flags: CqeOpcode::RespondRegular as u8,
-                            result: number as u64,
-                        }))
-                    }
-                    Err(err) => Err(err),
-                    Ok(OpenResult::OtherScheme { fd }) => {
-                        return Some(Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [0_u8; 3],
-                            flags: CqeOpcode::RespondWithFd as u8,
-                            result: fd as u64,
-                        }))
-                    }
-                },
-                Opcode::Rmdir => scheme
-                    .rmdir(
-                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                        hack_uid,
-                        hack_gid,
-                    )
-                    .transpose()?,
-                Opcode::Unlink => scheme
-                    .unlink(
-                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
-                        hack_uid,
-                        hack_gid,
-                    )
-                    .transpose()?,
-                Opcode::Dup => match scheme
-                    .xdup(a, slice::from_raw_parts(b as *const u8, c), &ctx)
-                    .transpose()?
-                {
-                    Ok(OpenResult::ThisScheme { number, flags }) => {
-                        return Some(Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [flags.bits(), 0, 0],
-                            flags: CqeOpcode::RespondRegular as u8,
-                            result: number as u64,
-                        }))
-                    }
-                    Err(err) => Err(err),
-                    Ok(OpenResult::OtherScheme { fd }) => {
-                        return Some(Response(Cqe {
-                            tag: self.inner.sqe.tag,
-                            extra_raw: [0_u8; 3],
-                            flags: CqeOpcode::RespondWithFd as u8,
-                            result: fd as u64,
-                        }))
-                    }
-                },
-                Opcode::Read => scheme
-                    .read(
-                        a,
-                        slice::from_raw_parts_mut(b as *mut u8, c),
-                        args[3],
-                        args[4] as u32,
-                    )
-                    .transpose()?,
-                Opcode::Write => scheme
-                    .write(
-                        a,
-                        slice::from_raw_parts(b as *const u8, c),
-                        args[3],
-                        args[4] as u32,
-                    )
-                    .transpose()?,
-
-                Opcode::Getdents => {
-                    DirentBuf::new(slice::from_raw_parts_mut(b as *mut u8, c), d as u16)
-                        .map_or(Err(Error::new(EINVAL)), |buf| {
-                            scheme.getdents(a, buf, e as u64)
-                        })
-                        .map(|b| b.map(|b| b.finalize()))
-                        .transpose()?
-                }
-
-                // TODO: 64-bit offset on 32-bit platforms
-                Opcode::Fsize => scheme.fsize(a).transpose()?.map(|o| o as usize),
-                Opcode::Fchmod => scheme.fchmod(a, b as u16).transpose()?,
-                Opcode::Fchown => scheme.fchown(a, b as u32, c as u32).transpose()?,
-                Opcode::Fcntl => scheme.fcntl(a, b, c).transpose()?,
-                Opcode::Fevent => scheme
-                    .fevent(a, EventFlags::from_bits_retain(b))
-                    .transpose()?
-                    .map(|fl| fl.bits()),
-                Opcode::Fpath => scheme
-                    .fpath(a, slice::from_raw_parts_mut(b as *mut u8, c))
-                    .transpose()?,
-                Opcode::Frename => scheme
-                    .frename(
-                        a,
-                        str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)),
-                        hack_uid,
-                        hack_gid,
-                    )
-                    .transpose()?,
-                Opcode::Fstat => {
-                    assert!(c >= size_of::<Stat>());
-                    scheme.fstat(a, &mut *(b as *mut Stat)).transpose()?
-                }
-                Opcode::Fstatvfs => {
-                    assert!(c >= size_of::<StatVfs>());
-                    scheme.fstatvfs(a, &mut *(b as *mut StatVfs)).transpose()?
-                }
-                Opcode::Fsync => scheme.fsync(a).transpose()?,
-                Opcode::Ftruncate => scheme.ftruncate(a, b).transpose()?,
-                Opcode::Futimens => {
-                    assert!(c <= 2 * core::mem::size_of::<TimeSpec>());
-                    scheme
-                        .futimens(
-                            a,
-                            slice::from_raw_parts(
-                                b as *const TimeSpec,
-                                c / core::mem::size_of::<TimeSpec>(),
-                            ),
-                        )
-                        .transpose()?
-                }
-
-                Opcode::MmapPrep => scheme
-                    .mmap_prep(a, args[3], b, MapFlags::from_bits_retain(c))
-                    .transpose()?,
-                Opcode::Munmap => scheme
-                    .munmap(a, args[3], b, MunmapFlags::from_bits_retain(c))
-                    .transpose()?,
-
-                _ => return Some(Response::new(&self, Err(Error::new(EOPNOTSUPP)))),
-            }
-        };
-        Some(Response::new(&self, result))
-    }
-}
-
 impl SendFdRequest {
     #[inline]
     pub fn request(&self) -> Request {
diff --git a/src/scheme.rs b/src/scheme.rs
index 07542c4eb5821d85fa99ce264d3f6477243bd2be..76f8553cd695c56611f59c38509800baeba928d4 100644
--- a/src/scheme.rs
+++ b/src/scheme.rs
@@ -1,133 +1,393 @@
-use crate::*;
-use syscall::dirent::DirentBuf;
-use syscall::error::*;
+#![allow(async_fn_in_trait)]
 
-pub trait Scheme {
-    #[allow(unused_variables)]
-    fn open(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> Result<usize> {
-        Err(Error::new(ENOENT))
+use syscall::schemev2::{Cqe, CqeOpcode, Opcode, Sqe};
+use syscall::{error::*, flag::*, Stat, StatVfs, TimeSpec};
+
+use crate::{CallRequest, CallerCtx, OpenResult, Response};
+
+#[non_exhaustive]
+pub enum Op<'a> {
+    Open { path: &'a str, flags: usize },
+    Rmdir { path: &'a str },
+    Unlink { path: &'a str },
+    Dup { old_fd: usize, buf: &'a [u8] },
+    Read { fd: usize, buf: &'a mut [u8], offset: u64, flags: u32 },
+    Write { fd: usize, buf: &'a [u8], offset: u64, flags: u32 },
+    Fsize { fd: usize },
+    Fchmod { fd: usize, new_mode: u16 },
+    Fchown { fd: usize, new_uid: u32, new_gid: u32 },
+    Fcntl { fd: usize, cmd: usize, arg: usize },
+    Fevent { fd: usize, req_flags: EventFlags },
+    Fpath { fd: usize, buf: &'a mut [u8] },
+    Frename { fd: usize, new_path: &'a str },
+    Fstat { fd: usize, stat: &'a mut Stat },
+    FstatVfs { fd: usize, stat: &'a mut StatVfs },
+    Fsync { fd: usize },
+    Ftruncate { fd: usize, new_sz: u64 },
+    Futimens { fd: usize, times: &'a [TimeSpec] },
+
+    MmapPrep { fd: usize, offset: u64, len: usize, flags: MapFlags },
+    Munmap { fd: usize, offset: u64, len: usize, flags: MunmapFlags },
+}
+
+impl<'a> Op<'a> {
+    pub unsafe fn from_sqe_unchecked(sqe: &Sqe) -> Option<Op<'a>> {
+        let opcode = Opcode::try_from_raw(sqe.opcode)?;
+        let args = sqe.args;
+
+        let [a, b, c, _d, _e, _f] = args.map(|a| a as usize);
+        use core::{str, slice};
+
+        Some(match opcode {
+            Opcode::Open => Op::Open { path: str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)), flags: c},
+            Opcode::Rmdir => Op::Rmdir { path: str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)) },
+            Opcode::Unlink => Op::Unlink { path: str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)) },
+            Opcode::Dup => Op::Dup { old_fd: a, buf: slice::from_raw_parts(b as *const u8, c) },
+            Opcode::Read => Op::Read { fd: a, buf: slice::from_raw_parts_mut(b as *mut u8, c), offset: args[3], flags: args[4] as u32 },
+            Opcode::Write => Op::Write { fd: a, buf: slice::from_raw_parts(b as *const u8, c), offset: args[3], flags: args[4] as u32 },
+
+            // TODO: 64-bit offset on 32-bit platforms
+            Opcode::Fsize => Op::Fsize { fd: a },
+            Opcode::Fchmod => Op::Fchmod { fd: a, new_mode: b as u16 },
+            Opcode::Fchown => Op::Fchown { fd: a, new_uid: b as u32, new_gid: c as u32 },
+            Opcode::Fcntl => Op::Fcntl { fd: a, cmd: b, arg: c },
+            Opcode::Fevent => Op::Fevent { fd: a, req_flags: EventFlags::from_bits_retain(b) },
+            Opcode::Fpath => Op::Fpath { fd: a, buf: slice::from_raw_parts_mut(b as *mut u8, c) },
+            Opcode::Frename => Op::Frename { fd: a, new_path: str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)) },
+            Opcode::Fstat => {
+                assert!(c >= size_of::<Stat>());
+                Op::Fstat { fd: a, stat: &mut *(b as *mut Stat) }
+            }
+            Opcode::Fstatvfs => {
+                assert!(c >= size_of::<StatVfs>());
+                Op::FstatVfs { fd: a, stat: &mut *(b as *mut StatVfs) }
+            }
+            Opcode::Fsync => Op::Fsync { fd: a },
+            Opcode::Ftruncate => Op::Ftruncate { fd: a, new_sz: args[1] },
+            Opcode::Futimens => {
+                assert!(c <= 2 * core::mem::size_of::<TimeSpec>());
+                Op::Futimens { fd: a, times: slice::from_raw_parts(b as *const TimeSpec, c / core::mem::size_of::<TimeSpec>()) }
+            }
+
+            Opcode::MmapPrep => Op::MmapPrep { fd: a, offset: args[3], len: b, flags: MapFlags::from_bits_retain(c) },
+            Opcode::Munmap => Op::Munmap { fd: a, offset: args[3], len: b, flags: MunmapFlags::from_bits_retain(c) },
+
+            _ => return None,
+        })
+    }
+}
+impl CallRequest {
+    pub async fn handle_async(self, s: &mut impl SchemeAsync) -> Result<Response> {
+        let sqe = &self.inner.sqe;
+
+        let caller = CallerCtx {
+            pid: sqe.caller as usize,
+            uid: sqe.args[5] as u32,
+            gid: (sqe.args[5] >> 32) as u32,
+        };
+
+        let Some(op) = (unsafe { Op::from_sqe_unchecked(sqe) }) else {
+            return Ok(Response::new(&self, Err(Error::new(ENOSYS))));
+        };
+
+        Ok(Response::new(&self, match op {
+            Op::Open { path, flags } => return match s.open(path, flags, &caller).await {
+                Ok(OpenResult::ThisScheme { number, flags }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [flags.bits(), 0, 0],
+                    flags: CqeOpcode::RespondRegular as u8,
+                    result: number as u64,
+                })),
+                Err(err) => Err(err),
+                Ok(OpenResult::OtherScheme { fd }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [0_u8; 3],
+                    flags: CqeOpcode::RespondWithFd as u8,
+                    result: fd as u64,
+                })),
+            },
+            Op::Rmdir { path } => s.rmdir(path, &caller).await.map(|()| 0),
+            Op::Unlink { path } => s.unlink(path, &caller).await.map(|()| 0),
+
+            Op::Dup { old_fd, buf } => return match s.dup(old_fd, buf, &caller).await {
+                Ok(OpenResult::ThisScheme { number, flags }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [flags.bits(), 0, 0],
+                    flags: CqeOpcode::RespondRegular as u8,
+                    result: number as u64,
+                })),
+                Err(err) => Err(err),
+                Ok(OpenResult::OtherScheme { fd }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [0_u8; 3],
+                    flags: CqeOpcode::RespondWithFd as u8,
+                    result: fd as u64,
+                })),
+            },
+            Op::Read { fd, buf, offset, flags } => s.read(fd, buf, offset, flags, &caller).await,
+            Op::Write { fd, buf, offset, flags } => s.write(fd, buf, offset, flags, &caller).await,
+
+            // TODO: Don't convert to usize
+            Op::Fsize { fd } => s.fsize(fd, &caller).await.map(|l| l as usize),
+
+            Op::Fchmod { fd, new_mode } => s.fchmod(fd, new_mode, &caller).await.map(|()| 0),
+            Op::Fchown { fd, new_uid, new_gid } => s.fchown(fd, new_uid, new_gid, &caller).await.map(|()| 0),
+            Op::Fcntl { fd, cmd, arg } => s.fcntl(fd, cmd, arg, &caller).await,
+            Op::Fevent { fd, req_flags } => s.fevent(fd, req_flags, &caller).await.map(|f| f.bits()),
+            Op::Fpath { fd, buf } => s.fpath(fd, buf, &caller).await,
+            Op::Frename { fd, new_path } => s.frename(fd, new_path, &caller).await,
+            Op::Fstat { fd, stat } => s.fstat(fd, stat, &caller).await.map(|()| 0),
+            Op::FstatVfs { fd, stat } => s.fstatvfs(fd, stat, &caller).await.map(|()| 0),
+            Op::Fsync { fd } => s.fsync(fd, &caller).await.map(|()| 0),
+            Op::Ftruncate { fd, new_sz } => s.ftruncate(fd, new_sz, &caller).await.map(|()| 0),
+            Op::Futimens { fd, times } => s.futimens(fd, times, &caller).await.map(|()| 0),
+
+            Op::MmapPrep { fd, offset, len, flags } => s.mmap_prep(fd, offset, len, flags, &caller).await,
+            Op::Munmap { fd, offset, len, flags } => s.munmap(fd, offset, len, flags, &caller).await.map(|()| 0),
+        }))
+    }
+    // TODO: Fix function coloring
+    pub async fn handle_sync(self, s: &mut impl SchemeSync) -> Result<Response> {
+        let sqe = &self.inner.sqe;
+
+        let caller = CallerCtx {
+            pid: sqe.caller as usize,
+            uid: sqe.args[5] as u32,
+            gid: (sqe.args[5] >> 32) as u32,
+        };
+
+        let Some(op) = (unsafe { Op::from_sqe_unchecked(sqe) }) else {
+            return Ok(Response::new(&self, Err(Error::new(ENOSYS))));
+        };
+
+        Ok(Response::new(&self, match op {
+            Op::Open { path, flags } => return match s.open(path, flags, &caller) {
+                Ok(OpenResult::ThisScheme { number, flags }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [flags.bits(), 0, 0],
+                    flags: CqeOpcode::RespondRegular as u8,
+                    result: number as u64,
+                })),
+                Err(err) => Err(err),
+                Ok(OpenResult::OtherScheme { fd }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [0_u8; 3],
+                    flags: CqeOpcode::RespondWithFd as u8,
+                    result: fd as u64,
+                })),
+            },
+            Op::Rmdir { path } => s.rmdir(path, &caller).map(|()| 0),
+            Op::Unlink { path } => s.unlink(path, &caller).map(|()| 0),
+
+            Op::Dup { old_fd, buf } => return match s.dup(old_fd, buf, &caller) {
+                Ok(OpenResult::ThisScheme { number, flags }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [flags.bits(), 0, 0],
+                    flags: CqeOpcode::RespondRegular as u8,
+                    result: number as u64,
+                })),
+                Err(err) => Err(err),
+                Ok(OpenResult::OtherScheme { fd }) => Ok(Response(Cqe {
+                    tag: sqe.tag,
+                    extra_raw: [0_u8; 3],
+                    flags: CqeOpcode::RespondWithFd as u8,
+                    result: fd as u64,
+                })),
+            },
+            Op::Read { fd, buf, offset, flags } => s.read(fd, buf, offset, flags, &caller),
+            Op::Write { fd, buf, offset, flags } => s.write(fd, buf, offset, flags, &caller),
+
+            // TODO: Don't convert to usize
+            Op::Fsize { fd } => s.fsize(fd, &caller).map(|l| l as usize),
+
+            Op::Fchmod { fd, new_mode } => s.fchmod(fd, new_mode, &caller).map(|()| 0),
+            Op::Fchown { fd, new_uid, new_gid } => s.fchown(fd, new_uid, new_gid, &caller).map(|()| 0),
+            Op::Fcntl { fd, cmd, arg } => s.fcntl(fd, cmd, arg, &caller),
+            Op::Fevent { fd, req_flags } => s.fevent(fd, req_flags, &caller).map(|f| f.bits()),
+            Op::Fpath { fd, buf } => s.fpath(fd, buf, &caller),
+            Op::Frename { fd, new_path } => s.frename(fd, new_path, &caller),
+            Op::Fstat { fd, stat } => s.fstat(fd, stat, &caller).map(|()| 0),
+            Op::FstatVfs { fd, stat } => s.fstatvfs(fd, stat, &caller).map(|()| 0),
+            Op::Fsync { fd } => s.fsync(fd, &caller).map(|()| 0),
+            Op::Ftruncate { fd, new_sz } => s.ftruncate(fd, new_sz, &caller).map(|()| 0),
+            Op::Futimens { fd, times } => s.futimens(fd, times, &caller).map(|()| 0),
+
+            Op::MmapPrep { fd, offset, len, flags } => s.mmap_prep(fd, offset, len, flags, &caller),
+            Op::Munmap { fd, offset, len, flags } => s.munmap(fd, offset, len, flags, &caller).map(|()| 0),
+        }))
     }
-    #[allow(unused_variables)]
-    fn xopen(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
-        convert_to_this_scheme(self.open(path, flags, ctx.uid, ctx.gid))
+}
+
+#[allow(unused_variables)]
+pub trait SchemeAsync {
+    /* Scheme operations */
+    async fn open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
+        Err(Error::new(ENOENT))
     }
 
-    #[allow(unused_variables)]
-    fn rmdir(&mut self, path: &str, uid: u32, gid: u32) -> Result<usize> {
+    async fn rmdir(&mut self, path: &str, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(ENOENT))
     }
 
-    #[allow(unused_variables)]
-    fn unlink(&mut self, path: &str, uid: u32, gid: u32) -> Result<usize> {
+    async fn unlink(&mut self, path: &str, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(ENOENT))
     }
 
     /* Resource operations */
-    #[allow(unused_variables)]
-    fn dup(&mut self, old_id: usize, buf: &[u8]) -> Result<usize> {
+    async fn dup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result<OpenResult> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn read(&self, id: usize, buf: &mut [u8], offset: u64, fcntl_flags: u32, ctx: &CallerCtx) -> Result<usize> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn write(&mut self, id: usize, buf: &[u8], offset: u64, fcntl_flags: u32, ctx: &CallerCtx) -> Result<usize> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn fsize(&mut self, id: usize, ctx: &CallerCtx) -> Result<u64> {
+        Err(Error::new(ESPIPE))
+    }
+
+    async fn fchmod(&mut self, id: usize, new_mode: u16, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn fchown(&mut self, id: usize, new_uid: u32, new_gid: u32, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn fcntl(&mut self, id: usize, cmd: usize, arg: usize, ctx: &CallerCtx) -> Result<usize> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn fevent(&mut self, id: usize, flags: EventFlags, ctx: &CallerCtx) -> Result<EventFlags> {
+        Ok(EventFlags::empty())
+    }
+
+    async fn fpath(&mut self, id: usize, buf: &mut [u8], ctx: &CallerCtx) -> Result<usize> {
+        Err(Error::new(EBADF))
+    }
+
+    async fn frename(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn xdup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result<OpenResult> {
-        convert_to_this_scheme(self.dup(old_id, buf))
+    async fn fstat(&mut self, id: usize, stat: &mut Stat, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn read_old(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
+    async fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
-    #[allow(unused_variables)]
-    fn read(&mut self, id: usize, buf: &mut [u8], offset: u64, fcntl_flags: u32) -> Result<usize> {
-        self.read_old(id, buf)
+
+    async fn fsync(&mut self, id: usize, ctx: &CallerCtx) -> Result<()> {
+        Ok(())
     }
 
-    #[allow(unused_variables)]
-    fn write_old(&mut self, id: usize, buf: &[u8]) -> Result<usize> {
+    async fn ftruncate(&mut self, id: usize, len: u64, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
-    #[allow(unused_variables)]
-    fn write(&mut self, id: usize, buf: &[u8], offset: u64, fcntl_flags: u32) -> Result<usize> {
-        self.write_old(id, buf)
+
+    async fn futimens(&mut self, id: usize, times: &[TimeSpec], ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn getdents<'buf>(
-        &mut self,
-        id: usize,
-        buf: DirentBuf<&'buf mut [u8]>,
-        opaque_offset: u64,
-    ) -> Result<DirentBuf<&'buf mut [u8]>> {
+    async fn mmap_prep(&mut self, id: usize, offset: u64, size: usize, flags: MapFlags, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EOPNOTSUPP))
     }
 
-    #[allow(unused_variables)]
-    fn fsize(&mut self, id: usize) -> Result<u64> {
-        Err(Error::new(ESPIPE))
+    async fn munmap(&mut self, id: usize, offset: u64, size: usize, flags: MunmapFlags, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(EOPNOTSUPP))
+    }
+}
+#[allow(unused_variables)]
+pub trait SchemeSync {
+    /* Scheme operations */
+    fn open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
+        Err(Error::new(ENOENT))
     }
 
-    #[allow(unused_variables)]
-    fn fchmod(&mut self, id: usize, mode: u16) -> Result<usize> {
+
+    fn rmdir(&mut self, path: &str, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(ENOENT))
+    }
+
+
+    fn unlink(&mut self, path: &str, ctx: &CallerCtx) -> Result<()> {
+        Err(Error::new(ENOENT))
+    }
+
+    /* Resource operations */
+
+    fn dup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result<OpenResult> {
+        Err(Error::new(EBADF))
+    }
+
+
+    fn read(&self, id: usize, buf: &mut [u8], offset: u64, fcntl_flags: u32, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fchown(&mut self, id: usize, uid: u32, gid: u32) -> Result<usize> {
+
+    fn write(&mut self, id: usize, buf: &[u8], offset: u64, fcntl_flags: u32, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fcntl(&mut self, id: usize, cmd: usize, arg: usize) -> Result<usize> {
+    fn fsize(&mut self, id: usize, ctx: &CallerCtx) -> Result<u64> {
+        Err(Error::new(ESPIPE))
+    }
+
+    fn fchmod(&mut self, id: usize, new_mode: u16, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fevent(&mut self, id: usize, flags: EventFlags) -> Result<EventFlags> {
+    fn fchown(&mut self, id: usize, new_uid: u32, new_gid: u32, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
+    fn fcntl(&mut self, id: usize, cmd: usize, arg: usize, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn frename(&mut self, id: usize, path: &str, uid: u32, gid: u32) -> Result<usize> {
+    fn fevent(&mut self, id: usize, flags: EventFlags, ctx: &CallerCtx) -> Result<EventFlags> {
+        Ok(EventFlags::empty())
+    }
+
+    fn fpath(&mut self, id: usize, buf: &mut [u8], ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<usize> {
+    fn frename(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs) -> Result<usize> {
+    fn fstat(&mut self, id: usize, stat: &mut Stat, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn fsync(&mut self, id: usize) -> Result<usize> {
+    fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn ftruncate(&mut self, id: usize, len: usize) -> Result<usize> {
+    fn fsync(&mut self, id: usize, ctx: &CallerCtx) -> Result<()> {
+        Ok(())
+    }
+
+    fn ftruncate(&mut self, id: usize, len: u64, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn futimens(&mut self, id: usize, times: &[TimeSpec]) -> Result<usize> {
+    fn futimens(&mut self, id: usize, times: &[TimeSpec], ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EBADF))
     }
 
-    #[allow(unused_variables)]
-    fn mmap_prep(&mut self, id: usize, offset: u64, size: usize, flags: MapFlags) -> Result<usize> {
+    fn mmap_prep(&mut self, id: usize, offset: u64, size: usize, flags: MapFlags, ctx: &CallerCtx) -> Result<usize> {
         Err(Error::new(EOPNOTSUPP))
     }
 
-    #[allow(unused_variables)]
-    fn munmap(&mut self, id: usize, offset: u64, size: usize, flags: MunmapFlags) -> Result<usize> {
+    fn munmap(&mut self, id: usize, offset: u64, size: usize, flags: MunmapFlags, ctx: &CallerCtx) -> Result<()> {
         Err(Error::new(EOPNOTSUPP))
     }
 }
diff --git a/src/scheme_block.rs b/src/scheme_block.rs
deleted file mode 100644
index e365c366c0695acb901b8a996a9378bcb5106108..0000000000000000000000000000000000000000
--- a/src/scheme_block.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-use crate::*;
-use syscall::dirent::DirentBuf;
-use syscall::error::*;
-
-pub trait SchemeBlock {
-    #[allow(unused_variables)]
-    fn open(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> Result<Option<usize>> {
-        Err(Error::new(ENOENT))
-    }
-    #[allow(unused_variables)]
-    fn xopen(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<Option<OpenResult>> {
-        convert_to_this_scheme_block(self.open(path, flags, ctx.uid, ctx.gid))
-    }
-
-    #[allow(unused_variables)]
-    fn rmdir(&mut self, path: &str, uid: u32, gid: u32) -> Result<Option<usize>> {
-        Err(Error::new(ENOENT))
-    }
-
-    #[allow(unused_variables)]
-    fn unlink(&mut self, path: &str, uid: u32, gid: u32) -> Result<Option<usize>> {
-        Err(Error::new(ENOENT))
-    }
-
-    /* Resource operations */
-    #[allow(unused_variables)]
-    fn dup(&mut self, old_id: usize, buf: &[u8]) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn xdup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result<Option<OpenResult>> {
-        convert_to_this_scheme_block(self.dup(old_id, buf))
-    }
-
-    #[allow(unused_variables)]
-    fn read_old(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-    #[allow(unused_variables)]
-    fn read(
-        &mut self,
-        id: usize,
-        buf: &mut [u8],
-        offset: u64,
-        fcntl_flags: u32,
-    ) -> Result<Option<usize>> {
-        self.read_old(id, buf)
-    }
-
-    #[allow(unused_variables)]
-    fn write_old(&mut self, id: usize, buf: &[u8]) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-    #[allow(unused_variables)]
-    fn write(
-        &mut self,
-        id: usize,
-        buf: &[u8],
-        offset: u64,
-        fcntl_flags: u32,
-    ) -> Result<Option<usize>> {
-        self.write_old(id, buf)
-    }
-
-    #[allow(unused_variables)]
-    fn getdents<'buf>(
-        &mut self,
-        id: usize,
-        buf: DirentBuf<&'buf mut [u8]>,
-        opaque_offset: u64,
-    ) -> Result<Option<DirentBuf<&'buf mut [u8]>>> {
-        Err(Error::new(EOPNOTSUPP))
-    }
-
-    #[allow(unused_variables)]
-    fn fsize(&mut self, id: usize) -> Result<Option<u64>> {
-        Err(Error::new(ESPIPE))
-    }
-
-    #[allow(unused_variables)]
-    fn fchmod(&mut self, id: usize, mode: u16) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fchown(&mut self, id: usize, uid: u32, gid: u32) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fcntl(&mut self, id: usize, cmd: usize, arg: usize) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fevent(&mut self, id: usize, flags: EventFlags) -> Result<Option<EventFlags>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn frename(&mut self, id: usize, path: &str, uid: u32, gid: u32) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn fsync(&mut self, id: usize) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn ftruncate(&mut self, id: usize, len: usize) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn futimens(&mut self, id: usize, times: &[TimeSpec]) -> Result<Option<usize>> {
-        Err(Error::new(EBADF))
-    }
-
-    #[allow(unused_variables)]
-    fn mmap_prep(
-        &mut self,
-        id: usize,
-        offset: u64,
-        size: usize,
-        flags: MapFlags,
-    ) -> Result<Option<usize>> {
-        Err(Error::new(EOPNOTSUPP))
-    }
-
-    #[allow(unused_variables)]
-    fn munmap(
-        &mut self,
-        id: usize,
-        offset: u64,
-        size: usize,
-        flags: MunmapFlags,
-    ) -> Result<Option<usize>> {
-        Err(Error::new(EOPNOTSUPP))
-    }
-}