| /* | |
| * Copyright (C) 2006-2009 Red Hat, Inc. | |
| * | |
| * This file is released under the LGPL. | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/slab.h> | |
| #include <net/sock.h> | |
| #include <linux/workqueue.h> | |
| #include <linux/connector.h> | |
| #include <linux/device-mapper.h> | |
| #include <linux/dm-log-userspace.h> | |
| #include "dm-log-userspace-transfer.h" | |
| static uint32_t dm_ulog_seq; | |
| /* | |
| * Netlink/Connector is an unreliable protocol. How long should | |
| * we wait for a response before assuming it was lost and retrying? | |
| * (If we do receive a response after this time, it will be discarded | |
| * and the response to the resent request will be waited for. | |
| */ | |
| #define DM_ULOG_RETRY_TIMEOUT (15 * HZ) | |
| /* | |
| * Pre-allocated space for speed | |
| */ | |
| #define DM_ULOG_PREALLOCED_SIZE 512 | |
| static struct cn_msg *prealloced_cn_msg; | |
| static struct dm_ulog_request *prealloced_ulog_tfr; | |
| static struct cb_id ulog_cn_id = { | |
| .idx = CN_IDX_DM, | |
| .val = CN_VAL_DM_USERSPACE_LOG | |
| }; | |
| static DEFINE_MUTEX(dm_ulog_lock); | |
| struct receiving_pkg { | |
| struct list_head list; | |
| struct completion complete; | |
| uint32_t seq; | |
| int error; | |
| size_t *data_size; | |
| char *data; | |
| }; | |
| static DEFINE_SPINLOCK(receiving_list_lock); | |
| static struct list_head receiving_list; | |
| static int dm_ulog_sendto_server(struct dm_ulog_request *tfr) | |
| { | |
| int r; | |
| struct cn_msg *msg = prealloced_cn_msg; | |
| memset(msg, 0, sizeof(struct cn_msg)); | |
| msg->id.idx = ulog_cn_id.idx; | |
| msg->id.val = ulog_cn_id.val; | |
| msg->ack = 0; | |
| msg->seq = tfr->seq; | |
| msg->len = sizeof(struct dm_ulog_request) + tfr->data_size; | |
| r = cn_netlink_send(msg, 0, 0, gfp_any()); | |
| return r; | |
| } | |
| /* | |
| * Parameters for this function can be either msg or tfr, but not | |
| * both. This function fills in the reply for a waiting request. | |
| * If just msg is given, then the reply is simply an ACK from userspace | |
| * that the request was received. | |
| * | |
| * Returns: 0 on success, -ENOENT on failure | |
| */ | |
| static int fill_pkg(struct cn_msg *msg, struct dm_ulog_request *tfr) | |
| { | |
| uint32_t rtn_seq = (msg) ? msg->seq : (tfr) ? tfr->seq : 0; | |
| struct receiving_pkg *pkg; | |
| /* | |
| * The 'receiving_pkg' entries in this list are statically | |
| * allocated on the stack in 'dm_consult_userspace'. | |
| * Each process that is waiting for a reply from the user | |
| * space server will have an entry in this list. | |
| * | |
| * We are safe to do it this way because the stack space | |
| * is unique to each process, but still addressable by | |
| * other processes. | |
| */ | |
| list_for_each_entry(pkg, &receiving_list, list) { | |
| if (rtn_seq != pkg->seq) | |
| continue; | |
| if (msg) { | |
| pkg->error = -msg->ack; | |
| /* | |
| * If we are trying again, we will need to know our | |
| * storage capacity. Otherwise, along with the | |
| * error code, we make explicit that we have no data. | |
| */ | |
| if (pkg->error != -EAGAIN) | |
| *(pkg->data_size) = 0; | |
| } else if (tfr->data_size > *(pkg->data_size)) { | |
| DMERR("Insufficient space to receive package [%u] " | |
| "(%u vs %zu)", tfr->request_type, | |
| tfr->data_size, *(pkg->data_size)); | |
| *(pkg->data_size) = 0; | |
| pkg->error = -ENOSPC; | |
| } else { | |
| pkg->error = tfr->error; | |
| memcpy(pkg->data, tfr->data, tfr->data_size); | |
| *(pkg->data_size) = tfr->data_size; | |
| } | |
| complete(&pkg->complete); | |
| return 0; | |
| } | |
| return -ENOENT; | |
| } | |
| /* | |
| * This is the connector callback that delivers data | |
| * that was sent from userspace. | |
| */ | |
| static void cn_ulog_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | |
| { | |
| struct dm_ulog_request *tfr = (struct dm_ulog_request *)(msg + 1); | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return; | |
| spin_lock(&receiving_list_lock); | |
| if (msg->len == 0) | |
| fill_pkg(msg, NULL); | |
| else if (msg->len < sizeof(*tfr)) | |
| DMERR("Incomplete message received (expected %u, got %u): [%u]", | |
| (unsigned)sizeof(*tfr), msg->len, msg->seq); | |
| else | |
| fill_pkg(NULL, tfr); | |
| spin_unlock(&receiving_list_lock); | |
| } | |
| /** | |
| * dm_consult_userspace | |
| * @uuid: log's universal unique identifier (must be DM_UUID_LEN in size) | |
| * @luid: log's local unique identifier | |
| * @request_type: found in include/linux/dm-log-userspace.h | |
| * @data: data to tx to the server | |
| * @data_size: size of data in bytes | |
| * @rdata: place to put return data from server | |
| * @rdata_size: value-result (amount of space given/amount of space used) | |
| * | |
| * rdata_size is undefined on failure. | |
| * | |
| * Memory used to communicate with userspace is zero'ed | |
| * before populating to ensure that no unwanted bits leak | |
| * from kernel space to user-space. All userspace log communications | |
| * between kernel and user space go through this function. | |
| * | |
| * Returns: 0 on success, -EXXX on failure | |
| **/ | |
| int dm_consult_userspace(const char *uuid, uint64_t luid, int request_type, | |
| char *data, size_t data_size, | |
| char *rdata, size_t *rdata_size) | |
| { | |
| int r = 0; | |
| unsigned long tmo; | |
| size_t dummy = 0; | |
| int overhead_size = sizeof(struct dm_ulog_request) + sizeof(struct cn_msg); | |
| struct dm_ulog_request *tfr = prealloced_ulog_tfr; | |
| struct receiving_pkg pkg; | |
| /* | |
| * Given the space needed to hold the 'struct cn_msg' and | |
| * 'struct dm_ulog_request' - do we have enough payload | |
| * space remaining? | |
| */ | |
| if (data_size > (DM_ULOG_PREALLOCED_SIZE - overhead_size)) { | |
| DMINFO("Size of tfr exceeds preallocated size"); | |
| return -EINVAL; | |
| } | |
| if (!rdata_size) | |
| rdata_size = &dummy; | |
| resend: | |
| /* | |
| * We serialize the sending of requests so we can | |
| * use the preallocated space. | |
| */ | |
| mutex_lock(&dm_ulog_lock); | |
| memset(tfr, 0, DM_ULOG_PREALLOCED_SIZE - sizeof(struct cn_msg)); | |
| memcpy(tfr->uuid, uuid, DM_UUID_LEN); | |
| tfr->version = DM_ULOG_REQUEST_VERSION; | |
| tfr->luid = luid; | |
| tfr->seq = dm_ulog_seq++; | |
| /* | |
| * Must be valid request type (all other bits set to | |
| * zero). This reserves other bits for possible future | |
| * use. | |
| */ | |
| tfr->request_type = request_type & DM_ULOG_REQUEST_MASK; | |
| tfr->data_size = data_size; | |
| if (data && data_size) | |
| memcpy(tfr->data, data, data_size); | |
| memset(&pkg, 0, sizeof(pkg)); | |
| init_completion(&pkg.complete); | |
| pkg.seq = tfr->seq; | |
| pkg.data_size = rdata_size; | |
| pkg.data = rdata; | |
| spin_lock(&receiving_list_lock); | |
| list_add(&(pkg.list), &receiving_list); | |
| spin_unlock(&receiving_list_lock); | |
| r = dm_ulog_sendto_server(tfr); | |
| mutex_unlock(&dm_ulog_lock); | |
| if (r) { | |
| DMERR("Unable to send log request [%u] to userspace: %d", | |
| request_type, r); | |
| spin_lock(&receiving_list_lock); | |
| list_del_init(&(pkg.list)); | |
| spin_unlock(&receiving_list_lock); | |
| goto out; | |
| } | |
| tmo = wait_for_completion_timeout(&(pkg.complete), DM_ULOG_RETRY_TIMEOUT); | |
| spin_lock(&receiving_list_lock); | |
| list_del_init(&(pkg.list)); | |
| spin_unlock(&receiving_list_lock); | |
| if (!tmo) { | |
| DMWARN("[%s] Request timed out: [%u/%u] - retrying", | |
| (strlen(uuid) > 8) ? | |
| (uuid + (strlen(uuid) - 8)) : (uuid), | |
| request_type, pkg.seq); | |
| goto resend; | |
| } | |
| r = pkg.error; | |
| if (r == -EAGAIN) | |
| goto resend; | |
| out: | |
| return r; | |
| } | |
| int dm_ulog_tfr_init(void) | |
| { | |
| int r; | |
| void *prealloced; | |
| INIT_LIST_HEAD(&receiving_list); | |
| prealloced = kmalloc(DM_ULOG_PREALLOCED_SIZE, GFP_KERNEL); | |
| if (!prealloced) | |
| return -ENOMEM; | |
| prealloced_cn_msg = prealloced; | |
| prealloced_ulog_tfr = prealloced + sizeof(struct cn_msg); | |
| r = cn_add_callback(&ulog_cn_id, "dmlogusr", cn_ulog_callback); | |
| if (r) { | |
| kfree(prealloced_cn_msg); | |
| return r; | |
| } | |
| return 0; | |
| } | |
| void dm_ulog_tfr_exit(void) | |
| { | |
| cn_del_callback(&ulog_cn_id); | |
| kfree(prealloced_cn_msg); | |
| } |