From c1a0f4254812d3588b3716204190a521e8f87db8 Mon Sep 17 00:00:00 2001 From: "Scott M. Kroll" Date: Mon, 14 Jul 2014 12:42:06 -0400 Subject: [PATCH 5/5] Update hgfs file operations for newer kernels * Keep track of write back pages so concurrent file validations do not invalidate the cache. * Handle file flush operations. --- open-vm-tools/modules/linux/vmhgfs/file.c | 210 +++++- open-vm-tools/modules/linux/vmhgfs/filesystem.c | 103 +-- open-vm-tools/modules/linux/vmhgfs/fsutil.c | 743 ++++++++++++++++---- open-vm-tools/modules/linux/vmhgfs/fsutil.h | 2 + open-vm-tools/modules/linux/vmhgfs/inode.c | 66 +- open-vm-tools/modules/linux/vmhgfs/link.c | 57 +- open-vm-tools/modules/linux/vmhgfs/module.h | 7 + open-vm-tools/modules/linux/vmhgfs/page.c | 862 ++++++++++++++++++++++-- 8 files changed, 1735 insertions(+), 315 deletions(-) diff --git a/open-vm-tools/modules/linux/vmhgfs/file.c b/open-vm-tools/modules/linux/vmhgfs/file.c index 3568f4a..825cebe 100644 --- a/modules/linux/vmhgfs/file.c +++ b/modules/linux/vmhgfs/file.c @@ -47,6 +47,20 @@ #include "vm_assert.h" #include "vm_basic_types.h" +/* + * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using + * the O_SYNC flag. We continue to use the existing numerical value + * for O_DSYNC semantics now, but using the correct symbolic name for it. + * This new value is used to request true Posix O_SYNC semantics. It is + * defined in this strange way to make sure applications compiled against + * new headers get at least O_DSYNC semantics on older kernels. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) +#define HGFS_FILECTL_SYNC(flags) ((flags) & O_DSYNC) +#else +#define HGFS_FILECTL_SYNC(flags) ((flags) & O_SYNC) +#endif + /* Private functions. */ static int HgfsPackOpenRequest(struct inode *inode, struct file *file, @@ -84,6 +98,15 @@ static ssize_t HgfsWrite(struct file *file, static loff_t HgfsSeek(struct file *file, loff_t offset, int origin); +static int HgfsFlush(struct file *file +#if !defined VMW_FLUSH_HAS_1_ARG + ,fl_owner_t id +#endif + ); + +#if !defined VMW_FSYNC_31 +static int HgfsDoFsync(struct inode *inode); +#endif static int HgfsFsync(struct file *file, #if defined VMW_FSYNC_OLD @@ -126,7 +149,10 @@ struct file_operations HgfsFileFileOperations = { .owner = THIS_MODULE, .open = HgfsOpen, .llseek = HgfsSeek, + .flush = HgfsFlush, #if defined VMW_USE_AIO + .read = do_sync_read, + .write = do_sync_write, .aio_read = HgfsAioRead, .aio_write = HgfsAioWrite, #else @@ -797,22 +823,63 @@ HgfsAioWrite(struct kiocb *iocb, // IN: I/O control block loff_t offset) // IN: Offset at which to read { int result; + struct dentry *writeDentry; + HgfsInodeInfo *iinfo; ASSERT(iocb); ASSERT(iocb->ki_filp); ASSERT(iocb->ki_filp->f_dentry); ASSERT(iov); - LOG(6, (KERN_DEBUG "VMware hgfs: HgfsAioWrite: was called\n")); + writeDentry = iocb->ki_filp->f_dentry; + iinfo = INODE_GET_II_P(writeDentry->d_inode); - result = HgfsRevalidate(iocb->ki_filp->f_dentry); + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsAioWrite(%s/%s, %lu@%Ld)\n", + writeDentry->d_parent->d_name.name, writeDentry->d_name.name, + (unsigned long) iov_length(iov, numSegs), (long long) offset)); + + spin_lock(&writeDentry->d_inode->i_lock); + /* + * Guard against dentry revalidation invalidating the inode underneath us. + * + * Data is being written and may have valid data in a page in the cache. + * This action prevents any invalidating of the inode when a flushing of + * cache data occurs prior to syncing the file with the server's attributes. + * The flushing of cache data would empty our in memory write pages list and + * would cause the inode modified write time to be updated and so the inode + * would also be invalidated. + */ + iinfo->numWbPages++; + spin_unlock(&writeDentry->d_inode->i_lock); + + result = HgfsRevalidate(writeDentry); if (result) { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsAioWrite: invalid dentry\n")); goto out; } result = generic_file_aio_write(iocb, iov, numSegs, offset); - out: + + if (result >= 0) { + if (IS_SYNC(writeDentry->d_inode) || + HGFS_FILECTL_SYNC(iocb->ki_filp->f_flags)) { + int error; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + error = vfs_fsync(iocb->ki_filp, 0); +#else + error = HgfsDoFsync(writeDentry->d_inode); +#endif + + if (error < 0) { + result = error; + } + } + } + +out: + spin_lock(&writeDentry->d_inode->i_lock); + iinfo->numWbPages--; + spin_unlock(&writeDentry->d_inode->i_lock); return result; } @@ -962,6 +1029,98 @@ HgfsSeek(struct file *file, // IN: File to seek } +#if !defined VMW_FSYNC_31 +/* + *---------------------------------------------------------------------- + * + * HgfsDoFsync -- + * + * Helper for HgfsFlush() and HgfsFsync(). + * + * The hgfs protocol doesn't support fsync explicityly yet. + * So for now, we flush all the pages to presumably honor the + * intent of an app calling fsync() which is to get the + * data onto persistent storage. As things stand now we're at + * the whim of the hgfs server code running on the host to fsync or + * not if and when it pleases. + * + * + * Results: + * Returns zero on success. Otherwise an error. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +HgfsDoFsync(struct inode *inode) // IN: File we operate on +{ + int ret; + + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsDoFsync(%"FMT64"u)\n", + INODE_GET_II_P(inode)->hostFileId)); + + ret = compat_filemap_write_and_wait(inode->i_mapping); + + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsDoFsync: returns %d\n", ret)); + + return ret; +} +#endif + + +/* + *---------------------------------------------------------------------- + * + * HgfsFlush -- + * + * Called when user process calls fflush() on an hgfs file. + * Flush all dirty pages and check for write errors. + * + * + * Results: + * Returns zero on success. (Currently always succeeds). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +HgfsFlush(struct file *file // IN: file to flush +#if !defined VMW_FLUSH_HAS_1_ARG + ,fl_owner_t id // IN: id not used +#endif + ) +{ + int ret = 0; + + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsFlush(%s/%s)\n", + file->f_dentry->d_parent->d_name.name, + file->f_dentry->d_name.name)); + + if ((file->f_mode & FMODE_WRITE) == 0) { + goto exit; + } + + + /* Flush writes to the server and return any errors */ + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsFlush: calling vfs_sync ... \n")); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + ret = vfs_fsync(file, 0); +#else + ret = HgfsDoFsync(file->f_dentry->d_inode); +#endif + +exit: + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsFlush: returns %d\n", ret)); + return ret; +} + + /* *---------------------------------------------------------------------- * @@ -969,21 +1128,13 @@ HgfsSeek(struct file *file, // IN: File to seek * * Called when user process calls fsync() on hgfs file. * - * The hgfs protocol doesn't support fsync yet, so for now, we punt - * and just return success. This is a little less sketchy than it - * might sound, because hgfs skips the buffer cache in the guest - * anyway (we always write to the host immediately). - * - * In the future we might want to try harder though, since - * presumably the intent of an app calling fsync() is to get the + * The hgfs protocol doesn't support fsync explicitly yet, + * so for now, we flush all the pages to presumably honor the + * intent of an app calling fsync() which is to get the * data onto persistent storage, and as things stand now we're at * the whim of the hgfs server code running on the host to fsync or * not if and when it pleases. * - * Note that do_fsync will call filemap_fdatawrite() before us and - * filemap_fdatawait() after us, so there's no need to do anything - * here w.r.t. writing out dirty pages. - * * Results: * Returns zero on success. (Currently always succeeds). * @@ -1003,9 +1154,36 @@ HgfsFsync(struct file *file, // IN: File we operate on #endif int datasync) // IN: fdatasync or fsync { - LOG(6, (KERN_DEBUG "VMware hgfs: HgfsFsync: was called\n")); + int ret = 0; + loff_t startRange; + loff_t endRange; + struct inode *inode; + +#if defined VMW_FSYNC_31 + startRange = start; + endRange = end; +#else + startRange = 0; + endRange = MAX_INT64; +#endif - return 0; + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsFsync(%s/%s, %lld, %lld, %d)\n", + file->f_dentry->d_parent->d_name.name, + file->f_dentry->d_name.name, + startRange, endRange, + datasync)); + + /* Flush writes to the server and return any errors */ + inode = file->f_dentry->d_inode; +#if defined VMW_FSYNC_31 + ret = filemap_write_and_wait_range(inode->i_mapping, startRange, endRange); +#else + ret = HgfsDoFsync(inode); +#endif + + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsFsync: written pages %lld, %lld returns %d)\n", + startRange, endRange, ret)); + return ret; } diff --git a/open-vm-tools/modules/linux/vmhgfs/filesystem.c b/open-vm-tools/modules/linux/vmhgfs/filesystem.c index c845b36..dc0adcd 100644 --- a/modules/linux/vmhgfs/filesystem.c +++ b/modules/linux/vmhgfs/filesystem.c @@ -83,7 +83,6 @@ HgfsOp hgfsVersionCreateSymlink; static inline unsigned long HgfsComputeBlockBits(unsigned long blockSize); static compat_kmem_cache_ctor HgfsInodeCacheCtor; static HgfsSuperInfo *HgfsInitSuperInfo(HgfsMountInfo *mountInfo); -static int HgfsGetRootDentry(struct super_block *sb, struct dentry **rootDentry); static int HgfsReadSuper(struct super_block *sb, void *rawData, int flags); @@ -335,103 +334,6 @@ HgfsInitSuperInfo(HgfsMountInfo *mountInfo) // IN: Passed down from the user /* - *---------------------------------------------------------------------------- - * - * HgfsGetRootDentry -- - * - * Gets the root dentry for a given super block. - * - * Results: - * zero and a valid root dentry on success - * negative value on failure - * - * Side effects: - * None. - * - *---------------------------------------------------------------------------- - */ - -static int -HgfsGetRootDentry(struct super_block *sb, // IN: Super block object - struct dentry **rootDentry) // OUT: Root dentry -{ - int result = -ENOMEM; - struct inode *rootInode; - struct dentry *tempRootDentry = NULL; - struct HgfsAttrInfo rootDentryAttr; - HgfsInodeInfo *iinfo; - - ASSERT(sb); - ASSERT(rootDentry); - - LOG(6, (KERN_DEBUG "VMware hgfs: %s: entered\n", __func__)); - - rootInode = HgfsGetInode(sb, HGFS_ROOT_INO); - if (rootInode == NULL) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: Could not get the root inode\n", - __func__)); - goto exit; - } - - /* - * On an allocation failure in read_super, the inode will have been - * marked "bad". If it was, we certainly don't want to start playing with - * the HgfsInodeInfo. So quietly put the inode back and fail. - */ - if (is_bad_inode(rootInode)) { - LOG(6, (KERN_DEBUG "VMware hgfs: %s: encountered bad inode\n", - __func__)); - goto exit; - } - - tempRootDentry = d_make_root(rootInode); - /* - * d_make_root() does iput() on failure; if d_make_root() completes - * successfully then subsequent dput() will do iput() for us, so we - * should just ignore root inode from now on. - */ - rootInode = NULL; - - if (tempRootDentry == NULL) { - LOG(4, (KERN_WARNING "VMware hgfs: %s: Could not get " - "root dentry\n", __func__)); - goto exit; - } - - result = HgfsPrivateGetattr(tempRootDentry, &rootDentryAttr, NULL); - if (result) { - LOG(4, (KERN_WARNING "VMware hgfs: HgfsReadSuper: Could not" - "instantiate the root dentry\n")); - goto exit; - } - - iinfo = INODE_GET_II_P(tempRootDentry->d_inode); - iinfo->isFakeInodeNumber = FALSE; - iinfo->isReferencedInode = TRUE; - - if (rootDentryAttr.mask & HGFS_ATTR_VALID_FILEID) { - iinfo->hostFileId = rootDentryAttr.hostFileId; - } - - HgfsChangeFileAttributes(tempRootDentry->d_inode, &rootDentryAttr); - HgfsDentryAgeReset(tempRootDentry); - tempRootDentry->d_op = &HgfsDentryOperations; - - *rootDentry = tempRootDentry; - result = 0; - - LOG(6, (KERN_DEBUG "VMware hgfs: %s: finished\n", __func__)); -exit: - if (result) { - iput(rootInode); - dput(tempRootDentry); - *rootDentry = NULL; - } - return result; -} - - -/* *----------------------------------------------------------------------------- * * HgfsReadSuper -- @@ -511,7 +413,10 @@ HgfsReadSuper(struct super_block *sb, // OUT: Superblock object sb->s_blocksize_bits = HgfsComputeBlockBits(HGFS_BLOCKSIZE); sb->s_blocksize = 1 << sb->s_blocksize_bits; - result = HgfsGetRootDentry(sb, &rootDentry); + /* + * Create the root dentry and its corresponding inode. + */ + result = HgfsInstantiateRoot(sb, &rootDentry); if (result) { LOG(4, (KERN_WARNING "VMware hgfs: HgfsReadSuper: Could not instantiate " "root dentry\n")); diff --git a/open-vm-tools/modules/linux/vmhgfs/fsutil.c b/open-vm-tools/modules/linux/vmhgfs/fsutil.c index 1028cc9..72f81f1 100644 --- a/modules/linux/vmhgfs/fsutil.c +++ b/modules/linux/vmhgfs/fsutil.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2006 VMware, Inc. All rights reserved. + * Copyright (C) 2006-2014 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -53,10 +53,13 @@ static int HgfsUnpackGetattrReply(HgfsReq *req, HgfsAttrInfo *attr, char **fileName); static int HgfsPackGetattrRequest(HgfsReq *req, - struct dentry *dentry, + HgfsOp opUsed, Bool allowHandleReuse, - HgfsOp opUsed, + struct dentry *dentry, HgfsAttrInfo *attr); +static int HgfsBuildRootPath(char *buffer, + size_t bufferLen, + HgfsSuperInfo *si); /* * Private function implementations. @@ -234,13 +237,17 @@ HgfsUnpackGetattrReply(HgfsReq *req, // IN: Reply packet /* *---------------------------------------------------------------------- * - * HgfsPackGetattrRequest -- + * HgfsPackCommonattr -- * - * Setup the getattr request, depending on the op version. When possible, - * we will issue the getattr using an existing open HGFS handle. + * This function abstracts the HgfsAttr struct behind HgfsAttrInfo. + * Callers can pass one of four replies into it and receive back the + * attributes for those replies. + * + * Callers must populate attr->requestType so that we know whether to + * expect a V1 or V2 Attr struct. * * Results: - * Returns zero on success, or negative error on failure. + * Zero on success, non-zero otherwise. * * Side effects: * None @@ -249,22 +256,18 @@ HgfsUnpackGetattrReply(HgfsReq *req, // IN: Reply packet */ static int -HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer - struct dentry *dentry, // IN: Dentry containing name - Bool allowHandleReuse, // IN: Can we use a handle? - HgfsOp opUsed, // IN: Op to be used - HgfsAttrInfo *attr) // OUT: Attrs to update +HgfsPackCommonattr(HgfsReq *req, // IN/OUT: request buffer + HgfsOp opUsed, // IN: Op to be used + Bool allowHandleReuse, // IN: Can we use a handle? + struct inode *fileInode, // IN: file inode + size_t *reqSize, // OUT: request size + size_t *reqBufferSize, // OUT: request buffer size + char **fileName, // OUT: pointer to request file name + uint32 **fileNameLength, // OUT: pointer to request file name length + HgfsAttrInfo *attr) // OUT: Attrs to update { - size_t reqBufferSize; - size_t reqSize; - int result = 0; HgfsHandle handle; - char *fileName = NULL; - uint32 *fileNameLength = NULL; - - ASSERT(attr); - ASSERT(dentry); - ASSERT(req); + int result = 0; attr->requestType = opUsed; @@ -287,24 +290,25 @@ HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer * by name. */ requestV3->hints = 0; - if (allowHandleReuse && HgfsGetHandle(dentry->d_inode, + if (allowHandleReuse && HgfsGetHandle(fileInode, 0, &handle) == 0) { requestV3->fileName.flags = HGFS_FILE_NAME_USE_FILE_DESC; requestV3->fileName.fid = handle; requestV3->fileName.length = 0; requestV3->fileName.caseType = HGFS_FILE_NAME_DEFAULT_CASE; - fileName = NULL; + *fileName = NULL; + *fileNameLength = NULL; } else { - fileName = requestV3->fileName.name; - fileNameLength = &requestV3->fileName.length; + *fileName = requestV3->fileName.name; + *fileNameLength = &requestV3->fileName.length; requestV3->fileName.flags = 0; requestV3->fileName.fid = HGFS_INVALID_HANDLE; requestV3->fileName.caseType = HGFS_FILE_NAME_CASE_SENSITIVE; } requestV3->reserved = 0; - reqSize = HGFS_REQ_PAYLOAD_SIZE_V3(requestV3); - reqBufferSize = HGFS_NAME_BUFFER_SIZET(req->bufferSize, reqSize); + *reqSize = HGFS_REQ_PAYLOAD_SIZE_V3(requestV3); + *reqBufferSize = HGFS_NAME_BUFFER_SIZET(req->bufferSize, *reqSize); break; } @@ -321,19 +325,20 @@ HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer * correct regardless. If we don't find a handle, fall back on getattr * by name. */ - if (allowHandleReuse && HgfsGetHandle(dentry->d_inode, + if (allowHandleReuse && HgfsGetHandle(fileInode, 0, &handle) == 0) { requestV2->hints = HGFS_ATTR_HINT_USE_FILE_DESC; requestV2->file = handle; - fileName = NULL; + *fileName = NULL; + *fileNameLength = NULL; } else { requestV2->hints = 0; - fileName = requestV2->fileName.name; - fileNameLength = &requestV2->fileName.length; + *fileName = requestV2->fileName.name; + *fileNameLength = &requestV2->fileName.length; } - reqSize = sizeof *requestV2; - reqBufferSize = HGFS_NAME_BUFFER_SIZE(req->bufferSize, requestV2); + *reqSize = sizeof *requestV2; + *reqBufferSize = HGFS_NAME_BUFFER_SIZE(req->bufferSize, requestV2); break; } @@ -344,10 +349,10 @@ HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer requestV1->header.op = opUsed; requestV1->header.id = req->id; - fileName = requestV1->fileName.name; - fileNameLength = &requestV1->fileName.length; - reqSize = sizeof *requestV1; - reqBufferSize = HGFS_NAME_BUFFER_SIZE(req->bufferSize, requestV1); + *fileName = requestV1->fileName.name; + *fileNameLength = &requestV1->fileName.length; + *reqSize = sizeof *requestV1; + *reqBufferSize = HGFS_NAME_BUFFER_SIZE(req->bufferSize, requestV1); break; } @@ -355,6 +360,57 @@ HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRequest: unexpected " "OP type encountered\n")); result = -EPROTO; + break; + } + + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsPackGetattrRequest -- + * + * Setup the getattr request, depending on the op version. When possible, + * we will issue the getattr using an existing open HGFS handle. + * + * Results: + * Returns zero on success, or negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer + HgfsOp opUsed, // IN: Op to be used + Bool allowHandleReuse, // IN: Can we use a handle? + struct dentry *dentry, // IN: Dentry containing name + HgfsAttrInfo *attr) // OUT: Attrs to update +{ + size_t reqBufferSize; + size_t reqSize; + char *fileName = NULL; + uint32 *fileNameLength = NULL; + int result = 0; + + ASSERT(attr); + ASSERT(dentry); + ASSERT(req); + + result = HgfsPackCommonattr(req, + opUsed, + allowHandleReuse, + dentry->d_inode, + &reqSize, + &reqBufferSize, + &fileName, + &fileNameLength, + attr); + if (0 > result) { goto out; } @@ -364,8 +420,90 @@ HgfsPackGetattrRequest(HgfsReq *req, // IN/OUT: Request buffer /* Build full name to send to server. */ if (HgfsBuildPath(fileName, reqBufferSize, dentry) < 0) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRequest: build path " - "failed\n")); + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRequest: build path failed\n")); + result = -EINVAL; + goto out; + } + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRequest: getting attrs for \"%s\"\n", + fileName)); + + /* Convert to CP name. */ + result = CPName_ConvertTo(fileName, + reqBufferSize, + fileName); + if (result < 0) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRequest: CP conversion failed\n")); + result = -EINVAL; + goto out; + } + + *fileNameLength = result; + } + + req->payloadSize = reqSize + result; + result = 0; + +out: + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsPackGetattrRootRequest -- + * + * Setup the getattr request for the root of the HGFS file system. + * + * When possible, we will issue the getattr using an existing open HGFS handle. + * + * Results: + * Returns zero on success, or negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +HgfsPackGetattrRootRequest(HgfsReq *req, // IN/OUT: Request buffer + HgfsOp opUsed, // IN: Op to be used + struct super_block *sb, // IN: Super block entry + HgfsAttrInfo *attr) // OUT: Attrs to update +{ + size_t reqBufferSize; + size_t reqSize; + char *fileName = NULL; + uint32 *fileNameLength = NULL; + int result = 0; + + ASSERT(attr); + ASSERT(sb); + ASSERT(req); + + result = HgfsPackCommonattr(req, + opUsed, + FALSE, + NULL, + &reqSize, + &reqBufferSize, + &fileName, + &fileNameLength, + attr); + if (0 > result) { + goto out; + } + + /* Avoid all this extra work when we're doing a getattr by handle. */ + if (fileName != NULL) { + HgfsSuperInfo *si = HGFS_SB_TO_COMMON(sb); + + /* Build full name to send to server. */ + if (HgfsBuildRootPath(fileName, + reqBufferSize, + si) < 0) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPackGetattrRootRequest: build path failed\n")); result = -EINVAL; goto out; } @@ -511,7 +649,8 @@ HgfsUnpackCommonAttr(HgfsReq *req, // IN: Reply packet attrInfo->groupId = attrV2->groupId; attrInfo->mask |= HGFS_ATTR_VALID_GROUPID; } - if (attrV2->mask & HGFS_ATTR_VALID_FILEID) { + if (attrV2->mask & (HGFS_ATTR_VALID_FILEID | + HGFS_ATTR_VALID_NON_STATIC_FILEID)) { attrInfo->hostFileId = attrV2->hostFileId; attrInfo->mask |= HGFS_ATTR_VALID_FILEID; } @@ -578,6 +717,18 @@ HgfsCalcBlockSize(uint64 tsize) } #endif + +static inline int +hgfs_timespec_compare(const struct timespec *lhs, const struct timespec *rhs) +{ + if (lhs->tv_sec < rhs->tv_sec) + return -1; + if (lhs->tv_sec > rhs->tv_sec) + return 1; + return lhs->tv_nsec - rhs->tv_nsec; +} + + /* *---------------------------------------------------------------------- * @@ -640,6 +791,74 @@ HgfsSetInodeUidGid(struct inode *inode, // IN/OUT: Inode } } +/* + *----------------------------------------------------------------------------- + * + * HgfsIsInodeWritable -- + * + * Helper function for verifying if a file is under write access. + * + * Results: + * TRUE if file is writable, FALSE otherwise. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static Bool +HgfsIsInodeWritable(struct inode *inode) // IN: File we're writing to +{ + HgfsInodeInfo *iinfo; + struct list_head *cur; + Bool isWritable = FALSE; + + iinfo = INODE_GET_II_P(inode); + /* + * Iterate over the open handles for this inode, and find if there + * is one that allows the write mode. + * Note, the mode is stored as incremented by one to prevent overload of + * the zero value. + */ + spin_lock(&hgfsBigLock); + list_for_each(cur, &iinfo->files) { + HgfsFileInfo *finfo = list_entry(cur, HgfsFileInfo, list); + + if (0 != (finfo->mode & (HGFS_OPEN_MODE_WRITE_ONLY + 1))) { + isWritable = TRUE; + break; + } + } + spin_unlock(&hgfsBigLock); + + return isWritable; +} + + +/* + *----------------------------------------------------------------------------- + * + * HgfsIsSafeToChange -- + * + * Helper function for verifying if a file inode size and time fields is safe + * to update. It is deemed safe only if there is not an open writer to the file. + * + * Results: + * TRUE if safe to change inode, FALSE otherwise. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static Bool +HgfsIsSafeToChange(struct inode *inode) // IN: File we're writing to +{ + return !HgfsIsInodeWritable(inode); +} + /* *---------------------------------------------------------------------- @@ -665,13 +884,34 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode HgfsAttrInfo const *attr) // IN: New attrs { HgfsSuperInfo *si; + HgfsInodeInfo *iinfo; Bool needInvalidate = FALSE; + Bool isSafeToChange; ASSERT(inode); ASSERT(inode->i_sb); ASSERT(attr); si = HGFS_SB_TO_COMMON(inode->i_sb); + iinfo = INODE_GET_II_P(inode); + + /* + * We do not want to update the file size from server or invalidate the inode + * for inodes open for write. We need to avoid races with the write page + * extending the file. This also will cause the server to possibly update the + * server side file's mod time too. For those situations we do not want to blindly + * go and invalidate the inode pages thus losing changes in flight and corrupting the + * file. + * We only need to invalidate the inode pages if the file has truly been modified + * on the server side by another server side application, not by our writes. + * If there are no writers it is safe to assume that newer mod time means the file + * changed on the server side underneath us. + */ + isSafeToChange = HgfsIsSafeToChange(inode); + + spin_lock(&inode->i_lock); + + iinfo = INODE_GET_II_P(inode); LOG(6, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: entered\n")); HgfsSetFileType(inode, attr); @@ -742,21 +982,23 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode /* * Invalidate cached pages if we didn't receive the file size, or if it has - * changed on the server. + * changed on the server, and no writes in flight. */ if (attr->mask & HGFS_ATTR_VALID_SIZE) { loff_t oldSize = compat_i_size_read(inode); inode->i_blocks = (attr->size + HGFS_BLOCKSIZE - 1) / HGFS_BLOCKSIZE; if (oldSize != attr->size) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: new file " - "size: %"FMT64"u, old file size: %Lu\n", attr->size, oldSize)); - needInvalidate = TRUE; + if (oldSize < attr->size || (iinfo->numWbPages == 0 && isSafeToChange)) { + needInvalidate = TRUE; + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: new file " + "size: %"FMT64"u, old file size: %Lu\n", attr->size, oldSize)); + inode->i_blocks = HgfsCalcBlockSize(attr->size); + compat_i_size_write(inode, attr->size); + } } - compat_i_size_write(inode, attr->size); } else { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: did not " "get file size\n")); - needInvalidate = TRUE; } if (attr->mask & HGFS_ATTR_VALID_ACCESS_TIME) { @@ -767,12 +1009,15 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode /* * Invalidate cached pages if we didn't receive the modification time, or if - * it has changed on the server. + * it has changed on the server and we don't have writes in flight and any open + * open writers. */ if (attr->mask & HGFS_ATTR_VALID_WRITE_TIME) { HGFS_DECLARE_TIME(newTime); HGFS_SET_TIME(newTime, attr->writeTime); - if (!HGFS_EQUAL_TIME(newTime, inode->i_mtime)) { + if (hgfs_timespec_compare(&newTime, &inode->i_mtime) > 0 && + iinfo->numWbPages == 0 && + isSafeToChange) { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: new mod " "time: %ld:%lu, old mod time: %ld:%lu\n", HGFS_PRINT_TIME(newTime), HGFS_PRINT_TIME(inode->i_mtime))); @@ -780,7 +1025,6 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode } HGFS_SET_TIME(inode->i_mtime, attr->writeTime); } else { - needInvalidate = TRUE; LOG(4, (KERN_DEBUG "VMware hgfs: HgfsChangeFileAttributes: did not " "get mod time\n")); HGFS_SET_TIME(inode->i_mtime, HGFS_GET_CURRENT_TIME()); @@ -798,6 +1042,8 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode HGFS_SET_TIME(inode->i_ctime, HGFS_GET_CURRENT_TIME()); } + spin_unlock(&inode->i_lock); + /* * Compare old size and write time with new size and write time. If there's * a difference (or if we didn't get a new size or write time), the file @@ -815,17 +1061,14 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode /* *---------------------------------------------------------------------- * - * HgfsPrivateGetattr -- + * HgfsCanRetryGetattrRequest -- * - * Internal getattr routine. Send a getattr request to the server - * for the indicated remote name, and if it succeeds copy the - * results of the getattr into the provided HgfsAttrInfo. - * - * fileName (if supplied) will be set to a newly allocated string - * if the file is a symlink; it's the caller's duty to free it. + * Checks the getattr request version and downgrades the global getattr + * version if we can. * * Results: - * Returns zero on success, or a negative error on failure. + * Returns TRUE on success and downgrades the global getattr protocol version, + * or FALSE if no retry is possible. * * Side effects: * None @@ -833,44 +1076,63 @@ HgfsChangeFileAttributes(struct inode *inode, // IN/OUT: Inode *---------------------------------------------------------------------- */ -int -HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name - HgfsAttrInfo *attr, // OUT: Attr to copy into - char **fileName) // OUT: pointer to allocated file name +static Bool +HgfsCanRetryGetattrRequest(HgfsOp getattrOp) // IN: getattrOp version used { - HgfsReq *req; - HgfsStatus replyStatus; - HgfsOp opUsed; - int result = 0; - Bool allowHandleReuse = TRUE; + Bool canRetry = FALSE; + + /* Retry with older version(s). Set globally. */ + if (getattrOp == HGFS_OP_GETATTR_V3) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsCanRetryGetattrRequest: Version 3 " + "not supported. Falling back to version 2.\n")); + hgfsVersionGetattr = HGFS_OP_GETATTR_V2; + canRetry = TRUE; + } else if (getattrOp == HGFS_OP_GETATTR_V2) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsCanRetryGetattrRequest: Version 2 " + "not supported. Falling back to version 1.\n")); + hgfsVersionGetattr = HGFS_OP_GETATTR; + canRetry = TRUE; + } + return canRetry; +} - ASSERT(dentry); - ASSERT(dentry->d_sb); - ASSERT(attr); - req = HgfsGetNewRequest(); - if (!req) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: out of memory " - "while getting new request\n")); - result = -ENOMEM; - goto out; - } +/* + *---------------------------------------------------------------------- + * + * HgfsSendGetattrRequest -- + * + * Send the getattr request and handle the reply. + * + * Results: + * Returns zero on success, or a negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ - retry: +int +HgfsSendGetattrRequest(HgfsReq *req, // IN: getattr request + Bool *doRetry, // OUT: Retry getattr request + Bool *allowHandleReuse, // IN/OUT: handle reuse + HgfsAttrInfo *attr, // OUT: Attr to copy into + char **fileName) // OUT: pointer to allocated file name +{ + int result; - opUsed = hgfsVersionGetattr; - result = HgfsPackGetattrRequest(req, dentry, allowHandleReuse, opUsed, attr); - if (result != 0) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: no attrs\n")); - goto out; - } + *doRetry = FALSE; result = HgfsSendRequest(req); if (result == 0) { - LOG(6, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: got reply\n")); - replyStatus = HgfsReplyStatus(req); + HgfsStatus replyStatus = HgfsReplyStatus(req); + result = HgfsStatusConvertToLinux(replyStatus); + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsSendGetattrRequest: reply status %d -> %d\n", + replyStatus, result)); + /* * If the getattr succeeded on the server, copy the stats * into the HgfsAttrInfo, otherwise return an error. @@ -889,7 +1151,7 @@ HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name * and it doesn't display any valid shares too. So as a workaround, we * remap EIO to success and create minimal fake attributes. */ - LOG(1, (KERN_DEBUG "Hgfs:Server returned EIO on unknown file\n")); + LOG(1, (KERN_DEBUG "Hgfs: HgfsSetInodeUidGid: Server returned EIO on unknown file\n")); /* Create fake attributes */ attr->mask = HGFS_ATTR_VALID_TYPE | HGFS_ATTR_VALID_SIZE; attr->type = HGFS_FILE_TYPE_DIRECTORY; @@ -906,9 +1168,9 @@ HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name * "goto retry" would cause an infinite loop. Instead, let's retry * with a getattr by name. */ - if (allowHandleReuse) { - allowHandleReuse = FALSE; - goto retry; + if (*allowHandleReuse) { + *allowHandleReuse = FALSE; + *doRetry = TRUE; } /* @@ -920,19 +1182,11 @@ HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name case -EPROTO: /* Retry with older version(s). Set globally. */ - if (attr->requestType == HGFS_OP_GETATTR_V3) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: Version 3 " - "not supported. Falling back to version 2.\n")); - hgfsVersionGetattr = HGFS_OP_GETATTR_V2; - goto retry; - } else if (attr->requestType == HGFS_OP_GETATTR_V2) { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: Version 2 " - "not supported. Falling back to version 1.\n")); - hgfsVersionGetattr = HGFS_OP_GETATTR; - goto retry; + if (HgfsCanRetryGetattrRequest(attr->requestType)) { + *doRetry = TRUE; } + break; - /* Fallthrough. */ default: break; } @@ -942,8 +1196,129 @@ HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: server " "returned error: %d\n", result)); } else { - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: unknown error: " - "%d\n", result)); + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsSendGetattrRequest: unknown error: %d\n", + result)); + } + + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsPrivateGetattrRoot -- + * + * The getattr for the root. Send a getattr request to the server + * for the indicated remote name, and if it succeeds copy the + * results of the getattr into the provided HgfsAttrInfo. + * + * fileName (of the root) will be set to a newly allocated string. + * + * Results: + * Returns zero on success, or a negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +int +HgfsPrivateGetattrRoot(struct super_block *sb, // IN: Super block object + HgfsAttrInfo *attr) // OUT: Attr to copy into +{ + HgfsReq *req; + HgfsOp opUsed; + int result = 0; + Bool doRetry; + Bool allowHandleReuse = FALSE; + + ASSERT(sb); + ASSERT(attr); + + req = HgfsGetNewRequest(); + if (!req) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattrRoot: out of memory " + "while getting new request\n")); + result = -ENOMEM; + goto out; + } + +retry: + opUsed = hgfsVersionGetattr; + result = HgfsPackGetattrRootRequest(req, opUsed, sb, attr); + if (result != 0) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattrRoot: no attrs\n")); + goto out; + } + + result = HgfsSendGetattrRequest(req, &doRetry, &allowHandleReuse, attr, NULL); + if (0 != result && doRetry) { + goto retry; + } + +out: + HgfsFreeRequest(req); + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsPrivateGetattr -- + * + * Internal getattr routine. Send a getattr request to the server + * for the indicated remote name, and if it succeeds copy the + * results of the getattr into the provided HgfsAttrInfo. + * + * fileName (if supplied) will be set to a newly allocated string + * if the file is a symlink; it's the caller's duty to free it. + * + * Results: + * Returns zero on success, or a negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +int +HgfsPrivateGetattr(struct dentry *dentry, // IN: Dentry containing name + HgfsAttrInfo *attr, // OUT: Attr to copy into + char **fileName) // OUT: pointer to allocated file name +{ + HgfsReq *req; + HgfsOp opUsed; + int result = 0; + Bool doRetry; + Bool allowHandleReuse = TRUE; + + ASSERT(dentry); + ASSERT(dentry->d_sb); + ASSERT(attr); + + req = HgfsGetNewRequest(); + if (!req) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: out of memory " + "while getting new request\n")); + result = -ENOMEM; + goto out; + } + +retry: + opUsed = hgfsVersionGetattr; + result = HgfsPackGetattrRequest(req, opUsed, allowHandleReuse, dentry, attr); + if (result != 0) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsPrivateGetattr: no attrs\n")); + goto out; + } + + result = HgfsSendGetattrRequest(req, &doRetry, &allowHandleReuse, attr, fileName); + if (0 != result && doRetry) { + goto retry; } out: @@ -1099,6 +1474,106 @@ HgfsIget(struct super_block *sb, // IN: Superblock of this fs /* *----------------------------------------------------------------------------- * + * HgfsInstantiateRoot -- + * + * Gets the root dentry for a given super block. + * + * Results: + * zero and a valid root dentry on success + * negative value on failure + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +int +HgfsInstantiateRoot(struct super_block *sb, // IN: Super block object + struct dentry **rootDentry) // OUT: Root dentry +{ + int result = -ENOMEM; + struct inode *rootInode; + struct dentry *tempRootDentry = NULL; + struct HgfsAttrInfo rootDentryAttr; + HgfsInodeInfo *iinfo; + + ASSERT(sb); + ASSERT(rootDentry); + + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsInstantiateRoot: entered\n")); + + rootInode = HgfsGetInode(sb, HGFS_ROOT_INO); + if (rootInode == NULL) { + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsInstantiateRoot: Could not get the root inode\n")); + goto exit; + } + + /* + * On an allocation failure in read_super, the inode will have been + * marked "bad". If it was, we certainly don't want to start playing with + * the HgfsInodeInfo. So quietly put the inode back and fail. + */ + if (is_bad_inode(rootInode)) { + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsInstantiateRoot: encountered bad inode\n")); + goto exit; + } + + LOG(8, (KERN_DEBUG "VMware hgfs: HgfsInstantiateRoot: retrieve root attrs\n")); + result = HgfsPrivateGetattrRoot(sb, &rootDentryAttr); + if (result) { + LOG(4, (KERN_WARNING "VMware hgfs: HgfsInstantiateRoot: Could not the root attrs\n")); + goto exit; + } + + iinfo = INODE_GET_II_P(rootInode); + iinfo->isFakeInodeNumber = FALSE; + iinfo->isReferencedInode = TRUE; + + if (rootDentryAttr.mask & HGFS_ATTR_VALID_FILEID) { + iinfo->hostFileId = rootDentryAttr.hostFileId; + } + + HgfsChangeFileAttributes(rootInode, &rootDentryAttr); + + /* + * Now the initialization of the inode is complete we can create + * the root dentry which has flags initialized from the inode itself. + */ + tempRootDentry = d_make_root(rootInode); + /* + * d_make_root() does iput() on failure; if d_make_root() completes + * successfully then subsequent dput() will do iput() for us, so we + * should just ignore root inode from now on. + */ + rootInode = NULL; + + if (tempRootDentry == NULL) { + LOG(4, (KERN_WARNING "VMware hgfs: HgfsInstantiateRoot: Could not get " + "root dentry\n")); + goto exit; + } + + HgfsDentryAgeReset(tempRootDentry); + tempRootDentry->d_op = &HgfsDentryOperations; + + *rootDentry = tempRootDentry; + result = 0; + + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsInstantiateRoot: finished\n")); +exit: + if (result) { + iput(rootInode); + dput(tempRootDentry); + *rootDentry = NULL; + } + return result; +} + + +/* + *----------------------------------------------------------------------------- + * * HgfsInstantiate -- * * Tie a dentry to a looked up or created inode. Callers may choose to @@ -1163,6 +1638,45 @@ HgfsInstantiate(struct dentry *dentry, // IN: Dentry to use /* *----------------------------------------------------------------------------- * + * HgfsBuildRootPath -- + * + * Constructs the root path given the super info. + * + * Results: + * If non-negative, the length of the buffer written. + * Otherwise, an error code. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +int +HgfsBuildRootPath(char *buffer, // IN/OUT: Buffer to write into + size_t bufferLen, // IN: Size of buffer + HgfsSuperInfo *si) // IN: First dentry to walk +{ + size_t shortestNameLength; + /* + * Buffer must hold at least the share name (which is already prefixed with + * a forward slash), and nul. + */ + shortestNameLength = si->shareNameLen + 1; + if (bufferLen < shortestNameLength) { + return -ENAMETOOLONG; + } + memcpy(buffer, si->shareName, shortestNameLength); + + /* Short-circuit if we're at the root already. */ + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsBuildRootPath: root path \"%s\"\n", buffer)); + return shortestNameLength; +} + + +/* + *----------------------------------------------------------------------------- + * * HgfsBuildPath -- * * Constructs the full path given a dentry by walking the dentry and its @@ -1184,7 +1698,7 @@ HgfsBuildPath(char *buffer, // IN/OUT: Buffer to write into size_t bufferLen, // IN: Size of buffer struct dentry *dentry) // IN: First dentry to walk { - int retval = 0; + int retval; size_t shortestNameLength; HgfsSuperInfo *si; @@ -1194,26 +1708,23 @@ HgfsBuildPath(char *buffer, // IN/OUT: Buffer to write into si = HGFS_SB_TO_COMMON(dentry->d_sb); - /* - * Buffer must hold at least the share name (which is already prefixed with - * a forward slash), and nul. - */ - shortestNameLength = si->shareNameLen + 1; - if (bufferLen < shortestNameLength) { - return -ENAMETOOLONG; + retval = HgfsBuildRootPath(buffer, bufferLen, si); + if (0 > retval) { + return retval; } - memcpy(buffer, si->shareName, shortestNameLength); /* Short-circuit if we're at the root already. */ if (IS_ROOT(dentry)) { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsBuildPath: Sending root \"%s\"\n", buffer)); - return shortestNameLength; + return retval; } /* Skip the share name, but overwrite our previous nul. */ + shortestNameLength = retval; buffer += shortestNameLength - 1; bufferLen -= shortestNameLength - 1; + retval = 0; /* * Build the path string walking the tree backward from end to ROOT @@ -1230,8 +1741,8 @@ HgfsBuildPath(char *buffer, // IN/OUT: Buffer to write into if (bufferLen < 0) { compat_unlock_dentry(dentry); dput(dentry); - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsBuildPath: Ran out of space " - "while writing dentry name\n")); + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsBuildPath: Ran out of space " + "while writing dentry name\n")); return -ENAMETOOLONG; } buffer[bufferLen] = '/'; @@ -1305,7 +1816,7 @@ HgfsDentryAgeReset(struct dentry *dentry) // IN: Dentry whose age to reset /* *----------------------------------------------------------------------------- * - * HgfsDentryAgeReset -- + * HgfsDentryAgeForce -- * * Set the dentry's time to 0. This makes the dentry's age "too old" and * forces subsequent HgfsRevalidates to go to the server for attributes. @@ -1808,5 +2319,7 @@ HgfsDoReadInode(struct inode *inode) // IN: Inode to initialize iinfo->isReferencedInode = FALSE; iinfo->isFakeInodeNumber = FALSE; iinfo->createdAndUnopened = FALSE; + iinfo->numWbPages = 0; + INIT_LIST_HEAD(&iinfo->listWbPages); } diff --git a/open-vm-tools/modules/linux/vmhgfs/fsutil.h b/open-vm-tools/modules/linux/vmhgfs/fsutil.h index 2767099..6cfc71a 100644 --- a/modules/linux/vmhgfs/fsutil.h +++ b/modules/linux/vmhgfs/fsutil.h @@ -74,6 +74,8 @@ int HgfsPrivateGetattr(struct dentry *dentry, struct inode *HgfsIget(struct super_block *sb, ino_t ino, HgfsAttrInfo const *attr); +int HgfsInstantiateRoot(struct super_block *sb, + struct dentry **rootDentry); int HgfsInstantiate(struct dentry *dentry, ino_t ino, HgfsAttrInfo const *attr); diff --git a/open-vm-tools/modules/linux/vmhgfs/inode.c b/open-vm-tools/modules/linux/vmhgfs/inode.c index caaa41a..93e28bf 100644 --- a/modules/linux/vmhgfs/inode.c +++ b/modules/linux/vmhgfs/inode.c @@ -159,6 +159,38 @@ struct inode_operations HgfsFileInodeOperations = { * Private functions implementations. */ + +/* + *---------------------------------------------------------------------- + * + * HgfsClearReadOnly -- + * + * Try to remove the file/dir read only attribute. + * + * Note when running on Windows servers the entry may have the read-only + * flag set and prevent a rename or delete operation from occuring. + * + * Results: + * Returns zero on success, or a negative error on failure. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +HgfsClearReadOnly(struct dentry *dentry) // IN: file/dir to remove read only +{ + struct iattr enableWrite; + + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsClearReadOnly: removing read-only\n")); + enableWrite.ia_mode = (dentry->d_inode->i_mode | S_IWUSR); + enableWrite.ia_valid = ATTR_MODE; + return HgfsSetattr(dentry, &enableWrite); +} + + /* *---------------------------------------------------------------------- * @@ -309,14 +341,8 @@ HgfsDelete(struct inode *dir, // IN: Parent dir of file/dir to delete * safe? */ if (!secondAttempt) { - struct iattr enableWrite; secondAttempt = TRUE; - - LOG(4, (KERN_DEBUG "VMware hgfs: HgfsDelete: access denied, " - "attempting to work around read-only bit\n")); - enableWrite.ia_mode = (dentry->d_inode->i_mode | S_IWUSR); - enableWrite.ia_valid = ATTR_MODE; - result = HgfsSetattr(dentry, &enableWrite); + result = HgfsClearReadOnly(dentry); if (result == 0) { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsDelete: file is no " "longer read-only, retrying delete\n")); @@ -1336,6 +1362,7 @@ HgfsRename(struct inode *oldDir, // IN: Inode of original directory HgfsReq *req = NULL; char *oldName; char *newName; + Bool secondAttempt=FALSE; uint32 *oldNameLength; uint32 *newNameLength; int result = 0; @@ -1500,6 +1527,31 @@ retry: "returned error: %d\n", result)); goto out; } + } else if ((-EACCES == result) || (-EPERM == result)) { + /* + * It's possible that we're talking to a Windows server with + * a file marked read-only. Let's try again, after removing + * the read-only bit from the file. + * + * XXX: I think old servers will send -EPERM here. Is this entirely + * safe? + */ + if (!secondAttempt) { + secondAttempt = TRUE; + result = HgfsClearReadOnly(newDentry); + if (result == 0) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsRename: file is no " + "longer read-only, retrying rename\n")); + goto retry; + } + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsRename: failed to remove " + "read-only property\n")); + } else { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsRename: second attempt at " + "rename failed\n")); + } + } else if (0 != result) { + LOG(4, (KERN_DEBUG "VMware hgfs: HgfsRename: failed with result %d\n", result)); } } else if (result == -EIO) { LOG(4, (KERN_DEBUG "VMware hgfs: HgfsRename: timed out\n")); diff --git a/open-vm-tools/modules/linux/vmhgfs/link.c b/open-vm-tools/modules/linux/vmhgfs/link.c index 06ea953..9140f4e 100644 --- a/modules/linux/vmhgfs/link.c +++ b/modules/linux/vmhgfs/link.c @@ -45,11 +45,20 @@ static int HgfsFollowlink(struct dentry *dentry, static int HgfsReadlink(struct dentry *dentry, char __user *buffer, int buflen); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) +static void HgfsPutlink(struct dentry *dentry, + struct nameidata *nd, + void *cookie); +#else +static void HgfsPutlink(struct dentry *dentry, + struct nameidata *nd); +#endif /* HGFS inode operations structure for symlinks. */ struct inode_operations HgfsLinkInodeOperations = { .follow_link = HgfsFollowlink, .readlink = HgfsReadlink, + .put_link = HgfsPutlink, }; /* @@ -109,6 +118,7 @@ HgfsFollowlink(struct dentry *dentry, // IN: Dentry containing link LOG(6, (KERN_DEBUG "VMware hgfs: HgfsFollowlink: got called " "on something that wasn't a symlink\n")); error = -EINVAL; + kfree(fileName); } else { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0) LOG(6, (KERN_DEBUG "VMware hgfs: HgfsFollowlink: calling " @@ -120,7 +130,6 @@ HgfsFollowlink(struct dentry *dentry, // IN: Dentry containing link error = vfs_follow_link(nd, fileName); #endif } - kfree(fileName); } out: @@ -181,9 +190,6 @@ HgfsReadlink(struct dentry *dentry, // IN: Dentry containing link #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) LOG(6, (KERN_DEBUG "VMware hgfs: HgfsReadlink: calling " "readlink_copy\n")); - LOG(6, (KERN_DEBUG "VMware hgfs: %s: calling " - "readlink_copy\n", - __func__)); error = readlink_copy(buffer, buflen, fileName); #else LOG(6, (KERN_DEBUG "VMware hgfs: HgfsReadlink: calling " @@ -195,3 +201,46 @@ HgfsReadlink(struct dentry *dentry, // IN: Dentry containing link } return error; } + + +/* + *---------------------------------------------------------------------- + * + * HgfsPutlink -- + * + * Modeled after page_put_link from a 2.6.9 kernel so it'll work + * across all kernel revisions we care about. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) +static void +HgfsPutlink(struct dentry *dentry, // dentry + struct nameidata *nd, // lookup name information + void *cookie) // cookie +#else +static void +HgfsPutlink(struct dentry *dentry, // dentry + struct nameidata *nd) // lookup name information +#endif +{ + char *fileName = NULL; + + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsPutlink: put for %s\n", + dentry->d_name.name)); + + fileName = nd_get_link(nd); + if (!IS_ERR(fileName)) { + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsPutlink: putting %s\n", + fileName)); + kfree(fileName); + nd_set_link(nd, NULL); + } +} diff --git a/open-vm-tools/modules/linux/vmhgfs/module.h b/open-vm-tools/modules/linux/vmhgfs/module.h index b6bcd1e..0c0a842 100644 --- a/modules/linux/vmhgfs/module.h +++ b/modules/linux/vmhgfs/module.h @@ -147,6 +147,13 @@ typedef struct HgfsInodeInfo { /* Is this a fake inode created in HgfsCreate that has yet to be opened? */ Bool createdAndUnopened; + /* + * The number of write back pages to the file which is tracked so any + * concurrent file validations such as reads will not invalidate the cache. + */ + unsigned long numWbPages; + struct list_head listWbPages; + /* Is this inode referenced by HGFS? (needed by HgfsInodeLookup()) */ Bool isReferencedInode; diff --git a/open-vm-tools/modules/linux/vmhgfs/page.c b/open-vm-tools/modules/linux/vmhgfs/page.c index 6d8b50f..cf3b8c9 100644 --- a/modules/linux/vmhgfs/page.c +++ b/modules/linux/vmhgfs/page.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2006 VMware, Inc. All rights reserved. + * Copyright (C) 2006-2014 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -64,15 +64,18 @@ static int HgfsDoWritepage(HgfsHandle handle, struct page *page, unsigned pageFrom, unsigned pageTo); -static void HgfsDoWriteBegin(struct page *page, - unsigned pageFrom, - unsigned pageTo); +static int HgfsDoWriteBegin(struct file *file, + struct page *page, + unsigned pageFrom, + unsigned pageTo); static int HgfsDoWriteEnd(struct file *file, struct page *page, unsigned pageFrom, unsigned pageTo, loff_t writeTo, unsigned copied); +static void HgfsDoExtendFile(struct inode *inode, + loff_t writeTo); /* HGFS address space operations. */ static int HgfsReadpage(struct file *file, @@ -128,6 +131,27 @@ struct address_space_operations HgfsAddressSpaceOperations = { .set_page_dirty = __set_page_dirty_nobuffers, }; +enum { + PG_BUSY = 0, +}; + +typedef struct HgfsWbPage { + struct list_head wb_list; /* Defines state of page: */ + struct page *wb_page; /* page to read in/write out */ + pgoff_t wb_index; /* Offset >> PAGE_CACHE_SHIFT */ + struct kref wb_kref; /* reference count */ + unsigned long wb_flags; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 13) + wait_queue_head_t wb_queue; +#endif +} HgfsWbPage; + +static void HgfsInodePageWbAdd(struct inode *inode, + struct page *page); +static void HgfsInodePageWbRemove(struct inode *inode, + struct page *page); +static void HgfsWbRequestDestroy(HgfsWbPage *req); + /* * Private functions. @@ -690,11 +714,11 @@ HgfsDoWritepage(HgfsHandle handle, // IN: Handle to use for writing pageFrom += result; /* Update the inode's size now rather than waiting for a revalidate. */ - if (curOffset > compat_i_size_read(inode)) { - compat_i_size_write(inode, curOffset); - } + HgfsDoExtendFile(inode, curOffset); } while ((result > 0) && (remainingCount > 0)); + HgfsInodePageWbRemove(inode, page); + result = 0; out: @@ -866,7 +890,7 @@ HgfsWritepage(struct page *page, // IN: Page to write from * Initialize the page if the file is to be appended. * * Results: - * None. + * Zero on success, always. * * Side effects: * None. @@ -874,37 +898,35 @@ HgfsWritepage(struct page *page, // IN: Page to write from *----------------------------------------------------------------------------- */ -static void -HgfsDoWriteBegin(struct page *page, // IN: Page to be written +static int +HgfsDoWriteBegin(struct file *file, // IN: File to be written + struct page *page, // IN: Page to be written unsigned pageFrom, // IN: Starting page offset unsigned pageTo) // IN: Ending page offset { - loff_t offset; - loff_t currentFileSize; - ASSERT(page); - offset = (loff_t)page->index << PAGE_CACHE_SHIFT; - currentFileSize = compat_i_size_read(page->mapping->host); - /* - * If we are doing a partial write into a new page (beyond end of - * file), then intialize it. This allows other writes to this page - * to accumulate before we need to write it to the server. - */ - if ((offset >= currentFileSize) || - ((pageFrom == 0) && (offset + pageTo) >= currentFileSize)) { - void *kaddr = compat_kmap_atomic(page); - - if (pageFrom) { + if (!PageUptodate(page)) { + /* + * If we are doing a partial write into a new page (beyond end of + * file), then intialize it. This allows other writes to this page + * to accumulate before we need to write it to the server. + */ + if (pageTo - pageFrom != PAGE_CACHE_SIZE) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + zero_user_segments(page, 0, pageFrom, pageTo, PAGE_CACHE_SIZE); +#else + void *kaddr = compat_kmap_atomic(page); memset(kaddr, 0, pageFrom); - } - if (pageTo < PAGE_CACHE_SIZE) { memset(kaddr + pageTo, 0, PAGE_CACHE_SIZE - pageTo); + flush_dcache_page(page); + compat_kunmap_atomic(kaddr); +#endif } - compat_kunmap_atomic(kaddr); - flush_dcache_page(page); } + + return 0; } @@ -919,7 +941,7 @@ HgfsDoWriteBegin(struct page *page, // IN: Page to be written * receiving the write. * * Results: - * Always zero. + * On success zero, always. * * Side effects: * None. @@ -928,14 +950,12 @@ HgfsDoWriteBegin(struct page *page, // IN: Page to be written */ static int -HgfsPrepareWrite(struct file *file, // IN: Ignored +HgfsPrepareWrite(struct file *file, // IN: File to be written struct page *page, // IN: Page to prepare unsigned pageFrom, // IN: Beginning page offset unsigned pageTo) // IN: Ending page offset { - HgfsDoWriteBegin(page, pageFrom, pageTo); - - return 0; + return HgfsDoWriteBegin(file, page, pageFrom, pageTo); } #else @@ -971,18 +991,29 @@ HgfsWriteBegin(struct file *file, // IN: File to be written void **clientData) // OUT: Opaque to pass to write_end, unused { pgoff_t index = pos >> PAGE_CACHE_SHIFT; - unsigned pageFrom = pos & (PAGE_CACHE_SHIFT - 1); - unsigned pageTo = pos + len; + unsigned pageFrom = pos & (PAGE_CACHE_SIZE - 1); + unsigned pageTo = pageFrom + len; struct page *page; + int result; page = compat_grab_cache_page_write_begin(mapping, index, flags); if (page == NULL) { - return -ENOMEM; + result = -ENOMEM; + goto exit; } *pagePtr = page; - HgfsDoWriteBegin(page, pageFrom, pageTo); - return 0; + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsWriteBegin: file size %Lu @ %Lu page %u to %u\n", + (loff_t)compat_i_size_read(page->mapping->host), + (loff_t)page->index << PAGE_CACHE_SHIFT, + pageFrom, pageTo)); + + result = HgfsDoWriteBegin(file, page, pageFrom, pageTo); + ASSERT(result == 0); + +exit: + LOG(6, (KERN_DEBUG "VMware hgfs: HgfsWriteBegin: return %d\n", result)); + return result; } #endif @@ -990,6 +1021,40 @@ HgfsWriteBegin(struct file *file, // IN: File to be written /* *----------------------------------------------------------------------------- * + * HgfsDoExtendFile -- + * + * Helper function for extending a file size. + * + * This function updates the inode->i_size, under the inode lock. + * + * Results: + * None. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static void +HgfsDoExtendFile(struct inode *inode, // IN: File we're writing to + loff_t writeTo) // IN: Offset we're written to +{ + loff_t currentFileSize; + + spin_lock(&inode->i_lock); + currentFileSize = compat_i_size_read(inode); + + if (writeTo > currentFileSize) { + compat_i_size_write(inode, writeTo); + } + spin_unlock(&inode->i_lock); +} + + +/* + *----------------------------------------------------------------------------- + * * HgfsDoWriteEnd -- * * Helper function for HgfsWriteEnd. @@ -1014,54 +1079,31 @@ HgfsDoWriteEnd(struct file *file, // IN: File we're writing to loff_t writeTo, // IN: File position to write to unsigned copied) // IN: Number of bytes copied to the page { - HgfsHandle handle; struct inode *inode; - loff_t currentFileSize; - loff_t offset; ASSERT(file); ASSERT(page); inode = page->mapping->host; - currentFileSize = compat_i_size_read(inode); - offset = (loff_t)page->index << PAGE_CACHE_SHIFT; - - if (writeTo > currentFileSize) { - compat_i_size_write(inode, writeTo); - } - - /* We wrote a complete page, so it is up to date. */ - if (copied == PAGE_CACHE_SIZE) { - SetPageUptodate(page); - } /* - * Check if this is a partial write to a new page, which was - * initialized in HgfsDoWriteBegin. + * Zero any uninitialised parts of the page, and then mark the page + * as up to date if it turns out that we're extending the file. */ - if ((offset >= currentFileSize) || - ((pageFrom == 0) && (writeTo >= currentFileSize))) { + if (!PageUptodate(page)) { SetPageUptodate(page); } /* - * If the page is uptodate, then just mark it dirty and let - * the page cache write it when it wants to. + * Track the pages being written. */ - if (PageUptodate(page)) { - set_page_dirty(page); - return 0; - } + HgfsInodePageWbAdd(inode, page); - /* - * We've recieved a partial write to page that is not uptodate, so - * do the write now while the page is still locked. Another - * alternative would be to read the page in HgfsDoWriteBegin, which - * would make it uptodate (ie a complete cached page). - */ - handle = FILE_GET_FI_P(file)->handle; - LOG(6, (KERN_WARNING "VMware hgfs: %s: writing to handle %u\n", __func__, - handle)); - return HgfsDoWritepage(handle, page, pageFrom, pageTo); + HgfsDoExtendFile(inode, writeTo); + + set_page_dirty(page); + + LOG(6, (KERN_WARNING "VMware hgfs: HgfsDoWriteEnd: return 0\n")); + return 0; } @@ -1143,7 +1185,7 @@ HgfsWriteEnd(struct file *file, // IN: File to write void *clientData) // IN: From write_begin, unused. { unsigned pageFrom = pos & (PAGE_CACHE_SIZE - 1); - unsigned pageTo = pageFrom + copied; + unsigned pageTo = pageFrom + len; loff_t writeTo = pos + copied; int ret; @@ -1151,6 +1193,10 @@ HgfsWriteEnd(struct file *file, // IN: File to write ASSERT(mapping); ASSERT(page); + if (copied < len) { + zero_user_segment(page, pageFrom + copied, pageFrom + len); + } + ret = HgfsDoWriteEnd(file, page, pageFrom, pageTo, writeTo, copied); if (ret == 0) { ret = copied; @@ -1161,3 +1207,671 @@ HgfsWriteEnd(struct file *file, // IN: File to write return ret; } #endif + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbPageAlloc -- + * + * Allocates a write-back page object. + * + * Results: + * The write-back page object + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline HgfsWbPage * +HgfsWbPageAlloc(void) +{ + return kmalloc(sizeof (HgfsWbPage), GFP_KERNEL); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbPageAlloc -- + * + * Frees a write-back page object. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + + +static inline void +HgfsWbPageFree(HgfsWbPage *page) // IN: request of page data to write +{ + ASSERT(page); + kfree(page); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestFree -- + * + * Frees the resources for a write-back page request. + * Calls the request destroy and then frees the object memory. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsWbRequestFree(struct kref *kref) // IN: ref field request of page data to write +{ + HgfsWbPage *req = container_of(kref, HgfsWbPage, wb_kref); + + /* Release write back request page and free it. */ + HgfsWbRequestDestroy(req); + HgfsWbPageFree(req); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestGet -- + * + * Reference the write-back page request. + * Calls the request destroy and then frees the object memory. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +void +HgfsWbRequestGet(HgfsWbPage *req) // IN: request of page data to write +{ + kref_get(&req->wb_kref); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestPut -- + * + * Remove a reference the write-back page request. + * Calls the request free to tear down the object memory if it was the + * final one. + * + * Results: + * None + * + * Side effects: + * Destroys the request if last one. + * + *---------------------------------------------------------------------- + */ + +void +HgfsWbRequestPut(HgfsWbPage *req) // IN: request of page data to write +{ + kref_put(&req->wb_kref, HgfsWbRequestFree); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestWaitUninterruptible -- + * + * Sleep function while waiting for requests to complete. + * + * Results: + * Always zero. + * + * Side effects: +* None + * + *---------------------------------------------------------------------- + */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) +static int +HgfsWbRequestWaitUninterruptible(void *word) // IN:unused +{ + io_schedule(); + return 0; +} +#endif + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestWait -- + * + * Wait for a write-back page request to complete. + * Interruptible by fatal signals only. + * The user is responsible for holding a count on the request. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + + +int +HgfsWbRequestWait(HgfsWbPage *req) // IN: request of page data to write +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) + return wait_on_bit(&req->wb_flags, + PG_BUSY, + HgfsWbRequestWaitUninterruptible, + TASK_UNINTERRUPTIBLE); +#else + wait_event(req->wb_queue, + !test_bit(PG_BUSY, &req->wb_flags)); + return 0; +#endif +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestLock -- + * + * Lock the write-back page request. + * + * Results: + * Non-zero if the lock was not already locked + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline int +HgfsWbRequestLock(HgfsWbPage *req) // IN: request of page data to write +{ + return !test_and_set_bit(PG_BUSY, &req->wb_flags); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestUnlock -- + * + * Unlock the write-back page request. + * Wakes up any waiting threads on the lock. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsWbRequestUnlock(HgfsWbPage *req) // IN: request of page data to write +{ + if (!test_bit(PG_BUSY,&req->wb_flags)) { + LOG(6, (KERN_WARNING "VMware Hgfs: HgfsWbRequestUnlock: Invalid unlock attempted\n")); + return; + } + smp_mb__before_clear_bit(); + clear_bit(PG_BUSY, &req->wb_flags); + smp_mb__after_clear_bit(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) + wake_up_bit(&req->wb_flags, PG_BUSY); +#else + wake_up(&req->wb_queue); +#endif +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestUnlockAndPut -- + * + * Unlock the write-back page request and removes a reference. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsWbRequestUnlockAndPut(HgfsWbPage *req) // IN: request of page data to write +{ + HgfsWbRequestUnlock(req); + HgfsWbRequestPut(req); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestListAdd -- + * + * Add the write-back page request into the list. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline void +HgfsWbRequestListAdd(HgfsWbPage *req, // IN: request of page data to write + struct list_head *head) // IN: list of requests +{ + list_add_tail(&req->wb_list, head); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestListRemove -- + * + * Remove the write-back page request from the list. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline void +HgfsWbRequestListRemove(HgfsWbPage *req) // IN: request of page data to write +{ + if (!list_empty(&req->wb_list)) { + list_del_init(&req->wb_list); + } +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestCreate -- + * + * Create the write-back page request. + * + * Results: + * The new write-back page request. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +HgfsWbPage * +HgfsWbRequestCreate(struct page *page) // IN: page of data to write +{ + HgfsWbPage *wbReq; + /* try to allocate the request struct */ + wbReq = HgfsWbPageAlloc(); + if (wbReq == NULL) { + wbReq = ERR_PTR(-ENOMEM); + goto exit; + } + + /* + * Initialize the request struct. Initially, we assume a + * long write-back delay. This will be adjusted in + * update_nfs_request below if the region is not locked. + */ + wbReq->wb_flags = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 13) + init_waitqueue_head(&wbReq->wb_queue); +#endif + INIT_LIST_HEAD(&wbReq->wb_list); + wbReq->wb_page = page; + wbReq->wb_index = page->index; + page_cache_get(page); + kref_init(&wbReq->wb_kref); + +exit: + LOG(6, (KERN_WARNING "VMware hgfs: HgfsWbRequestCreate: (%p, %p)\n", + wbReq, page)); + return wbReq; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsWbRequestDestroy -- + * + * Destroys by freeing up all resources allocated to the request. + * Release page associated with a write-back request after it has completed. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsWbRequestDestroy(HgfsWbPage *req) // IN: write page request +{ + struct page *page = req->wb_page; + + LOG(6, (KERN_WARNING"VMware hgfs: HgfsWbRequestDestroy: (%p, %p)\n", + req, req->wb_page)); + + if (page != NULL) { + page_cache_release(page); + req->wb_page = NULL; + } +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodeFindWbRequest -- + * + * Finds if there is a write-back page request on this inode and returns it. + * + * Results: + * NULL or the write-back request for the page. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static HgfsWbPage * +HgfsInodeFindWbRequest(struct inode *inode, // IN: inode of file to write to + struct page *page) // IN: page of data to write +{ + HgfsInodeInfo *iinfo; + HgfsWbPage *req = NULL; + HgfsWbPage *cur; + + iinfo = INODE_GET_II_P(inode); + + /* Linearly search the write back list for the correct req */ + list_for_each_entry(cur, &iinfo->listWbPages, wb_list) { + if (cur->wb_page == page) { + req = cur; + break; + } + } + + if (req != NULL) { + HgfsWbRequestGet(req); + } + + return req; +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodeFindExistingWbRequest -- + * + * Finds if there is a write-back page request on this inode and returns + * locked. + * If the request is busy (locked) then it drops the lock and waits for it + * be not locked and searches the list again. + * + * Results: + * NULL or the write-back request for the page. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static HgfsWbPage * +HgfsInodeFindExistingWbRequest(struct inode *inode, // IN: inode of file to write to + struct page *page) // IN: page of data to write +{ + HgfsWbPage *req; + int error; + + spin_lock(&inode->i_lock); + + for (;;) { + req = HgfsInodeFindWbRequest(inode, page); + if (req == NULL) { + goto out_exit; + } + + /* + * Try and lock the request if not already locked. + * If we find it is already locked, busy, then we drop + * the reference and wait to try again. Otherwise, + * once newly locked we break out and return to the caller. + */ + if (HgfsWbRequestLock(req)) { + break; + } + + /* The request was in use, so wait and then retry */ + spin_unlock(&inode->i_lock); + error = HgfsWbRequestWait(req); + HgfsWbRequestPut(req); + if (error != 0) { + goto out_nolock; + } + + spin_lock(&inode->i_lock); + } + +out_exit: + spin_unlock(&inode->i_lock); + return req; + +out_nolock: + return ERR_PTR(error); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodeAddWbRequest -- + * + * Add a write-back page request to an inode. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsInodeAddWbRequest(struct inode *inode, // IN: inode of file to write to + HgfsWbPage *req) // IN: page write request +{ + HgfsInodeInfo *iinfo = INODE_GET_II_P(inode); + + LOG(6, (KERN_WARNING "VMware hgfs: HgfsInodeAddWbRequest: (%p, %p, %lu)\n", + inode, req->wb_page, iinfo->numWbPages)); + + /* Lock the request! */ + HgfsWbRequestLock(req); + + HgfsWbRequestListAdd(req, &iinfo->listWbPages); + iinfo->numWbPages++; + HgfsWbRequestGet(req); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodeAddWbRequest -- + * + * Remove a write-back page request from an inode. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsInodeRemoveWbRequest(struct inode *inode, // IN: inode of file written to + HgfsWbPage *req) // IN: page write request +{ + HgfsInodeInfo *iinfo = INODE_GET_II_P(inode); + + LOG(6, (KERN_CRIT "VMware hgfs: HgfsInodeRemoveWbRequest: (%p, %p, %lu)\n", + inode, req->wb_page, iinfo->numWbPages)); + + iinfo->numWbPages--; + HgfsWbRequestListRemove(req); + HgfsWbRequestPut(req); +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodeAddWbRequest -- + * + * Add a write-back page request to an inode. + * If the page is already exists in the list for this inode nothing is + * done, otherwise a new object is created for the page and added to the + * inode list. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsInodePageWbAdd(struct inode *inode, // IN: inode of file to write to + struct page *page) // IN: page of data to write +{ + HgfsWbPage *req; + + LOG(6, (KERN_CRIT "VMware hgfs: HgfsInodePageWbAdd: (%p, %p)\n", + inode, page)); + + req = HgfsInodeFindExistingWbRequest(inode, page); + if (req != NULL) { + goto exit; + } + + /* + * We didn't find an existing write back request for that page so + * we create one. + */ + req = HgfsWbRequestCreate(page); + if (IS_ERR(req)) { + goto exit; + } + + spin_lock(&inode->i_lock); + /* + * Add the new write request for the page into our inode list to track. + */ + HgfsInodeAddWbRequest(inode, req); + spin_unlock(&inode->i_lock); + +exit: + if (!IS_ERR(req)) { + HgfsWbRequestUnlockAndPut(req); + } +} + + +/* + *---------------------------------------------------------------------- + * + * HgfsInodePageWbRemove -- + * + * Remove a write-back page request from an inode. + * + * Results: + * None + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static void +HgfsInodePageWbRemove(struct inode *inode, // IN: inode of file written to + struct page *page) // IN: page of data written +{ + HgfsWbPage *req; + + LOG(6, (KERN_WARNING "VMware hgfs: HgfsInodePageWbRemove: (%p, %p)\n", + inode, page)); + + req = HgfsInodeFindExistingWbRequest(inode, page); + if (req == NULL) { + goto exit; + } + spin_lock(&inode->i_lock); + /* + * Add the new write request for the page into our inode list to track. + */ + HgfsInodeRemoveWbRequest(inode, req); + HgfsWbRequestUnlockAndPut(req); + spin_unlock(&inode->i_lock); + +exit: + return; +} + -- 2.0.1