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;
}