You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

按K&R2实现自定义readdir时无法读取struct direct的问题

自定义readdir实现的读取失败问题解决

参照《K&R2》实现自定义readdir时,出现my_readdir函数中while循环体从未执行的问题——read(dp->fd, (char*) &dirbuf, sizeof(dirbuf))读取到的字节数始终不等于sizeof(dirbuf),导致函数返回NULL,在main函数中解引用该返回值触发段错误。

用户的示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>

#define NAME_MAX 14 /*longest filenames component; system-dependent */

#ifndef DIRSIZ
#define DIRSIZ 14
#endif

typedef struct {
    long ino; /*inode number */
    char name[NAME_MAX+1]; /*name + '\0' terminator */
} my_dirent;

typedef struct {
    int fd; /* file descriptor for directory */
    my_dirent d; /*the directory entry */
} MY_DIR;

/*
 * opendir: open a directory for readdir calls 
 */
MY_DIR *my_opendir(char *dirname)
{
    int fd;
    struct stat stbuf;
    MY_DIR *dp;

    if((fd = open(dirname, O_RDONLY, 0)) == -1
        || fstat(fd, &stbuf) == -1
        || (stbuf.st_mode & S_IFMT) != S_IFDIR
        || (dp = malloc(sizeof(MY_DIR))) == NULL) 
        return NULL;
    dp->fd = fd;
    return dp;
}

/*
 * closedir: close directory opened by opendir 
 */
void my_closedir(MY_DIR *dp)
{
    if(dp) {
        close(dp->fd);
        free(dp);
    }
}

#include <sys/dir.h>
/*
 * readdir: read directory entries in sequence 
 */
my_dirent *my_readdir(MY_DIR *dp)
{
    struct direct dirbuf; /* local directory structure */
    static my_dirent d; /* portable structure */

    // HERE BELOW: the body of while loop never executes (I have no idea why) so NULL is returned and causes segfault when dereferencing in printf
    while(read(dp->fd, (char*) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)) {
        if(dirbuf.d_ino == 0) /* slot not in use */
            continue;
        d.ino = dirbuf.d_ino;
        strncpy(d.name, dirbuf.d_name, DIRSIZ);
        d.name[DIRSIZ] = '\0';
        return &d;
    }
    return NULL;
}

int main()
{
    MY_DIR *dp = my_opendir(".");
    my_dirent *dent = my_readdir(dp);
    printf("directory info:\nname: %s; fd: %d; ino: %ld\n", dent->name, dp->fd, dent->ino);
}

从sys/dir.h中发现#define direct dirent,实际struct dirent的定义如下:

struct dirent
{
#ifndef __USE_FILE_OFFSET64
    __ino_t d_ino;
    __off_t d_off;
#else
    __ino64_t d_ino;
    __off64_t d_off;
#endif
    unsigned short int d_reclen;
    unsigned char d_type;
    char d_name[256];       /* We must not include limits.h! */
};

问题原因

K&R时代的目录项结构是固定长度的,但现代Linux系统中目录项是变长结构:

  • d_reclen字段表示当前目录项的实际长度,不同条目因文件名长度不同会有不同的d_reclen值
  • 直接用read按sizeof(struct dirent)读取,会因为实际条目长度不等于结构体大小而失败,而且read不保证一次能读取完整的变长结构体

解决方法

正确的做法是通过缓冲区批量读取目录数据,再根据每个条目的d_reclen字段解析并移动偏移,修改后的完整实现如下:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <sys/dir.h>

#define NAME_MAX 14 /*longest filenames component; system-dependent */

#ifndef DIRSIZ
#define DIRSIZ 14
#endif

typedef struct {
    long ino; /*inode number */
    char name[NAME_MAX+1]; /*name + '\0' terminator */
} my_dirent;

typedef struct {
    int fd; /* file descriptor for directory */
    off_t offset; /* current offset in directory */
    char buf[512]; /* buffer for directory entries */
    int buf_pos; /* current position in buffer */
    int buf_len; /* number of bytes read into buffer */
} MY_DIR;

/*
 * opendir: open a directory for readdir calls 
 */
MY_DIR *my_opendir(char *dirname)
{
    int fd;
    struct stat stbuf;
    MY_DIR *dp;

    if((fd = open(dirname, O_RDONLY | O_DIRECTORY, 0)) == -1
        || fstat(fd, &stbuf) == -1
        || (stbuf.st_mode & S_IFMT) != S_IFDIR
        || (dp = malloc(sizeof(MY_DIR))) == NULL) 
        return NULL;
    dp->fd = fd;
    dp->offset = 0;
    dp->buf_pos = 0;
    dp->buf_len = 0;
    return dp;
}

/*
 * closedir: close directory opened by opendir 
 */
void my_closedir(MY_DIR *dp)
{
    if(dp) {
        close(dp->fd);
        free(dp);
    }
}

/*
 * readdir: read directory entries in sequence 
 */
my_dirent *my_readdir(MY_DIR *dp)
{
    struct dirent *dirbuf;
    static my_dirent d;

    while(1) {
        // 缓冲区为空时,读取新的目录数据
        if(dp->buf_pos >= dp->buf_len) {
            dp->buf_len = read(dp->fd, dp->buf, sizeof(dp->buf));
            if(dp->buf_len <= 0) {
                // 读取完毕或出错
                return NULL;
            }
            dp->buf_pos = 0;
            dp->offset += dp->buf_len;
        }

        // 从缓冲区中获取当前目录项
        dirbuf = (struct dirent *)(dp->buf + dp->buf_pos);
        // 跳过无效inode的条目
        if(dirbuf->d_ino == 0) {
            dp->buf_pos += dirbuf->d_reclen;
            continue;
        }

        // 转换为自定义的my_dirent结构
        d.ino = (long)dirbuf->d_ino;
        strncpy(d.name, dirbuf->d_name, DIRSIZ);
        d.name[DIRSIZ] = '\0';

        // 移动缓冲区指针到下一个条目
        dp->buf_pos += dirbuf->d_reclen;
        return &d;
    }
}

int main()
{
    MY_DIR *dp = my_opendir(".");
    if(!dp) {
        perror("opendir failed");
        return 1;
    }
    my_dirent *dent;
    printf("directory info:\n");
    while((dent = my_readdir(dp)) != NULL) {
        printf("name: %s; fd: %d; ino: %ld\n", dent->name, dp->fd, dent->ino);
    }
    my_closedir(dp);
    return 0;
}

修改关键点

  1. 给MY_DIR添加缓冲区相关字段,批量读取目录数据,减少系统调用次数
  2. 不再直接读取固定大小的struct dirent,而是通过缓冲区解析变长条目
  3. 利用d_reclen字段定位下一个目录项的位置
  4. 增加错误检查,避免main函数中解引用NULL指针
  5. 支持遍历所有目录条目,而非仅读取第一个

内容的提问来源于stack exchange,提问作者milanHrabos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:35:50