From: VMware, Inc <> Date: Thu, 15 Oct 2009 21:31:03 +0000 (-0700) Subject: Linux HGFS: avoid unnecessary copying of buffers X-Git-Tag: 2009.10.15-201664~14 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ba7b44d054577ca9efee89fcd4001aa4ba6e016c;p=thirdparty%2Fopen-vm-tools.git Linux HGFS: avoid unnecessary copying of buffers Instead of allocating additional memory chunk when sending over TCP/Vsock channel and using local receiving buffer in TCP/Vsock receiver thread implement per-transport memory allocation that would allow allocating all necessary memory up-front. This also lays ground work for allocating variable-size requests. Additionally rework receiving thread logic so that we don't have to wait for receive operation to time our before we can suspend or shutdown TCP/Vsock channel. Also move most of the duplicated channel handling logic in generic transport code and reduce amount of locks (we really only need channel lock). Signed-off-by: Marcelo Vanzin --- diff --git a/open-vm-tools/modules/linux/vmhgfs/bdhandler.c b/open-vm-tools/modules/linux/vmhgfs/bdhandler.c index 0111e4df5..935c8a92e 100644 --- a/open-vm-tools/modules/linux/vmhgfs/bdhandler.c +++ b/open-vm-tools/modules/linux/vmhgfs/bdhandler.c @@ -37,8 +37,6 @@ #include "transport.h" #include "vm_assert.h" -HgfsTransportChannel bdChannel; - /* *----------------------------------------------------------------------------- @@ -61,29 +59,14 @@ HgfsBdChannelOpen(HgfsTransportChannel *channel) // IN: Channel { Bool ret = FALSE; - compat_mutex_lock(&channel->connLock); - switch (channel->status) { - case HGFS_CHANNEL_UNINITIALIZED: - ret = FALSE; - break; - case HGFS_CHANNEL_CONNECTED: + ASSERT(channel->status == HGFS_CHANNEL_NOTCONNECTED); + + if (HgfsBd_OpenBackdoor((RpcOut **)&channel->priv)) { + LOG(8, ("VMware hgfs: %s: backdoor opened.\n", __func__)); ret = TRUE; - break; - case HGFS_CHANNEL_NOTCONNECTED: - if (HgfsBd_OpenBackdoor((RpcOut **)&channel->priv)) { - LOG(8, ("VMware hgfs: %s: backdoor opened.\n", __func__)); - bdChannel.status = HGFS_CHANNEL_CONNECTED; - ret = TRUE; - ASSERT(channel->priv != NULL); - } else { - ret = FALSE; - } - break; - default: - ASSERT(0); /* Not reached. */ + ASSERT(channel->priv != NULL); } - compat_mutex_unlock(&channel->connLock); return ret; } @@ -107,18 +90,51 @@ HgfsBdChannelOpen(HgfsTransportChannel *channel) // IN: Channel static void HgfsBdChannelClose(HgfsTransportChannel *channel) // IN: Channel { - compat_mutex_lock(&channel->connLock); - if (channel->status == HGFS_CHANNEL_CONNECTED) { - ASSERT(channel->priv != NULL); - HgfsBd_CloseBackdoor((RpcOut **)&channel->priv); - ASSERT(channel->priv == NULL); - channel->status = HGFS_CHANNEL_NOTCONNECTED; - } - compat_mutex_unlock(&channel->connLock); + ASSERT(channel->priv != NULL); + + HgfsBd_CloseBackdoor((RpcOut **)&channel->priv); + ASSERT(channel->priv == NULL); + LOG(8, ("VMware hgfs: %s: backdoor closed.\n", __func__)); } +/* + *----------------------------------------------------------------------------- + * + * HgfsBdChannelAllocate -- + * + * Allocate request uin the way that is suitable for sending through + * backdoor. + * + * Results: + * NULL on failure; otherwise address of the new request. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static HgfsReq * +HgfsBdChannelAllocate(size_t payloadSize) // IN: size of requests payload +{ + HgfsReq *req; + + req = kmalloc(sizeof(*req) + HGFS_SYNC_REQREP_CLIENT_CMD_LEN + payloadSize, + GFP_KERNEL); + if (likely(req)) { + /* Setup the packet prefix. */ + memcpy(req->buffer, HGFS_SYNC_REQREP_CLIENT_CMD, + HGFS_SYNC_REQREP_CLIENT_CMD_LEN); + + req->payload = req->buffer + HGFS_SYNC_REQREP_CLIENT_CMD_LEN; + } + + return req; +} + + /* *---------------------------------------------------------------------- * @@ -147,62 +163,24 @@ HgfsBdChannelSend(HgfsTransportChannel *channel, // IN: Channel ASSERT(req->state == HGFS_REQ_STATE_UNSENT); ASSERT(req->payloadSize <= HGFS_PACKET_MAX); - compat_mutex_lock(&channel->connLock); - - if (channel->status != HGFS_CHANNEL_CONNECTED) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: Backdoor not opened\n", __func__)); - compat_mutex_unlock(&channel->connLock); - return -ENOTCONN; - } - - payloadSize = req->payloadSize; LOG(8, ("VMware hgfs: %s: backdoor sending.\n", __func__)); + payloadSize = req->payloadSize; ret = HgfsBd_Dispatch(channel->priv, HGFS_REQ_PAYLOAD(req), &payloadSize, &replyPacket); if (ret == 0) { LOG(8, ("VMware hgfs: %s: Backdoor reply received.\n", __func__)); /* Request sent successfully. Copy the reply and wake the client. */ ASSERT(replyPacket); - HgfsCompleteReq(req, replyPacket, payloadSize); - } else { - channel->priv = NULL; - channel->status = HGFS_CHANNEL_NOTCONNECTED; + ASSERT(payloadSize <= req->bufferSize); + memcpy(HGFS_REQ_PAYLOAD(req), replyPacket, payloadSize); + req->payloadSize = payloadSize; + HgfsCompleteReq(req); } - compat_mutex_unlock(&channel->connLock); return ret; } -/* - *---------------------------------------------------------------------- - * - * HgfsBdChannelExit -- - * - * Tear down the channel. - * - * Results: - * None - * - * Side effects: - * None - * - *---------------------------------------------------------------------- - */ - -static void -HgfsBdChannelExit(HgfsTransportChannel *channel) // IN -{ - compat_mutex_lock(&channel->connLock); - if (channel->priv != NULL) { - HgfsBd_CloseBackdoor((RpcOut **)&channel->priv); - ASSERT(channel->priv == NULL); - } - channel->status = HGFS_CHANNEL_UNINITIALIZED; - compat_mutex_unlock(&channel->connLock); -} - - /* *---------------------------------------------------------------------- * @@ -222,14 +200,15 @@ HgfsBdChannelExit(HgfsTransportChannel *channel) // IN HgfsTransportChannel* HgfsGetBdChannel(void) { - bdChannel.name = "backdoor"; - bdChannel.ops.open = HgfsBdChannelOpen; - bdChannel.ops.close = HgfsBdChannelClose; - bdChannel.ops.send = HgfsBdChannelSend; - bdChannel.ops.recv = NULL; - bdChannel.ops.exit = HgfsBdChannelExit; - bdChannel.priv = NULL; - compat_mutex_init(&bdChannel.connLock); - bdChannel.status = HGFS_CHANNEL_NOTCONNECTED; - return &bdChannel; + static HgfsTransportChannel channel; + + channel.name = "backdoor"; + channel.ops.open = HgfsBdChannelOpen; + channel.ops.close = HgfsBdChannelClose; + channel.ops.allocate = HgfsBdChannelAllocate; + channel.ops.send = HgfsBdChannelSend; + channel.priv = NULL; + channel.status = HGFS_CHANNEL_NOTCONNECTED; + + return &channel; } diff --git a/open-vm-tools/modules/linux/vmhgfs/filesystem.c b/open-vm-tools/modules/linux/vmhgfs/filesystem.c index 3e3ddeff0..645b57e76 100644 --- a/open-vm-tools/modules/linux/vmhgfs/filesystem.c +++ b/open-vm-tools/modules/linux/vmhgfs/filesystem.c @@ -72,7 +72,6 @@ spinlock_t hgfsBigLock = SPIN_LOCK_UNLOCKED; /* Other variables. */ -compat_kmem_cache *hgfsReqCache = NULL; compat_kmem_cache *hgfsInodeCache = NULL; /* Global protocol version switch. */ @@ -621,17 +620,6 @@ HgfsInitFileSystem(void) /* Initialize primitives. */ HgfsResetOps(); - /* Setup the request slab allocator. */ - hgfsReqCache = compat_kmem_cache_create("hgfsReqCache", - sizeof (HgfsReq), - 0, - SLAB_HWCACHE_ALIGN, - NULL); - if (hgfsReqCache == NULL) { - printk(KERN_WARNING "VMware hgfs: failed to create request allocator\n"); - goto error_caches; - } - /* Setup the inode slab allocator. */ hgfsInodeCache = compat_kmem_cache_create("hgfsInodeCache", sizeof (HgfsInodeInfo), @@ -640,7 +628,7 @@ HgfsInitFileSystem(void) HgfsInodeCacheCtor); if (hgfsInodeCache == NULL) { printk(KERN_WARNING "VMware hgfs: failed to create inode allocator\n"); - goto error_caches; + return FALSE; } /* Initialize the transport. */ @@ -652,22 +640,16 @@ HgfsInitFileSystem(void) */ if (register_filesystem(&hgfsType)) { printk(KERN_WARNING "VMware hgfs: failed to register filesystem\n"); - goto error_caches; + kmem_cache_destroy(hgfsInodeCache); + return FALSE; } LOG(4, (KERN_DEBUG "VMware hgfs: Module Loaded\n")); + #ifdef HGFS_ENABLE_WRITEBACK LOG(4, (KERN_DEBUG "VMware hgfs: writeback cache enabled\n")); #endif - return TRUE; -error_caches: - if (hgfsInodeCache != NULL) { - kmem_cache_destroy(hgfsInodeCache); - } - if (hgfsReqCache != NULL) { - kmem_cache_destroy(hgfsReqCache); - } - return FALSE; + return TRUE; } @@ -692,14 +674,6 @@ HgfsCleanupFileSystem(void) { Bool success = TRUE; -/* FIXME: Check actual kernel version when RR's modules went in */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 45) - if (MOD_IN_USE) { - printk(KERN_WARNING "VMware hgfs: filesystem in use, removal failed\n"); - success = FALSE; - } -#endif - /* * Unregister the filesystem. This should be the first thing we do in * the module cleanup code. @@ -714,7 +688,6 @@ HgfsCleanupFileSystem(void) /* Destroy the inode and request slabs. */ kmem_cache_destroy(hgfsInodeCache); - kmem_cache_destroy(hgfsReqCache); LOG(4, (KERN_DEBUG "VMware hgfs: Module Unloaded\n")); return success; diff --git a/open-vm-tools/modules/linux/vmhgfs/module.c b/open-vm-tools/modules/linux/vmhgfs/module.c index 2490f8c3e..39a2cfaa0 100644 --- a/open-vm-tools/modules/linux/vmhgfs/module.c +++ b/open-vm-tools/modules/linux/vmhgfs/module.c @@ -38,29 +38,10 @@ */ int LOGLEVEL_THRESHOLD = 4; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 9) module_param(LOGLEVEL_THRESHOLD, int, 0444); -#else -MODULE_PARM(LOGLEVEL_THRESHOLD, "i"); -#endif - MODULE_PARM_DESC(LOGLEVEL_THRESHOLD, "Set verbosity (0 means no log, 10 means very verbose, 4 is default)"); #endif -char *HOST_IP = NULL; -int HOST_PORT = 2000; /* Defaulted to 2000. */ -int HOST_VSOCKET_PORT = 0; /* Disabled by default. */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 9) -module_param(HOST_IP, charp, 0444); -module_param(HOST_PORT, int, 0444); -module_param(HOST_VSOCKET_PORT, int, 0444); -#else -MODULE_PARM(HOST_IP, "s"); -MODULE_PARM(HOST_PORT, "i"); -MODULE_PARM(HOST_VSOCKET_PORT, "i"); -#endif - /* Module information. */ MODULE_AUTHOR("VMware, Inc."); MODULE_DESCRIPTION("VMware Host/Guest File System"); diff --git a/open-vm-tools/modules/linux/vmhgfs/module.h b/open-vm-tools/modules/linux/vmhgfs/module.h index faeaaa7ed..5423ef84a 100644 --- a/open-vm-tools/modules/linux/vmhgfs/module.h +++ b/open-vm-tools/modules/linux/vmhgfs/module.h @@ -53,10 +53,6 @@ extern int LOGLEVEL_THRESHOLD; #define LOG(level, args) #endif -extern char *HOST_IP; -extern int HOST_PORT; -extern int HOST_VSOCKET_PORT; - /* Blocksize to be set in superblock. (XXX how is this used?) */ #define HGFS_BLOCKSIZE 1024 @@ -231,7 +227,6 @@ extern struct file_operations HgfsDirFileOperations; extern struct address_space_operations HgfsAddressSpaceOperations; /* Other global state. */ -extern compat_kmem_cache *hgfsReqCache; extern compat_kmem_cache *hgfsInodeCache; extern HgfsOp hgfsVersionOpen; diff --git a/open-vm-tools/modules/linux/vmhgfs/request.c b/open-vm-tools/modules/linux/vmhgfs/request.c index 344e78907..0bd6cb880 100644 --- a/open-vm-tools/modules/linux/vmhgfs/request.c +++ b/open-vm-tools/modules/linux/vmhgfs/request.c @@ -41,16 +41,44 @@ #include "fsutil.h" #include "vm_assert.h" -static uint64 hgfsIdCounter = 0; -static spinlock_t hgfsIdLock = SPIN_LOCK_UNLOCKED; +/* + *---------------------------------------------------------------------- + * + * HgfsRequestInit -- + * + * Initializes new request structure. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ +static void +HgfsRequestInit(HgfsReq *req, // IN: request to initialize + size_t bufferSize, // Size of the buffer allocated with request + int requestId) // IN: ID assigned to the request +{ + ASSERT(req); + + kref_init(&req->kref); + INIT_LIST_HEAD(&req->list); + init_waitqueue_head(&req->queue); + req->id = requestId; + req->payloadSize = 0; + req->bufferSize = bufferSize; + req->state = HGFS_REQ_STATE_ALLOCATED; +} /* *---------------------------------------------------------------------- * * HgfsGetNewRequest -- * - * Get a new request structure off the free list and initialize it. + * Allocates and initializes new request structure. * * Results: * On success the new struct is returned with all fields @@ -65,28 +93,60 @@ static spinlock_t hgfsIdLock = SPIN_LOCK_UNLOCKED; HgfsReq * HgfsGetNewRequest(void) { - HgfsReq *req = NULL; + static atomic_t hgfsIdCounter = ATOMIC_INIT(0); + HgfsReq *req; - req = kmem_cache_alloc(hgfsReqCache, GFP_KERNEL); + req = HgfsTransportAllocateRequest(HGFS_PACKET_MAX); if (req == NULL) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsGetNewRequest: " - "can't allocate memory\n")); + LOG(4, (KERN_DEBUG "VMware hgfs: %s: can't allocate memory\n", __func__)); return NULL; } - INIT_LIST_HEAD(&req->list); - init_waitqueue_head(&req->queue); - req->payloadSize = 0; - req->state = HGFS_REQ_STATE_ALLOCATED; - /* Setup the packet prefix. */ - memcpy(req->packet, HGFS_SYNC_REQREP_CLIENT_CMD, - HGFS_SYNC_REQREP_CLIENT_CMD_LEN); - spin_lock(&hgfsIdLock); - req->id = hgfsIdCounter; - hgfsIdCounter++; - spin_unlock(&hgfsIdLock); + + HgfsRequestInit(req, HGFS_PACKET_MAX, + atomic_inc_return(&hgfsIdCounter) - 1); + return req; } +/* + *---------------------------------------------------------------------- + * + * HgfsCopyRequest -- + * + * Allocates and initializes new request structure and copies + * existing request into it. + * + * Results: + * On success the new struct is returned with all fields + * initialized. Returns NULL on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +HgfsReq * +HgfsCopyRequest(HgfsReq *req) // IN: request to be copied +{ + HgfsReq *newReq; + + ASSERT(req); + + newReq = HgfsTransportAllocateRequest(req->bufferSize); + if (newReq == NULL) { + LOG(4, (KERN_DEBUG "VMware hgfs: %s: can't allocate memory\n", __func__)); + return NULL; + } + + HgfsRequestInit(newReq, req->bufferSize, req->id); + + /* Copy payload from the original request. */ + newReq->payloadSize = req->payloadSize; + memcpy(newReq->payload, req->payload, req->payloadSize); + + return newReq; +} /* *---------------------------------------------------------------------- @@ -119,16 +179,16 @@ HgfsSendRequest(HgfsReq *req) // IN/OUT: Outgoing request ret = HgfsTransportSendRequest(req); LOG(8, (KERN_DEBUG "VMware hgfs: HgfsSendRequest: request finished, " "return %d\n", ret)); + return ret; } - /* *---------------------------------------------------------------------- * - * HgfsFreeRequest -- + * HgfsRequestFreeMemory -- * - * Free an HGFS request. + * Frees memory allocated for a request. * * Results: * None @@ -139,14 +199,68 @@ HgfsSendRequest(HgfsReq *req) // IN/OUT: Outgoing request *---------------------------------------------------------------------- */ +static void HgfsRequestFreeMemory(struct kref *kref) +{ + HgfsReq *req = container_of(kref, HgfsReq, kref); + + LOG(10, (KERN_DEBUG "VMware hgfs: %s: freeing request %d\n", + __func__, req->id)); + kfree(req); +} + +/* + *---------------------------------------------------------------------- + * + * HgfsRequestPutRef -- + * + * Decrease reference count of HGFS request. + * + * Results: + * None + * + * Side effects: + * May cause request to be destroyed. + * + *---------------------------------------------------------------------- + */ + void -HgfsFreeRequest(HgfsReq *req) // IN: Request to free +HgfsRequestPutRef(HgfsReq *req) // IN: Request { - ASSERT(hgfsReqCache); + if (req) { + LOG(10, (KERN_DEBUG "VMware hgfs: %s: request %d\n", + __func__, req->id)); + kref_put(&req->kref, HgfsRequestFreeMemory); + } +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsRequestGetRef -- + * + * Increment reference count of HGFS request. + * + * Results: + * Pointer to the same HGFS request. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ - if (req != NULL) { - kmem_cache_free(hgfsReqCache, req); +HgfsReq * +HgfsRequestGetRef(HgfsReq *req) // IN: Request +{ + if (req) { + LOG(10, (KERN_DEBUG "VMware hgfs: %s: request %d\n", + __func__, req->id)); + kref_get(&req->kref); } + + return req; } @@ -183,8 +297,7 @@ HgfsReplyStatus(HgfsReq *req) // IN * * HgfsCompleteReq -- * - * Copies the reply packet into the request structure and wakes up - * the associated client. + * Marks request as completed and wakes up sender. * * Results: * None @@ -196,20 +309,41 @@ HgfsReplyStatus(HgfsReq *req) // IN */ void -HgfsCompleteReq(HgfsReq *req, // IN: Request - char const *reply, // IN: Reply packet - size_t replySize) // IN: Size of reply packet +HgfsCompleteReq(HgfsReq *req) // IN: Request { ASSERT(req); - ASSERT(reply); - ASSERT(replySize <= HGFS_PACKET_MAX); - memcpy(HGFS_REQ_PAYLOAD(req), reply, replySize); - req->payloadSize = replySize; req->state = HGFS_REQ_STATE_COMPLETED; - if (!list_empty(&req->list)) { - list_del_init(&req->list); - } /* Wake up the client process waiting for the reply to this request. */ wake_up(&req->queue); } + + +/* + *---------------------------------------------------------------------- + * + * HgfsFailReq -- + * + * Marks request as failed and calls HgfsCompleteReq to wake up + * sender. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +void HgfsFailReq(HgfsReq *req, // IN: erequest to be marked failed + int error) // IN: error code +{ + HgfsReply *reply = req->payload; + + reply->id = req->id; + reply->status = error; + + req->payloadSize = sizeof *reply; + HgfsCompleteReq(req); +} diff --git a/open-vm-tools/modules/linux/vmhgfs/request.h b/open-vm-tools/modules/linux/vmhgfs/request.h index 1b3edeae2..e4a9bdebf 100644 --- a/open-vm-tools/modules/linux/vmhgfs/request.h +++ b/open-vm-tools/modules/linux/vmhgfs/request.h @@ -38,7 +38,7 @@ #include "vm_basic_types.h" /* Macros for accessing the payload portion of the HGFS request packet. */ -#define HGFS_REQ_PAYLOAD(hgfsReq) ((hgfsReq)->packet + HGFS_CLIENT_CMD_LEN) +#define HGFS_REQ_PAYLOAD(hgfsReq) ((hgfsReq)->payload) /* XXX: Needs change when VMCI is supported. */ #define HGFS_REQ_PAYLOAD_V3(hgfsReq) (HGFS_REQ_PAYLOAD(hgfsReq) + sizeof(HgfsRequest)) @@ -73,7 +73,7 @@ typedef enum { HGFS_REQ_STATE_ALLOCATED, HGFS_REQ_STATE_UNSENT, HGFS_REQ_STATE_SUBMITTED, - HGFS_REQ_STATE_COMPLETED, + HGFS_REQ_STATE_COMPLETED, /* Both header and payload were received. */ } HgfsState; /* @@ -81,9 +81,15 @@ typedef enum { */ typedef struct HgfsReq { + /* Reference count */ + struct kref kref; + /* Links to place the object on various lists. */ struct list_head list; + /* ID of the transport (its address) */ + void *transportId; + /* * When clients wait for the reply to a request, they'll wait on this * wait queue. @@ -96,23 +102,35 @@ typedef struct HgfsReq { /* ID of this request */ uint32 id; + /* Pointer to payload in the buffer */ + void *payload; + /* Total size of the payload.*/ size_t payloadSize; + /* + * Size of the data buffer (below), not including size of chunk + * used by transport. Must be enough to hold both request and + * reply (but not at the same time). + */ + size_t bufferSize; + /* * Packet of data, for both incoming and outgoing messages. * Include room for the command. */ - char packet[HGFS_PACKET_MAX + HGFS_CLIENT_CMD_LEN]; + unsigned char buffer[]; } HgfsReq; /* Public functions (with respect to the entire module). */ HgfsReq *HgfsGetNewRequest(void); +HgfsReq *HgfsCopyRequest(HgfsReq *req); int HgfsSendRequest(HgfsReq *req); -void HgfsFreeRequest(HgfsReq *req); +HgfsReq *HgfsRequestGetRef(HgfsReq *req); +void HgfsRequestPutRef(HgfsReq *req); +#define HgfsFreeRequest(req) HgfsRequestPutRef(req) HgfsStatus HgfsReplyStatus(HgfsReq *req); -void HgfsCompleteReq(HgfsReq *req, - char const *reply, - size_t replySize); +void HgfsCompleteReq(HgfsReq *req); +void HgfsFailReq(HgfsReq *req, int error); #endif // _HGFS_DRIVER_REQUEST_H_ diff --git a/open-vm-tools/modules/linux/vmhgfs/tcp.c b/open-vm-tools/modules/linux/vmhgfs/tcp.c index 82743a104..a4e5f4642 100644 --- a/open-vm-tools/modules/linux/vmhgfs/tcp.c +++ b/open-vm-tools/modules/linux/vmhgfs/tcp.c @@ -41,14 +41,25 @@ #include "compat_sched.h" #include "compat_sock.h" #include "compat_timer.h" + #include "vm_assert.h" #include "hgfsProto.h" - #include "hgfsDevLinux.h" #include "module.h" #include "tcp.h" +static char *HOST_IP; +module_param(HOST_IP, charp, 0444); + +static int HOST_PORT = 2000; /* Defaulted to 2000. */ +module_param(HOST_PORT, int, 0444); + +static int HOST_VSOCKET_PORT = 0; /* Disabled by default. */ +module_param(HOST_VSOCKET_PORT, int, 0444); + +#define INCLUDE_VSOCKETS + #ifdef INCLUDE_VSOCKETS #include "vmci_defs.h" @@ -63,12 +74,6 @@ * Stubs for undefined functions. */ -int -VMCISock_GetAFValue (void) -{ - return -1; -} - void VMCISock_KernelDeregister(void) { @@ -81,178 +86,349 @@ VMCISock_KernelRegister(void) #endif - -/* Socket recv timeout value, counted in HZ. */ -#define HGFS_SOCKET_RECV_TIMEOUT 10 +/* Indicates that data is ready to be received */ +#define HGFS_REQ_THREAD_RECV (1 << 0) /* Recv states for the recvBuffer. */ typedef enum { - HGFS_TCP_CONN_RECV_HEADER, - HGFS_TCP_CONN_RECV_DATA, -} HgfsTcpRecvState; + HGFS_CONN_RECV_SOCK_HDR, /* Waiting for socket header */ + HGFS_CONN_RECV_REP_HDR, /* Waiting for HgfsReply header */ + HGFS_CONN_RECV_REP_PAYLOAD, /* Waiting for reply payload */ +} HgfsSocketRecvState; /* HGFS receive buffer. */ -typedef struct HgfsTcpRecvBuffer { +typedef struct HgfsSocketRecvBuffer { HgfsSocketHeader header; /* Buffer for receiving header. */ - char data[HGFS_PACKET_MAX]; /* Buffer for receiving packet. */ - HgfsTcpRecvState state; /* Reply receive state. */ + HgfsReply reply; /* Buffer for receiving reply */ + HgfsReq *req; /* Request currently being received. */ + char sink[HGFS_PACKET_MAX]; /* Buffer for data to be discarded. */ + HgfsSocketRecvState state; /* Reply receive state. */ int len; /* Number of bytes to receive. */ char *buf; /* Pointer to the buffer. */ -} HgfsTcpRecvBuffer; +} HgfsSocketRecvBuffer; + +static HgfsSocketRecvBuffer recvBuffer; /* Accessed only by the recv thread. */ -static HgfsTcpRecvBuffer recvBuffer; /* Accessed only by the recv thread. */ static struct task_struct *recvThread; /* The recv thread. */ +static DECLARE_WAIT_QUEUE_HEAD(hgfsRecvThreadWait); /* Wait queue for recv thread. */ +static long hgfsRecvThreadFlags; /* Used to signal recv data availability. */ + +static void (*oldSocketDataReady)(struct sock *, int); + +/* + *---------------------------------------------------------------------- + * + * HgfsSocketDataReady -- + * + * Called when there is data to read on the connected socket. + * + * Results: + * None. + * + * Side effects: + * Wakes up the receiving thread. + * + *---------------------------------------------------------------------- + */ -HgfsTransportChannel tcpChannel; -HgfsTransportChannel vsocketChannel; -typedef Bool (*HgfsConnect)(HgfsTransportChannel *); +static void +HgfsSocketDataReady(struct sock *sk, // IN: Server socket + int len) // IN: Data length +{ + LOG(4, (KERN_DEBUG "VMware hgfs: %s: data ready\n", __func__)); + /* Call the original data_ready function. */ + oldSocketDataReady(sk, len); -/* Private functions. */ -static Bool HgfsTcpChannelOpen(HgfsTransportChannel *); -static Bool HgfsVSocketChannelOpen(HgfsTransportChannel *); -static void HgfsSocketChannelClose(HgfsTransportChannel *); -static int HgfsTcpChannelRecvAsync(HgfsTransportChannel *, - char **replyPacket, - size_t *packetSize); + /* Wake up the recv thread. */ + set_bit(HGFS_REQ_THREAD_RECV, &hgfsRecvThreadFlags); + wake_up_interruptible(&hgfsRecvThreadWait); +} /* *---------------------------------------------------------------------- * - * HgfsTcpReceiveHandler -- + * HgfsSocketResetRecvBuffer -- * - * Function run in background thread and wait on the data in the - * connected channel. + * Reset recv buffer. * * Results: - * Always returns zero. + * None * * Side effects: - * Can be many. + * None * *---------------------------------------------------------------------- */ -static int -HgfsTcpReceiveHandler(void *data) +static void +HgfsSocketResetRecvBuffer(void) { - char *receivedPacket = NULL; - size_t receivedSize = 0; - int ret = 0; - HgfsTransportChannel *channel = data; + recvBuffer.state = HGFS_CONN_RECV_SOCK_HDR; + recvBuffer.req = NULL; + recvBuffer.len = sizeof recvBuffer.header; + recvBuffer.buf = (char *)&recvBuffer.header; +} - LOG(6, (KERN_DEBUG "VMware hgfs: %s: thread started\n", __func__)); - compat_set_freezable(); - for (;;) { - /* Kill yourself if told so. */ - if (compat_kthread_should_stop()) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: told to exit\n", __func__)); - break; - } +/* + *---------------------------------------------------------------------- + * + * HgfsSocketIsReceiverIdle -- + * + * Checks whether we are in the middle of receiving a packet. + * + * Results: + * FALSE if receiving thread is in the middle of receiving packet, + * otherwise TRUE. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ - /* Check for suspend. */ - if (compat_try_to_freeze()) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: closing transport and exiting " - "the thread after resume.\n", __func__)); - channel->ops.close(channel); - break; - } +static Bool +HgfsSocketIsReceiverIdle(void) +{ + return recvBuffer.state == HGFS_CONN_RECV_SOCK_HDR && + recvBuffer.len == sizeof recvBuffer.header; +} - /* Waiting on the data, may be blocked. */ - ret = HgfsTcpChannelRecvAsync(channel, &receivedPacket, &receivedSize); - /* The connection is not lost for the following returns, just continue. */ - if (ret == -EINTR || ret == -ERESTARTSYS || ret == -EAGAIN) { - continue; - } +/* + *---------------------------------------------------------------------- + * + * HgfsSocketRecvMsg -- + * + * Receive the message on the socket. + * + * Results: + * On success returns number of bytes received. + * On failure returns the negative errno. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ - if (ret < 0) { - /* The connection is broken. Close it to free up the resources. */ - channel->ops.close(channel); - if (!channel->ops.open(channel)) { - /* We are unable to reconnect to the server, exit the thread. */ - LOG(6, (KERN_DEBUG "VMware hgfs: %s: recv error: %d. Lost the " - "connection to server.\n", __func__, ret)); - break; - } - } else if (ret > 0) { - /* Process the received packet. */ - HgfsTransportProcessPacket(receivedPacket, receivedSize); - } - } +static int +HgfsSocketRecvMsg(struct socket *socket, // IN: TCP socket + char *buffer, // IN: Buffer to recv the message + size_t bufferLen) // IN: Buffer length +{ + struct iovec iov; + struct msghdr msg; + int ret; + int flags = MSG_DONTWAIT | MSG_NOSIGNAL; + mm_segment_t oldfs = get_fs(); - HgfsTransportBeforeExitingRecvThread(); - LOG(6, (KERN_DEBUG "VMware hgfs: %s: thread exited\n", __func__)); - recvThread = NULL; - return 0; + memset(&msg, 0, sizeof msg); + msg.msg_flags = flags; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + iov.iov_base = buffer; + iov.iov_len = bufferLen; + + set_fs(KERNEL_DS); + ret = sock_recvmsg(socket, &msg, bufferLen, flags); + set_fs(oldfs); + + return ret; } /* *---------------------------------------------------------------------- * - * HgfsTcpStopReceivingThread -- + * HgfsSocketChannelRecvAsync -- * - * Helper function to stop channel handler thread. + * Receive as much data from the socket as possible without blocking. + * Note, that we may return early with just a part of the packet + * received. * * Results: - * None + * On failure returns the negative errno, otherwise 0. * * Side effects: - * None + * Changes state of the receive buffer depending on what part of + * the packet has been received so far. * *---------------------------------------------------------------------- */ -static void -HgfsTcpStopReceivingThread(void) +static int +HgfsSocketChannelRecvAsync(HgfsTransportChannel *channel) // IN: Channel { - if (recvThread && current != recvThread) { /* Don't stop myself. */ - /* Wake up socket by sending signal. */ - force_sig(SIGKILL, recvThread); - compat_kthread_stop(recvThread); - recvThread = NULL; - LOG(8, ("VMware hgfs: %s: recv thread stopped.\n", __func__)); + int ret; + + if (channel->status != HGFS_CHANNEL_CONNECTED) { + LOG(6, (KERN_DEBUG "VMware hgfs: %s: Connection lost.\n", __func__)); + return -ENOTCONN; } + + /* We want to read as much data as possible without blocking */ + for (;;) { + + LOG(10, (KERN_DEBUG "VMware hgfs: %s: receiving %s\n", __func__, + recvBuffer.state == HGFS_CONN_RECV_SOCK_HDR ? "header" : + recvBuffer.state == HGFS_CONN_RECV_REP_HDR ? "reply" : "data")); + ret = HgfsSocketRecvMsg(channel->priv, recvBuffer.buf, recvBuffer.len); + LOG(10, (KERN_DEBUG "VMware hgfs: %s: sock_recvmsg returns: %d\n", + __func__, ret)); + + if (ret <= 0) { + break; + } + + ASSERT(ret <= recvBuffer.len); + recvBuffer.len -= ret; + recvBuffer.buf += ret; + + if (recvBuffer.len != 0) { + continue; + } + + /* Complete segment received. */ + switch (recvBuffer.state) { + + case HGFS_CONN_RECV_SOCK_HDR: + LOG(10, (KERN_DEBUG "VMware hgfs: %s: received packet header\n", + __func__)); + ASSERT(recvBuffer.header.version == HGFS_SOCKET_VERSION1); + ASSERT(recvBuffer.header.size == sizeof recvBuffer.header); + ASSERT(recvBuffer.header.status == HGFS_SOCKET_STATUS_SUCCESS); + + recvBuffer.state = HGFS_CONN_RECV_REP_HDR; + recvBuffer.len = sizeof recvBuffer.reply; + recvBuffer.buf = (char *)&recvBuffer.reply; + break; + + case HGFS_CONN_RECV_REP_HDR: + LOG(10, (KERN_DEBUG "VMware hgfs: %s: received packet reply\n", + __func__)); + recvBuffer.req = HgfsTransportGetPendingRequest(recvBuffer.reply.id); + if (recvBuffer.req) { + ASSERT(recvBuffer.header.packetLen <= recvBuffer.req->bufferSize); + recvBuffer.req->payloadSize = recvBuffer.header.packetLen; + memcpy(recvBuffer.req->payload, &recvBuffer.reply, sizeof recvBuffer.reply); + recvBuffer.buf = recvBuffer.req->payload + sizeof recvBuffer.reply; + } else { + recvBuffer.buf = recvBuffer.sink; + } + + recvBuffer.state = HGFS_CONN_RECV_REP_PAYLOAD; + recvBuffer.len = recvBuffer.header.packetLen - sizeof recvBuffer.reply; + if (recvBuffer.len) + break; + + /* There is no actual payload, fall through */ + + case HGFS_CONN_RECV_REP_PAYLOAD: + LOG(10, (KERN_DEBUG "VMware hgfs: %s: received packet payload\n", + __func__)); + if (recvBuffer.req) { + HgfsCompleteReq(recvBuffer.req); + HgfsRequestPutRef(recvBuffer.req); + recvBuffer.req = NULL; + } + HgfsSocketResetRecvBuffer(); + break; + + default: + ASSERT(0); + } + } + + return ret; } /* *---------------------------------------------------------------------- * - * HgfsTcpResetRecvBuffer -- + * HgfsSocketReceiveHandler -- * - * Reset recv buffer. + * Function run in background thread and wait on the data in the + * connected channel. * * Results: - * None + * Always returns zero. * * Side effects: - * None + * Can be many. * *---------------------------------------------------------------------- */ -static void -HgfsTcpResetRecvBuffer(void) +static int +HgfsSocketReceiveHandler(void *data) { - recvBuffer.state = HGFS_TCP_CONN_RECV_HEADER; - recvBuffer.len = sizeof recvBuffer.header; - recvBuffer.buf = (char *)&recvBuffer.header; + HgfsTransportChannel *channel = data; + int ret; + + LOG(6, (KERN_DEBUG "VMware hgfs: %s: thread started\n", __func__)); + + compat_set_freezable(); + + for (;;) { + + /* Wait for data to become available */ + wait_event_interruptible(hgfsRecvThreadWait, + (HgfsSocketIsReceiverIdle() && compat_kthread_should_stop()) || + test_bit(HGFS_REQ_THREAD_RECV, &hgfsRecvThreadFlags)); + + /* Kill yourself if told so. */ + if (compat_kthread_should_stop()) { + LOG(6, (KERN_DEBUG "VMware hgfs: %s: told to exit\n", __func__)); + break; + } + + /* Check for suspend. */ + if (compat_try_to_freeze()) { + LOG(6, (KERN_DEBUG "VMware hgfs: %s: continuing after resume.\n", + __func__)); + continue; + } + + if (test_and_clear_bit(HGFS_REQ_THREAD_RECV, &hgfsRecvThreadFlags)) { + + /* There is some data witing for us, let's read it */ + ret = HgfsSocketChannelRecvAsync(channel); + + if (ret < 0 && ret != -EINTR && ret != -ERESTARTSYS && ret != -EAGAIN) { + + if (recvBuffer.req) { + HgfsFailReq(recvBuffer.req, -EIO); + HgfsRequestPutRef(recvBuffer.req); + recvBuffer.req = NULL; + } + + /* The connection is broken, leave it to the senders to restore it. */ + HgfsTransportMarkDead(); + } + } + } + + LOG(6, (KERN_DEBUG "VMware hgfs: %s: thread exited\n", __func__)); + recvThread = NULL; + + return 0; } /* *---------------------------------------------------------------------- * - * HgfsTcpConnect -- + * HgfsCreateTcpSocket -- * * Connect to HGFS TCP server. * * Results: - * TRUE on success, FALSE on failure. + * NULL on failure; otherwise address of the newly created and + * connected TCP socket. * * Side effects: * None @@ -260,55 +436,45 @@ HgfsTcpResetRecvBuffer(void) *---------------------------------------------------------------------- */ -static Bool -HgfsTcpConnect(HgfsTransportChannel *channel) +static struct socket * +HgfsCreateTcpSocket(void) { - int error; - struct socket *sock; + struct socket *socket; struct sockaddr_in addr; - Bool ret = FALSE; - - if (channel->priv != NULL) { - sock_release(channel->priv); - channel->priv = NULL; - } + int error; addr.sin_family = AF_INET; addr.sin_addr.s_addr = in_aton(HOST_IP); addr.sin_port = htons(HOST_PORT); - error = compat_sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock); - if (error >= 0) { - error = sock->ops->connect(sock, (struct sockaddr *)&addr, - sizeof addr, 0); - if (error >= 0) { - channel->priv = sock; - /* Reset receive buffer when a new connection is connected. */ - HgfsTcpResetRecvBuffer(); - sock->sk->compat_sk_rcvtimeo = HGFS_SOCKET_RECV_TIMEOUT * HZ; - channel->status = HGFS_CHANNEL_CONNECTED; - LOG(8, ("%s: tcp channel connected.\n", __func__)); - ret = TRUE; - } else { - LOG(8, ("%s: connect failed: %d.\n", __func__, error)); - sock_release(sock); - } - } else { + error = compat_sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &socket); + if (error < 0) { LOG(8, ("%s: sock_create_kern failed: %d.\n", __func__, error)); + return NULL; } - return ret; + + error = socket->ops->connect(socket, (struct sockaddr *)&addr, + sizeof addr, 0); + if (error < 0) { + LOG(8, ("%s: connect failed: %d.\n", __func__, error)); + sock_release(socket); + return NULL; + } + + return socket; } /* *---------------------------------------------------------------------- * - * HgfsVSocketConnect -- + * HgfsCreateVsockSocket -- * - * Connect to HGFS VSOCKET server. + * Connect to HGFS VSocket server. * * Results: - * TRUE on success, FALSE on failure. + * NULL on failure; otherwise address of the newly created and + * connected VSock socket. * * Side effects: * None @@ -316,46 +482,38 @@ HgfsTcpConnect(HgfsTransportChannel *channel) *---------------------------------------------------------------------- */ -static Bool -HgfsVSocketConnect(HgfsTransportChannel *channel) +static struct socket * +HgfsCreateVsockSocket(void) { #ifdef INCLUDE_VSOCKETS - int error; - struct socket *sock; + struct socket *socket; struct sockaddr_vm addr; - int family; - Bool ret = FALSE; + int family = VMCISock_GetAFValue(); + int error; memset(&addr, 0, sizeof addr); - - family = VMCISock_GetAFValue(); - addr.svm_family = family; addr.svm_cid = VMCI_HOST_CONTEXT_ID; addr.svm_port = HOST_VSOCKET_PORT; - error = compat_sock_create_kern(family, SOCK_STREAM, 0, &sock); - if (error >= 0) { - error = sock->ops->connect(sock, (struct sockaddr *)&addr, - sizeof addr, 0); - if (error >= 0) { - channel->priv = sock; - /* Reset receive buffer when a new connection is connected. */ - HgfsTcpResetRecvBuffer(); - sock->sk->compat_sk_rcvtimeo = HGFS_SOCKET_RECV_TIMEOUT * HZ; - channel->status = HGFS_CHANNEL_CONNECTED; - LOG(8, ("%s: vsocket channel connected.\n", __func__)); - ret = TRUE; - } else { - LOG(4, ("%s: connect failed: %d.\n", __func__, error)); - sock_release(sock); - } - } else { + error = compat_sock_create_kern(family, SOCK_STREAM, IPPROTO_TCP, &socket); + if (error < 0) { LOG(8, ("%s: sock_create_kern failed: %d.\n", __func__, error)); + return NULL; } - return ret; + + error = socket->ops->connect(socket, (struct sockaddr *)&addr, + sizeof addr, 0); + if (error < 0) { + LOG(8, ("%s: connect failed: %d.\n", __func__, error)); + sock_release(socket); + return NULL; + } + + return socket; + #else - return FALSE; + return NULL; #endif } @@ -378,48 +536,45 @@ HgfsVSocketConnect(HgfsTransportChannel *channel) static Bool HgfsSocketChannelOpen(HgfsTransportChannel *channel, - HgfsConnect connect) + struct socket *(*create_socket)(void)) { - Bool ret = FALSE; - - compat_mutex_lock(&channel->connLock); - switch (channel->status) { - case HGFS_CHANNEL_UNINITIALIZED: - ret = FALSE; - break; - case HGFS_CHANNEL_CONNECTED: - ret = TRUE; - break; - case HGFS_CHANNEL_NOTCONNECTED: - ret = connect(channel); - - /* - * Ensure this function won't race with HgfsTcpChannelRecvAsync, which - * is called only by recvThread. If this thread is not the recv thread, - * make sure the recv thread is finished before we proceed. - */ - ASSERT(!recvThread || current == recvThread); - - if (ret && !recvThread) { - /* Create the recv thread. */ - recvThread = compat_kthread_run(HgfsTcpReceiveHandler, - channel, "vmhgfs-rep"); - if (IS_ERR(recvThread)) { - LOG(4, (KERN_ERR "VMware hgfs: %s: failed to create recv thread\n", - __func__)); - recvThread = NULL; - channel->ops.close(channel); - ret = FALSE; - break; - } - } - break; - default: - ASSERT(0); /* Not reached. */ + struct socket *socket; + + ASSERT(channel->status == HGFS_CHANNEL_NOTCONNECTED); + ASSERT(!recvThread); + + socket = create_socket(); + if (socket == NULL) + return FALSE; + + /* + * Install the new "data ready" handler that will wake up the + * receiving thread. + */ + oldSocketDataReady = xchg(&socket->sk->compat_sk_data_ready, + HgfsSocketDataReady); + + /* Reset receive buffer when a new connection is connected. */ + HgfsSocketResetRecvBuffer(); + + channel->priv = socket; + + LOG(8, ("%s: socket channel connected.\n", __func__)); + + /* Create the recv thread. */ + recvThread = compat_kthread_run(HgfsSocketReceiveHandler, + channel, "vmhgfs-rep"); + if (IS_ERR(recvThread)) { + LOG(4, (KERN_ERR "VMware hgfs: %s: " + "failed to create recv thread, err %ld\n", + __func__, PTR_ERR(recvThread))); + recvThread = NULL; + sock_release(channel->priv); + channel->priv = NULL; + return FALSE; } - compat_mutex_unlock(&channel->connLock); - return ret; + return TRUE; } @@ -442,7 +597,7 @@ HgfsSocketChannelOpen(HgfsTransportChannel *channel, static Bool HgfsTcpChannelOpen(HgfsTransportChannel *channel) { - return HgfsSocketChannelOpen(channel, HgfsTcpConnect); + return HgfsSocketChannelOpen(channel, HgfsCreateTcpSocket); } @@ -465,38 +620,14 @@ HgfsTcpChannelOpen(HgfsTransportChannel *channel) static Bool HgfsVSocketChannelOpen(HgfsTransportChannel *channel) { - return HgfsSocketChannelOpen(channel, HgfsVSocketConnect); -} - - -/* - *---------------------------------------------------------------------- - * - * HgfsSocketChannelCloseWork -- - * - * Close the tcp channel in an idempotent way. - * - * Results: - * None - * - * Side effects: - * None - * - *---------------------------------------------------------------------- - */ + VMCISock_KernelRegister(); -static void -HgfsTcpChannelCloseWork(HgfsTransportChannel *channel) -{ - if (channel->status != HGFS_CHANNEL_CONNECTED) { - return; + if (!HgfsSocketChannelOpen(channel, HgfsCreateVsockSocket)) { + VMCISock_KernelDeregister(); + return FALSE; } - if (channel->priv != NULL) { - sock_release(channel->priv); - channel->priv = NULL; - } - channel->status = HGFS_CHANNEL_NOTCONNECTED; + return TRUE; } @@ -505,7 +636,8 @@ HgfsTcpChannelCloseWork(HgfsTransportChannel *channel) * * HgfsSocketChannelClose -- * - * See above. + * Closes socket-based channel by closing socket and stopping the + * receiving thread. * * Results: * None @@ -520,23 +652,25 @@ static void HgfsSocketChannelClose(HgfsTransportChannel *channel) { /* Stop the recv thread before we change the channel status. */ - HgfsTcpStopReceivingThread(); - compat_mutex_lock(&channel->connLock); - HgfsTcpChannelCloseWork(channel); - compat_mutex_unlock(&channel->connLock); - LOG(8, ("VMware hgfs: %s: tcp channel closed.\n", __func__)); + ASSERT(recvThread != NULL); + compat_kthread_stop(recvThread); + + sock_release(channel->priv); + channel->priv = NULL; + + LOG(8, ("VMware hgfs: %s: socket channel closed.\n", __func__)); } + /* *---------------------------------------------------------------------- * - * HgfsTcpRecvMsg -- + * HgfsTcpChannelClose -- * - * Receive the message on the socket. + * Closes TCP channel. * * Results: - * On success returns number of bytes received. - * On failure returns the negative errno. + * None * * Side effects: * None @@ -544,44 +678,24 @@ HgfsSocketChannelClose(HgfsTransportChannel *channel) *---------------------------------------------------------------------- */ -static int -HgfsTcpRecvMsg(struct socket *socket, // IN: TCP socket - char *buffer, // IN: Buffer to recv the message - size_t bufferLen) // IN: Buffer length +static void +HgfsTcpChannelClose(HgfsTransportChannel *channel) { - struct iovec iov; - struct msghdr msg; - int ret; - int flags = 0; - mm_segment_t oldfs = get_fs(); + HgfsSocketChannelClose(channel); - memset(&msg, 0, sizeof msg); - msg.msg_flags = flags; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - iov.iov_base = buffer; - iov.iov_len = bufferLen; - - set_fs(KERNEL_DS); - ret = sock_recvmsg(socket, &msg, bufferLen, flags); - set_fs(oldfs); - - return ret; + LOG(8, ("VMware hgfs: %s: tcp channel closed.\n", __func__)); } /* *---------------------------------------------------------------------- * - * HgfsTcpChannelRecvAsync -- + * HgfsVSocketChannelClose -- * - * Receive the packet. Note, the recv may timeout and return just - * a part of the packet according to our setting of the socket. + * See above. * * Results: - * On complete packet received, returns its size. - * On part of the packet received, returns 0. - * On failure returns the negative errno. + * None * * Side effects: * None @@ -589,66 +703,20 @@ HgfsTcpRecvMsg(struct socket *socket, // IN: TCP socket *---------------------------------------------------------------------- */ -static int -HgfsTcpChannelRecvAsync(HgfsTransportChannel *channel, // IN: Channel - char **replyPacket, // OUT: Reply buffer - size_t *packetSize) // OUT: Packet size +static void +HgfsVSocketChannelClose(HgfsTransportChannel *channel) { - int ret = -EIO; - - ASSERT(replyPacket); - ASSERT(packetSize); - - if (channel->status != HGFS_CHANNEL_CONNECTED) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: Connection lost.\n", __func__)); - return -ENOTCONN; - } - - LOG(10, (KERN_DEBUG "VMware hgfs: %s: receiving %s\n", __func__, - recvBuffer.state == 0? "header" : "data")); - ret = HgfsTcpRecvMsg(channel->priv, recvBuffer.buf, recvBuffer.len); - LOG(10, (KERN_DEBUG "VMware hgfs: %s: sock_recvmsg returns: %d\n", - __func__, ret)); - if (ret > 0) { - ASSERT(ret <= recvBuffer.len); - recvBuffer.len -= ret; - if ( recvBuffer.len == 0) { - /* Complete header or reply packet received. */ - switch(recvBuffer.state) { - case HGFS_TCP_CONN_RECV_HEADER: - ASSERT(recvBuffer.header.version == HGFS_SOCKET_VERSION1); - ASSERT(recvBuffer.header.size == sizeof recvBuffer.header); - ASSERT(recvBuffer.header.status == HGFS_SOCKET_STATUS_SUCCESS); - *packetSize = 0; - recvBuffer.state = HGFS_TCP_CONN_RECV_DATA; - recvBuffer.len = recvBuffer.header.packetLen; - recvBuffer.buf = &recvBuffer.data[0]; - ret = 0; /* To indicate the reply packet is not fully received. */ - break; - case HGFS_TCP_CONN_RECV_DATA: - *packetSize = recvBuffer.header.packetLen; - *replyPacket = &recvBuffer.data[0]; - HgfsTcpResetRecvBuffer(); - break; - default: - ASSERT(0); - } - } else if (recvBuffer.len > 0) { /* No complete packet received. */ - recvBuffer.buf += ret; - ret = 0; - } - }else if (ret == 0) { /* The connection is actually broken. */ - ret = -ENOTCONN; - } + HgfsSocketChannelClose(channel); + VMCISock_KernelDeregister(); - return ret; + LOG(8, ("VMware hgfs: %s: VSock channel closed.\n", __func__)); } /* *---------------------------------------------------------------------- * - * HgfsTcpSendMsg -- + * HgfsSocketSendMsg -- * * Send the message via the socket. Add the header before sending. * @@ -663,72 +731,54 @@ HgfsTcpChannelRecvAsync(HgfsTransportChannel *channel, // IN: Channel */ static int -HgfsTcpSendMsg(struct socket *socket, // IN: socket - char *buffer, // IN: Buffer to send - size_t bufferLen) // IN: Buffer length +HgfsSocketSendMsg(struct socket *socket, // IN: socket + char *buffer, // IN: Buffer to send + size_t bufferLen) // IN: Buffer length { - HgfsSocketHeader sockHeader; - struct iovec iov[2]; + struct iovec iov; struct msghdr msg; - int totalLen; int ret = 0; int i = 0; mm_segment_t oldfs = get_fs(); - HgfsSocketHeaderInit(&sockHeader, HGFS_SOCKET_VERSION1, sizeof sockHeader, - HGFS_SOCKET_STATUS_SUCCESS, bufferLen, 0); - memset(&msg, 0, sizeof msg); - iov[0].iov_base = &sockHeader; - iov[0].iov_len = sizeof sockHeader; - iov[1].iov_base = buffer; - iov[1].iov_len = bufferLen; - msg.msg_iov = &iov[0]; - msg.msg_iovlen = 2; - totalLen = sizeof sockHeader + bufferLen; - - while (totalLen > 0) { + iov.iov_base = buffer; + iov.iov_len = bufferLen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + while (bufferLen > 0) { set_fs(KERNEL_DS); - ret = sock_sendmsg(socket, &msg, totalLen); + ret = sock_sendmsg(socket, &msg, bufferLen); set_fs(oldfs); - LOG(6, ("VMware hgfs: %s: sock_sendmsg returns %d.\n", __func__, ret)); + LOG(6, (KERN_DEBUG "VMware hgfs: %s: sock_sendmsg returns %d.\n", + __func__, ret)); - if (ret == totalLen) { /* Common case. */ + if (likely(ret == bufferLen)) { /* Common case. */ break; - } + } else if (ret < 0) { + if (ret == -ENOSPC || ret == -EAGAIN) { + if (++i <= 12) { + LOG(6, (KERN_DEBUG "VMware hgfs: %s: " + "Sleep for %d milliseconds before retry.\n", + __func__, (1 << i))); + compat_msleep(1 << i); + continue; + } - if (ret == -ENOSPC || ret == -EAGAIN) { - i++; - if (i > 12) { LOG(2, ("VMware hgfs: %s: send stuck for 8 seconds.\n", __func__)); ret = -EIO; - break; } - LOG(6, ("VMware hgfs: %s: Sleep for %d milliseconds before retry.\n", - __func__, (1 << i))); - compat_msleep(1 << i); - continue; - } else if (ret < 0) { break; - } else if (ret > totalLen) { + } else if (ret >= bufferLen) { LOG(2, ("VMware hgfs: %s: sent more than expected bytes. Sent: %d, " - "expected: %d\n", __func__, ret, totalLen)); + "expected: %d\n", __func__, ret, (int)bufferLen)); break; - } - - i = 0; - totalLen -= ret; - if (ret < iov[0].iov_len) { - /* Only part of the header has been sent out. */ - iov[0].iov_base += ret; - iov[0].iov_len -= ret; } else { - /* The header and part of the body have been sent out. */ - int temp = ret - iov[0].iov_len; - iov[1].iov_base += temp; - iov[1].iov_len -= temp; - msg.msg_iov = &iov[1]; - msg.msg_iovlen = 1; + i = 0; + bufferLen -= ret; + iov.iov_base += ret; + iov.iov_len -= ret; } } @@ -743,9 +793,9 @@ HgfsTcpSendMsg(struct socket *socket, // IN: socket /* *---------------------------------------------------------------------- * - * HgfsTcpChannelSendAsync -- + * HgfsSocketChannelSend -- * - * Send the request via TCP channel. + * Send the request via a socket channel. * * Results: * 0 on success, negative error on failure. @@ -757,31 +807,23 @@ HgfsTcpSendMsg(struct socket *socket, // IN: socket */ static int -HgfsTcpChannelSendAsync(HgfsTransportChannel *channel, // IN: Channel - HgfsReq *req) // IN: request to send +HgfsSocketChannelSend(HgfsTransportChannel *channel, // IN: Channel + HgfsReq *req) // IN: request to send { - struct socket *socket; int result; ASSERT(req); - compat_mutex_lock(&channel->connLock); - if (channel->status != HGFS_CHANNEL_CONNECTED) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: Connection lost\n", __func__)); - compat_mutex_unlock(&channel->connLock); - return -ENOTCONN; - } - - req->state = HGFS_REQ_STATE_SUBMITTED; - socket = channel->priv; - - result = HgfsTcpSendMsg(socket, HGFS_REQ_PAYLOAD(req), req->payloadSize); - compat_mutex_unlock(&channel->connLock); + HgfsSocketHeaderInit((HgfsSocketHeader *)req->buffer, HGFS_SOCKET_VERSION1, + sizeof(HgfsSocketHeader), HGFS_SOCKET_STATUS_SUCCESS, + req->payloadSize, 0); + req->state = HGFS_REQ_STATE_SUBMITTED; + result = HgfsSocketSendMsg((struct socket *)channel->priv, req->buffer, + sizeof(HgfsSocketHeader) + req->payloadSize); if (result < 0) { LOG(4, (KERN_DEBUG "VMware hgfs: %s: sendmsg, err: %d.\n", __func__, result)); - channel->ops.close(channel); req->state = HGFS_REQ_STATE_UNSENT; } @@ -792,12 +834,13 @@ HgfsTcpChannelSendAsync(HgfsTransportChannel *channel, // IN: Channel /* *---------------------------------------------------------------------- * - * HgfsSocketChannelExit -- + * HgfsSocketChannelAllocate -- * - * Tear down the channel. + * Allocates memory for HgfsReq, its payload plus additional memory + * needed for the socket transport itself. * * Results: - * None + * NULL on failure otherwise address of allocated memory. * * Side effects: * None @@ -805,42 +848,21 @@ HgfsTcpChannelSendAsync(HgfsTransportChannel *channel, // IN: Channel *---------------------------------------------------------------------- */ -static void -HgfsSocketChannelExit(HgfsTransportChannel* channel) // IN OUT +static HgfsReq * +HgfsSocketChannelAllocate(size_t payloadSize) // IN: size of the payload { - HgfsTcpStopReceivingThread(); - compat_mutex_lock(&channel->connLock); - if (channel->status != HGFS_CHANNEL_UNINITIALIZED) { - HgfsTcpChannelCloseWork(channel); - channel->status = HGFS_CHANNEL_UNINITIALIZED; - LOG(8, ("VMware hgfs: %s: tcp channel exited.\n", __func__)); + HgfsReq *req; + + req = kmalloc(sizeof(*req) + sizeof(HgfsSocketHeader) + payloadSize, + GFP_KERNEL); + if (likely(req)) { + req->payload = req->buffer + sizeof(HgfsSocketHeader); } - compat_mutex_unlock(&channel->connLock); + + return req; } -/* - *---------------------------------------------------------------------- - * - * HgfsVSocketChannelExit -- - * - * Tear down the channel. - * - * Results: - * None - * - * Side effects: - * None - * - *---------------------------------------------------------------------- - */ - -static void -HgfsVSocketChannelExit(HgfsTransportChannel* channel) // IN OUT -{ - HgfsSocketChannelExit(channel); - VMCISock_KernelDeregister(); -} /* *---------------------------------------------------------------------- * @@ -857,28 +879,24 @@ HgfsVSocketChannelExit(HgfsTransportChannel* channel) // IN OUT *---------------------------------------------------------------------- */ -HgfsTransportChannel* +HgfsTransportChannel * HgfsGetTcpChannel(void) { - tcpChannel.name = "tcp"; - tcpChannel.ops.open = HgfsTcpChannelOpen; - tcpChannel.ops.close = HgfsSocketChannelClose; - tcpChannel.ops.send = HgfsTcpChannelSendAsync; - tcpChannel.ops.recv = HgfsTcpChannelRecvAsync; - tcpChannel.ops.exit = HgfsSocketChannelExit; - tcpChannel.priv = NULL; - compat_mutex_init(&tcpChannel.connLock); - - memset(&recvBuffer, 0, sizeof recvBuffer); - HgfsTcpResetRecvBuffer(); - recvThread = NULL; - tcpChannel.status = HGFS_CHANNEL_NOTCONNECTED; + static HgfsTransportChannel channel; + + channel.name = "tcp"; + channel.ops.open = HgfsTcpChannelOpen; + channel.ops.close = HgfsTcpChannelClose; + channel.ops.allocate = HgfsSocketChannelAllocate; + channel.ops.send = HgfsSocketChannelSend; + channel.priv = NULL; + channel.status = HGFS_CHANNEL_NOTCONNECTED; - if (!HOST_IP) {/* HOST_IP is defined in module.c. */ + if (!HOST_IP) { return NULL; } - return &tcpChannel; + return &channel; } @@ -898,28 +916,21 @@ HgfsGetTcpChannel(void) *---------------------------------------------------------------------- */ -HgfsTransportChannel* +HgfsTransportChannel * HgfsGetVSocketChannel(void) { - vsocketChannel.name = "vsocket"; - vsocketChannel.ops.open = HgfsVSocketChannelOpen; - vsocketChannel.ops.close = HgfsSocketChannelClose; - vsocketChannel.ops.send = HgfsTcpChannelSendAsync; - vsocketChannel.ops.recv = HgfsTcpChannelRecvAsync; - vsocketChannel.ops.exit = HgfsVSocketChannelExit; - vsocketChannel.priv = NULL; - compat_mutex_init(&vsocketChannel.connLock); - - /* Initialize hgfsConn. */ - memset(&recvBuffer, 0, sizeof recvBuffer); - HgfsTcpResetRecvBuffer(); - recvThread = NULL; - vsocketChannel.status = HGFS_CHANNEL_NOTCONNECTED; + static HgfsTransportChannel channel; - if (!HOST_VSOCKET_PORT) {/* HOST_VSOCKET_PORT is defined in module.c. */ + channel.name = "vsocket"; + channel.ops.open = HgfsVSocketChannelOpen; + channel.ops.close = HgfsVSocketChannelClose; + channel.ops.send = HgfsSocketChannelSend; + channel.priv = NULL; + channel.status = HGFS_CHANNEL_NOTCONNECTED; + + if (!HOST_VSOCKET_PORT) { return NULL; } - VMCISock_KernelRegister(); - return &vsocketChannel; + return &channel; } diff --git a/open-vm-tools/modules/linux/vmhgfs/transport.c b/open-vm-tools/modules/linux/vmhgfs/transport.c index e5defb86c..747e92c7d 100644 --- a/open-vm-tools/modules/linux/vmhgfs/transport.c +++ b/open-vm-tools/modules/linux/vmhgfs/transport.c @@ -60,22 +60,15 @@ static compat_mutex_t hgfsChannelLock; /* Lock to protect hgfsChannel. */ static struct list_head hgfsRepPending; /* Reply pending queue. */ static spinlock_t hgfsRepQueueLock; /* Reply pending queue lock. */ -#define HgfsRequestId(req) ((HgfsRequest *)req)->id - - -/* - * Private function implementations. - */ - /* *---------------------------------------------------------------------- * - * HgfsTransportSetupNewChannel -- + * HgfsTransportOpenChannel -- * - * Find a new workable channel. + * Opens given communication channel with HGFS server. * * Results: - * TRUE on success, otherwise FALSE. + * TRUE on success, FALSE on failure. * * Side effects: * None @@ -84,40 +77,43 @@ static spinlock_t hgfsRepQueueLock; /* Reply pending queue lock. */ */ static Bool -HgfsTransportSetupNewChannel(void) +HgfsTransportOpenChannel(HgfsTransportChannel *channel) { - hgfsChannel = HgfsGetVSocketChannel(); - if (hgfsChannel != NULL) { - if (hgfsChannel->ops.open(hgfsChannel)) { - return TRUE; + Bool ret; + + switch (channel->status) { + case HGFS_CHANNEL_UNINITIALIZED: + case HGFS_CHANNEL_DEAD: + ret = FALSE; + break; + + case HGFS_CHANNEL_CONNECTED: + ret = TRUE; + break; + + case HGFS_CHANNEL_NOTCONNECTED: + ret = channel->ops.open(channel); + if (ret) { + channel->status = HGFS_CHANNEL_CONNECTED; } - } + break; - hgfsChannel = HgfsGetTcpChannel(); - if (hgfsChannel != NULL) { - if (hgfsChannel->ops.open(hgfsChannel)) { - return TRUE; - } - } - - hgfsChannel = HgfsGetBdChannel(); - if (hgfsChannel != NULL) { - if (hgfsChannel->ops.open(hgfsChannel)) { - return TRUE; - } + default: + ret = FALSE; + ASSERT(0); /* Not reached. */ } - hgfsChannel = NULL; - return FALSE; + return ret; } /* *---------------------------------------------------------------------- * - * HgfsTransportStopCurrentChannel -- + * HgfsTransportCloseChannel -- * - * Teardown current channel and stop current receive thread. + * Closes currently open communication channel. Has to be called + * while holdingChannelLock. * * Results: * None @@ -129,11 +125,13 @@ HgfsTransportSetupNewChannel(void) */ static void -HgfsTransportStopCurrentChannel(void) +HgfsTransportCloseChannel(HgfsTransportChannel *channel) { - if (hgfsChannel) { - hgfsChannel->ops.exit(hgfsChannel); - hgfsChannel = NULL; + if (channel->status == HGFS_CHANNEL_CONNECTED || + channel->status == HGFS_CHANNEL_DEAD) { + + channel->ops.close(channel); + channel->status = HGFS_CHANNEL_NOTCONNECTED; } } @@ -141,28 +139,44 @@ HgfsTransportStopCurrentChannel(void) /* *---------------------------------------------------------------------- * - * HgfsTransportChannelFailover -- + * HgfsTransportSetupNewChannel -- * - * Called when current channel doesn't work. Find a new channel - * for transport. + * Find a new workable channel. * * Results: - * TRUE on success, otherwise FALSE; + * TRUE on success, otherwise FALSE. * * Side effects: - * Teardown current opened channel and the receive thread, set up - * new channel and new receive thread. + * None * *---------------------------------------------------------------------- */ static Bool -HgfsTransportChannelFailover(void) { - Bool ret = FALSE; - HgfsTransportStopCurrentChannel(); - ret = HgfsTransportSetupNewChannel(); - LOG(8, ("VMware hgfs: %s result: %s.\n", __func__, ret ? "TRUE" : "FALSE")); - return ret; +HgfsTransportSetupNewChannel(void) +{ + HgfsTransportChannel *newChannel; + + newChannel = HgfsGetVSocketChannel(); + if (newChannel != NULL) { + if (HgfsTransportOpenChannel(newChannel)) { + hgfsChannel = newChannel; + return TRUE; + } + } + + newChannel = HgfsGetTcpChannel(); + if (newChannel != NULL) { + if (HgfsTransportOpenChannel(newChannel)) { + hgfsChannel = newChannel; + return TRUE; + } + } + + newChannel = HgfsGetBdChannel(); + ASSERT(newChannel); + hgfsChannel = newChannel; + return HgfsTransportOpenChannel(newChannel); } @@ -215,24 +229,18 @@ HgfsTransportRemovePendingRequest(HgfsReq *req) // IN: Request to dequeue ASSERT(req); spin_lock(&hgfsRepQueueLock); - if (!list_empty(&req->list)) { - list_del_init(&req->list); - } + list_del_init(&req->list); spin_unlock(&hgfsRepQueueLock); } -/* - * Public function implementations. - */ - /* *---------------------------------------------------------------------- * - * HgfsTransportProcessPacket -- + * HgfsTransportFlushPendingRequests -- * - * Helper function to process received packets, called by the channel - * handler thread. + * Complete all submitted requests with an error, called when + * we are about to tear down communication channel. * * Results: * None @@ -243,55 +251,72 @@ HgfsTransportRemovePendingRequest(HgfsReq *req) // IN: Request to dequeue *---------------------------------------------------------------------- */ -void -HgfsTransportProcessPacket(char *receivedPacket, //IN: received packet - size_t receivedSize) //IN: packet size +static void +HgfsTransportFlushPendingRequests(void) { - struct list_head *cur, *next; - HgfsHandle id; - Bool found = FALSE; + struct HgfsReq *req; + + spin_lock(&hgfsRepQueueLock); - /* Got the reply. */ + list_for_each_entry(req, &hgfsRepPending, list) { + if (req->state == HGFS_REQ_STATE_SUBMITTED) { + LOG(6, ("VMware hgfs: %s: injecting error reply to req id: %d\n", + __func__, req->id)); + HgfsFailReq(req, -EIO); + } + } + + spin_unlock(&hgfsRepQueueLock); +} + +/* + *---------------------------------------------------------------------- + * + * HgfsTransportGetPendingRequest -- + * + * Attempts to locate request with specified ID in the queue of + * pending (waiting for server's reply) requests. + * + * Results: + * NULL if request not found; otherwise address of the request + * structure. + * + * Side effects: + * Increments reference count of the request. + * + *---------------------------------------------------------------------- + */ + +HgfsReq * +HgfsTransportGetPendingRequest(HgfsHandle id) // IN: id of the request +{ + HgfsReq *cur, *req = NULL; - ASSERT(receivedPacket != NULL && receivedSize > 0); - id = HgfsRequestId(receivedPacket); - LOG(8, ("VMware hgfs: %s entered.\n", __func__)); - LOG(6, (KERN_DEBUG "VMware hgfs: %s: req id: %d\n", __func__, id)); - /* - * Search through hgfsRepPending queue for the matching id and wake up - * the associated waiting process. Delete the req from the queue. - */ spin_lock(&hgfsRepQueueLock); - list_for_each_safe(cur, next, &hgfsRepPending) { - HgfsReq *req; - req = list_entry(cur, HgfsReq, list); - if (req->id == id) { - ASSERT(req->state == HGFS_REQ_STATE_SUBMITTED); - HgfsCompleteReq(req, receivedPacket, receivedSize); - found = TRUE; + + list_for_each_entry(cur, &hgfsRepPending, list) { + if (cur->id == id) { + ASSERT(cur->state == HGFS_REQ_STATE_SUBMITTED); + req = HgfsRequestGetRef(cur); break; } } + spin_unlock(&hgfsRepQueueLock); - if (!found) { - LOG(4, ("VMware hgfs: %s: No matching id, dropping reply\n", - __func__)); - } - LOG(8, ("VMware hgfs: %s exited.\n", __func__)); + return req; } /* *---------------------------------------------------------------------- * - * HgfsTransportBeforeExitingRecvThread -- + * HgfsTransportAllocateRequest -- * - * The cleanup work to do before the recv thread exits, including - * completing pending requests with error. + * Allocates HGFS request structre using channel-specific allocator. * * Results: - * None + * NULL on failure; otherwisepointer to newly allocated request. * * Side effects: * None @@ -299,25 +324,25 @@ HgfsTransportProcessPacket(char *receivedPacket, //IN: received packet *---------------------------------------------------------------------- */ -void -HgfsTransportBeforeExitingRecvThread(void) +HgfsReq * +HgfsTransportAllocateRequest(size_t bufferSize) // IN: size of the buffer { - struct list_head *cur, *next; + HgfsReq *req = NULL; + /* + * We use a temporary variable to make sure we stamp the request with + * same channel as we used to make allocation since hgfsChannel can + * be changed while we do allocation. + */ + HgfsTransportChannel *currentChannel = hgfsChannel; - /* Walk through hgfsRepPending queue and reply them with error. */ - spin_lock(&hgfsRepQueueLock); - list_for_each_safe(cur, next, &hgfsRepPending) { - HgfsReq *req; - HgfsReply reply; - - /* XXX: Make the request senders be aware of this error. */ - reply.status = -EIO; - req = list_entry(cur, HgfsReq, list); - LOG(6, ("VMware hgfs: %s: injecting error reply to req id: %d\n", - __func__, req->id)); - HgfsCompleteReq(req, (char *)&reply, sizeof reply); + ASSERT(currentChannel); + + req = currentChannel->ops.allocate(bufferSize); + if (req) { + req->transportId = currentChannel; } - spin_unlock(&hgfsRepQueueLock); + + return req; } @@ -340,36 +365,67 @@ HgfsTransportBeforeExitingRecvThread(void) int HgfsTransportSendRequest(HgfsReq *req) // IN: Request to send { - int ret; + HgfsReq *origReq = req; + int ret = -EIO; + ASSERT(req); ASSERT(req->state == HGFS_REQ_STATE_UNSENT); ASSERT(req->payloadSize <= HGFS_PACKET_MAX); compat_mutex_lock(&hgfsChannelLock); - /* Try opening the channel. */ - if (!hgfsChannel && !HgfsTransportSetupNewChannel()) { - compat_mutex_unlock(&hgfsChannelLock); - return -EPROTO; - } + HgfsTransportAddPendingRequest(req); - ASSERT(hgfsChannel->ops.send); + do { - HgfsTransportAddPendingRequest(req); + if (unlikely(hgfsChannel->status != HGFS_CHANNEL_CONNECTED)) { + if (hgfsChannel->status == HGFS_CHANNEL_DEAD) { + HgfsTransportCloseChannel(hgfsChannel); + HgfsTransportFlushPendingRequests(); + } + + if (!HgfsTransportSetupNewChannel()) { + ret = -EIO; + goto out; + } + } + + ASSERT(hgfsChannel->ops.send); + + /* If channel changed since we created request we need to adjust */ + if (req->transportId != hgfsChannel) { + + HgfsTransportRemovePendingRequest(req); - while ((ret = hgfsChannel->ops.send(hgfsChannel, req)) != 0) { - LOG(4, (KERN_DEBUG "VMware hgfs: %s: send failed. Return %d\n", + if (req != origReq) { + HgfsRequestPutRef(req); + } + + req = HgfsCopyRequest(origReq); + if (req == NULL) { + req = origReq; + ret = -ENOMEM; + goto out; + } + + HgfsTransportAddPendingRequest(req); + } + + ret = hgfsChannel->ops.send(hgfsChannel, req); + if (likely(ret == 0)) + break; + + LOG(4, (KERN_DEBUG "VMware hgfs: %s: send failed with error %d\n", __func__, ret)); + if (ret == -EINTR) { /* Don't retry when we are interrupted by some signal. */ goto out; } - if (!hgfsChannel->ops.open(hgfsChannel) && !HgfsTransportChannelFailover()) { - /* Can't establish a working channel, just report error. */ - ret = -EIO; - goto out; - } - } + + hgfsChannel->status = HGFS_CHANNEL_DEAD; + + } while (1); ASSERT(req->state == HGFS_REQ_STATE_COMPLETED || req->state == HGFS_REQ_STATE_SUBMITTED); @@ -377,15 +433,25 @@ HgfsTransportSendRequest(HgfsReq *req) // IN: Request to send out: compat_mutex_unlock(&hgfsChannelLock); - if (ret == 0) { /* Send succeeded. */ + if (likely(ret == 0)) { + /* Send succeeded, wait for the reply */ if (wait_event_interruptible(req->queue, req->state == HGFS_REQ_STATE_COMPLETED)) { ret = -EINTR; /* Interrupted by some signal. */ } - } /* else send failed. */ + } + + HgfsTransportRemovePendingRequest(req); - if (ret < 0) { - HgfsTransportRemovePendingRequest(req); + /* + * If we used a copy of request because we changed transport we + * need to copy payload back into original request. + */ + if (req != origReq) { + ASSERT(req->payloadSize <= origReq->bufferSize); + origReq->payloadSize = req->payloadSize; + memcpy(origReq->payload, req->payload, req->payloadSize); + HgfsRequestPutRef(req); } return ret; @@ -418,7 +484,45 @@ HgfsTransportInit(void) spin_lock_init(&hgfsRepQueueLock); compat_mutex_init(&hgfsChannelLock); - hgfsChannel = NULL; + compat_mutex_lock(&hgfsChannelLock); + + hgfsChannel = HgfsGetBdChannel(); + ASSERT(hgfsChannel); + + compat_mutex_unlock(&hgfsChannelLock); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsTransportMarkDead -- + * + * Marks current channel as dead so it can be cleaned up and + * fails all submitted requests. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +void +HgfsTransportMarkDead(void) +{ + LOG(8, ("VMware hgfs: %s entered.\n", __func__)); + + compat_mutex_lock(&hgfsChannelLock); + + if (hgfsChannel) { + hgfsChannel->status = HGFS_CHANNEL_DEAD; + } + HgfsTransportFlushPendingRequests(); + + compat_mutex_unlock(&hgfsChannelLock); } @@ -442,10 +546,15 @@ void HgfsTransportExit(void) { LOG(8, ("VMware hgfs: %s entered.\n", __func__)); + compat_mutex_lock(&hgfsChannelLock); - HgfsTransportStopCurrentChannel(); + ASSERT(hgfsChannel); + HgfsTransportCloseChannel(hgfsChannel); + hgfsChannel = NULL; compat_mutex_unlock(&hgfsChannelLock); ASSERT(list_empty(&hgfsRepPending)); LOG(8, ("VMware hgfs: %s exited.\n", __func__)); } + + diff --git a/open-vm-tools/modules/linux/vmhgfs/transport.h b/open-vm-tools/modules/linux/vmhgfs/transport.h index a32584d26..478b59d11 100644 --- a/open-vm-tools/modules/linux/vmhgfs/transport.h +++ b/open-vm-tools/modules/linux/vmhgfs/transport.h @@ -25,6 +25,7 @@ #include "request.h" #include "compat_mutex.h" +#include "hgfsProto.h" /* * There are the operations a channel should implement. @@ -33,15 +34,15 @@ struct HgfsTransportChannel; typedef struct HgfsTransportChannelOps { Bool (*open)(struct HgfsTransportChannel *); void (*close)(struct HgfsTransportChannel *); + HgfsReq* (*allocate)(size_t payloadSize); int (*send)(struct HgfsTransportChannel *, HgfsReq *); - int (*recv)(struct HgfsTransportChannel *, char **, size_t *); - void (*exit)(struct HgfsTransportChannel *); } HgfsTransportChannelOps; typedef enum { HGFS_CHANNEL_UNINITIALIZED, HGFS_CHANNEL_NOTCONNECTED, HGFS_CHANNEL_CONNECTED, + HGFS_CHANNEL_DEAD, /* Error has been detected, need to shut it down. */ } HgfsChannelStatus; typedef struct HgfsTransportChannel { @@ -55,9 +56,11 @@ typedef struct HgfsTransportChannel { /* Public functions (with respect to the entire module). */ void HgfsTransportInit(void); void HgfsTransportExit(void); +HgfsReq *HgfsTransportAllocateRequest(size_t payloadSize); int HgfsTransportSendRequest(HgfsReq *req); -void HgfsTransportProcessPacket(char *receivedPacket, - size_t receivedSize); -void HgfsTransportBeforeExitingRecvThread(void); +HgfsReq *HgfsTransportGetPendingRequest(HgfsHandle id); +void HgfsTransportFinishRequest(HgfsReq *req, Bool success, Bool do_put); +void HgfsTransportFlushRequests(void); +void HgfsTransportMarkDead(void); #endif // _HGFS_DRIVER_TRANSPORT_H_