#include "str.h"
#include "cpName.h"
#include "cpNameLite.h"
+#include "hashTable.h"
#include "hgfsServerInt.h"
#include "hgfsServerPolicy.h"
#include "hgfsUtil.h"
HgfsSessionInfo *session,
HgfsSendFlags flags);
+static void HgfsCacheRemoveLRUCb(void *data);
+
/*
* Opcode handlers
*/
static void
HgfsServerCompleteRequest(HgfsInternalStatus status, // IN: Status of the request
size_t replyPayloadSize, // IN: sizeof the reply payload
- HgfsInputParam *input) // INOUT: request context
+ HgfsInputParam *input) // IN/OUT: request context
{
void *reply;
size_t replySize;
static HgfsSharedFolderHandle
HgfsServerShareAddInternal(const char *shareName, // IN: shared folder name
- const char *sharePath) // IN: shared folder path)
+ const char *sharePath) // IN: shared folder path
{
HgfsSharedFolderHandle handle = HGFS_INVALID_FOLDER_HANDLE;
DblLnkLst_Links *link, *nextElem;
static HgfsSharedFolderHandle
HgfsServerShareAdd(const char *shareName, // IN: shared folder name
- const char *sharePath) // IN: shared folder path) // IN: List of new shares
+ const char *sharePath) // IN: shared folder path
{
HgfsSharedFolderHandle handle;
Log("%s: initialized notification %s.\n", __FUNCTION__,
(gHgfsDirNotifyActive ? "active" : "inactive"));
}
- if ( 0 != (gHgfsCfgSettings.flags & HGFS_CONFIG_OPLOCK_ENABLED)
- || 0 != (gHgfsCfgSettings.flags & HGFS_CONFIG_OPLOCK_MONITOR_ENABLED)) {
+ if (0 != (gHgfsCfgSettings.flags & HGFS_CONFIG_OPLOCK_MONITOR_ENABLED)) {
if (!HgfsServerOplockInit()) {
Log("%s: failed to init oplock module.\n", __FUNCTION__);
HgfsServerOplockDestroy();
Bool
HgfsServerSetSessionCapability(HgfsOp op, // IN: operation code
HgfsOpCapFlags flags, // IN: flags that describe level of support
- HgfsSessionInfo *session) // INOUT: session to update
+ HgfsSessionInfo *session) // IN/OUT: session to update
{
int i;
Bool result = FALSE;
HGFS_OP_CAPFLAG_IS_SUPPORTED, session);
}
+ if (0 != (gHgfsCfgSettings.flags & HGFS_CONFIG_OPLOCK_MONITOR_ENABLED)) {
+ /* Allocate symlink check status cache. */
+ session->symlinkCache = HgfsCache_Alloc(HgfsCacheRemoveLRUCb);
+
+ /* Allocate file attributes cache. */
+ session->fileAttrCache = HgfsCache_Alloc(HgfsCacheRemoveLRUCb);
+ }
+
*sessionData = session;
Log("%s: init session %p id %"FMT64"x\n", __FUNCTION__, session, session->sessionId);
MXUser_ReleaseExclLock(session->searchArrayLock);
+ HgfsCache* caches[] = {session->symlinkCache, session->fileAttrCache };
+ for (i = 0; i < sizeof(caches) / sizeof(caches[0]); i++) {
+ const void **keys = NULL;
+ size_t nkeys;
+ int keyIdx;
+
+ if (!caches[i]) {
+ continue;
+ }
+ MXUser_AcquireExclLock(caches[i]->lock);
+ HashTable_KeyArray(caches[i]->hashTable, &keys, &nkeys);
+ MXUser_ReleaseExclLock(caches[i]->lock);
+ for (keyIdx = 0; keyIdx < nkeys; keyIdx++) {
+ DblLnkLst_Links *l;
+ const char *name = (const char *)keys[keyIdx];
+ for (l = shares->next; l != shares; l = l->next) {
+ HgfsSharedFolder *share;
+
+ share = DblLnkLst_Container(l, HgfsSharedFolder, links);
+ if (strncmp(name, share->path, strlen(name)) == 0) {
+ break;
+ }
+ }
+
+ if (l == shares) {
+ LOG(4, "%s: Remove %s from cache\n", __FUNCTION__, name);
+ HgfsCache_Invalidate(caches[i], name);
+
+ }
+ }
+ free((void *)keys);
+ }
+
LOG(4, "%s: Ending\n", __FUNCTION__);
}
}
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockFileChangeCb --
+ *
+ * The callback is invoked by the oplock when detecting file/directory is
+ * changed.
+ * This simply calls back to the cache's invalidate function.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+HgfsOplockFileChangeCb(HgfsSessionInfo *session, // IN: Session info
+ void *data) // IN: Path
+{
+ // There is no need to explicitly invoke HgfsOplockUnmonitorFileChange.
+ if (NULL != session->symlinkCache) {
+ HgfsCache_Invalidate(session->symlinkCache, data);
+ }
+ if (NULL != session->fileAttrCache) {
+ HgfsCache_Invalidate(session->fileAttrCache, data);
+ }
+ free(data);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsCacheRemoveLRUCb --
+ *
+ * The callback is invoked by the cache when removing the LRU entry.
+ * This simply calls back to the oplock to unmonitor the file/directory
+ * change event.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+HgfsCacheRemoveLRUCb(void *data) // IN
+{
+ HgfsOplockUnmonitorFileChange(((HOM_HANDLE *)data)[0]);
+}
+
/*
*-----------------------------------------------------------------------------
*
HgfsServerGetLocalNameInfo(const char *cpName, // IN: Cross-platform filename to check
size_t cpNameSize, // IN: Size of name cpName
uint32 caseFlags, // IN: Case-sensitivity flags
+ HgfsSessionInfo *session,// IN: Session info
HgfsShareInfo *shareInfo,// OUT: properties of the shared folder
char **bufOut, // OUT: File name in local fs
size_t *outLen) // OUT: Length of name out optional
char *tempPtr;
uint32 startIndex = 0;
HgfsShareOptions shareOptions;
+ HgfsSymlinkCacheEntry *entry;
ASSERT(cpName);
ASSERT(bufOut);
/* Check for symlinks if the followSymlinks option is not set. */
if (!HgfsServerPolicy_IsShareOptionSet(shareOptions,
HGFS_SHARE_FOLLOW_SYMLINKS)) {
- /*
- * Verify that either the path is same as share path or the path until the
- * parent directory is within the share.
- *
- * XXX: Symlink check could become susceptible to TOCTOU (time-of-check,
- * time-of-use) attack when we move to asynchrounous HGFS operations.
- * We should use the resolved file path for further file system
- * operations, instead of using the one passed from the client.
- */
- nameStatus = HgfsPlatformPathHasSymlink(myBufOut, myBufOutLen, shareInfo->rootDir,
- shareInfo->rootDirLen);
+ if (NULL != session->symlinkCache &&
+ HgfsCache_Get(session->symlinkCache, myBufOut, (void **)&entry)) {
+ nameStatus = entry->nameStatus;
+ } else {
+ /*
+ * Verify that either the path is same as share path or the path until
+ * the parent directory is within the share.
+ *
+ * XXX: Symlink check could become susceptible to
+ * TOCTOU (time-of-check, time-of-use) attack when we move to
+ * asynchrounous HGFS operations.
+ * We should use the resolved file path for further file system
+ * operations, instead of using the one passed from the client.
+ */
+ nameStatus = HgfsPlatformPathHasSymlink(myBufOut, myBufOutLen,
+ shareInfo->rootDir,
+ shareInfo->rootDirLen);
+ if (NULL != session->symlinkCache) {
+ HOM_HANDLE handle =
+ HgfsOplockMonitorFileChange(myBufOut, session,
+ HgfsOplockFileChangeCb,
+ Util_SafeStrdup(myBufOut));
+ if (handle != HGFS_OPLOCK_INVALID_MONITOR_HANDLE) {
+ entry = Util_SafeCalloc(1, sizeof *entry);
+ entry->handle = handle;
+ entry->nameStatus = nameStatus;
+ HgfsCache_Put(session->symlinkCache, myBufOut,
+ entry);
+ }
+ }
+ }
+
if (nameStatus != HGFS_NAME_STATUS_COMPLETE) {
LOG(4, "%s: parent path failed to be resolved: %d\n",
__FUNCTION__, nameStatus);
*/
Bool
-HgfsRemoveFromCache(HgfsHandle handle, // IN: Hgfs handle to the node
+HgfsRemoveFromCache(HgfsHandle handle, // IN: Hgfs handle to the node
HgfsSessionInfo *session) // IN: Session info
{
Bool removed = FALSE;
nameStatus = HgfsServerGetLocalNameInfo(fileName,
fileNameLength,
caseFlags,
+ session,
&shareInfo,
&utf8Name,
&utf8NameLen);
nameStatus = HgfsServerGetLocalNameInfo(srcFileName,
srcFileNameLength,
srcCaseFlags,
+ session,
&shareInfo,
&localSymlinkName,
&localSymlinkNameLen);
if (HgfsUnpackSearchOpenRequest(input->payload, input->payloadSize, input->op,
&dirName, &dirNameLength, &caseFlags)) {
nameStatus = HgfsServerGetLocalNameInfo(dirName, dirNameLength, caseFlags,
- &shareInfo, &baseDir, &baseDirLen);
+ input->session, &shareInfo,
+ &baseDir, &baseDirLen);
status = HgfsPlatformSearchDir(nameStatus, dirName, dirNameLength, caseFlags,
&shareInfo, baseDir, baseDirLen,
input->session, &search);
nameStatus = HgfsServerGetLocalNameInfo(cpName,
cpNameLength,
caseFlags,
+ session,
shareInfo,
localFileName,
localNameLength);
goto exit;
}
- nameStatus = HgfsServerGetLocalNameInfo(info.cpName, info.cpNameSize, info.caseFlags,
+ nameStatus = HgfsServerGetLocalNameInfo(info.cpName, info.cpNameSize,
+ info.caseFlags, input->session,
&shareInfo, &utf8Name, &utf8NameLen);
if (HGFS_NAME_STATUS_COMPLETE == nameStatus) {
ASSERT(utf8Name);
char *utf8Name = NULL;
size_t utf8NameLen;
- nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags, &shareInfo,
+ nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags,
+ input->session, &shareInfo,
&utf8Name, &utf8NameLen);
if (nameStatus == HGFS_NAME_STATUS_COMPLETE) {
/*
char *utf8Name = NULL;
size_t utf8NameLen;
- nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags, &shareInfo,
+ nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags,
+ input->session, &shareInfo,
&utf8Name, &utf8NameLen);
if (HGFS_NAME_STATUS_COMPLETE == nameStatus) {
ASSERT(utf8Name);
LOG(8, "%s: entered\n",__FUNCTION__);
- nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags, &shareInfo,
+ nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags,
+ input->session, &shareInfo,
&utf8Name, &utf8NameLen);
if (HGFS_NAME_STATUS_COMPLETE == nameStatus) {
char const *inEnd = cpName + cpNameSize;
size_t localNameLen;
HgfsShareInfo shareInfo;
size_t replyPayloadSize = 0;
+ HgfsSessionInfo *session;
+ HgfsFileAttrCacheEntry *entry;
HGFS_ASSERT_INPUT(input);
+ session = input->session;
+
if (HgfsUnpackGetattrRequest(input->payload, input->payloadSize, input->op, &attr,
&hints, &cpName, &cpNameSize, &file, &caseFlags)) {
/* Client wants us to reuse an existing handle. */
if (hints & HGFS_ATTR_HINT_USE_FILE_DESC) {
fileDesc fd;
+ HgfsFileNode node;
+ Bool found;
- targetNameLen = 0;
- status = HgfsPlatformGetFd(file, input->session, FALSE, &fd);
- if (HGFS_ERROR_SUCCESS == status) {
- status = HgfsPlatformGetattrFromFd(fd, input->session, &attr);
+ memset(&node, 0, sizeof node);
+ found = HgfsGetNodeCopy(file, session, TRUE, &node);
+
+ if (found && NULL != session->fileAttrCache &&
+ HgfsCache_Get(session->fileAttrCache, node.utf8Name,
+ (void **)&entry)) {
+ attr = entry->attr;
+ status = HGFS_ERROR_SUCCESS;
} else {
- LOG(4, "%s: Could not get file descriptor\n", __FUNCTION__);
+ targetNameLen = 0;
+ status = HgfsPlatformGetFd(file, session, FALSE, &fd);
+ if (HGFS_ERROR_SUCCESS == status) {
+ status = HgfsPlatformGetattrFromFd(fd, session, &attr);
+ if (found && HGFS_ERROR_SUCCESS == status &&
+ NULL != session->fileAttrCache) {
+ HOM_HANDLE handle =
+ HgfsOplockMonitorFileChange(node.utf8Name, session,
+ HgfsOplockFileChangeCb,
+ Util_SafeStrdup(node.utf8Name));
+ if (handle != HGFS_OPLOCK_INVALID_MONITOR_HANDLE) {
+ entry = Util_SafeCalloc(1, sizeof *entry);
+ entry->handle = handle;
+ entry->attr = attr;
+ HgfsCache_Put(session->fileAttrCache, node.utf8Name,
+ entry);
+ }
+ }
+ } else {
+ LOG(4, "%s: Could not get file descriptor\n", __FUNCTION__);
+ }
+ }
+ if (found) {
+ free(node.utf8Name);
}
} else {
* Depending on whether this file/dir is real or virtual, either
* forge its attributes or look them up in the actual filesystem.
*/
- nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags, &shareInfo,
+ nameStatus = HgfsServerGetLocalNameInfo(cpName, cpNameSize, caseFlags,
+ session, &shareInfo,
&localName, &localNameLen);
switch (nameStatus) {
case HGFS_NAME_STATUS_INCOMPLETE_BASE:
/* This is a regular lookup; proceed as usual */
ASSERT(localName);
- /* Get the config options. */
- nameStatus = HgfsServerPolicy_GetShareOptions(cpName, cpNameSize,
- &configOptions);
- if (HGFS_NAME_STATUS_COMPLETE == nameStatus) {
- status = HgfsPlatformGetattrFromName(localName, configOptions, (char *)cpName, &attr,
- &targetName);
+ if (NULL != session->fileAttrCache &&
+ HgfsCache_Get(session->fileAttrCache, localName,
+ (void **)&entry)) {
+ attr = entry->attr;
+ status = HGFS_ERROR_SUCCESS;
} else {
- LOG(4, "%s: no matching share: %s.\n", __FUNCTION__, cpName);
- status = HGFS_ERROR_FILE_NOT_FOUND;
- }
+ /* Get the config options. */
+ nameStatus = HgfsServerPolicy_GetShareOptions(cpName, cpNameSize,
+ &configOptions);
+ if (HGFS_NAME_STATUS_COMPLETE == nameStatus) {
+ status = HgfsPlatformGetattrFromName(localName, configOptions,
+ (char *)cpName, &attr,
+ &targetName);
+ if (HGFS_ERROR_SUCCESS == status &&
+ NULL != session->fileAttrCache) {
+ HOM_HANDLE handle =
+ HgfsOplockMonitorFileChange(localName, session,
+ HgfsOplockFileChangeCb,
+ Util_SafeStrdup(localName));
+ if (handle != HGFS_OPLOCK_INVALID_MONITOR_HANDLE) {
+ entry = Util_SafeCalloc(1, sizeof *entry);
+ entry->handle = handle;
+ entry->attr = attr;
+ HgfsCache_Put(session->fileAttrCache, localName,
+ entry);
+ }
+ }
+ } else {
+ LOG(4, "%s: no matching share: %s.\n", __FUNCTION__, cpName);
+ status = HGFS_ERROR_FILE_NOT_FOUND;
+ }
- if (HGFS_ERROR_SUCCESS == status &&
- !HgfsServer_ShareAccessCheck(HGFS_OPEN_MODE_READ_ONLY,
- shareInfo.writePermissions,
- shareInfo.readPermissions)) {
- status = HGFS_ERROR_ACCESS_DENIED;
- } else if (status != HGFS_ERROR_SUCCESS) {
- /*
- * If it is a dangling share server should not return
- * HGFS_ERROR_FILE_NOT_FOUND
- * to the client because it causes confusion: a name that is returned
- * by directory enumeration should not produce "name not found"
- * error.
- * Replace it with a more appropriate error code: no such device.
- */
- if (status == HGFS_ERROR_FILE_NOT_FOUND &&
- HgfsServerIsSharedFolderOnly(cpName, cpNameSize)) {
- status = HGFS_ERROR_IO;
+ if (HGFS_ERROR_SUCCESS == status &&
+ !HgfsServer_ShareAccessCheck(HGFS_OPEN_MODE_READ_ONLY,
+ shareInfo.writePermissions,
+ shareInfo.readPermissions)) {
+ status = HGFS_ERROR_ACCESS_DENIED;
+ } else if (status != HGFS_ERROR_SUCCESS) {
+ /*
+ * If it is a dangling share server should not return
+ * HGFS_ERROR_FILE_NOT_FOUND
+ * to the client because it causes confusion: a name that is returned
+ * by directory enumeration should not produce "name not found"
+ * error.
+ * Replace it with a more appropriate error code: no such device.
+ */
+ if (status == HGFS_ERROR_FILE_NOT_FOUND &&
+ HgfsServerIsSharedFolderOnly(cpName, cpNameSize)) {
+ status = HGFS_ERROR_IO;
+ }
}
}
break;
}
if (HGFS_ERROR_SUCCESS == status) {
if (!HgfsPackGetattrReply(input->packet, input->request, &attr, targetName,
- targetNameLen, &replyPayloadSize, input->session)) {
+ targetNameLen, &replyPayloadSize, session)) {
status = HGFS_ERROR_INTERNAL;
}
}
nameStatus = HgfsServerGetLocalNameInfo(cpName,
cpNameSize,
caseFlags,
+ input->session,
&shareInfo,
&utf8Name,
&utf8NameLen);
static HgfsInternalStatus
HgfsServerValidateOpenParameters(HgfsFileOpenInfo *openInfo, // IN/OUT: openfile info
+ HgfsSessionInfo *session, // IN: Session info
Bool *denyCreatingFile, // OUT: No new files
int *followSymlinks) // OUT: Host resolves link
{
nameStatus = HgfsServerGetLocalNameInfo(openInfo->cpName,
openInfo->cpNameSize,
openInfo->caseFlags,
+ session,
&openInfo->shareInfo,
&openInfo->utf8Name,
&utf8NameLen);
int followSymlinks;
Bool denyCreatingFile;
- status = HgfsServerValidateOpenParameters(&openInfo, &denyCreatingFile,
+ status = HgfsServerValidateOpenParameters(&openInfo, input->session,
+ &denyCreatingFile,
&followSymlinks);
if (HGFS_ERROR_SUCCESS == status) {
ASSERT(openInfo.utf8Name);
transportSession->defaultSessionId = HGFS_INVALID_SESSION_ID;
}
+ if (0 != (gHgfsCfgSettings.flags & HGFS_CONFIG_OPLOCK_MONITOR_ENABLED)) {
+ HgfsCache_Destroy(session->symlinkCache);
+ session->symlinkCache = NULL;
+ HgfsCache_Destroy(session->fileAttrCache);
+ session->fileAttrCache = NULL;
+ }
+
/*
* Remove the session from the list. By doing that, the refcount of
* the session will be decremented. Later, we will be invoking
size_t sizeNeeded;
uint32 notifyFlags;
- LOG(4, "%s:Entered shr hnd %u hnd %"FMT64"x file %s mask %u\n",
+ LOG(4, "%s: Entered shr hnd %u hnd %"FMT64"x file %s mask %u\n",
__FUNCTION__, sharedFolder, subscriber, fileName, mask);
if (session->state == HGFS_SESSION_STATE_CLOSED) {