rename/renameat/renameat2系统调用及示例

rename/renameat/renameat2系统调用及示例

Linux 文件重命名系统调用详解,Linux 提供了三种文件重命名系统调用:rename、renameat 和 renameat2。其中 rename 是最基础的文件重命名函数,renameat 支持相对路径操作,renameat2 则是最强大的版本,支持额外标志位控制(如不替换已存在文件、交换文件等)。这些函数成功时返回0,失败返回-1并设置errno。文章详细介绍了各函数的原型、参数、标志位选项、常见错误码以及关联函数,并提供了基础用法的示例代码,展示了如何创建测试文件、重命名文件以及覆盖已存在文件的操作过程。

1. 函数介绍

文件重命名是 Linux 系统中最常用的操作之一。可以把文件重命名想象成”文件的身份证号码变更”——文件的内容和属性都没有改变,只是改变了文件在文件系统中的”名字”或”位置”。

这三个函数都用于重命名文件或目录,但功能逐渐增强:

  • rename: 最基础的重命名函数
  • renameat: 支持相对路径的重命名函数
  • renameat2: 最强大的重命名函数,支持额外选项

2. 函数原型

#include <stdio.h>

// 基础重命名
int rename(const char *oldpath, const char *newpath);

// 相对路径重命名
int renameat(int olddirfd, const char *oldpath,
             int newdirfd, const char *newpath);

// 增强版重命名(需要定义 _GNU_SOURCE)
#define _GNU_SOURCE
#include <fcntl.h>
int renameat2(int olddirfd, const char *oldpath,
              int newdirfd, const char *newpath, unsigned int flags);

3. 功能

rename

将 oldpath 指定的文件或目录重命名为 newpath

renameat

在指定目录描述符的上下文中重命名文件或目录,支持相对路径。

renameat2

增强版重命名,支持额外的标志控制选项。

4. 参数

rename 参数

  • oldpath: 旧文件路径名
  • newpath: 新文件路径名

renameat/renameat2 参数

  • olddirfd: 旧文件路径的目录文件描述符
  • oldpath: 旧文件路径名
  • newdirfd: 新文件路径的目录文件描述符
  • newpath: 新文件路径名
  • flags: 控制重命名行为的标志位(仅 renameat2)

5. 标志位(renameat2 专用)

标志说明
RENAME_NOREPLACE(1 << 0)不替换已存在的文件
RENAME_EXCHANGE(1 << 1)交换两个文件
RENAME_WHITEOUT(1 << 2)创建白名单条目(overlayfs 专用)

6. 返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置相应的 errno 错误码

7. 常见错误码

  • EACCES: 权限不足
  • EBUSY: 文件正被使用
  • EDQUOT: 磁盘配额不足
  • EEXIST: 新文件已存在(当使用 RENAME_NOREPLACE 时)
  • EINVAL: 参数无效(如 flags 无效)
  • EISDIR: 试图用目录覆盖非目录
  • ELOOP: 符号链接循环
  • ENOENT: 文件或目录不存在
  • ENOTDIR: 路径组件不是目录
  • ENOTEMPTY: 目录非空
  • EPERM: 操作不被允许
  • EROFS: 文件系统只读
  • EXDEV: 跨文件系统移动(rename 不支持)

8. 相似函数或关联函数

  • link/unlink: 创建/删除硬链接
  • symlink: 创建符号链接
  • mv: 命令行重命名工具
  • stat: 获取文件状态
  • access: 检查文件访问权限
  • chown/chmod: 修改文件所有者和权限

9. 示例代码

示例1:基础用法 – rename 函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

// 创建测试文件
int create_test_file(const char *filename, const char *content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件内容
void show_file_content(const char *filename) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        printf("无法打开文件: %s\n", filename);
        return;
    }
    
    char buffer[256];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("文件 %s 内容: %s", filename, buffer);
    } else {
        printf("文件 %s 为空\n", filename);
    }
    
    close(fd);
}

// 检查文件是否存在
int file_exists(const char *filename) {
    return access(filename, F_OK) == 0;
}

int main() {
    printf("=== rename 基础示例 ===\n\n");
    
    // 1. 创建测试文件
    printf("1. 创建测试文件:\n");
    if (create_test_file("old_name.txt", "这是原始文件的内容\n") == -1) {
        return 1;
    }
    
    if (create_test_file("another_file.txt", "另一个文件的内容\n") == -1) {
        unlink("old_name.txt");
        return 1;
    }
    
    // 2. 显示原始文件内容
    printf("\n2. 原始文件内容:\n");
    show_file_content("old_name.txt");
    show_file_content("another_file.txt");
    
    // 3. 使用 rename 重命名文件
    printf("\n3. 重命名文件:\n");
    if (rename("old_name.txt", "new_name.txt") == 0) {
        printf("✓ 成功将 'old_name.txt' 重命名为 'new_name.txt'\n");
        
        // 验证重命名结果
        if (file_exists("new_name.txt")) {
            printf("✓ 新文件 'new_name.txt' 存在\n");
        }
        if (!file_exists("old_name.txt")) {
            printf("✓ 旧文件 'old_name.txt' 已不存在\n");
        }
        
        // 显示重命名后的文件内容
        printf("\n重命名后文件内容:\n");
        show_file_content("new_name.txt");
    } else {
        printf("✗ 重命名失败: %s\n", strerror(errno));
    }
    
    // 4. 使用 rename 覆盖已存在的文件
    printf("\n4. 覆盖已存在的文件:\n");
    if (file_exists("another_file.txt")) {
        printf("覆盖前 'another_file.txt' 存在\n");
        
        if (rename("new_name.txt", "another_file.txt") == 0) {
            printf("✓ 成功用 'new_name.txt' 覆盖 'another_file.txt'\n");
            
            // 验证覆盖结果
            if (file_exists("another_file.txt")) {
                printf("✓ 'another_file.txt' 现在包含原始文件的内容\n");
                show_file_content("another_file.txt");
            }
            if (!file_exists("new_name.txt")) {
                printf("✓ 原来的 'new_name.txt' 已不存在\n");
            }
        } else {
            printf("✗ 覆盖失败: %s\n", strerror(errno));
        }
    }
    
    // 5. 尝试重命名不存在的文件
    printf("\n5. 重命名不存在的文件:\n");
    if (rename("nonexistent.txt", "should_fail.txt") == -1) {
        printf("✓ 正确处理不存在的文件: %s\n", strerror(errno));
    }
    
    // 6. 清理测试文件
    printf("\n6. 清理测试文件:\n");
    if (file_exists("another_file.txt")) {
        unlink("another_file.txt");
        printf("✓ 删除 another_file.txt\n");
    }
    
    printf("\n=== rename 函数特点 ===\n");
    printf("1. 原子操作: 重命名是原子的\n");
    printf("2. 同一文件系统: 只能在同一文件系统内移动\n");
    printf("3. 覆盖行为: 默认会覆盖已存在的文件\n");
    printf("4. 权限保持: 文件权限和所有者保持不变\n");
    printf("5. 简单易用: 最基础的文件重命名函数\n");
    printf("6. 限制: 不支持相对路径和高级选项\n");
    
    return 0;
}

示例2:增强功能 – renameat 函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>

// 创建目录
int create_directory(const char *dirname) {
    if (mkdir(dirname, 0755) == -1) {
        if (errno != EEXIST) {
            perror("创建目录失败");
            return -1;
        }
    }
    printf("创建目录: %s\n", dirname);
    return 0;
}

// 在目录中创建文件
int create_file_in_directory(const char *dirname, const char *filename, 
                           const char *content) {
    char full_path[512];
    snprintf(full_path, sizeof(full_path), "%s/%s", dirname, filename);
    
    int fd = open(full_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("在目录 '%s' 中创建文件: %s\n", dirname, filename);
    return 0;
}

// 列出目录内容
void list_directory_contents(const char *dirname) {
    DIR *dir = opendir(dirname);
    if (!dir) {
        printf("无法打开目录: %s\n", dirname);
        return;
    }
    
    printf("目录 '%s' 的内容:\n", dirname);
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
            printf("  %s\n", entry->d_name);
        }
    }
    closedir(dir);
    printf("\n");
}

int main() {
    printf("=== renameat 增强功能示例 ===\n\n");
    
    // 1. 创建测试目录和文件
    printf("1. 创建测试环境:\n");
    if (create_directory("source_dir") == -1) {
        return 1;
    }
    if (create_directory("target_dir") == -1) {
        rmdir("source_dir");
        return 1;
    }
    
    // 在源目录中创建文件
    if (create_file_in_directory("source_dir", "file1.txt", "源目录中的文件1\n") == -1 ||
        create_file_in_directory("source_dir", "file2.txt", "源目录中的文件2\n") == -1) {
        rmdir("source_dir");
        rmdir("target_dir");
        return 1;
    }
    
    // 在目标目录中创建文件
    if (create_file_in_directory("target_dir", "existing.txt", "目标目录中的已有文件\n") == -1) {
        // 清理并退出
        unlink("source_dir/file1.txt");
        unlink("source_dir/file2.txt");
        rmdir("source_dir");
        rmdir("target_dir");
        return 1;
    }
    
    // 2. 显示初始状态
    printf("\n2. 初始状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
    // 3. 使用 renameat 移动文件(需要打开目录)
    printf("3. 使用 renameat 移动文件:\n");
    
    int source_fd = open("source_dir", O_RDONLY);
    int target_fd = open("target_dir", O_RDONLY);
    
    if (source_fd == -1 || target_fd == -1) {
        perror("打开目录失败");
        if (source_fd != -1) close(source_fd);
        if (target_fd != -1) close(target_fd);
        goto cleanup;
    }
    
    // 移动 file1.txt 从 source_dir 到 target_dir
    if (renameat(source_fd, "file1.txt", target_fd, "moved_file1.txt") == 0) {
        printf("✓ 成功移动文件 'file1.txt' -> 'moved_file1.txt'\n");
    } else {
        printf("✗ 移动文件失败: %s\n", strerror(errno));
    }
    
    // 使用 AT_FDCWD 移动 file2.txt
    printf("\n4. 使用 AT_FDCWD 移动文件:\n");
    if (renameat(AT_FDCWD, "source_dir/file2.txt", AT_FDCWD, "target_dir/moved_file2.txt") == 0) {
        printf("✓ 成功移动文件 'source_dir/file2.txt' -> 'target_dir/moved_file2.txt'\n");
    } else {
        printf("✗ 移动文件失败: %s\n", strerror(errno));
    }
    
    close(source_fd);
    close(target_fd);
    
    // 5. 显示移动后的状态
    printf("\n5. 移动后状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
    // 6. 覆盖已存在的文件
    printf("\n6. 覆盖已存在的文件:\n");
    printf("尝试用 'source_dir/file1.txt' 覆盖 'target_dir/existing.txt'\n");
    
    source_fd = open("source_dir", O_RDONLY);
    target_fd = open("target_dir", O_RDONLY);
    
    if (source_fd != -1 && target_fd != -1) {
        if (renameat(source_fd, "file1.txt", target_fd, "existing.txt") == 0) {
            printf("✓ 成功覆盖文件\n");
        } else {
            printf("✗ 覆盖失败: %s\n", strerror(errno));
        }
        close(source_fd);
        close(target_fd);
    }
    
    // 7. 显示最终状态
    printf("\n7. 最终状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
cleanup:
    // 8. 清理测试文件
    printf("\n8. 清理测试环境:\n");
    
    // 删除 target_dir 中的文件
    unlink("target_dir/moved_file1.txt");
    unlink("target_dir/moved_file2.txt");
    unlink("target_dir/existing.txt");
    
    // 删除 source_dir 中的文件
    unlink("source_dir/file1.txt");
    unlink("source_dir/file2.txt");
    
    // 删除目录
    rmdir("source_dir");
    rmdir("target_dir");
    
    printf("✓ 清理完成\n");
    
    printf("\n=== renameat 函数特点 ===\n");
    printf("1. 相对路径支持: 支持相对于目录描述符的路径\n");
    printf("2. 目录描述符: 可以使用已打开的目录文件描述符\n");
    printf("3. AT_FDCWD: 可以使用当前工作目录\n");
    printf("4. 原子操作: 重命名仍然是原子操作\n");
    printf("5. 跨目录移动: 可以在不同目录间移动文件\n");
    printf("6. 权限保持: 文件权限和所有者保持不变\n");
    printf("\n");
    printf("优势:\n");
    printf("1. 更灵活的路径处理\n");
    printf("2. 支持相对路径操作\n");
    printf("3. 可以避免重复打开目录\n");
    printf("4. 更好的错误处理\n");
    
    return 0;
}

示例3:高级功能 – renameat2 函数

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

// 检查系统是否支持 renameat2
int check_renameat2_support() {
    // 尝试一个简单的 renameat2 调用
    int result = renameat2(AT_FDCWD, "test", AT_FDCWD, "test", 0);
    if (result == -1 && errno == ENOSYS) {
        return 0;  // 不支持
    }
    return 1;  // 支持(或者其他错误)
}

// 创建测试文件
int create_test_file_advanced(const char *filename, const char *content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件详细信息
void show_file_details(const char *filename) {
    struct stat st;
    if (stat(filename, &st) == 0) {
        printf("文件: %s\n", filename);
        printf("  Inode: %ld\n", (long)st.st_ino);
        printf("  大小: %ld 字节\n", (long)st.st_size);
        printf("  权限: %o\n", st.st_mode & 0777);
        printf("  链接数: %ld\n", (long)st.st_nlink);
        printf("  修改时间: %s", ctime(&st.st_mtime));
    } else {
        printf("文件不存在: %s\n", filename);
    }
}

int main() {
    printf("=== renameat2 高级功能示例 ===\n\n");
    
    // 检查系统支持
    printf("1. 检查系统支持:\n");
    if (!check_renameat2_support()) {
        printf("⚠ 系统不支持 renameat2,使用模拟功能\n");
        printf("  Linux 内核 3.15+ 才支持 renameat2\n\n");
    } else {
        printf("✓ 系统支持 renameat2\n\n");
    }
    
    // 2. 创建测试文件
    printf("2. 创建测试文件:\n");
    if (create_test_file_advanced("file_a.txt", "文件 A 的内容\n") == -1 ||
        create_test_file_advanced("file_b.txt", "文件 B 的内容\n") == -1 ||
        create_test_file_advanced("protected_file.txt", "受保护的文件内容\n") == -1) {
        return 1;
    }
    
    // 3. 显示初始文件信息
    printf("\n3. 初始文件信息:\n");
    show_file_details("file_a.txt");
    show_file_details("file_b.txt");
    show_file_details("protected_file.txt");
    
    // 4. 演示 RENAME_NOREPLACE 标志(不覆盖已存在的文件)
    printf("\n4. 演示 RENAME_NOREPLACE (不覆盖):\n");
    printf("尝试将 'file_a.txt' 重命名为 'protected_file.txt' (不覆盖):\n");
    
    // 在支持的系统上使用 renameat2
    int result = renameat2(AT_FDCWD, "file_a.txt", AT_FDCWD, "protected_file.txt", 
                          RENAME_NOREPLACE);
    if (result == -1) {
        if (errno == EEXIST) {
            printf("✓ 正确阻止了覆盖操作: 文件已存在\n");
        } else if (errno == ENOSYS) {
            printf("⚠ 系统不支持 renameat2,使用模拟方式\n");
            // 检查文件是否存在,如果不存在则重命名
            if (access("protected_file.txt", F_OK) == 0) {
                printf("  文件已存在,阻止覆盖\n");
            } else {
                if (rename("file_a.txt", "protected_file.txt") == 0) {
                    printf("  成功重命名\n");
                } else {
                    printf("  重命名失败: %s\n", strerror(errno));
                }
            }
        } else {
            printf("✗ 操作失败: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 成功重命名,没有覆盖文件\n");
    }
    
    // 5. 演示普通的重命名(会覆盖)
    printf("\n5. 演示普通重命名 (会覆盖):\n");
    printf("将 'file_a.txt' 重命名为 'new_file_a.txt':\n");
    
    if (rename("file_a.txt", "new_file_a.txt") == 0) {
        printf("✓ 成功重命名\n");
        show_file_details("new_file_a.txt");
    } else {
        printf("✗ 重命名失败: %s\n", strerror(errno));
    }
    
    // 6. 演示文件交换功能(RENAME_EXCHANGE)
    printf("\n6. 演示文件交换功能:\n");
    printf("交换 'new_file_a.txt' 和 'file_b.txt' 的内容:\n");
    
    // 显示交换前的内容
    printf("交换前:\n");
    printf("  new_file_a.txt 内容: ");
    {
        int fd = open("new_file_a.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    printf("  file_b.txt 内容: ");
    {
        int fd = open("file_b.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    // 尝试使用 renameat2 交换文件
    result = renameat2(AT_FDCWD, "new_file_a.txt", AT_FDCWD, "file_b.txt", 
                      RENAME_EXCHANGE);
    if (result == -1) {
        if (errno == ENOSYS) {
            printf("⚠ 系统不支持 RENAME_EXCHANGE,使用传统方式模拟\n");
            printf("  传统方式: 需要临时文件进行三次重命名\n");
            
            // 创建临时文件名
            if (rename("new_file_a.txt", "temp_swap_file.txt") == 0) {
                if (rename("file_b.txt", "new_file_a.txt") == 0) {
                    if (rename("temp_swap_file.txt", "file_b.txt") == 0) {
                        printf("✓ 成功使用传统方式交换文件\n");
                    } else {
                        printf("✗ 第三步交换失败\n");
                        // 恢复第一步
                        rename("new_file_a.txt", "file_b.txt");
                        rename("temp_swap_file.txt", "new_file_a.txt");
                    }
                } else {
                    printf("✗ 第二步交换失败\n");
                    // 恢复第一步
                    rename("temp_swap_file.txt", "new_file_a.txt");
                }
            } else {
                printf("✗ 第一步交换失败\n");
            }
        } else {
            printf("✗ 交换失败: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 成功交换文件内容\n");
    }
    
    // 显示交换后的内容
    printf("交换后:\n");
    printf("  new_file_a.txt 内容: ");
    {
        int fd = open("new_file_a.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    printf("  file_b.txt 内容: ");
    {
        int fd = open("file_b.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    // 7. 跨文件系统移动演示
    printf("\n7. 跨文件系统移动演示:\n");
    printf("尝试跨文件系统移动文件 (通常会失败):\n");
    
    // 这个操作通常会失败,因为 rename 不支持跨文件系统
    result = rename("/etc/passwd", "./passwd_copy");
    if (result == -1) {
        if (errno == EXDEV) {
            printf("✓ 正确检测到跨文件系统移动: %s\n", strerror(errno));
            printf("  说明: rename 不支持跨文件系统移动\n");
            printf("  解决方案: 使用 copy + delete 或 mv 命令\n");
        } else {
            printf("✗ 其他错误: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 跨文件系统移动成功 (罕见情况)\n");
        // 如果成功,需要删除副本
        unlink("./passwd_copy");
    }
    
    // 8. 清理测试文件
    printf("\n8. 清理测试文件:\n");
    const char *files_to_delete[] = {
        "new_file_a.txt", "file_b.txt", "protected_file.txt", 
        "temp_swap_file.txt", NULL
    };
    
    for (int i = 0; files_to_delete[i]; i++) {
        if (access(files_to_delete[i], F_OK) == 0) {
            if (unlink(files_to_delete[i]) == 0) {
                printf("✓ 删除文件: %s\n", files_to_delete[i]);
            } else {
                printf("✗ 删除文件失败: %s\n", strerror(errno));
            }
        }
    }
    
    printf("\n=== 三种重命名函数对比 ===\n");
    printf("┌─────────────┬─────────────┬─────────────┬─────────────┐\n");
    printf("│ 函数        │ 基础功能    │ 相对路径    │ 高级选项    │\n");
    printf("├─────────────┼─────────────┼─────────────┼─────────────┤\n");
    printf("│ rename      │ ✓           │ ✗           │ ✗           │\n");
    printf("│ renameat    │ ✓           │ ✓           │ ✗           │\n");
    printf("│ renameat2   │ ✓           │ ✓           │ ✓           │\n");
    printf("└─────────────┴─────────────┴─────────────┴─────────────┘\n");
    printf("\n");
    
    printf("功能对比:\n");
    printf("1. rename:\n");
    printf("   - 最简单的重命名操作\n");
    printf("   - 只支持绝对路径\n");
    printf("   - 会自动覆盖已存在的文件\n");
    printf("   - 最广泛的系统支持\n");
    printf("\n");
    
    printf("2. renameat:\n");
    printf("   - 支持相对路径操作\n");
    printf("   - 可以使用目录文件描述符\n");
    printf("   - 支持 AT_FDCWD 常量\n");
    printf("   - 需要较新的系统支持\n");
    printf("\n");
    
    printf("3. renameat2:\n");
    printf("   - 支持所有 renameat 功能\n");
    printf("   - 添加了高级控制选项\n");
    printf("   - 支持原子文件交换\n");
    printf("   - 支持不覆盖选项\n");
    printf("   - 需要最新的内核支持\n");
    printf("\n");
    
    printf("=== 使用建议 ===\n");
    printf("选择原则:\n");
    printf("1. 简单重命名: 使用 rename\n");
    printf("2. 相对路径操作: 使用 renameat\n");
    printf("3. 高级控制需求: 使用 renameat2\n");
    printf("4. 跨平台兼容: 使用 rename\n");
    printf("5. 安全操作: 使用 renameat2 + RENAME_NOREPLACE\n");
    printf("\n");
    
    printf("安全最佳实践:\n");
    printf("1. 始终检查返回值和 errno\n");
    printf("2. 使用 RENAME_NOREPLACE 避免意外覆盖\n");
    printf("3. 在关键操作前备份重要文件\n");
    printf("4. 检查文件权限和磁盘空间\n");
    printf("5. 使用事务性操作确保数据完整性\n");
    
    return 0;
}

示例4:完整的文件管理工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/stat.h>
#include <libgen.h>

// 配置结构体
struct rename_config {
    int interactive;     // 交互模式
    int force;          // 强制模式
    int backup;         // 备份模式
    int verbose;        // 详细输出
    int no_replace;     // 不替换模式
    int exchange;       // 交换模式
    char *suffix;       // 备份后缀
};

// 安全的文件重命名函数
int safe_rename(const char *oldpath, const char *newpath, 
               const struct rename_config *config) {
    // 验证参数
    if (!oldpath || !newpath) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查源文件是否存在
    if (access(oldpath, F_OK) != 0) {
        if (errno == ENOENT) {
            fprintf(stderr, "错误: 源文件不存在 '%s'\n", oldpath);
        } else {
            perror("检查源文件失败");
        }
        return -1;
    }
    
    // 检查目标文件是否存在
    if (access(newpath, F_OK) == 0) {
        if (config->interactive) {
            printf("目标文件 '%s' 已存在,是否覆盖? (y/N) ", newpath);
            char response[10];
            if (fgets(response, sizeof(response), stdin)) {
                if (response[0] != 'y' && response[0] != 'Y') {
                    printf("取消操作\n");
                    return 0;
                }
            }
        } else if (config->no_replace) {
            fprintf(stderr, "错误: 目标文件已存在 '%s'\n", newpath);
            errno = EEXIST;
            return -1;
        }
        
        // 创建备份
        if (config->backup) {
            char backup_name[1024];
            if (config->suffix) {
                snprintf(backup_name, sizeof(backup_name), "%s%s", newpath, config->suffix);
            } else {
                snprintf(backup_name, sizeof(backup_name), "%s~", newpath);
            }
            
            if (config->verbose) {
                printf("创建备份: %s -> %s\n", newpath, backup_name);
            }
            
            if (rename(newpath, backup_name) != 0) {
                perror("创建备份失败");
                return -1;
            }
        }
    }
    
    // 执行重命名
    int result;
    if (config->no_replace) {
        // 使用 renameat2 避免覆盖
        #ifdef RENAME_NOREPLACE
        result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_NOREPLACE);
        #else
        // 如果不支持,手动检查
        if (access(newpath, F_OK) == 0) {
            errno = EEXIST;
            result = -1;
        } else {
            result = rename(oldpath, newpath);
        }
        #endif
    } else if (config->exchange) {
        // 使用 renameat2 交换文件
        #ifdef RENAME_EXCHANGE
        result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE);
        #else
        // 如果不支持,使用传统方式
        char temp_name[1024];
        snprintf(temp_name, sizeof(temp_name), "%s.temp_swap_%d", oldpath, getpid());
        
        result = rename(oldpath, temp_name);
        if (result == 0) {
            result = rename(newpath, oldpath);
            if (result == 0) {
                result = rename(temp_name, newpath);
                if (result != 0) {
                    // 如果第三步失败,恢复第二步
                    rename(oldpath, newpath);
                    rename(temp_name, oldpath);
                }
            } else {
                // 如果第二步失败,恢复第一步
                rename(temp_name, oldpath);
            }
        }
        #endif
    } else {
        result = rename(oldpath, newpath);
    }
    
    if (result == 0) {
        if (config->verbose) {
            if (config->exchange) {
                printf("✓ 成功交换文件 '%s' 和 '%s'\n", oldpath, newpath);
            } else {
                printf("✓ 成功重命名 '%s' -> '%s'\n", oldpath, newpath);
            }
        }
    } else {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "错误: 权限不足\n");
                break;
            case EEXIST:
                fprintf(stderr, "错误: 目标文件已存在\n");
                break;
            case ENOENT:
                fprintf(stderr, "错误: 文件不存在\n");
                break;
            case EXDEV:
                fprintf(stderr, "错误: 不支持跨文件系统移动\n");
                break;
            case EISDIR:
                fprintf(stderr, "错误: 目录操作冲突\n");
                break;
            case ENOTEMPTY:
                fprintf(stderr, "错误: 目录非空\n");
                break;
            default:
                fprintf(stderr, "错误: %s\n", strerror(errno));
                break;
        }
    }
    
    return result;
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项] 源文件 目标文件\n", program_name);
    printf("\n选项:\n");
    printf("  -i, --interactive      交互模式(覆盖前询问)\n");
    printf("  -f, --force             强制模式(忽略错误)\n");
    printf("  -b, --backup            创建备份\n");
    printf("  -S, --suffix=SUFFIX     备份文件后缀\n");
    printf("  -v, --verbose           详细输出\n");
    printf("  -n, --no-replace        不替换已存在的文件\n");
    printf("  -x, --exchange          交换两个文件\n");
    printf("  -h, --help              显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s old.txt new.txt              # 重命名文件\n", program_name);
    printf("  %s -i old.txt existing.txt       # 交互式重命名\n", program_name);
    printf("  %s -b old.txt new.txt            # 重命名并备份\n", program_name);
    printf("  %s -n old.txt existing.txt       # 不覆盖已存在文件\n", program_name);
    printf("  %s -x file1.txt file2.txt       # 交换两个文件\n", program_name);
    printf("  %s -v old.txt new.txt           # 详细输出\n", program_name);
}

int main(int argc, char *argv[]) {
    struct rename_config config = {
        .interactive = 0,
        .force = 0,
        .backup = 0,
        .verbose = 0,
        .no_replace = 0,
        .exchange = 0,
        .suffix = NULL
    };
    
    printf("=== 文件重命名管理工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"interactive", no_argument,       0, 'i'},
        {"force",       no_argument,       0, 'f'},
        {"backup",      no_argument,       0, 'b'},
        {"suffix",      required_argument, 0, 'S'},
        {"verbose",     no_argument,       0, 'v'},
        {"no-replace",  no_argument,       0, 'n'},
        {"exchange",    no_argument,       0, 'x'},
        {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "ifbS:vnxh", long_options, NULL)) != -1) {
        switch (opt) {
            case 'i':
                config.interactive = 1;
                break;
            case 'f':
                config.force = 1;
                break;
            case 'b':
                config.backup = 1;
                break;
            case 'S':
                config.suffix = optarg;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 'n':
                config.no_replace = 1;
                break;
            case 'x':
                config.exchange = 1;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 检查参数数量
    if (optind + 1 >= argc) {
        fprintf(stderr, "错误: 需要指定源文件和目标文件\n");
        fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
        return 1;
    }
    
    const char *oldpath = argv[optind];
    const char *newpath = argv[optind + 1];
    
    // 显示配置信息
    if (config.verbose) {
        printf("操作配置:\n");
        printf("  源文件: %s\n", oldpath);
        printf("  目标文件: %s\n", newpath);
        printf("  交互模式: %s\n", config.interactive ? "是" : "否");
        printf("  强制模式: %s\n", config.force ? "是" : "否");
        printf("  备份模式: %s\n", config.backup ? "是" : "否");
        printf("  不替换: %s\n", config.no_replace ? "是" : "否");
        printf("  文件交换: %s\n", config.exchange ? "是" : "否");
        printf("  详细输出: %s\n", config.verbose ? "是" : "否");
        if (config.backup && config.suffix) {
            printf("  备份后缀: %s\n", config.suffix);
        }
        printf("\n");
    }
    
    // 执行重命名操作
    int result = safe_rename(oldpath, newpath, &config);
    
    // 显示结果
    if (result == 0) {
        printf("操作完成\n");
    } else if (!config.force) {
        return 1;
    }
    
    // 显示使用建议
    printf("\n=== 重命名最佳实践 ===\n");
    printf("安全建议:\n");
    printf("1. 重要文件操作前创建备份\n");
    printf("2. 使用 -i 选项避免意外覆盖\n");
    printf("3. 使用 -n 选项保护已存在的文件\n");
    printf("4. 检查磁盘空间和权限\n");
    printf("5. 对于关键操作使用事务性处理\n");
    printf("\n");
    
    printf("性能优化:\n");
    printf("1. 批量操作时合并系统调用\n");
    printf("2. 避免跨文件系统移动\n");
    printf("3. 使用相对路径减少路径解析\n");
    printf("4. 合理使用目录文件描述符\n");
    printf("5. 避免频繁的小文件操作\n");
    printf("\n");
    
    printf("错误处理:\n");
    printf("1. 始终检查返回值和 errno\n");
    printf("2. 区分不同类型的错误\n");
    printf("3. 实现适当的重试机制\n");
    printf("4. 提供清晰的错误信息\n");
    printf("5. 保持操作的原子性\n");
    
    return (result == 0 || config.force) ? 0 : 1;
}

编译和运行说明

# 编译示例程序
gcc -o rename_example1 example1.c
gcc -o rename_example2 example2.c
gcc -o rename_example3 example3.c
gcc -o rename_example4 example4.c

# 运行示例
./rename_example1
./rename_example2
./rename_example3
./rename_example4 --help
./rename_example4 -v old.txt new.txt
./rename_example4 -i -b old.txt existing.txt

系统要求检查

# 检查内核版本(renameat2 需要 3.15+)
uname -r

# 检查 glibc 版本
ldd --version

# 检查系统调用支持
grep -w rename /usr/include/asm/unistd_64.h

# 检查 renameat2 支持
grep -w renameat2 /usr/include/asm/unistd_64.h

# 查看文件系统类型
df -T .

重要注意事项

  1. 原子性: 重命名操作是原子的
  2. 权限要求: 需要对目录有写权限
  3. 跨文件系统: 不支持跨文件系统移动
  4. 错误处理: 始终检查返回值和 errno
  5. 符号链接: 不会跟随符号链接
  6. 目录操作: 可以重命名目录

实际应用场景

  1. 文件管理: 日常文件重命名操作
  2. 版本控制: 文件版本管理
  3. 备份系统: 自动备份和版本控制
  4. 日志轮转: 日志文件重命名和轮转
  5. 临时文件: 临时文件重命名为正式文件
  6. 原子更新: 原子性的文件更新操作

最佳实践

// 安全的重命名函数
int secure_rename(const char *oldpath, const char *newpath) {
    // 验证参数
    if (!oldpath || !newpath) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查源文件
    if (access(oldpath, F_OK) != 0) {
        return -1;  // errno 已设置
    }
    
    // 检查目标文件
    if (access(newpath, F_OK) == 0) {
        printf("警告: 目标文件已存在 '%s'\n", newpath);
    }
    
    // 执行重命名
    int result = rename(oldpath, newpath);
    if (result == 0) {
        printf("成功重命名 '%s' -> '%s'\n", oldpath, newpath);
    } else {
        fprintf(stderr, "重命名失败 '%s' -> '%s': %s\n", 
                oldpath, newpath, strerror(errno));
    }
    
    return result;
}

// 原子文件更新
int atomic_file_update(const char *temp_file, const char *final_file) {
    // 先创建临时文件并写入数据
    // ...
    
    // 原子性地重命名为最终文件
    if (rename(temp_file, final_file) == 0) {
        return 0;  // 成功
    } else {
        // 清理临时文件
        unlink(temp_file);
        return -1;
    }
}

// 批量重命名
int batch_rename(const char **old_paths, const char **new_paths, int count) {
    int success_count = 0;
    int failed_count = 0;
    
    for (int i = 0; i < count; i++) {
        if (rename(old_paths[i], new_paths[i]) == 0) {
            printf("✓ 重命名: %s -> %s\n", old_paths[i], new_paths[i]);
            success_count++;
        } else {
            fprintf(stderr, "✗ 重命名失败: %s -> %s: %s\n", 
                    old_paths[i], new_paths[i], strerror(errno));
            failed_count++;
        }
    }
    
    printf("批量重命名完成: 成功 %d, 失败 %d\n", success_count, failed_count);
    return failed_count;
}

这些示例展示了 renamerenameat 和 renameat2 函数的各种使用方法,从基础的文件重命名到完整的文件管理工具,帮助你全面掌握 Linux 系统中的文件重命名机制。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注