1/*
2 * Copyright (c) 2010 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to The NetBSD Foundation
6 * by Adam Hamsik.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/types.h>
31#include <sys/param.h>
32
33#include <ctype.h>
34#include <err.h>
35#include <errno.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39
40#include <prop/proplib.h>
41
42#include <dm.h>
43
44#ifdef RUMP_ACTION
45#include <rump/rump.h>
46#include <rump/rumpclient.h>
47#include <rump/rump_syscalls.h>
48#endif
49
50/* dmctl command is used to communicate with device-mapper driver in NetBSD
51 * it uses libdm library to create and send required data to kernel.
52 *
53 * Main purpose of dmctl is to add posibility to use device-mapper driver
54 * from outside of LVM scope.
55 */
56
57#define DMCTL_CMD_REQ_NODEVNAME 0	/* Command do not require device name */
58#define DMCTL_CMD_REQ_DEVNAME	1	/* Command require device name to work */
59#define DMCTL_CMD_REQ_NEWNAME	2	/* Command require device name and
60					   newname to work */
61struct command {
62	const char *cmd_name;
63	const char *cmd_help;
64	const char *ioctl_cmd_name;
65	int min_args;
66	int (*cmd_func)(int, char *[], libdm_task_t);
67};
68
69static const   char *dvname;            /* device name */
70static const   char *cmdname;           /* command user issued */
71
72static char * parse_stdin(char *);
73
74static int dmctl_get_version(int, char *[], libdm_task_t);
75static int dmctl_get_targets(int, char *[], libdm_task_t);
76static int dmctl_get_device_info(int, char *[], libdm_task_t);
77static int dmctl_create_dev(int, char *[], libdm_task_t);
78static int dmctl_dev_rename(int, char *[], libdm_task_t);
79static int dmctl_dev_remove(int, char *[], libdm_task_t);
80static int dmctl_dev_resume(int, char *[], libdm_task_t);
81static int dmctl_dev_suspend(int, char *[], libdm_task_t);
82static int dmctl_dev_deps(int, char *[], libdm_task_t);
83static int dmctl_list_devices(int, char *[], libdm_task_t);
84static int dmctl_table_reload(int, char *[], libdm_task_t);
85static int dmctl_table_status(int, char *[], libdm_task_t);
86__dead static void usage(void);
87
88static struct command commands[] = {
89	{ "version",
90	  "Print driver and lib version.",
91	  NULL, DMCTL_CMD_REQ_NODEVNAME,
92	  dmctl_get_version },
93	{ "targets",
94	  "List available kernel targets.",
95	  NULL, DMCTL_CMD_REQ_NODEVNAME,
96	  dmctl_get_targets },
97	{ "create",
98	  "Create device with [dm device name].",
99	  NULL, DMCTL_CMD_REQ_DEVNAME,
100	  dmctl_create_dev },
101	{ "ls",
102	  "List existing dm devices.",
103	  "names", DMCTL_CMD_REQ_NODEVNAME,
104	  dmctl_list_devices },
105	{ "info",
106	  "Get info about device with [dm device name].",
107	  NULL, DMCTL_CMD_REQ_DEVNAME,
108	  dmctl_get_device_info },
109	{ "rename",
110	  "Rename device with [dm device name] to  [dm device new name].",
111	  NULL, DMCTL_CMD_REQ_NEWNAME,
112	  dmctl_dev_rename },
113	{ "remove",
114	  "Remove device with [dm device name].",
115	  NULL, DMCTL_CMD_REQ_DEVNAME,
116	  dmctl_dev_remove },
117	{ "resume",
118	  "Resume IO on dm device [dm device name].",
119	  NULL, DMCTL_CMD_REQ_DEVNAME,
120	  dmctl_dev_resume },
121	{ "suspend",
122	  "Suspend IO on dm device [dm device name].",
123	  NULL, DMCTL_CMD_REQ_DEVNAME,
124	  dmctl_dev_suspend },
125	{ "deps",
126	  "Print physical dependiences for dm device [dm device name].",
127	  NULL, DMCTL_CMD_REQ_DEVNAME,
128	  dmctl_dev_deps },
129	{ "reload",
130	  "Switch active and passive tables for device with [dm device name].",
131	  NULL, DMCTL_CMD_REQ_DEVNAME,
132	  dmctl_table_reload },
133	{ "status",
134	  "Print status for device with [dm device name].",
135	  "table", DMCTL_CMD_REQ_DEVNAME,
136	  dmctl_table_status },
137	{ "table",
138	  "Print active table for device with [dm device name].",
139	  NULL, DMCTL_CMD_REQ_DEVNAME,
140	  dmctl_table_status },
141	{ NULL,
142	  NULL,
143	  NULL, 0,
144	  NULL },
145};
146
147int
148main(int argc, char *argv[])
149{
150	int i;
151	int oargc;
152	libdm_task_t task;
153
154	oargc = 0;
155
156#ifdef RUMP_ACTION
157	if (rumpclient_init() == -1)
158	  err(EXIT_FAILURE, "rump client init failed");
159#endif
160
161	/* Must have at least: device command */
162	if (argc < 2)
163		usage();
164
165	/* Skip program name, get and skip device name and command. */
166	cmdname = argv[1];
167	if (argc > 2) {
168		oargc = 1;
169		dvname = argv[2];
170	}
171
172	if (argc > 3) {
173		argv += 3;
174		argc -= 3;
175		oargc = 2;
176	} else {
177		argv = 0;
178		argc = 0;
179	}
180
181	for (i = 0; commands[i].cmd_name != NULL; i++)
182		if (strcmp(cmdname, commands[i].cmd_name) == 0)
183			break;
184
185	if (commands[i].cmd_name == NULL)
186		errx(EXIT_FAILURE, "unknown command: %s", cmdname);
187
188	if (commands[i].ioctl_cmd_name != NULL)
189		cmdname = commands[i].ioctl_cmd_name;
190
191	if (oargc != commands[i].min_args) {
192		(void)fprintf(stderr, "Insufficient number of arguments for "
193		    "command: %s specified\n", commands[i].cmd_name);
194		usage();
195	}
196
197	/*
198	 * Create libdm task, and pass it to command handler later.
199	 * Don't release it here because it will be replaced by different
200	 * dictionary received from kernel after libdm_task_run.
201	 */
202	task = libdm_task_create(cmdname);
203
204	(*commands[i].cmd_func)(argc, argv, task);
205
206	return 0;
207}
208
209/*
210 * Print library and kernel driver versions if command can be used only when
211 * major, minor number of library version is <= kernel.
212 */
213static int
214dmctl_get_version(int argc __unused, char *argv[] __unused, libdm_task_t task)
215{
216	uint32_t ver[3], size;
217
218	size = libdm_task_get_cmd_version(task, ver, sizeof(ver));
219
220	printf("Library protocol version %d:%d:%d\n", ver[0], ver[1], ver[2]);
221
222	if (libdm_task_run(task) != 0)
223		err(EXIT_FAILURE, "dmctl_get_version: libdm_task_run failed.");
224
225	size = libdm_task_get_cmd_version(task, ver, 3);
226	printf("Kernel protocol version %d:%d:%d\n",ver[0], ver[1], ver[2]);
227
228	libdm_task_destroy(task);
229	return 0;
230}
231
232/*
233 * Get list of available targets from kernel and print them.
234 */
235static int
236dmctl_get_targets(int argc __unused, char *argv[] __unused, libdm_task_t task)
237{
238	libdm_cmd_t cmd;
239	libdm_iter_t iter;
240	libdm_target_t target;
241	uint32_t ver[3];
242
243	if (libdm_task_run(task) != 0)
244		err(EXIT_FAILURE, "dmctl_get_targets: libdm_task_run failed.");
245
246	if ((cmd = libdm_task_get_cmd(task)) == NULL)
247		return ENOENT;
248
249	iter = libdm_cmd_iter_create(cmd);
250
251	while((target = libdm_cmd_get_target(iter)) != NULL){
252		printf("Target name: %s\n", libdm_target_get_name(target));
253
254		libdm_target_get_version(target, ver, sizeof(ver));
255		printf("Target version %d.%d.%d\n\n", ver[0], ver[1], ver[2]);
256
257		libdm_target_destroy(target);
258	}
259
260	libdm_iter_destroy(iter);
261	libdm_cmd_destroy(cmd);
262	libdm_task_destroy(task);
263
264	return 0;
265}
266
267/*
268 * Create device with name used as second parameter.
269 * TODO: Support for UUIDs here.
270 */
271static int
272dmctl_create_dev(int argc __unused, char *argv[] __unused, libdm_task_t task)
273{
274
275	libdm_task_set_name(dvname, task);
276
277	if (libdm_task_run(task) != 0)
278		err(EXIT_FAILURE, "dmctl_create_dev: libdm_task_run failed.");
279
280	libdm_task_destroy(task);
281	return 0;
282}
283
284/*
285 * Get basic device info from device-mapper driver.
286 */
287static int
288dmctl_get_device_info(int argc __unused, char *argv[] __unused, libdm_task_t task)
289{
290
291	libdm_task_set_name(dvname, task);
292
293	if (libdm_task_run(task) != 0)
294		err(EXIT_FAILURE, "dmctl_get_device_info: libdm_task_run failed.\n");
295
296	printf("Printing Device info for:\n");
297	printf("Device name: \t\t%s\n", libdm_task_get_name(task));
298	printf("Device uuid: \t\t%s\n", libdm_task_get_uuid(task));
299	printf("Device minor: \t\t%d\n", libdm_task_get_minor(task));
300	printf("Device target number: \t%d\n", libdm_task_get_target_num(task));
301	printf("Device flags: \t\t%d\n", libdm_task_get_flags(task));
302
303	libdm_task_destroy(task);
304	return 0;
305}
306
307/*
308 * List all device in device-mapper driver.
309 */
310static int
311dmctl_list_devices(int argc __unused, char *argv[] __unused, libdm_task_t task)
312{
313	libdm_cmd_t cmd;
314	libdm_iter_t iter;
315	libdm_dev_t dev;
316
317	if (libdm_task_run(task) != 0)
318		err(EXIT_FAILURE, "dmctl_list_devices: libdm_task_run failed.");
319
320	if ((cmd = libdm_task_get_cmd(task)) == NULL)
321		return ENOENT;
322
323	iter = libdm_cmd_iter_create(cmd);
324
325	while((dev = libdm_cmd_get_dev(iter)) != NULL){
326		printf("Device name: %s, device minor: %d \n",
327		    libdm_dev_get_name(dev), libdm_dev_get_minor(dev));
328		libdm_dev_destroy(dev);
329	}
330
331	libdm_iter_destroy(iter);
332	libdm_cmd_destroy(cmd);
333	libdm_task_destroy(task);
334
335	return 0;
336}
337
338/*
339 * Rename device to new name
340 */
341static int
342dmctl_dev_rename(int argc __unused, char *argv[], libdm_task_t task)
343{
344	libdm_cmd_t cmd;
345
346	libdm_task_set_name(dvname, task);
347
348	cmd = libdm_cmd_create();
349	libdm_dev_set_newname(argv[0], cmd);
350	libdm_task_set_cmd(cmd, task);
351
352	if (libdm_task_run(task) != 0)
353		err(EXIT_FAILURE, "dmctl_dev_rename: libdm_task_run failed.");
354
355	libdm_cmd_destroy(cmd);
356	libdm_task_destroy(task);
357
358	return 0;
359}
360
361/*
362 * Remove device from dm device list.
363 */
364static int
365dmctl_dev_remove(int argc __unused, char *argv[] __unused, libdm_task_t task)
366{
367
368	if (dvname == NULL)
369		return (ENOENT);
370
371	libdm_task_set_name(dvname, task);
372
373	if (libdm_task_run(task) != 0)
374		err(EXIT_FAILURE, "dmctl_dev_remove: libdm_task_run failed.");
375
376	libdm_task_destroy(task);
377	return 0;
378}
379
380/*
381 * Resume device which was suspended or created right now.
382 * Replace table in "active slot" witg table in "inactive slot".
383 */
384static int
385dmctl_dev_resume(int argc __unused, char *argv[] __unused, libdm_task_t task)
386{
387
388	libdm_task_set_name(dvname, task);
389
390	if (libdm_task_run(task) != 0)
391		err(EXIT_FAILURE, "dmctl_dev_resume: libdm_task_run failed.");
392
393	libdm_task_destroy(task);
394	return 0;
395}
396
397/*
398 * Resume device which was suspended or created right now.
399 * Replace table in "active slot" with table in "inactive slot".
400 */
401static int
402dmctl_dev_suspend(int argc __unused, char *argv[] __unused, libdm_task_t task)
403{
404
405	libdm_task_set_name(dvname, task);
406	libdm_task_set_suspend_flag(task);
407
408	if (libdm_task_run(task) != 0)
409		err(EXIT_FAILURE, "dmctl_dev_suspend: libdm_task_run failed.");
410
411	libdm_task_destroy(task);
412	return 0;
413}
414
415/*
416 * Get device dependiences from device-mapper. Device dependency is physical
417 * device on which dm device depends.
418 */
419static int
420dmctl_dev_deps(int argc __unused, char *argv[] __unused, libdm_task_t task)
421{
422	libdm_cmd_t cmd;
423	libdm_iter_t iter;
424	dev_t dev_deps;
425
426	libdm_task_set_name(dvname, task);
427
428	if (libdm_task_run(task) != 0)
429		err(EXIT_FAILURE, "dmctl_dev_deps: libdm_task_run failed.");
430
431	if ((cmd = libdm_task_get_cmd(task)) == NULL)
432		return ENOENT;
433
434	iter = libdm_cmd_iter_create(cmd);
435
436	printf("Device %s dependiences \n", dvname);
437
438	while((dev_deps = libdm_cmd_get_deps(iter)) != 0)
439		printf("major: %d minor: %d\n", major(dev_deps), minor(dev_deps));
440
441	libdm_iter_destroy(iter);
442	libdm_cmd_destroy(cmd);
443	libdm_task_destroy(task);
444
445	return 0;
446}
447
448/*
449 * Reload device table to get new one to use.
450 */
451static int
452dmctl_table_reload(int argc, char *argv[], libdm_task_t task)
453{
454	libdm_cmd_t cmd;
455	libdm_table_t table;
456
457	char *params;
458	char *file_path;
459	char target[128];
460	int len;
461	uint64_t start, length;
462
463	file_path = NULL;
464	params = NULL;
465
466	cmd = libdm_cmd_create();
467
468	libdm_task_set_name(dvname, task);
469
470	if (argc != 0)
471		file_path = argv[0];
472
473	while ((params = parse_stdin(file_path)) != NULL) {
474		table = libdm_table_create();
475
476		sscanf(params, "%"PRIu64" %"PRIu64" %s %n", &start, &length, target, &len);
477
478		libdm_table_set_start(start, table);
479		libdm_table_set_length(length, table);
480		libdm_table_set_target(target, table);
481		libdm_table_set_params(params + len, table);
482		libdm_cmd_set_table(table, cmd);
483
484		libdm_table_destroy(table);
485
486		free(params);
487	}
488
489	libdm_task_set_cmd(cmd, task);
490
491	if (libdm_task_run(task) != 0)
492		err(EXIT_FAILURE, "libdm_task_run: from dmctl_table_reload failed.");
493
494	libdm_cmd_destroy(cmd);
495	libdm_task_destroy(task);
496	free(params);
497
498	return 0;
499}
500
501/*
502 * Get table status from device.
503 */
504static int
505dmctl_table_status(int argc __unused, char *argv[] __unused, libdm_task_t task)
506{
507	libdm_cmd_t cmd;
508	libdm_table_t table;
509	libdm_iter_t iter;
510
511	libdm_task_set_name(dvname, task);
512	libdm_task_set_status_flag(task);
513
514	if (libdm_task_run(task) != 0)
515		err(EXIT_FAILURE, "libdm_task_run");
516
517	if ((cmd = libdm_task_get_cmd(task)) == NULL)
518		return ENOENT;
519
520	iter = libdm_cmd_iter_create(cmd);
521
522	printf("Getting device table for device %s\n", dvname);
523
524	while ((table = libdm_cmd_get_table(iter)) != NULL) {
525		printf("%10"PRIu64" %10"PRIu64" %s\n",
526			libdm_table_get_start(table),
527			libdm_table_get_length(table),
528			libdm_table_get_target(table));
529		libdm_table_destroy(table);
530	}
531
532	libdm_iter_destroy(iter);
533	libdm_cmd_destroy(cmd);
534	libdm_task_destroy(task);
535
536	return 0;
537}
538
539static void
540usage(void)
541{
542	int i;
543
544	(void)fprintf(stderr, "usage: %s command [dm device name]\n"
545	    "Available commands are:\n ", getprogname());
546	for (i = 0; commands[i].cmd_name != NULL; i++)
547		(void)fprintf(stderr, "\t%s\t%s\n", commands[i].cmd_name, commands[i].cmd_help);
548	exit(EXIT_FAILURE);
549}
550
551static char *
552parse_stdin(char *file_path)
553{
554	char *buf, *lbuf;
555	size_t len;
556	FILE *fp;
557
558	lbuf = NULL;
559
560	if (file_path) {
561		if ((fp = fopen(file_path, "r")) == NULL)
562			err(ENOENT, "Cannot open table file\n");
563	} else
564		fp = stdin;
565
566	if ((buf = fgetln(fp, &len)) == NULL)
567		return NULL;
568
569	if (buf[len - 1] != '\n')
570		len++;
571
572	if ((lbuf = malloc(len)) == NULL)
573		err(EXIT_FAILURE, "malloc");
574
575	memcpy(lbuf, buf, len);
576	lbuf[len - 1] = '\0';
577
578	return lbuf;
579}
580