52. faccessat - 相对路径文件访问权限检查 链接到标题
函数介绍 链接到标题
faccessat
是一个Linux系统调用,用于检查相对于指定目录文件描述符的文件访问权限。它是access
函数的扩展版本,支持相对路径和额外的标志位。
函数原型 链接到标题
#include <fcntl.h>
#include <unistd.h>
int faccessat(int dirfd, const char *pathname, int mode, int flags);
功能 链接到标题
检查相对于目录文件描述符的文件访问权限,支持相对路径和额外控制标志。
参数 链接到标题
int dirfd
: 目录文件描述符AT_FDCWD
: 使用当前工作目录- 有效的目录文件描述符:相对于该目录检查
const char *pathname
: 文件路径名(相对或绝对)int mode
: 要检查的权限类型F_OK
: 检查文件是否存在R_OK
: 检查读权限W_OK
: 检查写权限X_OK
: 检查执行权限
int flags
: 控制标志0
: 基本行为AT_EACCESS
: 使用有效ID而不是实际ID检查AT_SYMLINK_NOFOLLOW
: 不跟随符号链接
返回值 链接到标题
- 成功时(具备指定权限或文件存在)返回0
- 失败时返回-1,并设置errno
特殊限制 链接到标题
- 需要Linux 2.6.16以上内核支持
- 某些标志需要特定内核版本
相似函数 链接到标题
access()
: 基础版本stat()
,fstat()
,lstat()
: 文件状态检查openat()
: 相对路径文件打开
示例代码 链接到标题
#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>
int main() {
int dirfd, result;
printf("=== Facccessat 函数示例 ===\n");
// 示例1: 基本使用(相对于当前目录)
printf("\n示例1: 基本使用(相对于当前目录)\n");
// 创建测试文件
int fd = open("test_faccessat.txt", O_CREAT | O_WRONLY, 0644);
if (fd != -1) {
write(fd, "test content", 12);
close(fd);
printf("创建测试文件: test_faccessat.txt\n");
}
// 使用AT_FDCWD表示当前目录
result = faccessat(AT_FDCWD, "test_faccessat.txt", F_OK, 0);
if (result == 0) {
printf("文件存在检查成功\n");
} else {
printf("文件存在检查失败: %s\n", strerror(errno));
}
result = faccessat(AT_FDCWD, "test_faccessat.txt", R_OK | W_OK, 0);
if (result == 0) {
printf("文件读写权限检查成功\n");
} else {
printf("文件读写权限检查失败: %s\n", strerror(errno));
}
// 检查不存在的文件
result = faccessat(AT_FDCWD, "nonexistent.txt", F_OK, 0);
if (result == -1) {
if (errno == ENOENT) {
printf("不存在文件检查正确返回ENOENT\n");
}
}
// 示例2: 相对于指定目录
printf("\n示例2: 相对于指定目录\n");
// 创建测试目录
if (mkdir("test_dir", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
} else {
printf("创建测试目录: test_dir\n");
// 在目录中创建文件
fd = open("test_dir/file_in_dir.txt", O_CREAT | O_WRONLY, 0644);
if (fd != -1) {
write(fd, "content in directory", 20);
close(fd);
printf("在目录中创建文件: file_in_dir.txt\n");
}
// 打开目录获取文件描述符
dirfd = open("test_dir", O_RDONLY);
if (dirfd != -1) {
printf("获取目录文件描述符: %d\n", dirfd);
// 相对于目录文件描述符检查文件
result = faccessat(dirfd, "file_in_dir.txt", F_OK, 0);
if (result == 0) {
printf("相对路径文件存在检查成功\n");
}
result = faccessat(dirfd, "file_in_dir.txt", R_OK, 0);
if (result == 0) {
printf("相对路径文件读权限检查成功\n");
}
// 检查不存在的文件
result = faccessat(dirfd, "nonexistent.txt", F_OK, 0);
if (result == -1) {
if (errno == ENOENT) {
printf("相对路径不存在文件检查正确返回ENOENT\n");
}
}
close(dirfd);
}
}
// 示例3: 使用标志位
printf("\n示例3: 使用标志位\n");
// 创建符号链接进行测试
if (symlink("test_faccessat.txt", "symlink_to_test.txt") == -1 && errno != EEXIST) {
perror("创建符号链接失败");
} else {
printf("创建符号链接: symlink_to_test.txt -> test_faccessat.txt\n");
// 默认行为(跟随符号链接)
result = faccessat(AT_FDCWD, "symlink_to_test.txt", F_OK, 0);
if (result == 0) {
printf("默认行为(跟随符号链接)检查成功\n");
}
#ifdef AT_SYMLINK_NOFOLLOW
// 不跟随符号链接
result = faccessat(AT_FDCWD, "symlink_to_test.txt", F_OK, AT_SYMLINK_NOFOLLOW);
if (result == 0) {
printf("不跟随符号链接检查成功\n");
}
#endif
unlink("symlink_to_test.txt");
}
// 示例4: 错误处理演示
printf("\n示例4: 错误处理演示\n");
// 使用无效的目录文件描述符
result = faccessat(999, "test.txt", F_OK, 0);
if (result == -1) {
if (errno == EBADF) {
printf("无效目录文件描述符错误处理正确: %s\n", strerror(errno));
}
}
// 使用无效的权限模式
result = faccessat(AT_FDCWD, "test_faccessat.txt", 0777, 0);
if (result == -1) {
if (errno == EINVAL) {
printf("无效权限模式错误处理正确: %s\n", strerror(errno));
}
}
// 使用无效的标志
result = faccessat(AT_FDCWD, "test_faccessat.txt", F_OK, 0x1000);
if (result == -1) {
printf("无效标志处理: %s\n", strerror(errno));
}
// 示例5: 与access函数对比
printf("\n示例5: 与access函数对比\n");
printf("access() vs faccessat() 对比:\n");
printf("access(\"file.txt\", R_OK):\n");
printf(" - 只能使用绝对路径或相对路径\n");
printf(" - 基于当前工作目录\n");
printf(" - 不支持额外标志\n\n");
printf("faccessat(AT_FDCWD, \"file.txt\", R_OK, 0):\n");
printf(" - 功能相同(AT_FDCWD时)\n");
printf(" - 支持相对目录文件描述符\n");
printf(" - 支持额外标志\n\n");
printf("faccessat(dirfd, \"file.txt\", R_OK, 0):\n");
printf(" - 相对于指定目录检查\n");
printf(" - 更灵活的路径处理\n");
printf(" - 支持原子性操作\n\n");
// 示例6: 实际应用场景
printf("示例6: 实际应用场景\n");
printf("faccessat的典型使用场景:\n");
printf("1. 安全的相对路径检查\n");
printf("2. 沙箱环境中的文件访问验证\n");
printf("3. 避免TOCTOU竞争条件\n");
printf("4. 多目录操作的安全检查\n");
printf("5. 与openat系列函数配合使用\n\n");
// 演示安全的相对路径检查
printf("安全相对路径检查示例:\n");
printf("int check_file_in_directory(int dirfd, const char* filename) {\n");
printf(" // 避免路径遍历攻击\n");
printf(" if (strstr(filename, \"../\") != NULL) {\n");
printf(" return -1; // 拒绝不安全路径\n");
printf(" }\n");
printf(" return faccessat(dirfd, filename, R_OK, 0);\n");
printf("}\n\n");
// 示例7: TOCTOU竞争条件说明
printf("示例7: TOCTOU竞争条件说明\n");
printf("TOCTOU (Time-of-Check to Time-of-Use) 问题:\n");
printf("传统方式:\n");
printf(" if (access(\"file.txt\", W_OK) == 0) { // 检查\n");
printf(" fd = open(\"file.txt\", O_WRONLY); // 使用\n");
printf(" }\n");
printf("问题: 检查和使用之间可能被其他进程修改\n\n");
printf("改进方式:\n");
printf(" fd = openat(dirfd, \"file.txt\", O_WRONLY);\n");
printf(" if (fd != -1) {\n");
printf(" // 直接使用,避免竞争\n");
printf(" }\n\n");
printf("或者使用faccessat进行更安全的检查:\n");
printf(" if (faccessat(dirfd, \"file.txt\", W_OK, 0) == 0) {\n");
printf(" fd = openat(dirfd, \"file.txt\", O_WRONLY);\n");
printf(" }\n\n");
// 示例8: 权限模型说明
printf("示例8: 权限模型说明\n");
printf("faccessat使用的权限检查:\n");
printf("1. 基于进程的实际用户ID和组ID\n");
printf("2. 考虑文件的权限位\n");
printf("3. 考虑目录的搜索权限\n");
printf("4. 考虑特殊权限位(setuid/setgid)\n\n");
// 示例9: 性能考虑
printf("示例9: 性能考虑\n");
printf("faccessat性能特点:\n");
printf("1. 比直接open更轻量\n");
printf("2. 避免不必要的文件打开\n");
printf("3. 减少文件描述符使用\n");
printf("4. 支持批量权限检查\n\n");
printf("使用建议:\n");
printf("1. 优先使用faccessat而不是access\n");
printf("2. 合理使用目录文件描述符\n");
printf("3. 注意错误处理\n");
printf("4. 考虑TOCTOU问题\n");
printf("5. 在安全敏感场景中谨慎使用\n\n");
// 清理测试文件
unlink("test_faccessat.txt");
unlink("test_dir/file_in_dir.txt");
rmdir("test_dir");
printf("总结:\n");
printf("faccessat是access的现代扩展版本\n");
printf("支持相对目录文件描述符\n");
printf("提供额外的控制标志\n");
printf("更安全的路径处理\n");
printf("适用于现代Linux应用开发\n");
return 0;
}