diff options
author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2023-01-27 14:28:30 -0300 |
---|---|---|
committer | Andreas K. Hüttel <dilfridge@gentoo.org> | 2023-07-31 22:14:32 +0200 |
commit | 7165ae0d0f3e181b18cb79e139821b1f00ded059 (patch) | |
tree | 0c66c9364a2cf6e747f85880656f935459dcfd92 /sysdeps/unix/sysv | |
parent | Fix miscompilation on ia64's gcc-10 (diff) | |
download | glibc-7165ae0d0f3e181b18cb79e139821b1f00ded059.tar.gz glibc-7165ae0d0f3e181b18cb79e139821b1f00ded059.tar.bz2 glibc-7165ae0d0f3e181b18cb79e139821b1f00ded059.zip |
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.
Diffstat (limited to 'sysdeps/unix/sysv')
-rw-r--r-- | sysdeps/unix/sysv/linux/dirstream.h | 5 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/readdir.c | 83 |
2 files changed, 61 insertions, 27 deletions
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 <dirent.h> #include <sys/types.h> #include <libc-lock.h> @@ -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 <dirstream.h> +/* 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 * |