1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use core::ffi::CStr;

/// A blocking flag when dealing with files
/// When using [`crate::syscalls::SYS_OPEN`], Bit 0 of `flags` argument can be:
/// 0 - non-blocking
/// 1 - line buffered
///
/// In order to use `Block` mode, you need to issue `syscall_blocking_mode`
///  with the whole range of blocking modes available for usage
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockingMode {
    None,
    Line,
    Block(u32),
}

impl BlockingMode {
    pub fn from_flags(flags: u64) -> Self {
        match flags & 1 {
            0 => BlockingMode::None,
            1 => BlockingMode::Line,
            _ => unreachable!(),
        }
    }

    pub fn to_u64(&self) -> u64 {
        match self {
            BlockingMode::None => 0,
            BlockingMode::Line => 1,
            BlockingMode::Block(num) => (*num as u64) << 2 | 3,
        }
    }
}

impl TryFrom<u64> for BlockingMode {
    type Error = ();

    fn try_from(value: u64) -> Result<Self, Self::Error> {
        let mode = value & 3;
        let rest = value >> 2;

        match mode {
            0 if rest == 0 => Ok(BlockingMode::None),
            1 if rest == 0 => Ok(BlockingMode::Line),
            3 if rest != 0 && rest <= 0xFFFF_FFFF => Ok(BlockingMode::Block(rest as u32)),
            _ => Err(()),
        }
    }
}

/// Will extract all the information from the flags, will return `None` if the argument
/// is invalid
pub fn parse_flags(flags: u64) -> Option<BlockingMode> {
    let blocking_mode = BlockingMode::from_flags(flags);
    let flags = flags & !1;
    // must be 0 at the end
    if flags == 0 {
        Some(blocking_mode)
    } else {
        None
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(C)]
pub enum FileType {
    #[default]
    File,
    Directory,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(C)]
pub struct FileStat {
    pub size: u64,
    pub file_type: FileType,
}

pub const MAX_FILENAME_LEN: usize = 255;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DirFilename([u8; MAX_FILENAME_LEN + 1]);

impl Default for DirFilename {
    fn default() -> Self {
        Self([0; MAX_FILENAME_LEN + 1])
    }
}

impl DirFilename {
    pub fn as_cstr(&self) -> &CStr {
        CStr::from_bytes_until_nul(&self.0).unwrap()
    }
}

impl From<&str> for DirFilename {
    fn from(s: &str) -> Self {
        let mut name = [0; MAX_FILENAME_LEN + 1];
        let bytes = s.as_bytes();
        assert!(bytes.len() < MAX_FILENAME_LEN);
        name[..bytes.len()].copy_from_slice(bytes);
        Self(name)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(C)]
pub struct DirEntry {
    pub stat: FileStat,
    pub name: DirFilename,
}

impl DirEntry {
    pub fn filename_cstr(&self) -> &CStr {
        self.name.as_cstr()
    }
}

#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FileMeta {
    BlockingMode(BlockingMode) = 0,
    IsTerminal(bool) = 1,
}

impl FileMeta {
    pub fn to_u64_meta_id(&self) -> u64 {
        match self {
            FileMeta::BlockingMode(_) => 0,
            FileMeta::IsTerminal(_) => 1,
        }
    }

    pub fn inner_u64(&self) -> u64 {
        match self {
            FileMeta::BlockingMode(mode) => mode.to_u64(),
            FileMeta::IsTerminal(is_terminal) => *is_terminal as u64,
        }
    }
}

impl TryFrom<(u64, u64)> for FileMeta {
    type Error = ();

    fn try_from(value: (u64, u64)) -> Result<Self, Self::Error> {
        match value.0 {
            0 => Ok(FileMeta::BlockingMode(BlockingMode::try_from(value.1)?)),
            1 => Ok(FileMeta::IsTerminal(value.1 != 0)),
            _ => Err(()),
        }
    }
}