24#include <freerdp/config.h>
30#define FUSE_USE_VERSION 30
31#include <fuse_lowlevel.h>
42#include <winpr/string.h>
43#include <winpr/assert.h>
44#include <winpr/image.h>
45#include <winpr/stream.h>
46#include <winpr/clipboard.h>
47#include <winpr/path.h>
49#include <freerdp/utils/signal.h>
50#include <freerdp/log.h>
51#include <freerdp/client/cliprdr.h>
52#include <freerdp/channels/channels.h>
53#include <freerdp/channels/cliprdr.h>
55#include <freerdp/client/client_cliprdr_file.h>
57#if defined(WITH_CLIPRDR_FILE_APPLE)
58#include <winpr/file.h>
60#include <freerdp/freerdp.h>
61#include <freerdp/settings.h>
62#include <freerdp/utils/cliprdr_utils.h>
64#include "client_cliprdr_file_apple.h"
67#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
68#define WIN32_FILETIME_TO_UNIX_EPOCH INT64_C(11644473600)
70#ifdef WITH_DEBUG_CLIPRDR
71#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
73#define DEBUG_CLIPRDR(log, ...) \
80typedef enum eFuseLowlevelOperationType
82 FUSE_LL_OPERATION_NONE,
83 FUSE_LL_OPERATION_LOOKUP,
84 FUSE_LL_OPERATION_GETATTR,
85 FUSE_LL_OPERATION_READ,
86} FuseLowlevelOperationType;
88typedef struct sCliprdrFuseFile CliprdrFuseFile;
90struct sCliprdrFuseFile
92 CliprdrFuseFile* parent;
98 size_t filename_with_root_len;
99 char* filename_with_root;
109 BOOL has_last_write_time;
110 INT64 last_write_time_unix;
112 BOOL has_clip_data_id;
118 CliprdrFileContext* file_context;
120 CliprdrFuseFile* clip_data_dir;
122 BOOL has_clip_data_id;
124} CliprdrFuseClipDataEntry;
128 CliprdrFileContext* file_context;
130 wArrayList* fuse_files;
133 BOOL has_clip_data_id;
135} FuseFileClearContext;
139 FuseLowlevelOperationType operation_type;
140 CliprdrFuseFile* fuse_file;
151 CliprdrFileContext* context;
159 CliprdrLocalFile* files;
160 CliprdrFileContext* context;
163struct cliprdr_file_context
165#if defined(WITH_FUSE)
167 HANDLE fuse_start_sync;
168 HANDLE fuse_stop_sync;
170 struct fuse_session* fuse_sess;
171#if FUSE_USE_VERSION < 30
172 struct fuse_chan* ch;
175 wHashTable* inode_table;
176 wHashTable* dir_table;
177 wHashTable* clip_data_table;
178 wHashTable* request_table;
180 CliprdrFuseClipDataEntry* clip_data_entry_without_id;
181 UINT32 current_clip_data_id;
184 UINT32 next_clip_data_id;
185 UINT32 next_stream_id;
188#if defined(WITH_CLIPRDR_FILE_APPLE)
191 UINT32 apple_files_count;
192 size_t* apple_top_level;
193 size_t apple_top_level_count;
194 char* apple_download_dir;
198 HANDLE apple_contents_event;
199 UINT32 apple_stream_id;
200 BOOL apple_contents_success;
201 BYTE* apple_contents_data;
202 UINT32 apple_contents_size;
206 BOOL file_formats_registered;
207 UINT32 file_capability_flags;
209 UINT32 local_lock_id;
211 wHashTable* local_streams;
214 CliprdrClientContext* context;
217 BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
218 BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
221#if defined(WITH_FUSE)
222static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino);
224static void fuse_file_free(
void* data)
226 CliprdrFuseFile* fuse_file = data;
231 ArrayList_Free(fuse_file->children);
232 free(fuse_file->filename_with_root);
237WINPR_ATTR_FORMAT_ARG(1, 2)
238WINPR_ATTR_MALLOC(fuse_file_free, 1)
240static CliprdrFuseFile* fuse_file_new(WINPR_FORMAT_ARG const
char* fmt, ...)
242 CliprdrFuseFile* file = calloc(1,
sizeof(CliprdrFuseFile));
246 file->children = ArrayList_New(FALSE);
253 va_list ap = WINPR_C_ARRAY_INIT;
256 winpr_vasprintf(&file->filename_with_root, &file->filename_with_root_len, fmt, ap);
263 if (file->filename_with_root && (file->filename_with_root_len > 0))
265 file->filename_len = 0;
266 file->filename = strrchr(file->filename_with_root,
'/') + 1;
268 file->filename_len = strnlen(file->filename, file->filename_with_root_len);
272 fuse_file_free(file);
276static void clip_data_entry_free(
void* data)
278 CliprdrFuseClipDataEntry* clip_data_entry = data;
280 if (!clip_data_entry)
283 if (clip_data_entry->has_clip_data_id)
285 CliprdrFileContext* file_context = clip_data_entry->file_context;
288 WINPR_ASSERT(file_context);
290 unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
291 unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
293 const UINT rc = file_context->context->ClientUnlockClipboardData(file_context->context,
294 &unlock_clipboard_data);
295 if (rc != CHANNEL_RC_OK)
296 WLog_Print(file_context->log, WLOG_DEBUG,
297 "ClientUnlockClipboardData failed with %" PRIu32, rc);
299 clip_data_entry->has_clip_data_id = FALSE;
301 WLog_Print(file_context->log, WLOG_DEBUG,
"Destroyed ClipDataEntry with id %u",
302 clip_data_entry->clip_data_id);
305 free(clip_data_entry);
308static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
310 WINPR_ASSERT(file_context);
312 return (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA) != 0;
315static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
317 UINT32 clip_data_id = 0;
319 WINPR_ASSERT(file_context);
321 HashTable_Lock(file_context->inode_table);
322 clip_data_id = file_context->next_clip_data_id;
323 while (clip_data_id == 0 ||
324 HashTable_GetItemValue(file_context->clip_data_table, (
void*)(uintptr_t)clip_data_id))
327 file_context->next_clip_data_id = clip_data_id + 1;
328 HashTable_Unlock(file_context->inode_table);
333static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
334 BOOL needs_clip_data_id)
336 CliprdrFuseClipDataEntry* clip_data_entry =
nullptr;
339 WINPR_ASSERT(file_context);
341 clip_data_entry = calloc(1,
sizeof(CliprdrFuseClipDataEntry));
342 if (!clip_data_entry)
345 clip_data_entry->file_context = file_context;
346 clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
348 if (!needs_clip_data_id)
349 return clip_data_entry;
351 lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
352 lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
354 if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
356 HashTable_Lock(file_context->inode_table);
357 clip_data_entry_free(clip_data_entry);
358 HashTable_Unlock(file_context->inode_table);
361 clip_data_entry->has_clip_data_id = TRUE;
363 WLog_Print(file_context->log, WLOG_DEBUG,
"Created ClipDataEntry with id %u",
364 clip_data_entry->clip_data_id);
366 return clip_data_entry;
369static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
370 BOOL has_clip_data_id, UINT32 clip_data_id)
375 if (fuse_file->ino == FUSE_ROOT_ID)
377 if (!fuse_file->has_clip_data_id && !has_clip_data_id)
379 if (fuse_file->has_clip_data_id && has_clip_data_id &&
380 (fuse_file->clip_data_id == clip_data_id))
386static BOOL maybe_clear_fuse_request(
const void* key,
void* value,
void* arg)
388 CliprdrFuseRequest* fuse_request = value;
389 FuseFileClearContext* clear_context = arg;
390 CliprdrFileContext* file_context = clear_context->file_context;
391 CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
393 WINPR_ASSERT(file_context);
395 if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
396 clear_context->has_clip_data_id, clear_context->clip_data_id))
399 DEBUG_CLIPRDR(file_context->log,
"Clearing FileContentsRequest for file \"%s\"",
400 fuse_file->filename_with_root);
402 fuse_reply_err(fuse_request->fuse_req, EIO);
403 HashTable_Remove(file_context->request_table, key);
408static BOOL maybe_steal_inode(
const void* key,
void* value,
void* arg)
410 CliprdrFuseFile* fuse_file = value;
411 FuseFileClearContext* clear_context = arg;
412 CliprdrFileContext* file_context = clear_context->file_context;
414 WINPR_ASSERT(file_context);
416 if (should_remove_fuse_file(fuse_file, clear_context->all_files,
417 clear_context->has_clip_data_id, clear_context->clip_data_id))
419 if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
420 WLog_Print(file_context->log, WLOG_ERROR,
421 "Failed to append FUSE file to list for deletion");
423 if (!clear_context->all_files && fuse_file->is_directory)
425 HashTable_Remove(file_context->dir_table, fuse_file->filename_with_root);
427 HashTable_Remove(file_context->inode_table, key);
433static BOOL notify_delete_child(
void* data, WINPR_ATTR_UNUSED
size_t index, va_list ap)
435 CliprdrFuseFile* child = data;
439 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
440 CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
442 WINPR_ASSERT(file_context);
443 WINPR_ASSERT(parent);
445 WINPR_ASSERT(file_context->fuse_sess);
446 fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
447 child->filename_len);
452static BOOL invalidate_inode(
void* data, WINPR_ATTR_UNUSED
size_t index, va_list ap)
454 CliprdrFuseFile* fuse_file = data;
456 WINPR_ASSERT(fuse_file);
458 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
459 WINPR_ASSERT(file_context);
460 WINPR_ASSERT(file_context->fuse_sess);
463 ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
465 DEBUG_CLIPRDR(file_context->log,
"Invalidating inode %lu for file \"%s\"", fuse_file->ino,
466 fuse_file->filename);
467 fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
468 WLog_Print(file_context->log, WLOG_DEBUG,
"Inode %lu invalidated", fuse_file->ino);
474static bool clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
475 CliprdrFuseClipDataEntry* clip_data_entry)
478 FuseFileClearContext clear_context = WINPR_C_ARRAY_INIT;
479 CliprdrFuseFile* clip_data_dir =
nullptr;
481 WINPR_ASSERT(file_context);
483 clear_context.file_context = file_context;
484 clear_context.fuse_files = ArrayList_New(FALSE);
485 WINPR_ASSERT(clear_context.fuse_files);
487 wObject* aobj = ArrayList_Object(clear_context.fuse_files);
493 clip_data_dir = clip_data_entry->clip_data_dir;
494 clip_data_entry->clip_data_dir =
nullptr;
496 WINPR_ASSERT(clip_data_dir);
498 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
500 ArrayList_Remove(root_dir->children, clip_data_dir);
502 clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
503 clear_context.clip_data_id = clip_data_dir->clip_data_id;
505 clear_context.all_files = all_selections;
507 if (clip_data_entry && clip_data_entry->has_clip_data_id)
508 WLog_Print(file_context->log, WLOG_DEBUG,
"Clearing selection for clipDataId %u",
509 clip_data_entry->clip_data_id);
511 WLog_Print(file_context->log, WLOG_DEBUG,
"Clearing selection%s",
512 all_selections ?
"s" :
"");
514 if (!HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context))
518 HashTable_Clear(file_context->dir_table);
520 if (!HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context))
522 HashTable_Unlock(file_context->inode_table);
524 if (file_context->fuse_sess)
534 if (!ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context))
536 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
537 if (clip_data_dir && root_dir)
539 fuse_lowlevel_notify_delete(file_context->fuse_sess, root_dir->ino, clip_data_dir->ino,
540 clip_data_dir->filename, clip_data_dir->filename_len);
543 ArrayList_Free(clear_context.fuse_files);
545 HashTable_Lock(file_context->inode_table);
546 if (clip_data_entry && clip_data_entry->has_clip_data_id)
547 WLog_Print(file_context->log, WLOG_DEBUG,
"Selection cleared for clipDataId %u",
548 clip_data_entry->clip_data_id);
550 WLog_Print(file_context->log, WLOG_DEBUG,
"Selection%s cleared", all_selections ?
"s" :
"");
555static bool clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
557 WINPR_ASSERT(clip_data_entry);
559 if (!clip_data_entry->clip_data_dir)
562 return clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
566static bool clear_no_cdi_entry(CliprdrFileContext* file_context)
569 WINPR_ASSERT(file_context);
571 WINPR_ASSERT(file_context->inode_table);
572 HashTable_Lock(file_context->inode_table);
573 if (file_context->clip_data_entry_without_id)
575 res = clear_entry_selection(file_context->clip_data_entry_without_id);
577 clip_data_entry_free(file_context->clip_data_entry_without_id);
578 file_context->clip_data_entry_without_id =
nullptr;
580 HashTable_Unlock(file_context->inode_table);
585static BOOL clear_clip_data_entries(WINPR_ATTR_UNUSED
const void* key,
void* value,
586 WINPR_ATTR_UNUSED
void* arg)
588 return clear_entry_selection(value);
592static UINT clear_cdi_entries(CliprdrFileContext* file_context)
594 UINT res = CHANNEL_RC_OK;
595 WINPR_ASSERT(file_context);
597 HashTable_Lock(file_context->inode_table);
598 if (!HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries,
nullptr))
599 res = ERROR_INTERNAL_ERROR;
601 HashTable_Clear(file_context->clip_data_table);
602 HashTable_Unlock(file_context->inode_table);
607static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
609 CliprdrFuseClipDataEntry* clip_data_entry =
nullptr;
611 WINPR_ASSERT(file_context);
613 clip_data_entry = clip_data_entry_new(file_context, TRUE);
614 if (!clip_data_entry)
616 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to create clipDataEntry");
617 return ERROR_INTERNAL_ERROR;
620 HashTable_Lock(file_context->inode_table);
621 if (!HashTable_Insert(file_context->clip_data_table,
622 (
void*)(uintptr_t)clip_data_entry->clip_data_id, clip_data_entry))
624 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to insert clipDataEntry");
625 clip_data_entry_free(clip_data_entry);
626 HashTable_Unlock(file_context->inode_table);
627 return ERROR_INTERNAL_ERROR;
629 HashTable_Unlock(file_context->inode_table);
633 file_context->current_clip_data_id = clip_data_entry->clip_data_id;
635 return CHANNEL_RC_OK;
638static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
640 WINPR_ASSERT(file_context);
641 WINPR_ASSERT(!file_context->clip_data_entry_without_id);
643 file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
644 if (!file_context->clip_data_entry_without_id)
646 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to create clipDataEntry");
647 return ERROR_INTERNAL_ERROR;
650 return CHANNEL_RC_OK;
654UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
656 UINT rc = CHANNEL_RC_OK;
657 WINPR_ASSERT(file_context);
658 WINPR_ASSERT(file_context->context);
660#if defined(WITH_FUSE)
661 if (!clear_no_cdi_entry(file_context))
662 return ERROR_INTERNAL_ERROR;
664 rc = clear_cdi_entries(file_context);
665 if (rc != CHANNEL_RC_OK)
668 if (does_server_support_clipdata_locking(file_context))
669 rc = prepare_clip_data_entry_with_id(file_context);
671 rc = prepare_clip_data_entry_without_id(file_context);
676UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
678 WINPR_ASSERT(file_context);
679 WINPR_ASSERT(file_context->context);
681#if defined(WITH_FUSE)
682 if (!clear_no_cdi_entry(file_context))
683 return ERROR_INTERNAL_ERROR;
685 return clear_cdi_entries(file_context);
688 return CHANNEL_RC_OK;
691static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
692 const char* data,
size_t size);
693static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
694static BOOL local_stream_discard(
const void* key,
void* value,
void* arg);
696WINPR_ATTR_FORMAT_ARG(6, 7)
697static
void writelog(wLog* log, DWORD level, const
char* fname, const
char* fkt,
size_t line,
698 WINPR_FORMAT_ARG const
char* fmt, ...)
700 if (!WLog_IsLevelActive(log, level))
703 va_list ap = WINPR_C_ARRAY_INIT;
705 WLog_PrintTextMessageVA(log, level, line, fname, fkt, fmt, ap);
709#if defined(WITH_FUSE)
710static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context);
711static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent,
const char* name);
712static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info* fi);
713static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino,
size_t size, off_t off,
714 struct fuse_file_info* fi);
715static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino,
size_t size, off_t off,
716 struct fuse_file_info* fi);
717static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info* fi);
718static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info* fi);
720static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
721 .lookup = cliprdr_file_fuse_lookup,
722 .getattr = cliprdr_file_fuse_getattr,
723 .readdir = cliprdr_file_fuse_readdir,
724 .open = cliprdr_file_fuse_open,
725 .read = cliprdr_file_fuse_read,
726 .opendir = cliprdr_file_fuse_opendir,
729CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
731 WINPR_ASSERT(file_context);
733 return HashTable_GetItemValue(file_context->inode_table, (
void*)(uintptr_t)fuse_ino);
736static CliprdrFuseFile*
737get_fuse_file_by_name_from_parent(WINPR_ATTR_UNUSED CliprdrFileContext* file_context,
738 CliprdrFuseFile* parent,
const char* name)
740 WINPR_ASSERT(file_context);
741 WINPR_ASSERT(parent);
743 for (
size_t i = 0; i < ArrayList_Count(parent->children); ++i)
745 CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
749 if (strncmp(name, child->filename, child->filename_len + 1) == 0)
753 DEBUG_CLIPRDR(file_context->log,
"Requested file \"%s\" in directory \"%s\" does not exist",
754 name, parent->filename);
759static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
760 CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
761 FuseLowlevelOperationType operation_type)
763 CliprdrFuseRequest* fuse_request =
nullptr;
764 UINT32 stream_id = file_context->next_stream_id;
766 WINPR_ASSERT(file_context);
767 WINPR_ASSERT(fuse_file);
769 fuse_request = calloc(1,
sizeof(CliprdrFuseRequest));
772 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to allocate FUSE request for file \"%s\"",
773 fuse_file->filename_with_root);
777 fuse_request->fuse_file = fuse_file;
778 fuse_request->fuse_req = fuse_req;
779 fuse_request->operation_type = operation_type;
781 while (stream_id == 0 ||
782 HashTable_GetItemValue(file_context->request_table, (
void*)(uintptr_t)stream_id))
784 fuse_request->stream_id = stream_id;
786 file_context->next_stream_id = stream_id + 1;
788 if (!HashTable_Insert(file_context->request_table, (
void*)(uintptr_t)fuse_request->stream_id,
791 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to track FUSE request for file \"%s\"",
792 fuse_file->filename_with_root);
800static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
801 fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
803 CliprdrFuseRequest* fuse_request =
nullptr;
806 WINPR_ASSERT(file_context);
807 WINPR_ASSERT(fuse_file);
809 fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
813 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
814 file_contents_request.streamId = fuse_request->stream_id;
815 file_contents_request.listIndex = fuse_file->list_idx;
816 file_contents_request.dwFlags = FILECONTENTS_SIZE;
817 file_contents_request.cbRequested = 0x8;
818 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
819 file_contents_request.clipDataId = fuse_file->clip_data_id;
821 if (file_context->context->ClientFileContentsRequest(file_context->context,
822 &file_contents_request))
824 WLog_Print(file_context->log, WLOG_ERROR,
825 "Failed to send FileContentsRequest for file \"%s\"",
826 fuse_file->filename_with_root);
827 HashTable_Remove(file_context->request_table, (
void*)(uintptr_t)fuse_request->stream_id);
831 DEBUG_CLIPRDR(file_context->log,
"Requested file size for file \"%s\" with stream id %u",
832 fuse_file->filename, fuse_request->stream_id);
838static void write_file_attributes(CliprdrFuseFile* fuse_file,
struct stat* attr)
840 memset(attr, 0,
sizeof(
struct stat));
845 attr->st_ino = fuse_file->ino;
846 if (fuse_file->is_directory)
848 attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
853 attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
855 attr->st_size = WINPR_ASSERTING_INT_CAST(off_t, fuse_file->size);
857 attr->st_uid = getuid();
858 attr->st_gid = getgid();
859 attr->st_atime = attr->st_mtime = attr->st_ctime =
860 (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(
nullptr));
863static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino,
const char* name)
865 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
866 CliprdrFuseFile* parent =
nullptr;
867 CliprdrFuseFile* fuse_file =
nullptr;
868 struct fuse_entry_param entry = WINPR_C_ARRAY_INIT;
870 WINPR_ASSERT(file_context);
872 HashTable_Lock(file_context->inode_table);
873 if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
875 HashTable_Unlock(file_context->inode_table);
876 fuse_reply_err(fuse_req, ENOENT);
879 if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
881 HashTable_Unlock(file_context->inode_table);
882 fuse_reply_err(fuse_req, ENOENT);
886 DEBUG_CLIPRDR(file_context->log,
"lookup() has been called for \"%s\"", name);
887 DEBUG_CLIPRDR(file_context->log,
"Parent is \"%s\", child is \"%s\"",
888 parent->filename_with_root, fuse_file->filename_with_root);
890 if (!fuse_file->is_directory && !fuse_file->has_size)
895 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
896 HashTable_Unlock(file_context->inode_table);
899 fuse_reply_err(fuse_req, EIO);
904 entry.ino = fuse_file->ino;
905 write_file_attributes(fuse_file, &entry.attr);
906 entry.attr_timeout = 1.0;
907 entry.entry_timeout = 1.0;
908 HashTable_Unlock(file_context->inode_table);
910 fuse_reply_entry(fuse_req, &entry);
913static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
914 WINPR_ATTR_UNUSED
struct fuse_file_info* file_info)
916 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
917 CliprdrFuseFile* fuse_file =
nullptr;
918 struct stat attr = WINPR_C_ARRAY_INIT;
920 WINPR_ASSERT(file_context);
922 HashTable_Lock(file_context->inode_table);
923 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
925 HashTable_Unlock(file_context->inode_table);
926 fuse_reply_err(fuse_req, ENOENT);
930 DEBUG_CLIPRDR(file_context->log,
"getattr() has been called for file \"%s\"",
931 fuse_file->filename_with_root);
933 if (!fuse_file->is_directory && !fuse_file->has_size)
938 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
939 HashTable_Unlock(file_context->inode_table);
942 fuse_reply_err(fuse_req, EIO);
947 write_file_attributes(fuse_file, &attr);
948 HashTable_Unlock(file_context->inode_table);
950 fuse_reply_attr(fuse_req, &attr, 1.0);
953static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
954 struct fuse_file_info* file_info)
956 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
957 CliprdrFuseFile* fuse_file =
nullptr;
959 WINPR_ASSERT(file_context);
961 HashTable_Lock(file_context->inode_table);
962 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
964 HashTable_Unlock(file_context->inode_table);
965 fuse_reply_err(fuse_req, ENOENT);
968 if (fuse_file->is_directory)
970 HashTable_Unlock(file_context->inode_table);
971 fuse_reply_err(fuse_req, EISDIR);
974 HashTable_Unlock(file_context->inode_table);
976 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
978 fuse_reply_err(fuse_req, EACCES);
983 file_info->direct_io = 1;
985 fuse_reply_open(fuse_req, file_info);
988static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
989 fuse_req_t fuse_req, off_t offset,
size_t requested_size)
993 WINPR_ASSERT(file_context);
994 WINPR_ASSERT(fuse_file);
996 if (requested_size > UINT32_MAX)
999 CliprdrFuseRequest* fuse_request =
1000 cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
1004 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
1005 file_contents_request.streamId = fuse_request->stream_id;
1006 file_contents_request.listIndex = fuse_file->list_idx;
1007 file_contents_request.dwFlags = FILECONTENTS_RANGE;
1008 file_contents_request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF);
1009 file_contents_request.nPositionHigh = (UINT32)((offset >> 32) & 0xFFFFFFFF);
1010 file_contents_request.cbRequested = (UINT32)requested_size;
1011 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
1012 file_contents_request.clipDataId = fuse_file->clip_data_id;
1014 if (file_context->context->ClientFileContentsRequest(file_context->context,
1015 &file_contents_request))
1017 WLog_Print(file_context->log, WLOG_ERROR,
1018 "Failed to send FileContentsRequest for file \"%s\"",
1019 fuse_file->filename_with_root);
1020 HashTable_Remove(file_context->request_table, (
void*)(uintptr_t)fuse_request->stream_id);
1028 "Requested file range (%zu Bytes at offset %ld) for file \"%s\" with stream id %u",
1029 requested_size, offset, fuse_file->filename, fuse_request->stream_id);
1035static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
size_t size,
1036 off_t offset, WINPR_ATTR_UNUSED
struct fuse_file_info* file_info)
1038 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1039 CliprdrFuseFile* fuse_file =
nullptr;
1042 WINPR_ASSERT(file_context);
1044 HashTable_Lock(file_context->inode_table);
1045 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1047 HashTable_Unlock(file_context->inode_table);
1048 fuse_reply_err(fuse_req, ENOENT);
1051 if (fuse_file->is_directory)
1053 HashTable_Unlock(file_context->inode_table);
1054 fuse_reply_err(fuse_req, EISDIR);
1057 if (!fuse_file->has_size || (offset < 0) || ((
size_t)offset > fuse_file->size))
1059 HashTable_Unlock(file_context->inode_table);
1060 fuse_reply_err(fuse_req, EINVAL);
1064 size = MIN(size, 8ULL * 1024ULL * 1024ULL);
1066 result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
1067 HashTable_Unlock(file_context->inode_table);
1070 fuse_reply_err(fuse_req, EIO);
1073static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
1074 struct fuse_file_info* file_info)
1076 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1077 CliprdrFuseFile* fuse_file =
nullptr;
1079 WINPR_ASSERT(file_context);
1081 HashTable_Lock(file_context->inode_table);
1082 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1084 HashTable_Unlock(file_context->inode_table);
1085 fuse_reply_err(fuse_req, ENOENT);
1088 if (!fuse_file->is_directory)
1090 HashTable_Unlock(file_context->inode_table);
1091 fuse_reply_err(fuse_req, ENOTDIR);
1094 HashTable_Unlock(file_context->inode_table);
1096 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
1098 fuse_reply_err(fuse_req, EACCES);
1102 fuse_reply_open(fuse_req, file_info);
1105static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
size_t max_size,
1107 WINPR_ATTR_UNUSED
struct fuse_file_info* file_info)
1109 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1110 CliprdrFuseFile* fuse_file =
nullptr;
1111 CliprdrFuseFile* child =
nullptr;
1112 struct stat attr = WINPR_C_ARRAY_INIT;
1113 size_t written_size = 0;
1114 size_t entry_size = 0;
1115 char* filename =
nullptr;
1116 char* buf =
nullptr;
1118 WINPR_ASSERT(file_context);
1120 HashTable_Lock(file_context->inode_table);
1121 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1123 HashTable_Unlock(file_context->inode_table);
1124 fuse_reply_err(fuse_req, ENOENT);
1127 if (!fuse_file->is_directory)
1129 HashTable_Unlock(file_context->inode_table);
1130 fuse_reply_err(fuse_req, ENOTDIR);
1134 DEBUG_CLIPRDR(file_context->log,
"Reading directory \"%s\" at offset %ld",
1135 fuse_file->filename_with_root, offset);
1137 if ((offset < 0) || ((
size_t)offset >= ArrayList_Count(fuse_file->children)))
1139 HashTable_Unlock(file_context->inode_table);
1140 fuse_reply_buf(fuse_req,
nullptr, 0);
1144 buf = calloc(max_size,
sizeof(
char));
1147 HashTable_Unlock(file_context->inode_table);
1148 fuse_reply_err(fuse_req, ENOMEM);
1153 for (off_t i = offset; i < 2; ++i)
1157 write_file_attributes(fuse_file, &attr);
1162 write_file_attributes(fuse_file->parent, &attr);
1163 attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
1164 attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
1169 WINPR_ASSERT(FALSE);
1176 entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1177 filename, &attr, i + 1);
1178 if (entry_size > max_size - written_size)
1181 written_size += entry_size;
1184 for (
size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
1186 if (i < (
size_t)offset)
1189 child = ArrayList_GetItem(fuse_file->children, j);
1191 write_file_attributes(child, &attr);
1193 fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1194 child->filename, &attr, WINPR_ASSERTING_INT_CAST(off_t, i + 1));
1195 if (entry_size > max_size - written_size)
1198 written_size += entry_size;
1200 HashTable_Unlock(file_context->inode_table);
1202 fuse_reply_buf(fuse_req, buf, written_size);
1206static void fuse_abort(
int sig,
const char* signame,
void* context)
1208 CliprdrFileContext* file = (CliprdrFileContext*)context;
1212 WLog_Print(file->log, WLOG_INFO,
"signal %s [%d] aborting session", signame, sig);
1213 cliprdr_file_session_terminate(file, FALSE);
1217static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
1219 CliprdrFileContext* file = (CliprdrFileContext*)arg;
1223 DEBUG_CLIPRDR(file->log,
"Starting fuse with mountpoint '%s'", file->path);
1225 struct fuse_args args = FUSE_ARGS_INIT(0,
nullptr);
1226 fuse_opt_add_arg(&args, file->path);
1227 file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
1228 sizeof(cliprdr_file_fuse_oper), (
void*)file);
1229 (void)SetEvent(file->fuse_start_sync);
1231 if (file->fuse_sess !=
nullptr)
1233 if (freerdp_add_signal_cleanup_handler(file, fuse_abort))
1235 if (0 == fuse_session_mount(file->fuse_sess, file->path))
1237 fuse_session_loop(file->fuse_sess);
1238 fuse_session_unmount(file->fuse_sess);
1240 freerdp_del_signal_cleanup_handler(file, fuse_abort);
1243 WLog_Print(file->log, WLOG_DEBUG,
"Waiting for FUSE stop sync");
1244 if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
1245 WLog_Print(file->log, WLOG_ERROR,
"Failed to wait for stop sync");
1246 fuse_session_destroy(file->fuse_sess);
1248 fuse_opt_free_args(&args);
1250 DEBUG_CLIPRDR(file->log,
"Quitting fuse with mountpoint '%s'", file->path);
1256static UINT cliprdr_file_context_server_file_contents_response(
1257 CliprdrClientContext* cliprdr_context,
1260 CliprdrFileContext* file_context =
nullptr;
1261 CliprdrFuseRequest* fuse_request =
nullptr;
1262 struct fuse_entry_param entry = WINPR_C_ARRAY_INIT;
1264 WINPR_ASSERT(cliprdr_context);
1265 WINPR_ASSERT(file_contents_response);
1267 file_context = cliprdr_context->custom;
1268 WINPR_ASSERT(file_context);
1270 HashTable_Lock(file_context->inode_table);
1271 fuse_request = HashTable_GetItemValue(file_context->request_table,
1272 (
void*)(uintptr_t)file_contents_response->streamId);
1275 HashTable_Unlock(file_context->inode_table);
1276 return CHANNEL_RC_OK;
1279 if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
1281 WLog_Print(file_context->log, WLOG_WARN,
1282 "FileContentsRequests for file \"%s\" was unsuccessful",
1283 fuse_request->fuse_file->filename);
1285 fuse_reply_err(fuse_request->fuse_req, EIO);
1286 HashTable_Remove(file_context->request_table,
1287 (
void*)(uintptr_t)file_contents_response->streamId);
1288 HashTable_Unlock(file_context->inode_table);
1289 return CHANNEL_RC_OK;
1292 if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1293 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
1294 file_contents_response->cbRequested !=
sizeof(UINT64))
1296 WLog_Print(file_context->log, WLOG_WARN,
1297 "Received invalid file size for file \"%s\" from the client",
1298 fuse_request->fuse_file->filename);
1299 fuse_reply_err(fuse_request->fuse_req, EIO);
1300 HashTable_Remove(file_context->request_table,
1301 (
void*)(uintptr_t)file_contents_response->streamId);
1302 HashTable_Unlock(file_context->inode_table);
1303 return CHANNEL_RC_OK;
1306 if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1307 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
1309 DEBUG_CLIPRDR(file_context->log,
"Received file size for file \"%s\" with stream id %u",
1310 fuse_request->fuse_file->filename, file_contents_response->streamId);
1312 fuse_request->fuse_file->size =
1313 *(WINPR_PACKED_ALIGN_CAST(
const UINT64*, file_contents_response->requestedData));
1314 fuse_request->fuse_file->has_size = TRUE;
1316 entry.ino = fuse_request->fuse_file->ino;
1317 write_file_attributes(fuse_request->fuse_file, &entry.attr);
1318 entry.attr_timeout = 1.0;
1319 entry.entry_timeout = 1.0;
1321 else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
1323 DEBUG_CLIPRDR(file_context->log,
"Received file range for file \"%s\" with stream id %u",
1324 fuse_request->fuse_file->filename, file_contents_response->streamId);
1326 HashTable_Unlock(file_context->inode_table);
1328 switch (fuse_request->operation_type)
1330 case FUSE_LL_OPERATION_NONE:
1332 case FUSE_LL_OPERATION_LOOKUP:
1333 fuse_reply_entry(fuse_request->fuse_req, &entry);
1335 case FUSE_LL_OPERATION_GETATTR:
1336 fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
1338 case FUSE_LL_OPERATION_READ:
1339 fuse_reply_buf(fuse_request->fuse_req,
1340 (
const char*)file_contents_response->requestedData,
1341 file_contents_response->cbRequested);
1347 HashTable_Remove(file_context->request_table,
1348 (
void*)(uintptr_t)file_contents_response->streamId);
1350 return CHANNEL_RC_OK;
1354#if defined(WITH_CLIPRDR_FILE_APPLE)
1357 WINPR_ASSERT(descriptor);
1358 return ConvertWCharNToUtf8Alloc(descriptor->cFileName, ARRAYSIZE(descriptor->cFileName),
1363static char* apple_local_relative_path(
const char* name)
1367 const size_t len = strlen(name);
1371 char* path = _strdup(name);
1376 for (
size_t x = 0; x <= len; x++)
1378 if ((x < len) && (name[x] ==
'/'))
1380 if ((x < len) && (name[x] !=
'\\'))
1383 const char* component = &name[start];
1384 const size_t clen = x - start;
1385 if ((clen == 0) || ((clen == 1) && (component[0] ==
'.')) ||
1386 ((clen == 2) && (strncmp(component,
"..", 2) == 0)))
1399static void apple_clear_server_files(CliprdrFileContext* file)
1403 free(file->apple_files);
1404 file->apple_files =
nullptr;
1405 file->apple_files_count = 0;
1406 free(file->apple_top_level);
1407 file->apple_top_level =
nullptr;
1408 file->apple_top_level_count = 0;
1409 free(file->apple_download_dir);
1410 file->apple_download_dir =
nullptr;
1413static BOOL apple_request_file_contents(CliprdrFileContext* file, UINT32 listIndex, UINT32 flags,
1414 UINT64 offset, UINT32 size, BYTE** pdata, UINT32* plen)
1417 WINPR_ASSERT(pdata);
1423 CliprdrClientContext* cliprdr = file->context;
1424 if (!cliprdr || !cliprdr->ClientFileContentsRequest)
1428 request.common.msgType = CB_FILECONTENTS_REQUEST;
1429 request.listIndex = listIndex;
1430 request.dwFlags = flags;
1431 request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF);
1432 request.nPositionHigh = (UINT32)(offset >> 32);
1433 request.cbRequested = size;
1435 EnterCriticalSection(&file->apple_lock);
1436 request.streamId = ++file->apple_stream_id;
1437 file->apple_contents_success = FALSE;
1438 free(file->apple_contents_data);
1439 file->apple_contents_data =
nullptr;
1440 file->apple_contents_size = 0;
1441 (void)ResetEvent(file->apple_contents_event);
1442 LeaveCriticalSection(&file->apple_lock);
1444 if (cliprdr->ClientFileContentsRequest(cliprdr, &request) != CHANNEL_RC_OK)
1447 rdpContext* context = cliprdr->rdpcontext;
1448 WINPR_ASSERT(context);
1450 HANDLE hdl[2] = { freerdp_abort_event(context), file->apple_contents_event };
1452 if (WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, timeout) != WAIT_OBJECT_0 + 1)
1454 WLog_Print(file->log, WLOG_ERROR,
"no file contents reply for stream %" PRIu32,
1459 EnterCriticalSection(&file->apple_lock);
1460 const BOOL rc = file->apple_contents_success;
1463 *pdata = file->apple_contents_data;
1464 *plen = file->apple_contents_size;
1465 file->apple_contents_data =
nullptr;
1466 file->apple_contents_size = 0;
1468 LeaveCriticalSection(&file->apple_lock);
1473cliprdr_file_context_apple_contents_response(CliprdrClientContext* cliprdr_context,
1476 WINPR_ASSERT(cliprdr_context);
1477 WINPR_ASSERT(response);
1479 CliprdrFileContext* file = cliprdr_context->custom;
1482 EnterCriticalSection(&file->apple_lock);
1483 if (response->streamId != file->apple_stream_id)
1486 WLog_Print(file->log, WLOG_WARN,
1487 "file contents response for stale stream %" PRIu32
", ignoring",
1488 response->streamId);
1489 LeaveCriticalSection(&file->apple_lock);
1490 return CHANNEL_RC_OK;
1493 BOOL success = (response->common.msgFlags & CB_RESPONSE_OK) &&
1494 !(response->common.msgFlags & CB_RESPONSE_FAIL);
1495 if (success && response->requestedData && (response->cbRequested > 0))
1497 file->apple_contents_data = malloc(response->cbRequested);
1498 if (file->apple_contents_data)
1500 memcpy(file->apple_contents_data, response->requestedData, response->cbRequested);
1501 file->apple_contents_size = response->cbRequested;
1506 file->apple_contents_success = success;
1507 (void)SetEvent(file->apple_contents_event);
1508 LeaveCriticalSection(&file->apple_lock);
1509 return CHANNEL_RC_OK;
1512static BOOL apple_download_file(CliprdrFileContext* file, UINT32 listIndex,
1516 WINPR_ASSERT(descriptor);
1520 if (descriptor->dwFlags & FD_FILESIZE)
1521 size = ((UINT64)descriptor->nFileSizeHigh << 32) | descriptor->nFileSizeLow;
1524 BYTE* data =
nullptr;
1526 const BOOL rc = apple_request_file_contents(file, listIndex, FILECONTENTS_SIZE, 0,
1527 sizeof(UINT64), &data, &len);
1528 if (rc && (len >=
sizeof(UINT64)))
1529 memcpy(&size, data,
sizeof(size));
1531 if (!rc || (len <
sizeof(UINT64)))
1535 FILE* fp = winpr_fopen(path,
"wb");
1540 const UINT32 chunk = 1024 * 1024;
1541 for (UINT64 offset = 0; offset < size;)
1543 const UINT32 requested = (size - offset < chunk) ? (UINT32)(size - offset) : chunk;
1544 BYTE* data =
nullptr;
1546 if (!apple_request_file_contents(file, listIndex, FILECONTENTS_RANGE, offset, requested,
1548 (len == 0) || (fwrite(data, 1, len, fp) != len))
1558 if (fclose(fp) != 0)
1561 (void)winpr_DeleteFile(path);
1566static BOOL apple_download_entry(CliprdrFileContext* file, UINT32 listIndex,
const char* name)
1571 char* relative = apple_local_relative_path(name);
1574 WLog_Print(file->log, WLOG_WARN,
"skipping invalid server file name %s", name);
1580 char* path =
nullptr;
1582 if (winpr_asprintf(&path, &pathlen,
"%s/%s", file->apple_download_dir, relative) < 0)
1585 if ((descriptor->dwFlags & FD_ATTRIBUTES) &&
1586 (descriptor->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1587 rc = winpr_PathMakePath(path,
nullptr);
1590 char* slash = strrchr(path,
'/');
1591 WINPR_ASSERT(slash);
1593 const BOOL parent = winpr_PathMakePath(path,
nullptr);
1597 rc = apple_download_file(file, listIndex, descriptor, path);
1599 WLog_Print(file->log, WLOG_ERROR,
"failed to download %s", name);
1610static char* apple_provide_file(
void* userdata,
size_t index)
1612 CliprdrFileContext* file = userdata;
1615 if (!file->context || (index >= file->apple_top_level_count))
1618 char* top = apple_descriptor_name(&file->apple_files[file->apple_top_level[index]]);
1621 const size_t toplen = strlen(top);
1623 char* result =
nullptr;
1624 for (UINT32 x = 0; x < file->apple_files_count; x++)
1626 char* name = apple_descriptor_name(&file->apple_files[x]);
1631 (strncmp(name, top, toplen) == 0) && ((name[toplen] ==
'\0') || (name[toplen] ==
'\\'));
1632 const BOOL rc = !below || apple_download_entry(file, x, name);
1640 if (winpr_asprintf(&result, &len,
"%s/%s", file->apple_download_dir, top) < 0)
1649static BOOL apple_set_server_files(CliprdrFileContext* file,
const void* data,
size_t size)
1653 if (size > UINT32_MAX)
1658 if (cliprdr_parse_file_list(data, (UINT32)size, &files, &count) != 0)
1661 size_t* top_level = (count > 0) ? calloc(count,
sizeof(
size_t)) : nullptr;
1662 size_t top_level_count = 0;
1663 for (UINT32 x = 0; top_level && (x < count); x++)
1665 char* name = apple_descriptor_name(&files[x]);
1666 if (name && (name[0] !=
'\0') && !strchr(name,
'\\'))
1667 top_level[top_level_count++] = x;
1671 char* dir = (top_level_count > 0) ? cliprdr_file_apple_new_download_dir() :
nullptr;
1679 apple_clear_server_files(file);
1680 file->apple_files = files;
1681 file->apple_files_count = count;
1682 file->apple_top_level = top_level;
1683 file->apple_top_level_count = top_level_count;
1684 file->apple_download_dir = dir;
1689static UINT cliprdr_file_context_send_file_contents_failure(
1695 WINPR_ASSERT(fileContentsRequest);
1697 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1698 ((UINT64)fileContentsRequest->nPositionLow);
1699 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1700 "server file contents request [lockID %" PRIu32
", streamID %" PRIu32
1701 ", index %" PRIu32
"] offset %" PRIu64
", size %" PRIu32
" failed",
1702 fileContentsRequest->clipDataId, fileContentsRequest->streamId,
1703 fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
1705 response.common.msgFlags = CB_RESPONSE_FAIL;
1706 response.streamId = fileContentsRequest->streamId;
1708 WINPR_ASSERT(file->context);
1709 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1710 return file->context->ClientFileContentsResponse(file->context, &response);
1714cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
1716 const void* data,
size_t size)
1718 if (size > UINT32_MAX)
1719 return ERROR_INVALID_PARAMETER;
1722 .requestedData = data,
1723 .cbRequested = (UINT32)size,
1724 .common.msgFlags = CB_RESPONSE_OK };
1726 WINPR_ASSERT(request);
1729 WLog_Print(file->log, WLOG_DEBUG,
"send contents response streamID=%" PRIu32
", size=%" PRIu32,
1730 response.streamId, response.cbRequested);
1731 WINPR_ASSERT(file->context);
1732 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1733 return file->context->ClientFileContentsResponse(file->context, &response);
1736static BOOL dump_streams(
const void* key,
void* value, WINPR_ATTR_UNUSED
void* arg)
1738 const UINT32* ukey = key;
1739 CliprdrLocalStream* cur = value;
1741 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1742 "[key %" PRIu32
"] lockID %" PRIu32
", count %" PRIuz
", locked %d", *ukey,
1743 cur->lockId, cur->count, cur->locked);
1744 for (
size_t x = 0; x < cur->count; x++)
1746 const CliprdrLocalFile* file = &cur->files[x];
1747 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1748 "file [%" PRIuz
"] %s: %" PRId64, x, file->name, file->size);
1753static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
1758 CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
1761 if (listIndex < cur->count)
1763 CliprdrLocalFile* f = &cur->files[listIndex];
1768 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1769 "invalid entry index for lockID %" PRIu32
", index %" PRIu32
" [count %" PRIuz
1771 lockId, listIndex, cur->count, cur->locked);
1776 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1777 "missing entry for lockID %" PRIu32
", index %" PRIu32, lockId, listIndex);
1778 if (!HashTable_Foreach(file->local_streams, dump_streams, file))
1779 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1780 "HashTable_Foreach failed");
1786static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
1788 CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
1793 const char* name = f->name;
1794 f->fp = winpr_fopen(name,
"rb");
1798 char ebuffer[256] = WINPR_C_ARRAY_INIT;
1799 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1800 "[lockID %" PRIu32
", index %" PRIu32
1801 "] failed to open file '%s' [size %" PRId64
"] %s [%d]",
1802 lockId, listIndex, f->name, f->size,
1803 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)), errno);
1811static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
1818 WINPR_ASSERT(file->context);
1819 WLog_Print(file->context->log, WLOG_DEBUG,
"closing file %s after error %" PRIu32,
1822 else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
1824 WINPR_ASSERT(file->context);
1825 WLog_Print(file->context->log, WLOG_DEBUG,
"closing file %s after read", file->name);
1833 (void)fclose(file->fp);
1837static UINT cliprdr_file_context_server_file_size_request(
1840 WINPR_ASSERT(fileContentsRequest);
1842 if (fileContentsRequest->cbRequested !=
sizeof(UINT64))
1844 WLog_Print(file->log, WLOG_WARN,
"unexpected FILECONTENTS_SIZE request: %" PRIu32
" bytes",
1845 fileContentsRequest->cbRequested);
1848 HashTable_Lock(file->local_streams);
1850 UINT res = CHANNEL_RC_OK;
1851 CliprdrLocalFile* rfile =
1852 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1854 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1857 if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
1858 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1861 const INT64 size = _ftelli64(rfile->fp);
1863 cliprdr_local_file_try_close(rfile, res, 0, 0);
1865 res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
1870 HashTable_Unlock(file->local_streams);
1874static UINT cliprdr_file_context_server_file_range_request(
1877 BYTE* data =
nullptr;
1879 WINPR_ASSERT(fileContentsRequest);
1881 HashTable_Lock(file->local_streams);
1882 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1883 ((UINT64)fileContentsRequest->nPositionLow);
1885 CliprdrLocalFile* rfile =
1886 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1890 if (_fseeki64(rfile->fp, WINPR_ASSERTING_INT_CAST(int64_t, offset), SEEK_SET) < 0)
1893 data = malloc(fileContentsRequest->cbRequested);
1898 const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
1900 cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
1903 cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
1904 HashTable_Unlock(file->local_streams);
1909 cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
1910 fileContentsRequest->cbRequested);
1912 HashTable_Unlock(file->local_streams);
1913 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1916static void cliprdr_local_stream_free(
void* obj);
1918static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
1920 UINT rc = CHANNEL_RC_OK;
1924 HashTable_Lock(file->local_streams);
1927 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
1928 if (lock && !stream)
1930 stream = cliprdr_local_stream_new(file, lockId,
nullptr, 0);
1931 if (!HashTable_Insert(file->local_streams, &lockId, stream))
1933 rc = ERROR_INTERNAL_ERROR;
1934 cliprdr_local_stream_free(stream);
1937 file->local_lock_id = lockId;
1941 stream->locked = lock;
1942 stream->lockId = lockId;
1949 if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
1950 rc = ERROR_INTERNAL_ERROR;
1953 HashTable_Unlock(file->local_streams);
1957static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
1960 WINPR_ASSERT(context);
1961 WINPR_ASSERT(lockClipboardData);
1962 CliprdrFileContext* file = (context->custom);
1963 return change_lock(file, lockClipboardData->clipDataId, TRUE);
1966static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
1969 WINPR_ASSERT(context);
1970 WINPR_ASSERT(unlockClipboardData);
1971 CliprdrFileContext* file = (context->custom);
1972 return change_lock(file, unlockClipboardData->clipDataId, FALSE);
1975static UINT cliprdr_file_context_server_file_contents_request(
1978 UINT error = NO_ERROR;
1980 WINPR_ASSERT(context);
1981 WINPR_ASSERT(fileContentsRequest);
1983 CliprdrFileContext* file = (context->custom);
1990 if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
1991 (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
1993 WLog_Print(file->log, WLOG_ERROR,
"invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
1994 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1997 if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
1998 error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
2000 if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
2001 error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
2005 WLog_Print(file->log, WLOG_ERROR,
"failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
2007 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
2010 return CHANNEL_RC_OK;
2013BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
2016 WINPR_ASSERT(cliprdr);
2018 cliprdr->custom = file;
2019 file->context = cliprdr;
2021 cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
2022 cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
2023 cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
2024#if defined(WITH_FUSE)
2025 cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
2027 CliprdrFuseFile* root_dir = fuse_file_new_root(file);
2028 return root_dir !=
nullptr;
2029#elif defined(WITH_CLIPRDR_FILE_APPLE)
2030 cliprdr->ServerFileContentsResponse = cliprdr_file_context_apple_contents_response;
2037#if defined(WITH_FUSE)
2039static bool clear_all_selections(CliprdrFileContext* file_context)
2041 WINPR_ASSERT(file_context);
2042 WINPR_ASSERT(file_context->inode_table);
2044 HashTable_Lock(file_context->inode_table);
2045 const bool rc = clear_selection(file_context, TRUE,
nullptr);
2047 HashTable_Clear(file_context->clip_data_table);
2048 HashTable_Unlock(file_context->inode_table);
2053BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
2060#if defined(WITH_FUSE)
2061 if (file->inode_table)
2063 if (!clear_no_cdi_entry(file))
2065 if (!clear_all_selections(file))
2069#if defined(WITH_CLIPRDR_FILE_APPLE)
2071 cliprdr_file_apple_detach();
2074 HashTable_Clear(file->local_streams);
2076 file->context =
nullptr;
2081#if defined(WITH_FUSE) || defined(WITH_CLIPRDR_FILE_APPLE)
2082 cliprdr->ServerFileContentsResponse =
nullptr;
2089static BOOL cliprdr_file_content_changed_and_update(
void* ihash,
size_t hsize,
const void* data,
2093 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
2095 if (hsize <
sizeof(hash))
2098 if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash,
sizeof(hash)))
2101 const BOOL changed = memcmp(hash, ihash,
sizeof(hash)) != 0;
2103 memcpy(ihash, hash,
sizeof(hash));
2107static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
2108 const void* data,
size_t size)
2111 return cliprdr_file_content_changed_and_update(file->client_data_hash,
2112 sizeof(file->client_data_hash), data, size);
2115#if defined(WITH_FUSE)
2116static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
2120 WINPR_ASSERT(file_context);
2122 ino = file_context->next_ino;
2123 while (ino == 0 || ino == FUSE_ROOT_ID ||
2124 HashTable_GetItemValue(file_context->inode_table, (
void*)(uintptr_t)ino))
2127 file_context->next_ino = ino + 1;
2132static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
2133 UINT32 clip_data_id)
2135 WINPR_ASSERT(file_context);
2137 UINT64 data_id = clip_data_id;
2138 if (!has_clip_data_id)
2139 data_id = NO_CLIP_DATA_ID;
2141 CliprdrFuseFile* clip_data_dir = fuse_file_new(
"/%" PRIu64, data_id);
2145 clip_data_dir->ino = get_next_free_inode(file_context);
2146 clip_data_dir->is_directory = TRUE;
2147 clip_data_dir->is_readonly = TRUE;
2148 clip_data_dir->has_clip_data_id = has_clip_data_id;
2149 clip_data_dir->clip_data_id = clip_data_id;
2151 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
2154 WLog_Print(file_context->log, WLOG_ERROR,
"FUSE root directory missing");
2155 fuse_file_free(clip_data_dir);
2158 if (!ArrayList_Append(root_dir->children, clip_data_dir))
2160 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to append FUSE file");
2161 fuse_file_free(clip_data_dir);
2164 clip_data_dir->parent = root_dir;
2166 if (!HashTable_Insert(file_context->inode_table, (
void*)(uintptr_t)clip_data_dir->ino,
2169 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to insert inode into inode table");
2170 ArrayList_Remove(root_dir->children, clip_data_dir);
2171 fuse_file_free(clip_data_dir);
2175 if (!HashTable_Insert(file_context->dir_table, clip_data_dir->filename_with_root,
2178 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to insert inode into dir table");
2179 ArrayList_Remove(root_dir->children, clip_data_dir);
2180 fuse_file_free(clip_data_dir);
2184 return clip_data_dir;
2187static char* get_parent_path(
const char* filepath)
2189 const char* base = strrchr(filepath,
'/');
2192 while ((base > filepath) && (*base ==
'/'))
2195 WINPR_ASSERT(base >= filepath);
2196 const size_t parent_path_length = 1ULL + (size_t)(base - filepath);
2197 char* parent_path = calloc(parent_path_length + 1,
sizeof(
char));
2201 memcpy(parent_path, filepath, parent_path_length);
2206static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context,
const char* path)
2209 WINPR_ASSERT(file_context);
2212 char* parent_path = get_parent_path(path);
2216 CliprdrFuseFile* parent = HashTable_GetItemValue(file_context->dir_table, parent_path);
2222static BOOL selection_handle_file(CliprdrFileContext* file_context,
2223 CliprdrFuseClipDataEntry* clip_data_entry, uint32_t index,
2227 WINPR_ASSERT(file_context);
2228 WINPR_ASSERT(clip_data_entry);
2231 CliprdrFuseFile* clip_data_dir = clip_data_entry->clip_data_dir;
2232 WINPR_ASSERT(clip_data_dir);
2234 char filename[ARRAYSIZE(file->cFileName) * 8] = WINPR_C_ARRAY_INIT;
2235 const SSIZE_T filenamelen = ConvertWCharNToUtf8(file->cFileName, ARRAYSIZE(file->cFileName),
2236 filename, ARRAYSIZE(filename) - 1);
2237 if (filenamelen < 0)
2239 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to convert filename");
2243 for (
size_t j = 0; filename[j]; ++j)
2245 if (filename[j] ==
'\\')
2250 CliprdrFuseFile* fuse_file = fuse_file_new(
2251 "%.*s/%s", WINPR_ASSERTING_INT_CAST(
int, clip_data_dir->filename_with_root_len),
2252 clip_data_dir->filename_with_root, filename);
2255 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to create FUSE file");
2259 fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
2260 if (!fuse_file->parent)
2262 WLog_Print(file_context->log, WLOG_ERROR,
"Found no parent for FUSE file");
2266 if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
2268 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to append FUSE file");
2272 fuse_file->list_idx = index;
2273 fuse_file->ino = get_next_free_inode(file_context);
2274 fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
2275 fuse_file->clip_data_id = clip_data_entry->clip_data_id;
2276 if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2277 fuse_file->is_directory = TRUE;
2278 if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
2279 fuse_file->is_readonly = TRUE;
2280 if (file->dwFlags & FD_FILESIZE)
2282 fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
2283 fuse_file->has_size = TRUE;
2285 if (file->dwFlags & FD_WRITESTIME)
2289 filetime = file->ftLastWriteTime.dwHighDateTime;
2291 filetime += file->ftLastWriteTime.dwLowDateTime;
2293 fuse_file->last_write_time_unix =
2294 1LL * filetime / (10LL * 1000LL * 1000LL) - WIN32_FILETIME_TO_UNIX_EPOCH;
2295 fuse_file->has_last_write_time = TRUE;
2298 if (!HashTable_Insert(file_context->inode_table, (
void*)(uintptr_t)fuse_file->ino, fuse_file))
2300 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to insert inode into inode table");
2304 if (fuse_file->is_directory)
2307 if (!HashTable_Insert(file_context->dir_table, fuse_file->filename_with_root, fuse_file))
2309 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to insert inode into dir table");
2319 fuse_file_free(fuse_file);
2320 return clear_entry_selection(clip_data_entry);
2326static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
2327 CliprdrFuseClipDataEntry* clip_data_entry,
2330 WINPR_ASSERT(file_context);
2331 WINPR_ASSERT(clip_data_entry);
2332 WINPR_ASSERT(files);
2334 if (clip_data_entry->has_clip_data_id)
2335 WLog_Print(file_context->log, WLOG_DEBUG,
"Setting selection for clipDataId %u",
2336 clip_data_entry->clip_data_id);
2338 WLog_Print(file_context->log, WLOG_DEBUG,
"Setting selection");
2340 for (UINT32 i = 0; i < n_files; ++i)
2343 if (!selection_handle_file(file_context, clip_data_entry, i, file))
2347 if (clip_data_entry->has_clip_data_id)
2348 WLog_Print(file_context->log, WLOG_DEBUG,
"Selection set for clipDataId %u",
2349 clip_data_entry->clip_data_id);
2351 WLog_Print(file_context->log, WLOG_DEBUG,
"Selection set");
2356static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
2357 CliprdrFuseClipDataEntry* clip_data_entry)
2359 wClipboardDelegate* delegate =
nullptr;
2360 CliprdrFuseFile* clip_data_dir =
nullptr;
2362 WINPR_ASSERT(file_context);
2364 WINPR_ASSERT(clip_data_entry);
2366 delegate = ClipboardGetDelegate(clip);
2367 WINPR_ASSERT(delegate);
2369 clip_data_dir = clip_data_entry->clip_data_dir;
2370 WINPR_ASSERT(clip_data_dir);
2372 free(file_context->exposed_path);
2373 file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
2374 if (file_context->exposed_path)
2375 WLog_Print(file_context->log, WLOG_DEBUG,
"Updated exposed path to \"%s\"",
2376 file_context->exposed_path);
2378 delegate->basePath = file_context->exposed_path;
2380 return delegate->basePath !=
nullptr;
2384BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
2385 const void* data,
size_t size)
2387#if defined(WITH_FUSE)
2388 CliprdrFuseClipDataEntry* clip_data_entry =
nullptr;
2393 WINPR_ASSERT(file_context);
2395 if (size > UINT32_MAX)
2398 if (cliprdr_parse_file_list(data, (UINT32)size, &files, &n_files))
2400 WLog_Print(file_context->log, WLOG_ERROR,
"Failed to parse file list");
2404 HashTable_Lock(file_context->inode_table);
2405 HashTable_Lock(file_context->clip_data_table);
2406 if (does_server_support_clipdata_locking(file_context))
2407 clip_data_entry = HashTable_GetItemValue(
2408 file_context->clip_data_table, (
void*)(uintptr_t)file_context->current_clip_data_id);
2410 clip_data_entry = file_context->clip_data_entry_without_id;
2412 WINPR_ASSERT(clip_data_entry);
2414 if (!clear_entry_selection(clip_data_entry))
2416 WINPR_ASSERT(!clip_data_entry->clip_data_dir);
2418 clip_data_entry->clip_data_dir =
2419 clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
2420 file_context->current_clip_data_id);
2421 if (!clip_data_entry->clip_data_dir)
2423 if (!update_exposed_path(file_context, clip, clip_data_entry))
2425 if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
2431 HashTable_Unlock(file_context->clip_data_table);
2432 HashTable_Unlock(file_context->inode_table);
2440void* cliprdr_file_context_get_context(CliprdrFileContext* file)
2443 return file->clipboard;
2446void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
2451#if defined(WITH_FUSE)
2452 WINPR_ASSERT(file->fuse_stop_sync);
2454 WLog_Print(file->log, WLOG_DEBUG,
"Setting FUSE exit flag");
2455 if (file->fuse_sess)
2456 fuse_session_exit(file->fuse_sess);
2460 WLog_Print(file->log, WLOG_DEBUG,
"Setting FUSE stop event");
2461 (void)SetEvent(file->fuse_stop_sync);
2467#if defined(WITH_FUSE)
2468 WLog_Print(file->log, WLOG_DEBUG,
"Forcing FUSE to check exit flag");
2470 if (winpr_PathFileExists(file->path))
2471 WLog_Print(file->log, WLOG_WARN,
"winpr_PathFileExists(%s) failed", file->path);
2474void cliprdr_file_context_free(CliprdrFileContext* file)
2479#if defined(WITH_FUSE)
2480 if (file->fuse_thread)
2482 WINPR_ASSERT(file->fuse_stop_sync);
2484 WLog_Print(file->log, WLOG_DEBUG,
"Stopping FUSE thread");
2485 cliprdr_file_session_terminate(file, TRUE);
2487 WLog_Print(file->log, WLOG_DEBUG,
"Waiting on FUSE thread");
2488 (void)WaitForSingleObject(file->fuse_thread, INFINITE);
2489 (void)CloseHandle(file->fuse_thread);
2491 if (file->fuse_stop_sync)
2492 (void)CloseHandle(file->fuse_stop_sync);
2493 if (file->fuse_start_sync)
2494 (void)CloseHandle(file->fuse_start_sync);
2496 HashTable_Free(file->request_table);
2497 HashTable_Free(file->clip_data_table);
2498 HashTable_Free(file->dir_table);
2499 HashTable_Free(file->inode_table);
2502#if defined(WITH_CLIPRDR_FILE_APPLE)
2503 apple_clear_server_files(file);
2504 free(file->apple_contents_data);
2505 if (file->apple_contents_event)
2507 (void)CloseHandle(file->apple_contents_event);
2508 DeleteCriticalSection(&file->apple_lock);
2511 HashTable_Free(file->local_streams);
2512 winpr_RemoveDirectory(file->path);
2514 free(file->exposed_path);
2518static BOOL create_base_path(CliprdrFileContext* file)
2522 char base[64] = WINPR_C_ARRAY_INIT;
2523 (void)_snprintf(base,
sizeof(base),
"com.freerdp.client.cliprdr.%" PRIu32,
2524 GetCurrentProcessId());
2526 file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
2530 if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path,
nullptr))
2532 WLog_Print(file->log, WLOG_ERROR,
"Failed to create directory '%s'", file->path);
2538static void cliprdr_local_file_free(CliprdrLocalFile* file)
2540 const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT;
2545 WLog_Print(file->context->log, WLOG_DEBUG,
"closing file %s, discarding entry", file->name);
2546 (void)fclose(file->fp);
2552static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
2555 const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT;
2557 WINPR_ASSERT(context);
2561 f->context = context;
2562 f->name = winpr_str_url_decode(path, strlen(path));
2568 cliprdr_local_file_free(f);
2572static void cliprdr_local_files_free(CliprdrLocalStream* stream)
2574 WINPR_ASSERT(stream);
2576 for (
size_t x = 0; x < stream->count; x++)
2577 cliprdr_local_file_free(&stream->files[x]);
2578 free(stream->files);
2580 stream->files =
nullptr;
2584static void cliprdr_local_stream_free(
void* obj)
2586 CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
2588 cliprdr_local_files_free(stream);
2593static BOOL append_entry(CliprdrLocalStream* stream,
const char* path)
2595 CliprdrLocalFile* tmp = realloc(stream->files,
sizeof(CliprdrLocalFile) * (stream->count + 1));
2598 stream->files = tmp;
2599 CliprdrLocalFile* f = &stream->files[stream->count++];
2601 return cliprdr_local_file_new(stream->context, f, path);
2604static BOOL is_directory(
const char* path)
2606 WCHAR* wpath = ConvertUtf8ToWCharAlloc(path,
nullptr);
2610 HANDLE hFile = CreateFileW(wpath, 0, FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING,
2611 FILE_ATTRIBUTE_NORMAL,
nullptr);
2614 if (hFile == INVALID_HANDLE_VALUE)
2618 const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
2619 (void)CloseHandle(hFile);
2623 return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
2626static BOOL add_directory(CliprdrLocalStream* stream,
const char* path)
2628 char* wildcardpath = GetCombinedPath(path,
"*");
2631 WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath,
nullptr);
2637 HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
2640 if (hFind == INVALID_HANDLE_VALUE)
2644 char* next =
nullptr;
2646 WCHAR dotbuffer[6] = WINPR_C_ARRAY_INIT;
2647 WCHAR dotdotbuffer[6] = WINPR_C_ARRAY_INIT;
2648 const WCHAR* dot = InitializeConstWCharFromUtf8(
".", dotbuffer, ARRAYSIZE(dotbuffer));
2649 const WCHAR* dotdot = InitializeConstWCharFromUtf8(
"..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
2652 if (_wcscmp(FindFileData.cFileName, dot) == 0)
2654 if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
2657 char cFileName[MAX_PATH] = WINPR_C_ARRAY_INIT;
2658 (void)ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName),
2659 cFileName, ARRAYSIZE(cFileName));
2662 next = GetCombinedPath(path, cFileName);
2666 if (!append_entry(stream, next))
2668 if (is_directory(next))
2670 if (!add_directory(stream, next))
2673 }
while (FindNextFileW(hFind, &FindFileData));
2683static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream,
const char* data,
size_t size)
2686 WINPR_ASSERT(stream);
2690 cliprdr_local_files_free(stream);
2692 stream->files = calloc(size,
sizeof(CliprdrLocalFile));
2696 char* copy = strndup(data, size);
2700 char* dname =
nullptr;
2701 char* saveptr =
nullptr;
2702 char* ptr = strtok_s(copy,
"\r\n", &saveptr);
2705 const char* cname = ptr;
2706 if (strncmp(
"file:///", ptr, 8) == 0)
2708 else if (strncmp(
"file:/", ptr, 6) == 0)
2711 const size_t nlen = strnlen(cname, size);
2713 dname = winpr_str_url_decode(cname, nlen);
2716 if (!append_entry(stream, dname))
2719 if (is_directory(dname))
2721 const BOOL res = add_directory(stream, dname);
2725 ptr = strtok_s(
nullptr,
"\r\n", &saveptr);
2735CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
2736 const char* data,
size_t size)
2738 WINPR_ASSERT(context);
2739 CliprdrLocalStream* stream = calloc(1,
sizeof(CliprdrLocalStream));
2743 stream->context = context;
2744 if (!cliprdr_local_stream_update(stream, data, size))
2747 stream->lockId = streamID;
2751 cliprdr_local_stream_free(stream);
2755static UINT32 UINTPointerHash(
const void*
id)
2758 return *((
const UINT32*)
id);
2761static BOOL UINTPointerCompare(
const void* pointer1,
const void* pointer2)
2763 if (!pointer1 || !pointer2)
2764 return pointer1 == pointer2;
2766 const UINT32* a = pointer1;
2767 const UINT32* b = pointer2;
2771static void* UINTPointerClone(
const void* other)
2773 const UINT32* src = other;
2777 UINT32* copy = calloc(1,
sizeof(UINT32));
2785#if defined(WITH_FUSE)
2786CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
2788 CliprdrFuseFile* root_dir = fuse_file_new(
"/");
2792 root_dir->ino = FUSE_ROOT_ID;
2793 root_dir->is_directory = TRUE;
2794 root_dir->is_readonly = TRUE;
2796 if (!HashTable_Insert(file_context->inode_table, (
void*)(uintptr_t)root_dir->ino, root_dir))
2798 fuse_file_free(root_dir);
2802 if (!HashTable_Insert(file_context->dir_table, root_dir->filename_with_root, root_dir))
2804 fuse_file_free(root_dir);
2812CliprdrFileContext* cliprdr_file_context_new(
void* context)
2814 CliprdrFileContext* file = calloc(1,
sizeof(CliprdrFileContext));
2818 file->log = WLog_Get(CLIENT_TAG(
"common.cliprdr.file"));
2819 file->clipboard = context;
2821 file->local_streams = HashTable_New(FALSE);
2822 if (!file->local_streams)
2825 if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
2829 wObject* hkobj = HashTable_KeyObject(file->local_streams);
2830 WINPR_ASSERT(hkobj);
2837 wObject* hobj = HashTable_ValueObject(file->local_streams);
2842#if defined(WITH_FUSE)
2843 file->inode_table = HashTable_New(FALSE);
2844 file->dir_table = HashTable_New(FALSE);
2845 file->clip_data_table = HashTable_New(FALSE);
2846 file->request_table = HashTable_New(FALSE);
2847 if (!file->inode_table || !file->dir_table || !file->clip_data_table || !file->request_table)
2851 wObject* ctobj = HashTable_ValueObject(file->request_table);
2852 WINPR_ASSERT(ctobj);
2856 wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
2857 WINPR_ASSERT(ctobj);
2861 if (!HashTable_SetHashFunction(file->dir_table, HashTable_StringHash))
2863 wObject* kobj = HashTable_KeyObject(file->dir_table);
2869 if (!create_base_path(file))
2872#if defined(WITH_CLIPRDR_FILE_APPLE)
2873 if (!InitializeCriticalSectionAndSpinCount(&file->apple_lock, 4000))
2875 if (!(file->apple_contents_event = CreateEvent(
nullptr, TRUE, FALSE,
nullptr)))
2877 DeleteCriticalSection(&file->apple_lock);
2882#if defined(WITH_FUSE)
2883 if (!(file->fuse_start_sync = CreateEvent(
nullptr, TRUE, FALSE,
nullptr)))
2885 if (!(file->fuse_stop_sync = CreateEvent(
nullptr, TRUE, FALSE,
nullptr)))
2887 if (!(file->fuse_thread = CreateThread(
nullptr, 0, cliprdr_file_fuse_thread, file, 0,
nullptr)))
2890 if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
2891 WLog_Print(file->log, WLOG_ERROR,
"Failed to wait for start sync");
2896 WINPR_PRAGMA_DIAG_PUSH
2897 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
2898 cliprdr_file_context_free(file);
2899 WINPR_PRAGMA_DIAG_POP
2903BOOL local_stream_discard(
const void* key,
void* value,
void* arg)
2905 CliprdrFileContext* file = arg;
2906 CliprdrLocalStream* stream = value;
2908 WINPR_ASSERT(stream);
2910 if (!stream->locked)
2911 HashTable_Remove(file->local_streams, key);
2915BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
2919 WLog_Print(file->log, WLOG_DEBUG,
"clear file clipboard...");
2921 HashTable_Lock(file->local_streams);
2922 const BOOL res = HashTable_Foreach(file->local_streams, local_stream_discard, file);
2923 HashTable_Unlock(file->local_streams);
2925 memset(file->server_data_hash, 0,
sizeof(file->server_data_hash));
2926 memset(file->client_data_hash, 0,
sizeof(file->client_data_hash));
2930BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file,
const char* data,
2936 if (!cliprdr_file_client_content_changed_and_update(file, data, size))
2939 if (!cliprdr_file_context_clear(file))
2942 UINT32 lockId = file->local_lock_id;
2944 HashTable_Lock(file->local_streams);
2945 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
2947 WLog_Print(file->log, WLOG_DEBUG,
"update client file list (stream=%p)...", (
void*)stream);
2949 rc = cliprdr_local_stream_update(stream, data, size);
2952 stream = cliprdr_local_stream_new(file, lockId, data, size);
2953 rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
2955 cliprdr_local_stream_free(stream);
2959 HashTable_Unlock(file->local_streams);
2963UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
2967 if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
2970 if (!file->file_formats_registered)
2973 return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
2974 CB_HUGE_FILE_SUPPORT_ENABLED;
2977BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
2980 file->file_formats_registered = available;
2984BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
2987 file->file_capability_flags = flags;
2991UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
2994 return file->file_capability_flags;
2997BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
3001#if defined(WITH_FUSE)
3008BOOL cliprdr_file_context_set_local_clipboard_data(CliprdrFileContext* file,
3009 cliprdr_file_context_data_fkt fkt,
3010 void* userdata,
const char** mime_types,
3015 WINPR_ASSERT(mime_types || (count == 0));
3017#if defined(WITH_CLIPRDR_FILE_APPLE)
3022 for (
size_t x = 0; x < count; x++)
3024 if (mime_types[x] && (strcmp(mime_types[x],
"text/uri-list") == 0))
3032 const void* data = fkt(userdata,
"FileGroupDescriptorW", &size);
3033 if (!data || !apple_set_server_files(file, data, size))
3036 WLog_Print(file->log, WLOG_DEBUG,
"offering %" PRIuz
" server files (%" PRIu32
" entries)",
3037 file->apple_top_level_count, file->apple_files_count);
3038 return cliprdr_file_apple_offer(file->apple_top_level_count, apple_provide_file, file,
3039 mime_types, count, fkt, userdata);
3043 WINPR_UNUSED(userdata);
3044 WINPR_UNUSED(mime_types);
3045 WINPR_UNUSED(count);
3050char* cliprdr_file_context_get_local_uri_list(CliprdrFileContext* file,
size_t* size)
3056#if defined(WITH_CLIPRDR_FILE_APPLE)
3057 return cliprdr_file_apple_get_uri_list(size);
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
This struct contains function pointer to initialize/free objects.
OBJECT_FREE_FN fnObjectFree
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
WINPR_ATTR_NODISCARD OBJECT_NEW_FN fnObjectNew