starry_api/imp/fs/
ctl.rs

1use core::{
2    ffi::{c_char, c_int, c_void},
3    mem::offset_of,
4};
5
6use alloc::ffi::CString;
7use axerrno::{LinuxError, LinuxResult};
8use axfs::fops::DirEntry;
9use linux_raw_sys::general::{
10    AT_FDCWD, AT_REMOVEDIR, DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, DT_UNKNOWN,
11    linux_dirent64,
12};
13
14use crate::{
15    file::{Directory, FileLike},
16    path::{HARDLINK_MANAGER, handle_file_path},
17    ptr::{UserConstPtr, UserPtr, nullable},
18};
19
20/// The ioctl() system call manipulates the underlying device parameters
21/// of special files.
22///
23/// # Arguments
24/// * `fd` - The file descriptor
25/// * `op` - The request code. It is of type unsigned long in glibc and BSD,
26///   and of type int in musl and other UNIX systems.
27/// * `argp` - The argument to the request. It is a pointer to a memory location
28pub fn sys_ioctl(_fd: i32, _op: usize, _argp: UserPtr<c_void>) -> LinuxResult<isize> {
29    warn!("Unimplemented syscall: SYS_IOCTL");
30    Ok(0)
31}
32
33pub fn sys_chdir(path: UserConstPtr<c_char>) -> LinuxResult<isize> {
34    let path = path.get_as_str()?;
35    debug!("sys_chdir <= {:?}", path);
36
37    axfs::api::set_current_dir(path)?;
38    Ok(0)
39}
40
41pub fn sys_mkdirat(dirfd: i32, path: UserConstPtr<c_char>, mode: u32) -> LinuxResult<isize> {
42    let path = path.get_as_str()?;
43    debug!(
44        "sys_mkdirat <= dirfd: {}, path: {}, mode: {}",
45        dirfd, path, mode
46    );
47
48    if mode != 0 {
49        warn!("directory mode not supported.");
50    }
51
52    let path = handle_file_path(dirfd, path)?;
53    axfs::api::create_dir(path.as_str())?;
54
55    Ok(0)
56}
57
58#[allow(dead_code)]
59#[repr(u8)]
60#[derive(Debug, Clone, Copy)]
61pub enum FileType {
62    Unknown = DT_UNKNOWN as u8,
63    Fifo = DT_FIFO as u8,
64    Chr = DT_CHR as u8,
65    Dir = DT_DIR as u8,
66    Blk = DT_BLK as u8,
67    Reg = DT_REG as u8,
68    Lnk = DT_LNK as u8,
69    Socket = DT_SOCK as u8,
70}
71
72impl From<axfs::api::FileType> for FileType {
73    fn from(ft: axfs::api::FileType) -> Self {
74        match ft {
75            ft if ft.is_dir() => FileType::Dir,
76            ft if ft.is_file() => FileType::Reg,
77            _ => FileType::Unknown,
78        }
79    }
80}
81
82// Directory buffer for getdents64 syscall
83struct DirBuffer<'a> {
84    buf: &'a mut [u8],
85    offset: usize,
86}
87
88impl<'a> DirBuffer<'a> {
89    fn new(buf: &'a mut [u8]) -> Self {
90        Self { buf, offset: 0 }
91    }
92
93    fn remaining_space(&self) -> usize {
94        self.buf.len().saturating_sub(self.offset)
95    }
96
97    fn write_entry(&mut self, d_type: FileType, name: &[u8]) -> bool {
98        const NAME_OFFSET: usize = offset_of!(linux_dirent64, d_name);
99
100        let len = NAME_OFFSET + name.len() + 1;
101        // alignment
102        let len = len.next_multiple_of(align_of::<linux_dirent64>());
103        if self.remaining_space() < len {
104            return false;
105        }
106
107        unsafe {
108            let entry_ptr = self.buf.as_mut_ptr().add(self.offset);
109            entry_ptr.cast::<linux_dirent64>().write(linux_dirent64 {
110                // FIXME: real inode number
111                d_ino: 1,
112                d_off: 0,
113                d_reclen: len as _,
114                d_type: d_type as _,
115                d_name: Default::default(),
116            });
117
118            let name_ptr = entry_ptr.add(NAME_OFFSET);
119            name_ptr.copy_from_nonoverlapping(name.as_ptr(), name.len());
120            name_ptr.add(name.len()).write(0);
121        }
122
123        self.offset += len;
124        true
125    }
126}
127
128pub fn sys_getdents64(fd: i32, buf: UserPtr<u8>, len: usize) -> LinuxResult<isize> {
129    let buf = buf.get_as_mut_slice(len)?;
130    debug!(
131        "sys_getdents64 <= fd: {}, buf: {:p}, len: {}",
132        fd,
133        buf.as_ptr(),
134        buf.len()
135    );
136
137    let mut buffer = DirBuffer::new(buf);
138
139    let dir = Directory::from_fd(fd)?;
140
141    let mut last_dirent = dir.last_dirent();
142    if let Some(ent) = last_dirent.take()
143        && !buffer.write_entry(ent.entry_type().into(), ent.name_as_bytes())
144    {
145        *last_dirent = Some(ent);
146        return Err(LinuxError::EINVAL);
147    }
148
149    let mut inner = dir.inner();
150    loop {
151        let mut dirents = [DirEntry::default()];
152        let cnt = inner.read_dir(&mut dirents)?;
153        if cnt == 0 {
154            break;
155        }
156
157        let [ent] = dirents;
158        if !buffer.write_entry(ent.entry_type().into(), ent.name_as_bytes()) {
159            *last_dirent = Some(ent);
160            break;
161        }
162    }
163
164    if last_dirent.is_some() && buffer.offset == 0 {
165        return Err(LinuxError::EINVAL);
166    }
167    Ok(buffer.offset as _)
168}
169
170/// create a link from new_path to old_path
171/// old_path: old file path
172/// new_path: new file path
173/// flags: link flags
174/// return value: return 0 when success, else return -1.
175pub fn sys_linkat(
176    old_dirfd: c_int,
177    old_path: UserConstPtr<c_char>,
178    new_dirfd: c_int,
179    new_path: UserConstPtr<c_char>,
180    flags: i32,
181) -> LinuxResult<isize> {
182    let old_path = old_path.get_as_str()?;
183    let new_path = new_path.get_as_str()?;
184    debug!(
185        "sys_linkat <= old_dirfd: {}, old_path: {}, new_dirfd: {}, new_path: {}, flags: {}",
186        old_dirfd, old_path, new_dirfd, new_path, flags
187    );
188
189    if flags != 0 {
190        warn!("Unsupported flags: {flags}");
191    }
192
193    // handle old path
194    let old_path = handle_file_path(old_dirfd, old_path)?;
195    // handle new path
196    let new_path = handle_file_path(new_dirfd, new_path)?;
197
198    HARDLINK_MANAGER.create_link(&new_path, &old_path)?;
199
200    Ok(0)
201}
202
203pub fn sys_link(
204    old_path: UserConstPtr<c_char>,
205    new_path: UserConstPtr<c_char>,
206) -> LinuxResult<isize> {
207    sys_linkat(AT_FDCWD, old_path, AT_FDCWD, new_path, 0)
208}
209
210/// remove link of specific file (can be used to delete file)
211/// dir_fd: the directory of link to be removed
212/// path: the name of link to be removed
213/// flags: can be 0 or AT_REMOVEDIR
214/// return 0 when success, else return -1
215pub fn sys_unlinkat(dirfd: c_int, path: UserConstPtr<c_char>, flags: u32) -> LinuxResult<isize> {
216    let path = path.get_as_str()?;
217    debug!(
218        "sys_unlinkat <= dirfd: {}, path: {}, flags: {}",
219        dirfd, path, flags
220    );
221
222    let path = handle_file_path(dirfd, path)?;
223
224    if flags == AT_REMOVEDIR {
225        axfs::api::remove_dir(path.as_str())?;
226    } else {
227        let metadata = axfs::api::metadata(path.as_str())?;
228        if metadata.is_dir() {
229            return Err(LinuxError::EISDIR);
230        } else {
231            debug!("unlink file: {:?}", path);
232            HARDLINK_MANAGER
233                .remove_link(&path)
234                .ok_or(LinuxError::ENOENT)?;
235        }
236    }
237    Ok(0)
238}
239
240pub fn sys_unlink(path: UserConstPtr<c_char>) -> LinuxResult<isize> {
241    sys_unlinkat(AT_FDCWD, path, 0)
242}
243
244pub fn sys_getcwd(buf: UserPtr<u8>, size: usize) -> LinuxResult<isize> {
245    let buf = nullable!(buf.get_as_mut_slice(size))?;
246
247    let Some(buf) = buf else {
248        return Ok(0);
249    };
250
251    let cwd = CString::new(axfs::api::current_dir()?).map_err(|_| LinuxError::EINVAL)?;
252    let cwd = cwd.as_bytes_with_nul();
253
254    if cwd.len() <= buf.len() {
255        buf[..cwd.len()].copy_from_slice(cwd);
256        Ok(buf.as_ptr() as _)
257    } else {
258        Err(LinuxError::ERANGE)
259    }
260}