好的,我们来学习一下 renameat 这个函数。它是一个用于重命名或移动文件和目录的系统调用,是 rename 函数的增强版,提供了更灵活的相对路径处理能力。
1. 函数介绍 見出しへのリンク
renameat 是一个 Linux 系统调用(System Call),它的主要作用是重命名文件或目录,或者将文件或目录移动到同一个文件系统内的另一个位置 。它与标准的 rename 函数功能相似,但关键区别在于它允许你指定相对路径是相对于哪个目录文件描述符来解析的 。这使得在处理复杂目录结构或受限环境(如沙箱)时更加方便和安全。
简单来说,如果你想把一个文件从 “old_name” 改成 “new_name”,或者把它从一个目录移动到另一个目录,就可以使用 renameat。它特别适用于需要基于特定目录上下文进行操作的场景。
2. 函数原型 見出しへのリンク
#include <stdio.h> // 或者 <fcntl.h> 在某些系统上
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
3. 功能 見出しへのリンク
这个函数的功能是将由 oldpath 指定的文件或目录重命名为由 newpath 指定的名称,或者将其移动到 newpath 指定的位置 。如果 newpath 已经存在,并且它不是 oldpath 本身,那么 newpath 会被原子地替换(如果类型允许,例如用新文件替换旧文件)。oldpath 和 newpath 都是相对于指定的目录文件描述符来解析的。
4. 参数 見出しへのリンク
olddirfd:int类型。- 这是一个文件描述符,指向包含
oldpath的目录。如果oldpath是绝对路径(以/开头),这个参数会被忽略。特殊的值AT_FDCWD可以用来表示当前工作目录,此时行为类似于rename函数 。
oldpath:const char *类型。- 指向一个以 null 结尾的字符串,表示要被重命名或移动的文件或目录的路径名。这个路径是相对于
olddirfd解析的(除非它是绝对路径)。
newdirfd:int类型。- 这是一个文件描述符,指向包含
newpath的目录。如果newpath是绝对路径(以/开头),这个参数会被忽略。特殊的值AT_FDCWD可以用来表示当前工作目录 。
newpath:const char *类型。- 指向一个以 null 结尾的字符串,表示新的文件或目录名,或者是目标位置的路径名。这个路径是相对于
newdirfd解析的(除非它是绝对路径)。
5. 返回值 見出しへのリンク
- 成功: 返回 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. 相似函数或关联函数 見出しへのリンク
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. 示例代码 見出しへのリンク
这个示例将演示几种使用 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