20. fchdir - 通过文件描述符改变当前工作目录 見出しへのリンク

函数介绍 見出しへのリンク

fchdir系统调用用于通过已打开的目录文件描述符来改变进程的当前工作目录。相比chdir,它避免了路径名解析,更加高效和安全。

函数原型 見出しへのリンク

#include <unistd.h>

int fchdir(int fd);

功能 見出しへのリンク

通过目录文件描述符改变进程当前工作目录。

参数 見出しへのリンク

  • int fd: 目录文件描述符(通过open()opendir()获得)

返回值 見出しへのリンク

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效或不是目录
    • EACCES: 权限不足
    • EIO: I/O错误
    • ENOMEM: 内存不足

相似函数 見出しへのリンク

  • chdir(): 通过路径名改变当前工作目录
  • getcwd(): 获取当前工作目录

示例代码 見出しへのリンク

#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>
#include <limits.h>
#include <dirent.h>

int main() {
    char buffer[PATH_MAX];
    char original_dir[PATH_MAX];
    int dir_fd;
    
    printf("=== Fchdir函数示例 ===\n");
    
    // 保存原始目录
    if (getcwd(original_dir, sizeof(original_dir)) == NULL) {
        perror("获取原始目录失败");
        exit(EXIT_FAILURE);
    }
    printf("原始工作目录: %s\n", original_dir);
    
    // 示例1: 基本的fchdir操作
    printf("\n示例1: 基本的fchdir操作\n");
    
    // 打开根目录
    dir_fd = open("/", O_RDONLY);
    if (dir_fd == -1) {
        perror("打开根目录失败");
    } else {
        printf("成功打开根目录,文件描述符: %d\n", dir_fd);
        
        // 使用fchdir切换到根目录
        if (fchdir(dir_fd) == -1) {
            perror("fchdir到根目录失败");
        } else {
            printf("成功使用fchdir切换到根目录\n");
            if (getcwd(buffer, sizeof(buffer)) != NULL) {
                printf("当前目录: %s\n", buffer);
            }
        }
        
        close(dir_fd);
    }
    
    // 返回原始目录
    if (chdir(original_dir) == -1) {
        perror("返回原始目录失败");
    } else {
        printf("成功返回原始目录\n");
    }
    
    // 示例2: 创建测试环境
    printf("\n示例2: 创建测试环境\n");
    
    // 创建测试目录结构
    const char *test_base = "test_fchdir_base";
    const char *subdir1 = "test_fchdir_base/subdir1";
    const char *subdir2 = "test_fchdir_base/subdir2";
    
    // 创建目录
    if (mkdir(test_base, 0755) == -1 && errno != EEXIST) {
        perror("创建基础目录失败");
    } else {
        printf("创建基础目录: %s\n", test_base);
    }
    
    if (mkdir(subdir1, 0755) == -1 && errno != EEXIST) {
        perror("创建子目录1失败");
    } else {
        printf("创建子目录1: %s\n", subdir1);
    }
    
    if (mkdir(subdir2, 0755) == -1 && errno != EEXIST) {
        perror("创建子目录2失败");
    } else {
        printf("创建子目录2: %s\n", subdir2);
    }
    
    // 在目录中创建测试文件
    char test_file_path[PATH_MAX * 2];
    snprintf(test_file_path, sizeof(test_file_path), "%s/test_file.txt", test_base);
    int fd = open(test_file_path, O_CREAT | O_WRONLY, 0644);
    if (fd != -1) {
        const char *content = "Test file for fchdir demonstration";
        write(fd, content, strlen(content));
        close(fd);
        printf("创建测试文件: %s\n", test_file_path);
    }
    
    // 示例3: fchdir与chdir对比
    printf("\n示例3: fchdir与chdir对比\n");
    
    // 使用chdir切换
    clock_t chdir_start = clock();
    for (int i = 0; i < 1000; i++) {
        chdir(test_base);
        chdir(original_dir);
    }
    clock_t chdir_time = clock() - chdir_start;
    
    // 使用fchdir切换(需要先打开目录)
    int test_dir_fd = open(test_base, O_RDONLY);
    if (test_dir_fd != -1) {
        clock_t fchdir_start = clock();
        for (int i = 0; i < 1000; i++) {
            fchdir(test_dir_fd);
            fchdir(dir_fd);  // dir_fd是之前打开的根目录
        }
        clock_t fchdir_time = clock() - fchdir_start;
        
        printf("1000次chdir操作耗时: %f 秒\n", 
               ((double)chdir_time) / CLOCKS_PER_SEC);
        printf("1000次fchdir操作耗时: %f 秒\n", 
               ((double)fchdir_time) / CLOCKS_PER_SEC);
        printf("fchdir通常比chdir更快(避免路径解析)\n");
        
        close(test_dir_fd);
    }
    
    // 示例4: 使用目录文件描述符的优势
    printf("\n示例4: 使用目录文件描述符的优势\n");
    
    // 打开测试目录
    dir_fd = open(test_base, O_RDONLY);
    if (dir_fd == -1) {
        perror("打开测试目录失败");
    } else {
        printf("打开测试目录,文件描述符: %d\n", dir_fd);
        
        // 使用fchdir切换
        if (fchdir(dir_fd) == -1) {
            perror("fchdir失败");
        } else {
            printf("使用fchdir切换成功\n");
            if (getcwd(buffer, sizeof(buffer)) != NULL) {
                printf("当前目录: %s\n", buffer);
            }
            
            // 在当前目录创建文件
            fd = open("file_via_fchdir.txt", O_CREAT | O_WRONLY, 0644);
            if (fd != -1) {
                const char *file_content = "File created after fchdir";
                write(fd, file_content, strlen(file_content));
                close(fd);
                printf("在切换后的目录创建文件\n");
            }
        }
        
        // 即使目录被重命名或删除,文件描述符仍然有效
        printf("演示文件描述符的持久性:\n");
        printf("  即使目录被重命名,fchdir仍然可以工作\n");
        
        close(dir_fd);
    }
    
    // 返回原始目录
    if (chdir(original_dir) == -1) {
        perror("返回原始目录失败");
    }
    
    // 示例5: 使用opendir和dirfd
    printf("\n示例5: 使用opendir和dirfd\n");
    
    // 使用opendir打开目录
    DIR *dir = opendir(test_base);
    if (dir == NULL) {
        perror("opendir失败");
    } else {
        printf("使用opendir打开目录成功\n");
        
        // 获取目录文件描述符
        int dirfd_from_opendir = dirfd(dir);
        printf("目录文件描述符: %d\n", dirfd_from_opendir);
        
        // 使用fchdir
        if (fchdir(dirfd_from_opendir) == -1) {
            perror("使用dirfd的fchdir失败");
        } else {
            printf("使用dirfd的fchdir成功\n");
            if (getcwd(buffer, sizeof(buffer)) != NULL) {
                printf("当前目录: %s\n", buffer);
            }
        }
        
        closedir(dir);
    }
    
    // 返回原始目录
    if (chdir(original_dir) == -1) {
        perror("返回原始目录失败");
    }
    
    // 示例6: 安全的目录切换模式
    printf("\n示例6: 安全的目录切换模式\n");
    
    // 保存当前目录的文件描述符
    int current_dir_fd = open(".", O_RDONLY);
    if (current_dir_fd == -1) {
        perror("保存当前目录失败");
    } else {
        printf("保存当前目录文件描述符: %d\n", current_dir_fd);
        
        // 切换到测试目录
        dir_fd = open(test_base, O_RDONLY);
        if (dir_fd != -1) {
            if (fchdir(dir_fd) == -1) {
                perror("切换到测试目录失败");
            } else {
                printf("切换到测试目录\n");
                if (getcwd(buffer, sizeof(buffer)) != NULL) {
                    printf("当前目录: %s\n", buffer);
                }
                
                // 执行一些操作
                system("ls -la");
                
                // 安全返回(使用保存的文件描述符)
                if (fchdir(current_dir_fd) == -1) {
                    perror("安全返回失败");
                } else {
                    printf("安全返回原始目录\n");
                    if (getcwd(buffer, sizeof(buffer)) != NULL) {
                        printf("当前目录: %s\n", buffer);
                    }
                }
            }
            close(dir_fd);
        }
        
        close(current_dir_fd);
    }
    
    // 示例7: 错误处理演示
    printf("\n示例7: 错误处理演示\n");
    
    // 尝试对无效文件描述符使用fchdir
    if (fchdir(999) == -1) {
        printf("对无效文件描述符使用fchdir: %s\n", strerror(errno));
    }
    
    // 尝试对普通文件使用fchdir
    int regular_fd = open("regular_file.txt", O_CREAT | O_WRONLY, 0644);
    if (regular_fd != -1) {
        write(regular_fd, "test", 4);
        if (fchdir(regular_fd) == -1) {
            printf("对普通文件使用fchdir: %s\n", strerror(errno));
        }
        close(regular_fd);
        unlink("regular_file.txt");
    }
    
    // 尝试对已关闭的文件描述符使用fchdir
    dir_fd = open(test_base, O_RDONLY);
    if (dir_fd != -1) {
        close(dir_fd);
        if (fchdir(dir_fd) == -1) {
            printf("对已关闭的文件描述符使用fchdir: %s\n", strerror(errno));
        }
    }
    
    // 示例8: 实际应用场景
    printf("\n示例8: 实际应用场景\n");
    
    // 场景1: 多线程环境中的目录操作
    printf("场景1: 多线程安全的目录操作\n");
    printf("说明: 在多线程环境中,使用文件描述符比路径名更安全\n");
    printf("因为文件描述符不受其他线程改变当前目录的影响\n");
    
    // 场景2: 高频目录切换优化
    printf("场景2: 高频目录切换优化\n");
    
    // 预打开常用目录
    struct {
        const char *name;
        int fd;
    } common_dirs[] = {
        {"根目录", -1},
        {"测试目录", -1},
        {"用户主目录", -1}
    };
    
    // 打开常用目录
    common_dirs[0].fd = open("/", O_RDONLY);
    common_dirs[1].fd = open(test_base, O_RDONLY);
    common_dirs[2].fd = open(getenv("HOME") ? getenv("HOME") : "/", O_RDONLY);
    
    printf("预打开常用目录:\n");
    for (int i = 0; i < 3; i++) {
        if (common_dirs[i].fd != -1) {
            printf("  %s: fd=%d\n", common_dirs[i].name, common_dirs[i].fd);
        }
    }
    
    // 快速切换演示
    printf("快速目录切换演示:\n");
    for (int i = 0; i < 3; i++) {
        if (common_dirs[i].fd != -1) {
            if (fchdir(common_dirs[i].fd) == 0) {
                if (getcwd(buffer, sizeof(buffer)) != NULL) {
                    printf("  切换到%s: %s\n", common_dirs[i].name, buffer);
                }
            }
        }
    }
    
    // 清理预打开的目录
    for (int i = 0; i < 3; i++) {
        if (common_dirs[i].fd != -1) {
            close(common_dirs[i].fd);
        }
    }
    
    // 场景3: 临时目录操作
    printf("场景3: 临时目录操作\n");
    char temp_dir[] = "/tmp/fchdir_test_XXXXXX";
    if (mkdtemp(temp_dir) != NULL) {
        printf("创建临时目录: %s\n", temp_dir);
        
        // 打开临时目录
        int temp_fd = open(temp_dir, O_RDONLY);
        if (temp_fd != -1) {
            printf("打开临时目录,fd=%d\n", temp_fd);
            
            // 切换到临时目录
            if (fchdir(temp_fd) == 0) {
                printf("切换到临时目录\n");
                
                // 在临时目录中创建工作
                system("echo 'Work in temporary directory' > work.txt");
                system("ls -la");
                
                // 完成后返回
                if (chdir(original_dir) == 0) {
                    printf("返回原始目录\n");
                }
            }
            
            close(temp_fd);
        }
        
        // 清理临时目录
        char cleanup_cmd[PATH_MAX + 50];
        snprintf(cleanup_cmd, sizeof(cleanup_cmd), "rm -rf %s", temp_dir);
        system(cleanup_cmd);
    }
    
    // 场景4: 权限受限环境
    printf("场景4: 权限受限环境\n");
    printf("说明: fchdir可以在某些权限受限的情况下工作\n");
    printf("当进程有目录的文件描述符但没有路径遍历权限时\n");
    
    // 示例9: 性能测试
    printf("\n示例9: 性能测试\n");
    
    // 测试fchdir性能
    dir_fd = open(test_base, O_RDONLY);
    if (dir_fd != -1) {
        current_dir_fd = open(".", O_RDONLY);
        if (current_dir_fd != -1) {
            const int iterations = 10000;
            
            clock_t start = clock();
            for (int i = 0; i < iterations; i++) {
                fchdir(dir_fd);
                fchdir(current_dir_fd);
            }
            clock_t fchdir_total = clock() - start;
            
            printf("%d次fchdir操作耗时: %f 秒\n", 
                   iterations * 2, ((double)fchdir_total) / CLOCKS_PER_SEC);
            printf("平均每次fchdir耗时: %f 微秒\n", 
                   (((double)fchdir_total) / CLOCKS_PER_SEC * 1000000) / (iterations * 2));
            
            close(current_dir_fd);
        }
        close(dir_fd);
    }
    
    // 示例10: 与符号链接的交互
    printf("\n示例10: 与符号链接的交互\n");
    
    // 创建符号链接目录
    const char *symlink_name = "test_fchdir_base/symlink_to_subdir1";
    if (symlink("subdir1", symlink_name) == -1 && errno != EEXIST) {
        perror("创建符号链接失败");
    } else {
        printf("创建符号链接: %s -> subdir1\n", symlink_name);
        
        // 通过符号链接打开目录
        int symlink_fd = open(symlink_name, O_RDONLY);
        if (symlink_fd != -1) {
            printf("通过符号链接打开目录,fd=%d\n", symlink_fd);
            
            // 使用fchdir(跟随符号链接)
            if (fchdir(symlink_fd) == 0) {
                if (getcwd(buffer, sizeof(buffer)) != NULL) {
                    printf("fchdir后的目录: %s\n", buffer);
                }
            }
            
            close(symlink_fd);
        }
        
        // 返回并清理
        if (chdir(original_dir) == 0) {
            if (chdir(test_base) == 0) {
                unlink(symlink_name);
                chdir(original_dir);
            }
        }
    }
    
    // 清理测试资源
    printf("\n清理测试资源...\n");
    
    // 删除测试目录中的文件
    char file_to_delete[PATH_MAX * 2];
    snprintf(file_to_delete, sizeof(file_to_delete), "%s/%s/file_via_fchdir.txt", 
             original_dir, test_base);
    if (access(file_to_delete, F_OK) == 0) {
        unlink(file_to_delete);
    }
    
    snprintf(file_to_delete, sizeof(file_to_delete), "%s/%s/test_file.txt", 
             original_dir, test_base);
    if (access(file_to_delete, F_OK) == 0) {
        unlink(file_to_delete);
    }
    
    // 删除测试目录结构
    char subdir1_path[PATH_MAX * 2];
    snprintf(subdir1_path, sizeof(subdir1_path), "%s/%s", original_dir, subdir1);
    if (access(subdir1_path, F_OK) == 0) {
        rmdir(subdir1_path);
    }
    
    char subdir2_path[PATH_MAX * 2];
    snprintf(subdir2_path, sizeof(subdir2_path), "%s/%s", original_dir, subdir2);
    if (access(subdir2_path, F_OK) == 0) {
        rmdir(subdir2_path);
    }
    
    char base_dir_path[PATH_MAX * 2];
    snprintf(base_dir_path, sizeof(base_dir_path), "%s/%s", original_dir, test_base);
    if (access(base_dir_path, F_OK) == 0) {
        rmdir(base_dir_path);
        printf("删除测试目录结构完成\n");
    }
    
    return 0;
}