mkdirat 函数详解 Link to heading

1. 函数介绍 Link to heading

mkdirat 是 Linux 系统中用于创建目录的系统调用,它是 mkdir 函数的增强版本,支持相对路径创建。可以把 mkdirat 想象成"目录创建的导航员"——它不仅能够创建目录,还能以指定的目录作为"参考点"来创建相对路径的目录,就像导航员告诉你从当前位置如何到达目标地点一样。

与传统的 mkdir 函数相比,mkdirat 提供了更大的灵活性,特别是在处理相对路径和在特定目录上下文中创建目录时非常有用。

2. 函数原型 Link to heading

#include <sys/stat.h>
#include <fcntl.h>

int mkdirat(int dirfd, const char *pathname, mode_t mode);

3. 功能 Link to heading

mkdirat 函数用于创建一个新的目录。如果 pathname 是相对路径,它相对于 dirfd 指定的目录来解释;如果 pathname 是绝对路径,则忽略 dirfd 参数。

4. 参数 Link to heading

  • dirfd: 目录文件描述符,作为路径解析的起始点
    • 如果是有效的文件描述符,则 pathname 相对于该目录解释
    • 如果是 AT_FDCWD,则 pathname 相对于当前工作目录解释
  • pathname: 要创建的目录路径名
    • 可以是相对路径或绝对路径
  • mode: 目录的权限模式(与 mkdir 的 mode 参数相同)

5. 权限模式(mode 参数) Link to heading

权限模式使用八进制数表示,常见的值包括:

模式 说明
0755 所有者可读写执行,组和其他用户可读执行
0777 所有用户都可读写执行
0700 仅所有者可读写执行
0644 所有者可读写,组和其他用户可读

注意:实际权限还会受到 umask 的影响。

6. 返回值 Link to heading

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

7. 常见错误码 Link to heading

  • EACCES: 搜索路径组件权限不足,或父目录不允许写入
  • EEXIST: 目录已存在
  • EFAULT: pathname 指针无效
  • EINVAL: 参数无效
  • ELOOP: 符号链接循环
  • EMLINK: 父目录链接数达到上限
  • ENAMETOOLONG: 路径名过长
  • ENOENT: 路径前缀不存在
  • ENOMEM: 内存不足
  • ENOSPC: 设备空间不足
  • ENOTDIR: dirfd 不是目录或路径组件不是目录
  • EPERM: 文件系统不支持创建目录或目录挂载为只读
  • EROFS: 文件系统只读

8. 相似函数或关联函数 Link to heading

  • mkdir: 传统的创建目录函数
  • openat: 相对路径打开文件
  • creat: 创建文件
  • mknod: 创建特殊文件
  • chmod: 改变文件权限
  • chown: 改变文件所有者
  • rmdir: 删除空目录

9. 示例代码 Link to heading

示例1:基础用法 - 基本目录创建 Link to heading

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

// 显示目录信息
void show_directory_info(const char *path) {
    struct stat dir_stat;
    
    if (stat(path, &dir_stat) == -1) {
        printf("  无法获取目录信息: %s\n", strerror(errno));
        return;
    }
    
    printf("  路径: %s\n", path);
    printf("  Inode: %ld\n", (long)dir_stat.st_ino);
    printf("  权限: %o\n", dir_stat.st_mode & 0777);
    printf("  链接数: %ld\n", (long)dir_stat.st_nlink);
    printf("  所有者: UID=%d, GID=%d\n", 
           dir_stat.st_uid, dir_stat.st_gid);
    printf("\n");
}

int main() {
    printf("=== mkdirat 基础示例 ===\n\n");
    
    // 1. 使用 AT_FDCWD 创建目录(相当于 mkdir)
    printf("1. 使用 AT_FDCWD 创建目录:\n");
    if (mkdirat(AT_FDCWD, "test_dir1", 0755) == 0) {
        printf("  ✓ 成功创建目录 test_dir1\n");
        show_directory_info("test_dir1");
    } else {
        printf("  ✗ 创建目录失败: %s\n", strerror(errno));
    }
    
    // 2. 使用相对路径创建目录
    printf("2. 创建嵌套目录结构:\n");
    if (mkdirat(AT_FDCWD, "test_dir2", 0755) == 0) {
        printf("  ✓ 成功创建目录 test_dir2\n");
        
        // 在 test_dir2 中创建子目录
        if (mkdirat(AT_FDCWD, "test_dir2/subdir", 0755) == 0) {
            printf("  ✓ 成功创建子目录 test_dir2/subdir\n");
            show_directory_info("test_dir2/subdir");
        } else {
            printf("  ✗ 创建子目录失败: %s\n", strerror(errno));
        }
    } else {
        printf("  ✗ 创建目录失败: %s\n", strerror(errno));
    }
    
    // 3. 使用不同权限创建目录
    printf("3. 使用不同权限创建目录:\n");
    if (mkdirat(AT_FDCWD, "test_dir3", 0700) == 0) {
        printf("  ✓ 成功创建私有目录 test_dir3 (权限 0700)\n");
        show_directory_info("test_dir3");
    } else {
        printf("  ✗ 创建目录失败: %s\n", strerror(errno));
    }
    
    // 4. 尝试创建已存在的目录
    printf("4. 尝试创建已存在的目录:\n");
    if (mkdirat(AT_FDCWD, "test_dir1", 0755) == -1) {
        if (errno == EEXIST) {
            printf("  ✓ 正确: 目录已存在 (EEXIST)\n");
        } else {
            printf("  ✗ 意外错误: %s\n", strerror(errno));
        }
    }
    
    // 清理创建的目录
    printf("\n清理测试目录:\n");
    if (rmdir("test_dir1") == 0) {
        printf("  ✓ 删除 test_dir1\n");
    }
    if (rmdir("test_dir2/subdir") == 0 && rmdir("test_dir2") == 0) {
        printf("  ✓ 删除 test_dir2 及其子目录\n");
    }
    if (rmdir("test_dir3") == 0) {
        printf("  ✓ 删除 test_dir3\n");
    }
    
    return 0;
}

示例2:使用目录文件描述符 Link to heading

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

// 显示目录内容
void show_directory_contents(const char *path) {
    DIR *dir = opendir(path);
    if (dir == NULL) {
        printf("  无法打开目录: %s\n", strerror(errno));
        return;
    }
    
    printf("  目录 '%s' 的内容:\n", path);
    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 create_and_show_dir(int dirfd, const char *pathname, mode_t mode, const char *description) {
    printf("=== %s ===\n", description);
    
    if (mkdirat(dirfd, pathname, mode) == 0) {
        printf("  ✓ 成功创建目录: %s\n", pathname);
        
        // 构造完整路径用于显示信息
        char full_path[512];
        if (dirfd == AT_FDCWD) {
            snprintf(full_path, sizeof(full_path), "%s", pathname);
        } else {
            // 这里简化处理,实际应用中需要获取 dirfd 对应的路径
            snprintf(full_path, sizeof(full_path), "./%s", pathname);
        }
        
        // 显示目录信息
        struct stat dir_stat;
        if (fstatat(dirfd, pathname, &dir_stat, 0) == 0) {
            printf("  权限: %o\n", dir_stat.st_mode & 0777);
            printf("  Inode: %ld\n", (long)dir_stat.st_ino);
        }
        
        return 0;
    } else {
        printf("  ✗ 创建目录失败: %s\n", strerror(errno));
        return -1;
    }
}

int main() {
    printf("=== mkdirat 目录文件描述符示例 ===\n\n");
    
    // 创建基础目录结构
    printf("创建测试环境:\n");
    if (mkdir("base_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建 base_dir 失败");
        return 1;
    }
    printf("  ✓ 创建 base_dir\n");
    
    // 打开目录获取文件描述符
    int base_dirfd = open("base_dir", O_RDONLY);
    if (base_dirfd == -1) {
        perror("打开 base_dir 失败");
        rmdir("base_dir");
        return 1;
    }
    printf("  ✓ 打开 base_dir,fd=%d\n\n", base_dirfd);
    
    // 1. 使用目录文件描述符创建相对路径目录
    printf("1. 使用目录文件描述符创建相对路径目录:\n");
    if (create_and_show_dir(base_dirfd, "relative_subdir", 0755, 
                           "在 base_dir 中创建 relative_subdir") == 0) {
        show_directory_contents("base_dir");
    }
    
    // 2. 创建多层嵌套目录
    printf("2. 创建多层嵌套目录:\n");
    if (create_and_show_dir(base_dirfd, "level1", 0755, "创建第一层目录") == 0) {
        // 在 level1 中创建子目录
        int level1_fd = openat(base_dirfd, "level1", O_RDONLY);
        if (level1_fd != -1) {
            if (create_and_show_dir(level1_fd, "level2", 0755, 
                                   "在 level1 中创建 level2") == 0) {
                // 在 level2 中创建子目录
                int level2_fd = openat(level1_fd, "level2", O_RDONLY);
                if (level2_fd != -1) {
                    if (create_and_show_dir(level2_fd, "level3", 0755, 
                                           "在 level2 中创建 level3") == 0) {
                        show_directory_contents("base_dir/level1/level2");
                    }
                    close(level2_fd);
                }
            }
            close(level1_fd);
        }
    }
    
    // 3. 使用绝对路径(忽略 dirfd)
    printf("3. 使用绝对路径创建目录:\n");
    char abs_path[512];
    snprintf(abs_path, sizeof(abs_path), "%s/absolute_dir", getcwd(NULL, 0));
    if (create_and_show_dir(base_dirfd, abs_path, 0755, 
                           "使用绝对路径创建目录") == 0) {
        show_directory_contents(".");
    }
    
    // 4. 使用 AT_FDCWD 创建目录
    printf("4. 使用 AT_FDCWD 创建目录:\n");
    if (create_and_show_dir(AT_FDCWD, "at_fdcwd_dir", 0755, 
                           "使用 AT_FDCWD 创建目录") == 0) {
        show_directory_contents(".");
    }
    
    // 清理资源
    printf("清理测试目录:\n");
    close(base_dirfd);
    
    // 递归删除目录
    system("rm -rf base_dir");
    rmdir("at_fdcwd_dir");
    rmdir("absolute_dir");
    
    printf("  ✓ 清理完成\n");
    
    return 0;
}

示例3:完整的目录管理工具 Link to heading

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

// 全局配置结构
struct mkdir_config {
    int parent_mode;    // 是否创建父目录
    mode_t dir_mode;    // 目录权限模式
    int verbose;        // 详细输出模式
    int ignore_exist;   // 忽略已存在错误
    char *base_dir;     // 基础目录路径
};

// 递归创建父目录
int create_parent_directories(int base_dirfd, const char *pathname, mode_t mode) {
    char *path_copy = strdup(pathname);
    if (!path_copy) {
        return -1;
    }
    
    char *dir_part = dirname(path_copy);
    if (strcmp(dir_part, ".") == 0 || strcmp(dir_part, "/") == 0) {
        free(path_copy);
        return 0;  // 没有父目录需要创建
    }
    
    // 检查父目录是否存在
    struct stat st;
    if (fstatat(base_dirfd, dir_part, &st, 0) == 0) {
        if (S_ISDIR(st.st_mode)) {
            free(path_copy);
            return 0;  // 父目录已存在
        } else {
            free(path_copy);
            errno = ENOTDIR;
            return -1;  // 父路径存在但不是目录
        }
    }
    
    // 父目录不存在,递归创建
    if (create_parent_directories(base_dirfd, dir_part, mode) == -1) {
        free(path_copy);
        return -1;
    }
    
    // 创建父目录
    int result = mkdirat(base_dirfd, dir_part, mode);
    free(path_copy);
    return result;
}

// 创建单个目录
int create_single_directory(int base_dirfd, const char *pathname, 
                          const struct mkdir_config *config) {
    // 如果需要创建父目录
    if (config->parent_mode) {
        if (create_parent_directories(base_dirfd, pathname, config->dir_mode) == -1) {
            if (errno != EEXIST) {
                fprintf(stderr, "创建父目录失败 '%s': %s\n", pathname, strerror(errno));
                return -1;
            }
        }
    }
    
    // 创建目标目录
    int result = mkdirat(base_dirfd, pathname, config->dir_mode);
    
    if (result == 0) {
        if (config->verbose) {
            printf("创建目录: %s (权限 %o)\n", pathname, config->dir_mode);
        }
    } else {
        if (errno == EEXIST) {
            if (config->ignore_exist) {
                if (config->verbose) {
                    printf("目录已存在: %s\n", pathname);
                }
                return 0;  // 视为成功
            } else {
                fprintf(stderr, "目录已存在: %s\n", pathname);
                return -1;
            }
        } else {
            fprintf(stderr, "创建目录失败 '%s': %s\n", pathname, strerror(errno));
            return -1;
        }
    }
    
    return result;
}

// 显示目录信息
void show_directory_info(int base_dirfd, const char *pathname) {
    struct stat st;
    if (fstatat(base_dirfd, pathname, &st, 0) == 0) {
        printf("目录信息:\n");
        printf("  路径: %s\n", pathname);
        printf("  权限: %o\n", st.st_mode & 0777);
        printf("  Inode: %ld\n", (long)st.st_ino);
        printf("  所有者: UID=%d, GID=%d\n", st.st_uid, st.st_gid);
    }
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项] 目录...\n", program_name);
    printf("\n选项:\n");
    printf("  -p, --parents          创建必要的父目录\n");
    printf("  -m, --mode=MODE        设置目录权限模式\n");
    printf("  -v, --verbose          显示详细信息\n");
    printf("  -i, --ignore-exist     忽略已存在的目录\n");
    printf("  -b, --base-dir=DIR     指定基础目录\n");
    printf("  -h, --help             显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s dir1 dir2 dir3           # 创建多个目录\n", program_name);
    printf("  %s -p a/b/c                 # 递归创建目录\n", program_name);
    printf("  %s -m 700 private_dir       # 创建私有目录\n", program_name);
    printf("  %s -p -m 755 /tmp/test/a/b  # 在指定位置创建目录\n", program_name);
    printf("  %s -b /home/user new_dir    # 在基础目录中创建\n", program_name);
}

int main(int argc, char *argv[]) {
    struct mkdir_config config = {
        .parent_mode = 0,
        .dir_mode = 0777,  // 受 umask 影响
        .verbose = 0,
        .ignore_exist = 0,
        .base_dir = NULL
    };
    
    printf("=== 目录创建工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"parents",     no_argument,       0, 'p'},
        {"mode",        required_argument, 0, 'm'},
        {"verbose",     no_argument,       0, 'v'},
        {"ignore-exist", no_argument,      0, 'i'},
        {"base-dir",    required_argument, 0, 'b'},
        {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "pm:vi:b:h", long_options, NULL)) != -1) {
        switch (opt) {
            case 'p':
                config.parent_mode = 1;
                break;
            case 'm': {
                char *endptr;
                config.dir_mode = strtol(optarg, &endptr, 8);
                if (*endptr != '\0') {
                    fprintf(stderr, "无效的权限模式: %s\n", optarg);
                    return 1;
                }
                break;
            }
            case 'v':
                config.verbose = 1;
                break;
            case 'i':
                config.ignore_exist = 1;
                break;
            case 'b':
                config.base_dir = optarg;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 检查目录参数
    if (optind >= argc) {
        fprintf(stderr, "错误: 请指定至少一个目录名\n");
        fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
        return 1;
    }
    
    // 打开基础目录
    int base_dirfd = AT_FDCWD;
    if (config.base_dir) {
        base_dirfd = open(config.base_dir, O_RDONLY);
        if (base_dirfd == -1) {
            fprintf(stderr, "无法打开基础目录 '%s': %s\n", 
                    config.base_dir, strerror(errno));
            return 1;
        }
        if (config.verbose) {
            printf("使用基础目录: %s (fd=%d)\n", config.base_dir, base_dirfd);
        }
    }
    
    // 创建所有指定的目录
    int success_count = 0;
    int error_count = 0;
    
    if (config.verbose) {
        printf("配置信息:\n");
        printf("  创建父目录: %s\n", config.parent_mode ? "是" : "否");
        printf("  权限模式: %o\n", config.dir_mode);
        printf("  忽略已存在: %s\n", config.ignore_exist ? "是" : "否");
        printf("\n");
    }
    
    for (int i = optind; i < argc; i++) {
        const char *dirname = argv[i];
        
        if (create_single_directory(base_dirfd, dirname, &config) == 0) {
            success_count++;
        } else {
            error_count++;
        }
    }
    
    // 显示统计信息
    printf("\n=== 操作统计 ===\n");
    printf("成功: %d 个目录\n", success_count);
    printf("失败: %d 个目录\n", error_count);
    
    // 清理资源
    if (config.base_dir && base_dirfd != AT_FDCWD) {
        close(base_dirfd);
    }
    
    return (error_count > 0) ? 1 : 0;
}

编译和运行说明 Link to heading

# 编译示例程序
gcc -o mkdirat_example1 example1.c
gcc -o mkdirat_example2 example2.c
gcc -o mkdirat_example3 example3.c

# 运行示例
./mkdirat_example1
./mkdirat_example2
./mkdirat_example3 --help
./mkdirat_example3 test_dir1 test_dir2
./mkdirat_example3 -p a/b/c/d
./mkdirat_example3 -m 700 private_dir

命令行工具配合使用 Link to heading

# 使用 mkdir 命令行工具
mkdir dir                      # 创建单个目录
mkdir -p a/b/c                # 递归创建目录
mkdir -m 700 private_dir      # 设置权限
mkdir -v dir1 dir2 dir3       # 详细输出

# 使用相对路径
cd /tmp
mkdir ./relative_dir          # 相对于当前目录
mkdir ../sibling_dir          # 相对于父目录

# 结合其他工具
mkdir -p project/{src,include,lib,bin}
mkdir -p backup/{daily,weekly,monthly}/{data,config}

重要注意事项 Link to heading

  1. 权限要求: 需要父目录的写权限
  2. 路径解析: 相对路径相对于 dirfd 解析
  3. 权限掩码: 实际权限受 umask 影响
  4. 错误处理: 始终检查返回值和 errno
  5. 原子性: 目录创建是原子操作

实际应用场景 Link to heading

  1. 项目初始化: 创建项目目录结构
  2. 备份系统: 创建按时间组织的目录
  3. 配置管理: 创建应用程序配置目录
  4. 日志管理: 创建日志文件目录
  5. 临时文件: 创建安全的临时目录

与相关函数的区别 Link to heading

// mkdir - 传统的目录创建
mkdir("path", mode);                    // 相对于当前目录

// mkdirat - 增强版目录创建
mkdirat(AT_FDCWD, "path", mode);        // 相对于当前目录
mkdirat(dirfd, "relative_path", mode);  // 相对于指定目录
mkdirat(dirfd, "/absolute_path", mode); // 绝对路径,忽略 dirfd

// openat - 相对路径文件操作
openat(dirfd, "file", flags);           // 相对路径打开文件

最佳实践 Link to heading

// 安全的目录创建函数
int safe_mkdirat(int dirfd, const char *pathname, mode_t mode) {
    // 验证参数
    if (!pathname) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查路径长度
    if (strlen(pathname) >= PATH_MAX) {
        errno = ENAMETOOLONG;
        return -1;
    }
    
    // 创建目录
    int result = mkdirat(dirfd, pathname, mode);
    
    // 处理常见情况
    if (result == -1) {
        switch (errno) {
            case EEXIST:
                // 检查是否确实是目录
                struct stat st;
                if (fstatat(dirfd, pathname, &st, 0) == 0 && S_ISDIR(st.st_mode)) {
                    return 0;  // 目录已存在,视为成功
                }
                break;
            case ENOENT:
                // 父目录不存在
                fprintf(stderr, "父目录不存在: %s\n", pathname);
                break;
        }
    }
    
    return result;
}

// 递归创建目录
int mkdirat_p(int dirfd, const char *pathname, mode_t mode) {
    char *path_copy = strdup(pathname);
    if (!path_copy) {
        return -1;
    }
    
    // 逐级创建目录
    char *slash = strchr(path_copy, '/');
    while (slash) {
        *slash = '\0';
        // 创建中间目录
        struct stat st;
        if (fstatat(dirfd, path_copy, &st, AT_SYMLINK_NOFOLLOW) == -1) {
            if (errno == ENOENT) {
                if (mkdirat(dirfd, path_copy, mode) == -1 && errno != EEXIST) {
                    free(path_copy);
                    return -1;
                }
            } else {
                free(path_copy);
                return -1;
            }
        } else if (!S_ISDIR(st.st_mode)) {
            free(path_copy);
            errno = ENOTDIR;
            return -1;
        }
        *slash = '/';
        slash = strchr(slash + 1, '/');
    }
    
    // 创建最终目录
    int result = mkdirat(dirfd, pathname, mode);
    free(path_copy);
    return result;
}

这些示例展示了 mkdirat 函数的各种使用方法,从基础的目录创建到完整的目录管理工具,帮助你全面掌握 Linux 系统中的目录创建机制。