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