107. newfstatat - 相对于目录文件描述符获取文件状态 見出しへのリンク
1. 函数介绍 見出しへのリンク
newfstatat
是一个 Linux 系统调用,用于相对于指定目录文件描述符获取文件的状态信息。它是 fstatat
的别名,提供了比传统 stat
函数更灵活的文件状态查询方式。
2. 函数原型 見出しへのリンク
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int newfstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags);
注意:在某些系统中,这个函数可能被称为
fstatat
,功能相同。
3. 功能 見出しへのリンク
相对于目录文件描述符 dirfd
获取文件的状态信息。如果 pathname
是相对路径,则相对于 dirfd
指定的目录解析;如果是绝对路径,则忽略 dirfd
。
4. 参数 見出しへのリンク
int dirfd
: 目录文件描述符AT_FDCWD
: 使用当前工作目录- 有效的目录文件描述符:相对于该目录进行操作
const char *pathname
: 文件路径名- 绝对路径:从根目录开始
- 相对路径:相对于
dirfd
指定的目录
struct stat *statbuf
: 指向stat
结构体的指针,用于存储文件状态信息int flags
: 控制标志0
: 基本行为AT_SYMLINK_NOFOLLOW
: 不跟随符号链接(获取符号链接本身的信息)AT_NO_AUTOMOUNT
: 不自动挂载(如果文件在未挂载的文件系统上)AT_EMPTY_PATH
: 允许空路径名(对dirfd
本身进行操作)
5. stat 结构体定义 見出しへのリンク
struct stat {
dev_t st_dev; /* 设备 ID(包含文件的设备)*/
ino_t st_ino; /* inode 号码 */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 所有者用户 ID */
gid_t st_gid; /* 所有者组 ID */
dev_t st_rdev; /* 设备 ID(如果是特殊文件)*/
off_t st_size; /* 文件总大小(字节)*/
blksize_t st_blksize; /* 文件系统 I/O 块大小 */
blkcnt_t st_blocks; /* 分配的 512B 块数 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};
6. 返回值 見出しへのリンク
- 成功时返回 0
- 失败时返回 -1,并设置
errno
7. 常见 errno 错误码 見出しへのリンク
EBADF
:dirfd
不是有效的文件描述符且不等于AT_FDCWD
ENOTDIR
:dirfd
不是目录文件描述符ENOENT
: 指定的文件或路径不存在EACCES
: 权限不足ELOOP
: 符号链接层级过深ENAMETOOLONG
: 路径名过长EFAULT
: 指针参数指向无效内存地址ENOMEM
: 内存不足EOVERFLOW
: 文件大小、inode 号或链接数超出范围
8. 相似函数,或关联函数 見出しへのリンク
stat()
: 通过路径获取文件状态lstat()
: 获取符号链接本身的状态(不跟随链接)fstat()
: 通过文件描述符获取文件状态openat()
: 相对于目录文件描述符打开文件readlinkat()
: 相对于目录文件描述符读取符号链接unlinkat()
: 相对于目录文件描述符删除文件mkdirat()
: 相对于目录文件描述符创建目录
9. 示例代码 見出しへのリンク
示例1:基本使用 - 获取文件状态信息 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <string.h>
void print_file_type(mode_t mode) {
if (S_ISREG(mode)) {
printf("文件类型: 普通文件\n");
} else if (S_ISDIR(mode)) {
printf("文件类型: 目录\n");
} else if (S_ISLNK(mode)) {
printf("文件类型: 符号链接\n");
} else if (S_ISCHR(mode)) {
printf("文件类型: 字符设备\n");
} else if (S_ISBLK(mode)) {
printf("文件类型: 块设备\n");
} else if (S_ISFIFO(mode)) {
printf("文件类型: FIFO/管道\n");
} else if (S_ISSOCK(mode)) {
printf("文件类型: 套接字\n");
} else {
printf("文件类型: 未知\n");
}
}
void print_file_permissions(mode_t mode) {
printf("权限: ");
printf((S_ISDIR(mode)) ? "d" : "-");
printf((mode & S_IRUSR) ? "r" : "-");
printf((mode & S_IWUSR) ? "w" : "-");
printf((mode & S_IXUSR) ? "x" : "-");
printf((mode & S_IRGRP) ? "r" : "-");
printf((mode & S_IWGRP) ? "w" : "-");
printf((mode & S_IXGRP) ? "x" : "-");
printf((mode & S_IROTH) ? "r" : "-");
printf((mode & S_IWOTH) ? "w" : "-");
printf((mode & S_IXOTH) ? "x" : "-");
printf("\n");
}
void print_stat_info(const char *label, const struct stat *st) {
printf("\n=== %s ===\n", label);
printf("设备 ID: %ld\n", (long)st->st_dev);
printf("inode 号: %ld\n", (long)st->st_ino);
print_file_type(st->st_mode);
print_file_permissions(st->st_mode);
printf("硬链接数: %ld\n", (long)st->st_nlink);
printf("所有者 UID: %d\n", st->st_uid);
printf("所属组 GID: %d\n", st->st_gid);
printf("文件大小: %ld 字节\n", (long)st->st_size);
printf("I/O 块大小: %ld\n", (long)st->st_blksize);
printf("分配的块数: %ld (512字节块)\n", (long)st->st_blocks);
// 打印时间信息
printf("最后访问时间: %s", ctime(&st->st_atime));
printf("最后修改时间: %s", ctime(&st->st_mtime));
printf("状态改变时间: %s", ctime(&st->st_ctime));
}
int main() {
printf("=== newfstatat 基本使用演示 ===\n");
// 创建测试目录结构
if (mkdir("test_dir", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
exit(EXIT_FAILURE);
}
// 在测试目录中创建文件
int fd = open("test_dir/test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
rmdir("test_dir");
exit(EXIT_FAILURE);
}
const char *content = "This is a test file for newfstatat demonstration.\n";
write(fd, content, strlen(content));
close(fd);
printf("创建测试文件: test_dir/test_file.txt\n");
// 创建符号链接
if (symlink("test_file.txt", "test_dir/test_symlink") == -1) {
if (errno != EEXIST) {
perror("创建符号链接失败");
unlink("test_dir/test_file.txt");
rmdir("test_dir");
exit(EXIT_FAILURE);
}
}
printf("创建符号链接: test_dir/test_symlink -> test_file.txt\n");
// 方法1: 使用 AT_FDCWD(当前工作目录)获取文件状态
struct stat stat_info1;
if (newfstatat(AT_FDCWD, "test_dir/test_file.txt", &stat_info1, 0) == 0) {
print_stat_info("AT_FDCWD 方式获取的文件状态", &stat_info1);
printf("✓ 使用 AT_FDCWD 成功获取文件状态\n");
} else {
perror("使用 AT_FDCWD 获取文件状态失败");
}
// 方法2: 打开目录获取文件描述符,然后相对获取文件状态
int dirfd = open("test_dir", O_RDONLY);
if (dirfd == -1) {
perror("打开目录失败");
unlink("test_dir/test_symlink");
unlink("test_dir/test_file.txt");
rmdir("test_dir");
exit(EXIT_FAILURE);
}
printf("\n✓ 成功打开目录: test_dir (dirfd: %d)\n", dirfd);
// 相对于目录文件描述符获取文件状态
struct stat stat_info2;
if (newfstatat(dirfd, "test_file.txt", &stat_info2, 0) == 0) {
print_stat_info("相对目录获取的文件状态", &stat_info2);
printf("✓ 相对目录成功获取文件状态\n");
} else {
perror("相对目录获取文件状态失败");
}
// 获取符号链接状态(不跟随链接)
struct stat stat_info3;
if (newfstatat(dirfd, "test_symlink", &stat_info3, AT_SYMLINK_NOFOLLOW) == 0) {
print_stat_info("符号链接本身的状态", &stat_info3);
if (S_ISLNK(stat_info3.st_mode)) {
printf("✓ 成功获取符号链接本身的状态\n");
}
} else {
perror("获取符号链接状态失败");
}
// 获取符号链接目标状态(跟随链接)
struct stat stat_info4;
if (newfstatat(dirfd, "test_symlink", &stat_info4, 0) == 0) {
print_stat_info("符号链接目标的状态", &stat_info4);
printf("✓ 成功获取符号链接目标的状态\n");
} else {
perror("获取符号链接目标状态失败");
}
// 验证 inode 号码
printf("\n=== inode 号码对比 ===\n");
printf("文件 inode: %ld\n", (long)stat_info1.st_ino);
printf("符号链接 inode: %ld\n", (long)stat_info3.st_ino);
printf("符号链接目标 inode: %ld\n", (long)stat_info4.st_ino);
if (stat_info3.st_ino != stat_info4.st_ino) {
printf("✓ 符号链接和目标文件有不同的 inode(正常)\n");
}
// 清理资源
close(dirfd);
unlink("test_dir/test_symlink");
unlink("test_dir/test_file.txt");
rmdir("test_dir");
printf("\n✓ 完成 newfstatat 基本使用演示\n");
return 0;
}
示例2:错误处理和特殊情况 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
void test_newfstatat_errors(int dirfd, const char *pathname, int flags, const char *description) {
printf("\n测试 %s:\n", description);
printf(" dirfd: %d\n", dirfd);
printf(" pathname: %s\n", pathname);
printf(" flags: 0x%x\n", flags);
struct stat st;
int result = newfstatat(dirfd, pathname, &st, flags);
if (result == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
switch (errno) {
case EBADF:
printf(" 原因: 无效的 dirfd\n");
break;
case ENOTDIR:
printf(" 原因: dirfd 不是目录\n");
break;
case ENOENT:
printf(" 原因: 文件或路径不存在\n");
break;
case EACCES:
printf(" 原因: 权限不足\n");
break;
case ELOOP:
printf(" 原因: 符号链接层级过深\n");
break;
case ENAMETOOLONG:
printf(" 原因: 路径名过长\n");
break;
case EFAULT:
printf(" 原因: 无效的指针参数\n");
break;
default:
printf(" 原因: 其他错误\n");
break;
}
} else {
printf(" 结果: 成功\n");
printf(" 文件类型: ");
if (S_ISREG(st.st_mode)) printf("普通文件\n");
else if (S_ISDIR(st.st_mode)) printf("目录\n");
else if (S_ISLNK(st.st_mode)) printf("符号链接\n");
else printf("其他\n");
printf(" inode: %ld\n", (long)st.st_ino);
printf(" 大小: %ld 字节\n", (long)st.st_size);
}
}
int main() {
printf("=== newfstatat 错误处理测试 ===\n");
// 创建测试环境
if (mkdir("error_test_dir", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
exit(EXIT_FAILURE);
}
// 创建测试文件
int fd = open("error_test_dir/test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "test content", 12);
close(fd);
printf("创建测试文件: error_test_dir/test_file.txt\n");
}
// 创建符号链接
if (symlink("test_file.txt", "error_test_dir/test_symlink") == 0) {
printf("创建符号链接: error_test_dir/test_symlink -> test_file.txt\n");
}
// 打开目录获取文件描述符
int dirfd = open("error_test_dir", O_RDONLY);
if (dirfd == -1) {
perror("打开测试目录失败");
unlink("error_test_dir/test_symlink");
unlink("error_test_dir/test_file.txt");
rmdir("error_test_dir");
exit(EXIT_FAILURE);
}
printf("打开测试目录: error_test_dir (dirfd: %d)\n", dirfd);
// 测试正常情况
test_newfstatat_errors(dirfd, "test_file.txt", 0, "正常相对路径获取状态");
test_newfstatat_errors(AT_FDCWD, "error_test_dir/test_file.txt", 0, "AT_FDCWD 绝对路径获取状态");
// 测试各种错误情况
test_newfstatat_errors(dirfd, "nonexistent.txt", 0, "不存在的文件");
test_newfstatat_errors(-2, "test_file.txt", 0, "无效的 dirfd");
test_newfstatat_errors(dirfd, "", 0, "空路径名");
test_newfstatat_errors(dirfd, "test_file.txt", 999, "无效的标志位");
// 测试符号链接相关标志
test_newfstatat_errors(dirfd, "test_symlink", AT_SYMLINK_NOFOLLOW, "不跟随符号链接");
test_newfstatat_errors(dirfd, "test_symlink", 0, "跟随符号链接");
// 测试过长路径名
char long_path[1024];
memset(long_path, 'a', sizeof(long_path) - 1);
long_path[sizeof(long_path) - 1] = '\0';
test_newfstatat_errors(dirfd, long_path, 0, "过长路径名");
// 测试 NULL 指针
printf("\n测试 NULL 指针:\n");
int result = newfstatat(dirfd, "test_file.txt", NULL, 0);
if (result == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == EFAULT) {
printf(" 原因: 无效的 statbuf 指针\n");
}
}
// 清理测试环境
close(dirfd);
unlink("error_test_dir/test_symlink");
unlink("error_test_dir/test_file.txt");
rmdir("error_test_dir");
return 0;
}
示例3:符号链接和特殊文件处理 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
void analyze_symlink_behavior() {
printf("=== 符号链接行为分析 ===\n");
// 创建测试环境
if (mkdir("symlink_test", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
return;
}
// 创建目标文件
int fd = open("symlink_test/target.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "This is the target file content.", 32);
close(fd);
printf("创建目标文件: symlink_test/target.txt\n");
}
// 创建符号链接
if (symlink("target.txt", "symlink_test/symlink_to_target") == 0) {
printf("创建符号链接: symlink_test/symlink_to_target -> target.txt\n");
}
// 创建悬空符号链接
if (symlink("nonexistent.txt", "symlink_test/dangling_symlink") == 0) {
printf("创建悬空符号链接: symlink_test/dangling_symlink -> nonexistent.txt\n");
}
// 打开目录
int dirfd = open("symlink_test", O_RDONLY);
if (dirfd == -1) {
perror("打开测试目录失败");
unlink("symlink_test/symlink_to_target");
unlink("symlink_test/dangling_symlink");
unlink("symlink_test/target.txt");
rmdir("symlink_test");
return;
}
printf("\n分析符号链接行为:\n");
// 获取目标文件状态
struct stat target_stat;
if (newfstatat(dirfd, "target.txt", &target_stat, 0) == 0) {
printf("1. 目标文件状态:\n");
printf(" inode: %ld\n", (long)target_stat.st_ino);
printf(" 大小: %ld 字节\n", (long)target_stat.st_size);
printf(" 类型: ");
if (S_ISREG(target_stat.st_mode)) printf("普通文件\n");
else printf("其他\n");
}
// 获取符号链接状态(不跟随)
struct stat symlink_stat;
if (newfstatat(dirfd, "symlink_to_target", &symlink_stat, AT_SYMLINK_NOFOLLOW) == 0) {
printf("2. 符号链接本身状态 (AT_SYMLINK_NOFOLLOW):\n");
printf(" inode: %ld\n", (long)symlink_stat.st_ino);
printf(" 大小: %ld 字节\n", (long)symlink_stat.st_size);
printf(" 类型: ");
if (S_ISLNK(symlink_stat.st_mode)) printf("符号链接\n");
else printf("其他\n");
}
// 获取符号链接目标状态(跟随)
struct stat symlink_target_stat;
if (newfstatat(dirfd, "symlink_to_target", &symlink_target_stat, 0) == 0) {
printf("3. 符号链接目标状态 (默认行为):\n");
printf(" inode: %ld\n", (long)symlink_target_stat.st_ino);
printf(" 大小: %ld 字节\n", (long)symlink_target_stat.st_size);
printf(" 类型: ");
if (S_ISREG(symlink_target_stat.st_mode)) printf("普通文件\n");
else printf("其他\n");
}
// 比较 inode 号码
printf("\ninode 号码对比:\n");
printf(" 目标文件 inode: %ld\n", (long)target_stat.st_ino);
printf(" 符号链接 inode: %ld\n", (long)symlink_stat.st_ino);
printf(" 符号链接目标 inode: %ld\n", (long)symlink_target_stat.st_ino);
if (target_stat.st_ino == symlink_target_stat.st_ino) {
printf("✓ 符号链接正确指向目标文件\n");
}
if (symlink_stat.st_ino != target_stat.st_ino) {
printf("✓ 符号链接和目标文件有不同的 inode\n");
}
// 测试悬空符号链接
printf("\n悬空符号链接测试:\n");
// 悬空符号链接(不跟随)
struct stat dangling_stat;
if (newfstatat(dirfd, "dangling_symlink", &dangling_stat, AT_SYMLINK_NOFOLLOW) == 0) {
printf("悬空符号链接状态 (AT_SYMLINK_NOFOLLOW):\n");
printf(" 类型: ");
if (S_ISLNK(dangling_stat.st_mode)) printf("符号链接\n");
else printf("其他\n");
printf("✓ 可以获取悬空符号链接本身的状态\n");
} else {
printf("获取悬空符号链接状态失败: %s\n", strerror(errno));
}
// 悬空符号链接(跟随)
if (newfstatat(dirfd, "dangling_symlink", &dangling_stat, 0) == -1) {
if (errno == ENOENT) {
printf("悬空符号链接目标 (默认行为): ENOENT (文件不存在)\n");
printf("✓ 正确处理悬空符号链接\n");
} else {
printf("悬空符号链接目标: %s\n", strerror(errno));
}
}
// 清理资源
close(dirfd);
unlink("symlink_test/symlink_to_target");
unlink("symlink_test/dangling_symlink");
unlink("symlink_test/target.txt");
rmdir("symlink_test");
printf("\n✓ 完成符号链接行为分析\n");
}
void demonstrate_special_files() {
printf("\n=== 特殊文件类型演示 ===\n");
// 创建测试目录
if (mkdir("special_test", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
return;
}
// 打开目录
int dirfd = open("special_test", O_RDONLY);
if (dirfd == -1) {
perror("打开测试目录失败");
rmdir("special_test");
return;
}
printf("分析当前目录中的特殊文件:\n");
// 获取当前目录状态
struct stat dir_stat;
if (newfstatat(dirfd, ".", &dir_stat, 0) == 0) {
printf("当前目录 (.) 状态:\n");
printf(" inode: %ld\n", (long)dir_stat.st_ino);
printf(" 类型: ");
if (S_ISDIR(dir_stat.st_mode)) printf("目录\n");
else printf("其他\n");
printf(" 权限: %o\n", dir_stat.st_mode & 0777);
printf(" 硬链接数: %ld\n", (long)dir_stat.st_nlink);
}
// 获取父目录状态
if (newfstatat(dirfd, "..", &dir_stat, 0) == 0) {
printf("父目录 (..) 状态:\n");
printf(" inode: %ld\n", (long)dir_stat.st_ino);
printf(" 类型: ");
if (S_ISDIR(dir_stat.st_mode)) printf("目录\n");
else printf("其他\n");
}
// 获取当前工作目录状态
if (newfstatat(AT_FDCWD, ".", &dir_stat, 0) == 0) {
printf("当前工作目录状态:\n");
printf(" inode: %ld\n", (long)dir_stat.st_ino);
printf(" 类型: ");
if (S_ISDIR(dir_stat.st_mode)) printf("目录\n");
else printf("其他\n");
}
// 清理资源
close(dirfd);
rmdir("special_test");
printf("✓ 完成特殊文件类型演示\n");
}
int main() {
printf("=== newfstatat 符号链接和特殊文件处理 ===\n");
analyze_symlink_behavior();
demonstrate_special_files();
return 0;
}
示例4:高级文件状态分析工具 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
typedef struct {
char path[256];
struct stat stat_info;
int is_symlink;
char target_path[256];
} file_info_t;
void print_detailed_stat_info(const file_info_t *file_info) {
const struct stat *st = &file_info->stat_info;
printf("=== 文件详细信息: %s ===\n", file_info->path);
// 基本信息
printf("设备 ID: %ld (0x%lx)\n", (long)st->st_dev, (long)st->st_dev);
printf("inode 号: %ld\n", (long)st->st_ino);
// 文件类型和权限
printf("文件类型和权限: ");
if (S_ISREG(st->st_mode)) printf("普通文件");
else if (S_ISDIR(st->st_mode)) printf("目录");
else if (S_ISLNK(st->st_mode)) printf("符号链接");
else if (S_ISCHR(st->st_mode)) printf("字符设备");
else if (S_ISBLK(st->st_mode)) printf("块设备");
else if (S_ISFIFO(st->st_mode)) printf("FIFO");
else if (S_ISSOCK(st->st_mode)) printf("套接字");
else printf("未知");
printf(" (0%o)\n", st->st_mode & 0777);
// 所有者信息
printf("所有者: %d", st->st_uid);
struct passwd *pwd = getpwuid(st->st_uid);
if (pwd) {
printf(" (%s)", pwd->pw_name);
}
printf("\n");
printf("所属组: %d", st->st_gid);
struct group *grp = getgrgid(st->st_gid);
if (grp) {
printf(" (%s)", grp->gr_name);
}
printf("\n");
// 大小和存储信息
printf("文件大小: %ld 字节", (long)st->st_size);
if (st->st_size >= 1024) {
printf(" (%.2f KB)", (double)st->st_size / 1024);
}
if (st->st_size >= 1024 * 1024) {
printf(" (%.2f MB)", (double)st->st_size / (1024 * 1024));
}
printf("\n");
printf("I/O 块大小: %ld 字节\n", (long)st->st_blksize);
printf("分配的块数: %ld (512字节块)\n", (long)st->st_blocks);
printf("实际占用空间: %ld 字节\n", (long)st->st_blocks * 512);
// 硬链接信息
printf("硬链接数: %ld\n", (long)st->st_nlink);
// 时间信息
printf("最后访问时间: %s", ctime(&st->st_atime));
printf("最后修改时间: %s", ctime(&st->st_mtime));
printf("状态改变时间: %s", ctime(&st->st_ctime));
// 符号链接信息
if (file_info->is_symlink) {
printf("符号链接目标: %s\n", file_info->target_path);
}
// 特殊文件信息
if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
printf("设备号: %ld (主设备号: %ld, 次设备号: %ld)\n",
(long)st->st_rdev,
(long)major(st->st_rdev),
(long)minor(st->st_rdev));
}
}
int get_file_info(int dirfd, const char *pathname, file_info_t *file_info) {
// 初始化结构体
memset(file_info, 0, sizeof(file_info_t));
strncpy(file_info->path, pathname, sizeof(file_info->path) - 1);
// 首先检查是否为符号链接(不跟随)
if (newfstatat(dirfd, pathname, &file_info->stat_info, AT_SYMLINK_NOFOLLOW) == 0) {
if (S_ISLNK(file_info->stat_info.st_mode)) {
file_info->is_symlink = 1;
// 获取符号链接目标路径
ssize_t len = readlinkat(dirfd, pathname, file_info->target_path,
sizeof(file_info->target_path) - 1);
if (len != -1) {
file_info->target_path[len] = '\0';
}
// 获取符号链接目标的 stat 信息
struct stat target_stat;
if (newfstatat(dirfd, pathname, &target_stat, 0) == 0) {
file_info->stat_info = target_stat;
}
}
return 0;
}
return -1;
}
void interactive_stat_tool() {
int choice;
char path[256];
int dirfd = AT_FDCWD;
int current_dirfd = -1;
while (1) {
printf("\n=== newfstatat 文件状态分析工具 ===\n");
printf("当前目录文件描述符: %d\n", dirfd);
printf("1. 设置目录文件描述符\n");
printf("2. 获取文件状态信息\n");
printf("3. 批量分析目录内容\n");
printf("4. 比较两个文件\n");
printf("5. 显示系统信息\n");
printf("6. 高级选项\n");
printf("0. 退出\n");
printf("请选择操作: ");
if (scanf("%d", &choice) != 1) {
printf("输入无效\n");
while (getchar() != '\n'); // 清空输入缓冲区
continue;
}
switch (choice) {
case 1:
printf("设置目录文件描述符:\n");
printf("1. 使用 AT_FDCWD (当前工作目录)\n");
printf("2. 打开指定目录\n");
printf("请选择: ");
int dir_choice;
if (scanf("%d", &dir_choice) == 1) {
if (dir_choice == 1) {
dirfd = AT_FDCWD;
if (current_dirfd != -1) {
close(current_dirfd);
current_dirfd = -1;
}
printf("✓ 设置为当前工作目录\n");
} else if (dir_choice == 2) {
printf("输入目录路径: ");
scanf("%255s", path);
int new_dirfd = open(path, O_RDONLY);
if (new_dirfd != -1) {
if (current_dirfd != -1) {
close(current_dirfd);
}
dirfd = current_dirfd = new_dirfd;
printf("✓ 成功打开目录 (dirfd: %d)\n", dirfd);
} else {
printf("✗ 打开目录失败: %s\n", strerror(errno));
}
}
}
break;
case 2: {
printf("获取文件状态信息:\n");
printf("输入文件路径: ");
scanf("%255s", path);
file_info_t file_info;
if (get_file_info(dirfd, path, &file_info) == 0) {
print_detailed_stat_info(&file_info);
} else {
printf("✗ 获取文件状态失败: %s\n", strerror(errno));
}
break;
}
case 3: {
printf("批量分析目录内容:\n");
printf("输入目录路径: ");
scanf("%255s", path);
int batch_dirfd = (dirfd == AT_FDCWD) ? open(path, O_RDONLY) :
openat(dirfd, path, O_RDONLY);
if (batch_dirfd != -1) {
DIR *dir = fdopendir(batch_dirfd);
if (dir) {
struct dirent *entry;
int count = 0;
printf("目录 %s 的内容:\n", path);
printf("%-20s %-10s %-8s %s\n", "名称", "类型", "大小", "权限");
printf("%-20s %-10s %-8s %s\n", "----", "----", "----", "----");
while ((entry = readdir(dir)) != NULL && count < 50) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0) {
continue;
}
struct stat st;
if (newfstatat(batch_dirfd, entry->d_name, &st,
AT_SYMLINK_NOFOLLOW) == 0) {
const char *type;
if (S_ISDIR(st.st_mode)) type = "目录";
else if (S_ISLNK(st.st_mode)) type = "链接";
else if (S_ISREG(st.st_mode)) type = "文件";
else type = "其他";
printf("%-20s %-10s %-8ld %o\n",
entry->d_name, type, (long)st.st_size,
st.st_mode & 0777);
count++;
}
}
if (count >= 50) {
printf("... (显示前50个项目)\n");
}
closedir(dir);
} else {
close(batch_dirfd);
printf("✗ 打开目录流失败\n");
}
} else {
printf("✗ 打开目录失败: %s\n", strerror(errno));
}
break;
}
case 4: {
printf("比较两个文件:\n");
char path1[256], path2[256];
printf("输入第一个文件路径: ");
scanf("%255s", path1);
printf("输入第二个文件路径: ");
scanf("%255s", path2);
file_info_t file1, file2;
if (get_file_info(dirfd, path1, &file1) == 0 &&
get_file_info(dirfd, path2, &file2) == 0) {
printf("文件比较结果:\n");
printf(" inode 相同: %s\n",
(file1.stat_info.st_ino == file2.stat_info.st_ino) ? "是" : "否");
printf(" 大小相同: %s\n",
(file1.stat_info.st_size == file2.stat_info.st_size) ? "是" : "否");
printf(" 修改时间相同: %s\n",
(file1.stat_info.st_mtime == file2.stat_info.st_mtime) ? "是" : "否");
printf(" 权限相同: %s\n",
((file1.stat_info.st_mode & 0777) == (file2.stat_info.st_mode & 0777)) ? "是" : "否");
} else {
printf("✗ 获取文件信息失败\n");
}
break;
}
case 5:
printf("系统信息:\n");
printf(" 当前 PID: %d\n", getpid());
printf(" 用户 ID: %d\n", getuid());
printf(" 组 ID: %d\n", getgid());
printf(" 页面大小: %ld 字节\n", (long)getpagesize());
system("uname -a");
break;
case 6: {
printf("高级选项:\n");
printf("1. 使用 AT_SYMLINK_NOFOLLOW 标志\n");
printf("2. 显示原始 stat 结构体\n");
printf("3. 性能测试\n");
printf("请选择: ");
int advanced_choice;
if (scanf("%d", &advanced_choice) == 1) {
switch (advanced_choice) {
case 1: {
printf("输入符号链接路径: ");
scanf("%255s", path);
struct stat st;
if (newfstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) == 0) {
printf("符号链接本身的状态:\n");
printf(" inode: %ld\n", (long)st.st_ino);
printf(" 类型: %s\n",
S_ISLNK(st.st_mode) ? "符号链接" : "其他");
printf(" 大小: %ld 字节\n", (long)st.st_size);
} else {
printf("获取状态失败: %s\n", strerror(errno));
}
break;
}
case 2: {
printf("输入文件路径: ");
scanf("%255s", path);
struct stat st;
if (newfstatat(dirfd, path, &st, 0) == 0) {
printf("原始 stat 结构体:\n");
printf(" st_dev: %ld\n", (long)st.st_dev);
printf(" st_ino: %ld\n", (long)st.st_ino);
printf(" st_mode: 0%o\n", st.st_mode);
printf(" st_nlink: %ld\n", (long)st.st_nlink);
printf(" st_uid: %d\n", st.st_uid);
printf(" st_gid: %d\n", st.st_gid);
printf(" st_rdev: %ld\n", (long)st.st_rdev);
printf(" st_size: %ld\n", (long)st.st_size);
printf(" st_blksize: %ld\n", (long)st.st_blksize);
printf(" st_blocks: %ld\n", (long)st.st_blocks);
printf(" st_atime: %ld\n", (long)st.st_atime);
printf(" st_mtime: %ld\n", (long)st.st_mtime);
printf(" st_ctime: %ld\n", (long)st.st_ctime);
}
break;
}
case 3: {
printf("性能测试:\n");
printf("输入测试文件路径: ");
scanf("%255s", path);
clock_t start = clock();
const int iterations = 10000;
for (int i = 0; i < iterations; i++) {
struct stat st;
newfstatat(dirfd, path, &st, 0);
}
clock_t end = clock();
double time_spent = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("执行 %d 次 newfstatat 调用\n", iterations);
printf("总时间: %.6f 秒\n", time_spent);
printf("平均每次调用: %.6f 微秒\n",
(time_spent * 1000000) / iterations);
break;
}
}
}
break;
}
case 0:
printf("退出文件状态分析工具\n");
if (current_dirfd != -1) {
close(current_dirfd);
}
return;
default:
printf("无效选择\n");
break;
}
}
}
void demonstrate_newfstatat_features() {
printf("=== newfstatat 特性演示 ===\n");
printf("newfstatat 主要特性:\n");
printf("1. 相对路径支持: 相对于目录文件描述符获取文件状态\n");
printf("2. 符号链接控制: 可选择是否跟随符号链接\n");
printf("3. 安全性提升: 避免路径解析竞态条件\n");
printf("4. 灵活性: 支持 AT_FDCWD 和绝对路径\n");
printf("5. 容器友好: 在受限环境中更安全\n");
printf("\n标志位说明:\n");
printf(" 0: 基本行为,跟随符号链接\n");
printf(" AT_SYMLINK_NOFOLLOW: 不跟随符号链接\n");
printf(" AT_NO_AUTOMOUNT: 不自动挂载\n");
printf(" AT_EMPTY_PATH: 允许空路径名\n");
printf("\n使用场景:\n");
printf("• 安全的文件状态查询\n");
printf("• 容器和沙箱环境\n");
printf("• 符号链接处理\n");
printf("• 系统管理和监控工具\n");
}
int main() {
printf("=== newfstatat 高级文件状态分析工具 ===\n");
// 演示特性
demonstrate_newfstatat_features();
// 启动交互式工具
char choice;
printf("\n是否启动交互式文件状态分析工具? (y/N): ");
if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
interactive_stat_tool();
}
return 0;
}
10. newfstatat 与相关函数的对比 見出しへのリンク
// 文件状态获取函数对比:
// 传统 stat 函数:
struct stat st1;
int result1 = stat("/path/to/file.txt", &st1);
// • 只能使用绝对或相对路径
// • 可能存在竞态条件
// • 对符号链接默认跟随
// 现代 newfstatat 函数:
int dirfd = open("/path/to", O_RDONLY);
struct stat st2;
int result2 = newfstatat(dirfd, "file.txt", &st2, AT_SYMLINK_NOFOLLOW);
// • 支持相对路径操作
// • 可控制符号链接行为
// • 基于已打开的目录文件描述符
// • 更安全的文件状态查询
// fstat 函数:
int fd = open("/path/to/file.txt", O_RDONLY);
struct stat st3;
int result3 = fstat(fd, &st3);
// • 通过文件描述符获取状态
// • 无需路径解析
// • 最安全但需要先打开文件
// 特殊值 AT_FDCWD:
int result4 = newfstatat(AT_FDCWD, "relative/path.txt", &st2, 0);
// • 等同于传统的相对路径 stat
// • 提供统一的接口
11. 实际应用场景 見出しへのリンク
场景1:安全的文件状态查询 見出しへのリンク
int secure_stat_check(const char *base_dir, const char *filename) {
// 打开基础目录
int dirfd = open(base_dir, O_RDONLY);
if (dirfd == -1) {
return -1;
}
// 相对获取文件状态(防止目录遍历攻击)
struct stat st;
int result = newfstatat(dirfd, filename, &st, 0);
close(dirfd);
return result;
}
场景2:符号链接安全处理 見出しへのリンク
int safe_symlink_check(const char *path) {
int dirfd = AT_FDCWD;
struct stat st_link, st_target;
// 获取符号链接本身的状态
if (newfstatat(dirfd, path, &st_link, AT_SYMLINK_NOFOLLOW) == 0) {
if (S_ISLNK(st_link.st_mode)) {
// 是符号链接,获取目标状态
if (newfstatat(dirfd, path, &st_target, 0) == 0) {
// 检查目标是否安全
return check_target_safety(&st_target);
} else {
// 目标不存在(悬空链接)
return -1;
}
}
}
return -1;
}
场景3:容器环境文件检查 見出しへのリンク
int container_file_check(int root_dirfd, const char *path) {
// 在容器根目录中安全检查文件
// 防止跳出容器文件系统
struct stat st;
return newfstatat(root_dirfd, path, &st, 0);
}
12. 注意事项 見出しへのリンク
使用 newfstatat
时需要注意:
- 目录文件描述符:
dirfd
必须是有效的目录文件描述符 - 路径安全: 防止目录遍历攻击(如
../
) - 符号链接处理: 正确使用
AT_SYMLINK_NOFOLLOW
标志 - 错误处理: 仔细处理各种可能的错误情况
- 权限检查: 确保有足够的权限访问目标文件
- 竞态条件: 在多线程环境中注意同步
13. 系统配置检查 見出しへのリンク
# 查看系统支持的文件操作特性
grep -i fstatat /proc/self/maps
# 查看文件系统信息
df -T
# 查看进程打开的文件描述符
ls -la /proc/self/fd/
# 检查系统调用表
ausyscall newfstatat
# 查看系统限制
ulimit -n
总结 見出しへのリンク
newfstatat
是现代 Linux 系统中推荐使用的文件状态获取函数:
关键特性:
- 相对路径支持: 相对于目录文件描述符获取文件状态
- 符号链接控制: 可选择是否跟随符号链接
- 安全性提升: 避免路径解析竞态条件
- 灵活性增强: 支持多种操作模式和标志
主要应用:
- 安全的文件状态查询程序
- 容器和虚拟化环境
- 符号链接处理工具
- 系统管理和监控应用
使用要点:
- 理解
dirfd
参数的含义和使用 - 正确处理相对路径和绝对路径
- 合理使用符号链接控制标志
- 注意资源管理和错误处理
newfstatat
为现代 Linux 应用程序提供了更安全、更灵活的文件状态查询方式,是系统编程的重要工具。