Explorar el Código

feat:添加symlink系统调用 (#984)

* 添加symlink系统调用

* 修改FATInode的dname的获取逻辑

* 修改fat对Dname的处理,分离dname和inode缓存的key

---------

Co-authored-by: sparkzky <[email protected]>
Co-authored-by: longjin <[email protected]>
sparkzky hace 4 meses
padre
commit
01c18c64b1

+ 28 - 28
kernel/src/filesystem/fat/fs.rs

@@ -2,11 +2,11 @@ use alloc::string::ToString;
 use core::cmp::Ordering;
 use core::intrinsics::unlikely;
 use core::{any::Any, fmt::Debug};
+use hashbrown::HashMap;
 use log::error;
 use system_error::SystemError;
 
 use alloc::{
-    collections::BTreeMap,
     string::String,
     sync::{Arc, Weak},
     vec::Vec,
@@ -36,6 +36,7 @@ use crate::{
 };
 
 use super::entry::FATFile;
+use super::utils::{to_search_name, to_search_name_string};
 use super::{
     bpb::{BiosParameterBlock, FATType},
     entry::{FATDir, FATDirEntry, FATDirIter, FATEntry},
@@ -106,9 +107,9 @@ pub struct FATInode {
     parent: Weak<LockedFATInode>,
     /// 指向自身的弱引用
     self_ref: Weak<LockedFATInode>,
-    /// 子Inode的B树. 该数据结构用作缓存区。其中,它的key表示inode的名称。
+    /// 子Inode的map. 该数据结构用作缓存区。其中,它的key表示inode的名称。
     /// 请注意,由于FAT的查询过程对大小写不敏感,因此我们选择让key全部是大写的,方便统一操作。
-    children: BTreeMap<DName, Arc<LockedFATInode>>,
+    children: HashMap<String, Arc<LockedFATInode>>,
     /// 当前inode的元数据
     metadata: Metadata,
     /// 指向inode所在的文件系统对象的指针
@@ -148,24 +149,25 @@ impl FATInode {
     fn find(&mut self, name: &str) -> Result<Arc<LockedFATInode>, SystemError> {
         match &self.inode_type {
             FATDirEntry::Dir(d) => {
-                let dname = DName::from(name.to_uppercase());
+                let search_name = to_search_name(name);
                 // 尝试在缓存区查找
-                if let Some(entry) = self.children.get(&dname) {
+                if let Some(entry) = self.children.get(&search_name) {
                     return Ok(entry.clone());
                 }
                 // 在缓存区找不到
                 // 在磁盘查找
                 let fat_entry: FATDirEntry =
                     d.find_entry(name, None, None, self.fs.upgrade().unwrap())?;
+                let dname = DName::from(name);
                 // 创建新的inode
                 let entry_inode: Arc<LockedFATInode> = LockedFATInode::new(
-                    dname.clone(),
+                    dname,
                     self.fs.upgrade().unwrap(),
                     self.self_ref.clone(),
                     fat_entry,
                 );
                 // 加入缓存区, 由于FAT文件系统的大小写不敏感问题,因此存入缓存区的key应当是全大写的
-                self.children.insert(dname, entry_inode.clone());
+                self.children.insert(search_name, entry_inode.clone());
                 return Ok(entry_inode);
             }
             FATDirEntry::UnInit => {
@@ -197,7 +199,7 @@ impl LockedFATInode {
         let inode: Arc<LockedFATInode> = Arc::new(LockedFATInode(SpinLock::new(FATInode {
             parent,
             self_ref: Weak::default(),
-            children: BTreeMap::new(),
+            children: HashMap::new(),
             fs: Arc::downgrade(&fs),
             inode_type,
             metadata: Metadata {
@@ -348,7 +350,7 @@ impl FATFileSystem {
         let root_inode: Arc<LockedFATInode> = Arc::new(LockedFATInode(SpinLock::new(FATInode {
             parent: Weak::default(),
             self_ref: Weak::default(),
-            children: BTreeMap::new(),
+            children: HashMap::new(),
             fs: Weak::default(),
             inode_type: FATDirEntry::UnInit,
             metadata: Metadata {
@@ -1559,14 +1561,15 @@ impl IndexNode for LockedFATInode {
                 for ent in dir_iter {
                     ret.push(ent.name());
 
-                    // ====== 生成inode缓存,存入B树
-                    let name = DName::from(ent.name().to_uppercase());
+                    // ====== 生成inode缓存
+                    let search_name = to_search_name_string(ent.name());
                     // debug!("name={name}");
 
-                    if !guard.children.contains_key(&name)
-                        && name.as_ref() != "."
-                        && name.as_ref() != ".."
+                    if !guard.children.contains_key(&search_name)
+                        && search_name != "."
+                        && search_name != ".."
                     {
+                        let name = DName::from(ent.name());
                         // 创建新的inode
                         let entry_inode: Arc<LockedFATInode> = LockedFATInode::new(
                             name.clone(),
@@ -1575,7 +1578,7 @@ impl IndexNode for LockedFATInode {
                             ent,
                         );
                         // 加入缓存区, 由于FAT文件系统的大小写不敏感问题,因此存入缓存区的key应当是全大写的
-                        guard.children.insert(name, entry_inode.clone());
+                        guard.children.insert(search_name, entry_inode.clone());
                     }
                 }
                 return Ok(ret);
@@ -1611,7 +1614,7 @@ impl IndexNode for LockedFATInode {
         // 对目标inode上锁,以防更改
         let target_guard: SpinLockGuard<FATInode> = target.0.lock();
         // 先从缓存删除
-        let nod = guard.children.remove(&DName::from(name.to_uppercase()));
+        let nod = guard.children.remove(&to_search_name(name));
 
         // 若删除缓存中为管道的文件,则不需要再到磁盘删除
         if nod.is_some() {
@@ -1646,7 +1649,7 @@ impl IndexNode for LockedFATInode {
         // 对目标inode上锁,以防更改
         let target_guard: SpinLockGuard<FATInode> = target.0.lock();
         // 先从缓存删除
-        guard.children.remove(&DName::from(name.to_uppercase()));
+        guard.children.remove(&to_search_name(name));
 
         let dir = match &guard.inode_type {
             FATDirEntry::File(_) | FATDirEntry::VolId(_) => {
@@ -1669,9 +1672,7 @@ impl IndexNode for LockedFATInode {
             Err(r) => {
                 if r == SystemError::ENOTEMPTY {
                     // 如果要删除的是目录,且不为空,则删除动作未发生,重新加入缓存
-                    guard
-                        .children
-                        .insert(DName::from(name.to_uppercase()), target.clone());
+                    guard.children.insert(to_search_name(name), target.clone());
                     drop(target_guard);
                 }
                 return Err(r);
@@ -1695,7 +1696,6 @@ impl IndexNode for LockedFATInode {
             let old_inode_guard: SpinLockGuard<FATInode> = old_inode.0.lock();
             let fs = old_inode_guard.fs.upgrade().unwrap();
             // 从缓存删除
-            let _nod = guard.children.remove(&DName::from(old_name.to_uppercase()));
             let old_dir = match &guard.inode_type {
                 FATDirEntry::File(_) | FATDirEntry::VolId(_) => {
                     return Err(SystemError::ENOTDIR);
@@ -1710,6 +1710,7 @@ impl IndexNode for LockedFATInode {
             // old_dir.check_existence(old_name, Some(false), guard.fs.upgrade().unwrap())?;
 
             old_dir.rename(fs, old_name, new_name)?;
+            let _nod = guard.children.remove(&to_search_name(old_name));
         } else {
             let mut old_guard = self.0.lock();
             let other: &LockedFATInode = target
@@ -1721,10 +1722,7 @@ impl IndexNode for LockedFATInode {
             // 对目标inode上锁,以防更改
             let old_inode_guard: SpinLockGuard<FATInode> = old_inode.0.lock();
             let fs = old_inode_guard.fs.upgrade().unwrap();
-            // 从缓存删除
-            let _nod = old_guard
-                .children
-                .remove(&DName::from(old_name.to_uppercase()));
+
             let old_dir = match &old_guard.inode_type {
                 FATDirEntry::File(_) | FATDirEntry::VolId(_) => {
                     return Err(SystemError::ENOTDIR);
@@ -1748,6 +1746,8 @@ impl IndexNode for LockedFATInode {
             // 检查文件是否存在
             old_dir.check_existence(old_name, Some(false), old_guard.fs.upgrade().unwrap())?;
             old_dir.rename_across(fs, new_dir, old_name, new_name)?;
+            // 从缓存删除
+            let _nod = old_guard.children.remove(&to_search_name(old_name));
         }
 
         return Ok(());
@@ -1806,9 +1806,9 @@ impl IndexNode for LockedFATInode {
             return self.create(filename, FileType::File, mode);
         }
 
-        let filename = DName::from(filename.to_uppercase());
+        let dname = DName::from(filename);
         let nod = LockedFATInode::new(
-            filename.clone(),
+            dname,
             inode.fs.upgrade().unwrap(),
             inode.self_ref.clone(),
             FATDirEntry::File(FATFile::default()),
@@ -1830,7 +1830,7 @@ impl IndexNode for LockedFATInode {
             return Err(SystemError::EINVAL);
         }
 
-        inode.children.insert(filename, nod.clone());
+        inode.children.insert(to_search_name(filename), nod.clone());
         Ok(nod)
     }
 

+ 15 - 1
kernel/src/filesystem/fat/utils.rs

@@ -1,3 +1,4 @@
+use alloc::string::String;
 use core::char::REPLACEMENT_CHARACTER;
 
 /// FAT文件系统保留开头的2个簇
@@ -5,7 +6,7 @@ pub const RESERVED_CLUSTERS: u32 = 2;
 
 /// @brief 将u8转为ascii字符。
 /// 当转码成功时,返回对应的ascii字符,否则返回Unicode占位符
-pub fn decode_u8_ascii(value: u8) -> char {
+pub(super) fn decode_u8_ascii(value: u8) -> char {
     if value <= 0x7f {
         return value as char;
     } else {
@@ -13,3 +14,16 @@ pub fn decode_u8_ascii(value: u8) -> char {
         return REPLACEMENT_CHARACTER;
     }
 }
+
+/// 把名称转为inode缓存里面的key
+#[inline(always)]
+pub(super) fn to_search_name(name: &str) -> String {
+    name.to_ascii_uppercase()
+}
+
+/// 把名称转为inode缓存里面的key(输入为string,原地替换)
+#[inline(always)]
+pub(super) fn to_search_name_string(mut name: String) -> String {
+    name.make_ascii_uppercase();
+    name
+}

+ 48 - 2
kernel/src/filesystem/vfs/core.rs

@@ -12,9 +12,13 @@ use crate::{
         procfs::procfs_init,
         ramfs::RamFS,
         sysfs::sysfs_init,
-        vfs::{mount::MountFS, syscall::ModeType, AtomicInodeId, FileSystem, FileType},
+        vfs::{
+            mount::MountFS, syscall::ModeType, AtomicInodeId, FileSystem, FileType, MAX_PATHLEN,
+        },
     },
+    libs::spinlock::SpinLock,
     process::ProcessManager,
+    syscall::user_access::check_and_clone_cstr,
 };
 
 use super::{
@@ -23,7 +27,7 @@ use super::{
     mount::{init_mountlist, MOUNT_LIST},
     syscall::UmountFlag,
     utils::{rsplit_path, user_path_at},
-    IndexNode, InodeId, VFS_MAX_FOLLOW_SYMLINK_TIMES,
+    FilePrivateData, IndexNode, InodeId, VFS_MAX_FOLLOW_SYMLINK_TIMES,
 };
 
 /// 当没有指定根文件系统时,尝试的根文件系统列表
@@ -248,6 +252,48 @@ pub fn do_unlink_at(dirfd: i32, path: &str) -> Result<u64, SystemError> {
     return Ok(0);
 }
 
+pub fn do_symlinkat(from: *const u8, newdfd: i32, to: *const u8) -> Result<usize, SystemError> {
+    let oldname = check_and_clone_cstr(from, Some(MAX_PATHLEN))?
+        .into_string()
+        .map_err(|_| SystemError::EINVAL)?;
+    let newname = check_and_clone_cstr(to, Some(MAX_PATHLEN))?
+        .into_string()
+        .map_err(|_| SystemError::EINVAL)?;
+    let from = oldname.as_str().trim();
+    let to = newname.as_str().trim();
+
+    // TODO: 添加权限检查,确保进程拥有目标路径的权限
+
+    let pcb = ProcessManager::current_pcb();
+    let (old_begin_inode, old_remain_path) = user_path_at(&pcb, AtFlags::AT_FDCWD.bits(), from)?;
+    // info!("old_begin_inode={:?}", old_begin_inode.metadata());
+    let _ =
+        old_begin_inode.lookup_follow_symlink(&old_remain_path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
+
+    // 得到新创建节点的父节点
+    let (new_begin_inode, new_remain_path) = user_path_at(&pcb, newdfd, to)?;
+    let (new_name, new_parent_path) = rsplit_path(&new_remain_path);
+    let new_parent = new_begin_inode
+        .lookup_follow_symlink(new_parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
+    // info!("new_parent={:?}", new_parent.metadata());
+
+    if new_parent.metadata()?.file_type != FileType::Dir {
+        return Err(SystemError::ENOTDIR);
+    }
+
+    let new_inode = new_parent.create_with_data(
+        new_name,
+        FileType::SymLink,
+        ModeType::from_bits_truncate(0o777),
+        0,
+    )?;
+
+    let buf = old_remain_path.as_bytes();
+    let len = buf.len();
+    new_inode.write_at(0, len, buf, SpinLock::new(FilePrivateData::Unused).lock())?;
+    return Ok(0);
+}
+
 /// # do_mount - 挂载文件系统
 ///
 /// 将给定的文件系统挂载到指定的挂载点。

+ 24 - 6
kernel/src/filesystem/vfs/mount.rs

@@ -8,6 +8,7 @@ use alloc::{
     collections::BTreeMap,
     string::{String, ToString},
     sync::{Arc, Weak},
+    vec::Vec,
 };
 use system_error::SystemError;
 
@@ -215,12 +216,29 @@ impl MountFSInode {
             .ok_or(SystemError::ENOENT);
     }
 
-    fn do_absolute_path(&self, len: usize) -> Result<String, SystemError> {
-        if self.metadata()?.inode_id == ROOT_INODE().metadata()?.inode_id {
-            return Ok(String::with_capacity(len));
+    fn do_absolute_path(&self) -> Result<String, SystemError> {
+        let mut path_parts = Vec::new();
+        let mut current = self.self_ref.upgrade().unwrap();
+
+        while current.metadata()?.inode_id != ROOT_INODE().metadata()?.inode_id {
+            let name = current.dname()?;
+            path_parts.push(name.0);
+            current = current.do_parent()?;
         }
-        let name = self.dname()?;
-        return Ok(self.do_parent()?.do_absolute_path(len + name.0.len() + 1)? + "/" + &name.0);
+
+        // 由于我们从叶子节点向上遍历到根节点,所以需要反转路径部分
+        path_parts.reverse();
+
+        // 构建最终的绝对路径字符串
+        let mut absolute_path = String::with_capacity(
+            path_parts.iter().map(|s| s.len()).sum::<usize>() + path_parts.len(),
+        );
+        for part in path_parts {
+            absolute_path.push('/');
+            absolute_path.push_str(&part);
+        }
+
+        Ok(absolute_path)
     }
 }
 
@@ -469,7 +487,7 @@ impl IndexNode for MountFSInode {
     }
 
     fn absolute_path(&self) -> Result<String, SystemError> {
-        self.do_absolute_path(0)
+        self.do_absolute_path()
     }
 
     #[inline]

+ 13 - 0
kernel/src/filesystem/vfs/syscall.rs

@@ -20,6 +20,7 @@ use crate::{
     time::{syscall::PosixTimeval, PosixTimeSpec},
 };
 
+use super::core::do_symlinkat;
 use super::{
     core::{do_mkdir_at, do_remove_dir, do_unlink_at},
     fcntl::{AtFlags, FcntlCommand, FD_CLOEXEC},
@@ -980,6 +981,18 @@ impl Syscall {
         return do_unlink_at(AtFlags::AT_FDCWD.bits(), &path).map(|v| v as usize);
     }
 
+    pub fn symlink(oldname: *const u8, newname: *const u8) -> Result<usize, SystemError> {
+        return do_symlinkat(oldname, AtFlags::AT_FDCWD.bits(), newname);
+    }
+
+    pub fn symlinkat(
+        oldname: *const u8,
+        newdfd: i32,
+        newname: *const u8,
+    ) -> Result<usize, SystemError> {
+        return do_symlinkat(oldname, newdfd, newname);
+    }
+
     /// # 修改文件名
     ///
     ///

+ 14 - 0
kernel/src/syscall/mod.rs

@@ -346,6 +346,20 @@ impl Syscall {
                 Self::unlinkat(dirfd, path, flags)
             }
 
+            #[cfg(target_arch = "x86_64")]
+            SYS_SYMLINK => {
+                let oldname = args[0] as *const u8;
+                let newname = args[1] as *const u8;
+                Self::symlink(oldname, newname)
+            }
+
+            SYS_SYMLINKAT => {
+                let oldname = args[0] as *const u8;
+                let newdfd = args[1] as i32;
+                let newname = args[2] as *const u8;
+                Self::symlinkat(oldname, newdfd, newname)
+            }
+
             #[cfg(target_arch = "x86_64")]
             SYS_RMDIR => {
                 let path = args[0] as *const u8;

+ 2 - 1
user/apps/test-mount/Cargo.toml

@@ -8,4 +8,5 @@ authors = [ "xiaolin2004 <[email protected]>" ]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-libc="0.2"
+errno = "0.3.9"
+libc="0.2"

+ 4 - 1
user/apps/test-mount/src/main.rs

@@ -1,8 +1,10 @@
 use core::ffi::{c_char, c_void};
+use errno::errno;
 use libc::{mount, MS_BIND};
 use std::fs;
 use std::path::Path;
 use std::time;
+
 fn main() {
     let path = Path::new("mnt/tmp");
     let dir = fs::create_dir_all(path);
@@ -26,7 +28,8 @@ fn main() {
     if result == 0 {
         println!("Mount successful");
     } else {
-        println!("Mount failed");
+        let err = errno();
+        println!("Mount failed with error code: {}", err.0);
     }
     let dur = clock.elapsed();
     println!("mount costing time: {} ns", dur.as_nanos());

+ 3 - 0
user/apps/test-symlink/.gitignore

@@ -0,0 +1,3 @@
+/target
+Cargo.lock
+/install/

+ 13 - 0
user/apps/test-symlink/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "test-symlink"
+version = "0.1.0"
+edition = "2021"
+description = "测试symlink系统调用"
+authors = [ "sparkzky <[email protected]>" ]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+errno = "0.3.9"
+libc="0.2"
+nix = "0.23"

+ 56 - 0
user/apps/test-symlink/Makefile

@@ -0,0 +1,56 @@
+TOOLCHAIN=
+RUSTFLAGS=
+
+ifdef DADK_CURRENT_BUILD_DIR
+# 如果是在dadk中编译,那么安装到dadk的安装目录中
+	INSTALL_DIR = $(DADK_CURRENT_BUILD_DIR)
+else
+# 如果是在本地编译,那么安装到当前目录下的install目录中
+	INSTALL_DIR = ./install
+endif
+
+ifeq ($(ARCH), x86_64)
+	export RUST_TARGET=x86_64-unknown-linux-musl
+else ifeq ($(ARCH), riscv64)
+	export RUST_TARGET=riscv64gc-unknown-linux-gnu
+else 
+# 默认为x86_86,用于本地编译
+	export RUST_TARGET=x86_64-unknown-linux-musl
+endif
+
+run:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET)
+
+build:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET)
+
+clean:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET)
+
+test:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET)
+
+doc:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) doc --target $(RUST_TARGET)
+
+fmt:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt
+
+fmt-check:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt --check
+
+run-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET) --release
+
+build-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) --release
+
+clean-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET) --release
+
+test-release:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET) --release
+
+.PHONY: install
+install:
+	RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) install --target $(RUST_TARGET) --path . --no-track --root $(INSTALL_DIR) --force

+ 1 - 0
user/apps/test-symlink/README.md

@@ -0,0 +1 @@
+# 测试Symlink系统调用的程序

+ 78 - 0
user/apps/test-symlink/src/main.rs

@@ -0,0 +1,78 @@
+extern crate libc;
+use core::ffi::{c_char, c_void};
+use libc::{mount, umount};
+use nix::errno::Errno;
+use std::fs;
+use std::os::unix::fs::symlink;
+use std::path::Path;
+
+fn main() {
+    mount_test_ramfs();
+
+    let target = "/mnt/myramfs/target_file.txt";
+    let symlink_path = "/mnt/myramfs/another/symlink_file.txt";
+    let dir = "/mnt/myramfs/another";
+
+    fs::write(target, "This is the content of the target file.")
+        .expect("Failed to create target file");
+    fs::create_dir(dir).expect("Failed to create target dir");
+
+    assert!(Path::new(target).exists(), "Target file was not created");
+    assert!(Path::new(dir).exists(), "Target dir was not created");
+
+    symlink(target, symlink_path).expect("Failed to create symlink");
+
+    assert!(Path::new(symlink_path).exists(), "Symlink was not created");
+
+    let symlink_content = fs::read_link(symlink_path).expect("Failed to read symlink");
+    assert_eq!(
+        symlink_content.display().to_string(),
+        target,
+        "Symlink points to the wrong target"
+    );
+
+    fs::remove_file(symlink_path).expect("Failed to remove symlink");
+    fs::remove_file(target).expect("Failed to remove target file");
+    fs::remove_dir(dir).expect("Failed to remove test_dir");
+
+    assert!(!Path::new(symlink_path).exists(), "Symlink was not deleted");
+    assert!(!Path::new(target).exists(), "Target file was not deleted");
+    assert!(!Path::new(dir).exists(), "Directory was not deleted");
+
+    umount_test_ramfs();
+
+    println!("All tests passed!");
+}
+
+fn mount_test_ramfs() {
+    let path = Path::new("mnt/myramfs");
+    let dir = fs::create_dir_all(path);
+    assert!(dir.is_ok(), "mkdir /mnt/myramfs failed");
+
+    let source = b"\0".as_ptr() as *const c_char;
+    let target = b"/mnt/myramfs\0".as_ptr() as *const c_char;
+    let fstype = b"ramfs\0".as_ptr() as *const c_char;
+    // let flags = MS_BIND;
+    let flags = 0;
+    let data = std::ptr::null() as *const c_void;
+    let result = unsafe { mount(source, target, fstype, flags, data) };
+
+    assert_eq!(
+        result,
+        0,
+        "Mount myramfs failed, errno: {}",
+        Errno::last().desc()
+    );
+    println!("Mount myramfs success!");
+}
+
+fn umount_test_ramfs() {
+    let path = b"/mnt/myramfs\0".as_ptr() as *const c_char;
+    let result = unsafe { umount(path) };
+    if result != 0 {
+        let err = Errno::last();
+        println!("Errno: {}", err);
+        println!("Infomation: {}", err.desc());
+    }
+    assert_eq!(result, 0, "Umount myramfs failed");
+}

+ 29 - 0
user/dadk/config/test_symlink_0_1_0.dadk

@@ -0,0 +1,29 @@
+{
+  "name": "test-symlink",
+  "version": "0.1.0",
+  "description": "测试symlink系统调用",
+  "rust_target": "x86_64-unknown-dragonos",
+  "task_type": {
+    "BuildFromSource": {
+      "Local": {
+        "path": "apps/test-symlink"
+      }
+    }
+  },
+  "depends": [],
+  "build": {
+    "build_command": "make install"
+  },
+  "install": {
+    "in_dragonos_path": "/"
+  },
+  "clean": {
+    "clean_command": "make clean"
+  },
+  "envs": [],
+  "build_once": false,
+  "install_once": false,
+  "target_arch": [
+    "x86_64"
+  ]
+}