From bc521cf2674e2a27cc4a0dfd638c151462d51d1b Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Fri, 27 Jan 2023 14:28:30 -0300 Subject: [PATCH 5/9] linux: Use getdents64 on non-LFS readdir The non-LFS opendir reserves a translation entry to be used to return the entry and the dirent64 struct is translated to the temporary buffer on each readdir call. Entries that overflow d_off/d_ino and the buffer reallocation failure (in case of large d_name) are ignored. Checked on x86_64-linux-gnu and i686-linux-gnu. --- dirent/tst-scandir.c | 6 ++- include/dirent.h | 2 +- sysdeps/unix/sysv/linux/dirstream.h | 5 ++ sysdeps/unix/sysv/linux/readdir.c | 83 +++++++++++++++++++---------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/dirent/tst-scandir.c b/dirent/tst-scandir.c index 8d87d4dd74..7bc666449e 100644 --- a/dirent/tst-scandir.c +++ b/dirent/tst-scandir.c @@ -155,8 +155,12 @@ do_test (void) } if (n != 6) { + /* Non-lfs opendir skips entries that can not be represented (for + instance if d_off is not an offset but rather an internal filesystem + representation. For this case there is no point in continue the + testcase. */ printf ("scandir returned %d entries instead of 6\n", n); - return 1; + return EXIT_UNSUPPORTED; } struct diff --git a/include/dirent.h b/include/dirent.h index d7567f5e86..17827176ba 100644 --- a/include/dirent.h +++ b/include/dirent.h @@ -1,8 +1,8 @@ #ifndef _DIRENT_H +# include # ifndef _ISOMAC # include # endif -# include # ifndef _ISOMAC # include # include diff --git a/sysdeps/unix/sysv/linux/dirstream.h b/sysdeps/unix/sysv/linux/dirstream.h index 3cb313b410..adcf8234f1 100644 --- a/sysdeps/unix/sysv/linux/dirstream.h +++ b/sysdeps/unix/sysv/linux/dirstream.h @@ -18,6 +18,7 @@ #ifndef _DIRSTREAM_H #define _DIRSTREAM_H 1 +#include #include #include @@ -41,6 +42,10 @@ struct __dirstream int errcode; /* Delayed error code. */ +#if !defined __OFF_T_MATCHES_OFF64_T || !defined __INO_T_MATCHES_INO64_T + struct dirent tdp; +#endif + /* Directory block. We must make sure that this block starts at an address that is aligned adequately enough to store dirent entries. Using the alignment of "void *" is not diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c index 4a4c00ea07..cd0ccaf33a 100644 --- a/sysdeps/unix/sysv/linux/readdir.c +++ b/sysdeps/unix/sysv/linux/readdir.c @@ -21,42 +21,71 @@ #if !_DIRENT_MATCHES_DIRENT64 #include +/* Translate the DP64 entry to the non-LFS one in the translation entry + at dirstream DS. Return true is the translation was possible or + false if either an internal field can not be represented in the non-LFS + entry or if the name is too long. */ +static bool +dirstream_entry (struct __dirstream *ds, const struct dirent64 *dp64) +{ + /* Check for overflow. */ + if (!in_off_t_range (dp64->d_off) || !in_ino_t_range (dp64->d_ino)) + return false; + + /* And if name is too large. */ + if (dp64->d_reclen - offsetof (struct dirent64, d_name) > NAME_MAX) + return false; + + ds->filepos = dp64->d_off; + + ds->tdp.d_off = dp64->d_off; + ds->tdp.d_ino = dp64->d_ino; + ds->tdp.d_reclen = sizeof (struct dirent) + + dp64->d_reclen - offsetof (struct dirent64, d_name); + ds->tdp.d_type = dp64->d_type; + memcpy (ds->tdp.d_name, dp64->d_name, + dp64->d_reclen - offsetof (struct dirent64, d_name)); + + return true; +} + /* Read a directory entry from DIRP. */ struct dirent * __readdir_unlocked (DIR *dirp) { - struct dirent *dp; int saved_errno = errno; - if (dirp->offset >= dirp->size) + while (1) { - /* We've emptied out our buffer. Refill it. */ - - size_t maxread = dirp->allocation; - ssize_t bytes; - - bytes = __getdents (dirp->fd, dirp->data, maxread); - if (bytes <= 0) + if (dirp->offset >= dirp->size) { - /* Linux may fail with ENOENT on some file systems if the - directory inode is marked as dead (deleted). POSIX - treats this as a regular end-of-directory condition, so - do not set errno in that case, to indicate success. */ - if (bytes == 0 || errno == ENOENT) - __set_errno (saved_errno); - return NULL; - } - dirp->size = (size_t) bytes; - - /* Reset the offset into the buffer. */ - dirp->offset = 0; + /* We've emptied out our buffer. Refill it. */ + ssize_t bytes = __getdents64 (dirp->fd, dirp->data, + dirp->allocation); + if (bytes <= 0) + { + /* Linux may fail with ENOENT on some file systems if the + directory inode is marked as dead (deleted). POSIX + treats this as a regular end-of-directory condition, so + do not set errno in that case, to indicate success. */ + if (bytes < 0 && errno == ENOENT) + __set_errno (saved_errno); + return NULL; + } + dirp->size = bytes; + + /* Reset the offset into the buffer. */ + dirp->offset = 0; + } + + struct dirent64 *dp64 = (struct dirent64 *) &dirp->data[dirp->offset]; + dirp->offset += dp64->d_reclen; + + /* Skip entries which might overflow d_off/d_ino or if the translation + buffer can not be resized. */ + if (dirstream_entry (dirp, dp64)) + return &dirp->tdp; } - - dp = (struct dirent *) &dirp->data[dirp->offset]; - dirp->offset += dp->d_reclen; - dirp->filepos = dp->d_off; - - return dp; } struct dirent * -- 2.39.3