devfs readdir: handle short buffer same as UFS

Return EINVAL if this is the first dirent encountered with the short
buffer, or EJUSTRETURN if something was already copied out.

This is needed to pass eof check in vop_readdir_post(): we are not at
eof but resid was not advanced.

Reported and tested by:	pho (previous version)
Reviewed by:	markj
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D51667
This commit is contained in:
Konstantin Belousov 2025-08-01 11:22:14 +03:00
parent d5ec97156d
commit 011efaa5cd

View file

@ -1450,6 +1450,7 @@ devfs_readdir(struct vop_readdir_args *ap)
struct devfs_mount *dmp;
off_t off;
int *tmp_ncookies = NULL;
ssize_t startresid;
if (ap->a_vp->v_type != VDIR)
return (ENOTDIR);
@ -1482,6 +1483,7 @@ devfs_readdir(struct vop_readdir_args *ap)
error = 0;
de = ap->a_vp->v_data;
off = 0;
startresid = uio->uio_resid;
TAILQ_FOREACH(dd, &de->de_dlist, de_list) {
KASSERT(dd->de_cdp != (void *)0xdeadc0de, ("%s %d\n", __func__, __LINE__));
if (dd->de_flags & (DE_COVERED | DE_WHITEOUT))
@ -1494,8 +1496,13 @@ devfs_readdir(struct vop_readdir_args *ap)
de = dd;
dp = dd->de_dirent;
MPASS(dp->d_reclen == GENERIC_DIRSIZ(dp));
if (dp->d_reclen > uio->uio_resid)
if (dp->d_reclen > uio->uio_resid) {
/* Nothing was copied out, return EINVAL. */
if (uio->uio_resid == startresid)
error = EINVAL;
/* Otherwise stop. */
break;
}
dp->d_fileno = de->de_inode;
/* NOTE: d_off is the offset for the *next* entry. */
dp->d_off = off + dp->d_reclen;