1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2013 Xin Li <delphij@FreeBSD.org>. All rights reserved.
23 * Copyright 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
24 * Portions Copyright 2005, 2010, Oracle and/or its affiliates.
25 * All rights reserved.
26 * Use is subject to license terms.
27 */
28
29#include <sys/types.h>
30#include <sys/param.h>
31#include <sys/cred.h>
32#include <sys/dmu.h>
33#include <sys/zio.h>
34#include <sys/nvpair.h>
35#include <sys/dsl_deleg.h>
36#include <sys/zfs_ioctl.h>
37#include "zfs_namecheck.h"
38#include <os/freebsd/zfs/sys/zfs_ioctl_compat.h>
39
40/*
41 * FreeBSD zfs_cmd compatibility with older binaries
42 * appropriately remap/extend the zfs_cmd_t structure
43 */
44void
45zfs_cmd_compat_get(zfs_cmd_t *zc, caddr_t addr, const int cflag)
46{
47
48}
49#if 0
50static int
51zfs_ioctl_compat_get_nvlist(uint64_t nvl, size_t size, int iflag,
52    nvlist_t **nvp)
53{
54	char *packed;
55	int error;
56	nvlist_t *list = NULL;
57
58	/*
59	 * Read in and unpack the user-supplied nvlist.
60	 */
61	if (size == 0)
62		return (EINVAL);
63
64#ifdef _KERNEL
65	packed = kmem_alloc(size, KM_SLEEP);
66	if ((error = ddi_copyin((void *)(uintptr_t)nvl, packed, size,
67	    iflag)) != 0) {
68		kmem_free(packed, size);
69		return (error);
70	}
71#else
72	packed = (void *)(uintptr_t)nvl;
73#endif
74
75	error = nvlist_unpack(packed, size, &list, 0);
76
77#ifdef _KERNEL
78	kmem_free(packed, size);
79#endif
80
81	if (error != 0)
82		return (error);
83
84	*nvp = list;
85	return (0);
86}
87
88static int
89zfs_ioctl_compat_put_nvlist(zfs_cmd_t *zc, nvlist_t *nvl)
90{
91	char *packed = NULL;
92	int error = 0;
93	size_t size;
94
95	VERIFY(nvlist_size(nvl, &size, NV_ENCODE_NATIVE) == 0);
96
97#ifdef _KERNEL
98	packed = kmem_alloc(size, KM_SLEEP);
99	VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
100	    KM_SLEEP) == 0);
101
102	if (ddi_copyout(packed,
103	    (void *)(uintptr_t)zc->zc_nvlist_dst, size, zc->zc_iflags) != 0)
104		error = EFAULT;
105	kmem_free(packed, size);
106#else
107	packed = (void *)(uintptr_t)zc->zc_nvlist_dst;
108	VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
109	    0) == 0);
110#endif
111
112	zc->zc_nvlist_dst_size = size;
113	return (error);
114}
115
116static void
117zfs_ioctl_compat_fix_stats_nvlist(nvlist_t *nvl)
118{
119	nvlist_t **child;
120	nvlist_t *nvroot = NULL;
121	vdev_stat_t *vs;
122	uint_t c, children, nelem;
123
124	if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN,
125	    &child, &children) == 0) {
126		for (c = 0; c < children; c++) {
127			zfs_ioctl_compat_fix_stats_nvlist(child[c]);
128		}
129	}
130
131	if (nvlist_lookup_nvlist(nvl, ZPOOL_CONFIG_VDEV_TREE,
132	    &nvroot) == 0)
133		zfs_ioctl_compat_fix_stats_nvlist(nvroot);
134	if ((nvlist_lookup_uint64_array(nvl, "stats",
135	    (uint64_t **)&vs, &nelem) == 0)) {
136		nvlist_add_uint64_array(nvl,
137		    ZPOOL_CONFIG_VDEV_STATS,
138		    (uint64_t *)vs, nelem);
139		nvlist_remove(nvl, "stats",
140		    DATA_TYPE_UINT64_ARRAY);
141	}
142}
143
144
145static int
146zfs_ioctl_compat_fix_stats(zfs_cmd_t *zc, const int nc)
147{
148	nvlist_t *nv, *nvp = NULL;
149	nvpair_t *elem;
150	int error;
151
152	if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
153	    zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
154		return (error);
155
156	if (nc == 5) { /* ZFS_IOC_POOL_STATS */
157		elem = NULL;
158		while ((elem = nvlist_next_nvpair(nv, elem)) != NULL) {
159			if (nvpair_value_nvlist(elem, &nvp) == 0)
160				zfs_ioctl_compat_fix_stats_nvlist(nvp);
161		}
162		elem = NULL;
163	} else
164		zfs_ioctl_compat_fix_stats_nvlist(nv);
165
166	error = zfs_ioctl_compat_put_nvlist(zc, nv);
167
168	nvlist_free(nv);
169
170	return (error);
171}
172
173static int
174zfs_ioctl_compat_pool_get_props(zfs_cmd_t *zc)
175{
176	nvlist_t *nv, *nva = NULL;
177	int error;
178
179	if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
180	    zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
181		return (error);
182
183	if (nvlist_lookup_nvlist(nv, "used", &nva) == 0) {
184		nvlist_add_nvlist(nv, "allocated", nva);
185		nvlist_remove(nv, "used", DATA_TYPE_NVLIST);
186	}
187
188	if (nvlist_lookup_nvlist(nv, "available", &nva) == 0) {
189		nvlist_add_nvlist(nv, "free", nva);
190		nvlist_remove(nv, "available", DATA_TYPE_NVLIST);
191	}
192
193	error = zfs_ioctl_compat_put_nvlist(zc, nv);
194
195	nvlist_free(nv);
196
197	return (error);
198}
199#endif
200
201#ifdef _KERNEL
202int
203zfs_ioctl_compat_pre(zfs_cmd_t *zc, int *vec, const int cflag)
204{
205	int error = 0;
206
207	/* are we creating a clone? */
208	if (*vec == ZFS_IOC_CREATE && zc->zc_value[0] != '\0')
209		*vec = ZFS_IOC_CLONE;
210
211	if (cflag == ZFS_CMD_COMPAT_V15) {
212		switch (*vec) {
213
214		case 7: /* ZFS_IOC_POOL_SCRUB (v15) */
215			zc->zc_cookie = POOL_SCAN_SCRUB;
216			break;
217		}
218	}
219
220	return (error);
221}
222
223void
224zfs_ioctl_compat_post(zfs_cmd_t *zc, int vec, const int cflag)
225{
226	if (cflag == ZFS_CMD_COMPAT_V15) {
227		switch (vec) {
228		case ZFS_IOC_POOL_CONFIGS:
229		case ZFS_IOC_POOL_STATS:
230		case ZFS_IOC_POOL_TRYIMPORT:
231			zfs_ioctl_compat_fix_stats(zc, vec);
232			break;
233		case 41: /* ZFS_IOC_POOL_GET_PROPS (v15) */
234			zfs_ioctl_compat_pool_get_props(zc);
235			break;
236		}
237	}
238}
239
240nvlist_t *
241zfs_ioctl_compat_innvl(zfs_cmd_t *zc, nvlist_t *innvl, const int vec,
242    const int cflag)
243{
244	nvlist_t *nvl, *tmpnvl, *hnvl;
245	nvpair_t *elem;
246	char *poolname, *snapname;
247	int err;
248
249	if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
250	    cflag == ZFS_CMD_COMPAT_ZCMD || cflag == ZFS_CMD_COMPAT_EDBP ||
251	    cflag == ZFS_CMD_COMPAT_RESUME || cflag == ZFS_CMD_COMPAT_INLANES)
252		goto out;
253
254	switch (vec) {
255	case ZFS_IOC_CREATE:
256		nvl = fnvlist_alloc();
257		fnvlist_add_int32(nvl, "type", zc->zc_objset_type);
258		if (innvl != NULL) {
259			fnvlist_add_nvlist(nvl, "props", innvl);
260			nvlist_free(innvl);
261		}
262		return (nvl);
263	break;
264	case ZFS_IOC_CLONE:
265		nvl = fnvlist_alloc();
266		fnvlist_add_string(nvl, "origin", zc->zc_value);
267		if (innvl != NULL) {
268			fnvlist_add_nvlist(nvl, "props", innvl);
269			nvlist_free(innvl);
270		}
271		return (nvl);
272	break;
273	case ZFS_IOC_SNAPSHOT:
274		if (innvl == NULL)
275			goto out;
276		nvl = fnvlist_alloc();
277		fnvlist_add_nvlist(nvl, "props", innvl);
278		tmpnvl = fnvlist_alloc();
279		snapname = kmem_asprintf("%s@%s", zc->zc_name, zc->zc_value);
280		fnvlist_add_boolean(tmpnvl, snapname);
281		kmem_free(snapname, strlen(snapname + 1));
282		/* check if we are doing a recursive snapshot */
283		if (zc->zc_cookie)
284			dmu_get_recursive_snaps_nvl(zc->zc_name, zc->zc_value,
285			    tmpnvl);
286		fnvlist_add_nvlist(nvl, "snaps", tmpnvl);
287		fnvlist_free(tmpnvl);
288		nvlist_free(innvl);
289		/* strip dataset part from zc->zc_name */
290		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
291		return (nvl);
292	break;
293	case ZFS_IOC_SPACE_SNAPS:
294		nvl = fnvlist_alloc();
295		fnvlist_add_string(nvl, "firstsnap", zc->zc_value);
296		if (innvl != NULL)
297			nvlist_free(innvl);
298		return (nvl);
299	break;
300	case ZFS_IOC_DESTROY_SNAPS:
301		if (innvl == NULL && cflag == ZFS_CMD_COMPAT_DEADMAN)
302			goto out;
303		nvl = fnvlist_alloc();
304		if (innvl != NULL) {
305			fnvlist_add_nvlist(nvl, "snaps", innvl);
306		} else {
307			/*
308			 * We are probably called by even older binaries,
309			 * allocate and populate nvlist with recursive
310			 * snapshots
311			 */
312			if (zfs_component_namecheck(zc->zc_value, NULL,
313			    NULL) == 0) {
314				tmpnvl = fnvlist_alloc();
315				if (dmu_get_recursive_snaps_nvl(zc->zc_name,
316				    zc->zc_value, tmpnvl) == 0)
317					fnvlist_add_nvlist(nvl, "snaps",
318					    tmpnvl);
319				nvlist_free(tmpnvl);
320			}
321		}
322		if (innvl != NULL)
323			nvlist_free(innvl);
324		/* strip dataset part from zc->zc_name */
325		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
326		return (nvl);
327	break;
328	case ZFS_IOC_HOLD:
329		nvl = fnvlist_alloc();
330		tmpnvl = fnvlist_alloc();
331		if (zc->zc_cleanup_fd != -1)
332			fnvlist_add_int32(nvl, "cleanup_fd",
333			    (int32_t)zc->zc_cleanup_fd);
334		if (zc->zc_cookie) {
335			hnvl = fnvlist_alloc();
336			if (dmu_get_recursive_snaps_nvl(zc->zc_name,
337			    zc->zc_value, hnvl) == 0) {
338				elem = NULL;
339				while ((elem = nvlist_next_nvpair(hnvl,
340				    elem)) != NULL) {
341					nvlist_add_string(tmpnvl,
342					    nvpair_name(elem), zc->zc_string);
343				}
344			}
345			nvlist_free(hnvl);
346		} else {
347			snapname = kmem_asprintf("%s@%s", zc->zc_name,
348			    zc->zc_value);
349			nvlist_add_string(tmpnvl, snapname, zc->zc_string);
350			kmem_free(snapname, strlen(snapname + 1));
351		}
352		fnvlist_add_nvlist(nvl, "holds", tmpnvl);
353		nvlist_free(tmpnvl);
354		if (innvl != NULL)
355			nvlist_free(innvl);
356		/* strip dataset part from zc->zc_name */
357		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
358		return (nvl);
359	break;
360	case ZFS_IOC_RELEASE:
361		nvl = fnvlist_alloc();
362		tmpnvl = fnvlist_alloc();
363		if (zc->zc_cookie) {
364			hnvl = fnvlist_alloc();
365			if (dmu_get_recursive_snaps_nvl(zc->zc_name,
366			    zc->zc_value, hnvl) == 0) {
367				elem = NULL;
368				while ((elem = nvlist_next_nvpair(hnvl,
369				    elem)) != NULL) {
370					fnvlist_add_boolean(tmpnvl,
371					    zc->zc_string);
372					fnvlist_add_nvlist(nvl,
373					    nvpair_name(elem), tmpnvl);
374				}
375			}
376			nvlist_free(hnvl);
377		} else {
378			snapname = kmem_asprintf("%s@%s", zc->zc_name,
379			    zc->zc_value);
380			fnvlist_add_boolean(tmpnvl, zc->zc_string);
381			fnvlist_add_nvlist(nvl, snapname, tmpnvl);
382			kmem_free(snapname, strlen(snapname + 1));
383		}
384		nvlist_free(tmpnvl);
385		if (innvl != NULL)
386			nvlist_free(innvl);
387		/* strip dataset part from zc->zc_name */
388		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
389		return (nvl);
390	break;
391	}
392out:
393	return (innvl);
394}
395
396nvlist_t *
397zfs_ioctl_compat_outnvl(zfs_cmd_t *zc, nvlist_t *outnvl, const int vec,
398    const int cflag)
399{
400	nvlist_t *tmpnvl;
401
402	if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
403	    cflag == ZFS_CMD_COMPAT_ZCMD || cflag == ZFS_CMD_COMPAT_EDBP ||
404	    cflag == ZFS_CMD_COMPAT_RESUME || cflag == ZFS_CMD_COMPAT_INLANES)
405		return (outnvl);
406
407	switch (vec) {
408	case ZFS_IOC_SPACE_SNAPS:
409		(void) nvlist_lookup_uint64(outnvl, "used", &zc->zc_cookie);
410		(void) nvlist_lookup_uint64(outnvl, "compressed",
411		    &zc->zc_objset_type);
412		(void) nvlist_lookup_uint64(outnvl, "uncompressed",
413		    &zc->zc_perm_action);
414		nvlist_free(outnvl);
415		/* return empty outnvl */
416		tmpnvl = fnvlist_alloc();
417		return (tmpnvl);
418	break;
419	case ZFS_IOC_CREATE:
420	case ZFS_IOC_CLONE:
421	case ZFS_IOC_HOLD:
422	case ZFS_IOC_RELEASE:
423		nvlist_free(outnvl);
424		/* return empty outnvl */
425		tmpnvl = fnvlist_alloc();
426		return (tmpnvl);
427	break;
428	}
429
430	return (outnvl);
431}
432#endif /* KERNEL */
433