zcp_iter.c revision 325534
1/*
2 * CDDL HEADER START
3 *
4 * This file and its contents are supplied under the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may only use this file in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source.  A copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14 */
15
16/*
17 * Copyright (c) 2016 by Delphix. All rights reserved.
18 */
19
20#include "lua.h"
21#include "lauxlib.h"
22
23#include <sys/dmu.h>
24#include <sys/dsl_prop.h>
25#include <sys/dsl_synctask.h>
26#include <sys/dsl_dataset.h>
27#include <sys/dsl_pool.h>
28#include <sys/dmu_tx.h>
29#include <sys/dmu_objset.h>
30#include <sys/zap.h>
31#include <sys/dsl_dir.h>
32#include <sys/zcp_prop.h>
33
34#include <sys/zcp.h>
35
36typedef int (zcp_list_func_t)(lua_State *);
37typedef struct zcp_list_info {
38	const char *name;
39	zcp_list_func_t *func;
40	zcp_list_func_t *gc;
41	const zcp_arg_t pargs[4];
42	const zcp_arg_t kwargs[2];
43} zcp_list_info_t;
44
45static int
46zcp_clones_iter(lua_State *state)
47{
48	int err;
49	char clonename[ZFS_MAX_DATASET_NAME_LEN];
50	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
51	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
52	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
53	dsl_dataset_t *ds, *clone;
54	zap_attribute_t za;
55	zap_cursor_t zc;
56
57	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
58	if (err == ENOENT) {
59		return (0);
60	} else if (err != 0) {
61		return (luaL_error(state,
62		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
63		    err));
64	}
65
66	if (dsl_dataset_phys(ds)->ds_next_clones_obj == 0) {
67		dsl_dataset_rele(ds, FTAG);
68		return (0);
69	}
70
71	zap_cursor_init_serialized(&zc, dp->dp_meta_objset,
72	    dsl_dataset_phys(ds)->ds_next_clones_obj, cursor);
73	dsl_dataset_rele(ds, FTAG);
74
75	err = zap_cursor_retrieve(&zc, &za);
76	if (err != 0) {
77		zap_cursor_fini(&zc);
78		if (err != ENOENT) {
79			return (luaL_error(state,
80			    "unexpected error %d from zap_cursor_retrieve()",
81			    err));
82		}
83		return (0);
84	}
85	zap_cursor_advance(&zc);
86	cursor = zap_cursor_serialize(&zc);
87	zap_cursor_fini(&zc);
88
89	err = dsl_dataset_hold_obj(dp, za.za_first_integer, FTAG, &clone);
90	if (err != 0) {
91		return (luaL_error(state,
92		    "unexpected error %d from "
93		    "dsl_dataset_hold_obj(za_first_integer)", err));
94	}
95
96	dsl_dir_name(clone->ds_dir, clonename);
97	dsl_dataset_rele(clone, FTAG);
98
99	lua_pushnumber(state, cursor);
100	lua_replace(state, lua_upvalueindex(2));
101
102	(void) lua_pushstring(state, clonename);
103	return (1);
104}
105
106static int zcp_clones_list(lua_State *);
107static zcp_list_info_t zcp_clones_list_info = {
108	.name = "clones",
109	.func = zcp_clones_list,
110	.gc = NULL,
111	.pargs = {
112	    { .za_name = "snapshot", .za_lua_type = LUA_TSTRING},
113	    {NULL, 0}
114	},
115	.kwargs = {
116	    {NULL, 0}
117	}
118};
119
120static int
121zcp_clones_list(lua_State *state)
122{
123	const char *snapname = lua_tostring(state, 1);
124	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
125	boolean_t issnap;
126	uint64_t dsobj, cursor;
127
128	/*
129	 * zcp_dataset_hold will either successfully return the requested
130	 * dataset or throw a lua error and longjmp out of the zfs.list.clones
131	 * call without returning.
132	 */
133	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, snapname, FTAG);
134	if (ds == NULL)
135		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
136	cursor = 0;
137	issnap = ds->ds_is_snapshot;
138	dsobj = ds->ds_object;
139	dsl_dataset_rele(ds, FTAG);
140
141	if (!issnap) {
142		return (zcp_argerror(state, 1, "%s is not a snapshot",
143		    snapname));
144	}
145
146	lua_pushnumber(state, dsobj);
147	lua_pushnumber(state, cursor);
148	lua_pushcclosure(state, &zcp_clones_iter, 2);
149	return (1);
150}
151
152static int
153zcp_snapshots_iter(lua_State *state)
154{
155	int err;
156	char snapname[ZFS_MAX_DATASET_NAME_LEN];
157	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
158	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
159	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
160	dsl_dataset_t *ds;
161	objset_t *os;
162	char *p;
163
164	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
165	if (err != 0) {
166		return (luaL_error(state,
167		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
168		    err));
169	}
170
171	dsl_dataset_name(ds, snapname);
172	VERIFY3U(sizeof (snapname), >,
173	    strlcat(snapname, "@", sizeof (snapname)));
174
175	p = strchr(snapname, '\0');
176	VERIFY0(dmu_objset_from_ds(ds, &os));
177	err = dmu_snapshot_list_next(os,
178	    sizeof (snapname) - (p - snapname), p, NULL, &cursor, NULL);
179	dsl_dataset_rele(ds, FTAG);
180
181	if (err == ENOENT) {
182		return (0);
183	} else if (err != 0) {
184		return (luaL_error(state,
185		    "unexpected error %d from dmu_snapshot_list_next()", err));
186	}
187
188	lua_pushnumber(state, cursor);
189	lua_replace(state, lua_upvalueindex(2));
190
191	(void) lua_pushstring(state, snapname);
192	return (1);
193}
194
195static int zcp_snapshots_list(lua_State *);
196static zcp_list_info_t zcp_snapshots_list_info = {
197	.name = "snapshots",
198	.func = zcp_snapshots_list,
199	.gc = NULL,
200	.pargs = {
201	    { .za_name = "filesystem | volume", .za_lua_type = LUA_TSTRING},
202	    {NULL, 0}
203	},
204	.kwargs = {
205	    {NULL, 0}
206	}
207};
208
209static int
210zcp_snapshots_list(lua_State *state)
211{
212	const char *fsname = lua_tostring(state, 1);
213	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
214	boolean_t issnap;
215	uint64_t dsobj;
216
217	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, fsname, FTAG);
218	if (ds == NULL)
219		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
220	issnap = ds->ds_is_snapshot;
221	dsobj = ds->ds_object;
222	dsl_dataset_rele(ds, FTAG);
223
224	if (issnap) {
225		return (zcp_argerror(state, 1,
226		    "argument %s cannot be a snapshot", fsname));
227	}
228
229	lua_pushnumber(state, dsobj);
230	lua_pushnumber(state, 0);
231	lua_pushcclosure(state, &zcp_snapshots_iter, 2);
232	return (1);
233}
234
235/*
236 * Note: channel programs only run in the global zone, so all datasets
237 * are visible to this zone.
238 */
239static boolean_t
240dataset_name_hidden(const char *name)
241{
242	if (strchr(name, '$') != NULL)
243		return (B_TRUE);
244	if (strchr(name, '%') != NULL)
245		return (B_TRUE);
246	return (B_FALSE);
247}
248
249static int
250zcp_children_iter(lua_State *state)
251{
252	int err;
253	char childname[ZFS_MAX_DATASET_NAME_LEN];
254	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
255	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
256	zcp_run_info_t *ri = zcp_run_info(state);
257	dsl_pool_t *dp = ri->zri_pool;
258	dsl_dataset_t *ds;
259	objset_t *os;
260	char *p;
261
262	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
263	if (err != 0) {
264		return (luaL_error(state,
265		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
266		    err));
267	}
268
269	dsl_dataset_name(ds, childname);
270	VERIFY3U(sizeof (childname), >,
271	    strlcat(childname, "/", sizeof (childname)));
272	p = strchr(childname, '\0');
273
274	VERIFY0(dmu_objset_from_ds(ds, &os));
275	do {
276		err = dmu_dir_list_next(os,
277		    sizeof (childname) - (p - childname), p, NULL, &cursor);
278	} while (err == 0 && dataset_name_hidden(childname));
279	dsl_dataset_rele(ds, FTAG);
280
281	if (err == ENOENT) {
282		return (0);
283	} else if (err != 0) {
284		return (luaL_error(state,
285		    "unexpected error %d from dmu_dir_list_next()",
286		    err));
287	}
288
289	lua_pushnumber(state, cursor);
290	lua_replace(state, lua_upvalueindex(2));
291
292	(void) lua_pushstring(state, childname);
293	return (1);
294}
295
296static int zcp_children_list(lua_State *);
297static zcp_list_info_t zcp_children_list_info = {
298	.name = "children",
299	.func = zcp_children_list,
300	.gc = NULL,
301	.pargs = {
302	    { .za_name = "filesystem | volume", .za_lua_type = LUA_TSTRING},
303	    {NULL, 0}
304	},
305	.kwargs = {
306	    {NULL, 0}
307	}
308};
309
310static int
311zcp_children_list(lua_State *state)
312{
313	const char *fsname = lua_tostring(state, 1);
314	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
315	boolean_t issnap;
316	uint64_t dsobj;
317
318	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, fsname, FTAG);
319	if (ds == NULL)
320		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
321
322	issnap = ds->ds_is_snapshot;
323	dsobj = ds->ds_object;
324	dsl_dataset_rele(ds, FTAG);
325
326	if (issnap) {
327		return (zcp_argerror(state, 1,
328		    "argument %s cannot be a snapshot", fsname));
329	}
330
331	lua_pushnumber(state, dsobj);
332	lua_pushnumber(state, 0);
333	lua_pushcclosure(state, &zcp_children_iter, 2);
334	return (1);
335}
336
337static int
338zcp_props_list_gc(lua_State *state)
339{
340	nvlist_t **props = lua_touserdata(state, 1);
341	if (*props != NULL)
342		fnvlist_free(*props);
343	return (0);
344}
345
346static int
347zcp_props_iter(lua_State *state)
348{
349	char *source, *val;
350	nvlist_t *nvprop;
351	nvlist_t **props = lua_touserdata(state, lua_upvalueindex(1));
352	nvpair_t *pair = lua_touserdata(state, lua_upvalueindex(2));
353
354	do {
355		pair = nvlist_next_nvpair(*props, pair);
356		if (pair == NULL) {
357			fnvlist_free(*props);
358			*props = NULL;
359			return (0);
360		}
361	} while (!zfs_prop_user(nvpair_name(pair)));
362
363	lua_pushlightuserdata(state, pair);
364	lua_replace(state, lua_upvalueindex(2));
365
366	nvprop = fnvpair_value_nvlist(pair);
367	val = fnvlist_lookup_string(nvprop, ZPROP_VALUE);
368	source = fnvlist_lookup_string(nvprop, ZPROP_SOURCE);
369
370	(void) lua_pushstring(state, nvpair_name(pair));
371	(void) lua_pushstring(state, val);
372	(void) lua_pushstring(state, source);
373	return (3);
374}
375
376static int zcp_props_list(lua_State *);
377static zcp_list_info_t zcp_props_list_info = {
378	.name = "properties",
379	.func = zcp_props_list,
380	.gc = zcp_props_list_gc,
381	.pargs = {
382	    { .za_name = "filesystem | snapshot | volume",
383	    .za_lua_type = LUA_TSTRING},
384	    {NULL, 0}
385	},
386	.kwargs = {
387	    {NULL, 0}
388	}
389};
390
391static int
392zcp_props_list(lua_State *state)
393{
394	const char *dsname = lua_tostring(state, 1);
395	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
396	objset_t *os;
397	nvlist_t **props = lua_newuserdata(state, sizeof (nvlist_t *));
398
399	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, dsname, FTAG);
400	if (ds == NULL)
401		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
402	VERIFY0(dmu_objset_from_ds(ds, &os));
403	VERIFY0(dsl_prop_get_all(os, props));
404	dsl_dataset_rele(ds, FTAG);
405
406	/*
407	 * Set the metatable for the properties list to free it on completion.
408	 */
409	luaL_getmetatable(state, zcp_props_list_info.name);
410	(void) lua_setmetatable(state, -2);
411
412	lua_pushlightuserdata(state, NULL);
413	lua_pushcclosure(state, &zcp_props_iter, 2);
414	return (1);
415}
416
417
418/*
419 * Populate nv with all valid properties and their values for the given
420 * dataset.
421 */
422static void
423zcp_dataset_props(dsl_dataset_t *ds, nvlist_t *nv)
424{
425	for (int prop = ZFS_PROP_TYPE; prop < ZFS_NUM_PROPS; prop++) {
426		/* Do not display hidden props */
427		if (!zfs_prop_visible(prop))
428			continue;
429		/* Do not display props not valid for this dataset */
430		if (!prop_valid_for_ds(ds, prop))
431			continue;
432		fnvlist_add_boolean(nv, zfs_prop_to_name(prop));
433	}
434}
435
436static int zcp_system_props_list(lua_State *);
437static zcp_list_info_t zcp_system_props_list_info = {
438	.name = "system_properties",
439	.func = zcp_system_props_list,
440	.pargs = {
441	    { .za_name = "dataset", .za_lua_type = LUA_TSTRING},
442	    {NULL, 0}
443	},
444	.kwargs = {
445	    {NULL, 0}
446	}
447};
448
449/*
450 * Get a list of all visble properties and their values for a given dataset.
451 * Returned on the stack as a Lua table.
452 */
453static int
454zcp_system_props_list(lua_State *state)
455{
456	int error;
457	char errbuf[128];
458	const char *dataset_name;
459	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
460	zcp_list_info_t *libinfo = &zcp_system_props_list_info;
461	zcp_parse_args(state, libinfo->name, libinfo->pargs, libinfo->kwargs);
462	dataset_name = lua_tostring(state, 1);
463	nvlist_t *nv = fnvlist_alloc();
464
465	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, dataset_name, FTAG);
466	if (ds == NULL)
467		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
468
469	/* Get the names of all valid properties for this dataset */
470	zcp_dataset_props(ds, nv);
471	dsl_dataset_rele(ds, FTAG);
472
473	/* push list as lua table */
474	error = zcp_nvlist_to_lua(state, nv, errbuf, sizeof (errbuf));
475	nvlist_free(nv);
476	if (error != 0) {
477		return (luaL_error(state,
478		    "Error returning nvlist: %s", errbuf));
479	}
480	return (1);
481}
482
483static int
484zcp_list_func(lua_State *state)
485{
486	zcp_list_info_t *info = lua_touserdata(state, lua_upvalueindex(1));
487
488	zcp_parse_args(state, info->name, info->pargs, info->kwargs);
489
490	return (info->func(state));
491}
492
493int
494zcp_load_list_lib(lua_State *state)
495{
496	int i;
497	zcp_list_info_t *zcp_list_funcs[] = {
498		&zcp_children_list_info,
499		&zcp_snapshots_list_info,
500		&zcp_props_list_info,
501		&zcp_clones_list_info,
502		&zcp_system_props_list_info,
503		NULL
504	};
505
506	lua_newtable(state);
507
508	for (i = 0; zcp_list_funcs[i] != NULL; i++) {
509		zcp_list_info_t *info = zcp_list_funcs[i];
510
511		if (info->gc != NULL) {
512			/*
513			 * If the function requires garbage collection, create
514			 * a metatable with its name and register the __gc
515			 * function.
516			 */
517			(void) luaL_newmetatable(state, info->name);
518			(void) lua_pushstring(state, "__gc");
519			lua_pushcfunction(state, info->gc);
520			lua_settable(state, -3);
521			lua_pop(state, 1);
522		}
523
524		lua_pushlightuserdata(state, info);
525		lua_pushcclosure(state, &zcp_list_func, 1);
526		lua_setfield(state, -2, info->name);
527		info++;
528	}
529
530	return (1);
531}
532