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
- 权限要求: 需要父目录的写权限
- 路径解析: 相对路径相对于 dirfd 解析
- 权限掩码: 实际权限受 umask 影响
- 错误处理: 始终检查返回值和 errno
- 原子性: 目录创建是原子操作
实际应用场景 Link to heading
- 项目初始化: 创建项目目录结构
- 备份系统: 创建按时间组织的目录
- 配置管理: 创建应用程序配置目录
- 日志管理: 创建日志文件目录
- 临时文件: 创建安全的临时目录
与相关函数的区别 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 系统中的目录创建机制。