mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
1592 lines
52 KiB
C
1592 lines
52 KiB
C
/*
|
|
* 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 <fuse_lowlevel.h>
|
|
#include <unistd.h>
|
|
|
|
#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;
|
|
}
|