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 = 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 = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1113  child->filename, &attr, i + 1);
1114  if (entry_size > max_size - written_size)
1115  break;
1116 
1117  written_size += entry_size;
1118  }
1119  HashTable_Unlock(file_context->inode_table);
1120 
1121  fuse_reply_buf(fuse_req, buf, written_size);
1122  free(buf);
1123 }
1124 
1125 static void fuse_abort(int sig, const char* signame, void* context)
1126 {
1127  CliprdrFileContext* file = (CliprdrFileContext*)context;
1128 
1129  if (file)
1130  {
1131  WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
1132  cliprdr_file_session_terminate(file, FALSE);
1133  }
1134 }
1135 
1136 static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
1137 {
1138  CliprdrFileContext* file = (CliprdrFileContext*)arg;
1139 
1140  WINPR_ASSERT(file);
1141 
1142  DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
1143 
1144  struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
1145  fuse_opt_add_arg(&args, file->path);
1146  file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
1147  sizeof(cliprdr_file_fuse_oper), (void*)file);
1148  (void)SetEvent(file->fuse_start_sync);
1149 
1150  if (file->fuse_sess != NULL)
1151  {
1152  freerdp_add_signal_cleanup_handler(file, fuse_abort);
1153  if (0 == fuse_session_mount(file->fuse_sess, file->path))
1154  {
1155  fuse_session_loop(file->fuse_sess);
1156  fuse_session_unmount(file->fuse_sess);
1157  }
1158  freerdp_del_signal_cleanup_handler(file, fuse_abort);
1159 
1160  WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
1161  if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
1162  WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
1163  fuse_session_destroy(file->fuse_sess);
1164  }
1165  fuse_opt_free_args(&args);
1166 
1167  DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
1168 
1169  ExitThread(0);
1170  return 0;
1171 }
1172 
1173 static UINT cliprdr_file_context_server_file_contents_response(
1174  CliprdrClientContext* cliprdr_context,
1175  const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
1176 {
1177  CliprdrFileContext* file_context = NULL;
1178  CliprdrFuseRequest* fuse_request = NULL;
1179  struct fuse_entry_param entry = { 0 };
1180 
1181  WINPR_ASSERT(cliprdr_context);
1182  WINPR_ASSERT(file_contents_response);
1183 
1184  file_context = cliprdr_context->custom;
1185  WINPR_ASSERT(file_context);
1186 
1187  HashTable_Lock(file_context->inode_table);
1188  fuse_request = HashTable_GetItemValue(file_context->request_table,
1189  (void*)(uintptr_t)file_contents_response->streamId);
1190  if (!fuse_request)
1191  {
1192  HashTable_Unlock(file_context->inode_table);
1193  return CHANNEL_RC_OK;
1194  }
1195 
1196  if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
1197  {
1198  WLog_Print(file_context->log, WLOG_WARN,
1199  "FileContentsRequests for file \"%s\" was unsuccessful",
1200  fuse_request->fuse_file->filename);
1201 
1202  fuse_reply_err(fuse_request->fuse_req, EIO);
1203  HashTable_Remove(file_context->request_table,
1204  (void*)(uintptr_t)file_contents_response->streamId);
1205  HashTable_Unlock(file_context->inode_table);
1206  return CHANNEL_RC_OK;
1207  }
1208 
1209  if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1210  fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
1211  file_contents_response->cbRequested != sizeof(UINT64))
1212  {
1213  WLog_Print(file_context->log, WLOG_WARN,
1214  "Received invalid file size for file \"%s\" from the client",
1215  fuse_request->fuse_file->filename);
1216  fuse_reply_err(fuse_request->fuse_req, EIO);
1217  HashTable_Remove(file_context->request_table,
1218  (void*)(uintptr_t)file_contents_response->streamId);
1219  HashTable_Unlock(file_context->inode_table);
1220  return CHANNEL_RC_OK;
1221  }
1222 
1223  if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1224  fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
1225  {
1226  DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
1227  fuse_request->fuse_file->filename, file_contents_response->streamId);
1228 
1229  fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData);
1230  fuse_request->fuse_file->has_size = TRUE;
1231 
1232  entry.ino = fuse_request->fuse_file->ino;
1233  write_file_attributes(fuse_request->fuse_file, &entry.attr);
1234  entry.attr_timeout = 1.0;
1235  entry.entry_timeout = 1.0;
1236  }
1237  else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
1238  {
1239  DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
1240  fuse_request->fuse_file->filename, file_contents_response->streamId);
1241  }
1242  HashTable_Unlock(file_context->inode_table);
1243 
1244  switch (fuse_request->operation_type)
1245  {
1246  case FUSE_LL_OPERATION_NONE:
1247  break;
1248  case FUSE_LL_OPERATION_LOOKUP:
1249  fuse_reply_entry(fuse_request->fuse_req, &entry);
1250  break;
1251  case FUSE_LL_OPERATION_GETATTR:
1252  fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
1253  break;
1254  case FUSE_LL_OPERATION_READ:
1255  fuse_reply_buf(fuse_request->fuse_req,
1256  (const char*)file_contents_response->requestedData,
1257  file_contents_response->cbRequested);
1258  break;
1259  default:
1260  break;
1261  }
1262 
1263  HashTable_Remove(file_context->request_table,
1264  (void*)(uintptr_t)file_contents_response->streamId);
1265 
1266  return CHANNEL_RC_OK;
1267 }
1268 #endif
1269 
1270 static UINT cliprdr_file_context_send_file_contents_failure(
1271  CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1272 {
1273  CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
1274 
1275  WINPR_ASSERT(file);
1276  WINPR_ASSERT(fileContentsRequest);
1277 
1278  const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1279  ((UINT64)fileContentsRequest->nPositionLow);
1280  writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1281  "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
1282  ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
1283  fileContentsRequest->clipDataId, fileContentsRequest->streamId,
1284  fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
1285 
1286  response.common.msgFlags = CB_RESPONSE_FAIL;
1287  response.streamId = fileContentsRequest->streamId;
1288 
1289  WINPR_ASSERT(file->context);
1290  WINPR_ASSERT(file->context->ClientFileContentsResponse);
1291  return file->context->ClientFileContentsResponse(file->context, &response);
1292 }
1293 
1294 static UINT
1295 cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
1296  const CLIPRDR_FILE_CONTENTS_REQUEST* request,
1297  const void* data, size_t size)
1298 {
1299  if (size > UINT32_MAX)
1300  return ERROR_INVALID_PARAMETER;
1301 
1302  CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
1303  .requestedData = data,
1304  .cbRequested = (UINT32)size,
1305  .common.msgFlags = CB_RESPONSE_OK };
1306 
1307  WINPR_ASSERT(request);
1308  WINPR_ASSERT(file);
1309 
1310  WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
1311  response.streamId, response.cbRequested);
1312  WINPR_ASSERT(file->context);
1313  WINPR_ASSERT(file->context->ClientFileContentsResponse);
1314  return file->context->ClientFileContentsResponse(file->context, &response);
1315 }
1316 
1317 static BOOL dump_streams(const void* key, void* value, void* arg)
1318 {
1319  const UINT32* ukey = key;
1320  CliprdrLocalStream* cur = value;
1321 
1322  writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1323  "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
1324  cur->lockId, cur->count, cur->locked);
1325  for (size_t x = 0; x < cur->count; x++)
1326  {
1327  const CliprdrLocalFile* file = &cur->files[x];
1328  writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, "file [%" PRIuz "] ",
1329  x, file->name, file->size);
1330  }
1331  return TRUE;
1332 }
1333 
1334 static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
1335  UINT32 listIndex)
1336 {
1337  WINPR_ASSERT(file);
1338 
1339  CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
1340  if (cur)
1341  {
1342  if (listIndex < cur->count)
1343  {
1344  CliprdrLocalFile* f = &cur->files[listIndex];
1345  return f;
1346  }
1347  else
1348  {
1349  writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1350  "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIu32
1351  "] [locked %d]",
1352  lockId, listIndex, cur->count, cur->locked);
1353  }
1354  }
1355  else
1356  {
1357  writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1358  "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
1359  HashTable_Foreach(file->local_streams, dump_streams, file);
1360  }
1361 
1362  return NULL;
1363 }
1364 
1365 static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
1366 {
1367  CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
1368  if (f)
1369  {
1370  if (!f->fp)
1371  {
1372  const char* name = f->name;
1373  f->fp = winpr_fopen(name, "rb");
1374  }
1375  if (!f->fp)
1376  {
1377  char ebuffer[256] = { 0 };
1378  writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1379  "[lockID %" PRIu32 ", index %" PRIu32
1380  "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
1381  lockId, listIndex, f->name, f->size,
1382  winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1383  return NULL;
1384  }
1385  }
1386 
1387  return f;
1388 }
1389 
1390 static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
1391  UINT64 size)
1392 {
1393  WINPR_ASSERT(file);
1394 
1395  if (res != 0)
1396  {
1397  WINPR_ASSERT(file->context);
1398  WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
1399  file->name, res);
1400  }
1401  else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
1402  {
1403  WINPR_ASSERT(file->context);
1404  WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
1405  }
1406  else
1407  {
1408  // TODO: we need to keep track of open files to avoid running out of file descriptors
1409  // TODO: for the time being just close again.
1410  }
1411  if (file->fp)
1412  (void)fclose(file->fp);
1413  file->fp = NULL;
1414 }
1415 
1416 static UINT cliprdr_file_context_server_file_size_request(
1417  CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1418 {
1419  WINPR_ASSERT(fileContentsRequest);
1420 
1421  if (fileContentsRequest->cbRequested != sizeof(UINT64))
1422  {
1423  WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
1424  fileContentsRequest->cbRequested);
1425  }
1426 
1427  HashTable_Lock(file->local_streams);
1428 
1429  UINT res = CHANNEL_RC_OK;
1430  CliprdrLocalFile* rfile =
1431  file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1432  if (!rfile)
1433  res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1434  else
1435  {
1436  if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
1437  res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1438  else
1439  {
1440  const INT64 size = _ftelli64(rfile->fp);
1441  rfile->size = size;
1442  cliprdr_local_file_try_close(rfile, res, 0, 0);
1443 
1444  res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
1445  sizeof(size));
1446  }
1447  }
1448 
1449  HashTable_Unlock(file->local_streams);
1450  return res;
1451 }
1452 
1453 static UINT cliprdr_file_context_server_file_range_request(
1454  CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1455 {
1456  BYTE* data = NULL;
1457 
1458  WINPR_ASSERT(fileContentsRequest);
1459 
1460  HashTable_Lock(file->local_streams);
1461  const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1462  ((UINT64)fileContentsRequest->nPositionLow);
1463 
1464  CliprdrLocalFile* rfile =
1465  file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1466  if (!rfile)
1467  goto fail;
1468 
1469  if (_fseeki64(rfile->fp, offset, SEEK_SET) < 0)
1470  goto fail;
1471 
1472  data = malloc(fileContentsRequest->cbRequested);
1473  if (!data)
1474  goto fail;
1475 
1476  const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
1477  const UINT rc = cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
1478  free(data);
1479 
1480  cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
1481  HashTable_Unlock(file->local_streams);
1482  return rc;
1483 fail:
1484  if (rfile)
1485  cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
1486  fileContentsRequest->cbRequested);
1487  free(data);
1488  HashTable_Unlock(file->local_streams);
1489  return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1490 }
1491 
1492 static void cliprdr_local_stream_free(void* obj);
1493 
1494 static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
1495 {
1496  UINT rc = CHANNEL_RC_OK;
1497 
1498  WINPR_ASSERT(file);
1499 
1500  HashTable_Lock(file->local_streams);
1501 
1502  {
1503  CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
1504  if (lock && !stream)
1505  {
1506  stream = cliprdr_local_stream_new(file, lockId, NULL, 0);
1507  if (!HashTable_Insert(file->local_streams, &lockId, stream))
1508  {
1509  rc = ERROR_INTERNAL_ERROR;
1510  cliprdr_local_stream_free(stream);
1511  stream = NULL;
1512  }
1513  file->local_lock_id = lockId;
1514  }
1515  if (stream)
1516  {
1517  stream->locked = lock;
1518  stream->lockId = lockId;
1519  }
1520  }
1521 
1522  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership stream
1523  if (!lock)
1524  {
1525  if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
1526  rc = ERROR_INTERNAL_ERROR;
1527  }
1528 
1529  HashTable_Unlock(file->local_streams);
1530  return rc;
1531 }
1532 
1533 static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
1534  const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
1535 {
1536  WINPR_ASSERT(context);
1537  WINPR_ASSERT(lockClipboardData);
1538  CliprdrFileContext* file = (context->custom);
1539  return change_lock(file, lockClipboardData->clipDataId, TRUE);
1540 }
1541 
1542 static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
1543  const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
1544 {
1545  WINPR_ASSERT(context);
1546  WINPR_ASSERT(unlockClipboardData);
1547  CliprdrFileContext* file = (context->custom);
1548  return change_lock(file, unlockClipboardData->clipDataId, FALSE);
1549 }
1550 
1551 static UINT cliprdr_file_context_server_file_contents_request(
1552  CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1553 {
1554  UINT error = NO_ERROR;
1555 
1556  WINPR_ASSERT(context);
1557  WINPR_ASSERT(fileContentsRequest);
1558 
1559  CliprdrFileContext* file = (context->custom);
1560  WINPR_ASSERT(file);
1561 
1562  /*
1563  * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
1564  * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
1565  */
1566  if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
1567  (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
1568  {
1569  WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
1570  return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1571  }
1572 
1573  if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
1574  error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
1575 
1576  if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
1577  error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
1578 
1579  if (error)
1580  {
1581  WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
1582  error);
1583  return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1584  }
1585 
1586  return CHANNEL_RC_OK;
1587 }
1588 
1589 BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
1590 {
1591  WINPR_ASSERT(file);
1592  WINPR_ASSERT(cliprdr);
1593 
1594  cliprdr->custom = file;
1595  file->context = cliprdr;
1596 
1597  cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
1598  cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
1599  cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
1600 #if defined(WITH_FUSE)
1601  cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
1602 #endif
1603 
1604  return TRUE;
1605 }
1606 
1607 #if defined(WITH_FUSE)
1608 static void clear_all_selections(CliprdrFileContext* file_context)
1609 {
1610  WINPR_ASSERT(file_context);
1611  WINPR_ASSERT(file_context->inode_table);
1612 
1613  HashTable_Lock(file_context->inode_table);
1614  clear_selection(file_context, TRUE, NULL);
1615 
1616  HashTable_Clear(file_context->clip_data_table);
1617  HashTable_Unlock(file_context->inode_table);
1618 }
1619 #endif
1620 
1621 BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
1622 {
1623  WINPR_ASSERT(file);
1624  WINPR_ASSERT(cliprdr);
1625 
1626  // Clear all data before the channel is closed
1627  // the cleanup handlers are dependent on a working channel.
1628 #if defined(WITH_FUSE)
1629  if (file->inode_table)
1630  {
1631  clear_no_cdi_entry(file);
1632  clear_all_selections(file);
1633  }
1634 #endif
1635 
1636  HashTable_Clear(file->local_streams);
1637 
1638  file->context = NULL;
1639 #if defined(WITH_FUSE)
1640  cliprdr->ServerFileContentsResponse = NULL;
1641 #endif
1642 
1643  return TRUE;
1644 }
1645 
1646 static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
1647  size_t size)
1648 {
1649 
1650  BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
1651 
1652  if (hsize < sizeof(hash))
1653  return FALSE;
1654 
1655  if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
1656  return FALSE;
1657 
1658  const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
1659  if (changed)
1660  memcpy(ihash, hash, sizeof(hash));
1661  return changed;
1662 }
1663 
1664 static BOOL cliprdr_file_server_content_changed_and_update(CliprdrFileContext* file,
1665  const void* data, size_t size)
1666 {
1667  WINPR_ASSERT(file);
1668  return cliprdr_file_content_changed_and_update(file->server_data_hash,
1669  sizeof(file->server_data_hash), data, size);
1670 }
1671 
1672 static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
1673  const void* data, size_t size)
1674 {
1675  WINPR_ASSERT(file);
1676  return cliprdr_file_content_changed_and_update(file->client_data_hash,
1677  sizeof(file->client_data_hash), data, size);
1678 }
1679 
1680 #if defined(WITH_FUSE)
1681 static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
1682 {
1683  fuse_ino_t ino = 0;
1684 
1685  WINPR_ASSERT(file_context);
1686 
1687  ino = file_context->next_ino;
1688  while (ino == 0 || ino == FUSE_ROOT_ID ||
1689  HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)ino))
1690  ++ino;
1691 
1692  file_context->next_ino = ino + 1;
1693 
1694  return ino;
1695 }
1696 
1697 static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
1698  UINT32 clip_data_id)
1699 {
1700  CliprdrFuseFile* root_dir = NULL;
1701  CliprdrFuseFile* clip_data_dir = NULL;
1702  size_t path_length = 0;
1703 
1704  WINPR_ASSERT(file_context);
1705 
1706  clip_data_dir = fuse_file_new();
1707  if (!clip_data_dir)
1708  return NULL;
1709 
1710  path_length = 1 + MAX_CLIP_DATA_DIR_LEN + 1;
1711 
1712  clip_data_dir->filename_with_root = calloc(path_length, sizeof(char));
1713  if (!clip_data_dir->filename_with_root)
1714  {
1715  WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
1716  fuse_file_free(clip_data_dir);
1717  return NULL;
1718  }
1719 
1720  if (has_clip_data_id)
1721  (void)_snprintf(clip_data_dir->filename_with_root, path_length, "/%u",
1722  (unsigned)clip_data_id);
1723  else
1724  (void)_snprintf(clip_data_dir->filename_with_root, path_length, "/%" PRIu64,
1725  NO_CLIP_DATA_ID);
1726 
1727  clip_data_dir->filename = strrchr(clip_data_dir->filename_with_root, '/') + 1;
1728 
1729  clip_data_dir->ino = get_next_free_inode(file_context);
1730  clip_data_dir->is_directory = TRUE;
1731  clip_data_dir->is_readonly = TRUE;
1732  clip_data_dir->has_clip_data_id = has_clip_data_id;
1733  clip_data_dir->clip_data_id = clip_data_id;
1734 
1735  root_dir = file_context->root_dir;
1736  if (!ArrayList_Append(root_dir->children, clip_data_dir))
1737  {
1738  WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
1739  fuse_file_free(clip_data_dir);
1740  return NULL;
1741  }
1742  clip_data_dir->parent = root_dir;
1743 
1744  if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)clip_data_dir->ino,
1745  clip_data_dir))
1746  {
1747  WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
1748  ArrayList_Remove(root_dir->children, clip_data_dir);
1749  fuse_file_free(clip_data_dir);
1750  return NULL;
1751  }
1752 
1753  return clip_data_dir;
1754 }
1755 
1756 static char* get_parent_path(const char* filepath)
1757 {
1758  char* base = NULL;
1759  size_t parent_path_length = 0;
1760  char* parent_path = NULL;
1761 
1762  base = strrchr(filepath, '/');
1763  WINPR_ASSERT(base);
1764 
1765  while (base > filepath && *base == '/')
1766  --base;
1767 
1768  parent_path_length = 1 + base - filepath;
1769  parent_path = calloc(parent_path_length + 1, sizeof(char));
1770  if (!parent_path)
1771  return NULL;
1772 
1773  memcpy(parent_path, filepath, parent_path_length);
1774 
1775  return parent_path;
1776 }
1777 
1778 static BOOL is_fuse_file_not_parent(const void* key, void* value, void* arg)
1779 {
1780  CliprdrFuseFile* fuse_file = value;
1781  CliprdrFuseFindParentContext* find_context = arg;
1782 
1783  if (!fuse_file->is_directory)
1784  return TRUE;
1785 
1786  if (strcmp(find_context->parent_path, fuse_file->filename_with_root) == 0)
1787  {
1788  find_context->parent = fuse_file;
1789  return FALSE;
1790  }
1791 
1792  return TRUE;
1793 }
1794 
1795 static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
1796 {
1797  CliprdrFuseFindParentContext find_context = { 0 };
1798 
1799  WINPR_ASSERT(file_context);
1800  WINPR_ASSERT(path);
1801 
1802  find_context.parent_path = get_parent_path(path);
1803  if (!find_context.parent_path)
1804  return NULL;
1805 
1806  WINPR_ASSERT(!find_context.parent);
1807 
1808  if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context))
1809  {
1810  free(find_context.parent_path);
1811  return NULL;
1812  }
1813  WINPR_ASSERT(find_context.parent);
1814 
1815  free(find_context.parent_path);
1816 
1817  return find_context.parent;
1818 }
1819 
1820 static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
1821  CliprdrFuseClipDataEntry* clip_data_entry,
1822  const FILEDESCRIPTORW* files, UINT32 n_files)
1823 {
1824  CliprdrFuseFile* clip_data_dir = NULL;
1825 
1826  WINPR_ASSERT(file_context);
1827  WINPR_ASSERT(clip_data_entry);
1828  WINPR_ASSERT(files);
1829 
1830  clip_data_dir = clip_data_entry->clip_data_dir;
1831  WINPR_ASSERT(clip_data_dir);
1832 
1833  if (clip_data_entry->has_clip_data_id)
1834  WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
1835  clip_data_entry->clip_data_id);
1836  else
1837  WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
1838 
1839  // NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
1840  for (UINT32 i = 0; i < n_files; ++i)
1841  {
1842  const FILEDESCRIPTORW* file = &files[i];
1843  CliprdrFuseFile* fuse_file = NULL;
1844  char* filename = NULL;
1845  size_t path_length = 0;
1846 
1847  fuse_file = fuse_file_new();
1848  if (!fuse_file)
1849  {
1850  WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
1851  clear_entry_selection(clip_data_entry);
1852  return FALSE;
1853  }
1854 
1855  filename = ConvertWCharToUtf8Alloc(file->cFileName, NULL);
1856  if (!filename)
1857  {
1858  WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
1859  fuse_file_free(fuse_file);
1860  clear_entry_selection(clip_data_entry);
1861  return FALSE;
1862  }
1863 
1864  for (size_t j = 0; filename[j]; ++j)
1865  {
1866  if (filename[j] == '\\')
1867  filename[j] = '/';
1868  }
1869 
1870  path_length = strlen(clip_data_dir->filename_with_root) + 1 + strlen(filename) + 1;
1871  fuse_file->filename_with_root = calloc(path_length, sizeof(char));
1872  if (!fuse_file->filename_with_root)
1873  {
1874  WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
1875  free(filename);
1876  fuse_file_free(fuse_file);
1877  clear_entry_selection(clip_data_entry);
1878  return FALSE;
1879  }
1880 
1881  (void)_snprintf(fuse_file->filename_with_root, path_length, "%s/%s",
1882  clip_data_dir->filename_with_root, filename);
1883  free(filename);
1884 
1885  fuse_file->filename = strrchr(fuse_file->filename_with_root, '/') + 1;
1886 
1887  fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
1888  if (!fuse_file->parent)
1889  {
1890  WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
1891  fuse_file_free(fuse_file);
1892  clear_entry_selection(clip_data_entry);
1893  return FALSE;
1894  }
1895 
1896  if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
1897  {
1898  WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
1899  fuse_file_free(fuse_file);
1900  clear_entry_selection(clip_data_entry);
1901  return FALSE;
1902  }
1903 
1904  fuse_file->list_idx = i;
1905  fuse_file->ino = get_next_free_inode(file_context);
1906  fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
1907  fuse_file->clip_data_id = clip_data_entry->clip_data_id;
1908  if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1909  fuse_file->is_directory = TRUE;
1910  if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
1911  fuse_file->is_readonly = TRUE;
1912  if (file->dwFlags & FD_FILESIZE)
1913  {
1914  fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
1915  fuse_file->has_size = TRUE;
1916  }
1917  if (file->dwFlags & FD_WRITESTIME)
1918  {
1919  INT64 filetime = 0;
1920 
1921  filetime = file->ftLastWriteTime.dwHighDateTime;
1922  filetime <<= 32;
1923  filetime += file->ftLastWriteTime.dwLowDateTime;
1924 
1925  fuse_file->last_write_time_unix =
1926  1LL * filetime / (10LL * 1000LL * 1000LL) - WIN32_FILETIME_TO_UNIX_EPOCH;
1927  fuse_file->has_last_write_time = TRUE;
1928  }
1929 
1930  if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)fuse_file->ino,
1931  fuse_file))
1932  {
1933  WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
1934  fuse_file_free(fuse_file);
1935  clear_entry_selection(clip_data_entry);
1936  return FALSE;
1937  }
1938  }
1939  // NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
1940 
1941  if (clip_data_entry->has_clip_data_id)
1942  WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
1943  clip_data_entry->clip_data_id);
1944  else
1945  WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
1946 
1947  return TRUE;
1948 }
1949 
1950 static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
1951  CliprdrFuseClipDataEntry* clip_data_entry)
1952 {
1953  wClipboardDelegate* delegate = NULL;
1954  CliprdrFuseFile* clip_data_dir = NULL;
1955 
1956  WINPR_ASSERT(file_context);
1957  WINPR_ASSERT(clip);
1958  WINPR_ASSERT(clip_data_entry);
1959 
1960  delegate = ClipboardGetDelegate(clip);
1961  WINPR_ASSERT(delegate);
1962 
1963  clip_data_dir = clip_data_entry->clip_data_dir;
1964  WINPR_ASSERT(clip_data_dir);
1965 
1966  free(file_context->exposed_path);
1967  file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
1968  if (file_context->exposed_path)
1969  WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
1970  file_context->exposed_path);
1971 
1972  delegate->basePath = file_context->exposed_path;
1973 
1974  return delegate->basePath != NULL;
1975 }
1976 #endif
1977 
1978 BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
1979  const void* data, size_t size)
1980 {
1981 #if defined(WITH_FUSE)
1982  CliprdrFuseClipDataEntry* clip_data_entry = NULL;
1983  FILEDESCRIPTORW* files = NULL;
1984  UINT32 n_files = 0;
1985  BOOL rc = FALSE;
1986 
1987  WINPR_ASSERT(file_context);
1988  WINPR_ASSERT(clip);
1989  if (size > UINT32_MAX)
1990  return FALSE;
1991 
1992  if (cliprdr_parse_file_list(data, (UINT32)size, &files, &n_files))
1993  {
1994  WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
1995  return FALSE;
1996  }
1997 
1998  HashTable_Lock(file_context->inode_table);
1999  HashTable_Lock(file_context->clip_data_table);
2000  if (does_server_support_clipdata_locking(file_context))
2001  clip_data_entry = HashTable_GetItemValue(
2002  file_context->clip_data_table, (void*)(uintptr_t)file_context->current_clip_data_id);
2003  else
2004  clip_data_entry = file_context->clip_data_entry_without_id;
2005 
2006  WINPR_ASSERT(clip_data_entry);
2007 
2008  clear_entry_selection(clip_data_entry);
2009  WINPR_ASSERT(!clip_data_entry->clip_data_dir);
2010 
2011  clip_data_entry->clip_data_dir =
2012  clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
2013  file_context->current_clip_data_id);
2014  if (!clip_data_entry->clip_data_dir)
2015  goto fail;
2016  if (!update_exposed_path(file_context, clip, clip_data_entry))
2017  goto fail;
2018  if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
2019  goto fail;
2020 
2021  rc = TRUE;
2022 
2023 fail:
2024  HashTable_Unlock(file_context->clip_data_table);
2025  HashTable_Unlock(file_context->inode_table);
2026  free(files);
2027  return rc;
2028 #else
2029  return FALSE;
2030 #endif
2031 }
2032 
2033 void* cliprdr_file_context_get_context(CliprdrFileContext* file)
2034 {
2035  WINPR_ASSERT(file);
2036  return file->clipboard;
2037 }
2038 
2039 void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
2040 {
2041  if (!file)
2042  return;
2043 
2044 #if defined(WITH_FUSE)
2045  WINPR_ASSERT(file->fuse_stop_sync);
2046 
2047  WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
2048  if (file->fuse_sess)
2049  fuse_session_exit(file->fuse_sess);
2050 
2051  if (stop_thread)
2052  {
2053  WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
2054  (void)SetEvent(file->fuse_stop_sync);
2055  }
2056 #endif
2057  /* not elegant but works for umounting FUSE
2058  fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
2059  */
2060 #if defined(WITH_FUSE)
2061  WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
2062 #endif
2063  (void)winpr_PathFileExists(file->path);
2064 }
2065 
2066 void cliprdr_file_context_free(CliprdrFileContext* file)
2067 {
2068  if (!file)
2069  return;
2070 
2071 #if defined(WITH_FUSE)
2072  if (file->inode_table)
2073  {
2074  clear_no_cdi_entry(file);
2075  clear_all_selections(file);
2076  }
2077 
2078  if (file->fuse_thread)
2079  {
2080  WINPR_ASSERT(file->fuse_stop_sync);
2081 
2082  WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
2083  cliprdr_file_session_terminate(file, TRUE);
2084 
2085  WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
2086  (void)WaitForSingleObject(file->fuse_thread, INFINITE);
2087  (void)CloseHandle(file->fuse_thread);
2088  }
2089  if (file->fuse_stop_sync)
2090  (void)CloseHandle(file->fuse_stop_sync);
2091  if (file->fuse_start_sync)
2092  (void)CloseHandle(file->fuse_start_sync);
2093 
2094  HashTable_Free(file->request_table);
2095  HashTable_Free(file->clip_data_table);
2096  HashTable_Free(file->inode_table);
2097 #endif
2098  HashTable_Free(file->local_streams);
2099  winpr_RemoveDirectory(file->path);
2100  free(file->path);
2101  free(file->exposed_path);
2102  free(file);
2103 }
2104 
2105 static BOOL create_base_path(CliprdrFileContext* file)
2106 {
2107  WINPR_ASSERT(file);
2108 
2109  char base[64] = { 0 };
2110  (void)_snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32,
2111  GetCurrentProcessId());
2112 
2113  file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
2114  if (!file->path)
2115  return FALSE;
2116 
2117  if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0))
2118  {
2119  WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
2120  return FALSE;
2121  }
2122  return TRUE;
2123 }
2124 
2125 static void cliprdr_local_file_free(CliprdrLocalFile* file)
2126 {
2127  const CliprdrLocalFile empty = { 0 };
2128  if (!file)
2129  return;
2130  if (file->fp)
2131  {
2132  WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
2133  (void)fclose(file->fp);
2134  }
2135  free(file->name);
2136  *file = empty;
2137 }
2138 
2139 static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
2140  const char* path)
2141 {
2142  const CliprdrLocalFile empty = { 0 };
2143  WINPR_ASSERT(f);
2144  WINPR_ASSERT(context);
2145  WINPR_ASSERT(path);
2146 
2147  *f = empty;
2148  f->context = context;
2149  f->name = winpr_str_url_decode(path, strlen(path));
2150  if (!f->name)
2151  goto fail;
2152 
2153  return TRUE;
2154 fail:
2155  cliprdr_local_file_free(f);
2156  return FALSE;
2157 }
2158 
2159 static void cliprdr_local_files_free(CliprdrLocalStream* stream)
2160 {
2161  WINPR_ASSERT(stream);
2162 
2163  for (size_t x = 0; x < stream->count; x++)
2164  cliprdr_local_file_free(&stream->files[x]);
2165  free(stream->files);
2166 
2167  stream->files = NULL;
2168  stream->count = 0;
2169 }
2170 
2171 static void cliprdr_local_stream_free(void* obj)
2172 {
2173  CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
2174  if (stream)
2175  cliprdr_local_files_free(stream);
2176 
2177  free(stream);
2178 }
2179 
2180 static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
2181 {
2182  CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
2183  if (!tmp)
2184  return FALSE;
2185  stream->files = tmp;
2186  CliprdrLocalFile* f = &stream->files[stream->count++];
2187 
2188  return cliprdr_local_file_new(stream->context, f, path);
2189 }
2190 
2191 static BOOL is_directory(const char* path)
2192 {
2193  WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, NULL);
2194  if (!wpath)
2195  return FALSE;
2196 
2197  HANDLE hFile =
2198  CreateFileW(wpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2199  free(wpath);
2200 
2201  if (hFile == INVALID_HANDLE_VALUE)
2202  return FALSE;
2203 
2204  BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
2205  const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
2206  (void)CloseHandle(hFile);
2207  if (!status)
2208  return FALSE;
2209 
2210  return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
2211 }
2212 
2213 static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
2214 {
2215  char* wildcardpath = GetCombinedPath(path, "*");
2216  if (!wildcardpath)
2217  return FALSE;
2218  WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, NULL);
2219  free(wildcardpath);
2220  if (!wpath)
2221  return FALSE;
2222 
2223  WIN32_FIND_DATAW FindFileData = { 0 };
2224  HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
2225  free(wpath);
2226 
2227  if (hFind == INVALID_HANDLE_VALUE)
2228  return FALSE;
2229 
2230  BOOL rc = FALSE;
2231  char* next = NULL;
2232 
2233  WCHAR dotbuffer[6] = { 0 };
2234  WCHAR dotdotbuffer[6] = { 0 };
2235  const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
2236  const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
2237  do
2238  {
2239  if (_wcscmp(FindFileData.cFileName, dot) == 0)
2240  continue;
2241  if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
2242  continue;
2243 
2244  char cFileName[MAX_PATH] = { 0 };
2245  (void)ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName),
2246  cFileName, ARRAYSIZE(cFileName));
2247 
2248  free(next);
2249  next = GetCombinedPath(path, cFileName);
2250  if (!next)
2251  goto fail;
2252 
2253  if (!append_entry(stream, next))
2254  goto fail;
2255  if (is_directory(next))
2256  {
2257  if (!add_directory(stream, next))
2258  goto fail;
2259  }
2260  } while (FindNextFileW(hFind, &FindFileData));
2261 
2262  rc = TRUE;
2263 fail:
2264  free(next);
2265  FindClose(hFind);
2266 
2267  return rc;
2268 }
2269 
2270 static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
2271 {
2272  BOOL rc = FALSE;
2273  WINPR_ASSERT(stream);
2274  if (size == 0)
2275  return TRUE;
2276 
2277  cliprdr_local_files_free(stream);
2278 
2279  stream->files = calloc(size, sizeof(CliprdrLocalFile));
2280  if (!stream->files)
2281  return FALSE;
2282 
2283  char* copy = strndup(data, size);
2284  if (!copy)
2285  return FALSE;
2286 
2287  char* saveptr = NULL;
2288  char* ptr = strtok_s(copy, "\r\n", &saveptr);
2289  while (ptr)
2290  {
2291  const char* name = ptr;
2292  if (strncmp("file:///", ptr, 8) == 0)
2293  name = &ptr[7];
2294  else if (strncmp("file:/", ptr, 6) == 0)
2295  name = &ptr[5];
2296 
2297  if (!append_entry(stream, name))
2298  goto fail;
2299 
2300  if (is_directory(name))
2301  {
2302  const BOOL res = add_directory(stream, name);
2303  if (!res)
2304  goto fail;
2305  }
2306  ptr = strtok_s(NULL, "\r\n", &saveptr);
2307  }
2308 
2309  rc = TRUE;
2310 fail:
2311  free(copy);
2312  return rc;
2313 }
2314 
2315 CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
2316  const char* data, size_t size)
2317 {
2318  WINPR_ASSERT(context);
2319  CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
2320  if (!stream)
2321  return NULL;
2322 
2323  stream->context = context;
2324  if (!cliprdr_local_stream_update(stream, data, size))
2325  goto fail;
2326 
2327  stream->lockId = streamID;
2328  return stream;
2329 
2330 fail:
2331  cliprdr_local_stream_free(stream);
2332  return NULL;
2333 }
2334 
2335 static UINT32 UINTPointerHash(const void* id)
2336 {
2337  WINPR_ASSERT(id);
2338  return *((const UINT32*)id);
2339 }
2340 
2341 static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
2342 {
2343  if (!pointer1 || !pointer2)
2344  return pointer1 == pointer2;
2345 
2346  const UINT32* a = pointer1;
2347  const UINT32* b = pointer2;
2348  return *a == *b;
2349 }
2350 
2351 static void* UINTPointerClone(const void* other)
2352 {
2353  const UINT32* src = other;
2354  if (!src)
2355  return NULL;
2356 
2357  UINT32* copy = calloc(1, sizeof(UINT32));
2358  if (!copy)
2359  return NULL;
2360 
2361  *copy = *src;
2362  return copy;
2363 }
2364 
2365 #if defined(WITH_FUSE)
2366 static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
2367 {
2368  CliprdrFuseFile* root_dir = NULL;
2369 
2370  root_dir = fuse_file_new();
2371  if (!root_dir)
2372  return NULL;
2373 
2374  root_dir->filename_with_root = calloc(2, sizeof(char));
2375  if (!root_dir->filename_with_root)
2376  {
2377  fuse_file_free(root_dir);
2378  return NULL;
2379  }
2380 
2381  (void)_snprintf(root_dir->filename_with_root, 2, "/");
2382  root_dir->filename = root_dir->filename_with_root;
2383 
2384  root_dir->ino = FUSE_ROOT_ID;
2385  root_dir->is_directory = TRUE;
2386  root_dir->is_readonly = TRUE;
2387 
2388  if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)root_dir->ino, root_dir))
2389  {
2390  fuse_file_free(root_dir);
2391  return NULL;
2392  }
2393 
2394  return root_dir;
2395 }
2396 #endif
2397 
2398 CliprdrFileContext* cliprdr_file_context_new(void* context)
2399 {
2400  CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
2401  if (!file)
2402  return NULL;
2403 
2404  file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
2405  file->clipboard = context;
2406 
2407  file->local_streams = HashTable_New(FALSE);
2408  if (!file->local_streams)
2409  goto fail;
2410 
2411  if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
2412  goto fail;
2413 
2414  wObject* hkobj = HashTable_KeyObject(file->local_streams);
2415  WINPR_ASSERT(hkobj);
2416  hkobj->fnObjectEquals = UINTPointerCompare;
2417  hkobj->fnObjectFree = free;
2418  hkobj->fnObjectNew = UINTPointerClone;
2419 
2420  wObject* hobj = HashTable_ValueObject(file->local_streams);
2421  WINPR_ASSERT(hobj);
2422  hobj->fnObjectFree = cliprdr_local_stream_free;
2423 
2424 #if defined(WITH_FUSE)
2425  file->inode_table = HashTable_New(FALSE);
2426  file->clip_data_table = HashTable_New(FALSE);
2427  file->request_table = HashTable_New(FALSE);
2428  if (!file->inode_table || !file->clip_data_table || !file->request_table)
2429  goto fail;
2430 
2431  {
2432  wObject* ctobj = HashTable_ValueObject(file->request_table);
2433  WINPR_ASSERT(ctobj);
2434  ctobj->fnObjectFree = free;
2435  }
2436  {
2437  wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
2438  WINPR_ASSERT(ctobj);
2439  ctobj->fnObjectFree = clip_data_entry_free;
2440  }
2441 
2442  file->root_dir = fuse_file_new_root(file);
2443  if (!file->root_dir)
2444  goto fail;
2445 #endif
2446 
2447  if (!create_base_path(file))
2448  goto fail;
2449 
2450 #if defined(WITH_FUSE)
2451  if (!(file->fuse_start_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
2452  goto fail;
2453  if (!(file->fuse_stop_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
2454  goto fail;
2455  if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL)))
2456  goto fail;
2457 
2458  if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
2459  WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
2460 #endif
2461  return file;
2462 
2463 fail:
2464  WINPR_PRAGMA_DIAG_PUSH
2465  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
2466  cliprdr_file_context_free(file);
2467  WINPR_PRAGMA_DIAG_POP
2468  return NULL;
2469 }
2470 
2471 BOOL local_stream_discard(const void* key, void* value, void* arg)
2472 {
2473  CliprdrFileContext* file = arg;
2474  CliprdrLocalStream* stream = value;
2475  WINPR_ASSERT(file);
2476  WINPR_ASSERT(stream);
2477 
2478  if (!stream->locked)
2479  HashTable_Remove(file->local_streams, key);
2480  return TRUE;
2481 }
2482 
2483 BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
2484 {
2485  WINPR_ASSERT(file);
2486 
2487  WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
2488 
2489  HashTable_Lock(file->local_streams);
2490  HashTable_Foreach(file->local_streams, local_stream_discard, file);
2491  HashTable_Unlock(file->local_streams);
2492 
2493  memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
2494  memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
2495  return TRUE;
2496 }
2497 
2498 BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
2499  size_t size)
2500 {
2501  BOOL rc = FALSE;
2502 
2503  WINPR_ASSERT(file);
2504  if (!cliprdr_file_client_content_changed_and_update(file, data, size))
2505  return TRUE;
2506 
2507  if (!cliprdr_file_context_clear(file))
2508  return FALSE;
2509 
2510  UINT32 lockId = file->local_lock_id;
2511 
2512  HashTable_Lock(file->local_streams);
2513  CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
2514 
2515  WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", stream);
2516  if (stream)
2517  rc = cliprdr_local_stream_update(stream, data, size);
2518  else
2519  {
2520  stream = cliprdr_local_stream_new(file, lockId, data, size);
2521  rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
2522  if (!rc)
2523  cliprdr_local_stream_free(stream);
2524  }
2525  // HashTable_Insert owns stream
2526  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
2527  HashTable_Unlock(file->local_streams);
2528  return rc;
2529 }
2530 
2531 UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
2532 {
2533  WINPR_ASSERT(file);
2534 
2535  if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
2536  return 0;
2537 
2538  if (!file->file_formats_registered)
2539  return 0;
2540 
2541  return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
2542  CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
2543 }
2544 
2545 BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
2546 {
2547  WINPR_ASSERT(file);
2548  file->file_formats_registered = available;
2549  return TRUE;
2550 }
2551 
2552 BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
2553 {
2554  WINPR_ASSERT(file);
2555  file->file_capability_flags = flags;
2556  return TRUE;
2557 }
2558 
2559 UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
2560 {
2561  WINPR_ASSERT(file);
2562  return file->file_capability_flags;
2563 }
2564 
2565 BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
2566 {
2567  WINPR_UNUSED(file);
2568 
2569 #if defined(WITH_FUSE)
2570  return TRUE;
2571 #else
2572  return FALSE;
2573 #endif
2574 }
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57