# chapter6 练习

## 编程作业

### 硬链接

硬链接要求两个不同的目录项指向同一个文件，在我们的文件系统中也就是两个不同名称目录项指向同一个磁盘块。

本节要求实现三个系统调用 `linkat`、`unlinkat`、`fstat`。

**linkat**：

- syscall ID: 37
- 功能：创建一个文件的一个硬链接，[linkat 标准接口](https://linux.die.net/man/2/linkat)。

```rust
fn linkat(&self, _caller: Caller, _olddirfd: i32, oldpath: usize, _newdirfd: i32, newpath: usize, _flags: u32) -> isize
```

- 参数：
  - olddirfd, newdirfd: 仅为了兼容性考虑，本次实验中始终为 AT_FDCWD (-100)，可以忽略。
  - flags: 仅为了兼容性考虑，本次实验中始终为 0，可以忽略。
  - oldpath：原有文件路径
  - newpath: 新的链接文件路径。
- 说明：
  - 为了方便，不考虑新文件路径已经存在的情况（属于未定义行为）。除非出现新旧名字一致的情况，此时需要返回 -1。
  - 返回值：如果出现了错误则返回 -1，否则返回 0。
- 可能的错误：
  - 链接同名文件。

**unlinkat**:

- syscall ID: 35
- 功能：取消一个文件路径到文件的链接，[unlinkat 标准接口](https://linux.die.net/man/2/unlinkat)。

```rust
fn unlinkat(&self, _caller: Caller, _dirfd: i32, path: usize, _flags: u32) -> isize
```

- 参数：
  - dirfd: 仅为了兼容性考虑，本次实验中始终为 AT_FDCWD (-100)，可以忽略。
  - flags: 仅为了兼容性考虑，本次实验中始终为 0，可以忽略。
  - path：文件路径。
- 说明：
  - 注意考虑使用 unlink 彻底删除文件的情况，此时需要回收 inode 以及它对应的数据块。
- 返回值：如果出现了错误则返回 -1，否则返回 0。
- 可能的错误：
  - 文件不存在。

**fstat**:

- syscall ID: 80
- 功能：获取文件状态。

```rust
fn fstat(&self, _caller: Caller, fd: usize, st: usize) -> isize
```

- 参数：
  - fd: 文件描述符
  - st: 文件状态结构体指针

```rust
// Stat 结构体和 StatMode 结构体已在 syscall/src/fs.rs 中定义
#[repr(C)]
#[derive(Debug)]
pub struct Stat {
    /// 文件所在磁盘驱动器号，该实验中写死为 0 即可
    pub dev: u64,
    /// inode 文件所在 inode 编号
    pub ino: u64,
    /// 文件类型
    pub mode: StatMode,
    /// 硬链接数量，初始为 1
    pub nlink: u32,
    /// 无需考虑，为了兼容性设计
    pad: [u64; 7],
}

bitflags! {
    pub struct StatMode: u32 {
        const NULL  = 0;
        /// directory
        const DIR   = 0o040000;
        /// ordinary regular file
        const FILE  = 0o100000;
    }
}
```

### HINT

- `linkat` 和 `unlinkat` 的文件路径 path 的读取可参考 `tg-rcore-tutorial-ch6/src/main.rs` 中的 `open` 系统调用实现。
- `fstat` 的文件状态结构体 `Stat` 指针 st 的写入可参考 `tg-rcore-tutorial-ch6/src/main.rs` 中的 `clock_gettime` 系统调用对 `TimeSpec` 的写入实现。

### 实验要求

- 在 tg-rcore-tutorial-ch6 目录下完成实验。
- 目录结构说明：

```
tg-rcore-tutorial-ch6/
├── Cargo.toml（内核配置文件，需要修改依赖配置）
├── src/（内核源代码，需要修改）
│   ├── main.rs（内核主函数，包括系统调用接口实现）
│   ├── fs.rs（文件系统相关）
│   ├── process.rs（进程结构）
│   ├── processor.rs（进程管理器）
│   └── virtio_block.rs（VirtIO 块设备实现）
├── tg-rcore-tutorial-easy-fs/（文件系统实现，需要拉取到本地并修改以支持硬链接）
│   └── src/
│       ├── lib.rs
│       └── ...
└── tg-rcore-tutorial-user/（用户程序，运行时自动拉取，无需修改）
    └── src/bin（测试用例）
```

> **说明**：
> - `tg-rcore-tutorial-user` 会在运行时自动拉取到 `tg-rcore-tutorial-ch6/tg-rcore-tutorial-user` 目录下
> - `tg-rcore-tutorial-easy-fs` 需要拉取到本地才能修改其代码以支持硬链接
>   - 在 tg-rcore-tutorial-ch6 目录下执行 `cargo clone tg-rcore-tutorial-easy-fs` 拉取到本地
>   - 在 tg-rcore-tutorial-ch6/Cargo.toml 中修改 tg-rcore-tutorial-easy-fs 为本地路径：
>     ```toml
>     [dependencies]
>     tg-rcore-tutorial-easy-fs = { path = "./tg-rcore-tutorial-easy-fs" }
>     ```

- 运行练习测例：
```bash
cargo run --features exercise
```
然后在终端中输入 `tg-rcore-tutorial-ch6_usertest` 运行，这个测例打包了所有你需要通过的测例。

- 测试练习测例：
```bash
./test.sh exercise
```

### 说明

- 你的内核必须前向兼容，需要能通过前一章的所有测例
