Merge branch 'master' of github.com:jthornber/thin-provisioning-tools

This commit is contained in:
Joe Thornber 2017-09-28 14:37:18 +01:00
commit 8035e10b2a
5 changed files with 820 additions and 1 deletions

View File

@ -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

View File

@ -0,0 +1,574 @@
#include <linux/dm-ioctl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//----------------------------------------------------------------
// 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->next = NULL;
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;
}
//----------------------------------------------------------------

View File

@ -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))
)

View File

@ -0,0 +1,206 @@
(library
(device-mapper ioctl)
(export dm-open
dm-close
with-dm
get-version
remove-all
list-devices
create-device)
(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 (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)))
(if (zero? (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)
(fail "couldn't get dm version"))))
(define (remove-all dm)
(define do-it
(foreign-procedure "dm_remove_all" ((* DMIoctlInterface)) int))
(let ((r (do-it dm)))
(unless (zero? r)
(fail "remove-all failed"))))
(define-ftype DevList
(struct
(next (* DevList))
(major unsigned)
(minor unsigned)
(name (* unsigned-8))))
(define-ftype DevListPtr (* DevList))
(define-record-type device-details
(fields name major minor))
(define (cstring->string str)
(let loop ((i 0)
(acc '()))
(let ((c (ftype-ref unsigned-8 () str i)))
(if (zero? c)
(list->string (reverse acc))
(loop (+ i 1) (cons (integer->char c) acc))))))
(define (string->cstring str)
(let* ((len (string-length str))
(cstr (make-ftype-pointer unsigned-8
(foreign-alloc (+ 1 len)))))
(let loop ((i 0))
(if (= i len)
(begin
(ftype-set! unsigned-8 () cstr i 0)
cstr)
(ftype-set! unsigned-8 () cstr i (string-ref str i))))))
;;; FIXME: put a dynamic wind in to ensure the dev list gets freed
(define (list-devices dm)
(define list-devs
(foreign-procedure "dm_list_devices" ((* DMIoctlInterface) (* DevListPtr)) int))
(let ((pp (make-ftype-pointer DevListPtr
(foreign-alloc (ftype-sizeof DevListPtr)))))
(if (zero? (list-devs dm pp))
(let loop ((dl (ftype-ref DevListPtr () pp))
(acc '()))
;(fmt #t "dl: " dl ", acc: " acc)
(if (ftype-pointer-null? dl)
acc
(loop (ftype-ref DevList (next) dl)
(cons (make-device-details
(cstring->string (ftype-ref DevList (name) dl))
(ftype-ref DevList (major) dl)
(ftype-ref DevList (minor) dl))
acc))))
(fail "dm_list_devices ioctl failed"))))
(define (create-device dm name uuid)
(define create
(foreign-procedure "dm_create_device" ((* DMIoctlInterface) string string) int))
(unless (zero? (create dm name uuid))
(fail "create-device failed")))
(define-syntax define-dev-cmd
(syntax-rules ()
((_ nm proc)
(define (nm dm name)
(define fn
(foreign-procedure proc ((* DMIoctlInterface) string) int))
(unless (zero? (fn dm name))
(fail (string-append proc " failed")))))))
(define-dev-cmd remove-device "dm_remove_device")
(define-dev-cmd suspend-device "dm_suspend_device")
(define-dev-cmd resume-device "dm_resume_device")
(define-dev-cmd clear-device "dm_clear_device")
(define-ftype Target
(struct
(next (* Target))
(len unsigned-64)
(type (* unsigned-8))
(args (* unsigned-8))))
(define-ftype TargetPtr (* Target))
(define-record-type target
(fields (mutable len) (mutable type) (mutable args)))
(define (build-c-target next len type args)
(let ((t (make-ftype-pointer Target
(foreign-alloc
(ftype-sizeof Target)))))
(ftype-set! Target (next) t next)
(ftype-set! Target (len) t len)
(ftype-set! Target (type) t (string->cstring type))
(ftype-set! Target (args) t (string->cstring args))))
(define (build-c-targets targets)
(let loop ((t targets)
(tail (make-ftype-pointer Target 0)))
(if (null? t)
tail
(loop (cdr targets)
(build-c-target tail (target-len t) (target-type t) (target-args t))))))
(define (free-c-targets t)
(let loop ((t t)
(acc '()))
(if (ftype-pointer-null? t)
(map foreign-free acc)
(loop (ftype-ref Target (next) t) (cons t acc)))))
(define (load-table dm name targets)
(define load
(foreign-procedure "dm_load" ((* DMIoctlInterface) string (* Target)) int))
(let* ((ctargets (build-c-targets targets))
(r (load dm name ctargets)))
(free-c-targets ctargets)
(unless (zero? r)
(fail "dm_load failed"))))
)

View File

@ -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)
@ -55,10 +56,14 @@
(intersperse "/"
(map symbol->string keys)))))))
(define (string-prefix? p str)
(and (>= (string-length str) (string-length p))
(string=? p (substring str 0 (string-length p)))))
;; If the filter begins with 're:' then we make a regex matcher, otherwise
;; we use a simple string matcher.
(define (mk-single-matcher pattern)
(if (string=? (substring pattern 0 3) "re:")
(if (string-prefix? "re:" pattern)
(mk-regex-matcher (substring pattern 3 (string-length pattern)))
(mk-string-matcher pattern)))
@ -175,6 +180,7 @@
(register-cache-tests)
(register-era-tests)
(register-bcache-tests)
(register-dm-tests)
(with-dir "test-output"
((parse-command-line)))