17. getdents - 获取目录项 Link to heading
函数介绍 Link to heading
getdents
系统调用用于读取目录文件的内容,获取目录中的文件项信息。它返回原始的目录项结构,需要手动解析。这是一个低级别的目录读取函数。
函数原型 Link to heading
#include <dirent.h>
#include <sys/syscall.h>
int getdents(unsigned int fd, struct linux_dirent *dirp, unsigned int count);
功能 Link to heading
从目录文件描述符中读取目录项信息。
参数 Link to heading
unsigned int fd
: 目录文件描述符(通过opendir或open获得)struct linux_dirent *dirp
: 指向存储目录项的缓冲区unsigned int count
: 缓冲区大小(字节)
返回值 Link to heading
- 成功时返回读取的字节数
- 失败时返回-1,并设置errno:
EBADF
: 文件描述符无效EFAULT
: 缓冲区地址无效EINVAL
: 参数无效ENOENT
: 目录不存在
相似函数 Link to heading
readdir()
: 标准C库函数,更易使用opendir()
,closedir()
: 打开和关闭目录getdents64()
: 64位版本,支持大文件
示例代码 Link to heading
#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 <sys/syscall.h>
#include <linux/types.h>
// 目录项结构体定义
struct linux_dirent {
long d_ino; // inode号
__kernel_off_t d_off; // 到下一个目录项的偏移
unsigned short d_reclen; // 当前目录项长度
char d_name[]; // 文件名(以null结尾)
};
int main() {
int fd;
char buf[4096];
int nread;
char *p;
struct linux_dirent *d;
printf("=== Getdents函数示例 ===\n");
// 示例1: 基本的目录读取操作
printf("\n示例1: 基本的目录读取操作\n");
// 打开当前目录
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd == -1) {
perror("打开当前目录失败");
exit(EXIT_FAILURE);
}
printf("成功打开当前目录,文件描述符: %d\n", fd);
// 使用getdents读取目录内容
printf("读取目录内容:\n");
while (1) {
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents失败");
close(fd);
exit(EXIT_FAILURE);
}
if (nread == 0) // 没有更多的目录项
break;
// 解析目录项
for (p = buf; p < buf + nread;) {
d = (struct linux_dirent *)p;
// 显示目录项信息
printf(" inode: %ld, ", d->d_ino);
printf("长度: %d, ", d->d_reclen);
printf("名称: %s\n", d->d_name);
p += d->d_reclen;
}
}
close(fd);
// 示例2: 创建测试目录和文件进行演示
printf("\n示例2: 创建测试环境进行演示\n");
// 创建测试目录
if (mkdir("test_getdents_dir", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
} else {
printf("创建测试目录: test_getdents_dir\n");
// 在测试目录中创建一些文件
chdir("test_getdents_dir");
// 创建测试文件
int test_files[] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
char filename[20];
sprintf(filename, "test_file_%d.txt", test_files[i]);
int file_fd = open(filename, O_CREAT | O_WRONLY, 0644);
if (file_fd != -1) {
const char *content = "Test file content";
write(file_fd, content, strlen(content));
close(file_fd);
printf("创建测试文件: %s\n", filename);
}
}
// 创建子目录
if (mkdir("subdir", 0755) == -1 && errno != EEXIST) {
perror("创建子目录失败");
} else {
printf("创建子目录: subdir\n");
}
// 打开测试目录
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
printf("\n读取测试目录内容:\n");
while (1) {
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents失败");
break;
}
if (nread == 0)
break;
// 解析并显示目录项
for (p = buf; p < buf + nread;) {
d = (struct linux_dirent *)p;
// 跳过.和..目录项
if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
printf(" 文件名: %-20s", d->d_name);
printf("inode: %ld, ", d->d_ino);
printf("长度: %d\n", d->d_reclen);
}
p += d->d_reclen;
}
}
close(fd);
}
// 返回上级目录并清理
chdir("..");
}
// 示例3: 与readdir的对比
printf("\n示例3: getdents与readdir对比\n");
// 使用getdents读取
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
printf("使用getdents读取目录:\n");
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread > 0) {
int count = 0;
for (p = buf; p < buf + nread && count < 5;) {
d = (struct linux_dirent *)p;
if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
printf(" %s\n", d->d_name);
count++;
}
p += d->d_reclen;
}
}
close(fd);
}
// 使用readdir读取(标准方法)
printf("使用readdir读取目录:\n");
DIR *dir = opendir(".");
if (dir != NULL) {
struct dirent *entry;
int count = 0;
while ((entry = readdir(dir)) != NULL && count < 5) {
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
printf(" %s\n", entry->d_name);
count++;
}
}
closedir(dir);
}
printf("getdents提供更多底层控制,但readdir更易使用\n");
// 示例4: 目录项详细信息解析
printf("\n示例4: 目录项详细信息解析\n");
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread > 0) {
printf("目录项详细信息:\n");
for (p = buf; p < buf + nread;) {
d = (struct linux_dirent *)p;
// 显示所有目录项(包括.和..)
printf(" 名称: %-20s", d->d_name);
printf("inode: %-10ld", d->d_ino);
printf("偏移: %-8ld", (long)d->d_off);
printf("长度: %d\n", d->d_reclen);
p += d->d_reclen;
}
}
close(fd);
}
// 示例5: 错误处理演示
printf("\n示例5: 错误处理演示\n");
// 尝试对无效文件描述符操作
nread = syscall(SYS_getdents, 999, buf, sizeof(buf));
if (nread == -1) {
printf("对无效文件描述符操作: %s\n", strerror(errno));
}
// 尝试对普通文件操作
int regular_fd = open("regular_file.txt", O_CREAT | O_WRONLY, 0644);
if (regular_fd != -1) {
write(regular_fd, "test", 4);
nread = syscall(SYS_getdents, regular_fd, buf, sizeof(buf));
if (nread == -1) {
printf("对普通文件操作: %s\n", strerror(errno));
}
close(regular_fd);
unlink("regular_file.txt");
}
// 尝试使用无效缓冲区
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
nread = syscall(SYS_getdents, fd, NULL, sizeof(buf));
if (nread == -1) {
printf("使用无效缓冲区: %s\n", strerror(errno));
}
close(fd);
}
// 示例6: 实际应用场景
printf("\n示例6: 实际应用场景\n");
// 场景1: 快速目录扫描(跳过隐藏文件)
printf("场景1: 快速目录扫描\n");
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
printf("非隐藏文件列表:\n");
int file_count = 0;
while (1) {
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread <= 0) break;
for (p = buf; p < buf + nread;) {
d = (struct linux_dirent *)p;
// 跳过.和..以及隐藏文件(以.开头)
if (strcmp(d->d_name, ".") != 0 &&
strcmp(d->d_name, "..") != 0 &&
d->d_name[0] != '.') {
printf(" %s\n", d->d_name);
file_count++;
}
p += d->d_reclen;
}
}
printf("总共找到 %d 个非隐藏文件\n", file_count);
close(fd);
}
// 场景2: 目录统计信息
printf("场景2: 目录统计信息\n");
fd = open(".", O_RDONLY | O_DIRECTORY);
if (fd != -1) {
int total_files = 0;
int total_dirs = 0;
long long total_size = 0;
while (1) {
nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
if (nread <= 0) break;
for (p = buf; p < buf + nread;) {
d = (struct linux_dirent *)p;
// 跳过.和..
if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
// 检查文件类型(需要stat)
struct stat st;
if (stat(d->d_name, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
total_dirs++;
} else {
total_files++;
total_size += st.st_size;
}
}
}
p += d->d_reclen;
}
}
printf("目录统计:\n");
printf(" 文件数: %d\n", total_files);
printf(" 目录数: %d\n", total_dirs);
printf(" 文件总大小: %lld 字节\n", total_size);
close(fd);
}
// 清理测试目录
printf("\n清理测试资源...\n");
if (access("test_getdents_dir", F_OK) == 0) {
// 删除测试目录中的文件
chdir("test_getdents_dir");
for (int i = 1; i <= 5; i++) {
char filename[20];
sprintf(filename, "test_file_%d.txt", i);
unlink(filename);
}
rmdir("subdir");
chdir("..");
rmdir("test_getdents_dir");
printf("删除测试目录完成\n");
}
return 0;
}