按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; }
修改关键点
- 给
MY_DIR添加缓冲区相关字段,批量读取目录数据,减少系统调用次数 - 不再直接读取固定大小的
struct dirent,而是通过缓冲区解析变长条目 - 利用
d_reclen字段定位下一个目录项的位置 - 增加错误检查,避免main函数中解引用NULL指针
- 支持遍历所有目录条目,而非仅读取第一个
内容的提问来源于stack exchange,提问作者milanHrabos
相关产品推荐
相关产品推荐

