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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
//! 模拟的链接、挂载模块
//! fat32本身不支持符号链接和硬链接,两个指向相同文件的目录条目将会被chkdsk报告为交叉链接并修复
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::{String, ToString};
use axerrno::{AxError, AxResult};
use axfs::api::{canonicalize, path_exists, remove_file, FileIOType};
use axlog::{debug, info, trace};
use axsync::Mutex;

use crate::current_process;
#[allow(unused)]
/// The file descriptor used to specify the current working directory of a process
pub const AT_FDCWD: usize = -100isize as usize;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
/// A struct to represent a file path, which will be canonicalized
pub struct FilePath(String);

impl FilePath {
    /// 创建一个 FilePath, 传入的 path 会被 canonicalize, 故可以是相对路径
    pub fn new(path: &str) -> AxResult<Self> {
        let new_path = canonicalize(path);
        if new_path.is_err() {
            return Err(AxError::NotFound);
        }
        let mut new_path = String::from(new_path.unwrap().trim());
        // canonicalize中没有处理末尾的空格、换行符等
        if path.ends_with('/') && !new_path.ends_with('/') {
            // 如果原始路径以 '/' 结尾,那么canonicalize后的路径也应该以 '/' 结尾
            new_path.push('/');
        }
        let new_path = real_path(&new_path);
        // assert!(!path.ends_with("/"), "path should not end with '/', link only support file");      // 链接只支持文件
        Ok(Self(new_path))
    }

    /// 获取路径
    pub fn path(&self) -> &str {
        &self.0
    }
    /// 获取所属目录
    #[allow(unused)]
    pub fn dir(&self) -> AxResult<&str> {
        if self.is_root() {
            return Ok("/");
        }
        let mut pos = if let Some(pos) = self.0.rfind('/') {
            pos
        } else {
            return Err(AxError::NotADirectory);
        };
        if pos == self.0.len() - 1 {
            // 如果是以 '/' 结尾,那么再往前找一次
            pos = if let Some(pos) = self.0[..pos].rfind('/') {
                pos
            } else {
                return Err(AxError::NotADirectory);
            };
        }
        Ok(&self.0[..=pos])
    }
    /// 获取文件/目录名
    #[allow(unused)]
    pub fn file(&self) -> AxResult<&str> {
        if self.is_root() {
            return Ok("/");
        }
        let mut pos = if let Some(pos) = self.0.rfind('/') {
            pos
        } else {
            return Err(AxError::NotFound);
        };
        if pos == self.0.len() - 1 {
            pos = if let Some(pos) = self.0[..pos].rfind('/') {
                pos
            } else {
                return Err(AxError::NotFound);
            };
        }
        Ok(&self.0[pos + 1..])
    }
    /// 返回是否是根目录
    #[allow(unused)]
    pub fn is_root(&self) -> bool {
        self.0 == "/"
    }
    /// 返回是否是目录
    pub fn is_dir(&self) -> bool {
        self.0.ends_with('/')
    }
    /// 返回是否是文件
    pub fn is_file(&self) -> bool {
        !self.0.ends_with('/')
    }
    /// 判断是否相同
    pub fn equal_to(&self, other: &Self) -> bool {
        self.0 == other.0
    }
    // /// 判断是否实际存在于文件系统(而不是只有链接)
    // pub fn exists(&self) -> bool {
    //     let path = self.0.clone();
    //     path_exists(path.as_str())
    // }
    /// 判断是否start_with
    pub fn start_with(&self, other: &Self) -> bool {
        self.0.starts_with(other.0.as_str())
    }
    /// 判断是否end_with
    #[allow(unused)]
    pub fn end_with(&self, other: &Self) -> bool {
        self.0.ends_with(other.0.as_str())
    }
}
#[allow(unused)]
/// # Safety
///
/// The caller must ensure that the pointer is valid and points to a valid C string.
/// The string must be null-terminated.
pub unsafe fn get_str_len(start: *const u8) -> usize {
    let mut ptr = start as usize;
    while *(ptr as *const u8) != 0 {
        ptr += 1;
    }
    ptr - start as usize
}

#[allow(unused)]
/// # Safety
///
/// The caller must ensure that the pointer is valid and points to a valid C string.
pub unsafe fn raw_ptr_to_ref_str(start: *const u8) -> &'static str {
    let len = unsafe { get_str_len(start) };
    // 因为这里直接用用户空间提供的虚拟地址来访问,所以一定能连续访问到字符串,不需要考虑物理地址是否连续
    let slice = unsafe { core::slice::from_raw_parts(start, len) };
    if let Ok(s) = core::str::from_utf8(slice) {
        s
    } else {
        axlog::error!("not utf8 slice");
        for c in slice {
            axlog::error!("{c} ");
        }
        axlog::error!("");
        ""
    }
}

/// 用户看到的文件到实际文件的映射
pub static LINK_PATH_MAP: Mutex<BTreeMap<String, String>> = Mutex::new(BTreeMap::new());
/// 实际文件(而不是用户文件)到链接数的映射
pub static LINK_COUNT_MAP: Mutex<BTreeMap<String, usize>> = Mutex::new(BTreeMap::new());

/// 将用户提供的路径转换成实际的路径
///
/// 如果在链接列表中找不到,则直接返回自己
pub fn real_path(src_path: &String) -> String {
    trace!("parse_file_name: {}", src_path);
    let map = LINK_PATH_MAP.lock();
    // 找到对应的链接
    match map.get(src_path) {
        Some(dest_path) => dest_path.clone(),
        None => {
            // 特判gcc的文件夹链接情况,即将一个文件夹前缀换成另一个文件夹前缀
            static GCC_DIR_SRC: &str =
                "/riscv64-linux-musl-native/lib/gcc/riscv64-linux-musl/11.2.1/include";
            static GCC_DIR_DST: &str = "/riscv64-linux-musl-native/include";

            static MUSL_DIR_SRC: &str = "/riscv64-linux-musl-native/riscv64-linux-musl/include";
            static MUSL_DIR_DST: &str = "/riscv64-linux-musl-native/include";
            if src_path.starts_with(GCC_DIR_SRC) {
                // 替换src为dst
                GCC_DIR_DST.to_string() + src_path.strip_prefix(GCC_DIR_SRC).unwrap()
            } else if src_path.starts_with(MUSL_DIR_SRC) {
                // 替换src为dst
                MUSL_DIR_DST.to_string() + src_path.strip_prefix(MUSL_DIR_SRC).unwrap()
            } else {
                src_path.clone()
            }
        }
    }
}

/// 删除一个链接
///
/// 如果在 map 中找不到对应链接,则什么都不做
/// 返回被删除的链接指向的文件
///
/// 现在的一个问题是,如果建立了dir1/A,并将dir2/B链接到dir1/A,那么删除dir1/A时,实际的文件不会被删除(连接数依然大于1),只有当删除dir2/B时,实际的文件才会被删除
/// 这样的话,如果新建了dir1/A,那么就会报错(create_new)或者覆盖原文件(create),从而影响到dir2/B
pub fn remove_link(src_path: &FilePath) -> Option<String> {
    trace!("remove_link: {}", src_path.path());
    let mut map = LINK_PATH_MAP.lock();
    // 找到对应的链接
    match map.remove(&src_path.path().to_string()) {
        Some(dest_path) => {
            // 更新链接数
            let mut count_map = LINK_COUNT_MAP.lock();
            let count = count_map.entry(dest_path.clone()).or_insert(0);
            assert!(*count > 0, "before removing, the link count should > 0");
            *count -= 1;
            // 如果链接数为0,那么删除文件
            if *count == 0 {
                debug!("link num down to zero, remove file: {}", dest_path);
                let _ = remove_file(dest_path.as_str());
            }
            Some(dest_path)
        }
        None => None,
    }
}

/// 获取文件的链接数
///
/// 如果文件不存在,那么返回 0
/// 如果文件存在,但是没有链接,那么返回 1
/// 如果文件存在,且有链接,那么返回链接数
pub fn get_link_count(src_path: &String) -> usize {
    trace!("get_link_count: {}", src_path);
    let map = LINK_PATH_MAP.lock();
    // 找到对应的链接
    match map.get(src_path) {
        Some(dest_path) => {
            let count_map = LINK_COUNT_MAP.lock();
            let count = count_map.get(dest_path).unwrap();
            *count
        }
        None => {
            // if path_exists(src_path.path()) {
            //     1
            // } else {
            //     0
            // }
            0
        }
    }
}

/// 创建一个链接
///
/// 返回是否创建成功(已存在的链接也会返回 true)
/// 创建新文件时注意调用该函数创建链接
pub fn create_link(src_path: &FilePath, dest_path: &FilePath) -> bool {
    info!("create_link: {} -> {}", src_path.path(), dest_path.path());
    // assert!(src_path.is_file() && dest_path.is_file(), "link only support file");
    // assert_ne!(src_path.path(), dest_path.path(), "link src and dest should not be the same");  // 否则在第一步删除旧链接时可能会删除源文件
    // 检查是否是文件
    if !src_path.is_file() || !dest_path.is_file() {
        debug!("link only support file");
        return false;
    }
    // 检查被链接到的文件是否存在
    if !path_exists(dest_path.path()) {
        debug!("link dest file not exists");
        return false;
    }

    // 一次性锁定LINK_PATH_MAP,避免重复加锁解锁
    let mut map = LINK_PATH_MAP.lock();

    // 检查链接是否已存在,并处理旧链接
    if let Some(old_dest_path) = map.get(&src_path.path().to_string()) {
        if old_dest_path != &dest_path.path().to_string() {
            // 旧链接存在且与新链接不同,移除旧链接
            drop(map); // 释放锁,因为remove_link可能需要锁
            remove_link(src_path);
            map = LINK_PATH_MAP.lock(); // 重新获取锁
        } else {
            // 链接已存在且相同,无需进一步操作
            debug!("link already exists");
            return true;
        }
    }

    // 创建新链接
    map.insert(
        src_path.path().to_string(),
        dest_path.path().to_string().clone(),
    );

    // 更新链接计数
    let mut count_map = LINK_COUNT_MAP.lock();
    let count = count_map.entry(dest_path.path().to_string()).or_insert(0);
    *count += 1;
    true
}

/// To deal with the path and return the canonicalized path
///
/// * `dir_fd` - The file descriptor of the directory, if it is AT_FDCWD, the call operates on the current working directory
///
/// * `path_addr` - The address of the path, if it is null, the call operates on the file that is specified by `dir_fd`
///
/// * `force_dir` - If true, the path will be treated as a directory
///
/// The path will be dealt with links and the path will be canonicalized
pub fn deal_with_path(
    dir_fd: usize,
    path_addr: Option<*const u8>,
    force_dir: bool,
) -> Option<FilePath> {
    let process = current_process();
    let mut path = "".to_string();
    if let Some(path_addr) = path_addr {
        if path_addr.is_null() {
            axlog::warn!("path address is null");
            return None;
        }
        if process
            .manual_alloc_for_lazy((path_addr as usize).into())
            .is_ok()
        {
            // 直接访问前需要确保已经被分配
            path = unsafe { raw_ptr_to_ref_str(path_addr) }.to_string().clone();
        } else {
            axlog::warn!("path address is invalid");
            return None;
        }
    }

    if path.is_empty() {
        // If pathname is an empty string, in this case, dirfd can refer to any type of file, not just a directory
        // and the behavior of fstatat() is similar to that of fstat()
        // If dirfd is AT_FDCWD, the call operates on the current working directory.
        if dir_fd == AT_FDCWD && dir_fd as u32 == AT_FDCWD as u32 {
            // return Some(FilePath::new(".").unwrap());
            path = String::from(".");
        } else {
            let fd_table = process.fd_manager.fd_table.lock();
            if dir_fd >= fd_table.len() {
                axlog::warn!("fd index out of range");
                return None;
            }
            match fd_table[dir_fd].as_ref() {
                Some(dir) => {
                    let dir = dir.clone();
                    path = dir.get_path();
                }
                None => {
                    axlog::warn!("fd not exist");
                    return None;
                }
            }
        }
    } else if !path.starts_with('/') && dir_fd != AT_FDCWD && dir_fd as u32 != AT_FDCWD as u32 {
        // 如果不是绝对路径, 且dir_fd不是AT_FDCWD, 则需要将dir_fd和path拼接起来
        let fd_table = process.fd_manager.fd_table.lock();
        if dir_fd >= fd_table.len() {
            axlog::warn!("fd index out of range");
            return None;
        }
        match fd_table[dir_fd].as_ref() {
            Some(dir) => {
                if dir.get_type() != FileIOType::DirDesc {
                    axlog::warn!("selected fd {} is not a dir", dir_fd);
                    return None;
                }
                let dir = dir.clone();
                // 有没有可能dir的尾部一定是一个/号,所以不用手工添加/
                path = format!("{}{}", dir.get_path(), path);
                axlog::warn!("handled_path: {}", path);
            }
            None => {
                axlog::warn!("fd not exist");
                return None;
            }
        }
    }
    if force_dir && !path.ends_with('/') {
        path = format!("{}/", path);
    }
    if path.ends_with('.') {
        // 如果path以.或..结尾, 则加上/告诉FilePath::new它是一个目录
        path = format!("{}/", path);
    }
    match FilePath::new(path.as_str()) {
        Ok(path) => Some(path),
        Err(err) => {
            axlog::warn!("error when creating FilePath: {:?}", err);
            None
        }
    }
}