Porting to OS/2: Case #12 readdir_r() and struct dirent
readdir() and readdir_r() are used to read directory entries. And readdir_r() is a re-entrant version of readdir(). Because of this, readdir_r() requires additional arguments. Let's see prototypes of them.
As you see, readdir_r() requires [struct dirent *entry]. And entry should be large enough for a dirent. For this, the recommended portable codes are like this.
By the way, this code does not work on kLIBC. Why ? First, let's see [struct dirent]. In general, It is defined like this,
Whereas, on kLIBC it is defined like this.
What's the difference ? That is, the position of d_name. The portable code assumes that d_name is the last member of struct dirent. Unfortunately, however, it is not true for kLIBC. kLIBC has extra members after d_name. So the portable code causes a memory corruption when it is used with readdir_r() on kLIBC actually. To avoid this, you should allocate the entire size of struct dirent.
Here are the codes used in VLC.
And as you read at line 18 to line 21, fpathconf(), which is a dirfd version of pathconf(), for _PC_NAME_MAX on kLIBC was broken.
Consequently, if you use readdir_r(), allocate the whole size of struct dirent for a second argument, entry, of readdir_r().
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
As you see, readdir_r() requires [struct dirent *entry]. And entry should be large enough for a dirent. For this, the recommended portable codes are like this.
1 2 3 4 5 | name_max = pathconf(dirpath, _PC_NAME_MAX); if (name_max == -1) /* Limit not defined, or error */ name_max = 255; /* Take a guess */ len = offsetof(struct dirent, d_name) + name_max + 1; entryp = malloc(len); |
By the way, this code does not work on kLIBC. Why ? First, let's see [struct dirent]. In general, It is defined like this,
1 2 3 4 5 6 7 8 | struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* offset to the next dirent */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all file system types */ char d_name[256]; /* filename */ }; |
Whereas, on kLIBC it is defined like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | struct dirent { __uint32_t d_fileno; /* file number of entry */ __uint16_t d_reclen; /* length of this record */ __uint8_t d_type; /* file type, see below */ __uint8_t d_namlen; /* length of string in d_name */ #if __BSD_VISIBLE #ifndef MAXNAMLEN /* bird */ #ifdef NAME_MAX /* bird */ #define MAXNAMLEN NAME_MAX /* bird */ #else /* bird */ #define MAXNAMLEN 256 #endif /* bird */ #endif /* bird */ char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ #else char d_name[255 + 1]; /* name must be no longer than this */ #endif /* bird: Extra EMX fields - start */ /** @todo move these up before the name! LIBC07 */ __uint8_t d_attr; /* OS file attributes */ __uint16_t d_time; /* OS file modification time */ __uint16_t d_date; /* OS file modification date */ __off_t d_size; /* File size (bytes) */ /* bird: Extra EMX fields - end */ }; |
What's the difference ? That is, the position of d_name. The portable code assumes that d_name is the last member of struct dirent. Unfortunately, however, it is not true for kLIBC. kLIBC has extra members after d_name. So the portable code causes a memory corruption when it is used with readdir_r() on kLIBC actually. To avoid this, you should allocate the entire size of struct dirent.
Here are the codes used in VLC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /** * Reads the next file name from an open directory. * * @param dir The directory that is being read * * @return a UTF-8 string of the directory entry. Use free() to release it. * If there are no more entries in the directory, NULL is returned. * If an error occurs, errno is set and NULL is returned. */ char *vlc_readdir( DIR *dir ) { /* Beware that readdir_r() assumes <buf> is large enough to hold the result * dirent including the file name. A buffer overflow could occur otherwise. * In particular, pathconf() and _POSIX_NAME_MAX cannot be used here. */ struct dirent *ent; char *path = NULL; /* In the implementation of Innotek LIBC, aka kLIBC on OS/2, * fpathconf (_PC_NAME_MAX) is broken, and errno is set to EBADF. * Moreover, d_name is not the last member of struct dirent. * So just allocate as many as the size of struct dirent. */ #if 1 long len = sizeof (struct dirent); #else long len = fpathconf (dirfd (dir), _PC_NAME_MAX); len += offsetof (struct dirent, d_name) + 1; #endif struct dirent *buf = malloc (len); if (unlikely(buf == NULL)) return NULL; int val = readdir_r (dir, buf, &ent); if (val != 0) errno = val; else if (ent != NULL) path = FromLocaleDup (ent->d_name); free (buf); return path; } |
And as you read at line 18 to line 21, fpathconf(), which is a dirfd version of pathconf(), for _PC_NAME_MAX on kLIBC was broken.
Consequently, if you use readdir_r(), allocate the whole size of struct dirent for a second argument, entry, of readdir_r().
댓글
댓글 쓰기