Make ftruncate a 'struct file' operation rather than a vnode operation.
This makes it possible to support ftruncate() on non-vnode file types in the future. - 'struct fileops' grows a 'fo_truncate' method to handle an ftruncate() on a given file descriptor. - ftruncate() moves to kern/sys_generic.c and now just fetches a file object and invokes fo_truncate(). - The vnode-specific portions of ftruncate() move to vn_truncate() in vfs_vnops.c which implements fo_truncate() for vnode file types. - Non-vnode file types return EINVAL in their fo_truncate() method. Submitted by: rwatson
This commit is contained in:
parent
1b130ab327
commit
f8a246b979
@ -90,6 +90,7 @@ static struct cdev *dt_ptm, *dt_arp, *dt_icmp, *dt_ip, *dt_tcp, *dt_udp,
|
||||
static struct fileops svr4_netops = {
|
||||
.fo_read = soo_read,
|
||||
.fo_write = soo_write,
|
||||
.fo_truncate = soo_truncate,
|
||||
.fo_ioctl = soo_ioctl,
|
||||
.fo_poll = soo_poll,
|
||||
.fo_kqfilter = soo_kqfilter,
|
||||
|
@ -1278,6 +1278,13 @@ devfs_symlink(struct vop_symlink_args *ap)
|
||||
return (devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp, td));
|
||||
}
|
||||
|
||||
static int
|
||||
devfs_truncate_f(struct file *fp, off_t length, struct ucred *cred, struct thread *td)
|
||||
{
|
||||
|
||||
return (vnops.fo_truncate(fp, length, cred, td));
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
devfs_write_f(struct file *fp, struct uio *uio, struct ucred *cred, int flags, struct thread *td)
|
||||
@ -1322,6 +1329,7 @@ dev2udev(struct cdev *x)
|
||||
static struct fileops devfs_ops_f = {
|
||||
.fo_read = devfs_read_f,
|
||||
.fo_write = devfs_write_f,
|
||||
.fo_truncate = devfs_truncate_f,
|
||||
.fo_ioctl = devfs_ioctl_f,
|
||||
.fo_poll = devfs_poll_f,
|
||||
.fo_kqfilter = devfs_kqfilter_f,
|
||||
|
@ -61,10 +61,12 @@ static fo_poll_t fifo_poll_f;
|
||||
static fo_kqfilter_t fifo_kqfilter_f;
|
||||
static fo_stat_t fifo_stat_f;
|
||||
static fo_close_t fifo_close_f;
|
||||
static fo_truncate_t fifo_truncate_f;
|
||||
|
||||
struct fileops fifo_ops_f = {
|
||||
.fo_read = fifo_read_f,
|
||||
.fo_write = fifo_write_f,
|
||||
.fo_truncate = fifo_truncate_f,
|
||||
.fo_ioctl = fifo_ioctl_f,
|
||||
.fo_poll = fifo_poll_f,
|
||||
.fo_kqfilter = fifo_kqfilter_f,
|
||||
@ -723,6 +725,13 @@ fifo_stat_f(struct file *fp, struct stat *sb, struct ucred *cred, struct thread
|
||||
return (vnops.fo_stat(fp, sb, cred, td));
|
||||
}
|
||||
|
||||
static int
|
||||
fifo_truncate_f(struct file *fp, off_t length, struct ucred *cred, struct thread *td)
|
||||
{
|
||||
|
||||
return (vnops.fo_truncate(fp, length, cred, td));
|
||||
}
|
||||
|
||||
static int
|
||||
fifo_write_f(struct file *fp, struct uio *uio, struct ucred *cred, int flags, struct thread *td)
|
||||
{
|
||||
|
@ -2759,6 +2759,13 @@ badfo_readwrite(struct file *fp, struct uio *uio, struct ucred *active_cred, int
|
||||
return (EBADF);
|
||||
}
|
||||
|
||||
static int
|
||||
badfo_truncate(struct file *fp, off_t length, struct ucred *active_cred, struct thread *td)
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
static int
|
||||
badfo_ioctl(struct file *fp, u_long com, void *data, struct ucred *active_cred, struct thread *td)
|
||||
{
|
||||
@ -2797,6 +2804,7 @@ badfo_close(struct file *fp, struct thread *td)
|
||||
struct fileops badfileops = {
|
||||
.fo_read = badfo_readwrite,
|
||||
.fo_write = badfo_readwrite,
|
||||
.fo_truncate = badfo_truncate,
|
||||
.fo_ioctl = badfo_ioctl,
|
||||
.fo_poll = badfo_poll,
|
||||
.fo_kqfilter = badfo_kqfilter,
|
||||
|
@ -105,6 +105,7 @@ static void kqueue_fo_release(int filt);
|
||||
|
||||
static fo_rdwr_t kqueue_read;
|
||||
static fo_rdwr_t kqueue_write;
|
||||
static fo_truncate_t kqueue_truncate;
|
||||
static fo_ioctl_t kqueue_ioctl;
|
||||
static fo_poll_t kqueue_poll;
|
||||
static fo_kqfilter_t kqueue_kqfilter;
|
||||
@ -114,6 +115,7 @@ static fo_close_t kqueue_close;
|
||||
static struct fileops kqueueops = {
|
||||
.fo_read = kqueue_read,
|
||||
.fo_write = kqueue_write,
|
||||
.fo_truncate = kqueue_truncate,
|
||||
.fo_ioctl = kqueue_ioctl,
|
||||
.fo_poll = kqueue_poll,
|
||||
.fo_kqfilter = kqueue_kqfilter,
|
||||
@ -1322,6 +1324,15 @@ kqueue_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static int
|
||||
kqueue_truncate(struct file *fp, off_t length, struct ucred *active_cred,
|
||||
struct thread *td)
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/*ARGSUSED*/
|
||||
static int
|
||||
kqueue_ioctl(struct file *fp, u_long cmd, void *data,
|
||||
|
@ -52,6 +52,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/socketvar.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/ktr.h>
|
||||
#include <sys/limits.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/poll.h>
|
||||
@ -69,7 +70,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/ktrace.h>
|
||||
#endif
|
||||
|
||||
#include <sys/ktr.h>
|
||||
#include <security/audit/audit.h>
|
||||
|
||||
static MALLOC_DEFINE(M_IOCTLOPS, "ioctlops", "ioctl data buffer");
|
||||
static MALLOC_DEFINE(M_SELECT, "select", "select() buffer");
|
||||
@ -544,6 +545,70 @@ dofilewrite(td, fd, fp, auio, offset, flags)
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Truncate a file given a file descriptor.
|
||||
*
|
||||
* Can't use fget_write() here, since must return EINVAL and not EBADF if the
|
||||
* descriptor isn't writable.
|
||||
*/
|
||||
int
|
||||
kern_ftruncate(td, fd, length)
|
||||
struct thread *td;
|
||||
int fd;
|
||||
off_t length;
|
||||
{
|
||||
struct file *fp;
|
||||
int error;
|
||||
|
||||
AUDIT_ARG(fd, fd);
|
||||
if (length < 0)
|
||||
return (EINVAL);
|
||||
error = fget(td, fd, &fp);
|
||||
if (error)
|
||||
return (error);
|
||||
AUDIT_ARG(file, td->td_proc, fp);
|
||||
if (!(fp->f_flag & FWRITE)) {
|
||||
fdrop(fp, td);
|
||||
return (EINVAL);
|
||||
}
|
||||
error = fo_truncate(fp, length, td->td_ucred, td);
|
||||
fdrop(fp, td);
|
||||
return (error);
|
||||
}
|
||||
|
||||
#ifndef _SYS_SYSPROTO_H_
|
||||
struct ftruncate_args {
|
||||
int fd;
|
||||
int pad;
|
||||
off_t length;
|
||||
};
|
||||
#endif
|
||||
int
|
||||
ftruncate(td, uap)
|
||||
struct thread *td;
|
||||
struct ftruncate_args *uap;
|
||||
{
|
||||
|
||||
return (kern_ftruncate(td, uap->fd, uap->length));
|
||||
}
|
||||
|
||||
#if defined(COMPAT_43)
|
||||
#ifndef _SYS_SYSPROTO_H_
|
||||
struct oftruncate_args {
|
||||
int fd;
|
||||
long length;
|
||||
};
|
||||
#endif
|
||||
int
|
||||
oftruncate(td, uap)
|
||||
struct thread *td;
|
||||
struct oftruncate_args *uap;
|
||||
{
|
||||
|
||||
return (kern_ftruncate(td, uap->fd, uap->length));
|
||||
}
|
||||
#endif /* COMPAT_43 */
|
||||
|
||||
#ifndef _SYS_SYSPROTO_H_
|
||||
struct ioctl_args {
|
||||
int fd;
|
||||
|
@ -140,6 +140,7 @@ __FBSDID("$FreeBSD$");
|
||||
*/
|
||||
static fo_rdwr_t pipe_read;
|
||||
static fo_rdwr_t pipe_write;
|
||||
static fo_truncate_t pipe_truncate;
|
||||
static fo_ioctl_t pipe_ioctl;
|
||||
static fo_poll_t pipe_poll;
|
||||
static fo_kqfilter_t pipe_kqfilter;
|
||||
@ -149,6 +150,7 @@ static fo_close_t pipe_close;
|
||||
static struct fileops pipeops = {
|
||||
.fo_read = pipe_read,
|
||||
.fo_write = pipe_write,
|
||||
.fo_truncate = pipe_truncate,
|
||||
.fo_ioctl = pipe_ioctl,
|
||||
.fo_poll = pipe_poll,
|
||||
.fo_kqfilter = pipe_kqfilter,
|
||||
@ -1230,6 +1232,18 @@ pipe_write(fp, uio, active_cred, flags, td)
|
||||
return (error);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
static int
|
||||
pipe_truncate(fp, length, active_cred, td)
|
||||
struct file *fp;
|
||||
off_t length;
|
||||
struct ucred *active_cred;
|
||||
struct thread *td;
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* we implement a very minimal set of ioctls for compatibility with sockets.
|
||||
*/
|
||||
|
@ -59,6 +59,7 @@ __FBSDID("$FreeBSD$");
|
||||
struct fileops socketops = {
|
||||
.fo_read = soo_read,
|
||||
.fo_write = soo_write,
|
||||
.fo_truncate = soo_truncate,
|
||||
.fo_ioctl = soo_ioctl,
|
||||
.fo_poll = soo_poll,
|
||||
.fo_kqfilter = soo_kqfilter,
|
||||
@ -109,6 +110,14 @@ soo_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
|
||||
return (error);
|
||||
}
|
||||
|
||||
int
|
||||
soo_truncate(struct file *fp, off_t length, struct ucred *active_cred,
|
||||
struct thread *td)
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
int
|
||||
soo_ioctl(struct file *fp, u_long cmd, void *data, struct ucred *active_cred,
|
||||
struct thread *td)
|
||||
|
@ -2316,6 +2316,14 @@ mqf_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
|
||||
return (EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static int
|
||||
mqf_truncate(struct file *fp, off_t length, struct ucred *active_cred,
|
||||
struct thread *td)
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
static int
|
||||
mqf_ioctl(struct file *fp, u_long cmd, void *data,
|
||||
struct ucred *active_cred, struct thread *td)
|
||||
@ -2433,6 +2441,7 @@ filt_mqwrite(struct knote *kn, long hint)
|
||||
static struct fileops mqueueops = {
|
||||
.fo_read = mqf_read,
|
||||
.fo_write = mqf_write,
|
||||
.fo_truncate = mqf_truncate,
|
||||
.fo_ioctl = mqf_ioctl,
|
||||
.fo_poll = mqf_poll,
|
||||
.fo_kqfilter = mqf_kqfilter,
|
||||
|
@ -3086,68 +3086,6 @@ kern_truncate(struct thread *td, char *path, enum uio_seg pathseg, off_t length)
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Truncate a file given a file descriptor.
|
||||
*/
|
||||
#ifndef _SYS_SYSPROTO_H_
|
||||
struct ftruncate_args {
|
||||
int fd;
|
||||
int pad;
|
||||
off_t length;
|
||||
};
|
||||
#endif
|
||||
int
|
||||
ftruncate(td, uap)
|
||||
struct thread *td;
|
||||
register struct ftruncate_args /* {
|
||||
int fd;
|
||||
int pad;
|
||||
off_t length;
|
||||
} */ *uap;
|
||||
{
|
||||
struct mount *mp;
|
||||
struct vattr vattr;
|
||||
struct vnode *vp;
|
||||
struct file *fp;
|
||||
int vfslocked;
|
||||
int error;
|
||||
|
||||
AUDIT_ARG(fd, uap->fd);
|
||||
if (uap->length < 0)
|
||||
return(EINVAL);
|
||||
if ((error = getvnode(td->td_proc->p_fd, uap->fd, &fp)) != 0)
|
||||
return (error);
|
||||
if ((fp->f_flag & FWRITE) == 0) {
|
||||
fdrop(fp, td);
|
||||
return (EINVAL);
|
||||
}
|
||||
vp = fp->f_vnode;
|
||||
vfslocked = VFS_LOCK_GIANT(vp->v_mount);
|
||||
if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0)
|
||||
goto drop;
|
||||
VOP_LEASE(vp, td, td->td_ucred, LEASE_WRITE);
|
||||
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td);
|
||||
AUDIT_ARG(vnode, vp, ARG_VNODE1);
|
||||
if (vp->v_type == VDIR)
|
||||
error = EISDIR;
|
||||
#ifdef MAC
|
||||
else if ((error = mac_vnode_check_write(td->td_ucred, fp->f_cred,
|
||||
vp))) {
|
||||
}
|
||||
#endif
|
||||
else if ((error = vn_writechk(vp)) == 0) {
|
||||
VATTR_NULL(&vattr);
|
||||
vattr.va_size = uap->length;
|
||||
error = VOP_SETATTR(vp, &vattr, fp->f_cred, td);
|
||||
}
|
||||
VOP_UNLOCK(vp, 0, td);
|
||||
vn_finished_write(mp);
|
||||
drop:
|
||||
VFS_UNLOCK_GIANT(vfslocked);
|
||||
fdrop(fp, td);
|
||||
return (error);
|
||||
}
|
||||
|
||||
#if defined(COMPAT_43)
|
||||
/*
|
||||
* Truncate a file given its path name.
|
||||
@ -3176,34 +3114,6 @@ otruncate(td, uap)
|
||||
nuap.length = uap->length;
|
||||
return (truncate(td, &nuap));
|
||||
}
|
||||
|
||||
/*
|
||||
* Truncate a file given a file descriptor.
|
||||
*/
|
||||
#ifndef _SYS_SYSPROTO_H_
|
||||
struct oftruncate_args {
|
||||
int fd;
|
||||
long length;
|
||||
};
|
||||
#endif
|
||||
int
|
||||
oftruncate(td, uap)
|
||||
struct thread *td;
|
||||
register struct oftruncate_args /* {
|
||||
int fd;
|
||||
long length;
|
||||
} */ *uap;
|
||||
{
|
||||
struct ftruncate_args /* {
|
||||
int fd;
|
||||
int pad;
|
||||
off_t length;
|
||||
} */ nuap;
|
||||
|
||||
nuap.fd = uap->fd;
|
||||
nuap.length = uap->length;
|
||||
return (ftruncate(td, &nuap));
|
||||
}
|
||||
#endif /* COMPAT_43 */
|
||||
|
||||
/* Versions with the pad argument */
|
||||
|
@ -66,6 +66,7 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
static fo_rdwr_t vn_read;
|
||||
static fo_rdwr_t vn_write;
|
||||
static fo_truncate_t vn_truncate;
|
||||
static fo_ioctl_t vn_ioctl;
|
||||
static fo_poll_t vn_poll;
|
||||
static fo_kqfilter_t vn_kqfilter;
|
||||
@ -75,6 +76,7 @@ static fo_close_t vn_closefile;
|
||||
struct fileops vnops = {
|
||||
.fo_read = vn_read,
|
||||
.fo_write = vn_write,
|
||||
.fo_truncate = vn_truncate,
|
||||
.fo_ioctl = vn_ioctl,
|
||||
.fo_poll = vn_poll,
|
||||
.fo_kqfilter = vn_kqfilter,
|
||||
@ -606,6 +608,53 @@ unlock:
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* File table truncate routine.
|
||||
*/
|
||||
static int
|
||||
vn_truncate(fp, length, active_cred, td)
|
||||
struct file *fp;
|
||||
off_t length;
|
||||
struct ucred *active_cred;
|
||||
struct thread *td;
|
||||
{
|
||||
struct vattr vattr;
|
||||
struct mount *mp;
|
||||
struct vnode *vp;
|
||||
int vfslocked;
|
||||
int error;
|
||||
|
||||
vp = fp->f_vnode;
|
||||
vfslocked = VFS_LOCK_GIANT(vp->v_mount);
|
||||
error = vn_start_write(vp, &mp, V_WAIT | PCATCH);
|
||||
if (error) {
|
||||
VFS_UNLOCK_GIANT(vfslocked);
|
||||
return (error);
|
||||
}
|
||||
VOP_LEASE(vp, td, active_cred, LEASE_WRITE);
|
||||
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td);
|
||||
if (vp->v_type == VDIR) {
|
||||
error = EISDIR;
|
||||
goto out;
|
||||
}
|
||||
#ifdef MAC
|
||||
error = mac_vnode_check_write(active_cred, fp->f_cred, vp);
|
||||
if (error)
|
||||
goto out;
|
||||
#endif
|
||||
error = vn_writechk(vp);
|
||||
if (error == 0) {
|
||||
VATTR_NULL(&vattr);
|
||||
vattr.va_size = length;
|
||||
error = VOP_SETATTR(vp, &vattr, fp->f_cred, td);
|
||||
}
|
||||
out:
|
||||
VOP_UNLOCK(vp, 0, td);
|
||||
vn_finished_write(mp);
|
||||
VFS_UNLOCK_GIANT(vfslocked);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* File table vnode stat routine.
|
||||
*/
|
||||
|
@ -86,6 +86,8 @@ struct fcrypt {
|
||||
|
||||
static int cryptof_rw(struct file *fp, struct uio *uio,
|
||||
struct ucred *cred, int flags, struct thread *);
|
||||
static int cryptof_truncate(struct file *, off_t, struct ucred *,
|
||||
struct thread *);
|
||||
static int cryptof_ioctl(struct file *, u_long, void *,
|
||||
struct ucred *, struct thread *);
|
||||
static int cryptof_poll(struct file *, int, struct ucred *, struct thread *);
|
||||
@ -97,6 +99,7 @@ static int cryptof_close(struct file *, struct thread *);
|
||||
static struct fileops cryptofops = {
|
||||
.fo_read = cryptof_rw,
|
||||
.fo_write = cryptof_rw,
|
||||
.fo_truncate = cryptof_truncate,
|
||||
.fo_ioctl = cryptof_ioctl,
|
||||
.fo_poll = cryptof_poll,
|
||||
.fo_kqfilter = cryptof_kqfilter,
|
||||
@ -129,6 +132,17 @@ cryptof_rw(
|
||||
return (EIO);
|
||||
}
|
||||
|
||||
static int
|
||||
cryptof_truncate(
|
||||
struct file *fp,
|
||||
off_t length,
|
||||
struct ucred *active_cred,
|
||||
struct thread *td)
|
||||
{
|
||||
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check a crypto identifier to see if it requested
|
||||
* a software device/driver. This can be done either
|
||||
|
@ -69,6 +69,8 @@ typedef int fo_rdwr_t(struct file *fp, struct uio *uio,
|
||||
struct ucred *active_cred, int flags,
|
||||
struct thread *td);
|
||||
#define FOF_OFFSET 1 /* Use the offset in uio argument */
|
||||
typedef int fo_truncate_t(struct file *fp, off_t length,
|
||||
struct ucred *active_cred, struct thread *td);
|
||||
typedef int fo_ioctl_t(struct file *fp, u_long com, void *data,
|
||||
struct ucred *active_cred, struct thread *td);
|
||||
typedef int fo_poll_t(struct file *fp, int events,
|
||||
@ -82,6 +84,7 @@ typedef int fo_flags_t;
|
||||
struct fileops {
|
||||
fo_rdwr_t *fo_read;
|
||||
fo_rdwr_t *fo_write;
|
||||
fo_truncate_t *fo_truncate;
|
||||
fo_ioctl_t *fo_ioctl;
|
||||
fo_poll_t *fo_poll;
|
||||
fo_kqfilter_t *fo_kqfilter;
|
||||
@ -175,6 +178,7 @@ int _fdrop(struct file *fp, struct thread *td);
|
||||
*/
|
||||
fo_rdwr_t soo_read;
|
||||
fo_rdwr_t soo_write;
|
||||
fo_truncate_t soo_truncate;
|
||||
fo_ioctl_t soo_ioctl;
|
||||
fo_poll_t soo_poll;
|
||||
fo_kqfilter_t soo_kqfilter;
|
||||
@ -195,6 +199,7 @@ void fputsock(struct socket *sp);
|
||||
|
||||
static __inline fo_rdwr_t fo_read;
|
||||
static __inline fo_rdwr_t fo_write;
|
||||
static __inline fo_truncate_t fo_truncate;
|
||||
static __inline fo_ioctl_t fo_ioctl;
|
||||
static __inline fo_poll_t fo_poll;
|
||||
static __inline fo_kqfilter_t fo_kqfilter;
|
||||
@ -225,6 +230,17 @@ fo_write(fp, uio, active_cred, flags, td)
|
||||
return ((*fp->f_ops->fo_write)(fp, uio, active_cred, flags, td));
|
||||
}
|
||||
|
||||
static __inline int
|
||||
fo_truncate(fp, length, active_cred, td)
|
||||
struct file *fp;
|
||||
off_t length;
|
||||
struct ucred *active_cred;
|
||||
struct thread *td;
|
||||
{
|
||||
|
||||
return ((*fp->f_ops->fo_truncate)(fp, length, active_cred, td));
|
||||
}
|
||||
|
||||
static __inline int
|
||||
fo_ioctl(fp, com, data, active_cred, td)
|
||||
struct file *fp;
|
||||
|
@ -82,6 +82,7 @@ int kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg);
|
||||
int kern_fhstatfs(struct thread *td, fhandle_t fh, struct statfs *buf);
|
||||
int kern_fstat(struct thread *td, int fd, struct stat *sbp);
|
||||
int kern_fstatfs(struct thread *td, int fd, struct statfs *buf);
|
||||
int kern_ftruncate(struct thread *td, int fd, off_t length);
|
||||
int kern_futimes(struct thread *td, int fd, struct timeval *tptr,
|
||||
enum uio_seg tptrseg);
|
||||
int kern_getfsstat(struct thread *td, struct statfs **buf, size_t bufsize,
|
||||
|
Loading…
x
Reference in New Issue
Block a user