add dropbear support (#1304)

https://github.com/DragonOS-Community/DragonOS/pull/1304

* fix rename error in fat32

add a fake link implementation for fat32(it will be removed in the
future).

Signed-off-by: Godones <chenlinfeng25@outlook.com>

* feat: add new syscall and fix the fnctl error

add sendfile syscall.
add rt_sigsuspend syscall.
add sendfile test.
add setown/getown command for fcntl.

Signed-off-by: Godones <chenlinfeng25@outlook.com>

---------

Signed-off-by: Godones <chenlinfeng25@outlook.com>
This commit is contained in:
linfeng
2025-10-27 22:29:50 +08:00
committed by GitHub
parent 9634e5e530
commit c651b2747c
13 changed files with 452 additions and 96 deletions
-47
View File
@@ -42,53 +42,6 @@ pub(super) fn pts_driver() -> Arc<TtyDriver> {
PTS_DRIVER.get().clone()
}
// lazy_static! {
// pub static ref PTM_DRIVER: Arc<TtyDriver> = {
// let mut ptm_driver = TtyDriver::new(
// NR_UNIX98_PTY_MAX,
// "ptm",
// 0,
// Major::UNIX98_PTY_MASTER_MAJOR,
// 0,
// TtyDriverType::Pty,
// *TTY_STD_TERMIOS,
// Arc::new(Unix98PtyDriverInner::new()),
// );
// ptm_driver.set_subtype(TtyDriverSubType::PtyMaster);
// let term = ptm_driver.init_termios_mut();
// term.input_mode = InputMode::empty();
// term.output_mode = OutputMode::empty();
// term.control_mode = ControlMode::B38400 | ControlMode::CS8 | ControlMode::CREAD;
// term.local_mode = LocalMode::empty();
// term.input_speed = 38400;
// term.output_speed = 38400;
// TtyDriverManager::tty_register_driver(ptm_driver).unwrap()
// };
// pub static ref PTS_DRIVER: Arc<TtyDriver> = {
// let mut pts_driver = TtyDriver::new(
// NR_UNIX98_PTY_MAX,
// "pts",
// 0,
// Major::UNIX98_PTY_SLAVE_MAJOR,
// 0,
// TtyDriverType::Pty,
// *TTY_STD_TERMIOS,
// Arc::new(Unix98PtyDriverInner::new()),
// );
// pts_driver.set_subtype(TtyDriverSubType::PtySlave);
// let term = pts_driver.init_termios_mut();
// term.input_mode = InputMode::empty();
// term.output_mode = OutputMode::empty();
// term.control_mode = ControlMode::B38400 | ControlMode::CS8 | ControlMode::CREAD;
// term.local_mode = LocalMode::empty();
// term.input_speed = 38400;
// term.output_speed = 38400;
// TtyDriverManager::tty_register_driver(pts_driver).unwrap()
// };
// }
pub struct PtyCommon;
impl PtyCommon {
+14 -10
View File
@@ -854,7 +854,7 @@ impl FATDir {
new_name: &str,
) -> Result<FATDirEntry, SystemError> {
// 判断源目录项是否存在
let old_dentry: FATDirEntry = if let FATDirEntryOrShortName::DirEntry(dentry) =
let old_dentry = if let FATDirEntryOrShortName::DirEntry(dentry) =
self.check_existence(old_name, None, fs.clone())?
{
dentry
@@ -863,22 +863,26 @@ impl FATDir {
return Err(SystemError::ENOENT);
};
let short_name = if let FATDirEntryOrShortName::ShortName(s) =
self.check_existence(new_name, None, fs.clone())?
{
s
} else {
// 如果目标目录项存在,那么就返回错误
return Err(SystemError::EEXIST);
let short_name = match self.check_existence(new_name, None, fs.clone())? {
FATDirEntryOrShortName::ShortName(s) => s,
// If newpath already exists, it will be atomically replaced, so that
// there is no point at which another process attempting to access
// newpath will find it missing.
// TODO: support other flags like RENAME_EXCHANGE
FATDirEntryOrShortName::DirEntry(e) => {
// remove the existing entry
self.remove(fs.clone(), new_name, true)?;
e.short_name_raw()
}
};
let old_short_dentry: Option<ShortDirEntry> = old_dentry.short_dir_entry();
let old_short_dentry = old_dentry.short_dir_entry();
if let Some(se) = old_short_dentry {
// 删除原来的目录项
self.remove(fs.clone(), old_dentry.name().as_str(), false)?;
// 创建新的目录项
let new_dentry: FATDirEntry = self.create_dir_entries(
let new_dentry = self.create_dir_entries(
new_name,
&short_name,
Some(se),
+74 -27
View File
@@ -18,6 +18,7 @@ use crate::filesystem::page_cache::PageCache;
use crate::filesystem::vfs::utils::DName;
use crate::filesystem::vfs::{Magic, SpecialNodeData, SuperBlock};
use crate::ipc::pipe::LockedPipeInode;
use crate::libs::casting::DowncastArc;
use crate::mm::fault::{PageFaultHandler, PageFaultMessage};
use crate::mm::VmFaultReason;
use crate::{
@@ -266,11 +267,11 @@ impl LockedFATInode {
new_name: &str,
) -> Result<(), SystemError> {
let mut guard = self.0.lock();
let old_inode: Arc<LockedFATInode> = guard.find(old_name)?;
let old_inode = guard.find(old_name)?;
// 对目标inode上锁,以防更改
let old_inode_guard: SpinLockGuard<FATInode> = old_inode.0.lock();
let old_inode_guard = old_inode.0.lock();
let fs = old_inode_guard.fs.upgrade().unwrap();
// 从缓存删除
let old_dir = match &guard.inode_type {
FATDirEntry::File(_) | FATDirEntry::VolId(_) => {
return Err(SystemError::ENOTDIR);
@@ -281,11 +282,14 @@ impl LockedFATInode {
return Err(SystemError::EROFS);
}
};
// 检查文件是否存在
// old_dir.check_existence(old_name, Some(false), guard.fs.upgrade().unwrap())?;
// remove entries
old_dir.rename(fs, old_name, new_name)?;
let _nod = guard.children.remove(&to_search_name(old_name));
let old_inode = guard.children.remove(&to_search_name(old_name)).unwrap();
// the new_name should refer to old_inode
guard.children.insert(to_search_name(new_name), old_inode);
Ok(())
}
@@ -1486,6 +1490,31 @@ impl FATFsInfo {
}
}
impl LockedFATInode {
fn try_read_pagecache(&self, offset: usize, buf: &mut [u8]) -> Result<usize, SystemError> {
let page_cache = self.0.lock().page_cache.clone();
if let Some(page_cache) = page_cache {
let r = page_cache.lock_irqsave().read(offset, buf);
return r;
} else {
return self.read_sync(offset, buf);
}
}
fn try_write_pagecache(&self, offset: usize, buf: &[u8]) -> Result<usize, SystemError> {
let page_cache = self.0.lock().page_cache.clone();
if let Some(page_cache) = page_cache {
let write_len = page_cache.lock_irqsave().write(offset, buf)?;
let mut guard = self.0.lock();
let old_size = guard.metadata.size;
guard.update_metadata(Some(core::cmp::max(old_size, (offset + write_len) as i64)));
return Ok(write_len);
} else {
return self.write_sync(offset, buf);
}
}
}
impl IndexNode for LockedFATInode {
fn read_sync(&self, offset: usize, buf: &mut [u8]) -> Result<usize, SystemError> {
let guard: SpinLockGuard<FATInode> = self.0.lock();
@@ -1531,18 +1560,11 @@ impl IndexNode for LockedFATInode {
offset: usize,
len: usize,
buf: &mut [u8],
data: SpinLockGuard<FilePrivateData>,
_data: SpinLockGuard<FilePrivateData>,
) -> Result<usize, SystemError> {
let len = core::cmp::min(len, buf.len());
let buf = &mut buf[0..len];
let page_cache = self.0.lock().page_cache.clone();
if let Some(page_cache) = page_cache {
let r = page_cache.lock_irqsave().read(offset, &mut buf[0..len]);
return r;
} else {
return self.read_direct(offset, len, buf, data);
}
self.try_read_pagecache(offset, buf)
}
fn write_at(
@@ -1550,21 +1572,11 @@ impl IndexNode for LockedFATInode {
offset: usize,
len: usize,
buf: &[u8],
data: SpinLockGuard<FilePrivateData>,
_data: SpinLockGuard<FilePrivateData>,
) -> Result<usize, SystemError> {
let len = core::cmp::min(len, buf.len());
let buf = &buf[0..len];
let page_cache = self.0.lock().page_cache.clone();
if let Some(page_cache) = page_cache {
let write_len = page_cache.lock_irqsave().write(offset, buf)?;
let mut guard = self.0.lock();
let old_size = guard.metadata.size;
guard.update_metadata(Some(core::cmp::max(old_size, (offset + write_len) as i64)));
return Ok(write_len);
} else {
return self.write_direct(offset, len, buf, data);
}
self.try_write_pagecache(offset, buf)
}
fn read_direct(
@@ -1633,6 +1645,41 @@ impl IndexNode for LockedFATInode {
}
}
// fat32 does not support hard link
// TODO: remove this function
fn link(&self, name: &str, other: &Arc<dyn IndexNode>) -> Result<(), SystemError> {
let ty = other.metadata()?.file_type;
let mode = other.metadata()?.mode;
let other = other
.downcast_ref::<LockedFATInode>()
.ok_or(SystemError::EINVAL)?;
let new_inode = self
.create(name, ty, mode)?
.downcast_arc::<LockedFATInode>()
.ok_or(SystemError::EINVAL)?;
let mut offset = 0;
let mut buf = [0u8; 512];
loop {
let read_len = other.try_read_pagecache(offset, &mut buf)?;
if read_len == 0 {
break;
}
log::error!("Fake FATFS(link): read_len={read_len}, offset={offset}");
let write_len = new_inode.try_write_pagecache(offset, &buf[0..read_len])?;
if write_len < read_len {
log::error!(
"Fake FATFS(link): write link file failed, read_len={read_len}, write_len={write_len}"
);
return Err(SystemError::EIO);
}
offset += write_len;
}
log::error!("Fake FATFS(link): link file {name} success, size={offset}");
Ok(())
}
fn fs(&self) -> Arc<dyn FileSystem> {
return self.0.lock().fs.upgrade().unwrap();
}
+4
View File
@@ -23,6 +23,10 @@ pub enum FcntlCommand {
SetLock = 6,
/// set record locking info (blocking)
SetLockWait = 7,
/// set owner
SetOwn = 8,
/// get owner
GetOwn = 9,
SetLease = F_LINUX_SPECIFIC_BASE,
GetLease = F_LINUX_SPECIFIC_BASE + 1,
+28 -3
View File
@@ -17,7 +17,7 @@ use crate::{
},
ipc::{kill::kill_process, pipe::PipeFsPrivateData},
libs::{rwlock::RwLock, spinlock::SpinLock},
process::{cred::Cred, resource::RLimitID, ProcessManager},
process::{cred::Cred, resource::RLimitID, ProcessControlBlock, ProcessManager, RawPid},
};
/// 文件私有信息的枚举类型
@@ -131,6 +131,8 @@ pub struct File {
cred: Arc<Cred>,
/// 文件描述符标志:是否在execve时关闭
close_on_exec: AtomicBool,
/// owner
pid: SpinLock<Option<Arc<ProcessControlBlock>>>,
}
impl File {
@@ -161,6 +163,7 @@ impl File {
private_data,
cred: ProcessManager::current_pcb().cred(),
close_on_exec: AtomicBool::new(close_on_exec),
pid: SpinLock::new(None),
};
return Ok(f);
@@ -230,7 +233,7 @@ impl File {
self.do_write(offset, len, buf, false)
}
fn do_read(
pub fn do_read(
&self,
offset: usize,
len: usize,
@@ -259,7 +262,7 @@ impl File {
Ok(len)
}
fn do_write(
pub fn do_write(
&self,
offset: usize,
len: usize,
@@ -452,6 +455,7 @@ impl File {
private_data: SpinLock::new(self.private_data.lock().clone()),
cred: self.cred.clone(),
close_on_exec: AtomicBool::new(self.close_on_exec.load(Ordering::SeqCst)),
pid: SpinLock::new(None),
};
// 调用inode的open方法,让inode知道有新的文件打开了这个inode
// TODO: reopen is not a good idea for some inodes, need a better design
@@ -544,6 +548,27 @@ impl File {
let private_data = self.private_data.lock();
self.inode.as_pollable_inode()?.poll(&private_data)
}
pub fn owner(&self) -> Option<RawPid> {
self.pid.lock().as_ref().map(|pcb| pcb.raw_pid())
}
/// Set a process (group) as owner of the file descriptor.
///
/// Such that this process (group) will receive `SIGIO` and `SIGURG` signals
/// for I/O events on the file descriptor, if `O_ASYNC` status flag is set
/// on this file.
pub fn set_owner(&self, pid: Option<Arc<ProcessControlBlock>>) -> Result<(), SystemError> {
let Some(pcb) = pid else {
*self.pid.lock() = None;
return Ok(());
};
self.pid.lock().replace(pcb);
// todo: update inode owner
log::error!("set_owner has not been implemented yet");
Ok(())
}
}
impl Drop for File {
+5 -5
View File
@@ -53,13 +53,13 @@ mod sys_epoll_ctl;
mod sys_epoll_pwait;
pub mod symlink_utils;
mod sys_fsync;
pub mod sys_mount;
mod sys_sync;
pub mod sys_umount2;
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
mod sys_fstat;
mod sys_fsync;
pub mod sys_mount;
mod sys_sendfile;
mod sys_sync;
pub mod sys_umount2;
#[cfg(target_arch = "x86_64")]
mod sys_access;
@@ -26,7 +26,7 @@ pub fn do_renameat2(
filename_from: *const u8,
newfd: i32,
filename_to: *const u8,
_flags: u32,
flags: u32,
) -> Result<usize, SystemError> {
let filename_from = check_and_clone_cstr(filename_from, Some(MAX_PATHLEN))
.unwrap()
@@ -41,6 +41,12 @@ pub fn do_renameat2(
return Err(SystemError::ENAMETOOLONG);
}
let flags = Flags::from_bits_truncate(flags);
if !flags.is_empty() {
log::warn!("renameat2 flags {flags:?} not supported yet");
return Err(SystemError::EINVAL);
}
//获取pcb,文件节点
let pcb = ProcessManager::current_pcb();
let (_old_inode_begin, old_remain_path) = user_path_at(&pcb, oldfd, &filename_from)?;
@@ -56,3 +62,16 @@ pub fn do_renameat2(
old_parent_inode.move_to(old_filename, &new_parent_inode, new_filename)?;
return Ok(0);
}
bitflags! {
/// Flags used in the `renameat2` system call.
///
/// Reference: <https://elixir.bootlin.com/linux/v6.16.3/source/include/uapi/linux/fcntl.h#L140-L143>.
///
/// Reference: <https://man7.org/linux/man-pages/man2/renameat.2.html>.
struct Flags: u32 {
const NOREPLACE = 1 << 0;
const EXCHANGE = 1 << 1;
const WHITEOUT = 1 << 2;
}
}
@@ -1,4 +1,5 @@
use crate::arch::syscall::nr::SYS_FCNTL;
use crate::process::RawPid;
use crate::{
arch::interrupt::TrapFrame,
filesystem::vfs::{
@@ -158,6 +159,37 @@ impl SysFcntlHandle {
return Err(SystemError::EBADF);
}
FcntlCommand::SetOwn => {
let pid = arg.unsigned_abs();
if pid > i32::MAX as u32 {
return Err(SystemError::EINVAL);
}
let pb = if pid == 0 {
None
} else {
let pb =
ProcessManager::find(RawPid::from(pid as _)).ok_or(SystemError::ESRCH)?;
Some(pb)
};
let binding = ProcessManager::current_pcb().fd_table();
let file = binding
.read()
.get_file_by_fd(fd)
.ok_or(SystemError::EBADF)?;
file.set_owner(pb)?;
Ok(0)
}
FcntlCommand::GetOwn => {
let binding = ProcessManager::current_pcb().fd_table();
let file = binding
.read()
.get_file_by_fd(fd)
.ok_or(SystemError::EBADF)?;
let owner = file.owner().unwrap_or(RawPid::from(0));
return Ok(owner.data());
}
_ => {
// TODO: unimplemented
// 未实现的命令,返回0,不报错。
@@ -0,0 +1,136 @@
use crate::arch::syscall::nr::SYS_SENDFILE;
use crate::process::ProcessManager;
use crate::syscall::table::Syscall;
use crate::syscall::user_access::UserBufferReader;
use alloc::vec::Vec;
use system_error::SystemError;
/// See <https://man7.org/linux/man-pages/man2/sendfile64.2.html>
pub struct SysSendfileHandle;
impl Syscall for SysSendfileHandle {
fn num_args(&self) -> usize {
4
}
fn handle(
&self,
args: &[usize],
_frame: &mut crate::arch::interrupt::TrapFrame,
) -> Result<usize, SystemError> {
let offset_ptr = args[2] as *const isize;
let out_fd = args[0] as i32;
let in_fd = args[1] as i32;
let count = args[3] as isize;
let offset = if offset_ptr.is_null() {
None
} else {
let offset = *UserBufferReader::new(offset_ptr, size_of::<isize>(), true)?
.read_one_from_user::<isize>(0)?;
if offset < 0 {
return Err(SystemError::EINVAL);
}
Some(offset)
};
log::trace!(
"out_fd = {}, in_fd = {}, offset = {:x?}, count = 0x{:x}",
out_fd,
in_fd,
offset,
count
);
let count = if count < 0 {
return Err(SystemError::EINVAL);
} else {
count as usize
};
let (out_file, in_file) = {
let binding = ProcessManager::current_pcb().fd_table();
let fd_table_guard = binding.write();
let out_file = fd_table_guard
.get_file_by_fd(out_fd)
.ok_or(SystemError::EBADF)?;
let in_file = fd_table_guard
.get_file_by_fd(in_fd)
.ok_or(SystemError::EBADF)?;
(out_file, in_file)
};
let mut buffer = vec![0u8; 4096].into_boxed_slice();
let mut total_len = 0;
let mut offset = offset.map(|offset| offset as usize);
while total_len < count {
// The offset decides how to read from `in_file`.
// If offset is `Some(_)`, the data will be read from the given offset,
// and after reading, the file offset of `in_file` will remain unchanged.
// If offset is `None`, the data will be read from the file offset,
// and the file offset of `in_file` is adjusted
// to reflect the number of bytes read from `in_file`.
let max_readlen = buffer.len().min(count - total_len);
// Read from `in_file`
let read_res = if let Some(offset) = offset.as_mut() {
let res = in_file.do_read(*offset, max_readlen, &mut buffer[..max_readlen], false);
if let Ok(len) = res.as_ref() {
*offset += *len;
}
res
} else {
in_file.read(max_readlen, &mut buffer[..max_readlen])
};
let read_len = match read_res {
Ok(len) => len,
Err(e) => {
if total_len > 0 {
log::warn!("error occurs when trying to read file: {:?}", e);
break;
}
return Err(e);
}
};
if read_len == 0 {
break;
}
// Note: `sendfile` allows sending partial data,
// so short reads and short writes are all acceptable
let write_res = out_file.write(read_len, &buffer[..read_len]);
match write_res {
Ok(len) => {
total_len += len;
if len < 4096 {
break;
}
}
Err(e) => {
if total_len > 0 {
log::warn!("error occurs when trying to write file: {:?}", e);
break;
}
return Err(e);
}
}
}
Ok(total_len)
}
fn entry_format(&self, args: &[usize]) -> Vec<crate::syscall::table::FormattedSyscallParam> {
vec![
crate::syscall::table::FormattedSyscallParam::new("out_fd", format!("{:#x}", args[0])),
crate::syscall::table::FormattedSyscallParam::new("in_fd", format!("{:#x}", args[1])),
crate::syscall::table::FormattedSyscallParam::new("offset", format!("{:#x}", args[2])),
crate::syscall::table::FormattedSyscallParam::new("count", format!("{:#x}", args[3])),
]
}
}
syscall_table_macros::declare_syscall!(SYS_SENDFILE, SysSendfileHandle);
+3 -3
View File
@@ -1,7 +1,10 @@
pub mod sys_kill;
#[cfg(target_arch = "x86_64")]
pub mod sys_pipe;
pub mod sys_pipe2;
mod sys_restart;
mod sys_rt_sigprocmask;
mod sys_rt_sigsuspend;
pub mod sys_rt_sigtimedwait;
mod sys_shmat;
mod sys_shmctl;
@@ -11,6 +14,3 @@ mod sys_sigaction;
mod sys_sigpending;
pub mod sys_tgkill;
pub mod sys_tkill;
#[cfg(target_arch = "x86_64")]
pub mod sys_pipe;
@@ -0,0 +1,72 @@
use syscall_table_macros::declare_syscall;
use system_error::SystemError;
use crate::arch::ipc::signal::SigSet;
use crate::arch::syscall::nr::SYS_RT_SIGSUSPEND;
use crate::ipc::signal::{set_sigprocmask, SigHow};
use crate::process::ProcessManager;
use crate::sched::{schedule, SchedMode};
use crate::{
mm::VirtAddr,
syscall::{
table::{FormattedSyscallParam, Syscall},
user_access::UserBufferReader,
},
};
/// See <https://man7.org/linux/man-pages/man2/rt_sigsuspend.2.html>
pub struct SysRtSigSuspend;
impl Syscall for SysRtSigSuspend {
fn num_args(&self) -> usize {
1
}
fn handle(
&self,
args: &[usize],
_frame: &mut crate::arch::interrupt::TrapFrame,
) -> Result<usize, system_error::SystemError> {
let sigsetsize = args[1];
if sigsetsize != size_of::<SigSet>() {
return Err(SystemError::EFAULT);
}
let reader = UserBufferReader::new(
VirtAddr::new(args[0]).as_ptr::<u64>(),
core::mem::size_of::<u64>(),
true,
)?;
let mask = reader.read_one_from_user::<u64>(0)?;
let mut mask = SigSet::from_bits_truncate(*mask);
// It is not possible to block SIGKILL or SIGSTOP; specifying these
// signals in mask, has no effect on the thread's signal mask.
mask -= SigSet::SIGKILL;
mask -= SigSet::SIGSTOP;
let pcb = ProcessManager::current_pcb();
let old_mask = *pcb.sig_info_irqsave().sig_blocked();
set_sigprocmask(SigHow::SetMask, mask).unwrap();
log::trace!("Process enter rt_sigsuspend, new mask: {mask:?}, old mask: {old_mask:?}");
loop {
if pcb.has_pending_signal_fast() && pcb.has_pending_not_masked_signal() {
set_sigprocmask(SigHow::SetMask, old_mask).unwrap();
return Err(SystemError::EINTR);
}
schedule(SchedMode::SM_NONE);
}
// unreachable!("rt_sigsuspend always return EINTR");
}
fn entry_format(
&self,
args: &[usize],
) -> alloc::vec::Vec<crate::syscall::table::FormattedSyscallParam> {
vec![FormattedSyscallParam::new(
"mask",
format!("{:#x}", args[0]),
)]
}
}
declare_syscall!(SYS_RT_SIGSUSPEND, SysRtSigSuspend);
+11
View File
@@ -68,6 +68,17 @@ impl<T: Socket + 'static> IndexNode for T {
ModeType::from_bits_truncate(0o755),
))
}
// TODO: implement ioctl for socket
fn ioctl(
&self,
_cmd: u32,
_data: usize,
_private_data: &FilePrivateData,
) -> Result<usize, SystemError> {
log::warn!("Socket not support ioctl");
return Ok(0);
}
}
impl<T: Socket + 'static> PollableInode for T {
+53
View File
@@ -0,0 +1,53 @@
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <源文件> <目标文件>\n", argv[0]);
return 1;
}
const char *src_path = argv[1];
const char *dst_path = argv[2];
int src_fd = open(src_path, O_RDONLY);
if (src_fd < 0) {
perror("打开源文件失败");
return 1;
}
int dst_fd = open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dst_fd < 0) {
perror("打开目标文件失败");
close(src_fd);
return 1;
}
struct stat stat_buf;
if (fstat(src_fd, &stat_buf) < 0) {
perror("fstat失败");
close(src_fd);
close(dst_fd);
return 1;
}
off_t offset = 0;
ssize_t sent = sendfile(dst_fd, src_fd, &offset, stat_buf.st_size);
if (sent < 0) {
perror("sendfile失败");
close(src_fd);
close(dst_fd);
return 1;
}
printf("成功复制 %zd 字节,从 %s 到 %s\n", sent, src_path, dst_path);
close(src_fd);
close(dst_fd);
return 0;
}