/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "core/clipboard_files.h" #include "core/clipboard.h" #include "platform/windows/clipboard_files.h" #include "platform/windows/clipboard_files_win32.h" #include "common/debug.h" #include "common/event.h" #include "common/locking.h" #include "common/random.h" #include "common/thread.h" #include #include #include #include #include #include #define LOCAL_WORKERS 2U #define MAX_LIST_BYTES (64U * 1024U * 1024U) #define REMOTE_TIMEOUT_MS 30000U enum RemoteActionType { REMOTE_ACTION_NONE, REMOTE_ACTION_CANCEL, REMOTE_ACTION_RELEASE, }; struct LocalDataset { struct LocalDataset * next; WindowsClipboardLocalFiles * files; uint64_t dataset; unsigned int references; bool current; }; struct LocalAcquisition { struct LocalAcquisition * next; struct LocalDataset * dataset; uint64_t wireDataset; uint64_t acquisition; }; struct LocalJob { struct LocalJob * next; struct LocalDataset * dataset; LG_ClipboardFileRequest request; LGEvent * ready; atomic_bool cancelled; }; struct RemoteDataset { struct RemoteDataset * next; WindowsClipboardRemoteManifest * manifestData; uint64_t dataset; uint64_t acquisition; uint64_t currentRequest; unsigned int presentationPins; bool current; bool acquiring; bool acquired; bool manifest; bool ready; bool stale; }; struct RemoteRequest { struct RemoteRequest * next; struct RemoteDataset * dataset; LG_ClipboardFileRequest request; LGEvent * completeEvent; uint8_t * data; size_t dataSize; size_t dataCapacity; uint8_t * output; size_t outputCapacity; uint64_t sizeHint; LG_ClipboardFileError error; bool began; bool complete; bool manifest; }; struct RemoteAction { enum RemoteActionType type; struct RemoteDataset * object; uint64_t dataset; uint64_t acquisition; }; struct RemoteProviderContext { LG_Lock lock; LGEvent * idle; unsigned int references; unsigned int calls; bool accepting; }; static struct { LG_Lock lock; bool lockInitialized; bool initialized; bool stopping; uint64_t transferSerial; uint64_t datasetSerial; struct LocalDataset * localDatasets; struct LocalDataset * localCurrent; struct LocalAcquisition * localAcquisitions; struct LocalJob * jobsHead; struct LocalJob ** jobsTail; struct LocalJob * activeJobs; struct RemoteDataset * remoteDatasets; struct RemoteDataset * remoteCurrent; struct RemoteRequest * remoteRequests; LGEvent * jobEvent; struct RemoteProviderContext * provider; LGThread * workers[LOCAL_WORKERS]; } files; static HRESULT fileErrorHRESULT(LG_ClipboardFileError error) { switch (error) { case LG_CLIPBOARD_FILE_ERROR_NONE: return S_OK; case LG_CLIPBOARD_FILE_ERROR_ACCESS: return STG_E_ACCESSDENIED; case LG_CLIPBOARD_FILE_ERROR_NO_MEMORY: return E_OUTOFMEMORY; case LG_CLIPBOARD_FILE_ERROR_INVALID: return STG_E_INVALIDPARAMETER; default: return STG_E_READFAULT; } } static uint64_t nextTransferNL(void) { do ++files.transferSerial; while (!files.transferSerial || kvmfrClipboardTransferFromHelper(files.transferSerial)); return files.transferSerial; } static uint64_t nextDatasetNL(void) { do ++files.datasetSerial; while (!files.datasetSerial || kvmfrClipboardTransferFromHelper(files.datasetSerial)); return files.datasetSerial; } static bool growBuffer(uint8_t ** buffer, size_t * capacity, size_t wanted) { if (wanted <= *capacity) return !wanted || *buffer; size_t next = *capacity ? *capacity : 4096U; while (next < wanted) { if (next > SIZE_MAX / 2U) { next = wanted; break; } next *= 2U; } void * resized = realloc(*buffer, next); if (!resized) return false; *buffer = resized; *capacity = next; return true; } static struct LocalDataset * localDatasetByIdNL(uint64_t dataset) { for (struct LocalDataset * item = files.localDatasets; item; item = item->next) if (item->dataset == dataset) return item; return NULL; } static struct LocalDataset * acquiredLocalDatasetNL(uint64_t dataset) { for (struct LocalAcquisition * item = files.localAcquisitions; item; item = item->next) if (item->wireDataset == dataset) return item->dataset; return NULL; } static bool localAcquisitionIdExistsNL(uint64_t acquisition) { for (struct LocalAcquisition * item = files.localAcquisitions; item; item = item->next) if (item->acquisition == acquisition) return true; return false; } static bool localJobIdExistsNL(uint64_t request) { for (struct LocalJob * job = files.jobsHead; job; job = job->next) if (job->request.request == request) return true; for (struct LocalJob * job = files.activeJobs; job; job = job->next) if (job->request.request == request) return true; return false; } static size_t localAcquisitionCountNL(void) { size_t count = 0; for (struct LocalAcquisition * item = files.localAcquisitions; item; item = item->next) ++count; return count; } static size_t localJobCountNL(void) { size_t count = 0; for (struct LocalJob * job = files.jobsHead; job; job = job->next) ++count; for (struct LocalJob * job = files.activeJobs; job; job = job->next) ++count; return count; } static size_t remoteDatasetCountNL(void) { size_t count = 0; for (struct RemoteDataset * item = files.remoteDatasets; item; item = item->next) ++count; return count; } static size_t remoteRequestCountNL(void) { size_t count = 0; for (struct RemoteRequest * item = files.remoteRequests; item; item = item->next) ++count; return count; } static struct LocalDataset * takeUnusedLocalDatasetNL( struct LocalDataset * dataset) { if (!dataset || dataset->current || dataset->references) return NULL; struct LocalDataset ** link = &files.localDatasets; while (*link && *link != dataset) link = &(*link)->next; if (!*link) return NULL; *link = dataset->next; dataset->next = NULL; return dataset; } static struct LocalDataset * takeAllUnusedLocalDatasetsNL(void) { struct LocalDataset * retired = NULL; struct LocalDataset ** link = &files.localDatasets; while (*link) { struct LocalDataset * dataset = *link; if (dataset->current || dataset->references) { link = &dataset->next; continue; } *link = dataset->next; dataset->next = retired; retired = dataset; } return retired; } static void freeLocalDatasets(struct LocalDataset * datasets) { while (datasets) { struct LocalDataset * next = datasets->next; windowsClipboardLocalFilesRelease(datasets->files); free(datasets); datasets = next; } } static struct RemoteDataset * remoteDatasetByIdNL(uint64_t dataset) { for (struct RemoteDataset * item = files.remoteDatasets; item; item = item->next) if (item->dataset == dataset) return item; return NULL; } static struct RemoteDataset * remoteDatasetByAcquisitionNL( uint64_t acquisition) { for (struct RemoteDataset * item = files.remoteDatasets; item; item = item->next) if (item->acquisition == acquisition) return item; return NULL; } static struct RemoteRequest * remoteRequestByIdNL(uint64_t request) { for (struct RemoteRequest * item = files.remoteRequests; item; item = item->next) if (item->request.request == request) return item; return NULL; } static struct RemoteRequest * takeRemoteRequestNL(uint64_t request) { struct RemoteRequest ** link = &files.remoteRequests; while (*link && (*link)->request.request != request) link = &(*link)->next; struct RemoteRequest * result = *link; if (result) { *link = result->next; result->next = NULL; } return result; } static void freeRemoteRequest(struct RemoteRequest * request) { if (!request) return; if (request->completeEvent) lgFreeEvent(request->completeEvent); free(request->data); free(request); } static void cancelRemoteManifest(struct RemoteRequest * request, LG_ClipboardFileError reason) { if (!request) return; if (!clipboard_fileCancel(request->request.dataset, request->request.request, reason)) DEBUG_WARN("Failed to cancel a clipboard file manifest request"); freeRemoteRequest(request); } /* files.lock must be held. Read requests remain owned by their waiting * providers; only manifest requests are returned to the caller. */ static struct RemoteRequest * disconnectRemoteRequestsNL(void) { struct RemoteRequest * manifests = NULL; struct RemoteRequest * request = files.remoteRequests; files.remoteRequests = NULL; while (request) { struct RemoteRequest * next = request->next; request->next = NULL; request->error = LG_CLIPBOARD_FILE_ERROR_DISCONNECTED; request->complete = true; if (request->manifest) { request->next = manifests; manifests = request; } else if (request->completeEvent) lgSignalEvent(request->completeEvent); request = next; } return manifests; } static struct RemoteAction retireRemoteDatasetNL( struct RemoteDataset * dataset) { struct RemoteAction action = { 0 }; if (!dataset || dataset->current || dataset->manifest || dataset->presentationPins) return action; struct RemoteDataset ** link = &files.remoteDatasets; while (*link && *link != dataset) link = &(*link)->next; if (!*link) return action; *link = dataset->next; dataset->next = NULL; dataset->ready = false; dataset->stale = true; action.object = dataset; action.dataset = dataset->dataset; action.acquisition = dataset->acquisition; if (dataset->acquired) action.type = REMOTE_ACTION_RELEASE; else if (dataset->acquiring) action.type = REMOTE_ACTION_CANCEL; dataset->acquired = false; dataset->acquiring = false; return action; } static void performRemoteAction(struct RemoteAction action) { switch (action.type) { case REMOTE_ACTION_CANCEL: if (!clipboard_fileCancel(action.dataset, action.acquisition, LG_CLIPBOARD_FILE_ERROR_CANCELLED)) DEBUG_WARN("Failed to cancel a clipboard file acquisition"); break; case REMOTE_ACTION_RELEASE: if (!clipboard_fileRelease(action.dataset, action.acquisition)) DEBUG_WARN("Failed to release a clipboard file acquisition"); break; case REMOTE_ACTION_NONE: break; } if (action.object) { windowsClipboardRemoteManifestFree(action.object->manifestData); free(action.object); } } static bool sameRemoteRequest(const struct RemoteRequest * request, const LG_ClipboardFileRequest * descriptor) { return request->request.dataset == descriptor->dataset && request->request.request == descriptor->request && request->request.node == descriptor->node && request->request.offset == descriptor->offset && request->request.length == descriptor->length && request->request.operation == descriptor->operation; } static void failRemoteDataset(struct RemoteDataset * dataset, LG_ClipboardFileError error) { if (!dataset) return; struct RemoteRequest * request = NULL; struct RemoteAction action = { 0 }; uint64_t failed = 0; LG_LOCK(files.lock); if (!dataset->stale && remoteDatasetByIdNL(dataset->dataset) == dataset) { failed = dataset->dataset; if (dataset->currentRequest) request = takeRemoteRequestNL(dataset->currentRequest); dataset->currentRequest = 0; dataset->manifest = false; dataset->current = false; dataset->ready = false; if (files.remoteCurrent == dataset) files.remoteCurrent = NULL; action = retireRemoteDatasetNL(dataset); } LG_UNLOCK(files.lock); if (request) cancelRemoteManifest(request, error); performRemoteAction(action); if (failed) clipboard_fileRemoteFailed(failed); } static bool startRemoteList(struct RemoteDataset * dataset, uint64_t parent) { struct RemoteRequest * request = calloc(1, sizeof(*request)); if (!request) { failRemoteDataset(dataset, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY); return false; } LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_NO_SPACE; LG_LOCK(files.lock); if (!files.stopping && dataset && !dataset->stale && dataset->current && dataset->manifest && !dataset->currentRequest && remoteRequestCountNL() + localJobCountNL() < KVMFR_CLIPBOARD_FILE_MAX_REQUESTS) { request->dataset = dataset; request->request.dataset = dataset->dataset; request->request.request = nextTransferNL(); request->request.node = parent; request->request.operation = LG_CLIPBOARD_FILE_LIST; request->sizeHint = LG_CLIPBOARD_SIZE_UNKNOWN; request->manifest = true; request->next = files.remoteRequests; files.remoteRequests = request; dataset->currentRequest = request->request.request; error = LG_CLIPBOARD_FILE_ERROR_NONE; } LG_UNLOCK(files.lock); if (error) { freeRemoteRequest(request); failRemoteDataset(dataset, error); return false; } if (clipboard_fileRequest(&request->request)) return true; LG_LOCK(files.lock); struct RemoteRequest * removed = takeRemoteRequestNL( request->request.request); if (dataset->currentRequest == request->request.request) dataset->currentRequest = 0; LG_UNLOCK(files.lock); freeRemoteRequest(removed); failRemoteDataset(dataset, LG_CLIPBOARD_FILE_ERROR_DISCONNECTED); return false; } static void continueRemoteManifest(struct RemoteRequest * request) { if (!request || !request->dataset) { freeRemoteRequest(request); return; } struct RemoteDataset * dataset = request->dataset; LG_ClipboardFileError error = request->error; uint64_t next = 0; bool ready = false; LG_LOCK(files.lock); const bool current = !dataset->stale && dataset->current && dataset->manifest && dataset->currentRequest == request->request.request; if (current && !error && !windowsClipboardRemoteManifestAppendList( dataset->manifestData, request->request.node, request->data, request->dataSize, &error)) error = error ? error : LG_CLIPBOARD_FILE_ERROR_INVALID; if (current && !error) { dataset->currentRequest = 0; const bool hasNext = windowsClipboardRemoteManifestNextDirectory( dataset->manifestData, &next); if (!hasNext) { if (!windowsClipboardRemoteManifestHasEntries(dataset->manifestData)) error = LG_CLIPBOARD_FILE_ERROR_INVALID; else { dataset->manifest = false; dataset->ready = true; ready = true; } } } LG_UNLOCK(files.lock); freeRemoteRequest(request); if (!current) return; if (error) { failRemoteDataset(dataset, error); return; } if (next) { startRemoteList(dataset, next); return; } if (ready) clipboard_fileRemoteReady(dataset->dataset); } static struct RemoteProviderContext * providerCreate(void) { struct RemoteProviderContext * context = calloc(1, sizeof(*context)); if (!context) return NULL; LG_LOCK_INIT(context->lock); context->idle = lgCreateEvent(false, 0); if (!context->idle) { LG_LOCK_FREE(context->lock); free(context); return NULL; } context->references = 1; context->accepting = true; lgSignalEvent(context->idle); return context; } static void providerRetain(struct RemoteProviderContext * context) { if (!context) return; LG_LOCK(context->lock); ++context->references; LG_UNLOCK(context->lock); } static void providerRelease(struct RemoteProviderContext * context) { if (!context) return; LG_LOCK(context->lock); const bool destroy = context->references && !--context->references; LG_UNLOCK(context->lock); if (!destroy) return; lgFreeEvent(context->idle); LG_LOCK_FREE(context->lock); free(context); } static bool providerEnter(struct RemoteProviderContext * context) { if (!context) return false; LG_LOCK(context->lock); if (!context->accepting) { LG_UNLOCK(context->lock); return false; } if (!context->calls) lgResetEvent(context->idle); ++context->calls; LG_UNLOCK(context->lock); return true; } static void providerLeave(struct RemoteProviderContext * context) { LG_LOCK(context->lock); if (context->calls && !--context->calls) lgSignalEvent(context->idle); LG_UNLOCK(context->lock); } static void providerDisable(struct RemoteProviderContext * context) { if (!context) return; LG_LOCK(context->lock); context->accepting = false; LG_UNLOCK(context->lock); } static void providerWait(struct RemoteProviderContext * context) { if (!context) return; LG_LOCK(context->lock); const bool idle = !context->calls; LG_UNLOCK(context->lock); if (!idle) lgWaitEvent(context->idle, TIMEOUT_INFINITE); } static HRESULT remoteProviderRead(void * opaque, uint64_t datasetId, uint64_t acquisition, uint64_t node, uint64_t offset, void * output, ULONG length, ULONG * read) { struct RemoteProviderContext * context = opaque; if (!read || (!output && length)) return STG_E_INVALIDPOINTER; *read = 0; if (!providerEnter(context)) return STG_E_READFAULT; HRESULT result = S_OK; uint8_t * destination = output; while (length) { const uint32_t wanted = length > KVMFR_CLIPBOARD_FILE_READ_BYTES ? KVMFR_CLIPBOARD_FILE_READ_BYTES : (uint32_t)length; struct RemoteRequest * request = calloc(1, sizeof(*request)); if (!request) { result = E_OUTOFMEMORY; break; } request->completeEvent = lgCreateEvent(true, 0); if (!request->completeEvent) { freeRemoteRequest(request); result = E_OUTOFMEMORY; break; } LG_LOCK(files.lock); struct RemoteDataset * dataset = remoteDatasetByIdNL(datasetId); const bool valid = !files.stopping && dataset && dataset->acquisition == acquisition && dataset->acquired && !dataset->stale && dataset->presentationPins && remoteRequestCountNL() + localJobCountNL() < KVMFR_CLIPBOARD_FILE_MAX_REQUESTS; if (valid) { request->dataset = dataset; request->request.dataset = datasetId; request->request.request = nextTransferNL(); request->request.node = node; request->request.offset = offset; request->request.length = wanted; request->request.operation = LG_CLIPBOARD_FILE_READ; request->output = destination; request->outputCapacity = wanted; request->sizeHint = LG_CLIPBOARD_SIZE_UNKNOWN; request->next = files.remoteRequests; files.remoteRequests = request; } LG_UNLOCK(files.lock); if (!valid) { freeRemoteRequest(request); result = STG_E_READFAULT; break; } if (!clipboard_fileRequest(&request->request)) { LG_LOCK(files.lock); takeRemoteRequestNL(request->request.request); LG_UNLOCK(files.lock); freeRemoteRequest(request); result = STG_E_READFAULT; break; } const bool signalled = lgWaitEvent(request->completeEvent, REMOTE_TIMEOUT_MS); bool removed; LG_LOCK(files.lock); removed = takeRemoteRequestNL(request->request.request) == request; if (!request->complete) { request->error = LG_CLIPBOARD_FILE_ERROR_IO; request->complete = true; } const LG_ClipboardFileError error = request->error; const size_t actual = request->dataSize; LG_UNLOCK(files.lock); if (!signalled && removed) clipboard_fileCancel(datasetId, request->request.request, LG_CLIPBOARD_FILE_ERROR_IO); if (error) { result = fileErrorHRESULT(error); freeRemoteRequest(request); break; } if (actual != wanted) { result = STG_E_READFAULT; freeRemoteRequest(request); break; } destination += actual; offset += actual; length -= (ULONG)actual; *read += (ULONG)actual; freeRemoteRequest(request); } providerLeave(context); return result; } static void remoteProviderRelease(void * opaque, uint64_t datasetId, uint64_t acquisition) { struct RemoteProviderContext * context = opaque; if (!providerEnter(context)) { providerRelease(context); return; } struct RemoteAction action = { 0 }; LG_LOCK(files.lock); struct RemoteDataset * dataset = datasetId ? remoteDatasetByIdNL(datasetId) : remoteDatasetByAcquisitionNL(acquisition); if (dataset && dataset->acquisition == acquisition && dataset->presentationPins) { --dataset->presentationPins; action = retireRemoteDatasetNL(dataset); } LG_UNLOCK(files.lock); performRemoteAction(action); providerLeave(context); providerRelease(context); } static const WindowsClipboardRemoteProviderOps remoteProviderOps = { .read = remoteProviderRead, .release = remoteProviderRelease, }; static bool sendLocalCall(struct LocalJob * job, int operation, uint64_t offset, const void * data, size_t size, bool end) { for (;;) { if (atomic_load_explicit(&job->cancelled, memory_order_acquire)) return false; LG_ClipboardResult result; switch (operation) { case 0: result = clipboard_fileDataBegin(&job->request, offset); break; case 1: result = clipboard_fileDataChunk(&job->request, offset, data, size, end); break; default: result = clipboard_fileDataEnd(&job->request, offset); break; } if (result == LG_CLIPBOARD_RESULT_ACCEPTED) return true; if (result == LG_CLIPBOARD_RESULT_FAILED) return false; if (!lgWaitEvent(job->ready, TIMEOUT_INFINITE)) return false; } } static bool sendLocalBuffer(struct LocalJob * job, const uint8_t * data, size_t size) { if (!sendLocalCall(job, 0, size, NULL, 0, false)) return false; size_t offset = 0; while (offset < size) { size_t chunk = size - offset; if (chunk > KVMFR_CLIPBOARD_DATA_BYTES) chunk = KVMFR_CLIPBOARD_DATA_BYTES; const bool end = chunk == size - offset; if (!sendLocalCall(job, 1, offset, data + offset, chunk, end)) return false; offset += chunk; } return sendLocalCall(job, 2, size, NULL, 0, true); } static void processLocalJob(struct LocalJob * job) { LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_CANCELLED; void * data = NULL; size_t size = 0; bool success = false; if (!atomic_load_explicit(&job->cancelled, memory_order_acquire)) { if (job->request.operation == LG_CLIPBOARD_FILE_LIST) success = windowsClipboardLocalFilesList(job->dataset->files, job->request.node, &data, &size, &error); else success = windowsClipboardLocalFilesRead(job->dataset->files, job->request.node, job->request.offset, job->request.length, &data, &size, &error); } if (success && !atomic_load_explicit( &job->cancelled, memory_order_acquire)) { success = sendLocalBuffer(job, data, size); if (!success) error = LG_CLIPBOARD_FILE_ERROR_DISCONNECTED; } free(data); if (!success && !atomic_load_explicit( &job->cancelled, memory_order_acquire) && error) clipboard_fileCancel(job->request.dataset, job->request.request, error); } static int localWorker(void * opaque) { (void)opaque; for (;;) { LG_LOCK(files.lock); struct LocalJob * job = files.jobsHead; if (job) { files.jobsHead = job->next; if (!files.jobsHead) files.jobsTail = &files.jobsHead; job->next = files.activeJobs; files.activeJobs = job; } const bool stopping = files.stopping; if (!job && !stopping) lgResetEvent(files.jobEvent); LG_UNLOCK(files.lock); if (!job) { if (stopping) return 0; lgWaitEvent(files.jobEvent, TIMEOUT_INFINITE); continue; } processLocalJob(job); LG_LOCK(files.lock); struct LocalJob ** link = &files.activeJobs; while (*link && *link != job) link = &(*link)->next; if (*link) *link = job->next; if (job->dataset->references) --job->dataset->references; struct LocalDataset * retired = takeUnusedLocalDatasetNL(job->dataset); LG_UNLOCK(files.lock); freeLocalDatasets(retired); lgFreeEvent(job->ready); free(job); } } static void cancelLocalState(bool stop) { struct LocalDataset * retired; LG_LOCK(files.lock); if (stop) files.stopping = true; while (files.localAcquisitions) { struct LocalAcquisition * item = files.localAcquisitions; files.localAcquisitions = item->next; if (item->dataset->references) --item->dataset->references; free(item); } for (struct LocalJob * job = files.jobsHead; job; job = job->next) atomic_store_explicit(&job->cancelled, true, memory_order_release); for (struct LocalJob * job = files.activeJobs; job; job = job->next) { atomic_store_explicit(&job->cancelled, true, memory_order_release); lgSignalEvent(job->ready); } retired = takeAllUnusedLocalDatasetsNL(); LG_UNLOCK(files.lock); freeLocalDatasets(retired); if (files.jobEvent) lgSignalEvent(files.jobEvent); } bool clipboardFiles_init(void) { if (files.lockInitialized) return false; memset(&files, 0, sizeof(files)); LG_LOCK_INIT(files.lock); files.lockInitialized = true; uint64_t nonce = 0; if (!lgRandomBytes(&nonce, sizeof(nonce))) { DEBUG_ERROR("Failed to seed clipboard file identifiers"); goto fail; } nonce &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER; if (!nonce) nonce = 1; files.transferSerial = nonce; files.datasetSerial = (nonce ^ UINT64_C(0x4c474346494c4553)) & ~KVMFR_CLIPBOARD_TRANSFER_HELPER; if (!files.datasetSerial) files.datasetSerial = 1; files.jobsTail = &files.jobsHead; files.jobEvent = lgCreateEvent(false, 0); files.provider = providerCreate(); if (!files.jobEvent || !files.provider) goto fail; for (unsigned int index = 0; index < LOCAL_WORKERS; ++index) if (!lgCreateThread("clipboard-files", localWorker, NULL, &files.workers[index])) goto fail; files.initialized = true; return true; fail: cancelLocalState(true); for (unsigned int index = 0; index < LOCAL_WORKERS; ++index) if (files.workers[index]) lgJoinThread(files.workers[index], NULL); if (files.jobEvent) lgFreeEvent(files.jobEvent); providerDisable(files.provider); providerWait(files.provider); providerRelease(files.provider); files.jobEvent = NULL; files.provider = NULL; files.lockInitialized = false; LG_LOCK_FREE(files.lock); return false; } void clipboardFiles_free(void) { if (!files.initialized) return; cancelLocalState(true); LG_LOCK(files.lock); struct RemoteProviderContext * provider = files.provider; files.provider = NULL; LG_UNLOCK(files.lock); providerDisable(provider); LG_LOCK(files.lock); struct RemoteRequest * requests = disconnectRemoteRequestsNL(); LG_UNLOCK(files.lock); while (requests) { struct RemoteRequest * next = requests->next; freeRemoteRequest(requests); requests = next; } providerWait(provider); for (unsigned int index = 0; index < LOCAL_WORKERS; ++index) if (files.workers[index]) lgJoinThread(files.workers[index], NULL); LG_LOCK(files.lock); struct RemoteDataset * datasets = files.remoteDatasets; struct LocalDataset * local = files.localDatasets; files.remoteDatasets = NULL; files.remoteCurrent = NULL; files.localDatasets = NULL; files.localCurrent = NULL; files.initialized = false; LG_UNLOCK(files.lock); while (datasets) { struct RemoteDataset * next = datasets->next; if (datasets->acquired) clipboard_fileRelease(datasets->dataset, datasets->acquisition); else if (datasets->acquiring) clipboard_fileCancel(datasets->dataset, datasets->acquisition, LG_CLIPBOARD_FILE_ERROR_CANCELLED); windowsClipboardRemoteManifestFree(datasets->manifestData); free(datasets); datasets = next; } freeLocalDatasets(local); lgFreeEvent(files.jobEvent); files.jobEvent = NULL; files.lockInitialized = false; LG_LOCK_FREE(files.lock); providerRelease(provider); } bool clipboardFiles_setLocal(const char * mime, const void * data, size_t size) { (void)mime; (void)data; (void)size; return false; } bool windowsClipboardFiles_setLocalDrop(HDROP drop) { LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_NONE; WindowsClipboardLocalFiles * captured = windowsClipboardLocalFilesCapture(drop, &error); if (!captured) { DEBUG_WARN("Failed to capture Windows clipboard files: error=%u", (unsigned int)error); return false; } struct LocalDataset * dataset = calloc(1, sizeof(*dataset)); if (!dataset) { windowsClipboardLocalFilesRelease(captured); return false; } dataset->files = captured; dataset->current = true; LG_LOCK(files.lock); if (!files.initialized || files.stopping) { LG_UNLOCK(files.lock); freeLocalDatasets(dataset); return false; } dataset->dataset = nextDatasetNL(); dataset->next = files.localDatasets; files.localDatasets = dataset; if (files.localCurrent) files.localCurrent->current = false; struct LocalDataset * retired = takeUnusedLocalDatasetNL( files.localCurrent); files.localCurrent = dataset; LG_UNLOCK(files.lock); freeLocalDatasets(retired); clipboard_notifyFiles(dataset->dataset); return true; } void clipboardFiles_clearLocal(void) { LG_LOCK(files.lock); if (files.localCurrent) files.localCurrent->current = false; struct LocalDataset * retired = takeUnusedLocalDatasetNL( files.localCurrent); files.localCurrent = NULL; LG_UNLOCK(files.lock); freeLocalDatasets(retired); } bool clipboardFiles_getRemote(const char * mime, char ** data, size_t * size) { (void)mime; if (data) *data = NULL; if (size) *size = 0; return false; } uint64_t clipboardFiles_remotePresentationAcquire(void) { LG_LOCK(files.lock); struct RemoteDataset * dataset = files.remoteCurrent; const uint64_t presentation = dataset && dataset->ready && dataset->acquired && !dataset->stale ? dataset->acquisition : 0; if (presentation) ++dataset->presentationPins; LG_UNLOCK(files.lock); return presentation; } bool windowsClipboardFiles_createRemoteDataObject(uint64_t presentation, IDataObject ** object) { if (!presentation || !object) return false; *object = NULL; LG_LOCK(files.lock); struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(presentation); const bool valid = dataset && dataset->presentationPins && dataset->ready && dataset->acquired && !dataset->stale && files.provider; HRESULT result = E_FAIL; if (valid) { providerRetain(files.provider); result = windowsClipboardRemoteCreateDataObject( dataset->manifestData, dataset->dataset, dataset->acquisition, &remoteProviderOps, files.provider, object); if (FAILED(result)) providerRelease(files.provider); } LG_UNLOCK(files.lock); if (FAILED(result)) { DEBUG_WARN("Failed to create the Windows clipboard file object: " "0x%08lx", (unsigned long)result); return false; } return true; } bool clipboardFiles_getRemotePresentation(uint64_t presentation, const char * mime, char ** data, size_t * size) { (void)presentation; (void)mime; if (data) *data = NULL; if (size) *size = 0; return false; } void clipboardFiles_remotePresentationDelivered(uint64_t presentation) { (void)presentation; } void clipboardFiles_remotePresentationRelease(uint64_t presentation) { struct RemoteAction action = { 0 }; LG_LOCK(files.lock); struct RemoteDataset * dataset = remoteDatasetByAcquisitionNL(presentation); if (dataset && dataset->presentationPins) { --dataset->presentationPins; action = retireRemoteDatasetNL(dataset); } LG_UNLOCK(files.lock); performRemoteAction(action); } bool clipboardFiles_remoteReady(uint64_t dataset) { LG_LOCK(files.lock); struct RemoteDataset * found = remoteDatasetByIdNL(dataset); const bool ready = found && found->current && found->ready && !found->stale; LG_UNLOCK(files.lock); return ready; } bool clipboardFiles_remoteOffer(uint64_t datasetId) { if (!datasetId) return false; struct RemoteDataset * candidate = calloc(1, sizeof(*candidate)); if (!candidate) return false; candidate->manifestData = windowsClipboardRemoteManifestCreate(); if (!candidate->manifestData) { free(candidate); return false; } struct RemoteRequest * cancelled = NULL; struct RemoteAction previousAction = { 0 }; LG_LOCK(files.lock); if (!files.initialized || files.stopping) { LG_UNLOCK(files.lock); windowsClipboardRemoteManifestFree(candidate->manifestData); free(candidate); return false; } if (files.remoteCurrent && files.remoteCurrent->dataset == datasetId && !files.remoteCurrent->stale) { LG_UNLOCK(files.lock); windowsClipboardRemoteManifestFree(candidate->manifestData); free(candidate); return true; } struct RemoteDataset * dataset = remoteDatasetByIdNL(datasetId); if (!dataset && remoteDatasetCountNL() + localAcquisitionCountNL() >= KVMFR_CLIPBOARD_FILE_MAX_ACQUISITIONS) { LG_UNLOCK(files.lock); windowsClipboardRemoteManifestFree(candidate->manifestData); free(candidate); return false; } if (!dataset) { dataset = candidate; candidate = NULL; dataset->dataset = datasetId; dataset->acquisition = nextTransferNL(); dataset->acquiring = true; dataset->next = files.remoteDatasets; files.remoteDatasets = dataset; } if (files.remoteCurrent) { struct RemoteDataset * previous = files.remoteCurrent; previous->current = false; if (previous->currentRequest) { cancelled = takeRemoteRequestNL(previous->currentRequest); previous->currentRequest = 0; previous->manifest = false; } previousAction = retireRemoteDatasetNL(previous); } dataset->current = true; files.remoteCurrent = dataset; LG_UNLOCK(files.lock); if (candidate) { windowsClipboardRemoteManifestFree(candidate->manifestData); free(candidate); } cancelRemoteManifest(cancelled, LG_CLIPBOARD_FILE_ERROR_CANCELLED); performRemoteAction(previousAction); if (!dataset->acquiring) { if (dataset->ready) clipboard_fileRemoteReady(datasetId); return true; } if (clipboard_fileAcquire(datasetId, dataset->acquisition)) return true; failRemoteDataset(dataset, LG_CLIPBOARD_FILE_ERROR_DISCONNECTED); return false; } void clipboardFiles_remoteClear(void) { struct RemoteRequest * cancelled = NULL; struct RemoteAction action = { 0 }; LG_LOCK(files.lock); struct RemoteDataset * dataset = files.remoteCurrent; if (dataset) { dataset->current = false; if (dataset->currentRequest) { cancelled = takeRemoteRequestNL(dataset->currentRequest); dataset->currentRequest = 0; dataset->manifest = false; } files.remoteCurrent = NULL; action = retireRemoteDatasetNL(dataset); } LG_UNLOCK(files.lock); cancelRemoteManifest(cancelled, LG_CLIPBOARD_FILE_ERROR_CANCELLED); performRemoteAction(action); } void clipboardFiles_providerUnavailable(void) { if (!files.initialized) return; cancelLocalState(false); LG_LOCK(files.lock); struct RemoteRequest * request = disconnectRemoteRequestsNL(); struct RemoteDataset * dataset = files.remoteDatasets; files.remoteDatasets = NULL; files.remoteCurrent = NULL; LG_UNLOCK(files.lock); while (request) { struct RemoteRequest * next = request->next; freeRemoteRequest(request); request = next; } while (dataset) { struct RemoteDataset * next = dataset->next; windowsClipboardRemoteManifestFree(dataset->manifestData); free(dataset); dataset = next; } } void clipboardFiles_remoteAcquired(uint64_t datasetId, uint64_t acquisition, LG_ClipboardFileError error) { LG_LOCK(files.lock); struct RemoteDataset * dataset = remoteDatasetByIdNL(datasetId); const bool valid = dataset && dataset->acquisition == acquisition && dataset->acquiring && !dataset->stale; if (valid) { dataset->acquiring = false; dataset->acquired = !error; dataset->manifest = !error; } LG_UNLOCK(files.lock); if (!valid) return; if (error) { failRemoteDataset(dataset, error); return; } startRemoteList(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE); } LG_ClipboardResult clipboardFiles_remoteDataBegin( const LG_ClipboardFileRequest * descriptor, uint64_t sizeHint) { if (!descriptor) return LG_CLIPBOARD_RESULT_FAILED; LG_LOCK(files.lock); struct RemoteRequest * request = remoteRequestByIdNL(descriptor->request); struct RemoteDataset * failedDataset = request && request->manifest ? request->dataset : NULL; const bool valid = request && !request->began && !request->complete && sameRemoteRequest(request, descriptor) && (descriptor->operation != LG_CLIPBOARD_FILE_READ || sizeHint == descriptor->length) && (descriptor->operation != LG_CLIPBOARD_FILE_LIST || sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN || sizeHint <= MAX_LIST_BYTES); if (valid) { request->began = true; request->sizeHint = sizeHint; } else if (request) { takeRemoteRequestNL(descriptor->request); if (!request->manifest) { request->error = LG_CLIPBOARD_FILE_ERROR_INVALID; request->complete = true; lgSignalEvent(request->completeEvent); } } LG_UNLOCK(files.lock); if (valid) return LG_CLIPBOARD_RESULT_ACCEPTED; if (failedDataset) { cancelRemoteManifest(request, LG_CLIPBOARD_FILE_ERROR_INVALID); failRemoteDataset(failedDataset, LG_CLIPBOARD_FILE_ERROR_INVALID); } return LG_CLIPBOARD_RESULT_FAILED; } LG_ClipboardResult clipboardFiles_remoteDataChunk( const LG_ClipboardFileRequest * descriptor, uint64_t responseOffset, const void * data, size_t size) { if (!descriptor) return LG_CLIPBOARD_RESULT_FAILED; const size_t responseStart = (size_t)responseOffset; const bool envelopeValid = data && size && (uint64_t)responseStart == responseOffset && size <= SIZE_MAX - responseStart; LG_LOCK(files.lock); struct RemoteRequest * request = remoteRequestByIdNL(descriptor->request); struct RemoteDataset * failedDataset = request && request->manifest ? request->dataset : NULL; const size_t end = envelopeValid ? responseStart + size : 0; bool valid = envelopeValid && request && request->began && !request->complete && sameRemoteRequest(request, descriptor) && responseOffset == request->dataSize && (request->sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN || end <= request->sizeHint); if (valid && descriptor->operation == LG_CLIPBOARD_FILE_READ) { valid = end <= descriptor->length && end <= request->outputCapacity && request->output; if (valid) memcpy(request->output + request->dataSize, data, size); } else if (valid) { valid = end <= MAX_LIST_BYTES && growBuffer(&request->data, &request->dataCapacity, end); if (valid) memcpy(request->data + request->dataSize, data, size); } if (valid) request->dataSize = end; else if (request) { takeRemoteRequestNL(descriptor->request); if (!request->manifest) { request->error = LG_CLIPBOARD_FILE_ERROR_INVALID; request->complete = true; lgSignalEvent(request->completeEvent); } } LG_UNLOCK(files.lock); if (valid) return LG_CLIPBOARD_RESULT_ACCEPTED; if (failedDataset) { cancelRemoteManifest(request, LG_CLIPBOARD_FILE_ERROR_INVALID); failRemoteDataset(failedDataset, LG_CLIPBOARD_FILE_ERROR_INVALID); } return LG_CLIPBOARD_RESULT_FAILED; } LG_ClipboardResult clipboardFiles_remoteDataEnd( const LG_ClipboardFileRequest * descriptor, uint64_t finalSize) { if (!descriptor) return LG_CLIPBOARD_RESULT_FAILED; LG_LOCK(files.lock); struct RemoteRequest * request = takeRemoteRequestNL(descriptor->request); const bool manifest = request && request->manifest; struct RemoteDataset * dataset = manifest ? request->dataset : NULL; const bool valid = request && request->began && !request->complete && sameRemoteRequest(request, descriptor) && request->dataSize == finalSize && (request->sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN || request->sizeHint == finalSize) && (descriptor->operation != LG_CLIPBOARD_FILE_READ || finalSize == descriptor->length); if (request) { request->error = valid ? LG_CLIPBOARD_FILE_ERROR_NONE : LG_CLIPBOARD_FILE_ERROR_INVALID; request->complete = true; if (!request->manifest) lgSignalEvent(request->completeEvent); } LG_UNLOCK(files.lock); if (!request) return LG_CLIPBOARD_RESULT_FAILED; if (manifest) { if (valid) continueRemoteManifest(request); else { freeRemoteRequest(request); failRemoteDataset(dataset, LG_CLIPBOARD_FILE_ERROR_INVALID); } } return valid ? LG_CLIPBOARD_RESULT_ACCEPTED : LG_CLIPBOARD_RESULT_FAILED; } void clipboardFiles_remoteCancel(uint64_t datasetId, uint64_t requestId, LG_ClipboardFileError reason) { LG_LOCK(files.lock); struct RemoteRequest * request = takeRemoteRequestNL(requestId); const bool manifest = request && request->manifest; struct RemoteDataset * failedDataset = manifest ? request->dataset : NULL; struct RemoteDataset * acquisition = remoteDatasetByIdNL(datasetId); if (!acquisition || acquisition->acquisition != requestId || !acquisition->acquiring) acquisition = NULL; else acquisition->acquiring = false; if (request) { request->error = reason; request->complete = true; if (!request->manifest) lgSignalEvent(request->completeEvent); } LG_UNLOCK(files.lock); if (manifest) { freeRemoteRequest(request); failRemoteDataset(failedDataset, reason); } else if (acquisition) failRemoteDataset(acquisition, reason); } void clipboardFiles_localAcquire(uint64_t wireDataset, uint64_t acquisition) { LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_STALE; struct LocalAcquisition * item = calloc(1, sizeof(*item)); LG_LOCK(files.lock); struct LocalDataset * dataset = localDatasetByIdNL(wireDataset); struct LocalAcquisition * existing = files.localAcquisitions; while (existing && existing->acquisition != acquisition) existing = existing->next; if (!acquisition || files.stopping) error = LG_CLIPBOARD_FILE_ERROR_INVALID; else if (existing) error = existing->wireDataset == wireDataset ? LG_CLIPBOARD_FILE_ERROR_NONE : LG_CLIPBOARD_FILE_ERROR_INVALID; else if (!item) error = LG_CLIPBOARD_FILE_ERROR_NO_MEMORY; else if (dataset && localAcquisitionCountNL() + remoteDatasetCountNL() < KVMFR_CLIPBOARD_FILE_MAX_ACQUISITIONS) { item->dataset = dataset; item->wireDataset = wireDataset; item->acquisition = acquisition; item->next = files.localAcquisitions; files.localAcquisitions = item; ++dataset->references; item = NULL; error = LG_CLIPBOARD_FILE_ERROR_NONE; } else if (dataset) error = LG_CLIPBOARD_FILE_ERROR_NO_SPACE; LG_UNLOCK(files.lock); free(item); clipboard_fileAcquired(wireDataset, acquisition, error); } void clipboardFiles_localRelease(uint64_t wireDataset, uint64_t acquisition) { struct LocalAcquisition * item = NULL; struct LocalDataset * retired = NULL; LG_LOCK(files.lock); struct LocalAcquisition ** link = &files.localAcquisitions; while (*link && ((*link)->wireDataset != wireDataset || (*link)->acquisition != acquisition)) link = &(*link)->next; if (*link) { item = *link; *link = item->next; if (item->dataset->references) --item->dataset->references; retired = takeUnusedLocalDatasetNL(item->dataset); } LG_UNLOCK(files.lock); free(item); freeLocalDatasets(retired); } void clipboardFiles_localRequest( const LG_ClipboardFileRequest * request) { if (!request || !request->dataset || !request->request || (request->operation != LG_CLIPBOARD_FILE_LIST && request->operation != LG_CLIPBOARD_FILE_READ) || (request->operation == LG_CLIPBOARD_FILE_LIST && (request->offset || request->length)) || (request->operation == LG_CLIPBOARD_FILE_READ && (!request->length || request->length > KVMFR_CLIPBOARD_FILE_READ_BYTES || request->offset > UINT64_MAX - request->length))) { if (request) clipboard_fileCancel(request->dataset, request->request, LG_CLIPBOARD_FILE_ERROR_INVALID); return; } struct LocalJob * job = calloc(1, sizeof(*job)); if (!job) { clipboard_fileCancel(request->dataset, request->request, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY); return; } job->ready = lgCreateEvent(true, 0); if (!job->ready) { free(job); clipboard_fileCancel(request->dataset, request->request, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY); return; } atomic_init(&job->cancelled, false); job->request = *request; LG_ClipboardFileError error = LG_CLIPBOARD_FILE_ERROR_STALE; LG_LOCK(files.lock); job->dataset = acquiredLocalDatasetNL(request->dataset); if (localAcquisitionIdExistsNL(request->request) || localJobIdExistsNL(request->request)) error = LG_CLIPBOARD_FILE_ERROR_INVALID; else if (job->dataset && !files.stopping && localJobCountNL() + remoteRequestCountNL() < KVMFR_CLIPBOARD_FILE_MAX_REQUESTS) { ++job->dataset->references; *files.jobsTail = job; files.jobsTail = &job->next; error = LG_CLIPBOARD_FILE_ERROR_NONE; } else if (job->dataset) error = LG_CLIPBOARD_FILE_ERROR_NO_SPACE; LG_UNLOCK(files.lock); if (error) { lgFreeEvent(job->ready); free(job); clipboard_fileCancel(request->dataset, request->request, error); return; } lgSignalEvent(files.jobEvent); } void clipboardFiles_localCancel(uint64_t dataset, uint64_t request, LG_ClipboardFileError reason) { (void)reason; struct LocalAcquisition * acquisition = NULL; struct LocalDataset * retired = NULL; LG_LOCK(files.lock); struct LocalAcquisition ** link = &files.localAcquisitions; while (*link && ((*link)->wireDataset != dataset || (*link)->acquisition != request)) link = &(*link)->next; if (*link) { acquisition = *link; *link = acquisition->next; if (acquisition->dataset->references) --acquisition->dataset->references; retired = takeUnusedLocalDatasetNL(acquisition->dataset); } for (struct LocalJob * job = files.jobsHead; job; job = job->next) if (job->request.dataset == dataset && job->request.request == request) atomic_store_explicit(&job->cancelled, true, memory_order_release); for (struct LocalJob * job = files.activeJobs; job; job = job->next) if (job->request.dataset == dataset && job->request.request == request) { atomic_store_explicit(&job->cancelled, true, memory_order_release); lgSignalEvent(job->ready); } LG_UNLOCK(files.lock); free(acquisition); freeLocalDatasets(retired); } void clipboardFiles_localReady(uint64_t request) { LG_LOCK(files.lock); for (struct LocalJob * job = files.activeJobs; job; job = job->next) if (job->request.request == request) lgSignalEvent(job->ready); LG_UNLOCK(files.lock); }