好的,我们来学习一下 renameat
这个函数。它是一个用于重命名或移动文件和目录的系统调用,是 rename
函数的增强版,提供了更灵活的相对路径处理能力。
1. 函数介绍 Link to heading
renameat
是一个 Linux 系统调用(System Call),它的主要作用是重命名文件或目录,或者将文件或目录移动到同一个文件系统内的另一个位置 。它与标准的 rename
函数功能相似,但关键区别在于它允许你指定相对路径是相对于哪个目录文件描述符来解析的 。这使得在处理复杂目录结构或受限环境(如沙箱)时更加方便和安全。
简单来说,如果你想把一个文件从 “old_name” 改成 “new_name”,或者把它从一个目录移动到另一个目录,就可以使用 renameat
。它特别适用于需要基于特定目录上下文进行操作的场景。
2. 函数原型 Link to heading
#include <stdio.h> // 或者 <fcntl.h> 在某些系统上
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
3. 功能 Link to heading
这个函数的功能是将由 oldpath
指定的文件或目录重命名为由 newpath
指定的名称,或者将其移动到 newpath
指定的位置 。如果 newpath
已经存在,并且它不是 oldpath
本身,那么 newpath
会被原子地替换(如果类型允许,例如用新文件替换旧文件)。oldpath
和 newpath
都是相对于指定的目录文件描述符来解析的。
4. 参数 Link to heading
olddirfd
:int
类型。- 这是一个文件描述符,指向包含
oldpath
的目录。如果oldpath
是绝对路径(以/
开头),这个参数会被忽略。特殊的值AT_FDCWD
可以用来表示当前工作目录,此时行为类似于rename
函数 。
oldpath
:const char *
类型。- 指向一个以 null 结尾的字符串,表示要被重命名或移动的文件或目录的路径名。这个路径是相对于
olddirfd
解析的(除非它是绝对路径)。
newdirfd
:int
类型。- 这是一个文件描述符,指向包含
newpath
的目录。如果newpath
是绝对路径(以/
开头),这个参数会被忽略。特殊的值AT_FDCWD
可以用来表示当前工作目录 。
newpath
:const char *
类型。- 指向一个以 null 结尾的字符串,表示新的文件或目录名,或者是目标位置的路径名。这个路径是相对于
newdirfd
解析的(除非它是绝对路径)。
5. 返回值 Link to heading
- 成功: 返回 0。
- 失败: 返回 -1,并设置全局变量
errno
来指示具体的错误类型 。EACCES
: 搜索路径上的目录不允许搜索权限,或者oldpath
或newpath
的父目录不允许写权限,或者需要覆盖但不允许写权限。EBUSY
: 要重命名的文件正被使用(例如是当前工作目录或根目录),或者文件系统不支持重命名操作。EDQUOT
: 用户磁盘配额不足。EEXIST
或ENOTEMPTY
:newpath
是一个非空目录,不能被oldpath
替换。EFAULT
:oldpath
或newpath
指向无效的地址空间。EINVAL
:oldpath
或newpath
的参数无效(例如,oldpath
或newpath
是目录,但新路径是该目录的子目录)。EISDIR
:newpath
是一个目录,但oldpath
是一个非目录。ELOOP
: 解析oldpath
或newpath
时遇到符号链接循环。EMLINK
:oldpath
或newpath
的父目录链接数超出限制。ENAMETOOLONG
:oldpath
或newpath
太长。ENOENT
:oldpath
不存在,或者olddirfd
或newdirfd
无效且不是AT_FDCWD
。ENOMEM
: 内核内存不足。ENOSPC
: 设备空间不足。ENOTDIR
:olddirfd
或newdirfd
不是目录文件描述符,或者oldpath
或newpath
的某个前缀不是目录。ENOTEMPTY
:newpath
是一个非空目录。EPERM
: 父目录的挂载点不支持重命名,或者oldpath
或newpath
是目录且调用者没有权限,或者文件系统不允许该操作。EROFS
: 文件系统是只读的。EXDEV
:olddirfd
和newdirfd
指向不同的文件系统挂载点(renameat
不能跨文件系统移动文件)。
6. 相似函数或关联函数 Link to heading
rename
: 标准的重命名函数,路径是相对于当前工作目录解析的。rename(oldpath, newpath)
等价于renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath)
。renameat2
:renameat
的扩展版本,提供了额外的标志位(如RENAME_EXCHANGE
,RENAME_NOREPLACE
),需要 Linux 3.15+ 内核支持。link
/linkat
: 创建硬链接。linkat
是link
的增强版,类似renameat
与rename
的关系。unlink
: 删除文件的链接(如果这是最后一个链接,则删除文件)。mkdir
/rmdir
: 创建和删除目录。
7. 示例代码 Link to heading
这个示例将演示几种使用 renameat
的场景:
#define _GNU_SOURCE // 启用 GNU 扩展,以使用 AT_FDCWD 等
#include <stdio.h>
#include <fcntl.h> // open, O_RDONLY, O_DIRECTORY
#include <unistd.h> // close
#include <sys/stat.h> // mkdir
#include <string.h>
#include <errno.h>
int main() {
const char *src_dir = "source_dir";
const char *dst_dir = "dest_dir";
const char *file_name = "test_file.txt";
const char *renamed_file_name = "moved_and_renamed_file.txt";
const char *dir_name = "test_subdir";
const char *renamed_dir_name = "moved_subdir";
int src_dir_fd, dst_dir_fd;
int result;
// 1. 创建源目录和目标目录
if (mkdir(src_dir, 0755) == -1 && errno != EEXIST) {
perror("mkdir source_dir");
return 1;
}
if (mkdir(dst_dir, 0755) == -1 && errno != EEXIST) {
perror("mkdir dest_dir");
return 1;
}
// 2. 在源目录中创建一个测试文件
char file_path[256];
snprintf(file_path, sizeof(file_path), "%s/%s", src_dir, file_name);
FILE *file = fopen(file_path, "w");
if (!file) {
perror("fopen test_file.txt");
return 1;
}
fprintf(file, "This is a test file for renameat.\n");
fclose(file);
printf("Created file: %s\n", file_path);
// 3. 在源目录中创建一个子目录
char dir_path[256];
snprintf(dir_path, sizeof(dir_path), "%s/%s", src_dir, dir_name);
if (mkdir(dir_path, 0755) == -1) {
perror("mkdir test_subdir");
return 1;
}
printf("Created directory: %s\n", dir_path);
// 4. 打开源目录和目标目录,获取文件描述符
// O_RDONLY: 只读打开
// O_DIRECTORY: 确保打开的是目录
src_dir_fd = open(src_dir, O_RDONLY | O_DIRECTORY);
if (src_dir_fd == -1) {
perror("open source_dir");
return 1;
}
dst_dir_fd = open(dst_dir, O_RDONLY | O_DIRECTORY);
if (dst_dir_fd == -1) {
perror("open dest_dir");
close(src_dir_fd); // 关闭已打开的
return 1;
}
// 5. 使用 renameat 将文件从源目录移动到目标目录并重命名
// olddirfd=src_dir_fd, oldpath=file_name (相对路径)
// newdirfd=dst_dir_fd, newpath=renamed_file_name (相对路径)
printf("Moving and renaming file using renameat...\n");
result = renameat(src_dir_fd, file_name, dst_dir_fd, renamed_file_name);
if (result == -1) {
perror("renameat file");
close(src_dir_fd);
close(dst_dir_fd);
return 1;
}
printf("File moved from %s/%s to %s/%s\n", src_dir, file_name, dst_dir, renamed_file_name);
// 6. 使用 renameat 将目录从源目录移动到目标目录并重命名
printf("Moving and renaming directory using renameat...\n");
result = renameat(src_dir_fd, dir_name, dst_dir_fd, renamed_dir_name);
if (result == -1) {
perror("renameat directory");
close(src_dir_fd);
close(dst_dir_fd);
return 1;
}
printf("Directory moved from %s/%s to %s/%s\n", src_dir, dir_name, dst_dir, renamed_dir_name);
// 7. 使用 renameat 在同一目录内重命名文件 (使用 AT_FDCWD)
// 将目标目录中的文件重命名回原始名称
printf("Renaming file within destination directory using AT_FDCWD...\n");
result = renameat(AT_FDCWD, // 当前工作目录 (或指定 dst_dir 的绝对路径)
"dest_dir/moved_and_renamed_file.txt", // oldpath (绝对路径)
dst_dir_fd, // newdirfd
file_name); // newpath (相对路径)
if (result == -1) {
perror("renameat within dest_dir");
close(src_dir_fd);
close(dst_dir_fd);
return 1;
}
printf("File renamed within %s from 'moved_and_renamed_file.txt' to '%s'\n", dst_dir, file_name);
// 8. 清理: 关闭目录文件描述符
close(src_dir_fd);
close(dst_dir_fd);
printf("Example completed successfully.\n");
printf("Check the contents of '%s' and '%s' directories.\n", src_dir, dst_dir);
return 0;
}
编译和运行:
# 假设代码保存在 renameat_example.c 中
gcc -o renameat_example renameat_example.c
# 运行
./renameat_example
# 查看结果
ls -R source_dir dest_dir
# 清理 (如果需要)
# rm -rf source_dir dest_dir