/* * 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 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 { status_handler status_fn; map_req_handler map_fn; }; struct dmu_event_data { void *status_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); } void dmu_map_set_writable(struct dmu_map_data *data, int writable) { if (writable) dmu_set_flag(&data->flags, DMU_FLAG_WR); else dmu_clr_flag(&data->flags, DMU_FLAG_WR); } 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 uint32_t make_version(int maj, int min, int patch) { return 0 | (maj << 16) | (min << 8) | patch; } 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) { printf("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; } int dmu_sync_complete(struct dmu_context *ctx, uint64_t id) { struct dmu_msg_status status_msg; status_msg.id_of_op = id; status_msg.status = DM_USERSPACE_SYNC_COMPLETE; DPRINTF("Queuing metadata written for id %llu\n", id); return dmu_ctl_queue_msg(ctx, DM_USERSPACE_STATUS, &status_msg); } 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"); 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; } struct dmu_context *dmu_ctl_open(char *dev, int flags) { int fd, r, type = 0; struct dmu_msg_version msg; struct dmu_msg_version *response; struct dmu_context *ctx = NULL; char *ctl_dev; 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 << 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)); // msg.userspace_ver = make_version(0, 1, 0); // r = dmu_ctl_queue_msg(ctx, DM_USERSPACE_GET_VERSION, &msg); // if (r < 0) // goto out; // // dmu_ctl_send_queue(ctx); // /* FIXME: Hack to recv only one message */ // ctx->buf_size = sizeof(struct dmu_msg) + 1; // dmu_ctl_recv_queue(ctx); // ctx->buf_size = QUEUE_SIZE_KB << 10; // // r = dmu_ctl_peek_queue(ctx, &type, (void**)&response); // if (r < 0) // goto out; // // if (type != DM_USERSPACE_GET_VERSION) { // DPRINTF("Got non-version ping back: %i\n", type); // goto out; // } // // if (response->kernel_ver != msg.userspace_ver) { // DPRINTF("Version mismatch: %x != %x\n", // msg.userspace_ver, response->kernel_ver); // goto out; // } else { // DPRINTF("Version match: %x == %x\n", // msg.userspace_ver, response->kernel_ver); // } 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_status_handler(struct dmu_context *ctx, status_handler handler, void *data) { ctx->events.status_fn = handler; ctx->event_data.status_user_data = data; } void dmu_register_map_handler(struct dmu_context *ctx, map_req_handler handler, void *data) { ctx->events.map_fn = handler; ctx->event_data.map_user_data = data; } 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, uint32_t id) { struct dmu_msg_map_response resp; 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); resp.org_block = req->org_block; resp.new_block = data.block; resp.offset = data.offset; resp.flags = data.flags; resp.id_of_req = data.id; dmu_split_dev(data.copy_src_dev, &resp.src_maj, &resp.src_min); dmu_split_dev(data.dest_dev, &resp.dst_maj, &resp.dst_min); DPRINTF("Mapped %llu -> %llu\n", resp.org_block, resp.new_block); if (ret < 0) dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_FAILED, &resp); else dmu_ctl_queue_msg(ctx, DM_USERSPACE_MAP_BLOCK_RESP, &resp); return ret; } static int fire_status_event(struct dmu_context *ctx, struct dmu_msg_status *status, uint32_t id) { uint32_t user_code; switch (status->status) { case DM_USERSPACE_SYNC_COMPLETE: user_code = DMU_STATUS_SYNC_COMPLETE; break; default: user_code = DMU_STATUS_UNKNOWN; }; if (ctx->events.status_fn) ctx->events.status_fn(ctx->event_data.status_user_data, status->id_of_op, user_code); return 0; } static int decode_message(struct dmu_context *ctx, int type, uint32_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_STATUS: DPRINTF("Status event\n"); return fire_status_event(ctx, (struct dmu_msg_status *)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; }