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.

  • 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().


댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷