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