- Fix handling of "new" style of ioctl in compatiblity mode [1];

- Reorganize code and reduce diff from upstream;
 - Improve forward compatibility shims for previous kernel;

Reported by:	sbruno [1]
X-MFC-With:	r268075
This commit is contained in:
Xin LI 2014-07-01 20:57:39 +00:00
parent c59e4cc34d
commit 55f6421982
3 changed files with 127 additions and 89 deletions

View File

@ -72,16 +72,23 @@ zcmd_ioctl(int fd, int request, zfs_cmd_t *zc)
if (zfs_ioctl_version == ZFS_IOCVER_UNDEF)
zfs_ioctl_version = get_zfs_ioctl_version();
if (zfs_ioctl_version == ZFS_IOCVER_LZC)
cflag = ZFS_CMD_COMPAT_LZC;
else if (zfs_ioctl_version == ZFS_IOCVER_DEADMAN)
cflag = ZFS_CMD_COMPAT_DEADMAN;
/*
* If vfs.zfs.version.ioctl is not defined, assume we have v28
* compatible binaries and use vfs.zfs.version.spa to test for v15
*/
if (zfs_ioctl_version < ZFS_IOCVER_DEADMAN) {
if (zfs_ioctl_version >= ZFS_IOCVER_DEADMAN) {
switch (zfs_ioctl_version) {
case ZFS_IOCVER_ZCMD:
cflag = ZFS_CMD_COMPAT_ZCMD;
break;
case ZFS_IOCVER_LZC:
cflag = ZFS_CMD_COMPAT_LZC;
break;
case ZFS_IOCVER_DEADMAN:
cflag = ZFS_CMD_COMPAT_DEADMAN;
break;
}
} else {
/*
* If vfs.zfs.version.ioctl is not defined, assume we have v28
* compatible binaries and use vfs.zfs.version.spa to test for v15
*/
cflag = ZFS_CMD_COMPAT_V28;
if (zfs_spa_version < 0)

View File

@ -697,6 +697,12 @@ zcmd_ioctl_compat(int fd, int request, zfs_cmd_t *zc, const int cflag)
zp.zfs_cmd_size = sizeof(zfs_cmd_t);
zp.zfs_ioctl_version = ZFS_IOCVER_CURRENT;
return (ioctl(fd, ncmd, &zp));
case ZFS_CMD_COMPAT_ZCMD:
ncmd = _IOWR('Z', request, struct zfs_iocparm);
zp.zfs_cmd = (uint64_t)zc;
zp.zfs_cmd_size = sizeof(zfs_cmd_zcmd_t);
zp.zfs_ioctl_version = ZFS_IOCVER_ZCMD;
return (ioctl(fd, ncmd, &zp));
case ZFS_CMD_COMPAT_LZC:
ncmd = _IOWR('Z', request, struct zfs_cmd);
return (ioctl(fd, ncmd, zc));
@ -794,7 +800,8 @@ zfs_ioctl_compat_innvl(zfs_cmd_t *zc, nvlist_t * innvl, const int vec,
char *poolname, *snapname;
int err;
if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC)
if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
cflag == ZFS_CMD_COMPAT_ZCMD)
goto out;
switch (vec) {
@ -945,7 +952,8 @@ zfs_ioctl_compat_outnvl(zfs_cmd_t *zc, nvlist_t * outnvl, const int vec,
{
nvlist_t *tmpnvl;
if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC)
if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
cflag == ZFS_CMD_COMPAT_ZCMD)
return (outnvl);
switch (vec) {

View File

@ -24,6 +24,7 @@
* Copyright (c) 2011-2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
* All rights reserved.
* Copyright 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
* Copyright 2014 Xin Li <delphij@FreeBSD.org>. All rights reserved.
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
* Copyright (c) 2013 by Delphix. All rights reserved.
@ -5922,6 +5923,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
zfs_iocparm_t *zc_iocparm;
int cflag, cmd, oldvecnum;
boolean_t newioc, compat;
void *compat_zc = NULL;
cred_t *cr = td->td_ucred;
#endif
const zfs_ioc_vec_t *vec;
@ -5930,10 +5932,10 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
cflag = ZFS_CMD_COMPAT_NONE;
compat = B_FALSE;
newioc = B_TRUE;
newioc = B_TRUE; /* "new" style (zfs_iocparm_t) ioctl */
len = IOCPARM_LEN(zcmd);
cmd = zcmd & 0xff;
vecnum = cmd = zcmd & 0xff;
/*
* Check if we are talking to supported older binaries
@ -5941,96 +5943,112 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
*/
if (len != sizeof(zfs_iocparm_t)) {
newioc = B_FALSE;
if (len == sizeof(zfs_cmd_t)) {
cflag = ZFS_CMD_COMPAT_LZC;
vecnum = cmd;
} else if (len == sizeof(zfs_cmd_deadman_t)) {
cflag = ZFS_CMD_COMPAT_DEADMAN;
compat = B_TRUE;
vecnum = cmd;
} else if (len == sizeof(zfs_cmd_v28_t)) {
cflag = ZFS_CMD_COMPAT_V28;
compat = B_TRUE;
vecnum = cmd;
} else if (len == sizeof(zfs_cmd_v15_t)) {
cflag = ZFS_CMD_COMPAT_V15;
compat = B_TRUE;
vecnum = zfs_ioctl_v15_to_v28[cmd];
} else
return (EINVAL);
} else
compat = B_TRUE;
vecnum = cmd;
switch (len) {
case sizeof(zfs_cmd_zcmd_t):
cflag = ZFS_CMD_COMPAT_LZC;
break;
case sizeof(zfs_cmd_deadman_t):
cflag = ZFS_CMD_COMPAT_DEADMAN;
break;
case sizeof(zfs_cmd_v28_t):
cflag = ZFS_CMD_COMPAT_V28;
break;
case sizeof(zfs_cmd_v15_t):
cflag = ZFS_CMD_COMPAT_V15;
vecnum = zfs_ioctl_v15_to_v28[cmd];
/*
* Return without further handling
* if the command is blacklisted.
*/
if (vecnum == ZFS_IOC_COMPAT_PASS)
return (0);
else if (vecnum == ZFS_IOC_COMPAT_FAIL)
return (ENOTSUP);
break;
default:
return (EINVAL);
}
}
#ifdef illumos
vecnum = cmd - ZFS_IOC_FIRST;
ASSERT3U(getmajor(dev), ==, ddi_driver_major(zfs_dip));
#endif
if (compat) {
if (vecnum == ZFS_IOC_COMPAT_PASS)
return (0);
else if (vecnum == ZFS_IOC_COMPAT_FAIL)
return (ENOTSUP);
}
/*
* Check if we have sufficient kernel memory allocated
* for the zfs_cmd_t request. Bail out if not so we
* will not access undefined memory region.
*/
if (vecnum >= sizeof (zfs_ioc_vec) / sizeof (zfs_ioc_vec[0]))
return (SET_ERROR(EINVAL));
vec = &zfs_ioc_vec[vecnum];
#ifdef illumos
zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP);
bzero(zc, sizeof(zfs_cmd_t));
#ifdef illumos
error = ddi_copyin((void *)arg, zc, sizeof (zfs_cmd_t), flag);
if (error != 0) {
error = SET_ERROR(EFAULT);
goto out;
}
#else /* !illumos */
/*
* We don't alloc/free zc only if talking to library ioctl version 2
*/
if (cflag != ZFS_CMD_COMPAT_LZC) {
zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP);
bzero(zc, sizeof(zfs_cmd_t));
} else {
zc = (void *)arg;
error = 0;
}
bzero(zc, sizeof(zfs_cmd_t));
if (newioc) {
zc_iocparm = (void *)arg;
if (zc_iocparm->zfs_cmd_size != sizeof(zfs_cmd_t)) {
error = SET_ERROR(EFAULT);
goto out;
}
error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd, zc,
sizeof(zfs_cmd_t), flag);
if (error != 0) {
error = SET_ERROR(EFAULT);
goto out;
}
if (zc_iocparm->zfs_ioctl_version != ZFS_IOCVER_CURRENT) {
compat = B_TRUE;
switch (zc_iocparm->zfs_ioctl_version) {
case ZFS_IOCVER_ZCMD:
cflag = ZFS_CMD_COMPAT_ZCMD;
break;
default:
switch (zc_iocparm->zfs_ioctl_version) {
case ZFS_IOCVER_CURRENT:
if (zc_iocparm->zfs_cmd_size != sizeof(zfs_cmd_t)) {
error = SET_ERROR(EINVAL);
goto out;
}
break;
case ZFS_IOCVER_ZCMD:
if (zc_iocparm->zfs_cmd_size > sizeof(zfs_cmd_t) ||
zc_iocparm->zfs_cmd_size < sizeof(zfs_cmd_zcmd_t)) {
error = SET_ERROR(EFAULT);
goto out;
}
compat = B_TRUE;
cflag = ZFS_CMD_COMPAT_ZCMD;
break;
default:
error = SET_ERROR(EINVAL);
goto out;
/* NOTREACHED */
}
if (compat) {
ASSERT(sizeof(zfs_cmd_t) >= zc_iocparm->zfs_cmd_size);
compat_zc = kmem_zalloc(sizeof(zfs_cmd_t), KM_SLEEP);
bzero(compat_zc, sizeof(zfs_cmd_t));
error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd,
compat_zc, zc_iocparm->zfs_cmd_size, flag);
if (error != 0) {
error = SET_ERROR(EFAULT);
goto out;
}
} else {
error = ddi_copyin((void *)(uintptr_t)zc_iocparm->zfs_cmd,
zc, zc_iocparm->zfs_cmd_size, flag);
if (error != 0) {
error = SET_ERROR(EFAULT);
goto out;
}
}
}
if (compat) {
zfs_cmd_compat_get(zc, arg, cflag);
if (newioc) {
ASSERT(compat_zc != NULL);
zfs_cmd_compat_get(zc, compat_zc, cflag);
} else {
ASSERT(compat_zc == NULL);
zfs_cmd_compat_get(zc, arg, cflag);
}
oldvecnum = vecnum;
error = zfs_ioctl_compat_pre(zc, &vecnum, cflag);
if (error != 0)
@ -6126,7 +6144,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
fnvlist_free(lognv);
/* rewrite outnvl for backwards compatibility */
if (cflag != ZFS_CMD_COMPAT_NONE && cflag != ZFS_CMD_COMPAT_LZC)
if (compat)
outnvl = zfs_ioctl_compat_outnvl(zc, outnvl, vecnum,
cflag);
@ -6151,17 +6169,30 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
out:
nvlist_free(innvl);
if (compat) {
zfs_ioctl_compat_post(zc, cmd, cflag);
zfs_cmd_compat_put(zc, arg, vecnum, cflag);
}
#ifdef illumos
rc = ddi_copyout(zc, (void *)arg, sizeof (zfs_cmd_t), flag);
if (error == 0 && rc != 0)
error = SET_ERROR(EFAULT);
#else
if (newioc) {
if (compat) {
zfs_ioctl_compat_post(zc, cmd, cflag);
if (newioc) {
ASSERT(compat_zc != NULL);
ASSERT(sizeof(zfs_cmd_t) >= zc_iocparm->zfs_cmd_size);
zfs_cmd_compat_put(zc, compat_zc, vecnum, cflag);
rc = ddi_copyout(compat_zc,
(void *)(uintptr_t)zc_iocparm->zfs_cmd,
zc_iocparm->zfs_cmd_size, flag);
if (error == 0 && rc != 0)
error = SET_ERROR(EFAULT);
kmem_free(compat_zc, sizeof (zfs_cmd_t));
} else {
zfs_cmd_compat_put(zc, arg, vecnum, cflag);
}
} else {
ASSERT(newioc);
rc = ddi_copyout(zc, (void *)(uintptr_t)zc_iocparm->zfs_cmd,
sizeof (zfs_cmd_t), flag);
if (error == 0 && rc != 0)
@ -6178,15 +6209,7 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
strfree(saved_poolname);
}
#ifdef illumos
kmem_free(zc, sizeof (zfs_cmd_t));
#else
/*
* We don't alloc/free zc only if talking to library ioctl version 2
*/
if (cflag != ZFS_CMD_COMPAT_LZC)
kmem_free(zc, sizeof (zfs_cmd_t));
#endif
return (error);
}