diff --git a/functional-tests/device-mapper/Makefile b/functional-tests/device-mapper/Makefile new file mode 100644 index 0000000..0102820 --- /dev/null +++ b/functional-tests/device-mapper/Makefile @@ -0,0 +1,11 @@ +dm-ioctl.so: dm-ioctl.o + gcc -shared -o $@ $< -laio + +dm-ioctl.o: dm-ioctl.c + gcc -std=gnu11 -fpic -I. -Wall -c -o $@ $< + +.PHONEY: clean +clean: + rm -f dm-ioctl.so dm-ioctl.o + + diff --git a/functional-tests/device-mapper/dm-ioctl.c b/functional-tests/device-mapper/dm-ioctl.c new file mode 100644 index 0000000..d643199 --- /dev/null +++ b/functional-tests/device-mapper/dm-ioctl.c @@ -0,0 +1,573 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//---------------------------------------------------------------- + +// assuming div is a power of 2 +static size_t round_up(size_t n, size_t div) +{ + size_t mask = div - 1; + return (n + mask) & ~mask; +} + +//---------------------------------------------------------------- + +static void *zalloc(size_t len) +{ + void *ptr = malloc(len); + if (ptr) + memset(ptr, 0, len); + + return ptr; +} + +static void *payload(struct dm_ioctl *ctl) +{ + return ((unsigned char *) ctl) + ctl->data_start; +} + +static struct dm_ioctl *alloc_ctl(size_t payload_size) +{ + size_t len = sizeof(struct dm_ioctl) + payload_size; + struct dm_ioctl *ctl = zalloc(len); + + if (ctl) { + ctl->version[0] = DM_VERSION_MAJOR; + ctl->version[1] = DM_VERSION_MINOR; + ctl->version[2] = DM_VERSION_PATCHLEVEL; + ctl->data_size = len; + ctl->data_start = sizeof(*ctl); + } + + return ctl; +} + +static void free_ctl(struct dm_ioctl *ctl) +{ + free(ctl); +} + +// realloc only copies the dm_ioctl struct, not the payload. +// old is always freed, even in case of error. +static struct dm_ioctl *realloc_ctl(struct dm_ioctl *old, size_t extra_payload) +{ + struct dm_ioctl *ctl; + size_t old_payload_size = old->data_size - sizeof(struct dm_ioctl); + size_t new_payload_size = old_payload_size + extra_payload; + + ctl = alloc_ctl(new_payload_size); + if (ctl) + memcpy(payload(ctl), payload(old), sizeof(*ctl)); + + free_ctl(old); + return ctl; +} + +//---------------------------------------------------------------- + +struct dm_interface { + int fd; +}; + +// FIXME: pass in some form of log object? +struct dm_interface *dm_open() +{ + int fd; + char path[1024]; + struct dm_interface *dmi; + + snprintf(path, sizeof(path), "/dev/%s/%s", DM_DIR, DM_CONTROL_NODE); + fd = open(path, O_RDWR | O_EXCL); + if (fd < 0) + return NULL; + + dmi = zalloc(sizeof(*dmi)); + if (dmi) + dmi->fd = fd; + else + close(fd); + + return dmi; +} + +void dm_close(struct dm_interface *dmi) +{ + close(dmi->fd); + free(dmi); +} + +//---------------------------------------------------------------- + +struct dev_list { + struct dev_list *next; + unsigned major; + unsigned minor; + char *name; +}; + +void free_dev_list(struct dev_list *dl) +{ + struct dev_list *next; + + while (dl) { + next = dl->next; + free(dl->name); + free(dl); + dl = next; + } +} + +struct dev_list_builder { + struct dev_list *head, *tail; +}; + +static void dlb_init(struct dev_list_builder *dlb) +{ + dlb->head = dlb->tail = NULL; +} + +static int dlb_append(struct dev_list_builder *dlb, + unsigned major, unsigned minor, const char *name) +{ + struct dev_list *dl = malloc(sizeof(*dl)); + + if (!dl) + return -ENOMEM; + + dl->major = major; + dl->minor = minor; + dl->name = strdup(name); + + if (dlb->head) { + dlb->tail->next = dl; + dlb->tail = dl; + } else + dlb->head = dlb->tail = dl; + + return 0; +} + +static struct dev_list *dlb_get(struct dev_list_builder *dlb) +{ + return dlb->head; +} + +//---------------------------------------------------------------- + +static int copy_string(char *dest, const char *src, size_t max) +{ + if (strlen(src) + 1 > max) + return -ENOMEM; + + strcpy(dest, src); + return 0; +} + +static int copy_name(struct dm_ioctl *ctl, const char *name) +{ + return copy_string(ctl->name, name, DM_NAME_LEN); +} + +static int copy_uuid(struct dm_ioctl *ctl, const char *uuid) +{ + return copy_string(ctl->uuid, uuid, DM_UUID_LEN); +} + +//---------------------------------------------------------------- + +int dm_version(struct dm_interface *dmi, uint32_t *major, uint32_t *minor, uint32_t *patch) +{ + int r; + struct dm_ioctl *ctl = alloc_ctl(0); + + if (!ctl) + return -ENOMEM; + + r = ioctl(dmi->fd, DM_VERSION, ctl); + *major = ctl->version[0]; + *minor = ctl->version[1]; + *patch = ctl->version[2]; + free_ctl(ctl); + + return r; +} + +int dm_remove_all(struct dm_interface *dmi) +{ + int r; + struct dm_ioctl *ctl = alloc_ctl(0); + + if (!ctl) + return -ENOMEM; + + r = ioctl(dmi->fd, DM_REMOVE_ALL, ctl); + free_ctl(ctl); + + return r; +} + +static bool list_devices(struct dm_interface *dmi, struct dm_ioctl *ctl, + size_t payload_size, struct dev_list **devs, + int *r) +{ + struct dm_name_list *nl; + struct dev_list_builder dlb; + + *r = ioctl(dmi->fd, DM_LIST_DEVICES, ctl); + if (*r < 0) + return true; + + if (ctl->flags & DM_BUFFER_FULL_FLAG) { + free_ctl(ctl); + return false; + } + + dlb_init(&dlb); + nl = (struct dm_name_list *) payload(ctl); + + if (nl->dev) { + for (;;) { + dlb_append(&dlb, major(nl->dev), minor(nl->dev), nl->name); + + if (!nl->next) + break; + + nl = (struct dm_name_list *) (((unsigned char *) nl) + nl->next); + } + } + + *devs = dlb_get(&dlb); + return true; +} + +int dm_list_devices(struct dm_interface *dmi, struct dev_list **devs) +{ + int r; + struct dm_ioctl *ctl; + size_t payload_size = 8192; + + ctl = alloc_ctl(payload_size); + if (!ctl) + return -ENOMEM; + + while (!list_devices(dmi, ctl, payload_size, devs, &r)) { + payload_size *= 2; + ctl = realloc_ctl(ctl, payload_size); + if (!ctl) + return -ENOMEM; + } + free_ctl(ctl); + + return r; +} + +int dm_create_device(struct dm_interface *dmi, const char *name, const char *uuid) +{ + int r; + struct dm_ioctl *ctl = alloc_ctl(0); + + if (!ctl) + return -ENOMEM; + + r = copy_name(ctl, name); + if (r) { + free_ctl(ctl); + return r; + } + + r = copy_uuid(ctl, name); + if (r) { + free_ctl(ctl); + return r; + } + + r = ioctl(dmi->fd, DM_DEV_CREATE, ctl); + free_ctl(ctl); + + return r; +} + +static int dev_cmd(struct dm_interface *dmi, const char *name, int request, unsigned flags) +{ + int r; + struct dm_ioctl *ctl = alloc_ctl(0); + ctl->flags = flags; + r = copy_name(ctl, name); + if (r) { + free_ctl(ctl); + return -ENOMEM; + } + r = ioctl(dmi->fd, request); + free_ctl(ctl); + + return r; +} + +int dm_remove_device(struct dm_interface *dmi, const char *name) +{ + return dev_cmd(dmi, name, DM_DEV_REMOVE, 0); +} + +int dm_suspend_device(struct dm_interface *dmi, const char *name) +{ + return dev_cmd(dmi, name, DM_DEV_SUSPEND, DM_SUSPEND_FLAG); +} + +int dm_resume_device(struct dm_interface *dmi, const char *name) +{ + return dev_cmd(dmi, name, DM_DEV_SUSPEND, 0); +} + +int dm_clear_device(struct dm_interface *dmi, const char *name) +{ + return dev_cmd(dmi, name, DM_TABLE_CLEAR, 0); +} + +//---------------------------------------------------------------- + +struct target { + struct target *next; + + uint64_t len; + char *type; + char *args; +}; + +void free_targets(struct target *t) +{ + while (t) { + struct target *next = t->next; + free(t->type); + free(t->args); + t = next; + } +} + +struct target_builder { + struct target *head, *tail; +}; + +static void tb_init(struct target_builder *tb) +{ + tb->head = tb->tail = NULL; +} + +static int tb_append(struct target_builder *tb, uint64_t len, char *type, char *args) +{ + struct target *t = malloc(sizeof(*t)); + if (!t) + return -ENOMEM; + + t->next = NULL; + t->len = len; + t->type = strdup(type); + t->args = strdup(args); + + if (tb->head) { + tb->tail->next = t; + tb->tail = t; + } else + tb->head = tb->tail = t; + + return 0; +} + +static struct target *tb_get(struct target_builder *tb) +{ + return tb->head; +} + +//---------------------------------------------------------------- +// FIXME: provide some way of freeing a target list. +// FIXME: check the result from alloc_ctl is always being checked. + +static size_t calc_load_payload(struct target *t) +{ + size_t space = 0; + + while (t) { + space += sizeof(struct dm_target_spec); + space += strlen(t->args) + 16; + t = t->next; + } + + return space + 128; +} + +static int prep_load(struct dm_ioctl *ctl, size_t payload_size, + const char *name, struct target *t) +{ + int r; + uint64_t current_sector = 0; + struct dm_target_spec *spec; + + spec = payload(ctl); + + while (t) { + spec->sector_start = current_sector; + current_sector += t->len; + spec->length = t->len; + spec->status = 0; + r = copy_string(spec->target_type, t->type, DM_MAX_TYPE_NAME); + if (r) + return r; + + r = copy_string((char *) spec + 1, t->args, payload_size); + if (r) + return r; + + spec->next = sizeof(*spec) + round_up(strlen(t->args) + 1, 8); + payload_size -= spec->next; + spec = (struct dm_target_spec *) (((char *) spec) + spec->next); + + t = t->next; + } + + return true; +} + +int dm_load(struct dm_interface *dmi, const char *name, + struct target *targets) +{ + int r; + size_t payload_size = calc_load_payload(targets); + struct dm_ioctl *ctl = alloc_ctl(payload_size); + + if (!ctl) + return -ENOMEM; + + r = prep_load(ctl, payload_size, name, targets); + if (r) { + free_ctl(ctl); + return r; + } + + r = copy_name(ctl, name); + if (r) { + free_ctl(ctl); + return -ENOMEM; + } + + r = ioctl(dmi->fd, DM_TABLE_LOAD, ctl); + free_ctl(ctl); + + return r; +} + +//---------------------------------------------------------------- +// returns false if control buffer too small. +static bool get_status(struct dm_interface *dmi, struct dm_ioctl *ctl, + const char *name, unsigned flags, + int *result) +{ + *result = copy_name(ctl, name); + if (*result) { + free_ctl(ctl); + return true; + } + + ctl->flags = flags; + ctl->target_count = 0; + + *result = ioctl(dmi->fd, DM_TABLE_STATUS, ctl); + if (*result) + return true; + + if (ctl->flags & DM_BUFFER_FULL_FLAG) + return false; + + return true; +} + +static int unpack_status(struct dm_ioctl *ctl, struct target **result) +{ + unsigned i; + struct target_builder tb; + struct dm_target_spec *spec = payload(ctl); + + tb_init(&tb); + for (i = 0; i < ctl->target_count; i++) { + tb_append(&tb, spec->length, spec->target_type, (char *) (spec + 1)); + spec = (struct dm_target_spec *) (((char *) spec) + spec->next); + } + + *result = tb_get(&tb); + return 0; +} + +static int status_cmd(struct dm_interface *dmi, const char *name, + struct target **targets, unsigned flags) +{ + int r; + size_t payload_size = 8192; + struct dm_ioctl *ctl = NULL; + + ctl = alloc_ctl(payload_size); + if (!ctl) + return -ENOMEM; + +retry: + if (!get_status(dmi, ctl, name, flags, &r)) { + payload_size *= 2; + ctl = realloc_ctl(ctl, payload_size); + if (!ctl) + return -ENOMEM; + + goto retry; + } + + r = unpack_status(ctl, targets); + free_ctl(ctl); + return r; +} + +int dm_status(struct dm_interface *dmi, const char *name, struct target **targets) +{ + return status_cmd(dmi, name, targets, 0); +} + +int dm_table(struct dm_interface *dmi, const char *name, struct target **targets) +{ + return status_cmd(dmi, name, targets, DM_STATUS_TABLE_FLAG); +} + +#if 0 +int dm_info(struct dm_interface *dmi, const char *name, struct target **targets) +{ + return status_cmd(dmi, name, targets, DM_STATUS_INFO_FLAG); +} +#endif + +int dm_message(struct dm_interface *dmi, const char *name, uint64_t sector, + const char *msg_str) +{ + int r; + size_t msg_len = strlen(msg_str) + 1; + size_t payload_size = msg_len + 32; + struct dm_ioctl *ctl = alloc_ctl(payload_size); + struct dm_target_msg *msg; + + if (!ctl) + return -ENOMEM; + msg = payload(ctl); + copy_name(ctl, name); + msg->sector = sector; + memcpy(msg->message, msg_str, msg_len); + + r = ioctl(dmi->fd, DM_TARGET_MSG, ctl); + free_ctl(ctl); + + return r; +} + +//---------------------------------------------------------------- diff --git a/functional-tests/device-mapper/dm-tests.scm b/functional-tests/device-mapper/dm-tests.scm new file mode 100644 index 0000000..1c174cc --- /dev/null +++ b/functional-tests/device-mapper/dm-tests.scm @@ -0,0 +1,22 @@ +(library + (device-mapper dm-tests) + (export register-dm-tests) + (import (device-mapper ioctl) + (chezscheme) + (functional-tests) + (fmt fmt) + (process) + (temp-file)) + + ;; We have to export something that forces all the initialisation expressions + ;; to run. + (define (register-dm-tests) #t) + + ;;;----------------------------------------------------------- + ;;; scenarios + ;;;----------------------------------------------------------- + (define-scenario (dm create-interface) + "create and destroy an ioctl interface object" + (with-dm (dm) #t)) + + ) diff --git a/functional-tests/device-mapper/ioctl.scm b/functional-tests/device-mapper/ioctl.scm new file mode 100644 index 0000000..bf5c37b --- /dev/null +++ b/functional-tests/device-mapper/ioctl.scm @@ -0,0 +1,78 @@ +(library + (device-mapper ioctl) + + (export dm-open + dm-close + with-dm + + get-version) + + (import (chezscheme) + (fmt fmt) + (srfi s8 receive) + (utils)) + + (define __ (load-shared-object "./device-mapper/dm-ioctl.so")) + + (define (fail msg) + (raise + (condition + (make-error) + (make-message-condition msg)))) + + (define-ftype DMIoctlInterface + (struct + (fd int))) + + (define open% (foreign-procedure "dm_open" () (* DMIoctlInterface))) + + (define (dm-open) + (let ((ptr (open%))) + (if (ftype-pointer-null? ptr) + (fail "couldn't open ioctl interface (permissions?)") + ptr))) + + (define dm-close + (foreign-procedure "dm_close" ((* DMIoctlInterface)) void)) + + (define-syntax with-dm + (syntax-rules () + ((_ (name) b1 b2 ...) + (let ((name (dm-open))) + (dynamic-wind + (lambda () #f) + (lambda () b1 b2 ...) + (lambda () (dm-close name))))))) + + (define-record-type dm-version (fields major minor patch)) + + (define-ftype PtrU32 (* unsigned-32)) + + (define (get-version dm) + (define get + (foreign-procedure "dm_version" ((* DMIoctlInterface) + (* unsigned-32) + (* unsigned-32) + (* unsigned-32)) int)) + + (define (alloc-u32) + (make-ftype-pointer unsigned-32 + (foreign-alloc (ftype-sizeof unsigned-32)))) + + (define (deref-u32 p) + (ftype-ref unsigned-32 () p)) + + (let ((major (alloc-u32)) + (minor (alloc-u32)) + (patch (alloc-u32))) + (get dm major minor patch) + (let ((r (make-dm-version (deref-u32 major) + (deref-u32 minor) + (deref-u32 patch)))) + (foreign-free (ftype-pointer-address major)) + (foreign-free (ftype-pointer-address minor)) + (foreign-free (ftype-pointer-address patch)) + r))) + + +) diff --git a/functional-tests/run-tests b/functional-tests/run-tests index c08f40f..333ea90 100755 --- a/functional-tests/run-tests +++ b/functional-tests/run-tests @@ -7,6 +7,7 @@ (functional-tests) (bcache bcache-tests) (cache-functional-tests) + (device-mapper dm-tests) (era-functional-tests) (parser-combinators) (only (srfi s1 lists) break) @@ -179,6 +180,7 @@ (register-cache-tests) (register-era-tests) (register-bcache-tests) +(register-dm-tests) (with-dir "test-output" ((parse-command-line)))