ソースを参照

feat:添加chown系统调用 (#962)

* 添加chown系统调用

---------

Co-authored-by: sparkzky <[email protected]>
Co-authored-by: longjin <[email protected]>
火花 4 ヶ月 前
コミット
55e6f0b65f

+ 48 - 14
kernel/src/filesystem/vfs/mod.rs

@@ -598,17 +598,43 @@ impl dyn IndexNode {
         return self.lookup_follow_symlink(path, 0);
     }
 
-    /// @brief 查找文件(考虑符号链接)
+    pub fn lookup_follow_symlink(
+        &self,
+        path: &str,
+        max_follow_times: usize,
+    ) -> Result<Arc<dyn IndexNode>, SystemError> {
+        return self.do_lookup_follow_symlink(path, max_follow_times, true);
+    }
+
+    pub fn lookup_follow_symlink2(
+        &self,
+        path: &str,
+        max_follow_times: usize,
+        follow_final_symlink: bool,
+    ) -> Result<Arc<dyn IndexNode>, SystemError> {
+        return self.do_lookup_follow_symlink(path, max_follow_times, follow_final_symlink);
+    }
+
+    /// # 查找文件
+    /// 查找指定路径的文件,考虑符号链接的存在,并可选择是否返回最终路径的符号链接文件本身。
     ///
-    /// @param path 文件路径
-    /// @param max_follow_times 最大经过的符号链接的大小
+    /// ## 参数
+    /// - `path`: 文件路径
+    /// - `max_follow_times`: 最大经过的符号链接的数量
+    /// - `follow_final_symlink`: 是否跟随最后的符号链接
     ///
-    /// @return Ok(Arc<dyn IndexNode>) 要寻找的目录项的inode
-    /// @return Err(SystemError) 错误码
-    pub fn lookup_follow_symlink(
+    /// ## 返回值
+    /// - `Ok(Arc<dyn IndexNode>)`: 要寻找的目录项的inode
+    /// - `Err(SystemError)`: 错误码,表示查找过程中遇到的错误
+    ///
+    /// ## Safety
+    /// 此函数在处理符号链接时可能会遇到循环引用的情况,`max_follow_times` 参数用于限制符号链接的跟随次数以避免无限循环。
+    #[inline(never)]
+    pub fn do_lookup_follow_symlink(
         &self,
         path: &str,
         max_follow_times: usize,
+        follow_final_symlink: bool,
     ) -> Result<Arc<dyn IndexNode>, SystemError> {
         if self.metadata()?.file_type != FileType::Dir {
             return Err(SystemError::ENOTDIR);
@@ -632,13 +658,10 @@ impl dyn IndexNode {
             }
 
             let name;
-
             // 寻找“/”
             match rest_path.find('/') {
                 Some(pos) => {
-                    // 找到了,设置下一个要查找的名字
                     name = String::from(&rest_path[0..pos]);
-                    // 剩余的路径字符串
                     rest_path = String::from(&rest_path[pos + 1..]);
                 }
                 None => {
@@ -653,11 +676,18 @@ impl dyn IndexNode {
             }
 
             let inode = result.find(&name)?;
+            let file_type = inode.metadata()?.file_type;
+            // 如果已经是路径的最后一个部分,并且不希望跟随最后的符号链接
+            if rest_path.is_empty() && !follow_final_symlink && file_type == FileType::SymLink {
+                // 返回符号链接本身
+                return Ok(inode);
+            }
 
-            // 处理符号链接的问题
-            if inode.metadata()?.file_type == FileType::SymLink && max_follow_times > 0 {
+            // 跟随符号链接跳转
+            if file_type == FileType::SymLink && max_follow_times > 0 {
                 let mut content = [0u8; 256];
                 // 读取符号链接
+
                 let len = inode.read_at(
                     0,
                     256,
@@ -667,12 +697,16 @@ impl dyn IndexNode {
 
                 // 将读到的数据转换为utf8字符串(先转为str,再转为String)
                 let link_path = String::from(
-                    ::core::str::from_utf8(&content[..len]).map_err(|_| SystemError::ENOTDIR)?,
+                    ::core::str::from_utf8(&content[..len]).map_err(|_| SystemError::EINVAL)?,
                 );
-
                 let new_path = link_path + "/" + &rest_path;
+
                 // 继续查找符号链接
-                return result.lookup_follow_symlink(&new_path, max_follow_times - 1);
+                return result.lookup_follow_symlink2(
+                    &new_path,
+                    max_follow_times - 1,
+                    follow_final_symlink,
+                );
             } else {
                 result = inode;
             }

+ 87 - 2
kernel/src/filesystem/vfs/open.rs

@@ -9,12 +9,15 @@ use super::{
     utils::{rsplit_path, user_path_at},
     FileType, IndexNode, MAX_PATHLEN, ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES,
 };
-use crate::filesystem::vfs::syscall::UtimensFlags;
-use crate::time::{syscall::PosixTimeval, PosixTimeSpec};
 use crate::{
     driver::base::block::SeekFrom, process::ProcessManager,
     syscall::user_access::check_and_clone_cstr,
 };
+use crate::{filesystem::vfs::syscall::UtimensFlags, process::cred::Kgid};
+use crate::{
+    process::cred::GroupInfo,
+    time::{syscall::PosixTimeval, PosixTimeSpec},
+};
 use alloc::string::String;
 
 pub(super) fn do_faccessat(
@@ -64,6 +67,88 @@ pub fn do_fchmodat(dirfd: i32, path: *const u8, _mode: ModeType) -> Result<usize
     return Ok(0);
 }
 
+pub fn do_fchownat(
+    dirfd: i32,
+    path: &str,
+    uid: usize,
+    gid: usize,
+    flag: AtFlags,
+) -> Result<usize, SystemError> {
+    // 检查flag是否合法
+    if flag.contains(!(AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH)) {
+        return Err(SystemError::EINVAL);
+    }
+
+    let follow_symlink = flag.contains(!AtFlags::AT_SYMLINK_NOFOLLOW);
+    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
+
+    // 如果找不到文件,则返回错误码ENOENT
+    let inode = if follow_symlink {
+        inode.lookup_follow_symlink2(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES, false)
+    } else {
+        inode.lookup(path.as_str())
+    };
+
+    if inode.is_err() {
+        let errno = inode.clone().unwrap_err();
+        // 文件不存在
+        if errno == SystemError::ENOENT {
+            return Err(SystemError::ENOENT);
+        }
+    }
+
+    let inode = inode.unwrap();
+
+    return chown_common(inode, uid, gid);
+}
+
+fn chown_common(inode: Arc<dyn IndexNode>, uid: usize, gid: usize) -> Result<usize, SystemError> {
+    let mut meta = inode.metadata()?;
+    let cred = ProcessManager::current_pcb().cred();
+    let current_uid = cred.uid.data();
+    let current_gid = cred.gid.data();
+    let mut group_info = GroupInfo::default();
+    if let Some(info) = cred.group_info.as_ref() {
+        group_info = info.clone();
+    }
+
+    // 检查权限
+    match current_uid {
+        0 => {
+            meta.uid = uid;
+            meta.gid = gid;
+        }
+        _ => {
+            // 非文件所有者不能更改信息,且不能更改uid
+            if current_uid != meta.uid || uid != meta.uid {
+                return Err(SystemError::EPERM);
+            }
+            if gid != current_gid && !group_info.gids.contains(&Kgid::from(gid)) {
+                return Err(SystemError::EPERM);
+            }
+            meta.gid = gid;
+        }
+    }
+
+    meta.mode.remove(ModeType::S_ISUID | ModeType::S_ISGID);
+    inode.set_metadata(&meta)?;
+
+    return Ok(0);
+}
+
+pub fn ksys_fchown(fd: i32, uid: usize, gid: usize) -> Result<usize, SystemError> {
+    let fd_table = &ProcessManager::current_pcb().fd_table();
+    let fd_table = fd_table.read();
+
+    let inode = fd_table.get_file_by_fd(fd).unwrap().inode();
+
+    let result = chown_common(inode, uid, gid);
+
+    drop(fd_table);
+
+    return result;
+}
+
 pub(super) fn do_sys_open(
     dfd: i32,
     path: &str,

+ 49 - 1
kernel/src/filesystem/vfs/syscall.rs

@@ -25,7 +25,9 @@ use super::{
     core::{do_mkdir_at, do_remove_dir, do_unlink_at},
     fcntl::{AtFlags, FcntlCommand, FD_CLOEXEC},
     file::{File, FileMode},
-    open::{do_faccessat, do_fchmodat, do_sys_open, do_utimensat, do_utimes},
+    open::{
+        do_faccessat, do_fchmodat, do_fchownat, do_sys_open, do_utimensat, do_utimes, ksys_fchown,
+    },
     utils::{rsplit_path, user_path_at},
     Dirent, FileType, IndexNode, SuperBlock, FSMAKER, MAX_PATHLEN, ROOT_INODE,
     VFS_MAX_FOLLOW_SYMLINK_TIMES,
@@ -1638,6 +1640,52 @@ impl Syscall {
         warn!("fchmod not fully implemented");
         return Ok(0);
     }
+
+    pub fn chown(pathname: *const u8, uid: usize, gid: usize) -> Result<usize, SystemError> {
+        let pathname = user_access::check_and_clone_cstr(pathname, Some(MAX_PATHLEN))?
+            .into_string()
+            .map_err(|_| SystemError::EINVAL)?;
+        return do_fchownat(
+            AtFlags::AT_FDCWD.bits(),
+            &pathname,
+            uid,
+            gid,
+            AtFlags::AT_STATX_SYNC_AS_STAT,
+        );
+    }
+
+    pub fn lchown(pathname: *const u8, uid: usize, gid: usize) -> Result<usize, SystemError> {
+        let pathname = user_access::check_and_clone_cstr(pathname, Some(MAX_PATHLEN))?
+            .into_string()
+            .map_err(|_| SystemError::EINVAL)?;
+        return do_fchownat(
+            AtFlags::AT_FDCWD.bits(),
+            &pathname,
+            uid,
+            gid,
+            AtFlags::AT_SYMLINK_NOFOLLOW,
+        );
+    }
+
+    pub fn fchownat(
+        dirfd: i32,
+        pathname: *const u8,
+        uid: usize,
+        gid: usize,
+        flags: i32,
+    ) -> Result<usize, SystemError> {
+        let pathname = user_access::check_and_clone_cstr(pathname, Some(MAX_PATHLEN))?
+            .into_string()
+            .map_err(|_| SystemError::EINVAL)?;
+        let pathname = pathname.as_str().trim();
+        let flags = AtFlags::from_bits_truncate(flags);
+        return do_fchownat(dirfd, pathname, uid, gid, flags);
+    }
+
+    pub fn fchown(fd: i32, uid: usize, gid: usize) -> Result<usize, SystemError> {
+        return ksys_fchown(fd, uid, gid);
+    }
+
     /// #挂载文件系统
     ///
     /// 用于挂载文件系统,目前仅支持ramfs挂载

+ 1 - 1
kernel/src/process/cred.rs

@@ -164,7 +164,7 @@ impl Cred {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
 pub struct GroupInfo {
     pub gids: Vec<Kgid>,
 }

+ 26 - 2
kernel/src/syscall/mod.rs

@@ -997,8 +997,32 @@ impl Syscall {
             }
 
             SYS_FCHOWN => {
-                warn!("SYS_FCHOWN has not yet been implemented");
-                Ok(0)
+                let dirfd = args[0] as i32;
+                let uid = args[1];
+                let gid = args[2];
+                Self::fchown(dirfd, uid, gid)
+            }
+            #[cfg(target_arch = "x86_64")]
+            SYS_CHOWN => {
+                let pathname = args[0] as *const u8;
+                let uid = args[1];
+                let gid = args[2];
+                Self::chown(pathname, uid, gid)
+            }
+            #[cfg(target_arch = "x86_64")]
+            SYS_LCHOWN => {
+                let pathname = args[0] as *const u8;
+                let uid = args[1];
+                let gid = args[2];
+                Self::lchown(pathname, uid, gid)
+            }
+            SYS_FCHOWNAT => {
+                let dirfd = args[0] as i32;
+                let pathname = args[1] as *const u8;
+                let uid = args[2];
+                let gid = args[3];
+                let flag = args[4] as i32;
+                Self::fchownat(dirfd, pathname, uid, gid, flag)
             }
 
             SYS_FSYNC => {

+ 4 - 0
user/apps/test-chown/.gitignore

@@ -0,0 +1,4 @@
+/target
+Cargo.lock
+testfile.txt
+/install/

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

@@ -0,0 +1,13 @@
+[package]
+name = "test-chown"
+version = "0.1.0"
+edition = "2021"
+description = "测试chown系列系统调用"
+authors = [ "sparkzky <[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"
+nix = "0.23"

+ 56 - 0
user/apps/test-chown/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

+ 8 - 0
user/apps/test-chown/README.md

@@ -0,0 +1,8 @@
+# 一个简单的用于测试chown系列系统调用的程序
+
+### 由于symlink系统调用还未实现,目前只测试chown和fchown
+
+### 测试前需要手动添加nogroup用户组和nobody用户(程序里加不了)
+```groupadd -g 65534 nogroup
+useradd -d /nonexistent -g 65534 -u 65534 -s /usr/local/bin/false nobody
+```

+ 160 - 0
user/apps/test-chown/src/main.rs

@@ -0,0 +1,160 @@
+use core::ffi::{c_char, c_void};
+use libc::{
+    chown, fchown, fchownat, getgrnam, getpwnam, gid_t, lchown, mount, uid_t, umount, AT_FDCWD,
+    AT_SYMLINK_NOFOLLOW,
+};
+use nix::errno::Errno;
+use std::{
+    ffi::CString,
+    fs::{self, metadata, File},
+    io::{self, Error, Write},
+    os::unix::{
+        fs::{MetadataExt, PermissionsExt},
+        io::AsRawFd,
+    },
+    path::Path,
+};
+
+fn print_file_owner_group(filename: &str) -> Result<(), Error> {
+    let metadata = std::fs::metadata(filename)?;
+    let uid = metadata.uid();
+    let gid = metadata.gid();
+
+    // 确保 UID 和 GID 打印正确
+    assert!(uid > 0, "UID should be greater than 0");
+    assert!(gid > 0, "GID should be greater than 0");
+
+    Ok(())
+}
+
+fn test_fchownat(filename: &str, new_uid: uid_t, new_gid: gid_t, flags: i32) -> Result<(), Error> {
+    let c_filename = CString::new(filename)?;
+    let result = unsafe { fchownat(AT_FDCWD, c_filename.as_ptr(), new_uid, new_gid, flags) };
+
+    // 确保 fchownat 成功
+    assert!(result != -1, "fchownat failed");
+
+    print_file_owner_group(filename)?;
+    Ok(())
+}
+
+fn test_chown(filename: &str, new_uid: uid_t, new_gid: gid_t) -> Result<(), Error> {
+    let c_filename = CString::new(filename)?;
+    let result = unsafe { chown(c_filename.as_ptr(), new_uid, new_gid) };
+
+    // 确保 chown 成功
+    assert!(result != -1, "chown failed");
+
+    print_file_owner_group(filename)?;
+    Ok(())
+}
+
+fn test_fchown(fd: i32, new_uid: uid_t, new_gid: gid_t) -> Result<(), Error> {
+    let result = unsafe { fchown(fd, new_uid, new_gid) };
+
+    // 确保 fchown 成功
+    assert!(result != -1, "fchown failed");
+
+    Ok(())
+}
+
+fn test_lchown(symlink_name: &str, new_uid: uid_t, new_gid: gid_t) -> Result<(), Error> {
+    let c_symlink = CString::new(symlink_name)?;
+    let result = unsafe { lchown(c_symlink.as_ptr(), new_uid, new_gid) };
+
+    // 确保 lchown 成功
+    assert!(result != -1, "lchown failed");
+
+    print_file_owner_group(symlink_name)?;
+    Ok(())
+}
+
+fn main() -> Result<(), Error> {
+    mount_test_ramfs();
+
+    let filename = "/mnt/myramfs/testfile.txt";
+    let symlink_name = "/mnt/myramfs/testsymlink";
+    let new_owner = "nobody"; // 替换为你测试系统中的有效用户名
+    let new_group = "nogroup"; // 替换为你测试系统中的有效组名
+
+    // 获取新的 UID 和 GID
+    let pw = unsafe { getpwnam(CString::new(new_owner)?.as_ptr()) };
+    let gr = unsafe { getgrnam(CString::new(new_group)?.as_ptr()) };
+
+    assert!(!pw.is_null(), "Invalid user name");
+    assert!(!gr.is_null(), "Invalid group name");
+
+    let new_uid = unsafe { (*pw).pw_uid };
+    let new_gid = unsafe { (*gr).gr_gid };
+
+    // 创建测试文件
+    let mut file = File::create(filename)?;
+    println!("Created test file: {}", filename);
+    writeln!(file, "This is a test file for chown system call")?;
+
+    // 创建符号链接
+    std::os::unix::fs::symlink(filename, symlink_name)?;
+    println!("Created symlink: {}", symlink_name);
+
+    // 打开文件以测试 fchown
+    let fd = file.as_raw_fd();
+
+    // 测试 chown
+    test_chown(filename, new_uid, new_gid)?;
+
+    // 测试 fchown
+    test_fchown(fd, new_uid, new_gid)?;
+
+    // 测试 lchown
+    test_lchown(symlink_name, new_uid, new_gid)?;
+
+    // 测试 fchownat,带 AT_SYMLINK_NOFOLLOW 标志(不会跟随符号链接)
+    test_fchownat(symlink_name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW)?;
+
+    // 清理测试文件
+    std::fs::remove_file(filename)?;
+
+    umount_test_ramfs();
+
+    println!("All tests passed!");
+
+    Ok(())
+}
+
+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 for test 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());
+    } else {
+        // 删除mnt/myramfs
+        let path = Path::new("mnt/myramfs");
+        let _ = fs::remove_dir(path);
+    }
+    assert_eq!(result, 0, "Umount myramfs failed");
+    println!("Umount myramfs for test success!");
+}

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

@@ -0,0 +1,29 @@
+{
+  "name": "test-chown",
+  "version": "0.1.0",
+  "description": "chown系列系统调用",
+  "rust_target": "x86_64-unknown-dragonos",
+  "task_type": {
+    "BuildFromSource": {
+      "Local": {
+        "path": "apps/test-chown"
+      }
+    }
+  },
+  "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"
+  ]
+}