/* * Copyright (C) 2020-2021 Pascal Nowack * * 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 Free Software Foundation; either version 2 of the * License, or (at your option) any 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 GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-fuse-clipboard.h" #define FUSE_USE_VERSION 35 #include #include #include "grd-clipboard-rdp.h" #include "grd-utils.h" #define CLIP_DATA_ENTRY_DROP_TIMEOUT_MS (60 * 1000) #define CLIP_DATA_ENTRY_DROP_TIMEOUT_DELTA_US (10 * G_USEC_PER_SEC) #define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C (11644473600) typedef enum _FuseLowlevelOperationType { FUSE_LL_OPERATION_NONE, FUSE_LL_OPERATION_LOOKUP, FUSE_LL_OPERATION_GETATTR, FUSE_LL_OPERATION_READ, } FuseLowlevelOperationType; typedef struct _FuseFileStealContext { gboolean all_files; gboolean has_clip_data_id; uint32_t clip_data_id; GList *fuse_files; } FuseFileStealContext; typedef struct _ClearRdpFuseRequestContext { GrdRdpFuseClipboard *rdp_fuse_clipboard; gboolean all_files; gboolean has_clip_data_id; uint32_t clip_data_id; } ClearRdpFuseRequestContext; typedef struct _FuseFile FuseFile; typedef struct _ClipDataEntry ClipDataEntry; struct _FuseFile { FuseFile *parent; GList *children; char *filename; char *filename_with_root; uint32_t list_idx; fuse_ino_t ino; gboolean is_directory; gboolean is_readonly; gboolean has_size; uint64_t size; gboolean has_last_write_time; uint64_t last_write_time_unix; gboolean has_clip_data_id; uint32_t clip_data_id; ClipDataEntry *entry; }; struct _ClipDataEntry { FuseFile *clip_data_dir; gboolean has_clip_data_id; uint32_t clip_data_id; GrdClipboardRdp *clipboard_rdp; gboolean had_file_contents_request; struct { GrdRdpFuseClipboard *rdp_fuse_clipboard; unsigned int drop_id; int64_t drop_id_timeout_set_us; } drop_context; }; typedef struct _RdpFuseFileContentsRequest { FuseFile *fuse_file; fuse_req_t fuse_req; FuseLowlevelOperationType operation_type; uint32_t stream_id; } RdpFuseFileContentsRequest; struct _GrdRdpFuseClipboard { GObject parent; GrdClipboardRdp *clipboard_rdp; char *mount_path; GThread *fuse_thread; struct fuse_session *fuse_handle; GrdSyncPoint sync_point_start; GrdSyncPoint sync_point_stop; GSource *timeout_reset_source; GHashTable *timeouts_to_reset; GMutex filesystem_mutex; GMutex selection_mutex; GHashTable *inode_table; GHashTable *clip_data_table; GHashTable *request_table; FuseFile *root_dir; ClipDataEntry *no_cdi_entry; fuse_ino_t next_ino; uint32_t next_clip_data_id; uint32_t next_stream_id; }; G_DEFINE_TYPE (GrdRdpFuseClipboard, grd_rdp_fuse_clipboard, G_TYPE_OBJECT) static gboolean should_remove_fuse_file (FuseFile *fuse_file, gboolean all_files, gboolean has_clip_data_id, uint32_t clip_data_id) { if (all_files) return TRUE; if (fuse_file->ino == FUSE_ROOT_ID) return FALSE; if (!fuse_file->has_clip_data_id && !has_clip_data_id) return TRUE; if (fuse_file->has_clip_data_id && has_clip_data_id && fuse_file->clip_data_id == clip_data_id) return TRUE; return FALSE; } static gboolean collect_fuse_file_to_steal (gpointer key, gpointer value, gpointer user_data) { FuseFile *fuse_file = value; FuseFileStealContext *steal_context = user_data; if (!should_remove_fuse_file (fuse_file, steal_context->all_files, steal_context->has_clip_data_id, steal_context->clip_data_id)) return FALSE; steal_context->fuse_files = g_list_prepend (steal_context->fuse_files, fuse_file); return TRUE; } static gboolean maybe_clear_rdp_fuse_request (gpointer key, gpointer value, gpointer user_data) { RdpFuseFileContentsRequest *rdp_fuse_request = value; ClearRdpFuseRequestContext *clear_context = user_data; GrdRdpFuseClipboard *rdp_fuse_clipboard = clear_context->rdp_fuse_clipboard; if (!should_remove_fuse_file (rdp_fuse_request->fuse_file, clear_context->all_files, clear_context->has_clip_data_id, clear_context->clip_data_id)) return FALSE; if (rdp_fuse_clipboard->fuse_handle) fuse_reply_err (rdp_fuse_request->fuse_req, EIO); g_free (rdp_fuse_request); return TRUE; } void grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (GrdRdpFuseClipboard *rdp_fuse_clipboard) { ClearRdpFuseRequestContext clear_context = {0}; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void invalidate_inode (gpointer data, gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; FuseFile *fuse_file = data; FuseFile *child; GList *l; for (l = fuse_file->children; l; l = l->next) { child = l->data; fuse_lowlevel_notify_delete (rdp_fuse_clipboard->fuse_handle, fuse_file->ino, child->ino, child->filename, strlen (child->filename)); } g_debug ("[FUSE Clipboard] Invalidating inode %lu for file \"%s\"", fuse_file->ino, fuse_file->filename); fuse_lowlevel_notify_inval_inode (rdp_fuse_clipboard->fuse_handle, fuse_file->ino, 0, 0); g_debug ("[FUSE Clipboard] Inode %lu invalidated", fuse_file->ino); } static void fuse_file_free (gpointer data) { FuseFile *fuse_file = data; g_list_free (fuse_file->children); g_free (fuse_file->filename_with_root); g_free (fuse_file); } static void clear_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, gboolean all_selections, ClipDataEntry *entry) { FuseFileStealContext steal_context = {0}; ClearRdpFuseRequestContext clear_context = {0}; FuseFile *clip_data_dir = NULL; g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->selection_mutex)); g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->filesystem_mutex)); if (entry) { FuseFile *root_dir = rdp_fuse_clipboard->root_dir; clip_data_dir = g_steal_pointer (&entry->clip_data_dir); root_dir->children = g_list_remove (root_dir->children, clip_data_dir); steal_context.has_clip_data_id = clear_context.has_clip_data_id = entry->has_clip_data_id; steal_context.clip_data_id = clear_context.clip_data_id = entry->clip_data_id; } steal_context.all_files = clear_context.all_files = all_selections; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; if (entry && entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Clearing selection for clipDataId %u", entry->clip_data_id); else g_debug ("[FUSE Clipboard] Clearing selection%s", all_selections ? "s" : ""); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_hash_table_foreach_steal (rdp_fuse_clipboard->inode_table, collect_fuse_file_to_steal, &steal_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); /** * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive * a FUSE request (e.g. read()), then FUSE would block in read() since * filesystem_mutex would still be locked, if we wouldn't unlock it here. * fuse_lowlevel_notify_inval_inode() will block, since it waits on the FUSE * operation to finish. * So, to avoid a deadlock here, unlock the mutex and reply all incoming * operations with -ENOENT until the invalidation process is complete. */ g_list_foreach (steal_context.fuse_files, invalidate_inode, rdp_fuse_clipboard); if (clip_data_dir) { fuse_lowlevel_notify_delete (rdp_fuse_clipboard->fuse_handle, rdp_fuse_clipboard->root_dir->ino, clip_data_dir->ino, clip_data_dir->filename, strlen (clip_data_dir->filename)); } g_list_free_full (steal_context.fuse_files, fuse_file_free); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (entry && entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Selection cleared for clipDataId %u", entry->clip_data_id); else g_debug ("[FUSE Clipboard] Selection%s cleared", all_selections ? "s" : ""); } static void clear_all_selections (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_selection (rdp_fuse_clipboard, TRUE, NULL); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static void clear_entry_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, ClipDataEntry *entry) { clear_selection (rdp_fuse_clipboard, FALSE, entry); } uint32_t grd_rdp_fuse_clipboard_clip_data_id_new (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; ClipDataEntry *entry = NULL; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (G_UNLIKELY (g_hash_table_size (rdp_fuse_clipboard->clip_data_table) >= UINT32_MAX)) { GHashTableIter iter; ClipDataEntry *iter_value; g_debug ("[FUSE Clipboard] All clipDataIds used. Removing the oldest one"); g_hash_table_iter_init (&iter, rdp_fuse_clipboard->clip_data_table); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &iter_value)) { g_assert (iter_value->drop_context.drop_id); if (!entry || entry->drop_context.drop_id_timeout_set_us > iter_value->drop_context.drop_id_timeout_set_us) entry = iter_value; } g_debug ("[FUSE Clipboard] Force clearing selection with clipDataId %u", entry->clip_data_id); clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); } entry = g_malloc0 (sizeof (ClipDataEntry)); entry->clipboard_rdp = clipboard_rdp; entry->has_clip_data_id = TRUE; entry->clip_data_id = rdp_fuse_clipboard->next_clip_data_id; while (g_hash_table_contains (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id))) ++entry->clip_data_id; rdp_fuse_clipboard->next_clip_data_id = entry->clip_data_id + 1; g_hash_table_insert (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id), entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); grd_clipboard_rdp_lock_remote_clipboard_data (clipboard_rdp, entry->clip_data_id); return entry->clip_data_id; } void grd_rdp_fuse_clipboard_clip_data_id_free (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t clip_data_id) { ClipDataEntry *entry; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return; } clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id)); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } void grd_rdp_fuse_clipboard_clear_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (rdp_fuse_clipboard->no_cdi_entry) clear_entry_selection (rdp_fuse_clipboard, rdp_fuse_clipboard->no_cdi_entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static gboolean drop_clip_data_entry (gpointer user_data) { ClipDataEntry *entry = user_data; GrdRdpFuseClipboard *rdp_fuse_clipboard = entry->drop_context.rdp_fuse_clipboard; g_debug ("[FUSE Clipboard] Dropping ClipDataEntry with clipDataId %u", entry->clip_data_id); g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_entry_selection (rdp_fuse_clipboard, entry); entry->drop_context.drop_id = 0; g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return G_SOURCE_REMOVE; } static void collect_instant_droppable_clip_data_entry (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; GList **droppable_clip_data_entries = user_data; if (!entry->had_file_contents_request) { *droppable_clip_data_entries = g_list_prepend (*droppable_clip_data_entries, entry); } } static void clear_instant_droppable_clip_data_entries (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GList *droppable_clip_data_entries = NULL; GList *l; g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->selection_mutex)); g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->filesystem_mutex)); g_hash_table_foreach (rdp_fuse_clipboard->clip_data_table, collect_instant_droppable_clip_data_entry, &droppable_clip_data_entries); for (l = droppable_clip_data_entries; l; l = l->next) { ClipDataEntry *entry = l->data; g_debug ("[FUSE Clipboard] Instantly clearing selection for clipDataId %u", entry->clip_data_id); clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); } g_list_free (droppable_clip_data_entries); } static void maybe_set_clip_data_entry_timeout (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; if (entry->drop_context.drop_id) return; g_debug ("[FUSE Clipboard] Setting timeout for selection with clipDataId %u", entry->clip_data_id); entry->drop_context.rdp_fuse_clipboard = user_data; entry->drop_context.drop_id = g_timeout_add (CLIP_DATA_ENTRY_DROP_TIMEOUT_MS, drop_clip_data_entry, entry); entry->drop_context.drop_id_timeout_set_us = g_get_monotonic_time (); } void grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_debug ("[FUSE Clipboard] Lazily clearing all selections with clipDataId"); g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_instant_droppable_clip_data_entries (rdp_fuse_clipboard); g_hash_table_foreach (rdp_fuse_clipboard->clip_data_table, maybe_set_clip_data_entry_timeout, rdp_fuse_clipboard); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static fuse_ino_t get_next_free_inode (GrdRdpFuseClipboard *rdp_fuse_clipboard) { fuse_ino_t ino = rdp_fuse_clipboard->next_ino; while (ino == 0 || ino == FUSE_ROOT_ID || g_hash_table_contains (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (ino))) ++ino; rdp_fuse_clipboard->next_ino = ino + 1; return ino; } static FuseFile * clip_data_dir_new (GrdRdpFuseClipboard *rdp_fuse_clipboard, gboolean has_clip_data_id, uint32_t clip_data_id) { FuseFile *root_dir = rdp_fuse_clipboard->root_dir; FuseFile *clip_data_dir; clip_data_dir = g_malloc0 (sizeof (FuseFile)); clip_data_dir->filename_with_root = has_clip_data_id ? g_strdup_printf ("/%u", clip_data_id) : g_strdup_printf ("/%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); clip_data_dir->filename = strrchr (clip_data_dir->filename_with_root, '/') + 1; clip_data_dir->ino = get_next_free_inode (rdp_fuse_clipboard); clip_data_dir->is_directory = TRUE; clip_data_dir->is_readonly = TRUE; clip_data_dir->has_clip_data_id = has_clip_data_id; clip_data_dir->clip_data_id = clip_data_id; root_dir->children = g_list_append (root_dir->children, clip_data_dir); clip_data_dir->parent = root_dir; g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (clip_data_dir->ino), clip_data_dir); return clip_data_dir; } static gboolean is_fuse_file_parent (gpointer key, gpointer value, gpointer user_data) { FuseFile *fuse_file = value; const char *parent_path = user_data; if (!fuse_file->is_directory) return FALSE; if (strcmp (parent_path, fuse_file->filename_with_root) == 0) return TRUE; return FALSE; } static FuseFile * get_parent_directory (GrdRdpFuseClipboard *rdp_fuse_clipboard, const char *path) { FuseFile *parent; char *parent_path; parent_path = g_path_get_dirname (path); parent = g_hash_table_find (rdp_fuse_clipboard->inode_table, is_fuse_file_parent, parent_path); g_free (parent_path); return parent; } static gboolean set_selection_for_clip_data_entry (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files, ClipDataEntry *entry) { FuseFile *clip_data_dir = entry->clip_data_dir; uint32_t clip_data_id = clip_data_dir->clip_data_id; uint32_t i; if (entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Setting selection for clipDataId %u", clip_data_id); else g_debug ("[FUSE Clipboard] Setting selection"); for (i = 0; i < n_files; ++i) { FILEDESCRIPTORW *file; FuseFile *fuse_file, *parent; char *filename = NULL; uint32_t j; file = &files[i]; fuse_file = g_malloc0 (sizeof (FuseFile)); if (!(file->dwFlags & FD_ATTRIBUTES)) g_warning ("[RDP.CLIPRDR] Client did not set the FD_ATTRIBUTES flag"); filename = ConvertWCharToUtf8Alloc (file->cFileName, NULL); if (!filename) { g_warning ("[RDP.CLIPRDR] Failed to convert filename. Aborting " "SelectionTransfer"); clear_entry_selection (rdp_fuse_clipboard, entry); fuse_file_free (fuse_file); return FALSE; } for (j = 0; filename[j]; ++j) { if (filename[j] == '\\') filename[j] = '/'; } fuse_file->filename_with_root = g_strdup_printf ("%s/%s", clip_data_dir->filename_with_root, filename); fuse_file->filename = strrchr (fuse_file->filename_with_root, '/') + 1; g_free (filename); parent = get_parent_directory (rdp_fuse_clipboard, fuse_file->filename_with_root); if (!parent) { g_warning ("[RDP.CLIPRDR] Failed to find parent directory. Aborting " "SelectionTransfer"); clear_entry_selection (rdp_fuse_clipboard, entry); fuse_file_free (fuse_file); return FALSE; } parent->children = g_list_append (parent->children, fuse_file); fuse_file->parent = parent; fuse_file->list_idx = i; fuse_file->ino = get_next_free_inode (rdp_fuse_clipboard); fuse_file->has_clip_data_id = entry->has_clip_data_id; fuse_file->clip_data_id = clip_data_id; fuse_file->entry = entry; if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) fuse_file->is_directory = TRUE; if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY) fuse_file->is_readonly = TRUE; if (file->dwFlags & FD_FILESIZE) { fuse_file->size = ((uint64_t) file->nFileSizeHigh << 32) + file->nFileSizeLow; fuse_file->has_size = TRUE; } if (file->dwFlags & FD_WRITESTIME) { uint64_t filetime; filetime = file->ftLastWriteTime.dwHighDateTime; filetime <<= 32; filetime += file->ftLastWriteTime.dwLowDateTime; fuse_file->last_write_time_unix = filetime / (10 * G_USEC_PER_SEC) - WIN32_FILETIME_TO_UNIX_EPOCH; fuse_file->has_last_write_time = TRUE; } g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (fuse_file->ino), fuse_file); } if (entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Selection set for clipDataId %u", clip_data_id); else g_debug ("[FUSE Clipboard] Selection set"); return TRUE; } gboolean grd_rdp_fuse_clipboard_set_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files, uint32_t clip_data_id) { ClipDataEntry *entry; gboolean result; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return FALSE; } entry->clip_data_dir = clip_data_dir_new (rdp_fuse_clipboard, TRUE, clip_data_id); result = set_selection_for_clip_data_entry (rdp_fuse_clipboard, files, n_files, entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return result; } gboolean grd_rdp_fuse_clipboard_set_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files) { gboolean result; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!rdp_fuse_clipboard->no_cdi_entry) rdp_fuse_clipboard->no_cdi_entry = g_malloc0 (sizeof (ClipDataEntry)); if (rdp_fuse_clipboard->no_cdi_entry->clip_data_dir) clear_entry_selection (rdp_fuse_clipboard, rdp_fuse_clipboard->no_cdi_entry); rdp_fuse_clipboard->no_cdi_entry->clip_data_dir = clip_data_dir_new (rdp_fuse_clipboard, FALSE, 0); result = set_selection_for_clip_data_entry (rdp_fuse_clipboard, files, n_files, rdp_fuse_clipboard->no_cdi_entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return result; } static void write_file_attributes (FuseFile *fuse_file, struct stat *attr) { memset (attr, 0, sizeof (struct stat)); if (!fuse_file) return; attr->st_ino = fuse_file->ino; if (fuse_file->is_directory) { attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755); attr->st_nlink = 2; } else { attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644); attr->st_nlink = 1; attr->st_size = fuse_file->size; } attr->st_uid = getuid (); attr->st_gid = getgid (); attr->st_atime = attr->st_mtime = attr->st_ctime = (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time (NULL)); } static void maybe_queue_clip_data_entry_timeout_reset (GrdRdpFuseClipboard *rdp_fuse_clipboard, ClipDataEntry *entry) { int64_t drop_id_timeout_set_us = entry->drop_context.drop_id_timeout_set_us; int64_t now_us; if (!entry->drop_context.drop_id) return; now_us = g_get_monotonic_time (); if (now_us - drop_id_timeout_set_us < CLIP_DATA_ENTRY_DROP_TIMEOUT_DELTA_US) return; g_debug ("[FUSE Clipboard] Queueing a timeout reset for selection with " "clipDataId %u", entry->clip_data_id); g_hash_table_add (rdp_fuse_clipboard->timeouts_to_reset, GUINT_TO_POINTER (entry->clip_data_id)); g_source_set_ready_time (rdp_fuse_clipboard->timeout_reset_source, 0); } void grd_rdp_fuse_clipboard_submit_file_contents_response (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t stream_id, gboolean response_ok, const uint8_t *data, uint32_t size) { RdpFuseFileContentsRequest *rdp_fuse_request; struct fuse_entry_param entry = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_steal_extended (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (stream_id), NULL, (gpointer *) &rdp_fuse_request)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } if (!response_ok) { g_warning ("[RDP.CLIPRDR] Failed to retrieve file data for file \"%s\" " "from the client", rdp_fuse_request->fuse_file->filename); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (rdp_fuse_request->fuse_req, EIO); g_free (rdp_fuse_request); return; } if ((rdp_fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP || rdp_fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) && size != sizeof (uint64_t)) { g_warning ("[RDP.CLIPRDR] Received invalid file size for file \"%s\" " "from the client", rdp_fuse_request->fuse_file->filename); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (rdp_fuse_request->fuse_req, EIO); g_free (rdp_fuse_request); return; } maybe_queue_clip_data_entry_timeout_reset (rdp_fuse_clipboard, rdp_fuse_request->fuse_file->entry); if (rdp_fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP || rdp_fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) { g_debug ("[FUSE Clipboard] Received file size for file \"%s\" with stream " "id %u", rdp_fuse_request->fuse_file->filename, stream_id); rdp_fuse_request->fuse_file->size = *((uint64_t *) data); rdp_fuse_request->fuse_file->has_size = TRUE; entry.ino = rdp_fuse_request->fuse_file->ino; write_file_attributes (rdp_fuse_request->fuse_file, &entry.attr); entry.attr_timeout = 1.0; entry.entry_timeout = 1.0; } else if (rdp_fuse_request->operation_type == FUSE_LL_OPERATION_READ) { g_debug ("[FUSE Clipboard] Received file range for file \"%s\" with stream " "id %u", rdp_fuse_request->fuse_file->filename, stream_id); } else { g_assert_not_reached (); } g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); switch (rdp_fuse_request->operation_type) { case FUSE_LL_OPERATION_NONE: break; case FUSE_LL_OPERATION_LOOKUP: fuse_reply_entry (rdp_fuse_request->fuse_req, &entry); break; case FUSE_LL_OPERATION_GETATTR: fuse_reply_attr (rdp_fuse_request->fuse_req, &entry.attr, entry.attr_timeout); break; case FUSE_LL_OPERATION_READ: fuse_reply_buf (rdp_fuse_request->fuse_req, (const char *) data, size); break; } g_free (rdp_fuse_request); } static RdpFuseFileContentsRequest * rdp_fuse_file_contents_request_new (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req) { RdpFuseFileContentsRequest *rdp_fuse_request; uint32_t stream_id = rdp_fuse_clipboard->next_stream_id; fuse_file->entry->had_file_contents_request = TRUE; maybe_queue_clip_data_entry_timeout_reset (rdp_fuse_clipboard, fuse_file->entry); rdp_fuse_request = g_malloc0 (sizeof (RdpFuseFileContentsRequest)); rdp_fuse_request->fuse_file = fuse_file; rdp_fuse_request->fuse_req = fuse_req; while (g_hash_table_contains (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (stream_id))) ++stream_id; rdp_fuse_request->stream_id = stream_id; rdp_fuse_clipboard->next_stream_id = stream_id + 1; return rdp_fuse_request; } static void request_file_size_async (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req, FuseLowlevelOperationType operation_type) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; RdpFuseFileContentsRequest *rdp_fuse_request; rdp_fuse_request = rdp_fuse_file_contents_request_new (rdp_fuse_clipboard, fuse_file, fuse_req); rdp_fuse_request->operation_type = operation_type; g_hash_table_insert (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (rdp_fuse_request->stream_id), rdp_fuse_request); g_debug ("[FUSE Clipboard] Requesting file size for file \"%s\" with stream id" " %u", fuse_file->filename, rdp_fuse_request->stream_id); grd_clipboard_rdp_request_remote_file_size_async (clipboard_rdp, rdp_fuse_request->stream_id, fuse_file->list_idx, fuse_file->has_clip_data_id, fuse_file->clip_data_id); } static void request_file_range_async (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req, uint64_t offset, uint32_t requested_size) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; RdpFuseFileContentsRequest *rdp_fuse_request; rdp_fuse_request = rdp_fuse_file_contents_request_new (rdp_fuse_clipboard, fuse_file, fuse_req); rdp_fuse_request->operation_type = FUSE_LL_OPERATION_READ; g_hash_table_insert (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (rdp_fuse_request->stream_id), rdp_fuse_request); g_debug ("[FUSE Clipboard] Requesting file range (%u Bytes at offset %lu) for " "file \"%s\" with stream id %u", requested_size, offset, fuse_file->filename, rdp_fuse_request->stream_id); grd_clipboard_rdp_request_remote_file_range_async (clipboard_rdp, rdp_fuse_request->stream_id, fuse_file->list_idx, offset, requested_size, fuse_file->has_clip_data_id, fuse_file->clip_data_id); } static FuseFile * get_fuse_file_by_ino (GrdRdpFuseClipboard *rdp_fuse_clipboard, fuse_ino_t fuse_ino) { FuseFile *fuse_file; fuse_file = g_hash_table_lookup (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (fuse_ino)); return fuse_file; } static FuseFile * get_fuse_file_by_name_from_parent (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *parent, const char *name) { FuseFile *child; GList *l; for (l = parent->children; l; l = l->next) { child = l->data; if (strcmp (name, child->filename) == 0) return child; } /** * This is not an error since several applications try to find specific files, * e.g. nautilus tries to find "/.Trash", etc. */ g_debug ("[FUSE Clipboard] Requested file \"%s\" in directory \"%s\" does not " "exist", name, parent->filename); return NULL; } static void fuse_ll_lookup (fuse_req_t fuse_req, fuse_ino_t parent_ino, const char *name) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *parent, *fuse_file; struct fuse_entry_param entry = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(parent = get_fuse_file_by_ino (rdp_fuse_clipboard, parent_ino)) || !parent->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!(fuse_file = get_fuse_file_by_name_from_parent ( rdp_fuse_clipboard, parent, name))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } g_debug ("[FUSE Clipboard] lookup() has been called for \"%s\"", name); g_debug ("[FUSE Clipboard] Parent is \"%s\", child is \"%s\"", parent->filename_with_root, fuse_file->filename_with_root); if (!fuse_file->is_directory && !fuse_file->has_size) { request_file_size_async (rdp_fuse_clipboard, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } entry.ino = fuse_file->ino; write_file_attributes (fuse_file, &entry.attr); entry.attr_timeout = 1.0; entry.entry_timeout = 1.0; g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_entry (fuse_req, &entry); } static void fuse_ll_getattr (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; struct stat attr = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } g_debug ("[FUSE Clipboard] getattr() has been called for file \"%s\"", fuse_file->filename_with_root); if (!fuse_file->is_directory && !fuse_file->has_size) { request_file_size_async (rdp_fuse_clipboard, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } write_file_attributes (fuse_file, &attr); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_attr (fuse_req, &attr, 1.0); } static void fuse_ll_open (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_autofree char *filename_with_root = NULL; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EISDIR); return; } filename_with_root = g_strdup (fuse_file->filename_with_root); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); if ((file_info->flags & O_ACCMODE) != O_RDONLY) { fuse_reply_err (fuse_req, EACCES); return; } /* Using direct_io also increases FUSE_MAX_PAGES_PER_REQ */ file_info->direct_io = 1; g_debug ("[FUSE Clipboard] Opening file \"%s\"", filename_with_root); fuse_reply_open (fuse_req, file_info); } static void fuse_ll_read (fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size, off_t offset, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EISDIR); return; } if (!fuse_file->has_size || offset > fuse_file->size) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EINVAL); return; } size = MIN (size, 8 * 1024 * 1024); g_assert (size > 0); request_file_range_async (rdp_fuse_clipboard, fuse_file, fuse_req, offset, size); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void fuse_ll_opendir (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_autofree char *filename_with_root = NULL; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOTDIR); return; } filename_with_root = g_strdup (fuse_file->filename_with_root); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); if ((file_info->flags & O_ACCMODE) != O_RDONLY) { fuse_reply_err (fuse_req, EACCES); return; } g_debug ("[FUSE Clipboard] Opening directory \"%s\"", filename_with_root); fuse_reply_open (fuse_req, file_info); } static void fuse_ll_readdir (fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size, off_t offset, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file, *child; struct stat attr = {0}; size_t written_size, entry_size; char *filename; char *buf; off_t i; GList *l; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOTDIR); return; } g_debug ("[FUSE Clipboard] Reading directory \"%s\" at offset %lu", fuse_file->filename_with_root, offset); if (offset >= g_list_length (fuse_file->children) + 1) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_buf (fuse_req, NULL, 0); return; } buf = g_malloc0 (max_size); written_size = 0; for (i = offset; i < 2; ++i) { if (i == 0) { write_file_attributes (fuse_file, &attr); filename = "."; } else if (i == 1) { write_file_attributes (fuse_file->parent, &attr); attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID; attr.st_mode = fuse_file->parent ? attr.st_mode : 0555; filename = ".."; } else { g_assert_not_reached (); } /** * buf needs to be large enough to hold the entry. If it's not, then the * entry is not filled in but the size of the entry is still returned. */ entry_size = fuse_add_direntry (fuse_req, buf + written_size, max_size - written_size, filename, &attr, i); if (entry_size > max_size - written_size) break; written_size += entry_size; } for (l = fuse_file->children, i = 2; l; l = l->next, ++i) { if (i <= offset) continue; child = l->data; write_file_attributes (child, &attr); entry_size = fuse_add_direntry (fuse_req, buf + written_size, max_size - written_size, child->filename, &attr, i); if (entry_size > max_size - written_size) break; written_size += entry_size; } g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_buf (fuse_req, buf, written_size); g_free (buf); } static const struct fuse_lowlevel_ops fuse_ll_ops = { .lookup = fuse_ll_lookup, .getattr = fuse_ll_getattr, .open = fuse_ll_open, .read = fuse_ll_read, .opendir = fuse_ll_opendir, .readdir = fuse_ll_readdir, }; static gpointer fuse_thread_func (gpointer data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = data; struct fuse_args args = {0}; char *argv[1]; int result; g_debug ("[FUSE Clipboard] FUSE thread started"); argv[0] = program_invocation_name; args.argc = 1; args.argv = argv; rdp_fuse_clipboard->fuse_handle = fuse_session_new (&args, &fuse_ll_ops, sizeof (fuse_ll_ops), rdp_fuse_clipboard); if (!rdp_fuse_clipboard->fuse_handle) g_error ("[FUSE Clipboard] Failed to create FUSE filesystem"); if (fuse_session_mount (rdp_fuse_clipboard->fuse_handle, rdp_fuse_clipboard->mount_path)) g_error ("[FUSE Clipboard] Failed to mount FUSE filesystem"); fuse_daemonize (1); grd_sync_point_complete (&rdp_fuse_clipboard->sync_point_start, TRUE); g_debug ("[FUSE Clipboard] Starting FUSE session"); result = fuse_session_loop (rdp_fuse_clipboard->fuse_handle); if (result < 0) g_error ("fuse_loop() failed: %s", g_strerror (-result)); g_debug ("[FUSE Clipboard] Unmounting FUSE filesystem"); fuse_session_unmount (rdp_fuse_clipboard->fuse_handle); grd_sync_point_wait_for_completion (&rdp_fuse_clipboard->sync_point_stop); g_clear_pointer (&rdp_fuse_clipboard->fuse_handle, fuse_session_destroy); return NULL; } GrdRdpFuseClipboard * grd_rdp_fuse_clipboard_new (GrdClipboardRdp *clipboard_rdp, const char *mount_path) { GrdRdpFuseClipboard *rdp_fuse_clipboard; rdp_fuse_clipboard = g_object_new (GRD_TYPE_RDP_FUSE_CLIPBOARD, NULL); rdp_fuse_clipboard->clipboard_rdp = clipboard_rdp; rdp_fuse_clipboard->mount_path = g_strdup (mount_path); rdp_fuse_clipboard->fuse_thread = g_thread_new ("RDP FUSE clipboard thread", fuse_thread_func, rdp_fuse_clipboard); if (!rdp_fuse_clipboard->fuse_thread) g_error ("[FUSE Clipboard] Failed to create FUSE thread"); grd_sync_point_wait_for_completion (&rdp_fuse_clipboard->sync_point_start); return rdp_fuse_clipboard; } static void dismiss_all_requests (GrdRdpFuseClipboard *rdp_fuse_clipboard) { ClearRdpFuseRequestContext clear_context = {0}; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; clear_context.all_files = TRUE; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void grd_rdp_fuse_clipboard_dispose (GObject *object) { GrdRdpFuseClipboard *rdp_fuse_clipboard = GRD_RDP_FUSE_CLIPBOARD (object); if (rdp_fuse_clipboard->timeout_reset_source) { g_source_destroy (rdp_fuse_clipboard->timeout_reset_source); g_clear_pointer (&rdp_fuse_clipboard->timeout_reset_source, g_source_unref); } if (rdp_fuse_clipboard->fuse_thread) { struct stat attr; g_assert (rdp_fuse_clipboard->fuse_handle); clear_all_selections (rdp_fuse_clipboard); g_debug ("[FUSE Clipboard] Stopping FUSE thread"); fuse_session_exit (rdp_fuse_clipboard->fuse_handle); dismiss_all_requests (rdp_fuse_clipboard); grd_sync_point_complete (&rdp_fuse_clipboard->sync_point_stop, TRUE); /** * FUSE does not immediately stop the session after fuse_session_exit() has * been called. * Instead, it waits on a new operation. Upon retrieving this new * operation, it checks the exit flag and then stops the session loop. * So, trigger a FUSE operation by poking at the FUSE root dir to * effectively stop the session. */ stat (rdp_fuse_clipboard->mount_path, &attr); g_debug ("[FUSE Clipboard] Waiting on FUSE thread"); g_clear_pointer (&rdp_fuse_clipboard->fuse_thread, g_thread_join); g_debug ("[FUSE Clipboard] FUSE thread stopped"); } g_clear_pointer (&rdp_fuse_clipboard->mount_path, g_free); if (rdp_fuse_clipboard->request_table) { dismiss_all_requests (rdp_fuse_clipboard); g_clear_pointer (&rdp_fuse_clipboard->request_table, g_hash_table_unref); } g_clear_pointer (&rdp_fuse_clipboard->no_cdi_entry, g_free); g_clear_pointer (&rdp_fuse_clipboard->clip_data_table, g_hash_table_destroy); g_clear_pointer (&rdp_fuse_clipboard->inode_table, g_hash_table_destroy); g_clear_pointer (&rdp_fuse_clipboard->timeouts_to_reset, g_hash_table_destroy); G_OBJECT_CLASS (grd_rdp_fuse_clipboard_parent_class)->dispose (object); } static void grd_rdp_fuse_clipboard_finalize (GObject *object) { GrdRdpFuseClipboard *rdp_fuse_clipboard = GRD_RDP_FUSE_CLIPBOARD (object); grd_sync_point_clear (&rdp_fuse_clipboard->sync_point_stop); grd_sync_point_clear (&rdp_fuse_clipboard->sync_point_start); g_mutex_clear (&rdp_fuse_clipboard->selection_mutex); g_mutex_clear (&rdp_fuse_clipboard->filesystem_mutex); G_OBJECT_CLASS (grd_rdp_fuse_clipboard_parent_class)->finalize (object); } static void clip_data_entry_free (gpointer data) { ClipDataEntry *entry = data; grd_clipboard_rdp_unlock_remote_clipboard_data (entry->clipboard_rdp, entry->clip_data_id); g_clear_handle_id (&entry->drop_context.drop_id, g_source_remove); g_free (entry); } static FuseFile * fuse_file_new_root (void) { FuseFile *root_dir; root_dir = g_malloc0 (sizeof (FuseFile)); root_dir->filename_with_root = g_strdup ("/"); root_dir->filename = root_dir->filename_with_root; root_dir->ino = FUSE_ROOT_ID; root_dir->is_directory = TRUE; root_dir->is_readonly = TRUE; return root_dir; } static gboolean maybe_reset_clip_data_entry_timeout (gpointer key, gpointer value, gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; uint32_t clip_data_id = GPOINTER_TO_UINT (key); ClipDataEntry *entry; if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) return TRUE; if (!entry->drop_context.drop_id) return TRUE; g_debug ("[FUSE Clipboard] Resetting timeout for selection with clipDataId %u", clip_data_id); g_clear_handle_id (&entry->drop_context.drop_id, g_source_remove); entry->drop_context.drop_id = g_timeout_add (CLIP_DATA_ENTRY_DROP_TIMEOUT_MS, drop_clip_data_entry, entry); entry->drop_context.drop_id_timeout_set_us = g_get_monotonic_time (); return TRUE; } static gboolean reset_clip_data_entry_timeouts (gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->timeouts_to_reset, maybe_reset_clip_data_entry_timeout, rdp_fuse_clipboard); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return G_SOURCE_CONTINUE; } static gboolean timeout_reset_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs timeout_reset_source_funcs = { .dispatch = timeout_reset_source_dispatch, }; static void grd_rdp_fuse_clipboard_init (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GSource *timeout_reset_source; rdp_fuse_clipboard->timeouts_to_reset = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->inode_table = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->clip_data_table = g_hash_table_new_full (NULL, NULL, NULL, clip_data_entry_free); rdp_fuse_clipboard->request_table = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->root_dir = fuse_file_new_root (); g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (rdp_fuse_clipboard->root_dir->ino), rdp_fuse_clipboard->root_dir); g_mutex_init (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_init (&rdp_fuse_clipboard->selection_mutex); grd_sync_point_init (&rdp_fuse_clipboard->sync_point_start); grd_sync_point_init (&rdp_fuse_clipboard->sync_point_stop); timeout_reset_source = g_source_new (&timeout_reset_source_funcs, sizeof (GSource)); g_source_set_callback (timeout_reset_source, reset_clip_data_entry_timeouts, rdp_fuse_clipboard, NULL); g_source_set_ready_time (timeout_reset_source, -1); g_source_attach (timeout_reset_source, NULL); rdp_fuse_clipboard->timeout_reset_source = timeout_reset_source; } static void grd_rdp_fuse_clipboard_class_init (GrdRdpFuseClipboardClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_fuse_clipboard_dispose; object_class->finalize = grd_rdp_fuse_clipboard_finalize; }