redox_scheme/
lib.rs

1#![no_std]
2
3extern crate alloc;
4use alloc::format;
5use syscall::dirent::DirentBuf;
6
7use core::str;
8
9use libredox::flag;
10use syscall::schemev2::{Cqe, CqeOpcode, NewFdFlags, Opcode, Sqe};
11use syscall::{
12    Error, EventFlags, FobtainFdFlags, MapFlags, MunmapFlags, Packet, Result, SendFdFlags, Stat,
13    StatVfs, TimeSpec, EBADF, EINTR, EINVAL, ENOENT, EOPNOTSUPP,
14};
15
16pub use self::scheme::Scheme;
17pub use self::scheme_block::SchemeBlock;
18
19mod scheme;
20mod scheme_block;
21
22pub struct CallerCtx {
23    pub pid: usize,
24    pub uid: u32,
25    pub gid: u32,
26}
27
28pub enum OpenResult {
29    ThisScheme { number: usize, flags: NewFdFlags },
30    OtherScheme { fd: usize },
31}
32
33// TODO: Find a better solution than generate.sh
34pub(crate) fn convert_to_this_scheme(r: Result<usize>) -> Result<OpenResult> {
35    r.map(|number| OpenResult::ThisScheme {
36        number,
37        flags: NewFdFlags::empty(),
38    })
39}
40pub(crate) fn convert_to_this_scheme_block(r: Result<Option<usize>>) -> Result<Option<OpenResult>> {
41    r.map(|o| {
42        o.map(|number| OpenResult::ThisScheme {
43            number,
44            flags: NewFdFlags::empty(),
45        })
46    })
47}
48
49impl CallerCtx {
50    pub fn from_packet(packet: &Packet) -> Self {
51        Self {
52            pid: packet.pid,
53            uid: packet.uid,
54            gid: packet.gid,
55        }
56    }
57}
58
59use core::mem::size_of;
60
61#[repr(transparent)]
62#[derive(Clone, Copy, Debug, Default)]
63pub struct Request {
64    sqe: Sqe,
65}
66
67#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
68pub struct Id(u32);
69
70#[derive(Debug)]
71pub struct CancellationRequest {
72    pub id: Id,
73}
74
75#[repr(transparent)]
76#[derive(Clone, Copy, Debug)]
77pub struct CallRequest {
78    inner: Request,
79}
80
81pub struct SendFdRequest {
82    inner: Request,
83}
84
85pub enum RequestKind {
86    Call(CallRequest),
87    Cancellation(CancellationRequest),
88    SendFd(SendFdRequest),
89    MsyncMsg,
90    MunmapMsg,
91    MmapMsg,
92    OnClose { id: usize },
93}
94
95impl CallRequest {
96    #[inline]
97    pub fn request(&self) -> Request {
98        self.inner
99    }
100}
101
102impl CallRequest {
103    pub fn handle_scheme(self, scheme: &mut impl Scheme) -> Response {
104        let Some(opcode) = Opcode::try_from_raw(self.inner.sqe.opcode) else {
105            return Response::new(&self, Err(Error::new(EOPNOTSUPP)));
106        };
107        let args = self.inner.sqe.args;
108
109        let hack_uid = args[5] as u32;
110        let hack_gid = (args[5] >> 32) as u32;
111        let ctx = CallerCtx {
112            pid: self.inner.sqe.caller as usize,
113            uid: hack_uid,
114            gid: hack_gid,
115        };
116
117        let [a, b, c, d, e, _f] = args.map(|a| a as usize);
118        let result = unsafe {
119            use core::{slice, str};
120            match opcode {
121                Opcode::Open => match scheme.xopen(
122                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
123                    c,
124                    &ctx,
125                ) {
126                    Ok(OpenResult::ThisScheme { number, flags }) => {
127                        return Response(Cqe {
128                            tag: self.inner.sqe.tag,
129                            extra_raw: [flags.bits(), 0, 0],
130                            flags: CqeOpcode::RespondRegular as u8,
131                            result: number as u64,
132                        })
133                    }
134                    Err(err) => Err(err),
135                    Ok(OpenResult::OtherScheme { fd }) => {
136                        return Response(Cqe {
137                            tag: self.inner.sqe.tag,
138                            extra_raw: [0_u8; 3],
139                            flags: CqeOpcode::RespondWithFd as u8,
140                            result: fd as u64,
141                        })
142                    }
143                },
144                Opcode::Rmdir => scheme.rmdir(
145                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
146                    hack_uid,
147                    hack_gid,
148                ),
149                Opcode::Unlink => scheme.unlink(
150                    str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
151                    hack_uid,
152                    hack_gid,
153                ),
154                Opcode::Dup => match scheme.xdup(a, slice::from_raw_parts(b as *const u8, c), &ctx)
155                {
156                    Ok(OpenResult::ThisScheme { number, flags }) => {
157                        return Response(Cqe {
158                            tag: self.inner.sqe.tag,
159                            extra_raw: [flags.bits(), 0, 0],
160                            flags: CqeOpcode::RespondRegular as u8,
161                            result: number as u64,
162                        })
163                    }
164                    Err(err) => Err(err),
165                    Ok(OpenResult::OtherScheme { fd }) => {
166                        return Response(Cqe {
167                            tag: self.inner.sqe.tag,
168                            extra_raw: [0_u8; 3],
169                            flags: CqeOpcode::RespondWithFd as u8,
170                            result: fd as u64,
171                        })
172                    }
173                },
174                Opcode::Read => scheme.read(
175                    a,
176                    slice::from_raw_parts_mut(b as *mut u8, c),
177                    args[3],
178                    args[4] as u32,
179                ),
180                Opcode::Write => scheme.write(
181                    a,
182                    slice::from_raw_parts(b as *const u8, c),
183                    args[3],
184                    args[4] as u32,
185                ),
186                Opcode::Getdents => {
187                    DirentBuf::new(slice::from_raw_parts_mut(b as *mut u8, c), d as u16)
188                        .map_or(Err(Error::new(EINVAL)), |buf| {
189                            scheme.getdents(a, buf, e as u64)
190                        })
191                        .map(|b| b.finalize())
192                }
193
194                // TODO: 64-bit offset on 32-bit platforms
195                Opcode::Fsize => scheme.fsize(a).map(|o| o as usize),
196                Opcode::Fchmod => scheme.fchmod(a, b as u16),
197                Opcode::Fchown => scheme.fchown(a, b as u32, c as u32),
198                Opcode::Fcntl => scheme.fcntl(a, b, c),
199                Opcode::Fevent => scheme
200                    .fevent(a, EventFlags::from_bits_retain(b))
201                    .map(|fl| fl.bits()),
202                Opcode::Fpath => scheme.fpath(a, slice::from_raw_parts_mut(b as *mut u8, c)),
203                Opcode::Frename => scheme.frename(
204                    a,
205                    str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)),
206                    hack_uid,
207                    hack_gid,
208                ),
209                Opcode::Fstat => {
210                    assert!(c >= size_of::<Stat>());
211                    scheme.fstat(a, &mut *(b as *mut Stat))
212                }
213                Opcode::Fstatvfs => {
214                    assert!(c >= size_of::<StatVfs>());
215                    scheme.fstatvfs(a, &mut *(b as *mut StatVfs))
216                }
217                Opcode::Fsync => scheme.fsync(a),
218                Opcode::Ftruncate => scheme.ftruncate(a, b),
219                Opcode::Futimens => {
220                    assert!(c <= 2 * core::mem::size_of::<TimeSpec>());
221                    scheme.futimens(
222                        a,
223                        slice::from_raw_parts(
224                            b as *const TimeSpec,
225                            c / core::mem::size_of::<TimeSpec>(),
226                        ),
227                    )
228                }
229
230                Opcode::MmapPrep => scheme.mmap_prep(a, args[3], b, MapFlags::from_bits_retain(c)),
231                Opcode::Munmap => scheme.munmap(a, args[3], b, MunmapFlags::from_bits_retain(c)),
232
233                _ => return Response::new(&self, Err(Error::new(EOPNOTSUPP))),
234            }
235        };
236        Response::new(&self, result)
237    }
238    // TODO: Copy paste is bad, but ./generate.sh is worse
239    pub fn handle_scheme_block(self, scheme: &mut impl SchemeBlock) -> Option<Response> {
240        let Some(opcode) = Opcode::try_from_raw(self.inner.sqe.opcode) else {
241            return Some(Response::new(&self, Err(Error::new(EOPNOTSUPP))));
242        };
243        let args = self.inner.sqe.args;
244
245        let hack_uid = args[5] as u32;
246        let hack_gid = (args[5] >> 32) as u32;
247        let ctx = CallerCtx {
248            pid: self.inner.sqe.caller as usize,
249            uid: hack_uid,
250            gid: hack_gid,
251        };
252
253        let [a, b, c, d, e, _f] = args.map(|a| a as usize);
254        let result = unsafe {
255            use core::{slice, str};
256            match opcode {
257                Opcode::Open => match scheme
258                    .xopen(
259                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
260                        c,
261                        &ctx,
262                    )
263                    .transpose()?
264                {
265                    Ok(OpenResult::ThisScheme { number, flags }) => {
266                        return Some(Response(Cqe {
267                            tag: self.inner.sqe.tag,
268                            extra_raw: [flags.bits(), 0, 0],
269                            flags: CqeOpcode::RespondRegular as u8,
270                            result: number as u64,
271                        }))
272                    }
273                    Err(err) => Err(err),
274                    Ok(OpenResult::OtherScheme { fd }) => {
275                        return Some(Response(Cqe {
276                            tag: self.inner.sqe.tag,
277                            extra_raw: [0_u8; 3],
278                            flags: CqeOpcode::RespondWithFd as u8,
279                            result: fd as u64,
280                        }))
281                    }
282                },
283                Opcode::Rmdir => scheme
284                    .rmdir(
285                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
286                        hack_uid,
287                        hack_gid,
288                    )
289                    .transpose()?,
290                Opcode::Unlink => scheme
291                    .unlink(
292                        str::from_utf8_unchecked(slice::from_raw_parts(a as *const u8, b)),
293                        hack_uid,
294                        hack_gid,
295                    )
296                    .transpose()?,
297                Opcode::Dup => match scheme
298                    .xdup(a, slice::from_raw_parts(b as *const u8, c), &ctx)
299                    .transpose()?
300                {
301                    Ok(OpenResult::ThisScheme { number, flags }) => {
302                        return Some(Response(Cqe {
303                            tag: self.inner.sqe.tag,
304                            extra_raw: [flags.bits(), 0, 0],
305                            flags: CqeOpcode::RespondRegular as u8,
306                            result: number as u64,
307                        }))
308                    }
309                    Err(err) => Err(err),
310                    Ok(OpenResult::OtherScheme { fd }) => {
311                        return Some(Response(Cqe {
312                            tag: self.inner.sqe.tag,
313                            extra_raw: [0_u8; 3],
314                            flags: CqeOpcode::RespondWithFd as u8,
315                            result: fd as u64,
316                        }))
317                    }
318                },
319                Opcode::Read => scheme
320                    .read(
321                        a,
322                        slice::from_raw_parts_mut(b as *mut u8, c),
323                        args[3],
324                        args[4] as u32,
325                    )
326                    .transpose()?,
327                Opcode::Write => scheme
328                    .write(
329                        a,
330                        slice::from_raw_parts(b as *const u8, c),
331                        args[3],
332                        args[4] as u32,
333                    )
334                    .transpose()?,
335
336                Opcode::Getdents => {
337                    DirentBuf::new(slice::from_raw_parts_mut(b as *mut u8, c), d as u16)
338                        .map_or(Err(Error::new(EINVAL)), |buf| {
339                            scheme.getdents(a, buf, e as u64)
340                        })
341                        .map(|b| b.map(|b| b.finalize()))
342                        .transpose()?
343                }
344
345                // TODO: 64-bit offset on 32-bit platforms
346                Opcode::Fsize => scheme.fsize(a).transpose()?.map(|o| o as usize),
347                Opcode::Fchmod => scheme.fchmod(a, b as u16).transpose()?,
348                Opcode::Fchown => scheme.fchown(a, b as u32, c as u32).transpose()?,
349                Opcode::Fcntl => scheme.fcntl(a, b, c).transpose()?,
350                Opcode::Fevent => scheme
351                    .fevent(a, EventFlags::from_bits_retain(b))
352                    .transpose()?
353                    .map(|fl| fl.bits()),
354                Opcode::Fpath => scheme
355                    .fpath(a, slice::from_raw_parts_mut(b as *mut u8, c))
356                    .transpose()?,
357                Opcode::Frename => scheme
358                    .frename(
359                        a,
360                        str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)),
361                        hack_uid,
362                        hack_gid,
363                    )
364                    .transpose()?,
365                Opcode::Fstat => {
366                    assert!(c >= size_of::<Stat>());
367                    scheme.fstat(a, &mut *(b as *mut Stat)).transpose()?
368                }
369                Opcode::Fstatvfs => {
370                    assert!(c >= size_of::<StatVfs>());
371                    scheme.fstatvfs(a, &mut *(b as *mut StatVfs)).transpose()?
372                }
373                Opcode::Fsync => scheme.fsync(a).transpose()?,
374                Opcode::Ftruncate => scheme.ftruncate(a, b).transpose()?,
375                Opcode::Futimens => {
376                    assert!(c <= 2 * core::mem::size_of::<TimeSpec>());
377                    scheme
378                        .futimens(
379                            a,
380                            slice::from_raw_parts(
381                                b as *const TimeSpec,
382                                c / core::mem::size_of::<TimeSpec>(),
383                            ),
384                        )
385                        .transpose()?
386                }
387
388                Opcode::MmapPrep => scheme
389                    .mmap_prep(a, args[3], b, MapFlags::from_bits_retain(c))
390                    .transpose()?,
391                Opcode::Munmap => scheme
392                    .munmap(a, args[3], b, MunmapFlags::from_bits_retain(c))
393                    .transpose()?,
394
395                _ => return Some(Response::new(&self, Err(Error::new(EOPNOTSUPP)))),
396            }
397        };
398        Some(Response::new(&self, result))
399    }
400}
401
402impl SendFdRequest {
403    #[inline]
404    pub fn request(&self) -> Request {
405        self.inner
406    }
407
408    pub fn id(&self) -> usize {
409        self.inner.sqe.args[0] as usize
410    }
411
412    pub fn flags(&self) -> SendFdFlags {
413        SendFdFlags::from_bits_retain(self.inner.sqe.args[1] as usize)
414    }
415
416    pub fn obtain_fd(
417        &self,
418        socket: &Socket,
419        flags: FobtainFdFlags,
420        dst_fd_or_ptr: Result<usize, &mut usize>,
421    ) -> Result<()> {
422        assert!(!flags.contains(FobtainFdFlags::MANUAL_FD));
423        match dst_fd_or_ptr {
424            Ok(dst_fd) => {
425                socket.inner.write(&Cqe {
426                    flags: CqeOpcode::ObtainFd as u8,
427                    extra_raw: usize::to_ne_bytes((flags | FobtainFdFlags::MANUAL_FD).bits())[..3]
428                        .try_into()
429                        .unwrap(),
430                    tag: self.inner.request_id().0,
431                    result: dst_fd as u64,
432                })?;
433            }
434            Err(ptr) => {
435                socket.inner.write(&Cqe {
436                    flags: CqeOpcode::ObtainFd as u8,
437                    extra_raw: usize::to_ne_bytes(flags.bits())[..3].try_into().unwrap(),
438                    tag: self.inner.request_id().0,
439                    result: ptr as *mut usize as u64,
440                })?;
441            }
442        }
443        Ok(())
444    }
445}
446
447impl Request {
448    #[inline]
449    pub fn context_id(&self) -> usize {
450        self.sqe.caller as usize
451    }
452    #[inline]
453    pub fn request_id(&self) -> Id {
454        Id(self.sqe.tag)
455    }
456    pub fn kind(self) -> RequestKind {
457        match Opcode::try_from_raw(self.sqe.opcode) {
458            Some(Opcode::Cancel) => RequestKind::Cancellation(CancellationRequest {
459                id: Id(self.sqe.tag),
460            }),
461            Some(Opcode::Sendfd) => RequestKind::SendFd(SendFdRequest {
462                inner: Request { sqe: self.sqe },
463            }),
464            Some(Opcode::Msync) => RequestKind::MsyncMsg,
465            //Some(Opcode::Munmap) => RequestKind::MunmapMsg,
466            Some(Opcode::RequestMmap) => RequestKind::MmapMsg,
467            Some(Opcode::CloseMsg) => RequestKind::OnClose {
468                id: self.sqe.args[0] as usize,
469            },
470
471            _ => RequestKind::Call(CallRequest {
472                inner: Request { sqe: self.sqe },
473            }),
474        }
475    }
476}
477
478pub struct Socket {
479    inner: libredox::Fd,
480}
481
482impl Socket {
483    fn create_inner(name: &str, nonblock: bool) -> Result<Self> {
484        let mut flags = flag::O_FSYNC | 0x0020_0000 /* O_EXLOCK */;
485
486        if nonblock {
487            flags |= flag::O_NONBLOCK;
488        }
489
490        let fd = libredox::Fd::open(
491            &format!(":{name}"),
492            flag::O_CLOEXEC | flag::O_CREAT | flags,
493            0,
494        )?;
495        Ok(Self { inner: fd })
496    }
497    pub fn create(name: impl AsRef<str>) -> Result<Self> {
498        Self::create_inner(name.as_ref(), false)
499    }
500    pub fn nonblock(name: impl AsRef<str>) -> Result<Self> {
501        Self::create_inner(name.as_ref(), true)
502    }
503    pub fn read_requests(&self, buf: &mut [Request], behavior: SignalBehavior) -> Result<usize> {
504        read_requests(self.inner.raw(), buf, behavior)
505    }
506    pub fn next_request(&self, behavior: SignalBehavior) -> Result<Option<Request>> {
507        let mut buf = [Request::default()];
508        Ok(if self.read_requests(&mut buf, behavior)? > 0 {
509            Some(buf[0])
510        } else {
511            None
512        })
513    }
514    pub fn write_responses(&self, buf: &[Response], behavior: SignalBehavior) -> Result<usize> {
515        write_responses(self.inner.raw(), buf, behavior)
516    }
517    pub fn write_response(&self, resp: Response, behavior: SignalBehavior) -> Result<bool> {
518        Ok(self.write_responses(&[resp], behavior)? > 0)
519    }
520    pub fn post_fevent(&self, id: usize, flags: usize) -> Result<()> {
521        self.inner.write(&Cqe {
522            flags: CqeOpcode::SendFevent as u8,
523            extra_raw: [0_u8; 3],
524            tag: flags as u32,
525            result: id as u64,
526        })?;
527        Ok(())
528    }
529    pub fn inner(&self) -> &libredox::Fd {
530        &self.inner
531    }
532}
533
534#[repr(transparent)]
535#[derive(Clone, Copy, Default)]
536pub struct Response(Cqe);
537
538impl Response {
539    pub fn new(req: &CallRequest, status: Result<usize>) -> Self {
540        Self(Cqe {
541            flags: CqeOpcode::RespondRegular as u8,
542            extra_raw: [0_u8; 3],
543            result: Error::mux(status) as u64,
544            tag: req.inner.sqe.tag,
545        })
546    }
547
548    pub fn for_sendfd(req: &SendFdRequest, status: Result<usize>) -> Self {
549        Self(Cqe {
550            flags: CqeOpcode::RespondRegular as u8,
551            extra_raw: [0_u8; 3],
552            result: Error::mux(status) as u64,
553            tag: req.inner.sqe.tag,
554        })
555    }
556}
557
558pub enum SignalBehavior {
559    Interrupt,
560    Restart,
561}
562
563// TODO: Support uninitialized memory
564#[inline]
565pub fn read_requests(
566    socket: usize,
567    buf: &mut [Request],
568    behavior: SignalBehavior,
569) -> Result<usize> {
570    let len = buf.len().checked_mul(size_of::<Request>()).unwrap();
571
572    let bytes_read = loop {
573        match libredox::call::read(socket, unsafe {
574            core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), len)
575        }) {
576            Ok(n) => break n,
577            Err(error) if error.errno() == EINTR => match behavior {
578                SignalBehavior::Restart => continue,
579                SignalBehavior::Interrupt => return Err(error.into()),
580            },
581            Err(err) => return Err(err.into()),
582        }
583    };
584
585    debug_assert_eq!(bytes_read % size_of::<Request>(), 0);
586
587    Ok(bytes_read / size_of::<Request>())
588}
589
590#[inline]
591pub fn write_responses(socket: usize, buf: &[Response], behavior: SignalBehavior) -> Result<usize> {
592    let bytes = unsafe {
593        core::slice::from_raw_parts(
594            buf.as_ptr().cast(),
595            buf.len().checked_mul(size_of::<Response>()).unwrap(),
596        )
597    };
598
599    let bytes_written = loop {
600        match libredox::call::write(socket, bytes) {
601            Ok(n) => break n,
602            Err(error) if error.errno() == EINTR => match behavior {
603                SignalBehavior::Restart => continue,
604                SignalBehavior::Interrupt => return Err(error.into()),
605            },
606            Err(err) => return Err(err.into()),
607        }
608    };
609    debug_assert_eq!(bytes_written % size_of::<Response>(), 0);
610    Ok(bytes_written / size_of::<Response>())
611}