From 55e6f0b65f91b32638fd56581f711a816eccdcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=AB=E8=8A=B1?= Date: Sat, 26 Oct 2024 18:13:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0chown=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=B0=83=E7=94=A8=20(#962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加chown系统调用 --------- Co-authored-by: sparkzky Co-authored-by: longjin --- kernel/src/filesystem/vfs/mod.rs | 64 +++++++--- kernel/src/filesystem/vfs/open.rs | 89 +++++++++++++- kernel/src/filesystem/vfs/syscall.rs | 50 +++++++- kernel/src/process/cred.rs | 2 +- kernel/src/syscall/mod.rs | 28 ++++- user/apps/test-chown/.gitignore | 4 + user/apps/test-chown/Cargo.toml | 13 ++ user/apps/test-chown/Makefile | 56 +++++++++ user/apps/test-chown/README.md | 8 ++ user/apps/test-chown/src/main.rs | 160 +++++++++++++++++++++++++ user/dadk/config/test_chown_0_1_0.dadk | 29 +++++ 11 files changed, 482 insertions(+), 21 deletions(-) create mode 100644 user/apps/test-chown/.gitignore create mode 100644 user/apps/test-chown/Cargo.toml create mode 100644 user/apps/test-chown/Makefile create mode 100644 user/apps/test-chown/README.md create mode 100644 user/apps/test-chown/src/main.rs create mode 100644 user/dadk/config/test_chown_0_1_0.dadk diff --git a/kernel/src/filesystem/vfs/mod.rs b/kernel/src/filesystem/vfs/mod.rs index 51d2c422..7801258e 100644 --- a/kernel/src/filesystem/vfs/mod.rs +++ b/kernel/src/filesystem/vfs/mod.rs @@ -598,17 +598,43 @@ impl dyn IndexNode { return self.lookup_follow_symlink(path, 0); } - /// @brief 查找文件(考虑符号链接) - /// - /// @param path 文件路径 - /// @param max_follow_times 最大经过的符号链接的大小 - /// - /// @return Ok(Arc) 要寻找的目录项的inode - /// @return Err(SystemError) 错误码 pub fn lookup_follow_symlink( &self, path: &str, max_follow_times: usize, + ) -> Result, 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, SystemError> { + return self.do_lookup_follow_symlink(path, max_follow_times, follow_final_symlink); + } + + /// # 查找文件 + /// 查找指定路径的文件,考虑符号链接的存在,并可选择是否返回最终路径的符号链接文件本身。 + /// + /// ## 参数 + /// - `path`: 文件路径 + /// - `max_follow_times`: 最大经过的符号链接的数量 + /// - `follow_final_symlink`: 是否跟随最后的符号链接 + /// + /// ## 返回值 + /// - `Ok(Arc)`: 要寻找的目录项的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, 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; } diff --git a/kernel/src/filesystem/vfs/open.rs b/kernel/src/filesystem/vfs/open.rs index 14f31fa1..108a114a 100644 --- a/kernel/src/filesystem/vfs/open.rs +++ b/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 Result { + // 检查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, uid: usize, gid: usize) -> Result { + 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 { + 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, diff --git a/kernel/src/filesystem/vfs/syscall.rs b/kernel/src/filesystem/vfs/syscall.rs index 5d256bf0..ef8220ef 100644 --- a/kernel/src/filesystem/vfs/syscall.rs +++ b/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 { + 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 { + 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 { + 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 { + return ksys_fchown(fd, uid, gid); + } + /// #挂载文件系统 /// /// 用于挂载文件系统,目前仅支持ramfs挂载 diff --git a/kernel/src/process/cred.rs b/kernel/src/process/cred.rs index 952cfbfd..6a6864c7 100644 --- a/kernel/src/process/cred.rs +++ b/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, } diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 6e7e9f7b..74f90610 100644 --- a/kernel/src/syscall/mod.rs +++ b/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 => { diff --git a/user/apps/test-chown/.gitignore b/user/apps/test-chown/.gitignore new file mode 100644 index 00000000..3e6e158b --- /dev/null +++ b/user/apps/test-chown/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +testfile.txt +/install/ \ No newline at end of file diff --git a/user/apps/test-chown/Cargo.toml b/user/apps/test-chown/Cargo.toml new file mode 100644 index 00000000..5966be30 --- /dev/null +++ b/user/apps/test-chown/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test-chown" +version = "0.1.0" +edition = "2021" +description = "测试chown系列系统调用" +authors = [ "sparkzky " ] + +# 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" \ No newline at end of file diff --git a/user/apps/test-chown/Makefile b/user/apps/test-chown/Makefile new file mode 100644 index 00000000..7522ea16 --- /dev/null +++ b/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 diff --git a/user/apps/test-chown/README.md b/user/apps/test-chown/README.md new file mode 100644 index 00000000..1a4585a4 --- /dev/null +++ b/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 +``` diff --git a/user/apps/test-chown/src/main.rs b/user/apps/test-chown/src/main.rs new file mode 100644 index 00000000..b2de5ef0 --- /dev/null +++ b/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!"); +} diff --git a/user/dadk/config/test_chown_0_1_0.dadk b/user/dadk/config/test_chown_0_1_0.dadk new file mode 100644 index 00000000..5da86f07 --- /dev/null +++ b/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" + ] +} \ No newline at end of file