This is a nontrivial example of ddlink usage, to implement the syntax of 
dmsetup, but using a considerably more compact and extensible kernel interface 
with better error reporting.

The do_create function provides the main device mapper functionality, that is 
creating a device that is composed of several underlying devices called 
targets.  Each target is specified by a set of strings, the first of which is 
the target type and rest being string parameters passed to the target's create 
method.

This ddlink interface employs both writes to the ddlink and ioctls on the 
ddlink.  Initially, an ioctl declares that a new device will be created and 
specifies the number of targets that it will be composed of along with some 
fixed data such as permissions.  (The ddsetup kernel code is perfectly capable 
of counting the number of targets itself, but the device mapper kernel code 
wants to know the final number of targets before creating any of them, for no 
apparent reason.)

For each target, the variable parameters (strings) are written to the ddlink 
one at a time, including the target name, followed by an ioctl that passes some 
fixed fields (sector offset and sector size, the former having no apparent 
purpose).  The ioctl creates the target internally and remembers it as internal 
ddsetup state.  After all the targets are created, the name of the new device 
is written to the ddlink and a final ioctl creates the device.

An error may be detected at any point in this somewhat complex process, in 
which case the ddsetup error handler reads the ddlink to obtain a detailed 
error message.

This is by no means the only way that the ddsetup interface could have been 
constructed using ddlink.  For example, instead of ioctling the ddlink, command 
codes could be written to the ddlink, either separate from or together with the 
string parameters.  However, a combination of writes and ioctls seemed to yield 
the simplest, easiest to read and modify userspace code.  The compactness and 
clarity of the example speaks for itself, at least I think so.  It would be 
instructive to compare this ddsetup.c code to the existing dmsetup and 
libdevmapper code that currently implements these interfaces.

Regards,

Daniel
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ddsetup.h"

// to do:
//  - spinlocks in base ddlink ops
//  - uuids and major/minor command forms
//  - more dmsetup commands

struct token { char *text; int size; };

int get_number(struct token *token, long long *result)
{
	char *next;
	long long number = strtoll(token->text, &next, 10);
	if (next - token->text == token->size) {
		*result = number;
		return 1;
	}
	return 0;
}

int table_line(int dd, struct token *token, int tokens)
{
	long long offset, sectors;
	struct ddtarget target;
	int i;

	if (tokens < 3 ||
		!get_number(&token[0], &offset) ||
		!get_number(&token[1], &sectors))
		return -1;

	if (dd < 0)
		return 0; /* only checking syntax */

	for (i = 2; i < tokens; i++)
		if (write(dd, token[i].text, token[i].size) == -1)
			return -1;

	target.offset = offset;
	target.sectors = sectors;
	if ((ioctl(dd, DMTARGET, &target)) == -1)
		return -1;
	return 0;
}

void set_dmname(char *buffer, unsigned size, char *name)
{
	snprintf(buffer, size , "%s%s", "/dev/mapper/", name);
}

int do_create(int dd, char *name, struct token *tokenvec, unsigned *linevec, int lines)
{
	int line, start, devnum;
	char filename[100];
	struct ddresult got;
	int mode = DMREAD|DMWRITE;

	if (ioctl(dd, DMTABLE, &(struct ddtable){ .targets = lines, .mode = mode }) == -1)
		return -1;

	for (start = 0, line = 0; line < lines; start = linevec[line++])
		if (table_line(dd, tokenvec + start, linevec[line] - start) == -1)
			return -1;

	if (write(dd, name, strlen(name)) == -1)
		return -2;
	if (ioctl(dd, DMCREATE) == -1)
		return -2;
	if (read(dd, &got, sizeof(got)) == -1)
		return -2;

	devnum = makedev(got.dev.major, got.dev.minor);
	set_dmname(filename, sizeof(filename), name);
	return mknod(filename, S_IFBLK, devnum);
}

int do_remove(int dd, char *name)
{
	char filename[100];
	if (write(dd, name, strlen(name)) == -1)
		return -2;
	if ((ioctl(dd, DMREMOVE)) == -1)
		return -2;
	set_dmname(filename, sizeof(filename), name);
	if (unlink(filename) == -1)
		return -2;
	return 0;
}

char *plural(int n)
{
	return n > 1 ? "s:" : n ? ":" : "";
}

char *flagged(int f)
{
	return f? "y" : "n";
}

int main(int argc, char *argv[])
{
	char *action = argv[1];
	int dm, dd, len, num;

	if (argc == 1)
		goto usage;
	if ((dm = open("/dev/mapper/control", O_RDWR)) == -1)
		goto whoops;
	if ((dd = ioctl(dm, DDLINK)) == -1)
		goto whoops;
	if (!strcmp(action, "create")) {
		if (argc < 3)
			goto usage;

		struct stat stat;
		if ((fstat(0, &stat)) == -1)
			return -1;

		if (S_ISCHR(stat.st_mode)) {
			printf("No table given\n");
			exit(1);
		}

		char text[2 << 12];
		int maxtokens = 1000, maxlines = 100;
		int tokens = 0, lines = 0;
		struct token tokenvec[maxtokens];
		unsigned linevec[maxlines]; // handle overflow!!!
		unsigned len = read(0, text, sizeof(text));
		char *next = text, *end = text + len;

		while(tokens < maxtokens) {
			while (next < end && !isgraph(*next) && *next != '\n')
				next++;
			if (next == end || *next == '\n') {
				linevec[lines++] = tokens;
				if (next == end || ++next == end)
					break;
				continue;
			}
			char *last = tokenvec[tokens].text = next ;
			while (next < end && isgraph(*next))
				next++;
			tokenvec[tokens].size = next - last;
			tokens++;
		}
		int line, start;
		for (start = 0, line = 0; line < lines; start = linevec[line++])
			if (table_line(-1, tokenvec + start, linevec[line] - start) < 0) {
				printf("input table syntax error, line %i\n", line + 1);
				exit(1);
			}
		if (do_create(dd, argv[2], tokenvec, linevec, lines))
			goto whoops;
		return 0;
	}
	if (!strcmp(action, "remove")) {
		if (argc < 3)
			goto usage;
		switch (do_remove(dd, argv[2])) {
			case 0: break;
			case -2: goto report;
			default: goto whoops;
		}
		return 0;
	}
	if (!strcmp(action, "suspend")) {
		if (argc < 3)
			goto usage;
		if (write(dd, argv[2], strlen(argv[2])) == -1)
			goto report;
		if ((ioctl(dd, DMSUSPEND)) == -1)
			goto report;
		return 0;
	}
	if (!strcmp(action, "resume")) {
		if (argc < 3)
			goto usage;
		if (write(dd, argv[2], strlen(argv[2])) == -1)
			goto report;
		if ((ioctl(dd, DMRESUME)) == -1)
			goto report;
		return 0;
	}
	if (!strcmp(action, "rename")) {
		if (argc != 4)
			goto usage;
		if (write(dd, argv[2], strlen(argv[2])) == -1)
			goto report;
		if (write(dd, argv[3], strlen(argv[3])) == -1)
			goto report;
		if ((ioctl(dd, DMRENAME)) == -1)
			goto report;
		return 0;
	}
	if (!strcmp(action, "ls")) {
		struct { struct ddname name; char space[100]; } got;
		if (argc != 2)
			goto usage;
		if ((ioctl(dd, DMNAMES)) == -1)
			goto report;
		while ((len = read(dd, &got, sizeof(got)))) {
			if (len == -1)
				goto whoops;
			printf("%.*s (%i.%i)\n",
				len - sizeof got.name,
				got.name.name,
				got.name.dev.major,
				got.name.dev.minor);
		}
		return 0;
	}
	if (!strcmp(action, "deps")) {
		struct devnum got;
		if (argc != 3)
			goto usage;
		if (write(dd, argv[2], strlen(argv[2])) == -1)
			goto report;
		if ((num = ioctl(dd, DMDEPS)) == -1)
			goto report;
		printf("%s has %i target%s", argv[2], num, plural(num));
		while ((len = read(dd, &got, sizeof(got)))) {
			if (len == -1)
				goto report;
			printf(" (%u.%u)", got.major, got.minor);
		}
		printf("\n");
		return 0;
	}
	if (!strcmp(action, "info")) {
		struct dmstatus got;
		if (argc != 3)
			goto usage;
		if (write(dd, argv[2], strlen(argv[2])) == -1)
			goto report;
		if ((ioctl(dd, DMSTATUS)) == -1)
			goto report;
		if ((len = read(dd, &got, sizeof(got))) == -1)
			goto report;
		if (len != sizeof(got) && (errno = ENODATA))
			goto whoops;
		printf("%s: (%u.%u) ", argv[2], got.dev.major, got.dev.minor);
		printf("targets %i, ", got.targets);
		printf("opens %i, ", got.opens);
		printf("event %i, ", got.event);
		printf("present=%s, ", flagged(got.flags & DMFLAG_PRESENT));
		printf("readonly=%s, ", flagged(got.flags & DMFLAG_READONLY));
		printf("suspended=%s\n", flagged(got.flags & DMFLAG_SUSPEND));
		while ((len = read(dd, &got, sizeof(got)))) {
			if (len == -1)
				goto report;
			printf(" (%u.%u)", got.dev.major, got.dev.minor);
		}
		return 0;
	}
	if (!strcmp(action, "version")) {
		struct ddversion got;
		if (argc != 2)
			goto usage;
		if ((ioctl(dd, DMVERSION)) == -1)
			goto report;
		if ((len = read(dd, &got, sizeof(got))) == -1)
			goto report;
		if (len != sizeof(got) && (errno = ENODATA))
			goto whoops;
		printf("ddsetup version %i.%i.%i\n", got.major, got.minor, got.point);
		return 0;
	}
	if (!strcmp(action, "targets")) {
		struct { struct ddtype type; char space[100]; } got;
		if (argc != 2)
			goto usage;
		if ((ioctl(dd, DMTYPES)) == -1)
			goto report;
		while ((len = read(dd, &got, sizeof(got)))) {
			if (len == -1)
				goto report;
			printf("%-18.*s v%i.%i.%i\n",
				len - sizeof(struct ddtype), got.type.name,
				got.type.version.major,
				got.type.version.minor,
				got.type.version.point);
		}
		return 0;
	}
usage:
	printf("usage: %s <command> [<device>] [<options>]\n", argv[0]);
	exit(1);
report:;
	char text[100];
	if ((len = read(dd, text, sizeof(text))) == -1 || !len)
		goto whoops;
	printf("%s: %s (%.*s)\n", argv[0], strerror(errno), len, text);
	exit(1);
whoops:
	printf("%s: %s (%i)\n", argv[0], strerror(errno), errno);
	return 1;
	exit(1);
}
_______________________________________________
Tux3 mailing list
Tux3@tux3.org
http://mailman.tux3.org/cgi-bin/mailman/listinfo/tux3

Reply via email to