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