/* * Copyright (C) International Business Machines Corp., 2006 * Author: Dan Smith * * This file is subject to the terms and conditions of the GNU Lesser * General Public License. See the file COPYING in the main directory * of this archive for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_MAJ_VER 0 #define MAX_MIN_VER 1 #define DMU_MSG_DEBUG 0 #define QUEUE_SIZE_KB 1024 #if DMU_MSG_DEBUG #define DPRINTF( s, arg... ) fprintf(stderr, s, ##arg) #else #define DPRINTF( s, arg... ) #endif struct dmu_events { map_done_handler_t map_done_fn; map_req_handler_t map_fn; }; struct dmu_event_data { void *map_done_user_data; void *map_user_data; }; struct dmu_context { int fd; unsigned int buf_size; unsigned int in_ptr; unsigned int out_ptr; uint8_t *in_buf; uint8_t *out_buf; uint32_t id_ctr; struct dmu_events events; struct dmu_event_data event_data; }; struct dmu_map_data { uint64_t block; int64_t offset; uint32_t id; uint32_t flags; dev_t dest_dev; dev_t copy_src_dev; }; void dmu_map_set_block(struct dmu_map_data *data, uint64_t block) { data->block = block; } uint64_t dmu_map_get_block(struct dmu_map_data *data) { return data->block; } void dmu_map_set_offset(struct dmu_map_data *data, int64_t offset) { data->offset = offset; } uint32_t dmu_map_get_id(struct dmu_map_data *data) { return data->id; } void dmu_map_set_dest_dev(struct dmu_map_data *data, dev_t dev) { data->dest_dev = dev; } void dmu_map_set_copy_src_dev(struct dmu_map_data *data, dev_t dev) { data->copy_src_dev = dev; dmu_set_flag(&data->flags, DMU_FLAG_COPY_FIRST); } int dmu_map_is_write(struct dmu_map_data *data) { return dmu_get_flag(&data->flags, DMU_FLAG_WR); } void dmu_map_set_sync(struct dmu_map_data *data) { dmu_set_flag(&data->flags, DMU_FLAG_SYNC); } /* * Get the major/minor of the character control device that @dm_device * has exported for us. We do this by looking at the device status * string. */ static int get_dm_control_dev(char *dm_device, unsigned *maj, unsigned *min) { struct dm_task *task; int ret; void *next = NULL; uint64_t start, length; char *ttype = NULL, *params = NULL; task = dm_task_create(DM_DEVICE_STATUS); ret = dm_task_set_name(task, dm_device); if (!ret) { DPRINTF("Failed to set device-mapper target name\n"); dm_task_destroy(task); return -1; } ret = dm_task_run(task); if (!ret) { DPRINTF("Failed to run device-mapper task\n"); dm_task_destroy(task); return -1; } ret = 0; do { next = dm_get_next_target(task, next, &start, &length, &ttype, ¶ms); if (strcmp(ttype, "userspace") == 0) { ret = sscanf(params, "%x:%x", maj, min); if (ret == 2) break; } } while (next); return 0; } /* * Create the character device node for our control channel */ static int make_device_node(unsigned major, unsigned minor) { char path[256]; sprintf(path, "/dev/dmu%i", minor); return mknod(path, S_IFCHR, makedev(major, minor)); } static char *dmu_get_ctl_device(char *dm_device) { unsigned ctl_major, ctl_minor; static char path[256]; if (get_dm_control_dev(dm_device, &ctl_major, &ctl_minor) < 0) return NULL; if (ctl_major == 0) { DPRINTF("Unable to get device number\n"); return NULL; } sprintf(path, "/dev/dmu%i", ctl_minor); if (access(path, R_OK | W_OK)) { if (make_device_node(ctl_major, ctl_minor)) { DPRINTF("Failed to create device node: %s", strerror(errno)); return NULL; } } return path; } static void dmu_split_dev(dev_t dev, uint32_t *maj, uint32_t *min) { *maj = (dev & 0xFF00) >> 8; *min = (dev & 0x00FF); } /* Queue a message for sending */ static int dmu_ctl_queue_msg(struct dmu_context *ctx, int type, void *msgbuf) { struct dmu_msg *msg; if ((ctx->out_ptr + sizeof(*msg)) > ctx->buf_size) { fprintf(stderr, "Out of buffer space for send\n"); return 0; /* No room for this */ } msg = (struct dmu_msg *)(ctx->out_buf + ctx->out_ptr); msg->hdr.msg_type = type; msg->hdr.id = ctx->id_ctr++; memcpy(&msg->payload, msgbuf, sizeof(msg->payload)); ctx->out_ptr += sizeof(*msg); return 1; } static int dmu_ctl_peek_queue(struct dmu_context *ctx, int *type, void **msg_buf) { struct dmu_msg *msg; if (ctx->in_ptr < sizeof(*msg)) return 0; msg = (struct dmu_msg *)ctx->in_buf; *type = msg->hdr.msg_type; *msg_buf = &msg->payload; return 1; } /* Flush queue of messages to the kernel */ int dmu_ctl_send_queue(struct dmu_context *ctx) { int r; DPRINTF("Flushing outgoing queue\n"); if (ctx->out_ptr < sizeof(struct dmu_msg)) { DPRINTF("Refusing to send empty message\n"); return 0; } r = write(ctx->fd, ctx->out_buf, ctx->out_ptr); if (r == ctx->out_ptr) r = 1; else r = 0; ctx->out_ptr = 0; DPRINTF("Finished flushing queue\n"); return r; } /* Fill the queue with requests from the kernel */ static int dmu_ctl_recv_queue(struct dmu_context *ctx) { int r; r = read(ctx->fd, ctx->in_buf, ctx->buf_size); ctx->in_ptr = r; if (r >= 0) r = 1; else r = 0; return r; } int check_version(char *dev) { struct dm_task *task; struct dm_versions *target, *last; int ret; task = dm_task_create(DM_DEVICE_LIST_VERSIONS); ret = dm_task_set_name(task, dev); if (!ret) { DPRINTF("Failed to set device-mapper target name\n"); dm_task_destroy(task); return -1; } ret = dm_task_run(task); if (!ret) { DPRINTF("Failed to run device-mapper task\n"); dm_task_destroy(task); return -1; } target = dm_task_get_versions(task); do { last = target; if (strcmp(target->name, "userspace") == 0) { DPRINTF("%s version: %i.%i.%i\n", target->name, target->version[0], target->version[1], target->version[2]); break; } target = (void *) target + target->next; } while (last != target); if (!target) { DPRINTF("userspace target not found\n"); return -1; } if ((target->version[0] == MAX_MAJ_VER) && (target->version[1] == MAX_MIN_VER)) return 1; else return 0; /* Unsupported */ } struct dmu_context *dmu_ctl_open(char *dev, int flags) { int fd, r, type = 0; struct dmu_context *ctx = NULL; char *ctl_dev; r = check_version(dev); if (r <= 0) { return NULL; } ctl_dev = dmu_get_ctl_device(dev); if (ctl_dev == NULL) return NULL; else if (access(ctl_dev, R_OK | W_OK)) return NULL; fd = open(ctl_dev, O_RDWR | flags); if (fd < 0) goto out; ctx = calloc(sizeof(*ctx), 1); if (!ctx) goto out; ctx->in_buf = malloc(QUEUE_SIZE_KB << 10); if (!ctx->in_buf) goto out; ctx->out_buf = malloc((QUEUE_SIZE_KB * 2) << 10); if (!ctx->out_buf) goto out; ctx->fd = fd; ctx->in_ptr = ctx->out_ptr = 0; ctx->id_ctr = 0; ctx->buf_size = QUEUE_SIZE_KB << 10; memset(&ctx->events, 0, sizeof(ctx->events)); memset(&ctx->event_data, 0, sizeof(ctx->event_data)); return ctx; out: if (ctx && ctx->in_buf) free(ctx->in_buf); if (ctx && ctx->out_buf) free(ctx->out_buf); if (ctx) free(ctx); return NULL; } int dmu_ctl_close(struct dmu_context *ctx) { return close(ctx->fd); } void dmu_register_map_done_handler(struct dmu_context *ctx, map_done_handler_t handler, void *data) { ctx->events.map_done_fn = handler; ctx->event_data.map_done_user_data = data; } void dmu_register_map_handler(struct dmu_context *ctx, map_req_handler_t handler, void *data) { ctx->events.map_fn = handler; ctx->event_data.map_user_data = data; } int dmu_async_map_done(struct dmu_context *ctx, uint64_t id, int fail) { struct dmu_msg_map_done msg; int r; msg.org_block = 0; msg.flags = 0; msg.id_of_op = id; if (fail) r = dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_DONE_FAILED, &msg); else r = dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_DONE, &msg); return r; } int dmu_async_map(struct dmu_context *ctx, struct dmu_map_data *data, int fail) { struct dmu_msg_map_response msg; int r; msg.new_block = data->block; msg.offset = data->offset; msg.flags = data->flags; msg.id_of_req = data->id; dmu_split_dev(data->copy_src_dev, &msg.src_maj, &msg.src_min); dmu_split_dev(data->dest_dev, &msg.dst_maj, &msg.dst_min); if (fail) r = dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_FAILED, &msg); else r = dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_BLOCK_RESP, &msg); return r; } int dmu_events_pending(struct dmu_context *ctx, unsigned int msec) { fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(ctx->fd, &fds); tv.tv_sec = msec / 1000; tv.tv_usec = (msec % 1000) * 1000; if (select(ctx->fd + 1, &fds, NULL, NULL, &tv) < 0) return 0; if (FD_ISSET(ctx->fd, &fds)) return 1; else return 0; } static int fire_map_req_event(struct dmu_context *ctx, struct dmu_msg_map_request *req, uint64_t id) { struct dmu_map_data data; int ret; if (!ctx->events.map_fn) return 1; DPRINTF("Map event for %llu %c\n", req->org_block, dmu_get_flag(&req->flags, DMU_FLAG_WR) ? 'W':'R'); data.block = req->org_block; data.offset = 0; data.id = id; data.flags = req->flags; data.dest_dev = data.copy_src_dev = 0; dmu_clr_flag(&data.flags, DMU_FLAG_COPY_FIRST); dmu_clr_flag(&data.flags, DMU_FLAG_SYNC); ret = ctx->events.map_fn(ctx->event_data.map_user_data, &data); if (ret != 0) { /* If the handler returns 0, we assume they will * complete the operation later */ dmu_async_map(ctx, &data, ret < 0); DPRINTF("Mapped %llu -> %llu\n", resp.org_block, resp.new_block); } return ret != 0; } static int fire_map_done_event(struct dmu_context *ctx, struct dmu_msg_map_done *msg, uint64_t id) { struct dmu_map_data data; int ret = 1; if (ctx->events.map_done_fn) { data.block = msg->org_block; data.offset = 0; data.id = msg->id_of_op; data.flags = msg->flags; data.dest_dev = data.copy_src_dev = 0; ret = ctx->events.map_done_fn(ctx->event_data.map_done_user_data, &data); } if (ret > 0) { /* If the handler returns 0, we assume they will * complete the operation later */ dmu_async_map_done(ctx, msg->id_of_op, ret < 0); DPRINTF("Completed %llu (%llu)\n", msg->org_block, msg->id_of_op); } return ret != 0; } static int decode_message(struct dmu_context *ctx, int type, uint64_t id, uint8_t *msg) { switch (type) { case DM_USERSPACE_MAP_BLOCK_REQ: DPRINTF("Request event: %u\n", id); return fire_map_req_event(ctx, (struct dmu_msg_map_request *)msg, id); case DM_USERSPACE_MAP_DONE: DPRINTF("Map Done event\n"); return fire_map_done_event(ctx, (struct dmu_msg_map_done *)msg, id); default: printf("Unknown message type: %i\n", type); return -1; /* Unknown message type */ }; } int dmu_process_events(struct dmu_context *ctx) { struct dmu_msg *msg; int ptr = 0, ret, do_flush = 0; if (!dmu_ctl_recv_queue(ctx)) return -1; /* Receive failed */ DPRINTF("Got %i bytes\n", ctx->in_ptr); ptr = 0; while (ptr < ctx->in_ptr) { msg = (struct dmu_msg *)&ctx->in_buf[ptr]; ptr += sizeof(*msg); ret = decode_message(ctx, msg->hdr.msg_type, msg->hdr.id, (uint8_t *)&msg->payload); if (ret != 0) do_flush = 1; }; ctx->in_ptr = 0; if (do_flush) { DPRINTF("Flushing outgoing message queue as requested\n"); dmu_ctl_send_queue(ctx); } return 1; }