FreeRDP
Loading...
Searching...
No Matches
client_cliprdr_file.c
1
24#include <freerdp/config.h>
25
26#include <stdlib.h>
27#include <errno.h>
28
29#ifdef WITH_FUSE
30#define FUSE_USE_VERSION 30
31#include <fuse_lowlevel.h>
32#endif
33
34#if defined(WITH_FUSE)
35#include <sys/mount.h>
36#include <sys/stat.h>
37#include <errno.h>
38#include <time.h>
39#endif
40
41#include <winpr/crt.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>
48
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>
54
55#include <freerdp/client/client_cliprdr_file.h>
56
57#define MAX_CLIP_DATA_DIR_LEN 10
58#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
59#define WIN32_FILETIME_TO_UNIX_EPOCH INT64_C(11644473600)
60
61#ifdef WITH_DEBUG_CLIPRDR
62#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
63#else
64#define DEBUG_CLIPRDR(log, ...) \
65 do \
66 { \
67 } while (0)
68#endif
69
70#if defined(WITH_FUSE)
71typedef enum eFuseLowlevelOperationType
72{
73 FUSE_LL_OPERATION_NONE,
74 FUSE_LL_OPERATION_LOOKUP,
75 FUSE_LL_OPERATION_GETATTR,
76 FUSE_LL_OPERATION_READ,
77} FuseLowlevelOperationType;
78
79typedef struct sCliprdrFuseFile CliprdrFuseFile;
80
81struct sCliprdrFuseFile
82{
83 CliprdrFuseFile* parent;
84 wArrayList* children;
85
86 char* filename;
87 char* filename_with_root;
88 UINT32 list_idx;
89 fuse_ino_t ino;
90
91 BOOL is_directory;
92 BOOL is_readonly;
93
94 BOOL has_size;
95 UINT64 size;
96
97 BOOL has_last_write_time;
98 INT64 last_write_time_unix;
99
100 BOOL has_clip_data_id;
101 UINT32 clip_data_id;
102};
103
104typedef struct
105{
106 CliprdrFileContext* file_context;
107
108 CliprdrFuseFile* clip_data_dir;
109
110 BOOL has_clip_data_id;
111 UINT32 clip_data_id;
112} CliprdrFuseClipDataEntry;
113
114typedef struct
115{
116 CliprdrFileContext* file_context;
117
118 wArrayList* fuse_files;
119
120 BOOL all_files;
121 BOOL has_clip_data_id;
122 UINT32 clip_data_id;
123} FuseFileClearContext;
124
125typedef struct
126{
127 FuseLowlevelOperationType operation_type;
128 CliprdrFuseFile* fuse_file;
129 fuse_req_t fuse_req;
130 UINT32 stream_id;
131} CliprdrFuseRequest;
132
133typedef struct
134{
135 CliprdrFuseFile* parent;
136 char* parent_path;
137} CliprdrFuseFindParentContext;
138#endif
139
140typedef struct
141{
142 char* name;
143 FILE* fp;
144 INT64 size;
145 CliprdrFileContext* context;
146} CliprdrLocalFile;
147
148typedef struct
149{
150 UINT32 lockId;
151 BOOL locked;
152 size_t count;
153 CliprdrLocalFile* files;
154 CliprdrFileContext* context;
155} CliprdrLocalStream;
156
157struct cliprdr_file_context
158{
159#if defined(WITH_FUSE)
160 /* FUSE related**/
161 HANDLE fuse_start_sync;
162 HANDLE fuse_stop_sync;
163 HANDLE fuse_thread;
164 struct fuse_session* fuse_sess;
165#if FUSE_USE_VERSION < 30
166 struct fuse_chan* ch;
167#endif
168
169 wHashTable* inode_table;
170 wHashTable* clip_data_table;
171 wHashTable* request_table;
172
173 CliprdrFuseFile* root_dir;
174 CliprdrFuseClipDataEntry* clip_data_entry_without_id;
175 UINT32 current_clip_data_id;
176
177 fuse_ino_t next_ino;
178 UINT32 next_clip_data_id;
179 UINT32 next_stream_id;
180#endif
181
182 /* File clipping */
183 BOOL file_formats_registered;
184 UINT32 file_capability_flags;
185
186 UINT32 local_lock_id;
187
188 wHashTable* local_streams;
189 wLog* log;
190 void* clipboard;
191 CliprdrClientContext* context;
192 char* path;
193 char* exposed_path;
194 BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
195 BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
196};
197
198#if defined(WITH_FUSE)
199static void fuse_file_free(void* data)
200{
201 CliprdrFuseFile* fuse_file = data;
202
203 if (!fuse_file)
204 return;
205
206 ArrayList_Free(fuse_file->children);
207 free(fuse_file->filename_with_root);
208
209 free(fuse_file);
210}
211
212static CliprdrFuseFile* fuse_file_new(void)
213{
214 CliprdrFuseFile* fuse_file = NULL;
215
216 fuse_file = calloc(1, sizeof(CliprdrFuseFile));
217 if (!fuse_file)
218 return NULL;
219
220 fuse_file->children = ArrayList_New(FALSE);
221 if (!fuse_file->children)
222 {
223 free(fuse_file);
224 return NULL;
225 }
226
227 return fuse_file;
228}
229
230static void clip_data_entry_free(void* data)
231{
232 CliprdrFuseClipDataEntry* clip_data_entry = data;
233
234 if (!clip_data_entry)
235 return;
236
237 if (clip_data_entry->has_clip_data_id)
238 {
239 CliprdrFileContext* file_context = clip_data_entry->file_context;
240 CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = { 0 };
241
242 WINPR_ASSERT(file_context);
243
244 unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
245 unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
246
247 file_context->context->ClientUnlockClipboardData(file_context->context,
248 &unlock_clipboard_data);
249 clip_data_entry->has_clip_data_id = FALSE;
250
251 WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u",
252 clip_data_entry->clip_data_id);
253 }
254
255 free(clip_data_entry);
256}
257
258static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
259{
260 WINPR_ASSERT(file_context);
261
262 if (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA)
263 return TRUE;
264
265 return FALSE;
266}
267
268static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
269{
270 UINT32 clip_data_id = 0;
271
272 WINPR_ASSERT(file_context);
273
274 HashTable_Lock(file_context->inode_table);
275 clip_data_id = file_context->next_clip_data_id;
276 while (clip_data_id == 0 ||
277 HashTable_GetItemValue(file_context->clip_data_table, (void*)(uintptr_t)clip_data_id))
278 ++clip_data_id;
279
280 file_context->next_clip_data_id = clip_data_id + 1;
281 HashTable_Unlock(file_context->inode_table);
282
283 return clip_data_id;
284}
285
286static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
287 BOOL needs_clip_data_id)
288{
289 CliprdrFuseClipDataEntry* clip_data_entry = NULL;
290 CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = { 0 };
291
292 WINPR_ASSERT(file_context);
293
294 clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry));
295 if (!clip_data_entry)
296 return NULL;
297
298 clip_data_entry->file_context = file_context;
299 clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
300
301 if (!needs_clip_data_id)
302 return clip_data_entry;
303
304 lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
305 lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
306
307 if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
308 {
309 HashTable_Lock(file_context->inode_table);
310 clip_data_entry_free(clip_data_entry);
311 HashTable_Unlock(file_context->inode_table);
312 return NULL;
313 }
314 clip_data_entry->has_clip_data_id = TRUE;
315
316 WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u",
317 clip_data_entry->clip_data_id);
318
319 return clip_data_entry;
320}
321
322static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
323 BOOL has_clip_data_id, UINT32 clip_data_id)
324{
325 if (all_files)
326 return TRUE;
327
328 if (fuse_file->ino == FUSE_ROOT_ID)
329 return FALSE;
330 if (!fuse_file->has_clip_data_id && !has_clip_data_id)
331 return TRUE;
332 if (fuse_file->has_clip_data_id && has_clip_data_id &&
333 (fuse_file->clip_data_id == clip_data_id))
334 return TRUE;
335
336 return FALSE;
337}
338
339static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg)
340{
341 CliprdrFuseRequest* fuse_request = value;
342 FuseFileClearContext* clear_context = arg;
343 CliprdrFileContext* file_context = clear_context->file_context;
344 CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
345
346 WINPR_ASSERT(file_context);
347
348 if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
349 clear_context->has_clip_data_id, clear_context->clip_data_id))
350 return TRUE;
351
352 DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"",
353 fuse_file->filename_with_root);
354
355 fuse_reply_err(fuse_request->fuse_req, EIO);
356 HashTable_Remove(file_context->request_table, key);
357
358 return TRUE;
359}
360
361static BOOL maybe_steal_inode(const void* key, void* value, void* arg)
362{
363 CliprdrFuseFile* fuse_file = value;
364 FuseFileClearContext* clear_context = arg;
365 CliprdrFileContext* file_context = clear_context->file_context;
366
367 WINPR_ASSERT(file_context);
368
369 if (should_remove_fuse_file(fuse_file, clear_context->all_files,
370 clear_context->has_clip_data_id, clear_context->clip_data_id))
371 {
372 if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
373 WLog_Print(file_context->log, WLOG_ERROR,
374 "Failed to append FUSE file to list for deletion");
375
376 HashTable_Remove(file_context->inode_table, key);
377 }
378
379 return TRUE;
380}
381
382static BOOL notify_delete_child(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
383{
384 CliprdrFuseFile* child = data;
385
386 WINPR_ASSERT(child);
387
388 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
389 CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
390
391 WINPR_ASSERT(file_context);
392 WINPR_ASSERT(parent);
393
394 WINPR_ASSERT(file_context->fuse_sess);
395 fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
396 strlen(child->filename));
397
398 return TRUE;
399}
400
401static BOOL invalidate_inode(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
402{
403 CliprdrFuseFile* fuse_file = data;
404
405 WINPR_ASSERT(fuse_file);
406
407 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
408 WINPR_ASSERT(file_context);
409 WINPR_ASSERT(file_context->fuse_sess);
410
411 ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
412
413 DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino,
414 fuse_file->filename);
415 fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
416 WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino);
417
418 return TRUE;
419}
420
421static void clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
422 CliprdrFuseClipDataEntry* clip_data_entry)
423{
424 FuseFileClearContext clear_context = { 0 };
425 CliprdrFuseFile* root_dir = NULL;
426 CliprdrFuseFile* clip_data_dir = NULL;
427
428 WINPR_ASSERT(file_context);
429
430 root_dir = file_context->root_dir;
431 WINPR_ASSERT(root_dir);
432
433 clear_context.file_context = file_context;
434 clear_context.fuse_files = ArrayList_New(FALSE);
435 WINPR_ASSERT(clear_context.fuse_files);
436
437 wObject* aobj = ArrayList_Object(clear_context.fuse_files);
438 WINPR_ASSERT(aobj);
439 aobj->fnObjectFree = fuse_file_free;
440
441 if (clip_data_entry)
442 {
443 clip_data_dir = clip_data_entry->clip_data_dir;
444 clip_data_entry->clip_data_dir = NULL;
445
446 WINPR_ASSERT(clip_data_dir);
447
448 ArrayList_Remove(root_dir->children, clip_data_dir);
449
450 clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
451 clear_context.clip_data_id = clip_data_dir->clip_data_id;
452 }
453 clear_context.all_files = all_selections;
454
455 if (clip_data_entry && clip_data_entry->has_clip_data_id)
456 WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u",
457 clip_data_entry->clip_data_id);
458 else
459 WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s",
460 all_selections ? "s" : "");
461
462 HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context);
463 HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context);
464 HashTable_Unlock(file_context->inode_table);
465
466 if (file_context->fuse_sess)
467 {
468 /*
469 * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a
470 * FUSE request (e.g. read()), then FUSE would block in read(), since the
471 * mutex of the inode_table would still be locked, if we wouldn't unlock it
472 * here.
473 * So, to avoid a deadlock here, unlock the mutex and reply all incoming
474 * operations with -ENOENT until the invalidation process is complete.
475 */
476 ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context);
477 if (clip_data_dir)
478 {
479 fuse_lowlevel_notify_delete(file_context->fuse_sess, file_context->root_dir->ino,
480 clip_data_dir->ino, clip_data_dir->filename,
481 strlen(clip_data_dir->filename));
482 }
483 }
484 ArrayList_Free(clear_context.fuse_files);
485
486 HashTable_Lock(file_context->inode_table);
487 if (clip_data_entry && clip_data_entry->has_clip_data_id)
488 WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u",
489 clip_data_entry->clip_data_id);
490 else
491 WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : "");
492}
493
494static void clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
495{
496 WINPR_ASSERT(clip_data_entry);
497
498 if (!clip_data_entry->clip_data_dir)
499 return;
500
501 clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
502}
503
504static void clear_no_cdi_entry(CliprdrFileContext* file_context)
505{
506 WINPR_ASSERT(file_context);
507
508 WINPR_ASSERT(file_context->inode_table);
509 HashTable_Lock(file_context->inode_table);
510 if (file_context->clip_data_entry_without_id)
511 {
512 clear_entry_selection(file_context->clip_data_entry_without_id);
513
514 clip_data_entry_free(file_context->clip_data_entry_without_id);
515 file_context->clip_data_entry_without_id = NULL;
516 }
517 HashTable_Unlock(file_context->inode_table);
518}
519
520static BOOL clear_clip_data_entries(WINPR_ATTR_UNUSED const void* key, void* value,
521 WINPR_ATTR_UNUSED void* arg)
522{
523 clear_entry_selection(value);
524
525 return TRUE;
526}
527
528static void clear_cdi_entries(CliprdrFileContext* file_context)
529{
530 WINPR_ASSERT(file_context);
531
532 HashTable_Lock(file_context->inode_table);
533 HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, NULL);
534
535 HashTable_Clear(file_context->clip_data_table);
536 HashTable_Unlock(file_context->inode_table);
537}
538
539static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
540{
541 CliprdrFuseClipDataEntry* clip_data_entry = NULL;
542
543 WINPR_ASSERT(file_context);
544
545 clip_data_entry = clip_data_entry_new(file_context, TRUE);
546 if (!clip_data_entry)
547 {
548 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
549 return ERROR_INTERNAL_ERROR;
550 }
551
552 HashTable_Lock(file_context->inode_table);
553 if (!HashTable_Insert(file_context->clip_data_table,
554 (void*)(uintptr_t)clip_data_entry->clip_data_id, clip_data_entry))
555 {
556 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry");
557 clip_data_entry_free(clip_data_entry);
558 HashTable_Unlock(file_context->inode_table);
559 return ERROR_INTERNAL_ERROR;
560 }
561 HashTable_Unlock(file_context->inode_table);
562
563 // HashTable_Insert owns clip_data_entry
564 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
565 file_context->current_clip_data_id = clip_data_entry->clip_data_id;
566
567 return CHANNEL_RC_OK;
568}
569
570static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
571{
572 WINPR_ASSERT(file_context);
573 WINPR_ASSERT(!file_context->clip_data_entry_without_id);
574
575 file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
576 if (!file_context->clip_data_entry_without_id)
577 {
578 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
579 return ERROR_INTERNAL_ERROR;
580 }
581
582 return CHANNEL_RC_OK;
583}
584#endif
585
586UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
587{
588 WINPR_ASSERT(file_context);
589 WINPR_ASSERT(file_context->context);
590
591#if defined(WITH_FUSE)
592 clear_no_cdi_entry(file_context);
593 /* TODO: assign timeouts to old locks instead */
594 clear_cdi_entries(file_context);
595
596 if (does_server_support_clipdata_locking(file_context))
597 return prepare_clip_data_entry_with_id(file_context);
598 else
599 return prepare_clip_data_entry_without_id(file_context);
600#else
601 return CHANNEL_RC_OK;
602#endif
603}
604
605UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
606{
607 WINPR_ASSERT(file_context);
608 WINPR_ASSERT(file_context->context);
609
610#if defined(WITH_FUSE)
611 clear_no_cdi_entry(file_context);
612 /* TODO: assign timeouts to old locks instead */
613 clear_cdi_entries(file_context);
614#endif
615
616 return CHANNEL_RC_OK;
617}
618
619static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
620 const char* data, size_t size);
621static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
622static BOOL local_stream_discard(const void* key, void* value, void* arg);
623
624static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
625{
626 if (!WLog_IsLevelActive(log, level))
627 return;
628
629 va_list ap = { 0 };
630 va_start(ap, line);
631 WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
632 va_end(ap);
633}
634
635#if defined(WITH_FUSE)
636static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name);
637static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
638static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
639 struct fuse_file_info* fi);
640static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
641 struct fuse_file_info* fi);
642static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
643static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
644
645static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
646 .lookup = cliprdr_file_fuse_lookup,
647 .getattr = cliprdr_file_fuse_getattr,
648 .readdir = cliprdr_file_fuse_readdir,
649 .open = cliprdr_file_fuse_open,
650 .read = cliprdr_file_fuse_read,
651 .opendir = cliprdr_file_fuse_opendir,
652};
653
654static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
655{
656 WINPR_ASSERT(file_context);
657
658 return HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)fuse_ino);
659}
660
661static CliprdrFuseFile*
662get_fuse_file_by_name_from_parent(WINPR_ATTR_UNUSED CliprdrFileContext* file_context,
663 CliprdrFuseFile* parent, const char* name)
664{
665 WINPR_ASSERT(file_context);
666 WINPR_ASSERT(parent);
667
668 for (size_t i = 0; i < ArrayList_Count(parent->children); ++i)
669 {
670 CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
671
672 WINPR_ASSERT(child);
673
674 if (strcmp(name, child->filename) == 0)
675 return child;
676 }
677
678 DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist",
679 name, parent->filename);
680
681 return NULL;
682}
683
684static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
685 CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
686 FuseLowlevelOperationType operation_type)
687{
688 CliprdrFuseRequest* fuse_request = NULL;
689 UINT32 stream_id = file_context->next_stream_id;
690
691 WINPR_ASSERT(file_context);
692 WINPR_ASSERT(fuse_file);
693
694 fuse_request = calloc(1, sizeof(CliprdrFuseRequest));
695 if (!fuse_request)
696 {
697 WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"",
698 fuse_file->filename_with_root);
699 return NULL;
700 }
701
702 fuse_request->fuse_file = fuse_file;
703 fuse_request->fuse_req = fuse_req;
704 fuse_request->operation_type = operation_type;
705
706 while (stream_id == 0 ||
707 HashTable_GetItemValue(file_context->request_table, (void*)(uintptr_t)stream_id))
708 ++stream_id;
709 fuse_request->stream_id = stream_id;
710
711 file_context->next_stream_id = stream_id + 1;
712
713 if (!HashTable_Insert(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id,
714 fuse_request))
715 {
716 WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"",
717 fuse_file->filename_with_root);
718 free(fuse_request);
719 return NULL;
720 }
721
722 return fuse_request;
723}
724
725static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
726 fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
727{
728 CliprdrFuseRequest* fuse_request = NULL;
729 CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
730
731 WINPR_ASSERT(file_context);
732 WINPR_ASSERT(fuse_file);
733
734 fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
735 if (!fuse_request)
736 return FALSE;
737
738 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
739 file_contents_request.streamId = fuse_request->stream_id;
740 file_contents_request.listIndex = fuse_file->list_idx;
741 file_contents_request.dwFlags = FILECONTENTS_SIZE;
742 file_contents_request.cbRequested = 0x8;
743 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
744 file_contents_request.clipDataId = fuse_file->clip_data_id;
745
746 if (file_context->context->ClientFileContentsRequest(file_context->context,
747 &file_contents_request))
748 {
749 WLog_Print(file_context->log, WLOG_ERROR,
750 "Failed to send FileContentsRequest for file \"%s\"",
751 fuse_file->filename_with_root);
752 HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id);
753 return FALSE;
754 }
755 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
756 DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u",
757 fuse_file->filename, fuse_request->stream_id);
758
759 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
760 return TRUE;
761}
762
763static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr)
764{
765 memset(attr, 0, sizeof(struct stat));
766
767 if (!fuse_file)
768 return;
769
770 attr->st_ino = fuse_file->ino;
771 if (fuse_file->is_directory)
772 {
773 attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
774 attr->st_nlink = 2;
775 }
776 else
777 {
778 attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
779 attr->st_nlink = 1;
780 attr->st_size = WINPR_ASSERTING_INT_CAST(off_t, fuse_file->size);
781 }
782 attr->st_uid = getuid();
783 attr->st_gid = getgid();
784 attr->st_atime = attr->st_mtime = attr->st_ctime =
785 (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(NULL));
786}
787
788static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name)
789{
790 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
791 CliprdrFuseFile* parent = NULL;
792 CliprdrFuseFile* fuse_file = NULL;
793 struct fuse_entry_param entry = { 0 };
794
795 WINPR_ASSERT(file_context);
796
797 HashTable_Lock(file_context->inode_table);
798 if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
799 {
800 HashTable_Unlock(file_context->inode_table);
801 fuse_reply_err(fuse_req, ENOENT);
802 return;
803 }
804 if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
805 {
806 HashTable_Unlock(file_context->inode_table);
807 fuse_reply_err(fuse_req, ENOENT);
808 return;
809 }
810
811 DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name);
812 DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"",
813 parent->filename_with_root, fuse_file->filename_with_root);
814
815 if (!fuse_file->is_directory && !fuse_file->has_size)
816 {
817 BOOL result = 0;
818
819 result =
820 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
821 HashTable_Unlock(file_context->inode_table);
822
823 if (!result)
824 fuse_reply_err(fuse_req, EIO);
825
826 return;
827 }
828
829 entry.ino = fuse_file->ino;
830 write_file_attributes(fuse_file, &entry.attr);
831 entry.attr_timeout = 1.0;
832 entry.entry_timeout = 1.0;
833 HashTable_Unlock(file_context->inode_table);
834
835 fuse_reply_entry(fuse_req, &entry);
836}
837
838static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
839 WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
840{
841 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
842 CliprdrFuseFile* fuse_file = NULL;
843 struct stat attr = { 0 };
844
845 WINPR_ASSERT(file_context);
846
847 HashTable_Lock(file_context->inode_table);
848 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
849 {
850 HashTable_Unlock(file_context->inode_table);
851 fuse_reply_err(fuse_req, ENOENT);
852 return;
853 }
854
855 DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"",
856 fuse_file->filename_with_root);
857
858 if (!fuse_file->is_directory && !fuse_file->has_size)
859 {
860 BOOL result = 0;
861
862 result =
863 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
864 HashTable_Unlock(file_context->inode_table);
865
866 if (!result)
867 fuse_reply_err(fuse_req, EIO);
868
869 return;
870 }
871
872 write_file_attributes(fuse_file, &attr);
873 HashTable_Unlock(file_context->inode_table);
874
875 fuse_reply_attr(fuse_req, &attr, 1.0);
876}
877
878static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
879 struct fuse_file_info* file_info)
880{
881 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
882 CliprdrFuseFile* fuse_file = NULL;
883
884 WINPR_ASSERT(file_context);
885
886 HashTable_Lock(file_context->inode_table);
887 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
888 {
889 HashTable_Unlock(file_context->inode_table);
890 fuse_reply_err(fuse_req, ENOENT);
891 return;
892 }
893 if (fuse_file->is_directory)
894 {
895 HashTable_Unlock(file_context->inode_table);
896 fuse_reply_err(fuse_req, EISDIR);
897 return;
898 }
899 HashTable_Unlock(file_context->inode_table);
900
901 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
902 {
903 fuse_reply_err(fuse_req, EACCES);
904 return;
905 }
906
907 /* Important for KDE to get file correctly */
908 file_info->direct_io = 1;
909
910 fuse_reply_open(fuse_req, file_info);
911}
912
913static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
914 fuse_req_t fuse_req, off_t offset, size_t requested_size)
915{
916 CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
917
918 WINPR_ASSERT(file_context);
919 WINPR_ASSERT(fuse_file);
920
921 if (requested_size > UINT32_MAX)
922 return FALSE;
923
924 CliprdrFuseRequest* fuse_request =
925 cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
926 if (!fuse_request)
927 return FALSE;
928
929 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
930 file_contents_request.streamId = fuse_request->stream_id;
931 file_contents_request.listIndex = fuse_file->list_idx;
932 file_contents_request.dwFlags = FILECONTENTS_RANGE;
933 file_contents_request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF);
934 file_contents_request.nPositionHigh = (UINT32)((offset >> 32) & 0xFFFFFFFF);
935 file_contents_request.cbRequested = (UINT32)requested_size;
936 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
937 file_contents_request.clipDataId = fuse_file->clip_data_id;
938
939 if (file_context->context->ClientFileContentsRequest(file_context->context,
940 &file_contents_request))
941 {
942 WLog_Print(file_context->log, WLOG_ERROR,
943 "Failed to send FileContentsRequest for file \"%s\"",
944 fuse_file->filename_with_root);
945 HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id);
946 return FALSE;
947 }
948
949 // file_context->request_table owns fuse_request
950 // NOLINTBEGIN(clang-analyzer-unix.Malloc)
951 DEBUG_CLIPRDR(
952 file_context->log,
953 "Requested file range (%zu Bytes at offset %lu) for file \"%s\" with stream id %u",
954 requested_size, offset, fuse_file->filename, fuse_request->stream_id);
955
956 return TRUE;
957 // NOLINTEND(clang-analyzer-unix.Malloc)
958}
959
960static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size,
961 off_t offset, WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
962{
963 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
964 CliprdrFuseFile* fuse_file = NULL;
965 BOOL result = 0;
966
967 WINPR_ASSERT(file_context);
968
969 HashTable_Lock(file_context->inode_table);
970 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
971 {
972 HashTable_Unlock(file_context->inode_table);
973 fuse_reply_err(fuse_req, ENOENT);
974 return;
975 }
976 if (fuse_file->is_directory)
977 {
978 HashTable_Unlock(file_context->inode_table);
979 fuse_reply_err(fuse_req, EISDIR);
980 return;
981 }
982 if (!fuse_file->has_size || (offset < 0) || ((size_t)offset > fuse_file->size))
983 {
984 HashTable_Unlock(file_context->inode_table);
985 fuse_reply_err(fuse_req, EINVAL);
986 return;
987 }
988
989 size = MIN(size, 8ULL * 1024ULL * 1024ULL);
990
991 result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
992 HashTable_Unlock(file_context->inode_table);
993
994 if (!result)
995 fuse_reply_err(fuse_req, EIO);
996}
997
998static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
999 struct fuse_file_info* file_info)
1000{
1001 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1002 CliprdrFuseFile* fuse_file = NULL;
1003
1004 WINPR_ASSERT(file_context);
1005
1006 HashTable_Lock(file_context->inode_table);
1007 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1008 {
1009 HashTable_Unlock(file_context->inode_table);
1010 fuse_reply_err(fuse_req, ENOENT);
1011 return;
1012 }
1013 if (!fuse_file->is_directory)
1014 {
1015 HashTable_Unlock(file_context->inode_table);
1016 fuse_reply_err(fuse_req, ENOTDIR);
1017 return;
1018 }
1019 HashTable_Unlock(file_context->inode_table);
1020
1021 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
1022 {
1023 fuse_reply_err(fuse_req, EACCES);
1024 return;
1025 }
1026
1027 fuse_reply_open(fuse_req, file_info);
1028}
1029
1030static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size,
1031 off_t offset,
1032 WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
1033{
1034 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1035 CliprdrFuseFile* fuse_file = NULL;
1036 CliprdrFuseFile* child = NULL;
1037 struct stat attr = { 0 };
1038 size_t written_size = 0;
1039 size_t entry_size = 0;
1040 char* filename = NULL;
1041 char* buf = NULL;
1042
1043 WINPR_ASSERT(file_context);
1044
1045 HashTable_Lock(file_context->inode_table);
1046 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1047 {
1048 HashTable_Unlock(file_context->inode_table);
1049 fuse_reply_err(fuse_req, ENOENT);
1050 return;
1051 }
1052 if (!fuse_file->is_directory)
1053 {
1054 HashTable_Unlock(file_context->inode_table);
1055 fuse_reply_err(fuse_req, ENOTDIR);
1056 return;
1057 }
1058
1059 DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %lu",
1060 fuse_file->filename_with_root, offset);
1061
1062 if ((offset < 0) || ((size_t)offset >= ArrayList_Count(fuse_file->children)))
1063 {
1064 HashTable_Unlock(file_context->inode_table);
1065 fuse_reply_buf(fuse_req, NULL, 0);
1066 return;
1067 }
1068
1069 buf = calloc(max_size, sizeof(char));
1070 if (!buf)
1071 {
1072 HashTable_Unlock(file_context->inode_table);
1073 fuse_reply_err(fuse_req, ENOMEM);
1074 return;
1075 }
1076 written_size = 0;
1077
1078 for (off_t i = offset; i < 2; ++i)
1079 {
1080 if (i == 0)
1081 {
1082 write_file_attributes(fuse_file, &attr);
1083 filename = ".";
1084 }
1085 else if (i == 1)
1086 {
1087 write_file_attributes(fuse_file->parent, &attr);
1088 attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
1089 attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
1090 filename = "..";
1091 }
1092 else
1093 {
1094 WINPR_ASSERT(FALSE);
1095 }
1096
1101 entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1102 filename, &attr, i + 1);
1103 if (entry_size > max_size - written_size)
1104 break;
1105
1106 written_size += entry_size;
1107 }
1108
1109 for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
1110 {
1111 if (i < (size_t)offset)
1112 continue;
1113
1114 child = ArrayList_GetItem(fuse_file->children, j);
1115
1116 write_file_attributes(child, &attr);
1117 entry_size =
1118 fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1119 child->filename, &attr, WINPR_ASSERTING_INT_CAST(off_t, i + 1));
1120 if (entry_size > max_size - written_size)
1121 break;
1122
1123 written_size += entry_size;
1124 }
1125 HashTable_Unlock(file_context->inode_table);
1126
1127 fuse_reply_buf(fuse_req, buf, written_size);
1128 free(buf);
1129}
1130
1131static void fuse_abort(int sig, const char* signame, void* context)
1132{
1133 CliprdrFileContext* file = (CliprdrFileContext*)context;
1134
1135 if (file)
1136 {
1137 WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
1138 cliprdr_file_session_terminate(file, FALSE);
1139 }
1140}
1141
1142static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
1143{
1144 CliprdrFileContext* file = (CliprdrFileContext*)arg;
1145
1146 WINPR_ASSERT(file);
1147
1148 DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
1149
1150 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
1151 fuse_opt_add_arg(&args, file->path);
1152 file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
1153 sizeof(cliprdr_file_fuse_oper), (void*)file);
1154 (void)SetEvent(file->fuse_start_sync);
1155
1156 if (file->fuse_sess != NULL)
1157 {
1158 freerdp_add_signal_cleanup_handler(file, fuse_abort);
1159 if (0 == fuse_session_mount(file->fuse_sess, file->path))
1160 {
1161 fuse_session_loop(file->fuse_sess);
1162 fuse_session_unmount(file->fuse_sess);
1163 }
1164 freerdp_del_signal_cleanup_handler(file, fuse_abort);
1165
1166 WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
1167 if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
1168 WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
1169 fuse_session_destroy(file->fuse_sess);
1170 }
1171 fuse_opt_free_args(&args);
1172
1173 DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
1174
1175 ExitThread(0);
1176 return 0;
1177}
1178
1179static UINT cliprdr_file_context_server_file_contents_response(
1180 CliprdrClientContext* cliprdr_context,
1181 const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
1182{
1183 CliprdrFileContext* file_context = NULL;
1184 CliprdrFuseRequest* fuse_request = NULL;
1185 struct fuse_entry_param entry = { 0 };
1186
1187 WINPR_ASSERT(cliprdr_context);
1188 WINPR_ASSERT(file_contents_response);
1189
1190 file_context = cliprdr_context->custom;
1191 WINPR_ASSERT(file_context);
1192
1193 HashTable_Lock(file_context->inode_table);
1194 fuse_request = HashTable_GetItemValue(file_context->request_table,
1195 (void*)(uintptr_t)file_contents_response->streamId);
1196 if (!fuse_request)
1197 {
1198 HashTable_Unlock(file_context->inode_table);
1199 return CHANNEL_RC_OK;
1200 }
1201
1202 if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
1203 {
1204 WLog_Print(file_context->log, WLOG_WARN,
1205 "FileContentsRequests for file \"%s\" was unsuccessful",
1206 fuse_request->fuse_file->filename);
1207
1208 fuse_reply_err(fuse_request->fuse_req, EIO);
1209 HashTable_Remove(file_context->request_table,
1210 (void*)(uintptr_t)file_contents_response->streamId);
1211 HashTable_Unlock(file_context->inode_table);
1212 return CHANNEL_RC_OK;
1213 }
1214
1215 if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1216 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
1217 file_contents_response->cbRequested != sizeof(UINT64))
1218 {
1219 WLog_Print(file_context->log, WLOG_WARN,
1220 "Received invalid file size for file \"%s\" from the client",
1221 fuse_request->fuse_file->filename);
1222 fuse_reply_err(fuse_request->fuse_req, EIO);
1223 HashTable_Remove(file_context->request_table,
1224 (void*)(uintptr_t)file_contents_response->streamId);
1225 HashTable_Unlock(file_context->inode_table);
1226 return CHANNEL_RC_OK;
1227 }
1228
1229 if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1230 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
1231 {
1232 DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
1233 fuse_request->fuse_file->filename, file_contents_response->streamId);
1234
1235 fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData);
1236 fuse_request->fuse_file->has_size = TRUE;
1237
1238 entry.ino = fuse_request->fuse_file->ino;
1239 write_file_attributes(fuse_request->fuse_file, &entry.attr);
1240 entry.attr_timeout = 1.0;
1241 entry.entry_timeout = 1.0;
1242 }
1243 else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
1244 {
1245 DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
1246 fuse_request->fuse_file->filename, file_contents_response->streamId);
1247 }
1248 HashTable_Unlock(file_context->inode_table);
1249
1250 switch (fuse_request->operation_type)
1251 {
1252 case FUSE_LL_OPERATION_NONE:
1253 break;
1254 case FUSE_LL_OPERATION_LOOKUP:
1255 fuse_reply_entry(fuse_request->fuse_req, &entry);
1256 break;
1257 case FUSE_LL_OPERATION_GETATTR:
1258 fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
1259 break;
1260 case FUSE_LL_OPERATION_READ:
1261 fuse_reply_buf(fuse_request->fuse_req,
1262 (const char*)file_contents_response->requestedData,
1263 file_contents_response->cbRequested);
1264 break;
1265 default:
1266 break;
1267 }
1268
1269 HashTable_Remove(file_context->request_table,
1270 (void*)(uintptr_t)file_contents_response->streamId);
1271
1272 return CHANNEL_RC_OK;
1273}
1274#endif
1275
1276static UINT cliprdr_file_context_send_file_contents_failure(
1277 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1278{
1279 CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
1280
1281 WINPR_ASSERT(file);
1282 WINPR_ASSERT(fileContentsRequest);
1283
1284 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1285 ((UINT64)fileContentsRequest->nPositionLow);
1286 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1287 "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
1288 ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
1289 fileContentsRequest->clipDataId, fileContentsRequest->streamId,
1290 fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
1291
1292 response.common.msgFlags = CB_RESPONSE_FAIL;
1293 response.streamId = fileContentsRequest->streamId;
1294
1295 WINPR_ASSERT(file->context);
1296 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1297 return file->context->ClientFileContentsResponse(file->context, &response);
1298}
1299
1300static UINT
1301cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
1302 const CLIPRDR_FILE_CONTENTS_REQUEST* request,
1303 const void* data, size_t size)
1304{
1305 if (size > UINT32_MAX)
1306 return ERROR_INVALID_PARAMETER;
1307
1308 CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
1309 .requestedData = data,
1310 .cbRequested = (UINT32)size,
1311 .common.msgFlags = CB_RESPONSE_OK };
1312
1313 WINPR_ASSERT(request);
1314 WINPR_ASSERT(file);
1315
1316 WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
1317 response.streamId, response.cbRequested);
1318 WINPR_ASSERT(file->context);
1319 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1320 return file->context->ClientFileContentsResponse(file->context, &response);
1321}
1322
1323static BOOL dump_streams(const void* key, void* value, WINPR_ATTR_UNUSED void* arg)
1324{
1325 const UINT32* ukey = key;
1326 CliprdrLocalStream* cur = value;
1327
1328 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1329 "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
1330 cur->lockId, cur->count, cur->locked);
1331 for (size_t x = 0; x < cur->count; x++)
1332 {
1333 const CliprdrLocalFile* file = &cur->files[x];
1334 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, "file [%" PRIuz "] ",
1335 x, file->name, file->size);
1336 }
1337 return TRUE;
1338}
1339
1340static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
1341 UINT32 listIndex)
1342{
1343 WINPR_ASSERT(file);
1344
1345 CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
1346 if (cur)
1347 {
1348 if (listIndex < cur->count)
1349 {
1350 CliprdrLocalFile* f = &cur->files[listIndex];
1351 return f;
1352 }
1353 else
1354 {
1355 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1356 "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIu32
1357 "] [locked %d]",
1358 lockId, listIndex, cur->count, cur->locked);
1359 }
1360 }
1361 else
1362 {
1363 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1364 "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
1365 HashTable_Foreach(file->local_streams, dump_streams, file);
1366 }
1367
1368 return NULL;
1369}
1370
1371static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
1372{
1373 CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
1374 if (f)
1375 {
1376 if (!f->fp)
1377 {
1378 const char* name = f->name;
1379 f->fp = winpr_fopen(name, "rb");
1380 }
1381 if (!f->fp)
1382 {
1383 char ebuffer[256] = { 0 };
1384 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1385 "[lockID %" PRIu32 ", index %" PRIu32
1386 "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
1387 lockId, listIndex, f->name, f->size,
1388 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1389 return NULL;
1390 }
1391 }
1392
1393 return f;
1394}
1395
1396static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
1397 UINT64 size)
1398{
1399 WINPR_ASSERT(file);
1400
1401 if (res != 0)
1402 {
1403 WINPR_ASSERT(file->context);
1404 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
1405 file->name, res);
1406 }
1407 else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
1408 {
1409 WINPR_ASSERT(file->context);
1410 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
1411 }
1412 else
1413 {
1414 // TODO: we need to keep track of open files to avoid running out of file descriptors
1415 // TODO: for the time being just close again.
1416 }
1417 if (file->fp)
1418 (void)fclose(file->fp);
1419 file->fp = NULL;
1420}
1421
1422static UINT cliprdr_file_context_server_file_size_request(
1423 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1424{
1425 WINPR_ASSERT(fileContentsRequest);
1426
1427 if (fileContentsRequest->cbRequested != sizeof(UINT64))
1428 {
1429 WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
1430 fileContentsRequest->cbRequested);
1431 }
1432
1433 HashTable_Lock(file->local_streams);
1434
1435 UINT res = CHANNEL_RC_OK;
1436 CliprdrLocalFile* rfile =
1437 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1438 if (!rfile)
1439 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1440 else
1441 {
1442 if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
1443 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1444 else
1445 {
1446 const INT64 size = _ftelli64(rfile->fp);
1447 rfile->size = size;
1448 cliprdr_local_file_try_close(rfile, res, 0, 0);
1449
1450 res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
1451 sizeof(size));
1452 }
1453 }
1454
1455 HashTable_Unlock(file->local_streams);
1456 return res;
1457}
1458
1459static UINT cliprdr_file_context_server_file_range_request(
1460 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1461{
1462 BYTE* data = NULL;
1463
1464 WINPR_ASSERT(fileContentsRequest);
1465
1466 HashTable_Lock(file->local_streams);
1467 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1468 ((UINT64)fileContentsRequest->nPositionLow);
1469
1470 CliprdrLocalFile* rfile =
1471 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1472 if (!rfile)
1473 goto fail;
1474
1475 if (_fseeki64(rfile->fp, WINPR_ASSERTING_INT_CAST(int64_t, offset), SEEK_SET) < 0)
1476 goto fail;
1477
1478 data = malloc(fileContentsRequest->cbRequested);
1479 if (!data)
1480 goto fail;
1481
1482 const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
1483 const UINT rc = cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
1484 free(data);
1485
1486 cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
1487 HashTable_Unlock(file->local_streams);
1488 return rc;
1489fail:
1490 if (rfile)
1491 cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
1492 fileContentsRequest->cbRequested);
1493 free(data);
1494 HashTable_Unlock(file->local_streams);
1495 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1496}
1497
1498static void cliprdr_local_stream_free(void* obj);
1499
1500static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
1501{
1502 UINT rc = CHANNEL_RC_OK;
1503
1504 WINPR_ASSERT(file);
1505
1506 HashTable_Lock(file->local_streams);
1507
1508 {
1509 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
1510 if (lock && !stream)
1511 {
1512 stream = cliprdr_local_stream_new(file, lockId, NULL, 0);
1513 if (!HashTable_Insert(file->local_streams, &lockId, stream))
1514 {
1515 rc = ERROR_INTERNAL_ERROR;
1516 cliprdr_local_stream_free(stream);
1517 stream = NULL;
1518 }
1519 file->local_lock_id = lockId;
1520 }
1521 if (stream)
1522 {
1523 stream->locked = lock;
1524 stream->lockId = lockId;
1525 }
1526 }
1527
1528 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership stream
1529 if (!lock)
1530 {
1531 if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
1532 rc = ERROR_INTERNAL_ERROR;
1533 }
1534
1535 HashTable_Unlock(file->local_streams);
1536 return rc;
1537}
1538
1539static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
1540 const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
1541{
1542 WINPR_ASSERT(context);
1543 WINPR_ASSERT(lockClipboardData);
1544 CliprdrFileContext* file = (context->custom);
1545 return change_lock(file, lockClipboardData->clipDataId, TRUE);
1546}
1547
1548static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
1549 const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
1550{
1551 WINPR_ASSERT(context);
1552 WINPR_ASSERT(unlockClipboardData);
1553 CliprdrFileContext* file = (context->custom);
1554 return change_lock(file, unlockClipboardData->clipDataId, FALSE);
1555}
1556
1557static UINT cliprdr_file_context_server_file_contents_request(
1558 CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1559{
1560 UINT error = NO_ERROR;
1561
1562 WINPR_ASSERT(context);
1563 WINPR_ASSERT(fileContentsRequest);
1564
1565 CliprdrFileContext* file = (context->custom);
1566 WINPR_ASSERT(file);
1567
1568 /*
1569 * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
1570 * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
1571 */
1572 if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
1573 (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
1574 {
1575 WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
1576 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1577 }
1578
1579 if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
1580 error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
1581
1582 if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
1583 error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
1584
1585 if (error)
1586 {
1587 WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
1588 error);
1589 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1590 }
1591
1592 return CHANNEL_RC_OK;
1593}
1594
1595BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
1596{
1597 WINPR_ASSERT(file);
1598 WINPR_ASSERT(cliprdr);
1599
1600 cliprdr->custom = file;
1601 file->context = cliprdr;
1602
1603 cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
1604 cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
1605 cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
1606#if defined(WITH_FUSE)
1607 cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
1608#endif
1609
1610 return TRUE;
1611}
1612
1613#if defined(WITH_FUSE)
1614static void clear_all_selections(CliprdrFileContext* file_context)
1615{
1616 WINPR_ASSERT(file_context);
1617 WINPR_ASSERT(file_context->inode_table);
1618
1619 HashTable_Lock(file_context->inode_table);
1620 clear_selection(file_context, TRUE, NULL);
1621
1622 HashTable_Clear(file_context->clip_data_table);
1623 HashTable_Unlock(file_context->inode_table);
1624}
1625#endif
1626
1627BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
1628{
1629 WINPR_ASSERT(file);
1630 WINPR_ASSERT(cliprdr);
1631
1632 // Clear all data before the channel is closed
1633 // the cleanup handlers are dependent on a working channel.
1634#if defined(WITH_FUSE)
1635 if (file->inode_table)
1636 {
1637 clear_no_cdi_entry(file);
1638 clear_all_selections(file);
1639 }
1640#endif
1641
1642 HashTable_Clear(file->local_streams);
1643
1644 file->context = NULL;
1645#if defined(WITH_FUSE)
1646 cliprdr->ServerFileContentsResponse = NULL;
1647#endif
1648
1649 return TRUE;
1650}
1651
1652static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
1653 size_t size)
1654{
1655
1656 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
1657
1658 if (hsize < sizeof(hash))
1659 return FALSE;
1660
1661 if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
1662 return FALSE;
1663
1664 const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
1665 if (changed)
1666 memcpy(ihash, hash, sizeof(hash));
1667 return changed;
1668}
1669
1670static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
1671 const void* data, size_t size)
1672{
1673 WINPR_ASSERT(file);
1674 return cliprdr_file_content_changed_and_update(file->client_data_hash,
1675 sizeof(file->client_data_hash), data, size);
1676}
1677
1678#if defined(WITH_FUSE)
1679static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
1680{
1681 fuse_ino_t ino = 0;
1682
1683 WINPR_ASSERT(file_context);
1684
1685 ino = file_context->next_ino;
1686 while (ino == 0 || ino == FUSE_ROOT_ID ||
1687 HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)ino))
1688 ++ino;
1689
1690 file_context->next_ino = ino + 1;
1691
1692 return ino;
1693}
1694
1695static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
1696 UINT32 clip_data_id)
1697{
1698 CliprdrFuseFile* root_dir = NULL;
1699 CliprdrFuseFile* clip_data_dir = NULL;
1700 size_t path_length = 0;
1701
1702 WINPR_ASSERT(file_context);
1703
1704 clip_data_dir = fuse_file_new();
1705 if (!clip_data_dir)
1706 return NULL;
1707
1708 path_length = 1 + MAX_CLIP_DATA_DIR_LEN + 1;
1709
1710 clip_data_dir->filename_with_root = calloc(path_length, sizeof(char));
1711 if (!clip_data_dir->filename_with_root)
1712 {
1713 WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
1714 fuse_file_free(clip_data_dir);
1715 return NULL;
1716 }
1717
1718 if (has_clip_data_id)
1719 (void)_snprintf(clip_data_dir->filename_with_root, path_length, "/%u",
1720 (unsigned)clip_data_id);
1721 else
1722 (void)_snprintf(clip_data_dir->filename_with_root, path_length, "/%" PRIu64,
1723 NO_CLIP_DATA_ID);
1724
1725 clip_data_dir->filename = strrchr(clip_data_dir->filename_with_root, '/') + 1;
1726
1727 clip_data_dir->ino = get_next_free_inode(file_context);
1728 clip_data_dir->is_directory = TRUE;
1729 clip_data_dir->is_readonly = TRUE;
1730 clip_data_dir->has_clip_data_id = has_clip_data_id;
1731 clip_data_dir->clip_data_id = clip_data_id;
1732
1733 root_dir = file_context->root_dir;
1734 if (!ArrayList_Append(root_dir->children, clip_data_dir))
1735 {
1736 WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
1737 fuse_file_free(clip_data_dir);
1738 return NULL;
1739 }
1740 clip_data_dir->parent = root_dir;
1741
1742 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)clip_data_dir->ino,
1743 clip_data_dir))
1744 {
1745 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
1746 ArrayList_Remove(root_dir->children, clip_data_dir);
1747 fuse_file_free(clip_data_dir);
1748 return NULL;
1749 }
1750
1751 return clip_data_dir;
1752}
1753
1754static char* get_parent_path(const char* filepath)
1755{
1756 const char* base = strrchr(filepath, '/');
1757 WINPR_ASSERT(base);
1758
1759 while ((base > filepath) && (*base == '/'))
1760 --base;
1761
1762 WINPR_ASSERT(base >= filepath);
1763 const size_t parent_path_length = 1ULL + (size_t)(base - filepath);
1764 char* parent_path = calloc(parent_path_length + 1, sizeof(char));
1765 if (!parent_path)
1766 return NULL;
1767
1768 memcpy(parent_path, filepath, parent_path_length);
1769
1770 return parent_path;
1771}
1772
1773static BOOL is_fuse_file_not_parent(WINPR_ATTR_UNUSED const void* key, void* value, void* arg)
1774{
1775 CliprdrFuseFile* fuse_file = value;
1776 CliprdrFuseFindParentContext* find_context = arg;
1777
1778 if (!fuse_file->is_directory)
1779 return TRUE;
1780
1781 if (strcmp(find_context->parent_path, fuse_file->filename_with_root) == 0)
1782 {
1783 find_context->parent = fuse_file;
1784 return FALSE;
1785 }
1786
1787 return TRUE;
1788}
1789
1790static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
1791{
1792 CliprdrFuseFindParentContext find_context = { 0 };
1793
1794 WINPR_ASSERT(file_context);
1795 WINPR_ASSERT(path);
1796
1797 find_context.parent_path = get_parent_path(path);
1798 if (!find_context.parent_path)
1799 return NULL;
1800
1801 WINPR_ASSERT(!find_context.parent);
1802
1803 if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context))
1804 {
1805 free(find_context.parent_path);
1806 return NULL;
1807 }
1808 WINPR_ASSERT(find_context.parent);
1809
1810 free(find_context.parent_path);
1811
1812 return find_context.parent;
1813}
1814
1815static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
1816 CliprdrFuseClipDataEntry* clip_data_entry,
1817 const FILEDESCRIPTORW* files, UINT32 n_files)
1818{
1819 CliprdrFuseFile* clip_data_dir = NULL;
1820
1821 WINPR_ASSERT(file_context);
1822 WINPR_ASSERT(clip_data_entry);
1823 WINPR_ASSERT(files);
1824
1825 clip_data_dir = clip_data_entry->clip_data_dir;
1826 WINPR_ASSERT(clip_data_dir);
1827
1828 if (clip_data_entry->has_clip_data_id)
1829 WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
1830 clip_data_entry->clip_data_id);
1831 else
1832 WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
1833
1834 // NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
1835 for (UINT32 i = 0; i < n_files; ++i)
1836 {
1837 const FILEDESCRIPTORW* file = &files[i];
1838 CliprdrFuseFile* fuse_file = NULL;
1839 char* filename = NULL;
1840 size_t path_length = 0;
1841
1842 fuse_file = fuse_file_new();
1843 if (!fuse_file)
1844 {
1845 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
1846 clear_entry_selection(clip_data_entry);
1847 return FALSE;
1848 }
1849
1850 filename = ConvertWCharToUtf8Alloc(file->cFileName, NULL);
1851 if (!filename)
1852 {
1853 WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
1854 fuse_file_free(fuse_file);
1855 clear_entry_selection(clip_data_entry);
1856 return FALSE;
1857 }
1858
1859 for (size_t j = 0; filename[j]; ++j)
1860 {
1861 if (filename[j] == '\\')
1862 filename[j] = '/';
1863 }
1864
1865 path_length = strlen(clip_data_dir->filename_with_root) + 1 + strlen(filename) + 1;
1866 fuse_file->filename_with_root = calloc(path_length, sizeof(char));
1867 if (!fuse_file->filename_with_root)
1868 {
1869 WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
1870 free(filename);
1871 fuse_file_free(fuse_file);
1872 clear_entry_selection(clip_data_entry);
1873 return FALSE;
1874 }
1875
1876 (void)_snprintf(fuse_file->filename_with_root, path_length, "%s/%s",
1877 clip_data_dir->filename_with_root, filename);
1878 free(filename);
1879
1880 fuse_file->filename = strrchr(fuse_file->filename_with_root, '/') + 1;
1881
1882 fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
1883 if (!fuse_file->parent)
1884 {
1885 WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
1886 fuse_file_free(fuse_file);
1887 clear_entry_selection(clip_data_entry);
1888 return FALSE;
1889 }
1890
1891 if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
1892 {
1893 WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
1894 fuse_file_free(fuse_file);
1895 clear_entry_selection(clip_data_entry);
1896 return FALSE;
1897 }
1898
1899 fuse_file->list_idx = i;
1900 fuse_file->ino = get_next_free_inode(file_context);
1901 fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
1902 fuse_file->clip_data_id = clip_data_entry->clip_data_id;
1903 if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1904 fuse_file->is_directory = TRUE;
1905 if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
1906 fuse_file->is_readonly = TRUE;
1907 if (file->dwFlags & FD_FILESIZE)
1908 {
1909 fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
1910 fuse_file->has_size = TRUE;
1911 }
1912 if (file->dwFlags & FD_WRITESTIME)
1913 {
1914 INT64 filetime = 0;
1915
1916 filetime = file->ftLastWriteTime.dwHighDateTime;
1917 filetime <<= 32;
1918 filetime += file->ftLastWriteTime.dwLowDateTime;
1919
1920 fuse_file->last_write_time_unix =
1921 1LL * filetime / (10LL * 1000LL * 1000LL) - WIN32_FILETIME_TO_UNIX_EPOCH;
1922 fuse_file->has_last_write_time = TRUE;
1923 }
1924
1925 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)fuse_file->ino,
1926 fuse_file))
1927 {
1928 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
1929 fuse_file_free(fuse_file);
1930 clear_entry_selection(clip_data_entry);
1931 return FALSE;
1932 }
1933 }
1934 // NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
1935
1936 if (clip_data_entry->has_clip_data_id)
1937 WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
1938 clip_data_entry->clip_data_id);
1939 else
1940 WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
1941
1942 return TRUE;
1943}
1944
1945static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
1946 CliprdrFuseClipDataEntry* clip_data_entry)
1947{
1948 wClipboardDelegate* delegate = NULL;
1949 CliprdrFuseFile* clip_data_dir = NULL;
1950
1951 WINPR_ASSERT(file_context);
1952 WINPR_ASSERT(clip);
1953 WINPR_ASSERT(clip_data_entry);
1954
1955 delegate = ClipboardGetDelegate(clip);
1956 WINPR_ASSERT(delegate);
1957
1958 clip_data_dir = clip_data_entry->clip_data_dir;
1959 WINPR_ASSERT(clip_data_dir);
1960
1961 free(file_context->exposed_path);
1962 file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
1963 if (file_context->exposed_path)
1964 WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
1965 file_context->exposed_path);
1966
1967 delegate->basePath = file_context->exposed_path;
1968
1969 return delegate->basePath != NULL;
1970}
1971#endif
1972
1973BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
1974 const void* data, size_t size)
1975{
1976#if defined(WITH_FUSE)
1977 CliprdrFuseClipDataEntry* clip_data_entry = NULL;
1978 FILEDESCRIPTORW* files = NULL;
1979 UINT32 n_files = 0;
1980 BOOL rc = FALSE;
1981
1982 WINPR_ASSERT(file_context);
1983 WINPR_ASSERT(clip);
1984 if (size > UINT32_MAX)
1985 return FALSE;
1986
1987 if (cliprdr_parse_file_list(data, (UINT32)size, &files, &n_files))
1988 {
1989 WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
1990 return FALSE;
1991 }
1992
1993 HashTable_Lock(file_context->inode_table);
1994 HashTable_Lock(file_context->clip_data_table);
1995 if (does_server_support_clipdata_locking(file_context))
1996 clip_data_entry = HashTable_GetItemValue(
1997 file_context->clip_data_table, (void*)(uintptr_t)file_context->current_clip_data_id);
1998 else
1999 clip_data_entry = file_context->clip_data_entry_without_id;
2000
2001 WINPR_ASSERT(clip_data_entry);
2002
2003 clear_entry_selection(clip_data_entry);
2004 WINPR_ASSERT(!clip_data_entry->clip_data_dir);
2005
2006 clip_data_entry->clip_data_dir =
2007 clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
2008 file_context->current_clip_data_id);
2009 if (!clip_data_entry->clip_data_dir)
2010 goto fail;
2011 if (!update_exposed_path(file_context, clip, clip_data_entry))
2012 goto fail;
2013 if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
2014 goto fail;
2015
2016 rc = TRUE;
2017
2018fail:
2019 HashTable_Unlock(file_context->clip_data_table);
2020 HashTable_Unlock(file_context->inode_table);
2021 free(files);
2022 return rc;
2023#else
2024 return FALSE;
2025#endif
2026}
2027
2028void* cliprdr_file_context_get_context(CliprdrFileContext* file)
2029{
2030 WINPR_ASSERT(file);
2031 return file->clipboard;
2032}
2033
2034void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
2035{
2036 if (!file)
2037 return;
2038
2039#if defined(WITH_FUSE)
2040 WINPR_ASSERT(file->fuse_stop_sync);
2041
2042 WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
2043 if (file->fuse_sess)
2044 fuse_session_exit(file->fuse_sess);
2045
2046 if (stop_thread)
2047 {
2048 WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
2049 (void)SetEvent(file->fuse_stop_sync);
2050 }
2051#endif
2052 /* not elegant but works for umounting FUSE
2053 fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
2054 */
2055#if defined(WITH_FUSE)
2056 WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
2057#endif
2058 (void)winpr_PathFileExists(file->path);
2059}
2060
2061void cliprdr_file_context_free(CliprdrFileContext* file)
2062{
2063 if (!file)
2064 return;
2065
2066#if defined(WITH_FUSE)
2067 if (file->inode_table)
2068 {
2069 clear_no_cdi_entry(file);
2070 clear_all_selections(file);
2071 }
2072
2073 if (file->fuse_thread)
2074 {
2075 WINPR_ASSERT(file->fuse_stop_sync);
2076
2077 WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
2078 cliprdr_file_session_terminate(file, TRUE);
2079
2080 WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
2081 (void)WaitForSingleObject(file->fuse_thread, INFINITE);
2082 (void)CloseHandle(file->fuse_thread);
2083 }
2084 if (file->fuse_stop_sync)
2085 (void)CloseHandle(file->fuse_stop_sync);
2086 if (file->fuse_start_sync)
2087 (void)CloseHandle(file->fuse_start_sync);
2088
2089 HashTable_Free(file->request_table);
2090 HashTable_Free(file->clip_data_table);
2091 HashTable_Free(file->inode_table);
2092#endif
2093 HashTable_Free(file->local_streams);
2094 winpr_RemoveDirectory(file->path);
2095 free(file->path);
2096 free(file->exposed_path);
2097 free(file);
2098}
2099
2100static BOOL create_base_path(CliprdrFileContext* file)
2101{
2102 WINPR_ASSERT(file);
2103
2104 char base[64] = { 0 };
2105 (void)_snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32,
2106 GetCurrentProcessId());
2107
2108 file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
2109 if (!file->path)
2110 return FALSE;
2111
2112 if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0))
2113 {
2114 WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
2115 return FALSE;
2116 }
2117 return TRUE;
2118}
2119
2120static void cliprdr_local_file_free(CliprdrLocalFile* file)
2121{
2122 const CliprdrLocalFile empty = { 0 };
2123 if (!file)
2124 return;
2125 if (file->fp)
2126 {
2127 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
2128 (void)fclose(file->fp);
2129 }
2130 free(file->name);
2131 *file = empty;
2132}
2133
2134static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
2135 const char* path)
2136{
2137 const CliprdrLocalFile empty = { 0 };
2138 WINPR_ASSERT(f);
2139 WINPR_ASSERT(context);
2140 WINPR_ASSERT(path);
2141
2142 *f = empty;
2143 f->context = context;
2144 f->name = winpr_str_url_decode(path, strlen(path));
2145 if (!f->name)
2146 goto fail;
2147
2148 return TRUE;
2149fail:
2150 cliprdr_local_file_free(f);
2151 return FALSE;
2152}
2153
2154static void cliprdr_local_files_free(CliprdrLocalStream* stream)
2155{
2156 WINPR_ASSERT(stream);
2157
2158 for (size_t x = 0; x < stream->count; x++)
2159 cliprdr_local_file_free(&stream->files[x]);
2160 free(stream->files);
2161
2162 stream->files = NULL;
2163 stream->count = 0;
2164}
2165
2166static void cliprdr_local_stream_free(void* obj)
2167{
2168 CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
2169 if (stream)
2170 cliprdr_local_files_free(stream);
2171
2172 free(stream);
2173}
2174
2175static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
2176{
2177 CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
2178 if (!tmp)
2179 return FALSE;
2180 stream->files = tmp;
2181 CliprdrLocalFile* f = &stream->files[stream->count++];
2182
2183 return cliprdr_local_file_new(stream->context, f, path);
2184}
2185
2186static BOOL is_directory(const char* path)
2187{
2188 WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, NULL);
2189 if (!wpath)
2190 return FALSE;
2191
2192 HANDLE hFile =
2193 CreateFileW(wpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2194 free(wpath);
2195
2196 if (hFile == INVALID_HANDLE_VALUE)
2197 return FALSE;
2198
2199 BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
2200 const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
2201 (void)CloseHandle(hFile);
2202 if (!status)
2203 return FALSE;
2204
2205 return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
2206}
2207
2208static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
2209{
2210 char* wildcardpath = GetCombinedPath(path, "*");
2211 if (!wildcardpath)
2212 return FALSE;
2213 WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, NULL);
2214 free(wildcardpath);
2215 if (!wpath)
2216 return FALSE;
2217
2218 WIN32_FIND_DATAW FindFileData = { 0 };
2219 HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
2220 free(wpath);
2221
2222 if (hFind == INVALID_HANDLE_VALUE)
2223 return FALSE;
2224
2225 BOOL rc = FALSE;
2226 char* next = NULL;
2227
2228 WCHAR dotbuffer[6] = { 0 };
2229 WCHAR dotdotbuffer[6] = { 0 };
2230 const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
2231 const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
2232 do
2233 {
2234 if (_wcscmp(FindFileData.cFileName, dot) == 0)
2235 continue;
2236 if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
2237 continue;
2238
2239 char cFileName[MAX_PATH] = { 0 };
2240 (void)ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName),
2241 cFileName, ARRAYSIZE(cFileName));
2242
2243 free(next);
2244 next = GetCombinedPath(path, cFileName);
2245 if (!next)
2246 goto fail;
2247
2248 if (!append_entry(stream, next))
2249 goto fail;
2250 if (is_directory(next))
2251 {
2252 if (!add_directory(stream, next))
2253 goto fail;
2254 }
2255 } while (FindNextFileW(hFind, &FindFileData));
2256
2257 rc = TRUE;
2258fail:
2259 free(next);
2260 FindClose(hFind);
2261
2262 return rc;
2263}
2264
2265static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
2266{
2267 BOOL rc = FALSE;
2268 WINPR_ASSERT(stream);
2269 if (size == 0)
2270 return TRUE;
2271
2272 cliprdr_local_files_free(stream);
2273
2274 stream->files = calloc(size, sizeof(CliprdrLocalFile));
2275 if (!stream->files)
2276 return FALSE;
2277
2278 char* copy = strndup(data, size);
2279 if (!copy)
2280 return FALSE;
2281
2282 char* saveptr = NULL;
2283 char* ptr = strtok_s(copy, "\r\n", &saveptr);
2284 while (ptr)
2285 {
2286 const char* name = ptr;
2287 if (strncmp("file:///", ptr, 8) == 0)
2288 name = &ptr[7];
2289 else if (strncmp("file:/", ptr, 6) == 0)
2290 name = &ptr[5];
2291
2292 if (!append_entry(stream, name))
2293 goto fail;
2294
2295 if (is_directory(name))
2296 {
2297 const BOOL res = add_directory(stream, name);
2298 if (!res)
2299 goto fail;
2300 }
2301 ptr = strtok_s(NULL, "\r\n", &saveptr);
2302 }
2303
2304 rc = TRUE;
2305fail:
2306 free(copy);
2307 return rc;
2308}
2309
2310CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
2311 const char* data, size_t size)
2312{
2313 WINPR_ASSERT(context);
2314 CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
2315 if (!stream)
2316 return NULL;
2317
2318 stream->context = context;
2319 if (!cliprdr_local_stream_update(stream, data, size))
2320 goto fail;
2321
2322 stream->lockId = streamID;
2323 return stream;
2324
2325fail:
2326 cliprdr_local_stream_free(stream);
2327 return NULL;
2328}
2329
2330static UINT32 UINTPointerHash(const void* id)
2331{
2332 WINPR_ASSERT(id);
2333 return *((const UINT32*)id);
2334}
2335
2336static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
2337{
2338 if (!pointer1 || !pointer2)
2339 return pointer1 == pointer2;
2340
2341 const UINT32* a = pointer1;
2342 const UINT32* b = pointer2;
2343 return *a == *b;
2344}
2345
2346static void* UINTPointerClone(const void* other)
2347{
2348 const UINT32* src = other;
2349 if (!src)
2350 return NULL;
2351
2352 UINT32* copy = calloc(1, sizeof(UINT32));
2353 if (!copy)
2354 return NULL;
2355
2356 *copy = *src;
2357 return copy;
2358}
2359
2360#if defined(WITH_FUSE)
2361static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
2362{
2363 CliprdrFuseFile* root_dir = NULL;
2364
2365 root_dir = fuse_file_new();
2366 if (!root_dir)
2367 return NULL;
2368
2369 root_dir->filename_with_root = calloc(2, sizeof(char));
2370 if (!root_dir->filename_with_root)
2371 {
2372 fuse_file_free(root_dir);
2373 return NULL;
2374 }
2375
2376 (void)_snprintf(root_dir->filename_with_root, 2, "/");
2377 root_dir->filename = root_dir->filename_with_root;
2378
2379 root_dir->ino = FUSE_ROOT_ID;
2380 root_dir->is_directory = TRUE;
2381 root_dir->is_readonly = TRUE;
2382
2383 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)root_dir->ino, root_dir))
2384 {
2385 fuse_file_free(root_dir);
2386 return NULL;
2387 }
2388
2389 return root_dir;
2390}
2391#endif
2392
2393CliprdrFileContext* cliprdr_file_context_new(void* context)
2394{
2395 CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
2396 if (!file)
2397 return NULL;
2398
2399 file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
2400 file->clipboard = context;
2401
2402 file->local_streams = HashTable_New(FALSE);
2403 if (!file->local_streams)
2404 goto fail;
2405
2406 if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
2407 goto fail;
2408
2409 wObject* hkobj = HashTable_KeyObject(file->local_streams);
2410 WINPR_ASSERT(hkobj);
2411 hkobj->fnObjectEquals = UINTPointerCompare;
2412 hkobj->fnObjectFree = free;
2413 hkobj->fnObjectNew = UINTPointerClone;
2414
2415 wObject* hobj = HashTable_ValueObject(file->local_streams);
2416 WINPR_ASSERT(hobj);
2417 hobj->fnObjectFree = cliprdr_local_stream_free;
2418
2419#if defined(WITH_FUSE)
2420 file->inode_table = HashTable_New(FALSE);
2421 file->clip_data_table = HashTable_New(FALSE);
2422 file->request_table = HashTable_New(FALSE);
2423 if (!file->inode_table || !file->clip_data_table || !file->request_table)
2424 goto fail;
2425
2426 {
2427 wObject* ctobj = HashTable_ValueObject(file->request_table);
2428 WINPR_ASSERT(ctobj);
2429 ctobj->fnObjectFree = free;
2430 }
2431 {
2432 wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
2433 WINPR_ASSERT(ctobj);
2434 ctobj->fnObjectFree = clip_data_entry_free;
2435 }
2436
2437 file->root_dir = fuse_file_new_root(file);
2438 if (!file->root_dir)
2439 goto fail;
2440#endif
2441
2442 if (!create_base_path(file))
2443 goto fail;
2444
2445#if defined(WITH_FUSE)
2446 if (!(file->fuse_start_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
2447 goto fail;
2448 if (!(file->fuse_stop_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
2449 goto fail;
2450 if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL)))
2451 goto fail;
2452
2453 if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
2454 WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
2455#endif
2456 return file;
2457
2458fail:
2459 WINPR_PRAGMA_DIAG_PUSH
2460 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
2461 cliprdr_file_context_free(file);
2462 WINPR_PRAGMA_DIAG_POP
2463 return NULL;
2464}
2465
2466BOOL local_stream_discard(const void* key, void* value, void* arg)
2467{
2468 CliprdrFileContext* file = arg;
2469 CliprdrLocalStream* stream = value;
2470 WINPR_ASSERT(file);
2471 WINPR_ASSERT(stream);
2472
2473 if (!stream->locked)
2474 HashTable_Remove(file->local_streams, key);
2475 return TRUE;
2476}
2477
2478BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
2479{
2480 WINPR_ASSERT(file);
2481
2482 WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
2483
2484 HashTable_Lock(file->local_streams);
2485 HashTable_Foreach(file->local_streams, local_stream_discard, file);
2486 HashTable_Unlock(file->local_streams);
2487
2488 memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
2489 memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
2490 return TRUE;
2491}
2492
2493BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
2494 size_t size)
2495{
2496 BOOL rc = FALSE;
2497
2498 WINPR_ASSERT(file);
2499 if (!cliprdr_file_client_content_changed_and_update(file, data, size))
2500 return TRUE;
2501
2502 if (!cliprdr_file_context_clear(file))
2503 return FALSE;
2504
2505 UINT32 lockId = file->local_lock_id;
2506
2507 HashTable_Lock(file->local_streams);
2508 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
2509
2510 WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", stream);
2511 if (stream)
2512 rc = cliprdr_local_stream_update(stream, data, size);
2513 else
2514 {
2515 stream = cliprdr_local_stream_new(file, lockId, data, size);
2516 rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
2517 if (!rc)
2518 cliprdr_local_stream_free(stream);
2519 }
2520 // HashTable_Insert owns stream
2521 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
2522 HashTable_Unlock(file->local_streams);
2523 return rc;
2524}
2525
2526UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
2527{
2528 WINPR_ASSERT(file);
2529
2530 if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
2531 return 0;
2532
2533 if (!file->file_formats_registered)
2534 return 0;
2535
2536 return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
2537 CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
2538}
2539
2540BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
2541{
2542 WINPR_ASSERT(file);
2543 file->file_formats_registered = available;
2544 return TRUE;
2545}
2546
2547BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
2548{
2549 WINPR_ASSERT(file);
2550 file->file_capability_flags = flags;
2551 return TRUE;
2552}
2553
2554UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
2555{
2556 WINPR_ASSERT(file);
2557 return file->file_capability_flags;
2558}
2559
2560BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
2561{
2562 WINPR_UNUSED(file);
2563
2564#if defined(WITH_FUSE)
2565 return TRUE;
2566#else
2567 return FALSE;
2568#endif
2569}
This struct contains function pointer to initialize/free objects.
Definition collections.h:57