Index: share/man/man9/Makefile =================================================================== --- share/man/man9/Makefile +++ share/man/man9/Makefile @@ -2308,7 +2308,8 @@ MLINKS+=vm_page_wire.9 vm_page_unwire.9 MLINKS+=VOP_ACCESS.9 VOP_ACCESSX.9 MLINKS+=VOP_ATTRIB.9 VOP_GETATTR.9 \ - VOP_ATTRIB.9 VOP_SETATTR.9 + VOP_ATTRIB.9 VOP_SETATTR.9 \ + VOP_ATTRIB.9 VOP_STAT.9 MLINKS+=VOP_CREATE.9 VOP_MKDIR.9 \ VOP_CREATE.9 VOP_MKNOD.9 \ VOP_CREATE.9 VOP_SYMLINK.9 Index: share/man/man9/VOP_ATTRIB.9 =================================================================== --- share/man/man9/VOP_ATTRIB.9 +++ share/man/man9/VOP_ATTRIB.9 @@ -28,7 +28,7 @@ .\" .\" $FreeBSD$ .\" -.Dd August 29, 2008 +.Dd August 7, 2020 .Dt VOP_ATTRIB 9 .Os .Sh NAME @@ -42,11 +42,19 @@ .Fn VOP_GETATTR "struct vnode *vp" "struct vattr *vap" "struct ucred *cred" .Ft int .Fn VOP_SETATTR "struct vnode *vp" "struct vattr *vap" "struct ucred *cred" +.Ft int +.Fn VOP_STAT "struct vnode *vp" "struct stat *sb" "struct ucred *cred" .Sh DESCRIPTION These entry points manipulate various attributes of a file or directory, including file permissions, owner, group, size, access time and modification time. .Pp +.Fn VOP_STAT +returns data in a format suitable for the +.Xr stat 2 +system call and by default is implemented as a wrapper around +.Fn VOP_GETATTR . +.Pp The arguments are: .Bl -tag -width cred .It Fa vp @@ -67,8 +75,11 @@ .Fa *vap prior to setting specific values. .Sh LOCKS +Both .Fn VOP_GETATTR -expects the vnode to be locked on entry and will leave the vnode locked on +and +.Fn VOP_STAT +expect the vnode to be locked on entry and will leave the vnode locked on return. The lock type can be either shared or exclusive. .Pp @@ -84,6 +95,10 @@ .Fn VOP_SETATTR returns zero if the attributes were changed successfully, otherwise an appropriate error is returned. +.Fn VOP_STAT +returns 0 if it was able to retrieve the attribute data +.Fa *sb , +otherwise an appropriate error is returned. .Sh ERRORS .Bl -tag -width Er .It Bq Er EPERM Index: sys/compat/linuxkpi/common/src/linux_compat.c =================================================================== --- sys/compat/linuxkpi/common/src/linux_compat.c +++ sys/compat/linuxkpi/common/src/linux_compat.c @@ -1691,7 +1691,7 @@ vp = filp->f_vnode; vn_lock(vp, LK_SHARED | LK_RETRY); - error = vn_stat(vp, sb, td->td_ucred, NOCRED, td); + error = VOP_STAT(vp, sb, td->td_ucred, NOCRED, td); VOP_UNLOCK(vp); return (error); Index: sys/fs/tmpfs/tmpfs_vnops.h =================================================================== --- sys/fs/tmpfs/tmpfs_vnops.h +++ sys/fs/tmpfs/tmpfs_vnops.h @@ -50,6 +50,7 @@ vop_access_t tmpfs_access; vop_fplookup_vexec_t tmpfs_fplookup_vexec; +vop_stat_t tmpfs_stat; vop_getattr_t tmpfs_getattr; vop_setattr_t tmpfs_setattr; vop_pathconf_t tmpfs_pathconf; Index: sys/fs/tmpfs/tmpfs_vnops.c =================================================================== --- sys/fs/tmpfs/tmpfs_vnops.c +++ sys/fs/tmpfs/tmpfs_vnops.c @@ -56,6 +56,8 @@ #include #include #include +#include +#include #include #include @@ -405,6 +407,52 @@ return error; } +int +tmpfs_stat(struct vop_stat_args *v) +{ + struct vnode *vp = v->a_vp; + struct stat *sb = v->a_sb; + vm_object_t obj; + struct tmpfs_node *node; + int error; + + node = VP_TO_TMPFS_NODE(vp); + + tmpfs_update_getattr(vp); + + error = vop_stat_helper_pre(v); + if (__predict_false(error)) + return (error); + + sb->st_dev = vp->v_mount->mnt_stat.f_fsid.val[0]; + sb->st_ino = node->tn_id; + sb->st_mode = node->tn_mode | VTTOIF(vp->v_type); + sb->st_nlink = node->tn_links; + sb->st_uid = node->tn_uid; + sb->st_gid = node->tn_gid; + sb->st_rdev = (vp->v_type == VBLK || vp->v_type == VCHR) ? + node->tn_rdev : NODEV; + sb->st_size = node->tn_size; + sb->st_atim.tv_sec = node->tn_atime.tv_sec; + sb->st_atim.tv_nsec = node->tn_atime.tv_nsec; + sb->st_mtim.tv_sec = node->tn_mtime.tv_sec; + sb->st_mtim.tv_nsec = node->tn_mtime.tv_nsec; + sb->st_ctim.tv_sec = node->tn_ctime.tv_sec; + sb->st_ctim.tv_nsec = node->tn_ctime.tv_nsec; + sb->st_birthtim.tv_sec = node->tn_birthtime.tv_sec; + sb->st_birthtim.tv_nsec = node->tn_birthtime.tv_nsec; + sb->st_blksize = PAGE_SIZE; + sb->st_flags = node->tn_flags; + sb->st_gen = 0; + if (vp->v_type == VREG) { + obj = node->tn_reg.tn_aobj; + sb->st_blocks = (u_quad_t)obj->resident_page_count * PAGE_SIZE; + } else + sb->st_blocks = node->tn_size; + sb->st_blocks /= S_BLKSIZE; + return (vop_stat_helper_post(v, error)); +} + int tmpfs_getattr(struct vop_getattr_args *v) { @@ -1675,6 +1723,7 @@ .vop_close = tmpfs_close, .vop_fplookup_vexec = tmpfs_fplookup_vexec, .vop_access = tmpfs_access, + .vop_stat = tmpfs_stat, .vop_getattr = tmpfs_getattr, .vop_setattr = tmpfs_setattr, .vop_read = tmpfs_read, Index: sys/kern/vfs_default.c =================================================================== --- sys/kern/vfs_default.c +++ sys/kern/vfs_default.c @@ -57,6 +57,9 @@ #include #include #include +#include +#include +#include #include @@ -87,6 +90,7 @@ static int vop_stdcopy_file_range(struct vop_copy_file_range_args *ap); static int vop_stdfdatasync(struct vop_fdatasync_args *ap); static int vop_stdgetpages_async(struct vop_getpages_async_args *ap); +static int vop_stdstat(struct vop_stat_args *ap); /* * This vnode table stores what we want to do if the filesystem doesn't @@ -114,6 +118,7 @@ .vop_bmap = vop_stdbmap, .vop_close = VOP_NULL, .vop_fsync = VOP_NULL, + .vop_stat = vop_stdstat, .vop_fdatasync = vop_stdfdatasync, .vop_getpages = vop_stdgetpages, .vop_getpages_async = vop_stdgetpages_async, @@ -1461,3 +1466,111 @@ sigallowstop(prev_stops); return (rc); } + +static int +vop_stdstat(struct vop_stat_args *a) +{ + struct vattr vattr; + struct vattr *vap; + struct vnode *vp; + struct stat *sb; + int error; + u_short mode; + + vp = a->a_vp; + sb = a->a_sb; + + error = vop_stat_helper_pre(a); + if (error != 0) + return (error); + + vap = &vattr; + + /* + * Initialize defaults for new and unusual fields, so that file + * systems which don't support these fields don't need to know + * about them. + */ + vap->va_birthtime.tv_sec = -1; + vap->va_birthtime.tv_nsec = 0; + vap->va_fsid = VNOVAL; + vap->va_rdev = NODEV; + + error = VOP_GETATTR(vp, vap, a->a_active_cred); + if (error) + goto out; + + /* + * Zero the spare stat fields + */ + bzero(sb, sizeof *sb); + + /* + * Copy from vattr table + */ + if (vap->va_fsid != VNOVAL) + sb->st_dev = vap->va_fsid; + else + sb->st_dev = vp->v_mount->mnt_stat.f_fsid.val[0]; + sb->st_ino = vap->va_fileid; + mode = vap->va_mode; + switch (vap->va_type) { + case VREG: + mode |= S_IFREG; + break; + case VDIR: + mode |= S_IFDIR; + break; + case VBLK: + mode |= S_IFBLK; + break; + case VCHR: + mode |= S_IFCHR; + break; + case VLNK: + mode |= S_IFLNK; + break; + case VSOCK: + mode |= S_IFSOCK; + break; + case VFIFO: + mode |= S_IFIFO; + break; + default: + error = EBADF; + goto out; + } + sb->st_mode = mode; + sb->st_nlink = vap->va_nlink; + sb->st_uid = vap->va_uid; + sb->st_gid = vap->va_gid; + sb->st_rdev = vap->va_rdev; + if (vap->va_size > OFF_MAX) { + error = EOVERFLOW; + goto out; + } + sb->st_size = vap->va_size; + sb->st_atim.tv_sec = vap->va_atime.tv_sec; + sb->st_atim.tv_nsec = vap->va_atime.tv_nsec; + sb->st_mtim.tv_sec = vap->va_mtime.tv_sec; + sb->st_mtim.tv_nsec = vap->va_mtime.tv_nsec; + sb->st_ctim.tv_sec = vap->va_ctime.tv_sec; + sb->st_ctim.tv_nsec = vap->va_ctime.tv_nsec; + sb->st_birthtim.tv_sec = vap->va_birthtime.tv_sec; + sb->st_birthtim.tv_nsec = vap->va_birthtime.tv_nsec; + + /* + * According to www.opengroup.org, the meaning of st_blksize is + * "a filesystem-specific preferred I/O block size for this + * object. In some filesystem types, this may vary from file + * to file" + * Use minimum/default of PAGE_SIZE (e.g. for VCHR). + */ + + sb->st_blksize = max(PAGE_SIZE, vap->va_blocksize); + sb->st_flags = vap->va_flags; + sb->st_blocks = vap->va_bytes / S_BLKSIZE; + sb->st_gen = vap->va_gen; +out: + return (vop_stat_helper_post(a, error)); +} Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c +++ sys/kern/vfs_syscalls.c @@ -1867,7 +1867,7 @@ if (vp->v_type == VDIR && oldinum == 0) { error = EPERM; /* POSIX */ } else if (oldinum != 0 && - ((error = vn_stat(vp, &sb, td->td_ucred, NOCRED, td)) == 0) && + ((error = VOP_STAT(vp, &sb, td->td_ucred, NOCRED, td)) == 0) && sb.st_ino != oldinum) { error = EIDRM; /* Identifier removed */ } else if (fp != NULL && fp->f_vnode != vp) { @@ -2381,7 +2381,7 @@ if ((error = namei(&nd)) != 0) return (error); - error = vn_stat(nd.ni_vp, sbp, td->td_ucred, NOCRED, td); + error = VOP_STAT(nd.ni_vp, sbp, td->td_ucred, NOCRED, td); if (error == 0) { SDT_PROBE2(vfs, , stat, mode, path, sbp->st_mode); if (S_ISREG(sbp->st_mode)) @@ -4566,7 +4566,7 @@ vfs_unbusy(mp); if (error != 0) return (error); - error = vn_stat(vp, sb, td->td_ucred, NOCRED, td); + error = VOP_STAT(vp, sb, td->td_ucred, NOCRED, td); vput(vp); return (error); } Index: sys/kern/vfs_vnops.c =================================================================== --- sys/kern/vfs_vnops.c +++ sys/kern/vfs_vnops.c @@ -1455,123 +1455,12 @@ int error; vn_lock(vp, LK_SHARED | LK_RETRY); - error = vn_stat(vp, sb, active_cred, fp->f_cred, td); + error = VOP_STAT(vp, sb, active_cred, fp->f_cred, td); VOP_UNLOCK(vp); return (error); } -/* - * Stat a vnode; implementation for the stat syscall - */ -int -vn_stat(struct vnode *vp, struct stat *sb, struct ucred *active_cred, - struct ucred *file_cred, struct thread *td) -{ - struct vattr vattr; - struct vattr *vap; - int error; - u_short mode; - - AUDIT_ARG_VNODE1(vp); -#ifdef MAC - error = mac_vnode_check_stat(active_cred, file_cred, vp); - if (error) - return (error); -#endif - - vap = &vattr; - - /* - * Initialize defaults for new and unusual fields, so that file - * systems which don't support these fields don't need to know - * about them. - */ - vap->va_birthtime.tv_sec = -1; - vap->va_birthtime.tv_nsec = 0; - vap->va_fsid = VNOVAL; - vap->va_rdev = NODEV; - - error = VOP_GETATTR(vp, vap, active_cred); - if (error) - return (error); - - /* - * Zero the spare stat fields - */ - bzero(sb, sizeof *sb); - - /* - * Copy from vattr table - */ - if (vap->va_fsid != VNOVAL) - sb->st_dev = vap->va_fsid; - else - sb->st_dev = vp->v_mount->mnt_stat.f_fsid.val[0]; - sb->st_ino = vap->va_fileid; - mode = vap->va_mode; - switch (vap->va_type) { - case VREG: - mode |= S_IFREG; - break; - case VDIR: - mode |= S_IFDIR; - break; - case VBLK: - mode |= S_IFBLK; - break; - case VCHR: - mode |= S_IFCHR; - break; - case VLNK: - mode |= S_IFLNK; - break; - case VSOCK: - mode |= S_IFSOCK; - break; - case VFIFO: - mode |= S_IFIFO; - break; - default: - return (EBADF); - } - sb->st_mode = mode; - sb->st_nlink = vap->va_nlink; - sb->st_uid = vap->va_uid; - sb->st_gid = vap->va_gid; - sb->st_rdev = vap->va_rdev; - if (vap->va_size > OFF_MAX) - return (EOVERFLOW); - sb->st_size = vap->va_size; - sb->st_atim.tv_sec = vap->va_atime.tv_sec; - sb->st_atim.tv_nsec = vap->va_atime.tv_nsec; - sb->st_mtim.tv_sec = vap->va_mtime.tv_sec; - sb->st_mtim.tv_nsec = vap->va_mtime.tv_nsec; - sb->st_ctim.tv_sec = vap->va_ctime.tv_sec; - sb->st_ctim.tv_nsec = vap->va_ctime.tv_nsec; - sb->st_birthtim.tv_sec = vap->va_birthtime.tv_sec; - sb->st_birthtim.tv_nsec = vap->va_birthtime.tv_nsec; - - /* - * According to www.opengroup.org, the meaning of st_blksize is - * "a filesystem-specific preferred I/O block size for this - * object. In some filesystem types, this may vary from file - * to file" - * Use minimum/default of PAGE_SIZE (e.g. for VCHR). - */ - - sb->st_blksize = max(PAGE_SIZE, vap->va_blocksize); - - sb->st_flags = vap->va_flags; - if (priv_check_cred_vfs_generation(td->td_ucred)) - sb->st_gen = 0; - else - sb->st_gen = vap->va_gen; - - sb->st_blocks = vap->va_bytes / S_BLKSIZE; - return (0); -} - /* * File table vnode ioctl routine. */ Index: sys/kern/vnode_if.src =================================================================== --- sys/kern/vnode_if.src +++ sys/kern/vnode_if.src @@ -177,6 +177,17 @@ }; +%% stat vp L L L + +vop_stat { + IN struct vnode *vp; + OUT struct stat *sb; + IN struct ucred *active_cred; + IN struct ucred *file_cred; + IN struct thread *td; +}; + + %% getattr vp L L L vop_getattr { Index: sys/security/audit/audit_arg.c =================================================================== --- sys/security/audit/audit_arg.c +++ sys/security/audit/audit_arg.c @@ -854,7 +854,7 @@ * It is assumed that the caller will hold any vnode locks necessary to * perform a VOP_GETATTR() on the passed vnode. * - * XXX: The attr code is very similar to vfs_vnops.c:vn_stat(), but always + * XXX: The attr code is very similar to vfs_default.c:vop_stdstat(), but always * provides access to the generation number as we need that to construct the * BSM file ID. * Index: sys/sys/vnode.h =================================================================== --- sys/sys/vnode.h +++ sys/sys/vnode.h @@ -737,8 +737,6 @@ struct thread *td); int vn_rlimit_fsize(const struct vnode *vn, const struct uio *uio, struct thread *td); -int vn_stat(struct vnode *vp, struct stat *sb, struct ucred *active_cred, - struct ucred *file_cred, struct thread *td); int vn_start_write(struct vnode *vp, struct mount **mpp, int flags); int vn_start_secondary_write(struct vnode *vp, struct mount **mpp, int flags); @@ -893,6 +891,22 @@ void vop_rename_fail(struct vop_rename_args *ap); +#define vop_stat_helper_pre(ap) ({ \ + int _error; \ + AUDIT_ARG_VNODE1(ap->a_vp); \ + _error = mac_vnode_check_stat(ap->a_active_cred, ap->a_file_cred, ap->a_vp);\ + if (__predict_true(_error == 0)) \ + bzero(ap->a_sb, sizeof(*ap->a_sb)); \ + _error; \ +}) + +#define vop_stat_helper_post(ap, error) ({ \ + int _error = (error); \ + if (priv_check_cred_vfs_generation(ap->a_td->td_ucred)) \ + ap->a_sb->st_gen = 0; \ + _error; \ +}) + #define VOP_WRITE_PRE(ap) \ struct vattr va; \ int error; \