From 8a96140415e0c99fb259a4d64da28c9d057842d6 Mon Sep 17 00:00:00 2001
From: Tomohiro Kusumi <tkusumi@netbsd.org>
Date: Sun, 5 Sep 2021 15:09:44 +0900
Subject: [PATCH] sys/kern: Add posix_fallocate(2)

Based on the initial posix_fallocate(2) implementation in FreeBSD
from d91f88f7f38078ac6dd4b0f0e77470d605d1518e in 2011.

There is nothing special or efficient about the default behavior.
VFS has no knowledge of fs details.
---
 lib/libc/sys/Symbol.map  |   3 ++
 sys/kern/init_sysent.c   |   1 +
 sys/kern/syscalls.c      |   1 +
 sys/kern/syscalls.master |   1 +
 sys/kern/vfs_default.c   | 113 +++++++++++++++++++++++++++++++++++++++
 sys/kern/vfs_syscalls.c  |  55 +++++++++++++++++++
 sys/kern/vfs_vopops.c    |  33 ++++++++++++
 sys/sys/fcntl.h          |   2 -
 sys/sys/kern_syscall.h   |   1 +
 sys/sys/param.h          |   3 +-
 sys/sys/syscall.h        |   3 +-
 sys/sys/syscall.mk       |   3 +-
 sys/sys/sysproto.h       |   6 +++
 sys/sys/sysunion.h       |   1 +
 sys/sys/vfsops.h         |  17 ++++++
 sys/sys/vnode.h          |   1 +
 16 files changed, 239 insertions(+), 5 deletions(-)

diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map
index a867b78364..c4ec140fa0 100644
--- a/lib/libc/sys/Symbol.map
+++ b/lib/libc/sys/Symbol.map
@@ -334,6 +334,7 @@ DF504.0 {
 DF510.0 {
     clock_nanosleep;
     fexecve;
+    posix_fallocate;
 };
 
 DFprivate_1.0 {
@@ -535,6 +536,7 @@ DFprivate_1.0 {
     __sys_pipe;
     __sys_pipe2;
     __sys_poll;
+    __sys_posix_fallocate;
     __sys_ppoll;
     __sys_procctl;
     __sys_profil;
@@ -840,6 +842,7 @@ DFprivate_1.0 {
     _pipe;
     _pipe2;
     _poll;
+    _posix_fallocate;
     _ppoll;
     _procctl;
     _profil;
diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c
index 2dc0b60083..ae34337d9e 100644
--- a/sys/kern/init_sysent.c
+++ b/sys/kern/init_sysent.c
@@ -567,4 +567,5 @@ struct sysent sysent[] = {
 	{ AS(getrandom_args), (sy_call_t *)sys_getrandom },	/* 550 = getrandom */
 	{ AS(__realpath_args), (sy_call_t *)sys___realpath },	/* 551 = __realpath */
 	{ AS(fexecve_args), (sy_call_t *)sys_fexecve },	/* 552 = fexecve */
+	{ AS(posix_fallocate_args), (sy_call_t *)sys_posix_fallocate },	/* 553 = posix_fallocate */
 };
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
index efbc838c96..d8706a7a0d 100644
--- a/sys/kern/syscalls.c
+++ b/sys/kern/syscalls.c
@@ -559,4 +559,5 @@ const char *syscallnames[] = {
 	"getrandom",			/* 550 = getrandom */
 	"__realpath",			/* 551 = __realpath */
 	"fexecve",			/* 552 = fexecve */
+	"posix_fallocate"		/* 553 = posix_fallocate */
 };
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index b1d6ec01eb..7fbe4ea771 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -736,3 +736,4 @@
 550	STD	{ ssize_t getrandom(void *buf, size_t len, unsigned flags); }
 551	STD	{ ssize_t __realpath(const char *path, char *buf, size_t len); }
 552	STD	{ int fexecve(int fd, char **argv, char **envv); }
+553	STD	{ int posix_fallocate(int fd, off_t offset, off_t len); }
diff --git a/sys/kern/vfs_default.c b/sys/kern/vfs_default.c
index e422d70c5b..c2ba330994 100644
--- a/sys/kern/vfs_default.c
+++ b/sys/kern/vfs_default.c
@@ -51,6 +51,7 @@
 #include <sys/namei.h>
 #include <sys/mountctl.h>
 #include <sys/vfs_quota.h>
+#include <sys/uio.h>
 
 #include <machine/limits.h>
 
@@ -89,6 +90,7 @@ struct vop_ops default_vnode_vops = {
 	.vop_getextattr		= (void *)vop_eopnotsupp,
 	.vop_setextattr		= (void *)vop_eopnotsupp,
 	.vop_markatime		= vop_stdmarkatime,
+	.vop_allocate		= vop_stdallocate,
 	.vop_nresolve		= vop_compat_nresolve,
 	.vop_nlookupdotdot	= vop_compat_nlookupdotdot,
 	.vop_ncreate		= vop_compat_ncreate,
@@ -135,6 +137,117 @@ vop_stdmarkatime(struct vop_markatime_args *ap)
 	return (EOPNOTSUPP);
 }
 
+int
+vop_stdallocate(struct vop_allocate_args *ap)
+{
+	struct thread *td;
+	struct vnode *vp;
+	struct vattr vattr, *vap;
+	struct uio auio;
+	struct iovec aiov;
+	uint8_t *buf;
+	off_t offset, len, fsize;
+	size_t iosize;
+	int error;
+
+	td = curthread;
+	vap = &vattr;
+	buf = NULL;
+
+	vp = ap->a_vp;
+	offset = ap->a_offset;
+	len = ap->a_len;
+
+	error = VOP_GETATTR(vp, vap);
+	if (error != 0)
+		goto out;
+	fsize = vap->va_size;
+	iosize = vap->va_blocksize;
+	if (iosize == 0)
+		iosize = BLKDEV_IOSIZE;
+	if (iosize > vmaxiosize(vp))
+		iosize = vmaxiosize(vp);
+	buf = kmalloc(iosize, M_TEMP, M_WAITOK);
+
+	if (offset + len > vap->va_size) {
+		/*
+		 * Test offset + len against the filesystem's maxfilesize.
+		 */
+		VATTR_NULL(&vattr);
+		vap->va_size = offset + len;
+		error = VOP_SETATTR(vp, vap, td->td_ucred);
+		if (error != 0)
+			goto out;
+		VATTR_NULL(&vattr);
+		vap->va_size = fsize;
+		error = VOP_SETATTR(vp, vap, td->td_ucred);
+		if (error != 0)
+			goto out;
+	}
+
+	for (;;) {
+		/*
+		 * Read and write back anything below the nominal file
+		 * size.  There's currently no way outside the filesystem
+		 * to know whether this area is sparse or not.
+		 */
+		off_t cur = iosize;
+		if ((offset % iosize) != 0)
+			cur -= (offset % iosize);
+		if (cur > len)
+			cur = len;
+		if (offset < fsize) {
+			aiov.iov_base = buf;
+			aiov.iov_len = cur;
+			auio.uio_iov = &aiov;
+			auio.uio_iovcnt = 1;
+			auio.uio_offset = offset;
+			auio.uio_resid = cur;
+			auio.uio_segflg = UIO_SYSSPACE;
+			auio.uio_rw = UIO_READ;
+			auio.uio_td = td;
+			error = VOP_READ(vp, &auio, 0, td->td_ucred);
+			if (error != 0)
+				break;
+			if (auio.uio_resid > 0) {
+				bzero(buf + cur - auio.uio_resid,
+				    auio.uio_resid);
+			}
+		} else {
+			bzero(buf, cur);
+		}
+
+		aiov.iov_base = buf;
+		aiov.iov_len = cur;
+		auio.uio_iov = &aiov;
+		auio.uio_iovcnt = 1;
+		auio.uio_offset = offset;
+		auio.uio_resid = cur;
+		auio.uio_segflg = UIO_SYSSPACE;
+		auio.uio_rw = UIO_WRITE;
+		auio.uio_td = td;
+
+		error = VOP_WRITE(vp, &auio, 0, td->td_ucred);
+		if (error != 0)
+			break;
+
+		len -= cur;
+		offset += cur;
+		if (len == 0)
+			break;
+		/*
+		if (should_yield())
+			break;
+		*/
+	}
+out:
+	ap->a_offset = offset;
+	ap->a_len = len;
+	kfree(buf, M_TEMP);
+
+	return (error);
+}
+
 int
 vop_null(struct vop_generic_args *ap)
 {
diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index a45f52909a..f5fac489a9 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -5338,3 +5338,58 @@ sys___realpath(struct sysmsg *sysmsg, const struct __realpath_args *uap)
 
 	return error;
 }
+
+int
+sys_posix_fallocate(struct sysmsg *sysmsg, const struct posix_fallocate_args *uap)
+{
+	return (kern_posix_fallocate(uap->fd, uap->offset, uap->len));
+}
+
+int
+kern_posix_fallocate(int fd, off_t offset, off_t len)
+{
+	struct thread *td = curthread;
+	struct vnode *vp;
+	struct file *fp;
+	int error;
+
+	if (offset < 0 || len <= 0)
+		return (EINVAL);
+	/* Check for wrap. */
+	if (offset > OFF_MAX - len)
+		return (EFBIG);
+
+	fp = holdfp(td, fd, -1);
+	if (fp == NULL)
+		return (EBADF);
+
+	switch (fp->f_type) {
+	case DTYPE_VNODE:
+		break;
+	case DTYPE_PIPE:
+	case DTYPE_FIFO:
+		error = ESPIPE;
+		goto out;
+	default:
+		error = ENODEV;
+		goto out;
+	}
+
+	if ((fp->f_flag & FWRITE) == 0) {
+		error = EBADF;
+		goto out;
+	}
+
+	vp = fp->f_data;
+	if (vp->v_type != VREG) {
+		error = ENODEV;
+		goto out;
+	}
+
+	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
+	error = VOP_ALLOCATE(vp, offset, len);
+	vn_unlock(vp);
+out:
+	dropfp(td, fd, fp);
+	return (error);
+}
diff --git a/sys/kern/vfs_vopops.c b/sys/kern/vfs_vopops.c
index e0c501634a..bce106b9cd 100644
--- a/sys/kern/vfs_vopops.c
+++ b/sys/kern/vfs_vopops.c
@@ -134,6 +134,7 @@ VNODEOP_DESC_INIT(getextattr);
 VNODEOP_DESC_INIT(setextattr);
 VNODEOP_DESC_INIT(mountctl);
 VNODEOP_DESC_INIT(markatime);
+VNODEOP_DESC_INIT(allocate);
 
 VNODEOP_DESC_INIT(nresolve);
 VNODEOP_DESC_INIT(nlookupdotdot);
@@ -1309,6 +1310,29 @@ vop_markatime(struct vop_ops *ops, struct vnode *vp, struct ucred *cred)
 	return(error);
 }
 
+/*
+ * MPSAFE
+ */
+int
+vop_allocate(struct vop_ops *ops, struct vnode *vp, off_t offset, off_t len)
+{
+	struct vop_allocate_args ap;
+	VFS_MPLOCK_DECLARE;
+	int error;
+
+	ap.a_head.a_desc = &vop_allocate_desc;
+	ap.a_head.a_ops = ops;
+	ap.a_vp = vp;
+	ap.a_offset = 0;
+	ap.a_len = len;
+
+	VFS_MPLOCK(vp->v_mount);
+	DO_OPS(ops, error, &ap, vop_allocate);
+	VFS_MPUNLOCK();
+
+	return(error);
+}
+
 /*
  * NEW API FUNCTIONS
  *
@@ -2058,6 +2082,15 @@ vop_markatime_ap(struct vop_markatime_args *ap)
 	return(error);
 }
 
+int
+vop_allocate_ap(struct vop_allocate_args *ap)
+{
+	int error;
+
+	DO_OPS(ap->a_head.a_ops, error, ap, vop_allocate);
+	return(error);
+}
+
 int
 vop_nresolve_ap(struct vop_nresolve_args *ap)
 {
diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h
index 1fe4277b63..be162abdb1 100644
--- a/sys/sys/fcntl.h
+++ b/sys/sys/fcntl.h
@@ -341,10 +341,8 @@ int	flock(int, int);
 #endif /* __BSD_VISIBLE */
 #if __POSIX_VISIBLE >= 200112
 int	posix_fadvise(int, off_t, off_t, int);
-#if 0 /* XXX missing */
 int	posix_fallocate(int, off_t, off_t);
 #endif
-#endif
 __END_DECLS
 #endif
 
diff --git a/sys/sys/kern_syscall.h b/sys/sys/kern_syscall.h
index 5ccac86663..e3e3079511 100644
--- a/sys/sys/kern_syscall.h
+++ b/sys/sys/kern_syscall.h
@@ -169,6 +169,7 @@ int kern_unlink(struct nlookupdata *nd);
 int kern_utimensat(struct nlookupdata *nd, const struct timespec *ts, int flag);
 int kern_utimes(struct nlookupdata *nd, struct timeval *tptr);
 struct uuid *kern_uuidgen(struct uuid *store, size_t count);
+int kern_posix_fallocate(int fd, off_t offset, off_t len);
 
 /*
  * Prototypes for syscalls in kern/kern_time.c
diff --git a/sys/sys/param.h b/sys/sys/param.h
index 648788a89d..3f1783d8c6 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -231,9 +231,10 @@
  * 600101 - kernel_{map,pmap,object} and {buffer,clean,pager}_map globals
  *          become pointers
  * 600102 - add nvmm(4) and libnvmm(3)
+ * 600103 - add posix_fallocate()
  */
 #undef __DragonFly_version
-#define __DragonFly_version 600102	/* propagated to newvers */
+#define __DragonFly_version 600103	/* propagated to newvers */
 
 #include <sys/_null.h>
 
diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h
index 25851565f3..2fad17656b 100644
--- a/sys/sys/syscall.h
+++ b/sys/sys/syscall.h
@@ -388,4 +388,5 @@
 #define	SYS_getrandom	550
 #define	SYS___realpath	551
 #define	SYS_fexecve	552
-#define	SYS_MAXSYSCALL	553
+#define	SYS_posix_fallocate	553
+#define	SYS_MAXSYSCALL	554
diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk
index fb4ed95c66..6aa0ade546 100644
--- a/sys/sys/syscall.mk
+++ b/sys/sys/syscall.mk
@@ -315,4 +315,5 @@ MIASM =  \
 	lwp_getname.o \
 	getrandom.o \
 	__realpath.o \
-	fexecve.o
+	fexecve.o \
+	posix_fallocate.o
diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h
index 6d97609fb9..b23e50ed4e 100644
--- a/sys/sys/sysproto.h
+++ b/sys/sys/sysproto.h
@@ -1440,6 +1440,11 @@ struct	fexecve_args {
 	char **	argv;	char argv_[PAD_(char **)];
 	char **	envv;	char envv_[PAD_(char **)];
 };
+struct	posix_fallocate_args {
+	int	fd;	char fd_[PAD_(int)];
+	off_t	offset;	char offset_[PAD_(off_t)];
+	off_t	len;	char len_[PAD_(off_t)];
+};
 
 #undef PAD_
 
@@ -1766,6 +1771,7 @@ int	sys_lwp_getname (struct sysmsg *sysmsg, const struct lwp_getname_args *);
 int	sys_getrandom (struct sysmsg *sysmsg, const struct getrandom_args *);
 int	sys___realpath (struct sysmsg *sysmsg, const struct __realpath_args *);
 int	sys_fexecve (struct sysmsg *sysmsg, const struct fexecve_args *);
+int	sys_posix_fallocate (struct sysmsg *sysmsg, const struct posix_fallocate_args *);
 
 #endif /* _KERNEL */
 
diff --git a/sys/sys/sysunion.h b/sys/sys/sysunion.h
index 55c15d9d64..58c7d2816b 100644
--- a/sys/sys/sysunion.h
+++ b/sys/sys/sysunion.h
@@ -322,4 +322,5 @@ union sysunion {
 	struct	getrandom_args getrandom;
 	struct	__realpath_args __realpath;
 	struct	fexecve_args fexecve;
+	struct	posix_fallocate_args posix_fallocate;
 };
diff --git a/sys/sys/vfsops.h b/sys/sys/vfsops.h
index fe313eb473..865e0b553f 100644
--- a/sys/sys/vfsops.h
+++ b/sys/sys/vfsops.h
@@ -441,6 +441,14 @@ struct vop_markatime_args {
 	struct ucred *a_cred;
 };
 
+struct vop_allocate_args {
+	struct vop_generic_args a_head;
+	int a_op;
+	struct vnode *a_vp;
+	off_t a_offset;
+	off_t a_len;
+};
+
 /*
  * NEW API
  */
@@ -647,6 +655,7 @@ struct vop_ops {
 	int	(*vop_unused08)(void *);
 	int	(*vop_mountctl)(struct vop_mountctl_args *);
 	int	(*vop_markatime)(struct vop_markatime_args *);
+	int	(*vop_allocate)(struct vop_allocate_args *);
 
 	int	(*vop_nresolve)(struct vop_nresolve_args *);
 	int	(*vop_nlookupdotdot)(struct vop_nlookupdotdot_args *);
@@ -721,6 +730,7 @@ union vop_args_union {
 	struct vop_setextattr_args vu_setextattr;
 	struct vop_mountctl_args vu_mountctl;
 	struct vop_markatime_args vu_markatime;
+	struct vop_allocate_args vu_allocate;
 
 	struct vop_nresolve_args vu_nresolve;
 	struct vop_nlookupdotdot_args vu_nlookupdotdot;
@@ -839,6 +849,9 @@ int vop_mountctl(struct vop_ops *ops, struct vnode *vp, int op,
 		struct file *fp, const void *ctl, int ctllen,
 		void *buf, int buflen, int *res);
 int vop_markatime(struct vop_ops *ops, struct vnode *vp, struct ucred *cred);
+int vop_allocate(struct vop_ops *ops, struct vnode *vp, off_t offset,
+		off_t len);
+
 int vop_nresolve(struct vop_ops *ops, struct nchandle *nch,
 		struct vnode *dvp, struct ucred *cred);
 int vop_nlookupdotdot(struct vop_ops *ops, struct vnode *dvp,
@@ -921,6 +934,7 @@ int vop_getextattr_ap(struct vop_getextattr_args *ap);
 int vop_setextattr_ap(struct vop_setextattr_args *ap);
 int vop_mountctl_ap(struct vop_mountctl_args *ap);
 int vop_markatime_ap(struct vop_markatime_args *ap);
+int vop_allocate_ap(struct vop_allocate_args *ap);
 
 int vop_nresolve_ap(struct vop_nresolve_args *ap);
 int vop_nlookupdotdot_ap(struct vop_nlookupdotdot_args *ap);
@@ -984,6 +998,7 @@ extern struct syslink_desc vop_getextattr_desc;
 extern struct syslink_desc vop_setextattr_desc;
 extern struct syslink_desc vop_mountctl_desc;
 extern struct syslink_desc vop_markatime_desc;
+extern struct syslink_desc vop_allocate_desc;
 
 extern struct syslink_desc vop_nresolve_desc;
 extern struct syslink_desc vop_nlookupdotdot_desc;
@@ -1084,6 +1099,8 @@ extern struct syslink_desc vop_nrename_desc;
 	vop_setextattr(*(vp)->v_ops, vp, attrnamespace, attrname, uio, cred)
 #define VOP_MARKATIME(vp, cred)			\
 	vop_markatime(*(vp)->v_ops, vp, cred)
+#define VOP_ALLOCATE(vp, offset, len)		\
+	vop_allocate(*(vp)->v_ops, vp, offset, len)
 /* no VOP_VFSSET() */
 /* VOP_STRATEGY - does not exist, use vn_strategy() */
 
diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h
index b51c0415fb..d99078d016 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -528,6 +528,7 @@ int	vop_stdmountctl(struct vop_mountctl_args *ap);
 int	vop_stdgetpages(struct vop_getpages_args *ap);
 int	vop_stdputpages(struct vop_putpages_args *ap);
 int	vop_stdmarkatime(struct vop_markatime_args *ap);
+int	vop_stdallocate(struct vop_allocate_args *ap);
 int	vop_stdnoread(struct vop_read_args *ap);
 int	vop_stdnowrite(struct vop_write_args *ap);
 int	vop_stdpathconf (struct vop_pathconf_args *ap);
-- 
2.28.0

