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 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <Python.h>
27#include <sys/zfs_ioctl.h>
28#include <sys/fs/zfs.h>
29#include <strings.h>
30#include <unistd.h>
31#include <libnvpair.h>
32#include <libintl.h>
33#include <libzfs.h>
34#include <libzfs_impl.h>
35#include "zfs_prop.h"
36
37static PyObject *ZFSError;
38static int zfsdevfd;
39
40#ifdef __lint
41#define	dgettext(x, y) y
42#endif
43
44#define	_(s) dgettext(TEXT_DOMAIN, s)
45
46/*PRINTFLIKE1*/
47static void
48seterr(char *fmt, ...)
49{
50	char errstr[1024];
51	va_list v;
52
53	va_start(v, fmt);
54	(void) vsnprintf(errstr, sizeof (errstr), fmt, v);
55	va_end(v);
56
57	PyErr_SetObject(ZFSError, Py_BuildValue("is", errno, errstr));
58}
59
60static char cmdstr[HIS_MAX_RECORD_LEN];
61
62static int
63ioctl_with_cmdstr(int ioc, zfs_cmd_t *zc)
64{
65	int err;
66
67	if (cmdstr[0])
68		zc->zc_history = (uint64_t)(uintptr_t)cmdstr;
69	err = ioctl(zfsdevfd, ioc, zc);
70	cmdstr[0] = '\0';
71	return (err);
72}
73
74static PyObject *
75nvl2py(nvlist_t *nvl)
76{
77	PyObject *pyo;
78	nvpair_t *nvp;
79
80	pyo = PyDict_New();
81
82	for (nvp = nvlist_next_nvpair(nvl, NULL); nvp;
83	    nvp = nvlist_next_nvpair(nvl, nvp)) {
84		PyObject *pyval;
85		char *sval;
86		uint64_t ival;
87		boolean_t bval;
88		nvlist_t *nval;
89
90		switch (nvpair_type(nvp)) {
91		case DATA_TYPE_STRING:
92			(void) nvpair_value_string(nvp, &sval);
93			pyval = Py_BuildValue("s", sval);
94			break;
95
96		case DATA_TYPE_UINT64:
97			(void) nvpair_value_uint64(nvp, &ival);
98			pyval = Py_BuildValue("K", ival);
99			break;
100
101		case DATA_TYPE_NVLIST:
102			(void) nvpair_value_nvlist(nvp, &nval);
103			pyval = nvl2py(nval);
104			break;
105
106		case DATA_TYPE_BOOLEAN:
107			Py_INCREF(Py_None);
108			pyval = Py_None;
109			break;
110
111		case DATA_TYPE_BOOLEAN_VALUE:
112			(void) nvpair_value_boolean_value(nvp, &bval);
113			pyval = Py_BuildValue("i", bval);
114			break;
115
116		default:
117			PyErr_SetNone(PyExc_ValueError);
118			Py_DECREF(pyo);
119			return (NULL);
120		}
121
122		PyDict_SetItemString(pyo, nvpair_name(nvp), pyval);
123		Py_DECREF(pyval);
124	}
125
126	return (pyo);
127}
128
129static nvlist_t *
130dict2nvl(PyObject *d)
131{
132	nvlist_t *nvl;
133	int err;
134	PyObject *key, *value;
135	int pos = 0;
136
137	if (!PyDict_Check(d)) {
138		PyErr_SetObject(PyExc_ValueError, d);
139		return (NULL);
140	}
141
142	err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0);
143	assert(err == 0);
144
145	while (PyDict_Next(d, &pos, &key, &value)) {
146		char *keystr = PyString_AsString(key);
147		if (keystr == NULL) {
148			PyErr_SetObject(PyExc_KeyError, key);
149			nvlist_free(nvl);
150			return (NULL);
151		}
152
153		if (PyDict_Check(value)) {
154			nvlist_t *valnvl = dict2nvl(value);
155			err = nvlist_add_nvlist(nvl, keystr, valnvl);
156			nvlist_free(valnvl);
157		} else if (value == Py_None) {
158			err = nvlist_add_boolean(nvl, keystr);
159		} else if (PyString_Check(value)) {
160			char *valstr = PyString_AsString(value);
161			err = nvlist_add_string(nvl, keystr, valstr);
162		} else if (PyInt_Check(value)) {
163			uint64_t valint = PyInt_AsUnsignedLongLongMask(value);
164			err = nvlist_add_uint64(nvl, keystr, valint);
165		} else if (PyBool_Check(value)) {
166			boolean_t valbool = value == Py_True ? B_TRUE : B_FALSE;
167			err = nvlist_add_boolean_value(nvl, keystr, valbool);
168		} else {
169			PyErr_SetObject(PyExc_ValueError, value);
170			nvlist_free(nvl);
171			return (NULL);
172		}
173		assert(err == 0);
174	}
175
176	return (nvl);
177}
178
179static PyObject *
180fakepropval(uint64_t value)
181{
182	PyObject *d = PyDict_New();
183	PyDict_SetItemString(d, "value", Py_BuildValue("K", value));
184	return (d);
185}
186
187static void
188add_ds_props(zfs_cmd_t *zc, PyObject *nvl)
189{
190	dmu_objset_stats_t *s = &zc->zc_objset_stats;
191	PyDict_SetItemString(nvl, "numclones",
192	    fakepropval(s->dds_num_clones));
193	PyDict_SetItemString(nvl, "issnap",
194	    fakepropval(s->dds_is_snapshot));
195	PyDict_SetItemString(nvl, "inconsistent",
196	    fakepropval(s->dds_inconsistent));
197}
198
199/* On error, returns NULL but does not set python exception. */
200static PyObject *
201ioctl_with_dstnv(int ioc, zfs_cmd_t *zc)
202{
203	int nvsz = 2048;
204	void *nvbuf;
205	PyObject *pynv = NULL;
206
207again:
208	nvbuf = malloc(nvsz);
209	zc->zc_nvlist_dst_size = nvsz;
210	zc->zc_nvlist_dst = (uintptr_t)nvbuf;
211
212	if (ioctl(zfsdevfd, ioc, zc) == 0) {
213		nvlist_t *nvl;
214
215		errno = nvlist_unpack(nvbuf, zc->zc_nvlist_dst_size, &nvl, 0);
216		if (errno == 0) {
217			pynv = nvl2py(nvl);
218			nvlist_free(nvl);
219		}
220	} else if (errno == ENOMEM) {
221		free(nvbuf);
222		nvsz = zc->zc_nvlist_dst_size;
223		goto again;
224	}
225	free(nvbuf);
226	return (pynv);
227}
228
229static PyObject *
230py_next_dataset(PyObject *self, PyObject *args)
231{
232	int ioc;
233	uint64_t cookie;
234	zfs_cmd_t zc = { 0 };
235	int snaps;
236	char *name;
237	PyObject *nvl;
238	PyObject *ret = NULL;
239
240	if (!PyArg_ParseTuple(args, "siK", &name, &snaps, &cookie))
241		return (NULL);
242
243	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
244	zc.zc_cookie = cookie;
245
246	if (snaps)
247		ioc = ZFS_IOC_SNAPSHOT_LIST_NEXT;
248	else
249		ioc = ZFS_IOC_DATASET_LIST_NEXT;
250
251	nvl = ioctl_with_dstnv(ioc, &zc);
252	if (nvl) {
253		add_ds_props(&zc, nvl);
254		ret = Py_BuildValue("sKO", zc.zc_name, zc.zc_cookie, nvl);
255		Py_DECREF(nvl);
256	} else if (errno == ESRCH) {
257		PyErr_SetNone(PyExc_StopIteration);
258	} else {
259		if (snaps)
260			seterr(_("cannot get snapshots of %s"), name);
261		else
262			seterr(_("cannot get child datasets of %s"), name);
263	}
264	return (ret);
265}
266
267static PyObject *
268py_dataset_props(PyObject *self, PyObject *args)
269{
270	zfs_cmd_t zc = { 0 };
271	int snaps;
272	char *name;
273	PyObject *nvl;
274
275	if (!PyArg_ParseTuple(args, "s", &name))
276		return (NULL);
277
278	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
279
280	nvl = ioctl_with_dstnv(ZFS_IOC_OBJSET_STATS, &zc);
281	if (nvl) {
282		add_ds_props(&zc, nvl);
283	} else {
284		seterr(_("cannot access dataset %s"), name);
285	}
286	return (nvl);
287}
288
289static PyObject *
290py_get_fsacl(PyObject *self, PyObject *args)
291{
292	zfs_cmd_t zc = { 0 };
293	char *name;
294	PyObject *nvl;
295
296	if (!PyArg_ParseTuple(args, "s", &name))
297		return (NULL);
298
299	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
300
301	nvl = ioctl_with_dstnv(ZFS_IOC_GET_FSACL, &zc);
302	if (nvl == NULL)
303		seterr(_("cannot get permissions on %s"), name);
304
305	return (nvl);
306}
307
308static PyObject *
309py_set_fsacl(PyObject *self, PyObject *args)
310{
311	int un;
312	size_t nvsz;
313	zfs_cmd_t zc = { 0 };
314	char *name, *nvbuf;
315	PyObject *dict, *file;
316	nvlist_t *nvl;
317	int err;
318
319	if (!PyArg_ParseTuple(args, "siO!", &name, &un,
320	    &PyDict_Type, &dict))
321		return (NULL);
322
323	nvl = dict2nvl(dict);
324	if (nvl == NULL)
325		return (NULL);
326
327	err = nvlist_size(nvl, &nvsz, NV_ENCODE_NATIVE);
328	assert(err == 0);
329	nvbuf = malloc(nvsz);
330	err = nvlist_pack(nvl, &nvbuf, &nvsz, NV_ENCODE_NATIVE, 0);
331	assert(err == 0);
332
333	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
334	zc.zc_nvlist_src_size = nvsz;
335	zc.zc_nvlist_src = (uintptr_t)nvbuf;
336	zc.zc_perm_action = un;
337
338	err = ioctl_with_cmdstr(ZFS_IOC_SET_FSACL, &zc);
339	free(nvbuf);
340	if (err) {
341		seterr(_("cannot set permissions on %s"), name);
342		return (NULL);
343	}
344
345	Py_RETURN_NONE;
346}
347
348static PyObject *
349py_get_holds(PyObject *self, PyObject *args)
350{
351	zfs_cmd_t zc = { 0 };
352	char *name;
353	PyObject *nvl;
354
355	if (!PyArg_ParseTuple(args, "s", &name))
356		return (NULL);
357
358	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
359
360	nvl = ioctl_with_dstnv(ZFS_IOC_GET_HOLDS, &zc);
361	if (nvl == NULL)
362		seterr(_("cannot get holds for %s"), name);
363
364	return (nvl);
365}
366
367static PyObject *
368py_userspace_many(PyObject *self, PyObject *args)
369{
370	zfs_cmd_t zc = { 0 };
371	zfs_userquota_prop_t type;
372	char *name, *propname;
373	int bufsz = 1<<20;
374	void *buf;
375	PyObject *dict, *file;
376	int error;
377
378	if (!PyArg_ParseTuple(args, "ss", &name, &propname))
379		return (NULL);
380
381	for (type = 0; type < ZFS_NUM_USERQUOTA_PROPS; type++)
382		if (strcmp(propname, zfs_userquota_prop_prefixes[type]) == 0)
383			break;
384	if (type == ZFS_NUM_USERQUOTA_PROPS) {
385		PyErr_SetString(PyExc_KeyError, propname);
386		return (NULL);
387	}
388
389	dict = PyDict_New();
390	buf = malloc(bufsz);
391
392	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
393	zc.zc_objset_type = type;
394	zc.zc_cookie = 0;
395
396	while (1) {
397		zfs_useracct_t *zua = buf;
398
399		zc.zc_nvlist_dst = (uintptr_t)buf;
400		zc.zc_nvlist_dst_size = bufsz;
401
402		error = ioctl(zfsdevfd, ZFS_IOC_USERSPACE_MANY, &zc);
403		if (error || zc.zc_nvlist_dst_size == 0)
404			break;
405
406		while (zc.zc_nvlist_dst_size > 0) {
407			PyObject *pykey, *pyval;
408
409			pykey = Py_BuildValue("sI",
410			    zua->zu_domain, zua->zu_rid);
411			pyval = Py_BuildValue("K", zua->zu_space);
412			PyDict_SetItem(dict, pykey, pyval);
413			Py_DECREF(pykey);
414			Py_DECREF(pyval);
415
416			zua++;
417			zc.zc_nvlist_dst_size -= sizeof (zfs_useracct_t);
418		}
419	}
420
421	free(buf);
422
423	if (error != 0) {
424		Py_DECREF(dict);
425		seterr(_("cannot get %s property on %s"), propname, name);
426		return (NULL);
427	}
428
429	return (dict);
430}
431
432static PyObject *
433py_userspace_upgrade(PyObject *self, PyObject *args)
434{
435	zfs_cmd_t zc = { 0 };
436	char *name;
437	int error;
438
439	if (!PyArg_ParseTuple(args, "s", &name))
440		return (NULL);
441
442	(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
443	error = ioctl(zfsdevfd, ZFS_IOC_USERSPACE_UPGRADE, &zc);
444
445	if (error != 0) {
446		seterr(_("cannot initialize user accounting information on %s"),
447		    name);
448		return (NULL);
449	}
450
451	Py_RETURN_NONE;
452}
453
454static PyObject *
455py_set_cmdstr(PyObject *self, PyObject *args)
456{
457	char *str;
458
459	if (!PyArg_ParseTuple(args, "s", &str))
460		return (NULL);
461
462	(void) strlcpy(cmdstr, str, sizeof (cmdstr));
463
464	Py_RETURN_NONE;
465}
466
467static PyObject *
468py_get_proptable(PyObject *self, PyObject *args)
469{
470	zprop_desc_t *t = zfs_prop_get_table();
471	PyObject *d = PyDict_New();
472	zfs_prop_t i;
473
474	for (i = 0; i < ZFS_NUM_PROPS; i++) {
475		zprop_desc_t *p = &t[i];
476		PyObject *tuple;
477		static const char *typetable[] =
478		    {"number", "string", "index"};
479		static const char *attrtable[] =
480		    {"default", "readonly", "inherit", "onetime"};
481		PyObject *indextable;
482
483		if (p->pd_proptype == PROP_TYPE_INDEX) {
484			const zprop_index_t *it = p->pd_table;
485			indextable = PyDict_New();
486			int j;
487			for (j = 0; it[j].pi_name; j++) {
488				PyDict_SetItemString(indextable,
489				    it[j].pi_name,
490				    Py_BuildValue("K", it[j].pi_value));
491			}
492		} else {
493			Py_INCREF(Py_None);
494			indextable = Py_None;
495		}
496
497		tuple = Py_BuildValue("sissKsissiiO",
498		    p->pd_name, p->pd_propnum, typetable[p->pd_proptype],
499		    p->pd_strdefault, p->pd_numdefault,
500		    attrtable[p->pd_attr], p->pd_types,
501		    p->pd_values, p->pd_colname,
502		    p->pd_rightalign, p->pd_visible, indextable);
503		PyDict_SetItemString(d, p->pd_name, tuple);
504		Py_DECREF(tuple);
505	}
506
507	return (d);
508}
509
510static PyMethodDef zfsmethods[] = {
511	{"next_dataset", py_next_dataset, METH_VARARGS,
512	    "Get next child dataset or snapshot."},
513	{"get_fsacl", py_get_fsacl, METH_VARARGS, "Get allowed permissions."},
514	{"set_fsacl", py_set_fsacl, METH_VARARGS, "Set allowed permissions."},
515	{"userspace_many", py_userspace_many, METH_VARARGS,
516	    "Get user space accounting."},
517	{"userspace_upgrade", py_userspace_upgrade, METH_VARARGS,
518	    "Upgrade fs to enable user space accounting."},
519	{"set_cmdstr", py_set_cmdstr, METH_VARARGS,
520	    "Set command string for history logging."},
521	{"dataset_props", py_dataset_props, METH_VARARGS,
522	    "Get dataset properties."},
523	{"get_proptable", py_get_proptable, METH_NOARGS,
524	    "Get property table."},
525	{"get_holds", py_get_holds, METH_VARARGS, "Get user holds."},
526	{NULL, NULL, 0, NULL}
527};
528
529void
530initioctl(void)
531{
532	PyObject *zfs_ioctl = Py_InitModule("zfs.ioctl", zfsmethods);
533	PyObject *zfs_util = PyImport_ImportModule("zfs.util");
534	PyObject *devfile;
535
536	if (zfs_util == NULL)
537		return;
538
539	ZFSError = PyObject_GetAttrString(zfs_util, "ZFSError");
540	devfile = PyObject_GetAttrString(zfs_util, "dev");
541	zfsdevfd = PyObject_AsFileDescriptor(devfile);
542
543	zfs_prop_init();
544}
545