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