--- /dev/null
+/*********************************************************
+ * Copyright (C) 2020 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/*
+ * hgfsServerOplockMonitor.c --
+ *
+ * Implements functions for HGFS server opportunistic lock monitoring
+ * subfeature.
+ */
+
+
+#include "vmware.h"
+#include "hashTable.h"
+#include "hgfsServerOplockMonitor.h"
+#include "mutexRankLib.h"
+#include "util.h"
+
+ /*
+ * Local data
+ */
+#define AS_KEY(_x) ((const void *)(uintptr_t)(_x))
+
+/*
+ * Define the max count for hash table gOplockMonitorMap.
+ */
+#define OPLOCK_MONITOR_MAP_MAX_COUNT HGFS_OPLOCK_MAX_COUNT
+
+/*
+ * Define the max count for hash table gOplockMonitorHandleMap.
+ * Different monitor requests may target the same file, for each file there is
+ * one item in hash table gOplockMonitorMap, and for each monitor request there
+ * is one item in hash table gOplockMonitorHandleMap.
+ * We support 4 monitor requests for each file.
+ */
+#define OPLOCK_MONITOR_HANDLE_MAP_MAX_COUNT (4 * OPLOCK_MONITOR_MAP_MAX_COUNT)
+
+
+/*
+ * This structure is the type of oplockMonitorData.callbackList.
+ */
+typedef struct {
+ DblLnkLst_Links links;
+ uint64 handle;
+ HgfsOplockCallback callback;
+ void *data;
+} oplockMonitorCallbackList;
+
+/*
+ * This structure is the value field of hash table gOplockMonitorMap.
+ */
+typedef struct {
+ fileDesc fileDesc;
+ char *utf8Name;
+ MXUserExclLock *lock;
+ DblLnkLst_Links callbackList;
+} oplockMonitorData;
+
+/*
+ * Caller can use oplock module to monitor the file change event by providing
+ * the file path instead of file descriptor.
+ * This hash table maps the file path to structure oplockMonitorData
+ * which stores the information for the file, for example the file descriptor.
+ * This hash table is mainly used to check if a file has already been opened,
+ * which means when many callers monitor the same file, we only need to open
+ * that file once.
+ */
+static HashTable *gOplockMonitorMap = NULL;
+
+/*
+ * This hash table is used to map the monitor handle to structure
+ * oplockMonitorData.
+ * This hash table is used when un-monitor the file change.
+ */
+static HashTable *gOplockMonitorHandleMap = NULL;
+
+/* Lock for gOplockMonitorMap and gOplockMonitorHandleMap. */
+static MXUserExclLock *oplockMonitorLock;
+
+/* Indicates if the oplock monitor module is initialized. */
+static Bool gOplockMonitorInit = FALSE;
+
+void
+HgfsOplockUnmonitorFileChangeInternal(HOM_HANDLE handle);
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockMonitorInit --
+ *
+ * Set up any related state for monitoring.
+ *
+ * Results:
+ * TRUE on success, FALSE on failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+HgfsOplockMonitorInit(void)
+{
+ if (gOplockMonitorInit) {
+ return TRUE;
+ }
+
+ // Oplock module must be initialized first.
+ if (!HgfsServerOplockIsInited()) {
+ Log("%s: Oplock module is not inited\n", __FUNCTION__);
+ return FALSE;
+ }
+
+ gOplockMonitorMap = HashTable_Alloc(OPLOCK_MONITOR_MAP_MAX_COUNT,
+ HASH_ISTRING_KEY | HASH_FLAG_COPYKEY,
+ NULL);
+ ASSERT(gOplockMonitorMap);
+
+ gOplockMonitorHandleMap = HashTable_Alloc(OPLOCK_MONITOR_HANDLE_MAP_MAX_COUNT,
+ HASH_INT_KEY,
+ NULL);
+ ASSERT(gOplockMonitorHandleMap);
+
+ oplockMonitorLock = MXUser_CreateExclLock("HgfsoplockMonitorLock",
+ RANK_hgfsSharedFolders);
+
+ gOplockMonitorInit = TRUE;
+ return TRUE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockMonitorDestroy --
+ *
+ * Tear down any related state for monitoring.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+HgfsOplockMonitorDestroy(void)
+{
+ if (!gOplockMonitorInit) {
+ return;
+ }
+
+ HashTable_Free(gOplockMonitorMap);
+ HashTable_Free(gOplockMonitorHandleMap);
+ MXUser_DestroyExclLock(oplockMonitorLock);
+ gOplockMonitorInit = FALSE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockMonitorFileChangeCallback --
+ *
+ * A callback function that called when the target file/directory is
+ * changed.
+ * Calls the caller provided callback.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+HgfsOplockMonitorFileChangeCallback(HgfsSessionInfo *session, // IN:
+ void *data) // IN:
+{
+ oplockMonitorData *monitorData = data;
+ DblLnkLst_Links *link, *nextLink;
+ oplockMonitorCallbackList *callbackItem;
+
+ ASSERT(monitorData);
+ MXUser_AcquireExclLock(oplockMonitorLock);
+ if (HashTable_Lookup(gOplockMonitorMap, monitorData->utf8Name, NULL)) {
+ DblLnkLst_ForEachSafe(link, nextLink, &monitorData->callbackList) {
+ callbackItem = DblLnkLst_Container(link, oplockMonitorCallbackList, links);
+ callbackItem->callback(session, callbackItem->data);
+ /*
+ * callbackItem->data has been freed in the user callback.
+ */
+ callbackItem->data = NULL;
+ HgfsOplockUnmonitorFileChangeInternal(callbackItem->handle);
+ /*
+ * callbackItem has been freed in above function.
+ */
+ callbackItem = NULL;
+ }
+ }
+ MXUser_ReleaseExclLock(oplockMonitorLock);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockMonitorFileChange --
+ *
+ * Monitor the file/directory change event by using oplock.
+ * The caller provided callback will be called if the file/directory is
+ * changed.
+ * This is one-shot action, after the event is fired, the oplock will be
+ * removed.
+ * The data that caller provides will be freed by:
+ * 1. caller callback if the callback is called;
+ * 2. caller callback if this function failed;
+ * 3. this module if caller cancels the file change monitor.
+ *
+ * Results:
+ * HGFS_OPLOCK_INVALID_MONITOR_HANDLE on fail, handle on success.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+HOM_HANDLE
+HgfsOplockMonitorFileChange(char *utf8Name, // IN: Name in UTF8
+ HgfsSessionInfo *session, // IN:
+ HgfsOplockCallback callback,// IN:
+ void *data) // IN:
+{
+ oplockMonitorData *monitorData = NULL;
+ oplockMonitorCallbackList *callbackItem;
+ HOM_HANDLE handle = HGFS_OPLOCK_INVALID_MONITOR_HANDLE;
+ HgfsFileOpenInfo openInfo;
+ HgfsLocalId localId;
+ fileDesc newHandle;
+ HgfsInternalStatus status;
+ HgfsLockType serverLock = HGFS_LOCK_SHARED;
+
+ MXUser_AcquireExclLock(oplockMonitorLock);
+ if (!gOplockMonitorInit) {
+ LOG(4, "%s: Oplock monitor is not inited\n", __FUNCTION__);
+ goto error;
+ }
+
+ if ( HashTable_GetNumElements(gOplockMonitorMap)
+ >= OPLOCK_MONITOR_MAP_MAX_COUNT) {
+ LOG(4, "%s: Exceeds OPLOCK_MONITOR_MAP_MAX_COUNT\n", __FUNCTION__);
+ goto error;
+ }
+
+ if ( HashTable_GetNumElements(gOplockMonitorHandleMap)
+ >= OPLOCK_MONITOR_HANDLE_MAP_MAX_COUNT) {
+ LOG(4, "%s: Exceeds OPLOCK_MONITOR_HANDLE_MAP_MAX_COUNT\n", __FUNCTION__);
+ goto error;
+ }
+
+ /*
+ * If there are multiple monitor request for the same file, we should open
+ * the file only once, and add all the callback functions into one double
+ * link list.
+ */
+ if (HashTable_Lookup(gOplockMonitorMap, utf8Name, (void **)&monitorData)) {
+ callbackItem = Util_SafeMalloc(sizeof *callbackItem);
+ handle = (HOM_HANDLE)callbackItem;
+ DblLnkLst_Init(&callbackItem->links);
+ callbackItem->handle = handle;
+ callbackItem->callback = callback;
+ callbackItem->data = data;
+ DblLnkLst_LinkLast(&monitorData->callbackList,
+ &callbackItem->links);
+ HashTable_Insert(gOplockMonitorHandleMap,
+ AS_KEY(handle),
+ (void *)monitorData);
+ MXUser_ReleaseExclLock(oplockMonitorLock);
+ return handle;
+ }
+
+ memset(&openInfo, 0, sizeof(openInfo));
+ openInfo.mask = HGFS_OPEN_VALID_MODE | HGFS_OPEN_VALID_SHARE_ACCESS;
+ openInfo.mode = HGFS_OPEN_MODE_READ_ONLY;
+#ifdef _WIN32
+ openInfo.shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+#endif
+ openInfo.flags = HGFS_OPEN;
+ openInfo.utf8Name = utf8Name;
+ openInfo.shareInfo.readPermissions = TRUE;
+ status = HgfsPlatformValidateOpen(&openInfo, TRUE, session,
+ &localId, &newHandle);
+
+ if (status != HGFS_ERROR_SUCCESS) {
+ LOG(4, "%s: Failed to open file: %s\n", __FUNCTION__, utf8Name);
+ goto error;
+ }
+
+ monitorData = Util_SafeMalloc(sizeof *monitorData);
+ monitorData->fileDesc = newHandle;
+ monitorData->utf8Name = Util_SafeStrdup(utf8Name);
+ DblLnkLst_Init(&monitorData->callbackList);
+
+ if (!HgfsAcquireAIOServerLock(newHandle,
+ session,
+ &serverLock,
+ HgfsOplockMonitorFileChangeCallback,
+ monitorData)) {
+ HgfsPlatformCloseFile(newHandle, NULL);
+ LOG(4, "%s: Failed to acquire server lock for file: %s\n", __FUNCTION__, utf8Name);
+ goto error;
+ }
+
+ callbackItem = Util_SafeMalloc(sizeof *callbackItem);
+ handle = (HOM_HANDLE)callbackItem;
+ DblLnkLst_Init(&callbackItem->links);
+ callbackItem->handle = handle;
+ callbackItem->callback = callback;
+ callbackItem->data = data;
+ DblLnkLst_LinkLast(&monitorData->callbackList,
+ &callbackItem->links);
+
+ HashTable_Insert(gOplockMonitorMap, utf8Name, (void *)monitorData);
+ HashTable_Insert(gOplockMonitorHandleMap, AS_KEY(handle), (void *)monitorData);
+ MXUser_ReleaseExclLock(oplockMonitorLock);
+ return handle;
+
+error:
+ if (monitorData) {
+ free(monitorData->utf8Name);
+ free(monitorData);
+ }
+
+ MXUser_ReleaseExclLock(oplockMonitorLock);
+ return HGFS_OPLOCK_INVALID_MONITOR_HANDLE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockUnmonitorFileChangeInternal --
+ *
+ * Cancel the monitor action by closing the file descriptor.
+ * The lock for oplockMonitorLock should be acquired before calling this
+ * funcion.
+ * All the objects that related to handle will be released when returns
+ * from this function.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+HgfsOplockUnmonitorFileChangeInternal(HOM_HANDLE handle) // IN:
+{
+ oplockMonitorData *monitorData = NULL;
+ DblLnkLst_Links *link, *nextLink;
+ oplockMonitorCallbackList *callbackItem;
+
+ if (HashTable_Lookup(gOplockMonitorHandleMap,
+ AS_KEY(handle),
+ (void **)&monitorData)) {
+ HashTable_Delete(gOplockMonitorHandleMap, AS_KEY(handle));
+
+ DblLnkLst_ForEachSafe(link, nextLink, &monitorData->callbackList) {
+ callbackItem = DblLnkLst_Container(link, oplockMonitorCallbackList, links);
+ if (callbackItem->handle == handle) {
+ DblLnkLst_Unlink1(&callbackItem->links);
+ free(callbackItem->data);
+ free(callbackItem);
+ break;
+ }
+ }
+
+ /*
+ * Close the file if no one is monitoring it anymore.
+ */
+ if (DblLnkLst_IsLinked(&monitorData->callbackList) == FALSE) {
+ HashTable_Delete(gOplockMonitorMap, monitorData->utf8Name);
+ HgfsRemoveAIOServerLock(monitorData->fileDesc);
+ free(monitorData->utf8Name);
+ free(monitorData);
+ }
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * HgfsOplockUnmonitorFileChange --
+ *
+ * Cancel the monitor action.
+ * All the objects that related to handle will be released when returns
+ * from this function.
+ * the caller provided callback will not be called.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+HgfsOplockUnmonitorFileChange(HOM_HANDLE handle) // IN:
+{
+ MXUser_AcquireExclLock(oplockMonitorLock);
+ HgfsOplockUnmonitorFileChangeInternal(handle);
+ MXUser_ReleaseExclLock(oplockMonitorLock);
+}